From 074bbade3a98597bf9b427e89dbc3f36d17e7f4f Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Mon, 3 Oct 2016 13:26:36 -0700 Subject: [PATCH 001/966] Initial commit --- packages/google-auth/LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 packages/google-auth/LICENSE diff --git a/packages/google-auth/LICENSE b/packages/google-auth/LICENSE new file mode 100644 index 000000000000..0fd73eb55aa4 --- /dev/null +++ b/packages/google-auth/LICENSE @@ -0,0 +1,21 @@ + Copyright 2014 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +Dependent Modules +================= + +This code has the following dependencies +above and beyond the Python standard library: + +httplib2 - MIT License From ad05d8cc884952f0d973f44ecf0dcc7ff3afa568 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Tue, 4 Oct 2016 10:20:36 -0700 Subject: [PATCH 002/966] Basic project setup (#4) --- packages/google-auth/.coveragerc | 14 + packages/google-auth/.gitignore | 31 ++ packages/google-auth/.travis.yml | 23 ++ packages/google-auth/CONTRIBUTING.rst | 78 ++++ packages/google-auth/CONTRIBUTORS.md | 96 +++++ packages/google-auth/MANIFEST.in | 4 + packages/google-auth/README.rst | 53 +++ packages/google-auth/docs/Makefile | 225 +++++++++++ packages/google-auth/docs/conf.py | 352 ++++++++++++++++++ packages/google-auth/docs/index.rst | 22 ++ .../docs/reference/google.auth.rst | 8 + .../google-auth/docs/reference/google.rst | 16 + .../google-auth/docs/reference/modules.rst | 7 + packages/google-auth/google/__init__.py | 22 ++ packages/google-auth/google/auth/__init__.py | 15 + .../google-auth/google/oauth2/__init__.py | 15 + packages/google-auth/pylintrc | 164 ++++++++ packages/google-auth/pylintrc.tests | 165 ++++++++ packages/google-auth/setup.cfg | 2 + packages/google-auth/setup.py | 58 +++ packages/google-auth/tests/__init__.py | 0 packages/google-auth/tox.ini | 49 +++ 22 files changed, 1419 insertions(+) create mode 100644 packages/google-auth/.coveragerc create mode 100644 packages/google-auth/.gitignore create mode 100644 packages/google-auth/.travis.yml create mode 100644 packages/google-auth/CONTRIBUTING.rst create mode 100644 packages/google-auth/CONTRIBUTORS.md create mode 100644 packages/google-auth/MANIFEST.in create mode 100644 packages/google-auth/README.rst create mode 100644 packages/google-auth/docs/Makefile create mode 100644 packages/google-auth/docs/conf.py create mode 100644 packages/google-auth/docs/index.rst create mode 100644 packages/google-auth/docs/reference/google.auth.rst create mode 100644 packages/google-auth/docs/reference/google.rst create mode 100644 packages/google-auth/docs/reference/modules.rst create mode 100644 packages/google-auth/google/__init__.py create mode 100644 packages/google-auth/google/auth/__init__.py create mode 100644 packages/google-auth/google/oauth2/__init__.py create mode 100644 packages/google-auth/pylintrc create mode 100644 packages/google-auth/pylintrc.tests create mode 100644 packages/google-auth/setup.cfg create mode 100644 packages/google-auth/setup.py create mode 100644 packages/google-auth/tests/__init__.py create mode 100644 packages/google-auth/tox.ini diff --git a/packages/google-auth/.coveragerc b/packages/google-auth/.coveragerc new file mode 100644 index 000000000000..494c03f07220 --- /dev/null +++ b/packages/google-auth/.coveragerc @@ -0,0 +1,14 @@ +[run] +branch = True + +[report] +omit = + */samples/* + */conftest.py +exclude_lines = + # Re-enable the standard pragma + pragma: NO COVER + # Ignore debug-only repr + def __repr__ + # Don't complain if tests don't hit defensive assertion code: + raise NotImplementedError diff --git a/packages/google-auth/.gitignore b/packages/google-auth/.gitignore new file mode 100644 index 000000000000..cd35d1544c66 --- /dev/null +++ b/packages/google-auth/.gitignore @@ -0,0 +1,31 @@ +# Build artifacts +*.py[cod] +__pycache__ +*.egg-info/ +build/ +dist/ + +# Documentation-related +docs/_build + +# Test files +.tox/ +.cache/ + +# Django test database +db.sqlite3 + +# Coverage files +.coverage +coverage.xml +nosetests.xml +htmlcov/ + +# Files with private / local data +scripts/local_test_setup +tests/data/key.json +tests/data/key.p12 +tests/data/user-key.json + +# PyCharm configuration: +.idea diff --git a/packages/google-auth/.travis.yml b/packages/google-auth/.travis.yml new file mode 100644 index 000000000000..c95bd9189d4c --- /dev/null +++ b/packages/google-auth/.travis.yml @@ -0,0 +1,23 @@ +language: python +sudo: false +matrix: + include: + - python: 3.5 + env: TOXENV=lint + - python: 3.5 + env: TOXENV=docs + - python: 2.7 + env: TOXENV=py27 + - python: 3.4 + env: TOXENV=py34 + - python: 3.5 + env: TOXENV=py35 + - python: 3.5 + env: TOXENV=cover +cache: + directories: + - ${HOME}/.cache +install: +- pip install --upgrade tox +script: +- tox diff --git a/packages/google-auth/CONTRIBUTING.rst b/packages/google-auth/CONTRIBUTING.rst new file mode 100644 index 000000000000..8581688b5af8 --- /dev/null +++ b/packages/google-auth/CONTRIBUTING.rst @@ -0,0 +1,78 @@ +Contributing +============ + +#. **Please sign one of the contributor license agreements below.** +#. Fork the repo, develop and test your code changes, add docs. +#. Make sure that your commit messages clearly describe the changes. +#. Send a pull request. + +Here are some guidelines for hacking on ``google-auth-library-python``. + +Making changes +-------------- + +A few notes on making changes to ``google-auth-libary-python``. + +- If you've added a new feature or modified an existing feature, be sure to + add or update any applicable documentation in docstrings and in the + documentation (in ``docs/``). You can re-generate the reference documentation + using ``tox -e docgen``. + +- The change must work fully on the following CPython versions: 2.7, + 3.4, and 3.5 across macOS, Linux, and Windows. + +- The codebase *must* have 100% test statement coverage after each commit. + You can test coverage via ``tox -e cover``. + +Testing changes +--------------- + +To test your changes, run unit tests with ``tox``:: + + $ tox -e py27 + $ tox -e py34 + $ tox -e py35 + +Coding Style +------------ + +This library is PEP8 & Pylint compliant. Our Pylint config is defined at +``pylintrc`` for package code and ``pylintrc.tests`` for test code. Use +``tox`` to check for non-compliant code:: + + $ tox -e lint + +Documentation Coverage and Building HTML Documentation +------------------------------------------------------ + +If you fix a bug, and the bug requires an API or behavior modification, all +documentation in this package which references that API or behavior must be +changed to reflect the bug fix, ideally in the same commit that fixes the bug +or adds the feature. + +To build and review docs use ``tox``:: + + $ tox -e docs + +The HTML version of the docs will be built in ``docs/_build/html`` + +Versioning +---------- + +This library follows `Semantic Versioning`_. + +.. _Semantic Versioning: http://semver.org/ + +It is currently in major version zero (``0.y.z``), which means that anything +may change at any time and the public API should not be considered +stable. + +Contributor License Agreements +------------------------------ + +Before we can accept your pull requests you'll need to sign a Contributor License Agreement (CLA): + +- **If you are an individual writing original source code** and **you own the intellectual property**, then you'll need to sign an `individual CLA `__. +- **If you work for a company that wants to allow you to contribute your work**, then you'll need to sign a `corporate CLA `__. + +You can sign these electronically (just scroll to the bottom). After that, we'll be able to accept your pull requests. diff --git a/packages/google-auth/CONTRIBUTORS.md b/packages/google-auth/CONTRIBUTORS.md new file mode 100644 index 000000000000..501db6328958 --- /dev/null +++ b/packages/google-auth/CONTRIBUTORS.md @@ -0,0 +1,96 @@ +# Contribors to oauth2client / google-auth + +## Maintainers + +* [Jon Wayne Parrott](https://github.com/jonparrott) +* [Danny Hermes](https://github.com/dhermes) +* [Brian Watson](https://github.com/bjwatson) + +Previous maintainers: + +* [Nathaniel Manista](https://github.com/nathanielmanistaatgoogle) +* [Craig Citro](https://github.com/craigcitro) +* [Joe Gregorio](https://github.com/jcgregorio) + +## Contributors + +This list is generated from git commit authors. + +* aalexand +* Aaron +* Adam Chainz +* ade@google.com +* Alexandre Vivien +* Ali Afshar +* Andrzej Pragacz +* api.nickm@gmail.com +* Ben Demaree +* Bill Prin +* Brendan McCollam +* Craig Citro +* Dan Ring +* Daniel Hermes +* Danilo Akamine +* daryl herzmann +* dlorenc +* Dominik Miedziński +* dr. Kertész Csaba-Zoltán +* Dustin Farris +* Eddie Warner +* Edwin Amsler +* elibixby +* Emanuele Pucciarelli +* Eric Koleda +* Frederik Creemers +* Guido van Rossum +* Harsh Vardhan +* Herr Kaste +* INADA Naoki +* JacobMoshenko +* Jay Lee +* Jed Hartman +* Jeff Terrace +* Jeffrey Sorensen +* Jeremi Joslin +* Jin Liu +* Joe Beda +* Joe Gregorio +* Johan Euphrosine +* John Asmuth +* John Vandenberg +* Jon Wayne Parrott +* Jose Alcerreca +* KCs +* Keith Maxwell +* Ken Payson +* Kevin Regan +* lraccomando +* Luar Roji +* Luke Blanshard +* Marc Cohen +* Mark Pellegrini +* Martin Trigaux +* Matt McDonald +* Nathan Naze +* Nathaniel Manista +* Orest Bolohan +* Pat Ferate +* Patrick Costello +* Rafe Kaplan +* rahulpaul@google.com +* RM Saksida +* Robert Kaplow +* Robert Spies +* Sergei Trofimovich +* sgomes@google.com +* Simon Cadman +* soltanmm +* Sébastien de Melo +* takuya sato +* thobrla +* Tom Miller +* Tony Aiuto +* Travis Hobrla +* Veres Lajos +* Vivek Seth +* Éamonn McManus diff --git a/packages/google-auth/MANIFEST.in b/packages/google-auth/MANIFEST.in new file mode 100644 index 000000000000..d4dadec1a695 --- /dev/null +++ b/packages/google-auth/MANIFEST.in @@ -0,0 +1,4 @@ +include README.rst +graft google +graft tests +global-exclude *.pyc __pycache__ diff --git a/packages/google-auth/README.rst b/packages/google-auth/README.rst new file mode 100644 index 000000000000..0f0b99984a64 --- /dev/null +++ b/packages/google-auth/README.rst @@ -0,0 +1,53 @@ +Google Auth Python Library (Experimental) +========================================= + +|build| |docs| |pypi| + +This library simplifies using Google's various server-to-server authentication +mechanisms to access Google APIs. + +.. |build| image:: https://travis-ci.org/GoogleCloudPlatform/google-auth-library-python.svg?branch=master + :target: https://travis-ci.org/GoogleCloudPlatform//google-auth-library-python +.. |docs| image:: https://readthedocs.org/projects/google-auth/badge/?version=latest + :target: https://google-auth.readthedocs.io/en/latest/ +.. |pypi| image:: https://img.shields.io/pypi/v//google-auth.svg + :target: https://pypi.python.org/pypi/google-auth + +Installing +---------- + +You can install directly from `GitHub`_:: + + $ pip install git+https://github.com/GoogleCloudPlatform/google-auth-library-python.git + +This package is not currently available from PyPI because it is experimental. + +.. _GitHub: https://github.com/GoogleCloudPlatform/google-auth-library-python + +Documentation +------------- + +Google Auth Python Library has usage and reference documentation at `google-auth.readthedocs.io `_. + +Maintainers +----------- + +- `@jonparrott `_ (Jon Wayne Parrott) +- `@dhermes `_ (Danny Hermes) +- `@bjwatson `_ (Brian Watson) + +Contributing +------------ + +Contributions to this library are always welcome and highly encouraged. + +See `CONTRIBUTING`_ for more information on how to get started. + +.. _CONTRIBUTING: https://github.com/GoogleCloudPlatform/google-auth-library-python/blob/master/CONTRIBUTING.rst + +License +------- + +Apache 2.0 - See `LICENSE`_ for more information. + +.. _LICENSE: https://github.com/GoogleCloudPlatform/google-auth-library-python/blob/master/LICENSE diff --git a/packages/google-auth/docs/Makefile b/packages/google-auth/docs/Makefile new file mode 100644 index 000000000000..4887d62e3015 --- /dev/null +++ b/packages/google-auth/docs/Makefile @@ -0,0 +1,225 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " applehelp to make an Apple Help Book" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " epub3 to make an epub3" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " coverage to run coverage check of the documentation (if enabled)" + @echo " dummy to check syntax errors of document sources" + +.PHONY: clean +clean: + rm -rf $(BUILDDIR)/* + +.PHONY: html +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +.PHONY: dirhtml +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +.PHONY: singlehtml +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +.PHONY: pickle +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +.PHONY: json +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +.PHONY: htmlhelp +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +.PHONY: qthelp +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/google-auth.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/google-auth.qhc" + +.PHONY: applehelp +applehelp: + $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp + @echo + @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." + @echo "N.B. You won't be able to view it unless you put it in" \ + "~/Library/Documentation/Help or install it in your application" \ + "bundle." + +.PHONY: devhelp +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/google-auth" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/google-auth" + @echo "# devhelp" + +.PHONY: epub +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +.PHONY: epub3 +epub3: + $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 + @echo + @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." + +.PHONY: latex +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +.PHONY: latexpdf +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +.PHONY: latexpdfja +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +.PHONY: text +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +.PHONY: man +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +.PHONY: texinfo +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +.PHONY: info +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +.PHONY: gettext +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +.PHONY: changes +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +.PHONY: linkcheck +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +.PHONY: doctest +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +.PHONY: coverage +coverage: + $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage + @echo "Testing of coverage in the sources finished, look at the " \ + "results in $(BUILDDIR)/coverage/python.txt." + +.PHONY: xml +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +.PHONY: pseudoxml +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." + +.PHONY: dummy +dummy: + $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy + @echo + @echo "Build finished. Dummy builder generates no files." diff --git a/packages/google-auth/docs/conf.py b/packages/google-auth/docs/conf.py new file mode 100644 index 000000000000..bf271652c7b8 --- /dev/null +++ b/packages/google-auth/docs/conf.py @@ -0,0 +1,352 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# google-auth documentation build configuration file, created by +# sphinx-quickstart on Thu Sep 22 12:50:15 2016. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.intersphinx', + 'sphinx.ext.viewcode', + 'sphinx.ext.napoleon', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The encoding of source files. +# +# source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = 'google-auth' +copyright = '2016, Google, Inc.' +author = 'Google, Inc.' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '0.0.1a' +# The full version, including alpha/beta/rc tags. +release = '0.0.1a' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +# +# today = '' +# +# Else, today_fmt is used as the format for a strftime call. +# +# today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +# +# default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +# +# add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +# +# add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +# +# show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +# modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +# keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'alabaster' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +# html_theme_path = [] + +# The name for this set of Sphinx documents. +# " v documentation" by default. +# +# html_title = 'google-auth v0.0.1a' + +# A shorter title for the navigation bar. Default is the same as html_title. +# +# html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +# +# html_logo = None + +# The name of an image file (relative to this directory) to use as a favicon of +# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +# +# html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +# +# html_extra_path = [] + +# If not None, a 'Last updated on:' timestamp is inserted at every page +# bottom, using the given strftime format. +# The empty string is equivalent to '%b %d, %Y'. +# +# html_last_updated_fmt = None + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +# +# html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +# +# html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +# +# html_additional_pages = {} + +# If false, no module index is generated. +# +# html_domain_indices = True + +# If false, no index is generated. +# +# html_use_index = True + +# If true, the index is split into individual pages for each letter. +# +# html_split_index = False + +# If true, links to the reST sources are added to the pages. +# +# html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +# +# html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +# +# html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +# +# html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +# html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr', 'zh' +# +# html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# 'ja' uses this config value. +# 'zh' user can custom change `jieba` dictionary path. +# +# html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +# +# html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'google-authdoc' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'google-auth.tex', 'google-auth Documentation', + 'Google, Inc.', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +# +# latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +# +# latex_use_parts = False + +# If true, show page references after internal links. +# +# latex_show_pagerefs = False + +# If true, show URL addresses after external links. +# +# latex_show_urls = False + +# Documents to append as an appendix to all manuals. +# +# latex_appendices = [] + +# It false, will not define \strong, \code, itleref, \crossref ... but only +# \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added +# packages. +# +# latex_keep_old_macro_names = True + +# If false, no module index is generated. +# +# latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'google-auth', 'google-auth Documentation', + [author], 1) +] + +# If true, show URL addresses after external links. +# +# man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'google-auth', 'google-auth Documentation', + author, 'google-auth', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +# +# texinfo_appendices = [] + +# If false, no module index is generated. +# +# texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +# +# texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +# +# texinfo_no_detailmenu = False + + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = {'https://docs.python.org/3.5': None} + +# Autodoc config +autoclass_content = 'both' +autodoc_member_order = 'bysource' diff --git a/packages/google-auth/docs/index.rst b/packages/google-auth/docs/index.rst new file mode 100644 index 000000000000..e7a17e3302aa --- /dev/null +++ b/packages/google-auth/docs/index.rst @@ -0,0 +1,22 @@ +Welcome to ``google-auth``'s documentation! +=========================================== + +Contents: + +.. toctree:: + :maxdepth: 2 + + reference/modules + +Reference documentation +======================= + +The :doc:`reference documentation ` details the complete API for :mod:`google.auth` and :mod:`google.oauth2`. + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/packages/google-auth/docs/reference/google.auth.rst b/packages/google-auth/docs/reference/google.auth.rst new file mode 100644 index 000000000000..0533449e0831 --- /dev/null +++ b/packages/google-auth/docs/reference/google.auth.rst @@ -0,0 +1,8 @@ +google.auth package +=================== + +.. automodule:: google.auth + :members: + :undoc-members: + :show-inheritance: + diff --git a/packages/google-auth/docs/reference/google.rst b/packages/google-auth/docs/reference/google.rst new file mode 100644 index 000000000000..58c6da449afa --- /dev/null +++ b/packages/google-auth/docs/reference/google.rst @@ -0,0 +1,16 @@ +google package +============== + +.. automodule:: google + :members: + :undoc-members: + :show-inheritance: + +Subpackages +----------- + +.. toctree:: + + google.auth + google.oauth2 + diff --git a/packages/google-auth/docs/reference/modules.rst b/packages/google-auth/docs/reference/modules.rst new file mode 100644 index 000000000000..b1816cc9b408 --- /dev/null +++ b/packages/google-auth/docs/reference/modules.rst @@ -0,0 +1,7 @@ +google +====== + +.. toctree:: + :maxdepth: 4 + + google diff --git a/packages/google-auth/google/__init__.py b/packages/google-auth/google/__init__.py new file mode 100644 index 000000000000..a35569c36339 --- /dev/null +++ b/packages/google-auth/google/__init__.py @@ -0,0 +1,22 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google namespace package.""" + +try: + import pkg_resources + pkg_resources.declare_namespace(__name__) +except ImportError: + import pkgutil + __path__ = pkgutil.extend_path(__path__, __name__) diff --git a/packages/google-auth/google/auth/__init__.py b/packages/google-auth/google/auth/__init__.py new file mode 100644 index 000000000000..01e343112901 --- /dev/null +++ b/packages/google-auth/google/auth/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google Auth Library for Python.""" diff --git a/packages/google-auth/google/oauth2/__init__.py b/packages/google-auth/google/oauth2/__init__.py new file mode 100644 index 000000000000..6d3ee7f98db8 --- /dev/null +++ b/packages/google-auth/google/oauth2/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google OAuth 2.0 Library for Python.""" diff --git a/packages/google-auth/pylintrc b/packages/google-auth/pylintrc new file mode 100644 index 000000000000..1a47c025b7f8 --- /dev/null +++ b/packages/google-auth/pylintrc @@ -0,0 +1,164 @@ +[MASTER] +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS,.git,.cache,.tox,.nox + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +# DEFAULT: load-plugins= +# RATIONALE: We want to make sure our docstrings match the objects +# they document. +load-plugins=pylint.extensions.check_docs + +[MESSAGES CONTROL] +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once).You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use"--disable=all --enable=classes +# --disable=W" +# +# RATIONALE: +# - maybe-no-member: bi-modal functions confuse pylint type inference. +# - no-member: indirections in protobuf-generated code +# - protected-access: helpers use '_foo' of classes from generated code. +# - similarities: 'Bucket' and 'Blob' define 'metageneration' and 'owner' with +# identical implementation but different docstrings. +# - star-args: standard Python idioms for varargs: +# ancestor = Query().filter(*order_props) +# - redefined-variable-type: This error is overzealous and complains at e.g. +# if some_prop: +# return int(value) +# else: +# return float(value) +# - import-error: imports are checked via tests. +# - wrong-import-position: This error is overzealous. It assumes imports are +# completed whenever something non-trivial is +# defined, e.g. +# try: +# from foo import Bar +# except ImportError: +# class Bar(object): +# """Hi everyone""" +# and thus causes subsequent imports to be +# diagnosed as out-of-order. +# - no-name-in-module: Error gives a lot of false positives for names which +# are defined dynamically. Also, any truly missing names +# will be detected by our 100% code coverage. +# - locally-disabled: Allow us to make exceptions in edge cases, notably where +# pylint doesn't recognize inherited properties and methods +# and gives unused-argument errors. +# TEMPORARILY DISABLE AND SHOULD BE REMOVED: +# - fixme: disabled until 1.0 +# +disable = + import-star-module-level, + old-octal-literal, + oct-method, + print-statement, + unpacking-in-except, + parameter-unpacking, + backtick, + old-raise-syntax, + old-ne-operator, + long-suffix, + dict-view-method, + dict-iter-method, + metaclass-assignment, + next-method-called, + raising-string, + indexing-exception, + raw_input-builtin, + long-builtin, + file-builtin, + execfile-builtin, + coerce-builtin, + cmp-builtin, + buffer-builtin, + basestring-builtin, + apply-builtin, + filter-builtin-not-iterating, + using-cmp-argument, + useless-suppression, + range-builtin-not-iterating, + suppressed-message, + no-absolute-import, + old-division, + cmp-method, + reload-builtin, + zip-builtin-not-iterating, + intern-builtin, + unichr-builtin, + reduce-builtin, + standarderror-builtin, + unicode-builtin, + xrange-builtin, + coerce-method, + delslice-method, + getslice-method, + setslice-method, + input-builtin, + round-builtin, + hex-method, + nonzero-method, + map-builtin-not-iterating, + maybe-no-member, + no-member, + protected-access, + similarities, + star-args, + redefined-variable-type, + import-error, + wrong-import-position, + no-name-in-module, + locally-disabled, + fixme + + +[REPORTS] +# Tells whether to display a full report or only the messages +# RATIONALE: noisy +reports=no + +[BASIC] +# Regular expression matching correct method names +# DEFAULT: method-rgx=[a-z_][a-z0-9_]{2,30}$ +# RATIONALE: Some methods have longer names to be more descriptive or precise, +# especially those that implemented wordy RFCs. +method-rgx=[a-z_][a-z0-9_]{2,40}$ + +# Regular expression matching correct function names +# DEFAULT function-rgx=[a-z_][a-z0-9_]{2,30}$ +# RATIONALE: Some methods have longer names to be more descriptive or precise, +# especially those that implemented wordy RFCs. +function-rgx=[a-z_][a-z0-9_]{2,40}$ + +[TYPECHECK] +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis. It +# supports qualified module names, as well as Unix pattern matching. +# DEFAULT: ignored-modules= +# RATIONALE: six aliases stuff for compatibility. +# google.protobuf fixes up namespace package "late". +ignored-modules = six, google.protobuf + + +[DESIGN] +# Minimum number of public methods for a class (see R0903). +# DEFAULT: min-public-methods=2 +# RATIONALE: context mgrs may have *no* public methods +min-public-methods=0 + +# Maximum number of arguments for function / method +# DEFAULT: max-args=5 +# RATIONALE: Many credentials classes take a lot of parameters. +max-args = 10 + +# Maximum number of attributes for a class (see R0902). +# DEFAULT: max-attributes=7 +# RATIONALE: Many credentials need to track lots of properties. +max-attributes=15 diff --git a/packages/google-auth/pylintrc.tests b/packages/google-auth/pylintrc.tests new file mode 100644 index 000000000000..73b2766c84cb --- /dev/null +++ b/packages/google-auth/pylintrc.tests @@ -0,0 +1,165 @@ +[MASTER] +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS,.git,.cache,.tox,.nox + +[MESSAGES CONTROL] +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once).You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use"--disable=all --enable=classes +# --disable=W" +# +# RATIONALE: +# - maybe-no-member: bi-modal functions confuse pylint type inference. +# - no-member: indirections in protobuf-generated code +# - protected-access: helpers use '_foo' of classes from generated code. +# - similarities: 'Bucket' and 'Blob' define 'metageneration' and 'owner' with +# identical implementation but different docstrings. +# - star-args: standard Python idioms for varargs: +# ancestor = Query().filter(*order_props) +# - redefined-variable-type: This error is overzealous and complains at e.g. +# if some_prop: +# return int(value) +# else: +# return float(value) +# - import-error: imports are checked via tests. +# - wrong-import-position: This error is overzealous. It assumes imports are +# completed whenever something non-trivial is +# defined, e.g. +# try: +# from foo import Bar +# except ImportError: +# class Bar(object): +# """Hi everyone""" +# and thus causes subsequent imports to be +# diagnosed as out-of-order. +# - no-name-in-module: Error gives a lot of false positives for names which +# are defined dynamically. Also, any truly missing names +# will be detected by our 100% code coverage. +# - locally-disabled: Allow us to make exceptions in edge cases, notably where +# pylint doesn't recognize inherited properties and methods +# and gives unused-argument errors. +disable = + import-star-module-level, + old-octal-literal, + oct-method, + print-statement, + unpacking-in-except, + parameter-unpacking, + backtick, + old-raise-syntax, + old-ne-operator, + long-suffix, + dict-view-method, + dict-iter-method, + metaclass-assignment, + next-method-called, + raising-string, + indexing-exception, + raw_input-builtin, + long-builtin, + file-builtin, + execfile-builtin, + coerce-builtin, + cmp-builtin, + buffer-builtin, + basestring-builtin, + apply-builtin, + filter-builtin-not-iterating, + using-cmp-argument, + useless-suppression, + range-builtin-not-iterating, + suppressed-message, + no-absolute-import, + old-division, + cmp-method, + reload-builtin, + zip-builtin-not-iterating, + intern-builtin, + unichr-builtin, + reduce-builtin, + standarderror-builtin, + unicode-builtin, + xrange-builtin, + coerce-method, + delslice-method, + getslice-method, + setslice-method, + input-builtin, + round-builtin, + hex-method, + nonzero-method, + map-builtin-not-iterating, + maybe-no-member, + no-member, + protected-access, + similarities, + star-args, + redefined-variable-type, + import-error, + wrong-import-position, + no-name-in-module, + locally-disabled, + missing-docstring, + redefined-outer-name, + no-self-use, + too-many-locals, + too-many-public-methods, + abstract-method, + method-hidden, + unused-argument + + +[REPORTS] +# Tells whether to display a full report or only the messages +# RATIONALE: noisy +reports=no + +[BASIC] +# Good variable names which should always be accepted, separated by a comma +# DEFAULT: good-names=i,j,k,ex,Run,_ +# RATIONALE: 'fh' is a well-known file handle variable name. +good-names = i, j, k, ex, Run, _, + fh, + +# Regular expression matching correct method names +# DEFAULT: method-rgx=[a-z_][a-z0-9_]{2,30}$ +# RATIONALE: Some methods have longer names to be precise against wordy RFCs. +method-rgx=[a-z_][a-z0-9_]{2,80}$ + +# Regular expression matching correct function names +# DEFAULT function-rgx=[a-z_][a-z0-9_]{2,30}$ +# RATIONALE: Some test methods have long descriptive names. +function-rgx=[a-z_][a-z0-9_]{2,80}$ + +[TYPECHECK] +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis. It +# supports qualified module names, as well as Unix pattern matching. +# DEFAULT: ignored-modules= +# RATIONALE: six aliases stuff for compatibility. +# google.protobuf fixes up namespace package "late". +ignored-modules = six, google.protobuf + + +[DESIGN] +# Minimum number of public methods for a class (see R0903). +# DEFAULT: min-public-methods=2 +# RATIONALE: context mgrs may have *no* public methods +min-public-methods=0 + +# Maximum number of arguments for function / method +# DEFAULT: max-args=5 +# RATIONALE: Many credentials classes take a lot of parameters. +max-args = 10 + +# Maximum number of attributes for a class (see R0902). +# DEFAULT: max-attributes=7 +# RATIONALE: Many credentials need to track lots of properties. +max-attributes=15 diff --git a/packages/google-auth/setup.cfg b/packages/google-auth/setup.cfg new file mode 100644 index 000000000000..2a9acf13daa9 --- /dev/null +++ b/packages/google-auth/setup.cfg @@ -0,0 +1,2 @@ +[bdist_wheel] +universal = 1 diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py new file mode 100644 index 000000000000..32c5d02119fd --- /dev/null +++ b/packages/google-auth/setup.py @@ -0,0 +1,58 @@ +# Copyright 2014 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from setuptools import find_packages +from setuptools import setup + + +DEPENDENCIES = ( + 'pyasn1>=0.1.7', + 'pyasn1-modules>=0.0.5', + 'rsa>=3.1.4', + 'six>=1.9.0', +) + + +with open('README.rst', 'r') as fh: + long_description = fh.read() + + +setup( + name='google-auth', + version='0.0.1', + author='Google Cloud Platform', + author_email='jonwayne+google-auth@google.com', + description='Google Authentication Library', + long_description=long_description, + packages=find_packages(exclude='tests'), + namespace_packages=('google',), + install_requires=DEPENDENCIES, + license='Apache 2.0', + keywords='google auth oauth client', + classifiers=( + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Development Status :: 3 - Alpha', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: Apache Software License', + 'Operating System :: POSIX', + 'Operating System :: Microsoft :: Windows', + 'Operating System :: MacOS :: MacOS X', + 'Operating System :: OS Independent', + 'Topic :: Internet :: WWW/HTTP', + ), +) diff --git a/packages/google-auth/tests/__init__.py b/packages/google-auth/tests/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/google-auth/tox.ini b/packages/google-auth/tox.ini new file mode 100644 index 000000000000..d2a5f1f8b9df --- /dev/null +++ b/packages/google-auth/tox.ini @@ -0,0 +1,49 @@ +[tox] +envlist = lint,py27,py34,py35,pypy,cover + +[testenv] +deps = + flask + mock + pytest + pytest-cov + pytest-localserver + urllib3 +commands = + py.test --cov=google.auth --cov=google.oauth2 --cov=tests {posargs:tests} + +[testenv:cover] +basepython = python3.5 +commands = + py.test --cov=google.auth --cov=google.oauth2 --cov=tests --cov-report= tests + coverage report --show-missing --fail-under=100 +deps = + {[testenv]deps} + +[testenv:docgen] +basepython = python3.5 +deps = + {[testenv]deps} + sphinx +commands = sphinx-apidoc --output-dir docs/reference --separate --module-first google + +[testenv:docs] +basepython = python3.5 +deps = + {[testenv]deps} + sphinx +commands = make -C docs html + +[testenv:lint] +basepython = python3.5 +commands = + flake8 \ + --import-order-style=google \ + --application-import-names="google,tests" \ + google tests + pylint --rcfile pylintrc google + pylint --rcfile pylintrc.tests tests +deps = + flake8 + flake8-import-order + pylint From b7865c5f8513c29bbf1decb4afb9ecdfbe3c3585 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Tue, 4 Oct 2016 14:19:01 -0700 Subject: [PATCH 003/966] Add google.auth.crypt (#6) --- .../docs/reference/google.auth.crypt.rst | 7 + .../docs/reference/google.auth.rst | 7 + .../docs/reference/google.oauth2.rst | 8 + packages/google-auth/google/auth/_helpers.py | 65 +++++ packages/google-auth/google/auth/crypt.py | 229 ++++++++++++++++++ .../google-auth/tests/data/other_cert.pem | 33 +++ .../tests/data/pem_from_pkcs12.pem | 32 +++ .../google-auth/tests/data/privatekey.p12 | Bin 0 -> 2452 bytes .../google-auth/tests/data/privatekey.pem | 27 +++ .../google-auth/tests/data/privatekey.pub | 8 + .../google-auth/tests/data/public_cert.pem | 19 ++ packages/google-auth/tests/test__helpers.py | 50 ++++ packages/google-auth/tests/test_crypt.py | 186 ++++++++++++++ packages/google-auth/tox.ini | 4 +- 14 files changed, 674 insertions(+), 1 deletion(-) create mode 100644 packages/google-auth/docs/reference/google.auth.crypt.rst create mode 100644 packages/google-auth/docs/reference/google.oauth2.rst create mode 100644 packages/google-auth/google/auth/_helpers.py create mode 100644 packages/google-auth/google/auth/crypt.py create mode 100644 packages/google-auth/tests/data/other_cert.pem create mode 100644 packages/google-auth/tests/data/pem_from_pkcs12.pem create mode 100644 packages/google-auth/tests/data/privatekey.p12 create mode 100644 packages/google-auth/tests/data/privatekey.pem create mode 100644 packages/google-auth/tests/data/privatekey.pub create mode 100644 packages/google-auth/tests/data/public_cert.pem create mode 100644 packages/google-auth/tests/test__helpers.py create mode 100644 packages/google-auth/tests/test_crypt.py diff --git a/packages/google-auth/docs/reference/google.auth.crypt.rst b/packages/google-auth/docs/reference/google.auth.crypt.rst new file mode 100644 index 000000000000..7f8c0f837dc8 --- /dev/null +++ b/packages/google-auth/docs/reference/google.auth.crypt.rst @@ -0,0 +1,7 @@ +google.auth.crypt module +======================== + +.. automodule:: google.auth.crypt + :members: + :undoc-members: + :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.auth.rst b/packages/google-auth/docs/reference/google.auth.rst index 0533449e0831..4c39cb96b58c 100644 --- a/packages/google-auth/docs/reference/google.auth.rst +++ b/packages/google-auth/docs/reference/google.auth.rst @@ -6,3 +6,10 @@ google.auth package :undoc-members: :show-inheritance: +Submodules +---------- + +.. toctree:: + + google.auth.crypt + diff --git a/packages/google-auth/docs/reference/google.oauth2.rst b/packages/google-auth/docs/reference/google.oauth2.rst new file mode 100644 index 000000000000..fb67d279ca6d --- /dev/null +++ b/packages/google-auth/docs/reference/google.oauth2.rst @@ -0,0 +1,8 @@ +google.oauth2 package +===================== + +.. automodule:: google.oauth2 + :members: + :undoc-members: + :show-inheritance: + diff --git a/packages/google-auth/google/auth/_helpers.py b/packages/google-auth/google/auth/_helpers.py new file mode 100644 index 000000000000..2d3b6534c204 --- /dev/null +++ b/packages/google-auth/google/auth/_helpers.py @@ -0,0 +1,65 @@ +# Copyright 2015 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Helper functions for commonly used utilities.""" + +import six + + +def to_bytes(value, encoding='utf-8'): + """Converts a string value to bytes, if necessary. + + Unfortunately, ``six.b`` is insufficient for this task since in + Python 2 because it does not modify ``unicode`` objects. + + Args: + value (Union[str, bytes]): The value to be converted. + encoding (str): The encoding to use to convert unicode to bytes. + Defaults to "utf-8". + + Returns: + bytes: The original value converted to bytes (if unicode) or as + passed in if it started out as bytes. + + Raises: + ValueError: If the value could not be converted to bytes. + """ + result = (value.encode(encoding) + if isinstance(value, six.text_type) else value) + if isinstance(result, six.binary_type): + return result + else: + raise ValueError('{0!r} could not be converted to bytes'.format(value)) + + +def from_bytes(value): + """Converts bytes to a string value, if necessary. + + Args: + value (Union[str, bytes]): The value to be converted. + + Returns: + str: The original value converted to unicode (if bytes) or as passed in + if it started out as unicode. + + Raises: + ValueError: If the value could not be converted to unicode. + """ + result = (value.decode('utf-8') + if isinstance(value, six.binary_type) else value) + if isinstance(result, six.text_type): + return result + else: + raise ValueError( + '{0!r} could not be converted to unicode'.format(value)) diff --git a/packages/google-auth/google/auth/crypt.py b/packages/google-auth/google/auth/crypt.py new file mode 100644 index 000000000000..d347600f09be --- /dev/null +++ b/packages/google-auth/google/auth/crypt.py @@ -0,0 +1,229 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Cryptography helpers for verifying and signing messages. + +Uses the ``rsa``, ``pyasn1`` and ``pyasn1_modules`` packages +to parse PEM files storing PKCS#1 or PKCS#8 keys as well as +certificates. There is no support for p12 files. + +The simplest way to verify signatures is using :func:`verify_signature`:: + + cert = open('certs.pem').read() + valid = crypt.verify_signature(message, signature, cert) + +If you're going to verify many messages with the same certificate, you can use +:class:`Verifier`:: + + cert = open('certs.pem').read() + verifier = crypt.Verifier.from_string(cert) + valid = verifier.verify(message, signature) + + +To sign messages use :class:`Signer` with a private key:: + + private_key = open('private_key.pem').read() + signer = crypt.Signer(private_key) + signature = signer.sign(message) + +""" + +from pyasn1.codec.der import decoder +from pyasn1_modules import pem +from pyasn1_modules.rfc2459 import Certificate +from pyasn1_modules.rfc5208 import PrivateKeyInfo +import rsa +import six + +from google.auth import _helpers + +_POW2 = (128, 64, 32, 16, 8, 4, 2, 1) +_CERTIFICATE_MARKER = b'-----BEGIN CERTIFICATE-----' +_PKCS1_MARKER = ('-----BEGIN RSA PRIVATE KEY-----', + '-----END RSA PRIVATE KEY-----') +_PKCS8_MARKER = ('-----BEGIN PRIVATE KEY-----', + '-----END PRIVATE KEY-----') +_PKCS8_SPEC = PrivateKeyInfo() + + +def _bit_list_to_bytes(bit_list): + """Converts an iterable of 1s and 0s to bytes. + + Combines the list 8 at a time, treating each group of 8 bits + as a single byte. + + Args: + bit_list (Sequence): Sequence of 1s and 0s. + + Returns: + bytes: The decoded bytes. + """ + num_bits = len(bit_list) + byte_vals = bytearray() + for start in six.moves.xrange(0, num_bits, 8): + curr_bits = bit_list[start:start + 8] + char_val = sum(val * digit + for val, digit in six.moves.zip(_POW2, curr_bits)) + byte_vals.append(char_val) + return bytes(byte_vals) + + +class Verifier(object): + """This object is used to verify cryptographic signatures. + + Args: + public_key (rsa.key.PublicKey): The public key used to verify + signatures. + """ + + def __init__(self, public_key): + self._pubkey = public_key + + def verify(self, message, signature): + """Verifies a message against a cryptographic signature. + + Args: + message (Union[str, bytes]): The message to verify. + signature (Union[str, bytes]): The cryptography signature to check. + + Returns: + bool: True if message was signed by the private key associated + with the public key that this object was constructed with. + """ + message = _helpers.to_bytes(message) + try: + return rsa.pkcs1.verify(message, signature, self._pubkey) + except (ValueError, rsa.pkcs1.VerificationError): + return False + + @classmethod + def from_string(cls, public_key): + """Construct an Verifier instance from a public key or public + certificate string. + + Args: + public_key (Union[str, bytes]): The public key in PEM format or the + x509 public key certificate. + + Returns: + Verifier: The constructed verifier. + + Raises: + ValueError: If the public_key can't be parsed. + """ + public_key = _helpers.to_bytes(public_key) + is_x509_cert = _CERTIFICATE_MARKER in public_key + + # If this is a certificate, extract the public key info. + if is_x509_cert: + der = rsa.pem.load_pem(public_key, 'CERTIFICATE') + asn1_cert, remaining = decoder.decode(der, asn1Spec=Certificate()) + if remaining != b'': + raise ValueError('Unused bytes', remaining) + + cert_info = asn1_cert['tbsCertificate']['subjectPublicKeyInfo'] + key_bytes = _bit_list_to_bytes(cert_info['subjectPublicKey']) + pubkey = rsa.PublicKey.load_pkcs1(key_bytes, 'DER') + else: + pubkey = rsa.PublicKey.load_pkcs1(public_key, 'PEM') + return cls(pubkey) + + +def verify_signature(message, signature, certs): + """Verify a cryptographic signature. + + Checks that the provided ``signature`` was generated from ``bytes`` using + the private key associated with the ``cert``. + + Args: + message (Union[str, bytes]): The plaintext message. + signature (Union[str, bytes]): The cryptographic signature to check. + certs (Union[Sequence, str, bytes]): The certificate or certificates + to use to check the signature. + + Returns: + bool: True if the signature is valid, otherwise False. + """ + if isinstance(certs, (six.text_type, six.binary_type)): + certs = [certs] + + for cert in certs: + verifier = Verifier.from_string(cert) + if verifier.verify(message, signature): + return True + return False + + +class Signer(object): + """Signs messages with a private key. + + Args: + private_key (rsa.key.PrivateKey): The private key to sign with. + key_id (str): Optional key ID used to identify this private key. This + can be useful to associate the private key with its associated + public key or certificate. + """ + + def __init__(self, private_key, key_id=None): + self._key = private_key + self.key_id = key_id + + def sign(self, message): + """Signs a message. + + Args: + message (Union[str, bytes]): The message to be signed. + + Returns: + bytes: The signature of the message for the given key. + """ + message = _helpers.to_bytes(message) + return rsa.pkcs1.sign(message, self._key, 'SHA-256') + + @classmethod + def from_string(cls, key, key_id=None): + """Construct an Signer instance from a private key in PEM format. + + Args: + key (str): Private key in PEM format. + key_id (str): An optional key id used to identify the private key. + + Returns: + Signer: The constructed signer. + + Raises: + ValueError: If the key cannot be parsed as PKCS#1 or PKCS#8 in + PEM format. + """ + key = _helpers.from_bytes(key) # PEM expects str in Python 3 + marker_id, key_bytes = pem.readPemBlocksFromFile( + six.StringIO(key), _PKCS1_MARKER, _PKCS8_MARKER) + + # Key is in pkcs1 format. + if marker_id == 0: + private_key = rsa.key.PrivateKey.load_pkcs1( + key_bytes, format='DER') + # Key is in pkcs8. + elif marker_id == 1: + key_info, remaining = decoder.decode( + key_bytes, asn1Spec=_PKCS8_SPEC) + if remaining != b'': + raise ValueError('Unused bytes', remaining) + private_key_info = key_info.getComponentByName('privateKey') + private_key = rsa.key.PrivateKey.load_pkcs1( + private_key_info.asOctets(), format='DER') + else: + raise ValueError('No key could be detected.') + + return cls(private_key, key_id=key_id) diff --git a/packages/google-auth/tests/data/other_cert.pem b/packages/google-auth/tests/data/other_cert.pem new file mode 100644 index 000000000000..6895d1e7bfaf --- /dev/null +++ b/packages/google-auth/tests/data/other_cert.pem @@ -0,0 +1,33 @@ +-----BEGIN CERTIFICATE----- +MIIFtTCCA52gAwIBAgIJAPBsLZmNGfKtMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQwHhcNMTYwOTIxMDI0NTEyWhcNMTYxMDIxMDI0NTEyWjBF +MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50 +ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC +CgKCAgEAsiMC7mTsmUXwZoYlT4aHY1FLw8bxIXC+z3IqA+TY1WqfbeiZRo8MA5Zx +lTTxYMKPCZUE1XBc7jvD8GJhWIj6pToPYHn73B01IBkLBxq4kF1yV2Z7DVmkvc6H +EcxXXq8zkCx0j6XOfiI4+qkXnuQn8cvrk8xfhtnMMZM7iVm6VSN93iRP/8ey6xuL +XTHrDX7ukoRce1hpT8O+15GXNrY0irhhYQz5xKibNCJF3EjV28WMry8y7I8uYUFU +RWDiQawwK9ec1zhZ94v92+GZDlPevmcFmSERKYQ0NsKcT0Y3lGuGnaExs8GyOpnC +oksu4YJGXQjg7lkv4MxzsNbRqmCkUwxw1Mg6FP0tsCNsw9qTrkvWCRA9zp/aU+sZ +IBGh1t4UGCub8joeQFvHxvr/3F7mH/dyvCjA34u0Lo1VPx+jYUIi9i0odltMspDW +xOpjqdGARZYmlJP5Au9q5cQjPMcwS/EBIb8cwNl32mUE6WnFlep+38mNR/FghIjO +ViAkXuKQmcHe6xppZAoHFsO/t3l4Tjek5vNW7erI1rgrFku/fvkIW/G8V1yIm/+Q +F+CE4maQzCJfhftpkhM/sPC/FuLNBmNE8BHVX8y58xG4is/cQxL4Z9TsFIw0C5+3 +uTrFW9D0agysahMVzPGtCqhDQqJdIJrBQqlS6bztpzBA8zEI0skCAwEAAaOBpzCB +pDAdBgNVHQ4EFgQUz/8FmW6TfqXyNJZr7rhc+Tn5sKQwdQYDVR0jBG4wbIAUz/8F +mW6TfqXyNJZr7rhc+Tn5sKShSaRHMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpT +b21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGSCCQDw +bC2ZjRnyrTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4ICAQCQmrcfhurX +riR3Q0Y+nq040/3dJIAJXjyI9CEtxaU0nzCNTng7PwgZ0CKmCelQfInuwWFwBSHS +6kBfC1rgJeFnjnTt8a3RCgRlIgUr9NCdPSEccB7TurobwPJ2h6cJjjR8urcb0CXh +CEMvPneyPj0xUFY8vVKXMGWahz/kyfwIiVqcX/OtMZ29fUu1onbWl71g2gVLtUZl +sECdZ+AC/6HDCVpYIVETMl1T7N/XyqXZQiDLDNRDeZhnapz8w9fsW1KVujAZLNQR +pVnw2qa2UK1dSf2FHX+lQU5mFSYM4vtwaMlX/LgfdLZ9I796hFh619WwTVz+LO2N +vHnwBMabld3XSPuZRqlbBulDQ07Vbqdjv8DYSLA2aKI4ZkMMKuFLG/oS28V2ZYmv +/KpGEs5UgKY+P9NulYpTDwCU/6SomuQpP795wbG6sm7Hzq82r2RmB61GupNRGeqi +pXKsy69T388zBxYu6zQrosXiDl5YzaViH7tm0J7opye8dCWjjpnahki0vq2znti7 +6cWla2j8Xz1glvLz+JI/NCOMfxUInb82T7ijo80N0VJ2hzf7p2GxRZXAxAV9knLI +nM4F5TLjSd7ZhOOZ7ni/eZFueTMisWfypt2nc41whGjHMX/Zp1kPfhB4H2bLKIX/ +lSrwNr3qbGTEJX8JqpDBNVAd96XkMvDNyA== +-----END CERTIFICATE----- diff --git a/packages/google-auth/tests/data/pem_from_pkcs12.pem b/packages/google-auth/tests/data/pem_from_pkcs12.pem new file mode 100644 index 000000000000..2d77e10c1fd1 --- /dev/null +++ b/packages/google-auth/tests/data/pem_from_pkcs12.pem @@ -0,0 +1,32 @@ +Bag Attributes + friendlyName: key + localKeyID: 22 7E 04 FC 64 48 20 83 1E C1 BD E3 F5 2F 44 7D EA 99 A5 BC +Key Attributes: +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDh6PSnttDsv+vi +tUZTP1E3hVBah6PUGDWZhYgNiyW8quTWCmPvBmCR2YzuhUrY5+CtKP8UJOQico+p +oJHSAPsrzSr6YsGs3c9SQOslBmm9Fkh9/f/GZVTVZ6u5AsUmOcVvZ2q7Sz8Vj/aR +aIm0EJqRe9cQ5vvN9sg25rIv4xKwIZJ1VixKWJLmpCmDINqn7xvl+ldlUmSr3aGt +w21uSDuEJhQlzO3yf2FwJMkJ9SkCm9oVDXyl77OnKXj5bOQ/rojbyGeIxDJSUDWE +GKyRPuqKi6rSbwg6h2G/Z9qBJkqM5NNTbGRIFz/9/LdmmwvtaqCxlLtD7RVEryAp ++qTGDk5hAgMBAAECggEBAMYYfNDEYpf4A2SdCLne/9zrrfZ0kphdUkL48MDPj5vN +TzTRj6f9s5ixZ/+QKn3hdwbguCx13QbH5mocP0IjUhyqoFFHYAWxyyaZfpjM8tO4 +QoEYxby3BpjLe62UXESUzChQSytJZFwIDXKcdIPNO3zvVzufEJcfG5no2b9cIvsG +Dy6J1FNILWxCtDIqBM+G1B1is9DhZnUDgn0iKzINiZmh1I1l7k/4tMnozVIKAfwo +f1kYjG/d2IzDM02mTeTElz3IKeNriaOIYTZgI26xLJxTkiFnBV4JOWFAZw15X+yR ++DrjGSIkTfhzbLa20Vt3AFM+LFK0ZoXT2dRnjbYPjQECgYEA+9XJFGwLcEX6pl1p +IwXAjXKJdju9DDn4lmHTW0Pbw25h1EXONwm/NPafwsWmPll9kW9IwsxUQVUyBC9a +c3Q7rF1e8ai/qqVFRIZof275MI82ciV2Mw8Hz7FPAUyoju5CvnjAEH4+irt1VE/7 +SgdvQ1gDBQFegS69ijdz+cOhFxkCgYEA5aVoseMy/gIlsCvNPyw9+Jz/zBpKItX0 +jGzdF7lhERRO2cursujKaoHntRckHcE3P/Z4K565bvVq+VaVG0T/BcBKPmPHrLmY +iuVXidltW7Jh9/RCVwb5+BvqlwlC470PEwhqoUatY/fPJ74srztrqJHvp1L29FT5 +sdmlJW8YwokCgYAUa3dMgp5C0knKp5RY1KSSU5E11w4zKZgwiWob4lq1dAPWtHpO +GCo63yyBHImoUJVP75gUw4Cpc4EEudo5tlkIVuHV8nroGVKOhd9/Rb5K47Hke4kk +Brn5a0Ues9qPDF65Fw1ryPDFSwHufjXAAO5SpZZJF51UGDgiNvDedbBgMQKBgHSk +t7DjPhtW69234eCckD2fQS5ijBV1p2lMQmCygGM0dXiawvN02puOsCqDPoz+fxm2 +DwPY80cw0M0k9UeMnBxHt25JMDrDan/iTbxu++T/jlNrdebOXFlxlI5y3c7fULDS +LZcNVzTXwhjlt7yp6d0NgzTyJw2ju9BiREfnTiRBAoGBAOPHrTOnPyjO+bVcCPTB +WGLsbBd77mVPGIuL0XGrvbVYPE8yIcNbZcthd8VXL/38Ygy8SIZh2ZqsrU1b5WFa +XUMLnGEODSS8x/GmW3i3KeirW5OxBNjfUzEF4XkJP8m41iTdsQEXQf9DdUY7X+CB +VL5h7N0VstYhGgycuPpcIUQa +-----END PRIVATE KEY----- diff --git a/packages/google-auth/tests/data/privatekey.p12 b/packages/google-auth/tests/data/privatekey.p12 new file mode 100644 index 0000000000000000000000000000000000000000..c369ecb6e664cafddf5a817ce0d50c2059867042 GIT binary patch literal 2452 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 Kf`EbF -keyout privatekey.pem +# $ openssl rsa -in privatekey.pem -pubout -out privatekey.pub + +with open(os.path.join(DATA_DIR, 'privatekey.pem'), 'rb') as fh: + PRIVATE_KEY_BYTES = fh.read() + PKCS1_KEY_BYTES = PRIVATE_KEY_BYTES + +with open(os.path.join(DATA_DIR, 'privatekey.pub'), 'rb') as fh: + PUBLIC_KEY_BYTES = fh.read() + +with open(os.path.join(DATA_DIR, 'public_cert.pem'), 'rb') as fh: + PUBLIC_CERT_BYTES = fh.read() + +# To generate other_cert.pem: +# $ openssl req -new -newkey rsa:1024 -x509 -nodes -out other_cert.pem + +with open(os.path.join(DATA_DIR, 'other_cert.pem'), 'rb') as fh: + OTHER_CERT_BYTES = fh.read() + +# To generate pem_from_pkcs12.pem and privatekey.p12: +# $ openssl pkcs12 -export -out privatekey.p12 -inkey privatekey.pem \ +# > -in public_cert.pem +# $ openssl pkcs12 -in privatekey.p12 -nocerts -nodes \ +# > -out pem_from_pkcs12.pem + +with open(os.path.join(DATA_DIR, 'pem_from_pkcs12.pem'), 'rb') as fh: + PKCS8_KEY_BYTES = fh.read() + +with open(os.path.join(DATA_DIR, 'privatekey.p12'), 'rb') as fh: + PKCS12_KEY_BYTES = fh.read() + + +def test_verify_signature(): + to_sign = b'foo' + signer = crypt.Signer.from_string(PRIVATE_KEY_BYTES) + signature = signer.sign(to_sign) + + assert crypt.verify_signature( + to_sign, signature, PUBLIC_CERT_BYTES) + + # List of certs + assert crypt.verify_signature( + to_sign, signature, [OTHER_CERT_BYTES, PUBLIC_CERT_BYTES]) + + +def test_verify_signature_failure(): + to_sign = b'foo' + signer = crypt.Signer.from_string(PRIVATE_KEY_BYTES) + signature = signer.sign(to_sign) + + assert not crypt.verify_signature( + to_sign, signature, OTHER_CERT_BYTES) + + +class TestVerifier(object): + def test_verify_success(self): + to_sign = b'foo' + signer = crypt.Signer.from_string(PRIVATE_KEY_BYTES) + actual_signature = signer.sign(to_sign) + + verifier = crypt.Verifier.from_string(PUBLIC_KEY_BYTES) + assert verifier.verify(to_sign, actual_signature) + + def test_verify_unicode_success(self): + to_sign = u'foo' + signer = crypt.Signer.from_string(PRIVATE_KEY_BYTES) + actual_signature = signer.sign(to_sign) + + verifier = crypt.Verifier.from_string(PUBLIC_KEY_BYTES) + assert verifier.verify(to_sign, actual_signature) + + def test_verify_failure(self): + verifier = crypt.Verifier.from_string(PUBLIC_KEY_BYTES) + bad_signature1 = b'' + assert not verifier.verify(b'foo', bad_signature1) + bad_signature2 = b'a' + assert not verifier.verify(b'foo', bad_signature2) + + def test_from_string_pub_key(self): + verifier = crypt.Verifier.from_string(PUBLIC_KEY_BYTES) + assert isinstance(verifier, crypt.Verifier) + assert isinstance(verifier._pubkey, rsa.key.PublicKey) + + def test_from_string_pub_key_unicode(self): + public_key = _helpers.from_bytes(PUBLIC_KEY_BYTES) + verifier = crypt.Verifier.from_string(public_key) + assert isinstance(verifier, crypt.Verifier) + assert isinstance(verifier._pubkey, rsa.key.PublicKey) + + def test_from_string_pub_cert(self): + verifier = crypt.Verifier.from_string(PUBLIC_CERT_BYTES) + assert isinstance(verifier, crypt.Verifier) + assert isinstance(verifier._pubkey, rsa.key.PublicKey) + + def test_from_string_pub_cert_unicode(self): + public_cert = _helpers.from_bytes(PUBLIC_CERT_BYTES) + verifier = crypt.Verifier.from_string(public_cert) + assert isinstance(verifier, crypt.Verifier) + assert isinstance(verifier._pubkey, rsa.key.PublicKey) + + def test_from_string_pub_cert_failure(self): + cert_bytes = PUBLIC_CERT_BYTES + true_der = rsa.pem.load_pem(cert_bytes, 'CERTIFICATE') + with mock.patch('rsa.pem.load_pem', + return_value=true_der + b'extra') as load_pem: + with pytest.raises(ValueError): + crypt.Verifier.from_string(cert_bytes) + load_pem.assert_called_once_with(cert_bytes, 'CERTIFICATE') + + +class TestSigner(object): + def test_from_string_pkcs1(self): + signer = crypt.Signer.from_string(PKCS1_KEY_BYTES) + assert isinstance(signer, crypt.Signer) + assert isinstance(signer._key, rsa.key.PrivateKey) + + def test_from_string_pkcs1_unicode(self): + key_bytes = _helpers.from_bytes(PKCS1_KEY_BYTES) + signer = crypt.Signer.from_string(key_bytes) + assert isinstance(signer, crypt.Signer) + assert isinstance(signer._key, rsa.key.PrivateKey) + + def test_from_string_pkcs8(self): + signer = crypt.Signer.from_string(PKCS8_KEY_BYTES) + assert isinstance(signer, crypt.Signer) + assert isinstance(signer._key, rsa.key.PrivateKey) + + def test_from_string_pkcs8_extra_bytes(self): + key_bytes = PKCS8_KEY_BYTES + _, pem_bytes = pem.readPemBlocksFromFile( + six.StringIO(_helpers.from_bytes(key_bytes)), + crypt._PKCS8_MARKER) + + with mock.patch('pyasn1.codec.der.decoder.decode') as mock_decode: + key_info, remaining = None, 'extra' + mock_decode.return_value = (key_info, remaining) + with pytest.raises(ValueError): + crypt.Signer.from_string(key_bytes) + # Verify mock was called. + mock_decode.assert_called_once_with( + pem_bytes, asn1Spec=crypt._PKCS8_SPEC) + + def test_from_string_pkcs8_unicode(self): + key_bytes = _helpers.from_bytes(PKCS8_KEY_BYTES) + signer = crypt.Signer.from_string(key_bytes) + assert isinstance(signer, crypt.Signer) + assert isinstance(signer._key, rsa.key.PrivateKey) + + def test_from_string_pkcs12(self): + with pytest.raises(ValueError): + crypt.Signer.from_string(PKCS12_KEY_BYTES) + + def test_from_string_bogus_key(self): + key_bytes = 'bogus-key' + with pytest.raises(ValueError): + crypt.Signer.from_string(key_bytes) diff --git a/packages/google-auth/tox.ini b/packages/google-auth/tox.ini index d2a5f1f8b9df..5f2df1c92531 100644 --- a/packages/google-auth/tox.ini +++ b/packages/google-auth/tox.ini @@ -25,7 +25,9 @@ basepython = python3.5 deps = {[testenv]deps} sphinx -commands = sphinx-apidoc --output-dir docs/reference --separate --module-first google +commands = + rm -r docs/reference + sphinx-apidoc --output-dir docs/reference --separate --module-first google [testenv:docs] basepython = python3.5 From f7c96634dce23fe32871e16bcb1a2853220e8b72 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Thu, 6 Oct 2016 09:27:44 -0700 Subject: [PATCH 004/966] Add google.auth.jwt (#7) --- .../docs/reference/google.auth.jwt.rst | 7 + .../docs/reference/google.auth.rst | 1 + packages/google-auth/google/auth/_helpers.py | 25 ++ packages/google-auth/google/auth/jwt.py | 235 ++++++++++++++++++ packages/google-auth/tests/test__helpers.py | 12 + packages/google-auth/tests/test_jwt.py | 189 ++++++++++++++ 6 files changed, 469 insertions(+) create mode 100644 packages/google-auth/docs/reference/google.auth.jwt.rst create mode 100644 packages/google-auth/google/auth/jwt.py create mode 100644 packages/google-auth/tests/test_jwt.py diff --git a/packages/google-auth/docs/reference/google.auth.jwt.rst b/packages/google-auth/docs/reference/google.auth.jwt.rst new file mode 100644 index 000000000000..79e3080d0811 --- /dev/null +++ b/packages/google-auth/docs/reference/google.auth.jwt.rst @@ -0,0 +1,7 @@ +google.auth.jwt module +====================== + +.. automodule:: google.auth.jwt + :members: + :undoc-members: + :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.auth.rst b/packages/google-auth/docs/reference/google.auth.rst index 4c39cb96b58c..57a16b475985 100644 --- a/packages/google-auth/docs/reference/google.auth.rst +++ b/packages/google-auth/docs/reference/google.auth.rst @@ -12,4 +12,5 @@ Submodules .. toctree:: google.auth.crypt + google.auth.jwt diff --git a/packages/google-auth/google/auth/_helpers.py b/packages/google-auth/google/auth/_helpers.py index 2d3b6534c204..0a62209d6d5e 100644 --- a/packages/google-auth/google/auth/_helpers.py +++ b/packages/google-auth/google/auth/_helpers.py @@ -14,9 +14,34 @@ """Helper functions for commonly used utilities.""" + +import calendar +import datetime + import six +def utcnow(): + """Returns the current UTC datetime. + + Returns: + datetime: The current time in UTC. + """ + return datetime.datetime.utcnow() + + +def datetime_to_secs(value): + """Convert a datetime object to the number of seconds since the UNIX epoch. + + Args: + value (datetime): The datetime to convert. + + Returns: + int: The number of seconds since the UNIX epoch. + """ + return calendar.timegm(value.utctimetuple()) + + def to_bytes(value, encoding='utf-8'): """Converts a string value to bytes, if necessary. diff --git a/packages/google-auth/google/auth/jwt.py b/packages/google-auth/google/auth/jwt.py new file mode 100644 index 000000000000..5349e2982a43 --- /dev/null +++ b/packages/google-auth/google/auth/jwt.py @@ -0,0 +1,235 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""JSON Web Tokens + +Provides support for creating (encoding) and verifying (decoding) JWTs, +especially JWTs generated and consumed by Google infrastructure. + +See `rfc7519`_ for more details on JWTs. + +To encode a JWT:: + + from google.auth import crypto + from google.auth import jwt + + signer = crypt.Signer(private_key) + payload = {'some': 'payload'} + encoded = jwt.encode(signer, payload) + +To decode a JWT and verify claims:: + + claims = jwt.decode(encoded, certs=public_certs) + +You can also skip verification:: + + claims = jwt.decode(encoded, verify=False) + +.. _rfc7519: https://tools.ietf.org/html/rfc7519 + +""" + +import base64 +import collections +import json + +from google.auth import crypt +from google.auth import _helpers + + +_DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in sections +_CLOCK_SKEW_SECS = 300 # 5 minutes in seconds + + +def encode(signer, payload, header=None, key_id=None): + """Make a signed JWT. + + Args: + signer (google.auth.crypt.Signer): The signer used to sign the JWT. + payload (Mapping): The JWT payload. + header (Mapping): Additional JWT header payload. + key_id (str): The key id to add to the JWT header. If the + signer has a key id it will be used as the default. If this is + specified it will override the signer's key id. + + Returns: + bytes: The encoded JWT. + """ + if header is None: + header = {} + + if key_id is None: + key_id = signer.key_id + + header.update({'typ': 'JWT', 'alg': 'RS256'}) + + if key_id is not None: + header['kid'] = key_id + + segments = [ + base64.urlsafe_b64encode(json.dumps(header).encode('utf-8')), + base64.urlsafe_b64encode(json.dumps(payload).encode('utf-8')), + ] + + signing_input = b'.'.join(segments) + signature = signer.sign(signing_input) + segments.append(base64.urlsafe_b64encode(signature)) + + return b'.'.join(segments) + + +def _decode_jwt_segment(encoded_section): + """Decodes a single JWT segment.""" + section_bytes = base64.urlsafe_b64decode(encoded_section) + try: + return json.loads(section_bytes.decode('utf-8')) + except ValueError: + raise ValueError('Can\'t parse segment: {0}'.format(section_bytes)) + + +def _unverified_decode(token): + """Decodes a token and does no verification. + + Args: + token (Union[str, bytes]): The encoded JWT. + + Returns: + Tuple(str, str, str, str): header, payload, signed_section, and + signature. + + Raises: + ValueError: if there are an incorrect amount of segments in the token. + """ + token = _helpers.to_bytes(token) + + if token.count(b'.') != 2: + raise ValueError( + 'Wrong number of segments in token: {0}'.format(token)) + + encoded_header, encoded_payload, signature = token.split(b'.') + signed_section = encoded_header + b'.' + encoded_payload + signature = base64.urlsafe_b64decode(signature) + + # Parse segments + header = _decode_jwt_segment(encoded_header) + payload = _decode_jwt_segment(encoded_payload) + + return header, payload, signed_section, signature + + +def decode_header(token): + """Return the decoded header of a token. + + No verification is done. This is useful to extract the key id from + the header in order to acquire the appropriate certificate to verify + the token. + + Args: + token (Union[str, bytes]): the encoded JWT. + + Returns: + Mapping: The decoded JWT header. + """ + header, _, _, _ = _unverified_decode(token) + return header + + +def _verify_iat_and_exp(payload): + """Verifies the iat (Issued At) and exp (Expires) claims in a token + payload. + + Args: + payload (mapping): The JWT payload. + + Raises: + ValueError: if any checks failed. + """ + now = _helpers.datetime_to_secs(_helpers.utcnow()) + + # Make sure the iat and exp claims are present + for key in ('iat', 'exp'): + if key not in payload: + raise ValueError( + 'Token does not contain required claim {}'.format(key)) + + # Make sure the token wasn't issued in the future + iat = payload['iat'] + earliest = iat - _CLOCK_SKEW_SECS + if now < earliest: + raise ValueError('Token used too early, {} < {}'.format(now, iat)) + + # Make sure the token wasn't issue in the past + exp = payload['exp'] + latest = exp + _CLOCK_SKEW_SECS + if latest < now: + raise ValueError('Token expired, {} < {}'.format(latest, now)) + + +def decode(token, certs=None, verify=True, audience=None): + """Decode and verify a JWT. + + Args: + token (string): The encoded JWT. + certs (Union[str, bytes, Mapping]): The certificate used to + validate. If bytes or string, it must the the public key + certificate in PEM format. If a mapping, it must be a mapping of + key IDs to public key certificates in PEM format. The mapping must + contain the same key ID that's specified in the token's header. + verify (bool): Whether to perform signature and claim validation. + Verification is done by default. + audience (str): The audience claim, 'aud', that this JWT should + contain. If None then the JWT's 'aud' parameter is not verified. + + Returns: + Mapping: The deserialized JSON payload in the JWT. + + Raises: + ValueError: if any verification checks failed. + """ + header, payload, signed_section, signature = _unverified_decode(token) + + if not verify: + return payload + + # If certs is specified as a dictionary of key IDs to certificates, then + # use the certificate identified by the key ID in the token header. + if isinstance(certs, collections.Mapping): + key_id = header.get('kid') + if key_id: + if key_id not in certs: + raise ValueError( + 'Certificate for key id {} not found.'.format(key_id)) + certs_to_check = [certs[key_id]] + # If there's no key id in the header, check against all of the certs. + else: + certs_to_check = certs.values() + else: + certs_to_check = certs + + # Verify that the signature matches the message. + if not crypt.verify_signature(signed_section, signature, certs_to_check): + raise ValueError('Could not verify token signature.') + + # Verify the issued at and created times in the payload. + _verify_iat_and_exp(payload) + + # Check audience. + if audience is not None: + claim_audience = payload.get('aud') + if audience != claim_audience: + raise ValueError( + 'Token has wrong audience {}, expected {}'.format( + claim_audience, audience)) + + return payload diff --git a/packages/google-auth/tests/test__helpers.py b/packages/google-auth/tests/test__helpers.py index b7e0bab84770..c2bc4a7331bd 100644 --- a/packages/google-auth/tests/test__helpers.py +++ b/packages/google-auth/tests/test__helpers.py @@ -12,12 +12,24 @@ # See the License for the specific language governing permissions and # limitations under the License. +import datetime import pytest from google.auth import _helpers +def test_utcnow(): + assert isinstance(_helpers.utcnow(), datetime.datetime) + + +def test_datetime_to_secs(): + assert _helpers.datetime_to_secs( + datetime.datetime(1970, 1, 1)) == 0 + assert _helpers.datetime_to_secs( + datetime.datetime(1990, 5, 29)) == 643939200 + + def test_to_bytes_with_bytes(): value = b'bytes-val' assert _helpers.to_bytes(value) == value diff --git a/packages/google-auth/tests/test_jwt.py b/packages/google-auth/tests/test_jwt.py new file mode 100644 index 000000000000..69628e5c9c96 --- /dev/null +++ b/packages/google-auth/tests/test_jwt.py @@ -0,0 +1,189 @@ +# Copyright 2014 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import base64 +import datetime +import os + +import pytest + +from google.auth import _helpers +from google.auth import crypt +from google.auth import jwt + + +DATA_DIR = os.path.join(os.path.dirname(__file__), 'data') + +with open(os.path.join(DATA_DIR, 'privatekey.pem'), 'rb') as fh: + PRIVATE_KEY_BYTES = fh.read() + +with open(os.path.join(DATA_DIR, 'public_cert.pem'), 'rb') as fh: + PUBLIC_CERT_BYTES = fh.read() + +with open(os.path.join(DATA_DIR, 'other_cert.pem'), 'rb') as fh: + OTHER_CERT_BYTES = fh.read() + + +@pytest.fixture +def signer(): + return crypt.Signer.from_string(PRIVATE_KEY_BYTES, '1') + + +def test_encode_basic(signer): + test_payload = {'test': 'value'} + encoded = jwt.encode(signer, test_payload) + header, payload, _, _ = jwt._unverified_decode(encoded) + assert payload == test_payload + assert header == {'typ': 'JWT', 'alg': 'RS256', 'kid': signer.key_id} + + +def test_encode_extra_headers(signer): + encoded = jwt.encode(signer, {}, header={'extra': 'value'}) + header = jwt.decode_header(encoded) + assert header == { + 'typ': 'JWT', 'alg': 'RS256', 'kid': signer.key_id, 'extra': 'value'} + + +@pytest.fixture +def token_factory(signer): + def factory(claims=None, key_id=None): + now = _helpers.datetime_to_secs(_helpers.utcnow()) + payload = { + 'aud': 'audience@example.com', + 'iat': now, + 'exp': now + 300, + 'user': 'billy bob', + 'metadata': {'meta': 'data'} + } + payload.update(claims or {}) + + # False is specified to remove the signer's key id for testing + # headers without key ids. + if key_id is False: + signer.key_id = None + key_id = None + + return jwt.encode(signer, payload, key_id=key_id) + return factory + + +def test_decode_valid(token_factory): + payload = jwt.decode(token_factory(), certs=PUBLIC_CERT_BYTES) + assert payload['aud'] == 'audience@example.com' + assert payload['user'] == 'billy bob' + assert payload['metadata']['meta'] == 'data' + + +def test_decode_valid_with_audience(token_factory): + payload = jwt.decode( + token_factory(), certs=PUBLIC_CERT_BYTES, + audience='audience@example.com') + assert payload['aud'] == 'audience@example.com' + assert payload['user'] == 'billy bob' + assert payload['metadata']['meta'] == 'data' + + +def test_decode_valid_unverified(token_factory): + payload = jwt.decode(token_factory(), certs=OTHER_CERT_BYTES, verify=False) + assert payload['aud'] == 'audience@example.com' + assert payload['user'] == 'billy bob' + assert payload['metadata']['meta'] == 'data' + + +def test_decode_bad_token_wrong_number_of_segments(): + with pytest.raises(ValueError) as excinfo: + jwt.decode('1.2', PUBLIC_CERT_BYTES) + assert excinfo.match(r'Wrong number of segments') + + +def test_decode_bad_token_not_base64(): + with pytest.raises((ValueError, TypeError)) as excinfo: + jwt.decode('1.2.3', PUBLIC_CERT_BYTES) + assert excinfo.match(r'Incorrect padding') + + +def test_decode_bad_token_not_json(): + token = b'.'.join([base64.urlsafe_b64encode(b'123!')] * 3) + with pytest.raises(ValueError) as excinfo: + jwt.decode(token, PUBLIC_CERT_BYTES) + assert excinfo.match(r'Can\'t parse segment') + + +def test_decode_bad_token_no_iat_or_exp(signer): + token = jwt.encode(signer, {'test': 'value'}) + with pytest.raises(ValueError) as excinfo: + jwt.decode(token, PUBLIC_CERT_BYTES) + assert excinfo.match(r'Token does not contain required claim') + + +def test_decode_bad_token_too_early(token_factory): + token = token_factory(claims={ + 'iat': _helpers.datetime_to_secs( + _helpers.utcnow() + datetime.timedelta(hours=1)) + }) + with pytest.raises(ValueError) as excinfo: + jwt.decode(token, PUBLIC_CERT_BYTES) + assert excinfo.match(r'Token used too early') + + +def test_decode_bad_token_expired(token_factory): + token = token_factory(claims={ + 'exp': _helpers.datetime_to_secs( + _helpers.utcnow() - datetime.timedelta(hours=1)) + }) + with pytest.raises(ValueError) as excinfo: + jwt.decode(token, PUBLIC_CERT_BYTES) + assert excinfo.match(r'Token expired') + + +def test_decode_bad_token_wrong_audience(token_factory): + token = token_factory() + audience = 'audience2@example.com' + with pytest.raises(ValueError) as excinfo: + jwt.decode(token, PUBLIC_CERT_BYTES, audience=audience) + assert excinfo.match(r'Token has wrong audience') + + +def test_decode_wrong_cert(token_factory): + with pytest.raises(ValueError) as excinfo: + jwt.decode(token_factory(), OTHER_CERT_BYTES) + assert excinfo.match(r'Could not verify token signature') + + +def test_decode_multicert_bad_cert(token_factory): + certs = {'1': OTHER_CERT_BYTES, '2': PUBLIC_CERT_BYTES} + with pytest.raises(ValueError) as excinfo: + jwt.decode(token_factory(), certs) + assert excinfo.match(r'Could not verify token signature') + + +def test_decode_no_cert(token_factory): + certs = {'2': PUBLIC_CERT_BYTES} + with pytest.raises(ValueError) as excinfo: + jwt.decode(token_factory(), certs) + assert excinfo.match(r'Certificate for key id 1 not found') + + +def test_decode_no_key_id(token_factory): + token = token_factory(key_id=False) + certs = {'2': PUBLIC_CERT_BYTES} + payload = jwt.decode(token, certs) + assert payload['user'] == 'billy bob' + + +def test_roundtrip_explicit_key_id(token_factory): + token = token_factory(key_id='3') + certs = {'2': OTHER_CERT_BYTES, '3': PUBLIC_CERT_BYTES} + payload = jwt.decode(token, certs) + assert payload['user'] == 'billy bob' From 2d2ef3da707e8e0203019e58129f5e33e18fd716 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Thu, 6 Oct 2016 10:37:33 -0700 Subject: [PATCH 005/966] Add google.auth.transport and google.auth.exceptions (#9) --- .../google-auth/google/auth/exceptions.py | 32 +++++++ .../google/auth/transport/__init__.py | 85 +++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 packages/google-auth/google/auth/exceptions.py create mode 100644 packages/google-auth/google/auth/transport/__init__.py diff --git a/packages/google-auth/google/auth/exceptions.py b/packages/google-auth/google/auth/exceptions.py new file mode 100644 index 000000000000..dedde9e211e2 --- /dev/null +++ b/packages/google-auth/google/auth/exceptions.py @@ -0,0 +1,32 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Exceptions used in the google.auth package.""" + + +class GoogleAuthError(Exception): + """Base class for all google.auth errors.""" + + +class TransportError(Exception): + """Used to indicate an error occurred during an HTTP request.""" + + +class RefreshError(GoogleAuthError): + """Used to indicate that an refreshing the credentials' access token + failed.""" + + +class DefaultCredentialsError(GoogleAuthError): + """Used to indicate that acquiring default credentials failed.""" diff --git a/packages/google-auth/google/auth/transport/__init__.py b/packages/google-auth/google/auth/transport/__init__.py new file mode 100644 index 000000000000..32de8f17bf77 --- /dev/null +++ b/packages/google-auth/google/auth/transport/__init__.py @@ -0,0 +1,85 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Transport - HTTP client library support. + +:mod:`google.auth` is designed to work with various HTTP client libraries such +as urllib3 and requests. In order to work across these libraries with different +interfaces some abstraction is needed. + +This module provides two interfaces that are implemented by transport adapters +to support HTTP libraries. :class:`Request` defines the interface expected by +:mod:`google.auth` to make requests. :class:`Response` defines the interface +for the return value of :class:`Request`. +""" + +import abc + +import six + + +@six.add_metaclass(abc.ABCMeta) +class Response(object): + """HTTP Response data.""" + + @abc.abstractproperty + def status(self): + """int: The HTTP status code.""" + raise NotImplementedError('status must be implemented.') + + @abc.abstractproperty + def headers(self): + """Mapping: The HTTP response headers.""" + raise NotImplementedError('headers must be implemented.') + + @abc.abstractproperty + def data(self): + """bytes: The response body.""" + raise NotImplementedError('data must be implemented.') + + +@six.add_metaclass(abc.ABCMeta) +class Request(object): + """Interface for a callable that makes HTTP requests. + + Specific transport implementations should provide an implementation of + this that adapts their specific request / response API. + """ + + @abc.abstractmethod + def __call__(self, url, method='GET', body=None, headers=None, + timeout=None, **kwargs): + """Make an HTTP request. + + Args: + url (str): The URI to be requested. + method (str): The HTTP method to use for the request. Defaults + to 'GET'. + body (bytes): The payload / body in HTTP request. + headers (Mapping): Request headers. + timeout (Optional(int)): The number of seconds to wait for a + response from the server. If not specified or if None, the + transport-specific default timeout will be used. + kwargs: Additionally arguments passed on to the transport's + request method. + + Returns: + Response: The HTTP response. + + Raises: + google.auth.exceptions.TransportError: If any exception occurred. + """ + # pylint: disable=redundant-returns-doc, missing-raises-doc + # (pylint doesn't play well with abstract docstrings.) + raise NotImplementedError('__call__ must be implemented.') From a686678e44905e37e82e8a74c2737222ae59d420 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Fri, 7 Oct 2016 14:02:53 -0700 Subject: [PATCH 006/966] Add compute engine metadata client (#11) --- packages/google-auth/google/auth/_helpers.py | 47 +++++ .../google/auth/compute_engine/__init__.py | 13 ++ .../google/auth/compute_engine/_metadata.py | 178 ++++++++++++++++++ packages/google-auth/pylintrc.tests | 2 +- .../tests/compute_engine/__init__.py | 0 .../tests/compute_engine/test__metadata.py | 159 ++++++++++++++++ packages/google-auth/tests/test__helpers.py | 33 ++++ 7 files changed, 431 insertions(+), 1 deletion(-) create mode 100644 packages/google-auth/google/auth/compute_engine/__init__.py create mode 100644 packages/google-auth/google/auth/compute_engine/_metadata.py create mode 100644 packages/google-auth/tests/compute_engine/__init__.py create mode 100644 packages/google-auth/tests/compute_engine/test__metadata.py diff --git a/packages/google-auth/google/auth/_helpers.py b/packages/google-auth/google/auth/_helpers.py index 0a62209d6d5e..d4bf8f1b41d2 100644 --- a/packages/google-auth/google/auth/_helpers.py +++ b/packages/google-auth/google/auth/_helpers.py @@ -19,6 +19,7 @@ import datetime import six +from six.moves import urllib def utcnow(): @@ -88,3 +89,49 @@ def from_bytes(value): else: raise ValueError( '{0!r} could not be converted to unicode'.format(value)) + + +def update_query(url, params, remove=None): + """Updates a URL's query parameters. + + Replaces any current values if they are already present in the URL. + + Args: + url (str): The URL to update. + params (Mapping[str, str]): A mapping of query parameter + keys to values. + remove (Sequence[str]): Parameters to remove from the query string. + + Returns: + str: The URL with updated query parameters. + + Examples: + + >>> url = 'http://example.com?a=1' + >>> update_query(url, {'a': '2'}) + http://example.com?a=2 + >>> update_query(url, {'b': '3'}) + http://example.com?a=1&b=3 + >> update_query(url, {'b': '3'}, remove=['a']) + http://example.com?b=3 + + """ + if remove is None: + remove = [] + + # Split the URL into parts. + parts = urllib.parse.urlparse(url) + # Parse the query string. + query_params = urllib.parse.parse_qs(parts.query) + # Update the query parameters with the new parameters. + query_params.update(params) + # Remove any values specified in remove. + query_params = { + key: value for key, value + in six.iteritems(query_params) + if key not in remove} + # Re-encoded the query string. + new_query = urllib.parse.urlencode(query_params, doseq=True) + # Unsplit the url. + new_parts = parts._replace(query=new_query) + return urllib.parse.urlunparse(new_parts) diff --git a/packages/google-auth/google/auth/compute_engine/__init__.py b/packages/google-auth/google/auth/compute_engine/__init__.py new file mode 100644 index 000000000000..e3a7f6c35405 --- /dev/null +++ b/packages/google-auth/google/auth/compute_engine/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/packages/google-auth/google/auth/compute_engine/_metadata.py b/packages/google-auth/google/auth/compute_engine/_metadata.py new file mode 100644 index 000000000000..28d57f66eec4 --- /dev/null +++ b/packages/google-auth/google/auth/compute_engine/_metadata.py @@ -0,0 +1,178 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Provides helper methods for talking to the Compute Engine metadata server. + +See https://cloud.google.com/compute/docs/metadata for more details. +""" + +import datetime +import json +import os + +from six.moves import http_client +from six.moves.urllib import parse as urlparse + +from google.auth import _helpers +from google.auth import exceptions + +_METADATA_ROOT = 'http://metadata.google.internal/computeMetadata/v1/' + +# This is used to ping the metadata server, it avoids the cost of a DNS +# lookup. +_METADATA_IP_ROOT = 'http://169.254.169.254' +_METADATA_FLAVOR_HEADER = 'metdata-flavor' +_METADATA_FLAVOR_VALUE = 'Google' +_METADATA_HEADERS = {_METADATA_FLAVOR_HEADER: _METADATA_FLAVOR_VALUE} + +# Timeout in seconds to wait for the GCE metadata server when detecting the +# GCE environment. +try: + _METADATA_DEFAULT_TIMEOUT = int(os.getenv('GCE_METADATA_TIMEOUT', 3)) +except ValueError: # pragma: NO COVER + _METADATA_DEFAULT_TIMEOUT = 3 + + +def ping(request, timeout=_METADATA_DEFAULT_TIMEOUT): + """Checks to see if the metadata server is available. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + timeout (int): How long to wait for the metadata server to respond. + + Returns: + bool: True if the metadata server is reachable, False otherwise. + """ + # NOTE: The explicit ``timeout`` is a workaround. The underlying + # issue is that resolving an unknown host on some networks will take + # 20-30 seconds; making this timeout short fixes the issue, but + # could lead to false negatives in the event that we are on GCE, but + # the metadata resolution was particularly slow. The latter case is + # "unlikely". + try: + response = request( + url=_METADATA_IP_ROOT, method='GET', headers=_METADATA_HEADERS, + timeout=timeout) + + metadata_flavor = response.headers.get(_METADATA_FLAVOR_HEADER) + return (response.status == http_client.OK and + metadata_flavor == _METADATA_FLAVOR_VALUE) + + except exceptions.TransportError: + return False + + +def get(request, path, root=_METADATA_ROOT, recursive=False): + """Fetch a resource from the metadata server. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + path (str): The resource to retrieve. For example, + ``'instance/service-accounts/defualt'``. + root (str): The full path to the metadata server root. + recursive (bool): Whether to do a recursive query of metadata. See + https://cloud.google.com/compute/docs/metadata#aggcontents for more + details. + + Returns: + Union[Mapping, str]: If the metadata server returns JSON, a mapping of + the decoded JSON is return. Otherwise, the response content is + returned as a string. + + Raises: + google.auth.exceptions.TransportError: if an error occurred while + retrieving metadata. + """ + base_url = urlparse.urljoin(root, path) + query_params = {} + + if recursive: + query_params['recursive'] = 'true' + + url = _helpers.update_query(base_url, query_params) + + response = request(url=url, method='GET', headers=_METADATA_HEADERS) + + if response.status == http_client.OK: + content = _helpers.from_bytes(response.data) + if response.headers['content-type'] == 'application/json': + try: + return json.loads(content) + except ValueError: + raise exceptions.TransportError( + 'Received invalid JSON from the Google Compute Engine' + 'metadata service: {:.20}'.format(content)) + else: + return content + else: + raise exceptions.TransportError( + 'Failed to retrieve {} from the Google Compute Engine' + 'metadata service. Status: {} Response:\n{}'.format( + url, response.status, response.data), response) + + +def get_service_account_info(request, service_account='default'): + """Get information about a service account from the metadata server. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + service_account (str): The string 'default' or a service account email + address. The determines which service account for which to acquire + information. + + Returns: + Mapping: The service account's information, for example:: + + { + 'email': '...', + 'scopes': ['scope', ...], + 'aliases': ['default', '...'] + } + + Raises: + google.auth.exceptions.TransportError: if an error occurred while + retrieving metadata. + """ + return get( + request, + 'instance/service-accounts/{0}/'.format(service_account), + recursive=True) + + +def get_service_account_token(request, service_account='default'): + """Get the OAuth 2.0 access token for a service account. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + service_account (str): The string 'default' or a service account email + address. The determines which service account for which to acquire + an access token. + + Returns: + Union[str, datetime]: The access token and its expiration. + + Raises: + google.auth.exceptions.TransportError: if an error occurred while + retrieving metadata. + """ + token_json = get( + request, + 'instance/service-accounts/{0}/token'.format(service_account)) + token_expiry = _helpers.utcnow() + datetime.timedelta( + seconds=token_json['expires_in']) + return token_json['access_token'], token_expiry diff --git a/packages/google-auth/pylintrc.tests b/packages/google-auth/pylintrc.tests index 73b2766c84cb..de1964f3df5c 100644 --- a/packages/google-auth/pylintrc.tests +++ b/packages/google-auth/pylintrc.tests @@ -125,7 +125,7 @@ reports=no # DEFAULT: good-names=i,j,k,ex,Run,_ # RATIONALE: 'fh' is a well-known file handle variable name. good-names = i, j, k, ex, Run, _, - fh, + fh # Regular expression matching correct method names # DEFAULT: method-rgx=[a-z_][a-z0-9_]{2,30}$ diff --git a/packages/google-auth/tests/compute_engine/__init__.py b/packages/google-auth/tests/compute_engine/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/google-auth/tests/compute_engine/test__metadata.py b/packages/google-auth/tests/compute_engine/test__metadata.py new file mode 100644 index 000000000000..4cc3e55e3296 --- /dev/null +++ b/packages/google-auth/tests/compute_engine/test__metadata.py @@ -0,0 +1,159 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import datetime +import json + +import mock +import pytest +from six.moves import http_client + +from google.auth import _helpers +from google.auth import exceptions +from google.auth.compute_engine import _metadata + +PATH = 'instance/service-accounts/default' + + +@pytest.fixture +def mock_request(): + request_mock = mock.Mock() + + def set_response(data, status=http_client.OK, headers=None): + response = mock.Mock() + response.status = status + response.data = _helpers.to_bytes(data) + response.headers = headers or {} + request_mock.return_value = response + return request_mock + + yield set_response + + +def test_ping_success(mock_request): + request_mock = mock_request('', headers=_metadata._METADATA_HEADERS) + + assert _metadata.ping(request_mock) + + request_mock.assert_called_once_with( + method='GET', + url=_metadata._METADATA_IP_ROOT, + headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT) + + +def test_ping_failure_bad_flavor(mock_request): + request_mock = mock_request( + '', headers={_metadata._METADATA_FLAVOR_HEADER: 'meep'}) + + assert not _metadata.ping(request_mock) + + +def test_ping_failure_connection_failed(mock_request): + request_mock = mock_request('') + request_mock.side_effect = exceptions.TransportError() + + assert not _metadata.ping(request_mock) + + +def test_get_success_json(mock_request): + key, value = 'foo', 'bar' + + data = json.dumps({key: value}) + request_mock = mock_request( + data, headers={'content-type': 'application/json'}) + + result = _metadata.get(request_mock, PATH) + + request_mock.assert_called_once_with( + method='GET', + url=_metadata._METADATA_ROOT + PATH, + headers=_metadata._METADATA_HEADERS) + assert result[key] == value + + +def test_get_success_text(mock_request): + data = 'foobar' + request_mock = mock_request(data, headers={'content-type': 'text/plain'}) + + result = _metadata.get(request_mock, PATH) + + request_mock.assert_called_once_with( + method='GET', + url=_metadata._METADATA_ROOT + PATH, + headers=_metadata._METADATA_HEADERS) + assert result == data + + +def test_get_failure(mock_request): + request_mock = mock_request( + 'Metadata error', status=http_client.NOT_FOUND) + + with pytest.raises(exceptions.TransportError) as excinfo: + _metadata.get(request_mock, PATH) + + assert excinfo.match(r'Metadata error') + + request_mock.assert_called_once_with( + method='GET', + url=_metadata._METADATA_ROOT + PATH, + headers=_metadata._METADATA_HEADERS) + + +def test_get_failure_bad_json(mock_request): + request_mock = mock_request( + '{', headers={'content-type': 'application/json'}) + + with pytest.raises(exceptions.TransportError) as excinfo: + _metadata.get(request_mock, PATH) + + assert excinfo.match(r'invalid JSON') + + request_mock.assert_called_once_with( + method='GET', + url=_metadata._METADATA_ROOT + PATH, + headers=_metadata._METADATA_HEADERS) + + +@mock.patch('google.auth._helpers.utcnow', return_value=datetime.datetime.min) +def test_get_service_account_token(utcnow, mock_request): + ttl = 500 + request_mock = mock_request( + json.dumps({'access_token': 'token', 'expires_in': ttl}), + headers={'content-type': 'application/json'}) + + token, expiry = _metadata.get_service_account_token(request_mock) + + request_mock.assert_called_once_with( + method='GET', + url=_metadata._METADATA_ROOT + PATH + '/token', + headers=_metadata._METADATA_HEADERS) + assert token == 'token' + assert expiry == utcnow() + datetime.timedelta(seconds=ttl) + + +def test_get_service_account_info(mock_request): + key, value = 'foo', 'bar' + request_mock = mock_request( + json.dumps({key: value}), + headers={'content-type': 'application/json'}) + + info = _metadata.get_service_account_info(request_mock) + + request_mock.assert_called_once_with( + method='GET', + url=_metadata._METADATA_ROOT + PATH + '/?recursive=true', + headers=_metadata._METADATA_HEADERS) + + assert info[key] == value diff --git a/packages/google-auth/tests/test__helpers.py b/packages/google-auth/tests/test__helpers.py index c2bc4a7331bd..d475fc4bcbd5 100644 --- a/packages/google-auth/tests/test__helpers.py +++ b/packages/google-auth/tests/test__helpers.py @@ -15,6 +15,7 @@ import datetime import pytest +from six.moves import urllib from google.auth import _helpers @@ -60,3 +61,35 @@ def test_from_bytes_with_bytes(): def test_from_bytes_with_nonstring_type(): with pytest.raises(ValueError): _helpers.from_bytes(object()) + + +def _assert_query(url, expected): + parts = urllib.parse.urlsplit(url) + query = urllib.parse.parse_qs(parts.query) + assert query == expected + + +def test_update_query_params_no_params(): + uri = 'http://www.google.com' + updated = _helpers.update_query(uri, {'a': 'b'}) + assert updated == uri + '?a=b' + + +def test_update_query_existing_params(): + uri = 'http://www.google.com?x=y' + updated = _helpers.update_query(uri, {'a': 'b', 'c': 'd&'}) + _assert_query(updated, {'x': ['y'], 'a': ['b'], 'c': ['d&']}) + + +def test_update_query_replace_param(): + base_uri = 'http://www.google.com' + uri = base_uri + '?x=a' + updated = _helpers.update_query(uri, {'x': 'b', 'y': 'c'}) + _assert_query(updated, {'x': ['b'], 'y': ['c']}) + + +def test_update_query_remove_param(): + base_uri = 'http://www.google.com' + uri = base_uri + '?x=a' + updated = _helpers.update_query(uri, {'y': 'c'}, remove=['x']) + _assert_query(updated, {'y': ['c']}) From dde328a1531c50bf388b34ce463d2f3de99b2689 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Fri, 7 Oct 2016 14:05:31 -0700 Subject: [PATCH 007/966] Add http.client transport (#10) --- .../docs/reference/google.auth.exceptions.rst | 7 ++ .../docs/reference/google.auth.rst | 8 ++ .../docs/reference/google.auth.transport.rst | 8 ++ .../google/auth/transport/_http_client.py | 104 ++++++++++++++++++ packages/google-auth/pylintrc | 1 + packages/google-auth/pylintrc.tests | 1 + .../google-auth/tests/transport/__init__.py | 0 .../google-auth/tests/transport/compliance.py | 93 ++++++++++++++++ .../tests/transport/test__http_client.py | 32 ++++++ 9 files changed, 254 insertions(+) create mode 100644 packages/google-auth/docs/reference/google.auth.exceptions.rst create mode 100644 packages/google-auth/docs/reference/google.auth.transport.rst create mode 100644 packages/google-auth/google/auth/transport/_http_client.py create mode 100644 packages/google-auth/tests/transport/__init__.py create mode 100644 packages/google-auth/tests/transport/compliance.py create mode 100644 packages/google-auth/tests/transport/test__http_client.py diff --git a/packages/google-auth/docs/reference/google.auth.exceptions.rst b/packages/google-auth/docs/reference/google.auth.exceptions.rst new file mode 100644 index 000000000000..c34e97332469 --- /dev/null +++ b/packages/google-auth/docs/reference/google.auth.exceptions.rst @@ -0,0 +1,7 @@ +google.auth.exceptions module +============================= + +.. automodule:: google.auth.exceptions + :members: + :undoc-members: + :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.auth.rst b/packages/google-auth/docs/reference/google.auth.rst index 57a16b475985..0f147e9b11e3 100644 --- a/packages/google-auth/docs/reference/google.auth.rst +++ b/packages/google-auth/docs/reference/google.auth.rst @@ -6,11 +6,19 @@ google.auth package :undoc-members: :show-inheritance: +Subpackages +----------- + +.. toctree:: + + google.auth.transport + Submodules ---------- .. toctree:: google.auth.crypt + google.auth.exceptions google.auth.jwt diff --git a/packages/google-auth/docs/reference/google.auth.transport.rst b/packages/google-auth/docs/reference/google.auth.transport.rst new file mode 100644 index 000000000000..88b427c99d6f --- /dev/null +++ b/packages/google-auth/docs/reference/google.auth.transport.rst @@ -0,0 +1,8 @@ +google.auth.transport package +============================= + +.. automodule:: google.auth.transport + :members: + :undoc-members: + :show-inheritance: + diff --git a/packages/google-auth/google/auth/transport/_http_client.py b/packages/google-auth/google/auth/transport/_http_client.py new file mode 100644 index 000000000000..e50a3b590206 --- /dev/null +++ b/packages/google-auth/google/auth/transport/_http_client.py @@ -0,0 +1,104 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Transport adapter for http.client, for internal use only.""" + +import socket + +from six.moves import http_client +from six.moves import urllib + +from google.auth import exceptions +from google.auth import transport + + +class Response(transport.Response): + """http.client transport request adapter. + + Args: + response (http.client.HTTPResponse): The raw http client response. + """ + def __init__(self, response): + self._status = response.status + self._headers = { + key.lower(): value for key, value in response.getheaders()} + self._data = response.read() + + @property + def status(self): + return self._status + + @property + def headers(self): + return self._headers + + @property + def data(self): + return self._data + + +class Request(transport.Request): + """http.client transport request adapter.""" + + def __call__(self, url, method='GET', body=None, headers=None, + timeout=None, **kwargs): + """Make an HTTP request using http.client. + + Args: + url (str): The URI to be requested. + method (str): The HTTP method to use for the request. Defaults + to 'GET'. + body (bytes): The payload / body in HTTP request. + headers (Mapping): Request headers. + timeout (Optional(int)): The number of seconds to wait for a + response from the server. If not specified or if None, the + socket global default timeout will be used. + kwargs: Additional arguments passed throught to the underlying + :meth:`~http.client.HTTPConnection.request` method. + + Returns: + Response: The HTTP response. + + Raises: + google.auth.exceptions.TransportError: If any exception occurred. + """ + # socket._GLOBAL_DEFAULT_TIMEOUT is the default in http.client. + if timeout is None: + timeout = socket._GLOBAL_DEFAULT_TIMEOUT + + # http.client doesn't allow None as the headers argument. + if headers is None: + headers = {} + + # http.client needs the host and path parts specified separately. + parts = urllib.parse.urlsplit(url) + path = urllib.parse.urlunsplit( + ('', '', parts.path, parts.query, parts.fragment)) + + if parts.scheme != 'http': + raise exceptions.TransportError( + 'http.client transport only supports the http scheme, {}' + 'was specified'.format(parts.scheme)) + + connection = http_client.HTTPConnection(parts.netloc) + + try: + connection.request( + method, path, body=body, headers=headers, **kwargs) + response = connection.getresponse() + return Response(response) + except (http_client.HTTPException, socket.error) as exc: + raise exceptions.TransportError(exc) + finally: + connection.close() diff --git a/packages/google-auth/pylintrc b/packages/google-auth/pylintrc index 1a47c025b7f8..1f01cfa1d04f 100644 --- a/packages/google-auth/pylintrc +++ b/packages/google-auth/pylintrc @@ -115,6 +115,7 @@ disable = wrong-import-position, no-name-in-module, locally-disabled, + locally-enabled, fixme diff --git a/packages/google-auth/pylintrc.tests b/packages/google-auth/pylintrc.tests index de1964f3df5c..09772c5f50f1 100644 --- a/packages/google-auth/pylintrc.tests +++ b/packages/google-auth/pylintrc.tests @@ -105,6 +105,7 @@ disable = wrong-import-position, no-name-in-module, locally-disabled, + locally-enabled, missing-docstring, redefined-outer-name, no-self-use, diff --git a/packages/google-auth/tests/transport/__init__.py b/packages/google-auth/tests/transport/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/google-auth/tests/transport/compliance.py b/packages/google-auth/tests/transport/compliance.py new file mode 100644 index 000000000000..ad4e491215cc --- /dev/null +++ b/packages/google-auth/tests/transport/compliance.py @@ -0,0 +1,93 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import flask +import pytest +from pytest_localserver.http import WSGIServer +from six.moves import http_client + +from google.auth import exceptions + +# .invalid will never resolve, see https://tools.ietf.org/html/rfc2606 +NXDOMAIN = 'test.invalid' + + +class RequestResponseTests(object): + + @pytest.fixture + def server(self): + """Provides a test HTTP server. + + The test server is automatically created before + a test and destroyed at the end. The server is serving a test + application that can be used to verify requests. + """ + app = flask.Flask(__name__) + app.debug = True + + # pylint: disable=unused-variable + # (pylint thinks the flask routes are unusued.) + @app.route('/basic') + def index(): + header_value = flask.request.headers.get('x-test-header', 'value') + headers = {'X-Test-Header': header_value} + return 'Basic Content', http_client.OK, headers + + @app.route('/server_error') + def server_error(): + return 'Error', http_client.INTERNAL_SERVER_ERROR + # pylint: enable=unused-variable + + server = WSGIServer(application=app.wsgi_app) + server.start() + yield server + server.stop() + + def test_request_basic(self, server): + request = self.make_request() + response = request(url=server.url + '/basic', method='GET') + + assert response.status == http_client.OK + assert response.headers['x-test-header'] == 'value' + assert response.data == b'Basic Content' + + def test_request_timeout(self, server): + request = self.make_request() + response = request(url=server.url + '/basic', method='GET', timeout=2) + + assert response.status == http_client.OK + assert response.headers['x-test-header'] == 'value' + assert response.data == b'Basic Content' + + def test_request_headers(self, server): + request = self.make_request() + response = request( + url=server.url + '/basic', method='GET', headers={ + 'x-test-header': 'hello world'}) + + assert response.status == http_client.OK + assert response.headers['x-test-header'] == 'hello world' + assert response.data == b'Basic Content' + + def test_request_error(self, server): + request = self.make_request() + response = request(url=server.url + '/server_error', method='GET') + + assert response.status == http_client.INTERNAL_SERVER_ERROR + assert response.data == b'Error' + + def test_connection_error(self): + request = self.make_request() + with pytest.raises(exceptions.TransportError): + request(url='http://{}'.format(NXDOMAIN), method='GET') diff --git a/packages/google-auth/tests/transport/test__http_client.py b/packages/google-auth/tests/transport/test__http_client.py new file mode 100644 index 000000000000..b66e8a4a00ac --- /dev/null +++ b/packages/google-auth/tests/transport/test__http_client.py @@ -0,0 +1,32 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from google.auth import exceptions +import google.auth.transport._http_client + +from tests.transport import compliance + + +class TestRequestResponse(compliance.RequestResponseTests): + def make_request(self): + return google.auth.transport._http_client.Request() + + def test_non_http(self): + request = self.make_request() + with pytest.raises(exceptions.TransportError) as excinfo: + request(url='https://{}'.format(compliance.NXDOMAIN), method='GET') + + assert excinfo.match('https') From 2ff7f3b401cd3f73c194b74558fa2e563ed0b9c9 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Fri, 7 Oct 2016 15:32:49 -0700 Subject: [PATCH 008/966] Add google.oauth2._client (#13) --- packages/google-auth/google/oauth2/_client.py | 200 ++++++++++++++++++ packages/google-auth/tests/oauth2/__init__.py | 0 .../google-auth/tests/oauth2/test__client.py | 169 +++++++++++++++ 3 files changed, 369 insertions(+) create mode 100644 packages/google-auth/google/oauth2/_client.py create mode 100644 packages/google-auth/tests/oauth2/__init__.py create mode 100644 packages/google-auth/tests/oauth2/test__client.py diff --git a/packages/google-auth/google/oauth2/_client.py b/packages/google-auth/google/oauth2/_client.py new file mode 100644 index 000000000000..1b26549b75cb --- /dev/null +++ b/packages/google-auth/google/oauth2/_client.py @@ -0,0 +1,200 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""OAuth 2.0 client. + +This is a client for interacting with an OAuth 2.0 authorization server's +token endpoint. + +For more information about the token endpoint, see +`Section 3.1 of rfc6749`_ + +.. _Section 3.1 of rfc6749: https://tools.ietf.org/html/rfc6749#section-3.2 +""" + +import datetime +import json + +from six.moves import http_client +from six.moves import urllib + +from google.auth import _helpers +from google.auth import exceptions + +_URLENCODED_CONTENT_TYPE = 'application/x-www-form-urlencoded' +_JWT_GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:jwt-bearer' +_REFRESH_GRANT_TYPE = 'refresh_token' + + +def _handle_error_response(response_body): + """"Translates an error response into an exception. + + Args: + response_body (str): The decoded response data. + + Raises: + google.auth.exceptions.RefreshError + """ + try: + error_data = json.loads(response_body) + error_details = ': '.join([ + error_data['error'], + error_data.get('error_description')]) + # If no details could be extracted, use the response data. + except (KeyError, ValueError): + error_details = response_body + + raise exceptions.RefreshError( + error_details, response_body) + + +def _parse_expiry(response_data): + """Parses the expiry field from a response into a datetime. + + Args: + response_data (Mapping): The JSON-parsed response data. + + Returns: + Optional[datetime]: The expiration or ``None`` if no expiration was + specified. + """ + expires_in = response_data.get('expires_in', None) + + if expires_in is not None: + return _helpers.utcnow() + datetime.timedelta( + seconds=expires_in) + else: + return None + + +def _token_endpoint_request(request, token_uri, body): + """Makes a request to the OAuth 2.0 authorization server's token endpoint. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + token_uri (str): The OAuth 2.0 authorizations server's token endpoint + URI. + body (Mapping[str, str]): The parameters to send in the request body. + + Returns: + Mapping[str, str]: The JSON-decoded response data. + + Raises: + google.auth.exceptions.RefreshError: If the token endpoint returned + an error. + """ + body = urllib.parse.urlencode(body) + headers = { + 'content-type': _URLENCODED_CONTENT_TYPE, + } + + response = request( + method='POST', url=token_uri, headers=headers, body=body) + + response_body = response.data.decode('utf-8') + + if response.status != http_client.OK: + _handle_error_response(response_body) + + response_data = json.loads(response_body) + + return response_data + + +def jwt_grant(request, token_uri, assertion): + """Implements the JWT Profile for OAuth 2.0 Authorization Grants. + + For more details, see `rfc7523 section 4`_. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + token_uri (str): The OAuth 2.0 authorizations server's token endpoint + URI. + assertion (str): The OAuth 2.0 assertion. + + Returns: + Tuple[str, Optional[datetime], Mapping[str, str]]: The access token, + expiration, and additional data returned by the token endpoint. + + Raises: + google.auth.exceptions.RefreshError: If the token endpoint returned + an error. + + .. _rfc7523 section 4: https://tools.ietf.org/html/rfc7523#section-4 + """ + body = { + 'assertion': assertion, + 'grant_type': _JWT_GRANT_TYPE, + } + + response_data = _token_endpoint_request(request, token_uri, body) + + try: + access_token = response_data['access_token'] + except KeyError: + raise exceptions.RefreshError( + 'No access token in response.', response_data) + + expiry = _parse_expiry(response_data) + + return access_token, expiry, response_data + + +def refresh_grant(request, token_uri, refresh_token, client_id, client_secret): + """Implements the OAuth 2.0 refresh token grant. + + For more details, see `rfc678 section 6`_. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + token_uri (str): The OAuth 2.0 authorizations server's token endpoint + URI. + refresh_token (str): The refresh token to use to get a new access + token. + client_id (str): The OAuth 2.0 application's client ID. + client_secret (str): The Oauth 2.0 appliaction's client secret. + + Returns: + Tuple[str, Optional[str], Optional[datetime], Mapping[str, str]]: The + access token, new refresh token, expiration, and additional data + returned by the token endpoint. + + Raises: + google.auth.exceptions.RefreshError: If the token endpoint returned + an error. + + .. _rfc6748 section 6: https://tools.ietf.org/html/rfc6749#section-6 + """ + body = { + 'grant_type': _REFRESH_GRANT_TYPE, + 'client_id': client_id, + 'client_secret': client_secret, + 'refresh_token': refresh_token, + } + + response_data = _token_endpoint_request(request, token_uri, body) + + try: + access_token = response_data['access_token'] + except KeyError: + raise exceptions.RefreshError( + 'No access token in response.', response_data) + + refresh_token = response_data.get('refresh_token', refresh_token) + expiry = _parse_expiry(response_data) + + return access_token, refresh_token, expiry, response_data diff --git a/packages/google-auth/tests/oauth2/__init__.py b/packages/google-auth/tests/oauth2/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/google-auth/tests/oauth2/test__client.py b/packages/google-auth/tests/oauth2/test__client.py new file mode 100644 index 000000000000..8c19c3ee98c1 --- /dev/null +++ b/packages/google-auth/tests/oauth2/test__client.py @@ -0,0 +1,169 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import datetime +import json + +import mock +import pytest +import six +from six.moves import http_client +from six.moves import urllib + +from google.auth import exceptions +from google.oauth2 import _client + + +def test__handle_error_response(): + response_data = json.dumps({ + 'error': 'help', + 'error_description': 'I\'m alive'}) + + with pytest.raises(exceptions.RefreshError) as excinfo: + _client._handle_error_response(response_data) + + assert excinfo.match(r'help: I\'m alive') + + +def test__handle_error_response_non_json(): + response_data = 'Help, I\'m alive' + + with pytest.raises(exceptions.RefreshError) as excinfo: + _client._handle_error_response(response_data) + + assert excinfo.match(r'Help, I\'m alive') + + +@mock.patch('google.auth._helpers.utcnow', return_value=datetime.datetime.min) +def test__parse_expiry(now_mock): + result = _client._parse_expiry({'expires_in': 500}) + assert result == datetime.datetime.min + datetime.timedelta(seconds=500) + + +def test__parse_expiry_none(): + assert _client._parse_expiry({}) is None + + +def _make_request(response_data): + response = mock.Mock() + response.status = http_client.OK + response.data = json.dumps(response_data).encode('utf-8') + return mock.Mock(return_value=response) + + +def test__token_endpoint_request(): + request = _make_request({'test': 'response'}) + + result = _client._token_endpoint_request( + request, 'http://example.com', {'test': 'params'}) + + # Check request call + request.assert_called_with( + method='POST', + url='http://example.com', + headers={'content-type': 'application/x-www-form-urlencoded'}, + body='test=params') + + # Check result + assert result == {'test': 'response'} + + +def test__token_endpoint_request_error(): + response = mock.Mock() + response.status = http_client.BAD_REQUEST + response.data = b'Error' + request = mock.Mock(return_value=response) + + with pytest.raises(exceptions.RefreshError): + _client._token_endpoint_request(request, 'http://example.com', {}) + + +def _verify_request_params(request, params): + request_body = request.call_args[1]['body'] + request_params = urllib.parse.parse_qs(request_body) + + for key, value in six.iteritems(params): + assert request_params[key][0] == value + + +@mock.patch('google.auth._helpers.utcnow', return_value=datetime.datetime.min) +def test_jwt_grant(now_mock): + request = _make_request({ + 'access_token': 'token', + 'expires_in': 500, + 'extra': 'data'}) + + token, expiry, extra_data = _client.jwt_grant( + request, 'http://example.com', 'assertion_value') + + # Check request call + _verify_request_params(request, { + 'grant_type': _client._JWT_GRANT_TYPE, + 'assertion': 'assertion_value' + }) + + # Check result + assert token == 'token' + assert expiry == datetime.datetime.min + datetime.timedelta(seconds=500) + assert extra_data['extra'] == 'data' + + +def test_jwt_grant_no_access_token(): + request = _make_request({ + # No access token. + 'expires_in': 500, + 'extra': 'data'}) + + with pytest.raises(exceptions.RefreshError): + _client.jwt_grant(request, 'http://example.com', 'assertion_value') + + +@mock.patch('google.auth._helpers.utcnow', return_value=datetime.datetime.min) +def test_refresh_grant(now_mock): + request = _make_request({ + 'access_token': 'token', + 'refresh_token': 'new_refresh_token', + 'expires_in': 500, + 'extra': 'data'}) + + token, refresh_token, expiry, extra_data = _client.refresh_grant( + request, 'http://example.com', 'refresh_token', 'client_id', + 'client_secret') + + # Check request call + _verify_request_params(request, { + 'grant_type': _client._REFRESH_GRANT_TYPE, + 'refresh_token': 'refresh_token', + 'client_id': 'client_id', + 'client_secret': 'client_secret' + }) + + # Check result + assert token == 'token' + assert refresh_token == 'new_refresh_token' + assert expiry == datetime.datetime.min + datetime.timedelta(seconds=500) + assert extra_data['extra'] == 'data' + + +def test_refresh_grant_no_access_token(): + request = _make_request({ + # No access token. + 'refresh_token': 'new_refresh_token', + 'expires_in': 500, + 'extra': 'data'}) + + with pytest.raises(exceptions.RefreshError): + _client.refresh_grant( + request, 'http://example.com', 'refresh_token', 'client_id', + 'client_secret') From 135d69c1db6a54ad6b4d07a7ffd84cf68fbceeff Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Mon, 10 Oct 2016 12:57:18 -0700 Subject: [PATCH 009/966] Add urllib3 transport (#14) --- .../reference/google.auth.compute_engine.rst | 8 ++ .../docs/reference/google.auth.rst | 1 + .../docs/reference/google.auth.transport.rst | 7 ++ .../google.auth.transport.urllib3.rst | 7 ++ .../google/auth/transport/_http_client.py | 2 +- .../google/auth/transport/urllib3.py | 91 +++++++++++++++++++ .../google-auth/tests/transport/compliance.py | 2 +- .../tests/transport/test_urllib3.py | 33 +++++++ 8 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 packages/google-auth/docs/reference/google.auth.compute_engine.rst create mode 100644 packages/google-auth/docs/reference/google.auth.transport.urllib3.rst create mode 100644 packages/google-auth/google/auth/transport/urllib3.py create mode 100644 packages/google-auth/tests/transport/test_urllib3.py diff --git a/packages/google-auth/docs/reference/google.auth.compute_engine.rst b/packages/google-auth/docs/reference/google.auth.compute_engine.rst new file mode 100644 index 000000000000..9bf9866cad78 --- /dev/null +++ b/packages/google-auth/docs/reference/google.auth.compute_engine.rst @@ -0,0 +1,8 @@ +google.auth.compute_engine package +================================== + +.. automodule:: google.auth.compute_engine + :members: + :undoc-members: + :show-inheritance: + diff --git a/packages/google-auth/docs/reference/google.auth.rst b/packages/google-auth/docs/reference/google.auth.rst index 0f147e9b11e3..ef0d68981acd 100644 --- a/packages/google-auth/docs/reference/google.auth.rst +++ b/packages/google-auth/docs/reference/google.auth.rst @@ -11,6 +11,7 @@ Subpackages .. toctree:: + google.auth.compute_engine google.auth.transport Submodules diff --git a/packages/google-auth/docs/reference/google.auth.transport.rst b/packages/google-auth/docs/reference/google.auth.transport.rst index 88b427c99d6f..706633473c8c 100644 --- a/packages/google-auth/docs/reference/google.auth.transport.rst +++ b/packages/google-auth/docs/reference/google.auth.transport.rst @@ -6,3 +6,10 @@ google.auth.transport package :undoc-members: :show-inheritance: +Submodules +---------- + +.. toctree:: + + google.auth.transport.urllib3 + diff --git a/packages/google-auth/docs/reference/google.auth.transport.urllib3.rst b/packages/google-auth/docs/reference/google.auth.transport.urllib3.rst new file mode 100644 index 000000000000..32de225c70a9 --- /dev/null +++ b/packages/google-auth/docs/reference/google.auth.transport.urllib3.rst @@ -0,0 +1,7 @@ +google.auth.transport.urllib3 module +==================================== + +.. automodule:: google.auth.transport.urllib3 + :members: + :undoc-members: + :show-inheritance: diff --git a/packages/google-auth/google/auth/transport/_http_client.py b/packages/google-auth/google/auth/transport/_http_client.py index e50a3b590206..ddd58186fa44 100644 --- a/packages/google-auth/google/auth/transport/_http_client.py +++ b/packages/google-auth/google/auth/transport/_http_client.py @@ -24,7 +24,7 @@ class Response(transport.Response): - """http.client transport request adapter. + """http.client transport response adapter. Args: response (http.client.HTTPResponse): The raw http client response. diff --git a/packages/google-auth/google/auth/transport/urllib3.py b/packages/google-auth/google/auth/transport/urllib3.py new file mode 100644 index 000000000000..4ac0f98e75a0 --- /dev/null +++ b/packages/google-auth/google/auth/transport/urllib3.py @@ -0,0 +1,91 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Transport adapter for urllib3.""" + +from __future__ import absolute_import + +import urllib3 +import urllib3.exceptions + +from google.auth import exceptions +from google.auth import transport + + +class Response(transport.Response): + """urllib3 transport response adapter. + + Args: + response (urllib3.response.HTTPResponse): The raw urllib3 response. + """ + def __init__(self, response): + self._response = response + + @property + def status(self): + return self._response.status + + @property + def headers(self): + return self._response.headers + + @property + def data(self): + return self._response.data + + +class Request(transport.Request): + """urllib3 request adapter + + Args: + http (urllib3.requests.RequestMethods): An instance of any urllib3 + class that implements :class:`~urllib3.requests.RequestMethods`, + usually :class:`urllib3.PoolManager`. + """ + def __init__(self, http): + self.http = http + + def __call__(self, url, method='GET', body=None, headers=None, + timeout=None, **kwargs): + """Make an HTTP request using urllib3. + + Args: + url (str): The URI to be requested. + method (str): The HTTP method to use for the request. Defaults + to 'GET'. + body (bytes): The payload / body in HTTP request. + headers (Mapping): Request headers. + timeout (Optional(int)): The number of seconds to wait for a + response from the server. If not specified or if None, the + urllib3 default timeout will be used. + kwargs: Additional arguments passed throught to the underlying + urllib3 :meth:`urlopen` method. + + Returns: + Response: The HTTP response. + + Raises: + google.auth.exceptions.TransportError: If any exception occurred. + """ + # urllib3 uses a sentinel default value for timeout, so only set it if + # specified. + if timeout is not None: + kwargs['timeout'] = timeout + + try: + response = self.http.request( + method, url, body=body, headers=headers, **kwargs) + return Response(response) + except urllib3.exceptions.HTTPError as exc: + raise exceptions.TransportError(exc) diff --git a/packages/google-auth/tests/transport/compliance.py b/packages/google-auth/tests/transport/compliance.py index ad4e491215cc..6d2a30452157 100644 --- a/packages/google-auth/tests/transport/compliance.py +++ b/packages/google-auth/tests/transport/compliance.py @@ -25,7 +25,7 @@ class RequestResponseTests(object): - @pytest.fixture + @pytest.fixture(scope='module') def server(self): """Provides a test HTTP server. diff --git a/packages/google-auth/tests/transport/test_urllib3.py b/packages/google-auth/tests/transport/test_urllib3.py new file mode 100644 index 000000000000..bdd5ac9f4fd7 --- /dev/null +++ b/packages/google-auth/tests/transport/test_urllib3.py @@ -0,0 +1,33 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import mock +import urllib3 + +import google.auth.transport.urllib3 +from tests.transport import compliance + + +class TestRequestResponse(compliance.RequestResponseTests): + def make_request(self): + http = urllib3.PoolManager() + return google.auth.transport.urllib3.Request(http) + + +def test_timeout(): + http = mock.Mock() + request = google.auth.transport.urllib3.Request(http) + request(url='http://example.com', method='GET', timeout=5) + + assert http.request.call_args[1]['timeout'] == 5 From feac37cefc5e21dbf96750fdd901af80b7d9026b Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Mon, 10 Oct 2016 13:05:46 -0700 Subject: [PATCH 010/966] Setup logging (#16) --- packages/google-auth/google/auth/__init__.py | 6 ++++++ .../google-auth/google/auth/compute_engine/_metadata.py | 4 ++++ packages/google-auth/google/auth/transport/_http_client.py | 7 +++++++ packages/google-auth/google/auth/transport/urllib3.py | 5 +++++ 4 files changed, 22 insertions(+) diff --git a/packages/google-auth/google/auth/__init__.py b/packages/google-auth/google/auth/__init__.py index 01e343112901..339b3d6bf92c 100644 --- a/packages/google-auth/google/auth/__init__.py +++ b/packages/google-auth/google/auth/__init__.py @@ -13,3 +13,9 @@ # limitations under the License. """Google Auth Library for Python.""" + +import logging + + +# Set default logging handler to avoid "No handler found" warnings. +logging.getLogger(__name__).addHandler(logging.NullHandler()) diff --git a/packages/google-auth/google/auth/compute_engine/_metadata.py b/packages/google-auth/google/auth/compute_engine/_metadata.py index 28d57f66eec4..6d13a542a48f 100644 --- a/packages/google-auth/google/auth/compute_engine/_metadata.py +++ b/packages/google-auth/google/auth/compute_engine/_metadata.py @@ -19,6 +19,7 @@ import datetime import json +import logging import os from six.moves import http_client @@ -27,6 +28,8 @@ from google.auth import _helpers from google.auth import exceptions +_LOGGER = logging.getLogger(__name__) + _METADATA_ROOT = 'http://metadata.google.internal/computeMetadata/v1/' # This is used to ping the metadata server, it avoids the cost of a DNS @@ -71,6 +74,7 @@ def ping(request, timeout=_METADATA_DEFAULT_TIMEOUT): metadata_flavor == _METADATA_FLAVOR_VALUE) except exceptions.TransportError: + _LOGGER.info('Compute Engine Metadata server unavailable.') return False diff --git a/packages/google-auth/google/auth/transport/_http_client.py b/packages/google-auth/google/auth/transport/_http_client.py index ddd58186fa44..3cf5583eeb32 100644 --- a/packages/google-auth/google/auth/transport/_http_client.py +++ b/packages/google-auth/google/auth/transport/_http_client.py @@ -14,6 +14,7 @@ """Transport adapter for http.client, for internal use only.""" +import logging import socket from six.moves import http_client @@ -22,6 +23,8 @@ from google.auth import exceptions from google.auth import transport +_LOGGER = logging.getLogger(__name__) + class Response(transport.Response): """http.client transport response adapter. @@ -94,11 +97,15 @@ def __call__(self, url, method='GET', body=None, headers=None, connection = http_client.HTTPConnection(parts.netloc) try: + _LOGGER.debug('Making request: %s %s', method, url) + connection.request( method, path, body=body, headers=headers, **kwargs) response = connection.getresponse() return Response(response) + except (http_client.HTTPException, socket.error) as exc: raise exceptions.TransportError(exc) + finally: connection.close() diff --git a/packages/google-auth/google/auth/transport/urllib3.py b/packages/google-auth/google/auth/transport/urllib3.py index 4ac0f98e75a0..9fc58f38d89c 100644 --- a/packages/google-auth/google/auth/transport/urllib3.py +++ b/packages/google-auth/google/auth/transport/urllib3.py @@ -16,12 +16,16 @@ from __future__ import absolute_import +import logging + import urllib3 import urllib3.exceptions from google.auth import exceptions from google.auth import transport +_LOGGER = logging.getLogger(__name__) + class Response(transport.Response): """urllib3 transport response adapter. @@ -84,6 +88,7 @@ def __call__(self, url, method='GET', body=None, headers=None, kwargs['timeout'] = timeout try: + _LOGGER.debug('Making request: %s %s', method, url) response = self.http.request( method, url, body=body, headers=headers, **kwargs) return Response(response) From 4d0a2c17d603c5dd7a38d20984f72a21bd1388c6 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Mon, 10 Oct 2016 13:08:02 -0700 Subject: [PATCH 011/966] Add pypy to travis (#17) --- packages/google-auth/.travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/google-auth/.travis.yml b/packages/google-auth/.travis.yml index c95bd9189d4c..3cee40e70228 100644 --- a/packages/google-auth/.travis.yml +++ b/packages/google-auth/.travis.yml @@ -12,6 +12,8 @@ matrix: env: TOXENV=py34 - python: 3.5 env: TOXENV=py35 + - python: pypy + env: TOXENV=pypy - python: 3.5 env: TOXENV=cover cache: From 574fef7fc3f473b936057439d6c582aa6c586ba2 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Mon, 10 Oct 2016 13:32:16 -0700 Subject: [PATCH 012/966] Release v0.0.1 (#18) --- packages/google-auth/.travis.yml | 9 +++++++++ packages/google-auth/CHANGELOG.rst | 10 ++++++++++ packages/google-auth/README.rst | 8 +++----- 3 files changed, 22 insertions(+), 5 deletions(-) create mode 100644 packages/google-auth/CHANGELOG.rst diff --git a/packages/google-auth/.travis.yml b/packages/google-auth/.travis.yml index 3cee40e70228..95e1494b858d 100644 --- a/packages/google-auth/.travis.yml +++ b/packages/google-auth/.travis.yml @@ -23,3 +23,12 @@ install: - pip install --upgrade tox script: - tox +deploy: + provider: pypi + user: google_opensource + password: + secure: Pbj0eXpOrBvRcBAevX86T1gS54d19yRUv/3e1DEj4+3KKiOP6JH5OLXNCUrd+rqZrdg/3bC1PLtIUWh3oecy7Ngi9ya6kHk3QCTQ6fnl0CfvUlCMPM+Y6tGwA4aSNl2qZQnZSubaMiMUEg92UIPclfrq92fnQvCML+pqedh8BCZgEaPnOJKOtS8FRUrXRZHZ/fKEZpw71gXoWCYj5gzZ1HSoXQkfnqKukAes9mqMax84MKy0NPY7FHxejfH3O5BqJ+InSKG2F6EVZ/9uKQ4NxnUjMcNTU0YBVNkEhjagmSE+7+Xs3EiZbcZMseZXbVTeZSBLBX9+25eW2naMIHlzh7atelYktGnlwjlWi/lf8V8JJ4oY9K8Z2/Lau/5Cdtlq9mmyeKEJt9ltRI8Ll0619EKiJtc/Racg0F9qRR+C+yliPsIEZyopnm8bQVIfDr7RmSYzwOkP9eM+YRerD3cvGprLMkq+t/56zM2YUXxGKoqAve1Cu4oj91TjK4DxJ9Lm4fO9fosLhb8I719mBtZifDFQm1TLgOdyHJ5/+APqaVrWnKrbpCPxt+sYyqXVH1dXEULIDNqm3AwgLWjqAA9dxEV5Uats1LDoM6LaT1CgKc/O/FAQgTTJHPV2cWCVntdXGsZDCs/G9IcxBoXzBqz8TdQ5BEyOS2A8Ws/yjVk7tpw= + on: + tags: true + distributions: sdist bdist_wheel + repo: GoogleCloudPlatform/google-auth-library-python diff --git a/packages/google-auth/CHANGELOG.rst b/packages/google-auth/CHANGELOG.rst new file mode 100644 index 000000000000..6299f68bf03e --- /dev/null +++ b/packages/google-auth/CHANGELOG.rst @@ -0,0 +1,10 @@ +Changelog +========= + +v0.0.1 +------ + +Initial release with foundational functionality for cryptography and JWTs. + +- ``google.auth.crypt`` for creating and verifying cryptographic signatures. +- ``google.auth.jwt`` for creating (encoding) and verifying (decoding) JSON Web tokens. diff --git a/packages/google-auth/README.rst b/packages/google-auth/README.rst index 0f0b99984a64..9609666e33dc 100644 --- a/packages/google-auth/README.rst +++ b/packages/google-auth/README.rst @@ -16,13 +16,11 @@ mechanisms to access Google APIs. Installing ---------- -You can install directly from `GitHub`_:: +You can install using `pip`_:: - $ pip install git+https://github.com/GoogleCloudPlatform/google-auth-library-python.git + $ pip install google-auth -This package is not currently available from PyPI because it is experimental. - -.. _GitHub: https://github.com/GoogleCloudPlatform/google-auth-library-python +.. _pip: https://pip.pypa.io/en/stable/ Documentation ------------- From 94a8522df9ea8028b3377f71ceeb2f9852a8d1c8 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Tue, 11 Oct 2016 17:21:53 -0700 Subject: [PATCH 013/966] Update docs config --- .../google-auth/docs/_templates/fonts.html | 2 ++ packages/google-auth/docs/conf.py | 24 ++++++++++++++++--- 2 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 packages/google-auth/docs/_templates/fonts.html diff --git a/packages/google-auth/docs/_templates/fonts.html b/packages/google-auth/docs/_templates/fonts.html new file mode 100644 index 000000000000..5fc0470c8c15 --- /dev/null +++ b/packages/google-auth/docs/_templates/fonts.html @@ -0,0 +1,2 @@ + + diff --git a/packages/google-auth/docs/conf.py b/packages/google-auth/docs/conf.py index bf271652c7b8..ccd864ac7442 100644 --- a/packages/google-auth/docs/conf.py +++ b/packages/google-auth/docs/conf.py @@ -100,7 +100,7 @@ # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # -# add_module_names = True +add_module_names = False # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. @@ -131,7 +131,16 @@ # further. For a list of options available for each theme, see the # documentation. # -# html_theme_options = {} +html_theme_options = { + 'description': 'Google Auth Library for Python', + 'github_user': 'GoogleCloudPlatform', + 'github_repo': 'google-auth-library-python', + 'github_banner': True, + 'travis_button': True, + 'font_family': "'Roboto', Georgia, sans", + 'head_font_family': "'Roboto', Georgia, serif", + 'code_font_family': "'Roboto Mono', 'Consolas', monospace", +} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] @@ -180,7 +189,16 @@ # Custom sidebar templates, maps document names to template names. # -# html_sidebars = {} + +html_sidebars = { + '**': [ + 'about.html', + 'navigation.html', + 'relations.html', + 'searchbox.html', + 'fonts.html' + ] +} # Additional templates that should be rendered to pages, maps page names to # template names. From 7922e1d9e9d7d3346320d565c5968e88c3007419 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Wed, 12 Oct 2016 14:29:54 -0700 Subject: [PATCH 014/966] Add stylesheet for slightly more legible docs --- packages/google-auth/docs/_static/custom.css | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 packages/google-auth/docs/_static/custom.css diff --git a/packages/google-auth/docs/_static/custom.css b/packages/google-auth/docs/_static/custom.css new file mode 100644 index 000000000000..e69de29bb2d1 From f7b839191cdb48efa8226ace366dc2499135e421 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Wed, 12 Oct 2016 14:40:08 -0700 Subject: [PATCH 015/966] Color identifiers (descname) blue for readability --- packages/google-auth/docs/_static/custom.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/google-auth/docs/_static/custom.css b/packages/google-auth/docs/_static/custom.css index e69de29bb2d1..cd83aa861c80 100644 --- a/packages/google-auth/docs/_static/custom.css +++ b/packages/google-auth/docs/_static/custom.css @@ -0,0 +1,3 @@ +code.descname { + color: #4885ed; +} From 813ec1cec1c1672f88cdc71f702553f923260c9a Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Wed, 12 Oct 2016 15:02:37 -0700 Subject: [PATCH 016/966] Fixing various docstrings --- .../google-auth/google/auth/exceptions.py | 2 +- packages/google-auth/google/auth/jwt.py | 27 ++++++++++--------- .../google/auth/transport/__init__.py | 8 +++--- .../google/auth/transport/urllib3.py | 10 ++++--- 4 files changed, 26 insertions(+), 21 deletions(-) diff --git a/packages/google-auth/google/auth/exceptions.py b/packages/google-auth/google/auth/exceptions.py index dedde9e211e2..2be9fd6df403 100644 --- a/packages/google-auth/google/auth/exceptions.py +++ b/packages/google-auth/google/auth/exceptions.py @@ -19,7 +19,7 @@ class GoogleAuthError(Exception): """Base class for all google.auth errors.""" -class TransportError(Exception): +class TransportError(GoogleAuthError): """Used to indicate an error occurred during an HTTP request.""" diff --git a/packages/google-auth/google/auth/jwt.py b/packages/google-auth/google/auth/jwt.py index 5349e2982a43..f68fc4b4df9d 100644 --- a/packages/google-auth/google/auth/jwt.py +++ b/packages/google-auth/google/auth/jwt.py @@ -19,7 +19,7 @@ See `rfc7519`_ for more details on JWTs. -To encode a JWT:: +To encode a JWT use :func:`encode`:: from google.auth import crypto from google.auth import jwt @@ -28,7 +28,7 @@ payload = {'some': 'payload'} encoded = jwt.encode(signer, payload) -To decode a JWT and verify claims:: +To decode a JWT and verify claims use :func:`decode`:: claims = jwt.decode(encoded, certs=public_certs) @@ -57,8 +57,8 @@ def encode(signer, payload, header=None, key_id=None): Args: signer (google.auth.crypt.Signer): The signer used to sign the JWT. - payload (Mapping): The JWT payload. - header (Mapping): Additional JWT header payload. + payload (Mapping[str, str]): The JWT payload. + header (Mapping[str, str]): Additional JWT header payload. key_id (str): The key id to add to the JWT header. If the signer has a key id it will be used as the default. If this is specified it will override the signer's key id. @@ -146,11 +146,11 @@ def decode_header(token): def _verify_iat_and_exp(payload): - """Verifies the iat (Issued At) and exp (Expires) claims in a token + """Verifies the ``iat`` (Issued At) and ``exp`` (Expires) claims in a token payload. Args: - payload (mapping): The JWT payload. + payload (Mapping[str, str]): The JWT payload. Raises: ValueError: if any checks failed. @@ -180,19 +180,20 @@ def decode(token, certs=None, verify=True, audience=None): """Decode and verify a JWT. Args: - token (string): The encoded JWT. - certs (Union[str, bytes, Mapping]): The certificate used to - validate. If bytes or string, it must the the public key - certificate in PEM format. If a mapping, it must be a mapping of - key IDs to public key certificates in PEM format. The mapping must - contain the same key ID that's specified in the token's header. + token (str): The encoded JWT. + certs (Union[str, bytes, Mapping[str, Union[str, bytes]]]): The + certificate used to validate the JWT signatyre. If bytes or string, + it must the the public key certificate in PEM format. If a mapping, + it must be a mapping of key IDs to public key certificates in PEM + format. The mapping must contain the same key ID that's specified + in the token's header. verify (bool): Whether to perform signature and claim validation. Verification is done by default. audience (str): The audience claim, 'aud', that this JWT should contain. If None then the JWT's 'aud' parameter is not verified. Returns: - Mapping: The deserialized JSON payload in the JWT. + Mapping[str, str]: The deserialized JSON payload in the JWT. Raises: ValueError: if any verification checks failed. diff --git a/packages/google-auth/google/auth/transport/__init__.py b/packages/google-auth/google/auth/transport/__init__.py index 32de8f17bf77..50b1c4393152 100644 --- a/packages/google-auth/google/auth/transport/__init__.py +++ b/packages/google-auth/google/auth/transport/__init__.py @@ -40,7 +40,7 @@ def status(self): @abc.abstractproperty def headers(self): - """Mapping: The HTTP response headers.""" + """Mapping[str, str]: The HTTP response headers.""" raise NotImplementedError('headers must be implemented.') @abc.abstractproperty @@ -55,6 +55,8 @@ class Request(object): Specific transport implementations should provide an implementation of this that adapts their specific request / response API. + + .. automethod:: __call__ """ @abc.abstractmethod @@ -67,8 +69,8 @@ def __call__(self, url, method='GET', body=None, headers=None, method (str): The HTTP method to use for the request. Defaults to 'GET'. body (bytes): The payload / body in HTTP request. - headers (Mapping): Request headers. - timeout (Optional(int)): The number of seconds to wait for a + headers (Mapping[str, str]): Request headers. + timeout (Optional[int]): The number of seconds to wait for a response from the server. If not specified or if None, the transport-specific default timeout will be used. kwargs: Additionally arguments passed on to the transport's diff --git a/packages/google-auth/google/auth/transport/urllib3.py b/packages/google-auth/google/auth/transport/urllib3.py index 9fc58f38d89c..be6e55bc8e4a 100644 --- a/packages/google-auth/google/auth/transport/urllib3.py +++ b/packages/google-auth/google/auth/transport/urllib3.py @@ -53,9 +53,11 @@ class Request(transport.Request): """urllib3 request adapter Args: - http (urllib3.requests.RequestMethods): An instance of any urllib3 - class that implements :class:`~urllib3.requests.RequestMethods`, + http (urllib3.request.RequestMethods): An instance of any urllib3 + class that implements :class:`~urllib3.request.RequestMethods`, usually :class:`urllib3.PoolManager`. + + .. automethod:: __call__ """ def __init__(self, http): self.http = http @@ -69,8 +71,8 @@ def __call__(self, url, method='GET', body=None, headers=None, method (str): The HTTP method to use for the request. Defaults to 'GET'. body (bytes): The payload / body in HTTP request. - headers (Mapping): Request headers. - timeout (Optional(int)): The number of seconds to wait for a + headers (Mapping[str, str]): Request headers. + timeout (Optional[int]): The number of seconds to wait for a response from the server. If not specified or if None, the urllib3 default timeout will be used. kwargs: Additional arguments passed throught to the underlying From 5b1f4b514bc36fb6b3fd193a07e9bdde31e09901 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Thu, 13 Oct 2016 09:46:49 -0700 Subject: [PATCH 017/966] Add urllib3 AuthorizedHttp (#19) --- packages/google-auth/docs/conf.py | 5 +- .../google/auth/transport/__init__.py | 9 + .../google/auth/transport/urllib3.py | 158 +++++++++++++++++- .../tests/transport/test_urllib3.py | 114 ++++++++++++- packages/google-auth/tox.ini | 1 + 5 files changed, 277 insertions(+), 10 deletions(-) diff --git a/packages/google-auth/docs/conf.py b/packages/google-auth/docs/conf.py index ccd864ac7442..b70918fe44d2 100644 --- a/packages/google-auth/docs/conf.py +++ b/packages/google-auth/docs/conf.py @@ -363,7 +363,10 @@ # Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'https://docs.python.org/3.5': None} +intersphinx_mapping = { + 'python': ('https://docs.python.org/3.5', None), + 'urllib3': ('https://urllib3.readthedocs.io/en/latest', None), +} # Autodoc config autoclass_content = 'both' diff --git a/packages/google-auth/google/auth/transport/__init__.py b/packages/google-auth/google/auth/transport/__init__.py index 50b1c4393152..d73c63cd7581 100644 --- a/packages/google-auth/google/auth/transport/__init__.py +++ b/packages/google-auth/google/auth/transport/__init__.py @@ -27,6 +27,15 @@ import abc import six +from six.moves import http_client + +DEFAULT_REFRESH_STATUS_CODES = (http_client.UNAUTHORIZED,) +"""Sequence[int]: Which HTTP status code indicate that credentials should be +refreshed and a request should be retried. +""" + +DEFAULT_MAX_REFRESH_ATTEMPTS = 2 +"""int: How many times to refresh the credentials and retry a request.""" @six.add_metaclass(abc.ABCMeta) diff --git a/packages/google-auth/google/auth/transport/urllib3.py b/packages/google-auth/google/auth/transport/urllib3.py index be6e55bc8e4a..9d417b5b6e66 100644 --- a/packages/google-auth/google/auth/transport/urllib3.py +++ b/packages/google-auth/google/auth/transport/urllib3.py @@ -18,6 +18,18 @@ import logging + +# Certifi is Mozilla's certificate bundle. Urllib3 needs a certificate bundle +# to verify HTTPS requests, and certifi is the recommended and most reliable +# way to get a root certificate bundle. See +# http://urllib3.readthedocs.io/en/latest/user-guide.html\ +# #certificate-verification +# For more details. +try: + import certifi +except ImportError: # pragma: NO COVER + certifi = None + import urllib3 import urllib3.exceptions @@ -27,7 +39,7 @@ _LOGGER = logging.getLogger(__name__) -class Response(transport.Response): +class _Response(transport.Response): """urllib3 transport response adapter. Args: @@ -50,7 +62,22 @@ def data(self): class Request(transport.Request): - """urllib3 request adapter + """urllib3 request adapter. + + This class is used internally for making requests using various transports + in a consistent way. If you use :class:`AuthorizedHttp` you do not need + to construct or use this class directly. + + This class can be useful if you want to manually refresh a + :class:`~google.auth.credentials.Credentials` instance:: + + import google.auth.transport.urllib3 + import urllib3 + + http = urllib3.PoolManager() + request = google.auth.transport.urllib3.Request(http) + + credentials.refresh(request) Args: http (urllib3.request.RequestMethods): An instance of any urllib3 @@ -79,7 +106,7 @@ def __call__(self, url, method='GET', body=None, headers=None, urllib3 :meth:`urlopen` method. Returns: - Response: The HTTP response. + google.auth.transport.Response: The HTTP response. Raises: google.auth.exceptions.TransportError: If any exception occurred. @@ -93,6 +120,129 @@ def __call__(self, url, method='GET', body=None, headers=None, _LOGGER.debug('Making request: %s %s', method, url) response = self.http.request( method, url, body=body, headers=headers, **kwargs) - return Response(response) + return _Response(response) except urllib3.exceptions.HTTPError as exc: raise exceptions.TransportError(exc) + + +def _make_default_http(): + if certifi is not None: + return urllib3.PoolManager( + cert_reqs='CERT_REQUIRED', + ca_certs=certifi.where()) + else: + return urllib3.PoolManager() + + +class AuthorizedHttp(urllib3.request.RequestMethods): + """A urllib3 HTTP class with credentials. + + This class is used to perform requests to API endpoints that require + authorization:: + + from google.auth.transport.urllib3 import AuthorizedHttp + + authed_http = AuthorizedHttp(credentials) + + response = authed_http.request( + 'GET', 'https://www.googleapis.com/storage/v1/b') + + This class implements :class:`urllib3.request.RequestMethods` and can be + used just like any other :class:`urllib3.PoolManager`. + + The underlying :meth:`urlopen` implementation handles adding the + credentials' headers to the request and refreshing credentials as needed. + + Args: + credentials (google.auth.credentials.Credentials): The credentials to + add to the request. + http (urllib3.PoolManager): The underlying HTTP object to + use to make requests. If not specified, a + :class:`urllib3.PoolManager` instance will be constructed with + sane defaults. + refresh_status_codes (Sequence[int]): Which HTTP status codes indicate + that credentials should be refreshed and the request should be + retried. + max_refresh_attempts (int): The maximum number of times to attempt to + refresh the credentials and retry the request. + """ + def __init__(self, credentials, http=None, + refresh_status_codes=transport.DEFAULT_REFRESH_STATUS_CODES, + max_refresh_attempts=transport.DEFAULT_MAX_REFRESH_ATTEMPTS): + + if http is None: + http = _make_default_http() + + self.http = http + self.credentials = credentials + self._refresh_status_codes = refresh_status_codes + self._max_refresh_attempts = max_refresh_attempts + # Request instance used by internal methods (for example, + # credentials.refresh). + self._request = Request(self.http) + + def urlopen(self, method, url, body=None, headers=None, **kwargs): + """Implementation of urllib3's urlopen.""" + + # Use a kwarg for this instead of an attribute to maintain + # thread-safety. + _credential_refresh_attempt = kwargs.pop( + '_credential_refresh_attempt', 0) + + if headers is None: + headers = self.headers + + # Make a copy of the headers. They will be modified by the credentials + # and we want to pass the original headers if we recurse. + request_headers = headers.copy() + + self.credentials.before_request( + self._request, method, url, request_headers) + + response = self.http.urlopen( + method, url, body=body, headers=request_headers, **kwargs) + + # If the response indicated that the credentials needed to be + # refreshed, then refresh the credentials and re-attempt the + # request. + # A stored token may expire between the time it is retrieved and + # the time the request is made, so we may need to try twice. + # The reason urllib3's retries aren't used is because they + # don't allow you to modify the request headers. :/ + if (response.status in self._refresh_status_codes + and _credential_refresh_attempt < self._max_refresh_attempts): + + _LOGGER.info( + 'Refreshing credentials due to a %s response. Attempt %s/%s.', + response.status, _credential_refresh_attempt + 1, + self._max_refresh_attempts) + + self.credentials.refresh(self._request) + + # Recurse. Pass in the original headers, not our modified set. + return self.urlopen( + method, url, body=body, headers=headers, + _credential_refresh_attempt=_credential_refresh_attempt + 1, + **kwargs) + + return response + + # Proxy methods for compliance with the urllib3.PoolManager interface + + def __enter__(self): + """Proxy to ``self.http``.""" + return self.http.__enter__() + + def __exit__(self, exc_type, exc_val, exc_tb): + """Proxy to ``self.http``.""" + return self.http.__exit__(exc_type, exc_val, exc_tb) + + @property + def headers(self): + """Proxy to ``self.http``.""" + return self.http.headers + + @headers.setter + def headers(self, value): + """Proxy to ``self.http``.""" + self.http.headers = value diff --git a/packages/google-auth/tests/transport/test_urllib3.py b/packages/google-auth/tests/transport/test_urllib3.py index bdd5ac9f4fd7..d35a759cb208 100644 --- a/packages/google-auth/tests/transport/test_urllib3.py +++ b/packages/google-auth/tests/transport/test_urllib3.py @@ -13,6 +13,7 @@ # limitations under the License. import mock +from six.moves import http_client import urllib3 import google.auth.transport.urllib3 @@ -24,10 +25,113 @@ def make_request(self): http = urllib3.PoolManager() return google.auth.transport.urllib3.Request(http) + def test_timeout(self): + http = mock.Mock() + request = google.auth.transport.urllib3.Request(http) + request(url='http://example.com', method='GET', timeout=5) -def test_timeout(): - http = mock.Mock() - request = google.auth.transport.urllib3.Request(http) - request(url='http://example.com', method='GET', timeout=5) + assert http.request.call_args[1]['timeout'] == 5 - assert http.request.call_args[1]['timeout'] == 5 + +def test__make_default_http_with_certfi(): + http = google.auth.transport.urllib3._make_default_http() + assert 'cert_reqs' in http.connection_pool_kw + + +@mock.patch.object(google.auth.transport.urllib3, 'certifi', new=None) +def test__make_default_http_without_certfi(): + http = google.auth.transport.urllib3._make_default_http() + assert 'cert_reqs' not in http.connection_pool_kw + + +class MockCredentials(object): + def __init__(self, token='token'): + self.token = token + + def apply(self, headers): + headers['authorization'] = self.token + + def before_request(self, request, method, url, headers): + self.apply(headers) + + def refresh(self, request): + self.token += '1' + + +class MockHttp(object): + def __init__(self, responses, headers=None): + self.responses = responses + self.requests = [] + self.headers = headers or {} + + def urlopen(self, method, url, body=None, headers=None, **kwargs): + self.requests.append((method, url, body, headers, kwargs)) + return self.responses.pop(0) + + +class MockResponse(object): + def __init__(self, status=http_client.OK, data=None): + self.status = status + self.data = data + + +class TestAuthorizedHttp(object): + TEST_URL = 'http://example.com' + + def test_authed_http_defaults(self): + authed_http = google.auth.transport.urllib3.AuthorizedHttp( + mock.sentinel.credentials) + + assert authed_http.credentials == mock.sentinel.credentials + assert isinstance(authed_http.http, urllib3.PoolManager) + + def test_urlopen_no_refresh(self): + mock_credentials = mock.Mock(wraps=MockCredentials()) + mock_response = MockResponse() + mock_http = MockHttp([mock_response]) + + authed_http = google.auth.transport.urllib3.AuthorizedHttp( + mock_credentials, http=mock_http) + + response = authed_http.urlopen('GET', self.TEST_URL) + + assert response == mock_response + assert mock_credentials.before_request.called + assert not mock_credentials.refresh.called + assert mock_http.requests == [ + ('GET', self.TEST_URL, None, {'authorization': 'token'}, {})] + + def test_urlopen_refresh(self): + mock_credentials = mock.Mock(wraps=MockCredentials()) + mock_final_response = MockResponse(status=http_client.OK) + # First request will 401, second request will succeed. + mock_http = MockHttp([ + MockResponse(status=http_client.UNAUTHORIZED), + mock_final_response]) + + authed_http = google.auth.transport.urllib3.AuthorizedHttp( + mock_credentials, http=mock_http) + + response = authed_http.urlopen('GET', 'http://example.com') + + assert response == mock_final_response + assert mock_credentials.before_request.call_count == 2 + assert mock_credentials.refresh.called + assert mock_http.requests == [ + ('GET', self.TEST_URL, None, {'authorization': 'token'}, {}), + ('GET', self.TEST_URL, None, {'authorization': 'token1'}, {})] + + def test_proxies(self): + mock_http = mock.MagicMock() + + authed_http = google.auth.transport.urllib3.AuthorizedHttp( + None, http=mock_http) + + with authed_http: + pass + + assert mock_http.__enter__.called + assert mock_http.__exit__.called + + authed_http.headers = mock.sentinel.headers + assert authed_http.headers == mock_http.headers diff --git a/packages/google-auth/tox.ini b/packages/google-auth/tox.ini index 5f2df1c92531..ffaef944e919 100644 --- a/packages/google-auth/tox.ini +++ b/packages/google-auth/tox.ini @@ -9,6 +9,7 @@ deps = pytest-cov pytest-localserver urllib3 + certifi commands = py.test --cov=google.auth --cov=google.oauth2 --cov=tests {posargs:tests} From 91db974fccf8d3870f3af8129af9112957c37c3b Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Thu, 13 Oct 2016 10:52:04 -0700 Subject: [PATCH 018/966] Add sphinx-docstring-typing --- packages/google-auth/docs/conf.py | 1 + packages/google-auth/docs/requirements-docs.txt | 1 + packages/google-auth/tox.ini | 1 + 3 files changed, 3 insertions(+) create mode 100644 packages/google-auth/docs/requirements-docs.txt diff --git a/packages/google-auth/docs/conf.py b/packages/google-auth/docs/conf.py index b70918fe44d2..bd64578d596e 100644 --- a/packages/google-auth/docs/conf.py +++ b/packages/google-auth/docs/conf.py @@ -35,6 +35,7 @@ 'sphinx.ext.intersphinx', 'sphinx.ext.viewcode', 'sphinx.ext.napoleon', + 'sphinx_docstring_typing' ] # Add any paths that contain templates here, relative to this directory. diff --git a/packages/google-auth/docs/requirements-docs.txt b/packages/google-auth/docs/requirements-docs.txt new file mode 100644 index 000000000000..5922c575897d --- /dev/null +++ b/packages/google-auth/docs/requirements-docs.txt @@ -0,0 +1 @@ +sphinx-docstring-typing diff --git a/packages/google-auth/tox.ini b/packages/google-auth/tox.ini index ffaef944e919..ce942f165f2f 100644 --- a/packages/google-auth/tox.ini +++ b/packages/google-auth/tox.ini @@ -35,6 +35,7 @@ basepython = python3.5 deps = {[testenv]deps} sphinx + -r{toxinidir}/docs/requirements-docs.txt commands = make -C docs html [testenv:lint] From c527daceeeafaed0e10852acc71f78ec072e4315 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Fri, 14 Oct 2016 12:56:14 -0700 Subject: [PATCH 019/966] Fix distributions --- packages/google-auth/.travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/.travis.yml b/packages/google-auth/.travis.yml index 95e1494b858d..295f14cb5ab6 100644 --- a/packages/google-auth/.travis.yml +++ b/packages/google-auth/.travis.yml @@ -26,9 +26,9 @@ script: deploy: provider: pypi user: google_opensource + distributions: sdist bdist_wheel password: secure: Pbj0eXpOrBvRcBAevX86T1gS54d19yRUv/3e1DEj4+3KKiOP6JH5OLXNCUrd+rqZrdg/3bC1PLtIUWh3oecy7Ngi9ya6kHk3QCTQ6fnl0CfvUlCMPM+Y6tGwA4aSNl2qZQnZSubaMiMUEg92UIPclfrq92fnQvCML+pqedh8BCZgEaPnOJKOtS8FRUrXRZHZ/fKEZpw71gXoWCYj5gzZ1HSoXQkfnqKukAes9mqMax84MKy0NPY7FHxejfH3O5BqJ+InSKG2F6EVZ/9uKQ4NxnUjMcNTU0YBVNkEhjagmSE+7+Xs3EiZbcZMseZXbVTeZSBLBX9+25eW2naMIHlzh7atelYktGnlwjlWi/lf8V8JJ4oY9K8Z2/Lau/5Cdtlq9mmyeKEJt9ltRI8Ll0619EKiJtc/Racg0F9qRR+C+yliPsIEZyopnm8bQVIfDr7RmSYzwOkP9eM+YRerD3cvGprLMkq+t/56zM2YUXxGKoqAve1Cu4oj91TjK4DxJ9Lm4fO9fosLhb8I719mBtZifDFQm1TLgOdyHJ5/+APqaVrWnKrbpCPxt+sYyqXVH1dXEULIDNqm3AwgLWjqAA9dxEV5Uats1LDoM6LaT1CgKc/O/FAQgTTJHPV2cWCVntdXGsZDCs/G9IcxBoXzBqz8TdQ5BEyOS2A8Ws/yjVk7tpw= on: tags: true - distributions: sdist bdist_wheel repo: GoogleCloudPlatform/google-auth-library-python From 5008fd1a990a008f838de6d7c737255771b0ea0b Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Fri, 14 Oct 2016 13:55:55 -0700 Subject: [PATCH 020/966] Add sphinx-apidoc options --- packages/google-auth/tox.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/google-auth/tox.ini b/packages/google-auth/tox.ini index ce942f165f2f..65178285b6a7 100644 --- a/packages/google-auth/tox.ini +++ b/packages/google-auth/tox.ini @@ -26,6 +26,8 @@ basepython = python3.5 deps = {[testenv]deps} sphinx +setenv = + SPHINX_APIDOC_OPTIONS=members,inherited-members,show-inheritance commands = rm -r docs/reference sphinx-apidoc --output-dir docs/reference --separate --module-first google From dbcc4bac6603d5328c44ce57f0892112e8ee09a6 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Fri, 14 Oct 2016 14:08:10 -0700 Subject: [PATCH 021/966] Add google.auth.credentials - the public interfaces for credentials (#8) --- .../reference/google.auth.credentials.rst | 7 + .../docs/reference/google.auth.rst | 1 + packages/google-auth/google/auth/_helpers.py | 29 ++- .../google-auth/google/auth/credentials.py | 205 ++++++++++++++++++ packages/google-auth/tests/test__helpers.py | 27 +++ .../google-auth/tests/test_credentials.py | 94 ++++++++ 6 files changed, 362 insertions(+), 1 deletion(-) create mode 100644 packages/google-auth/docs/reference/google.auth.credentials.rst create mode 100644 packages/google-auth/google/auth/credentials.py create mode 100644 packages/google-auth/tests/test_credentials.py diff --git a/packages/google-auth/docs/reference/google.auth.credentials.rst b/packages/google-auth/docs/reference/google.auth.credentials.rst new file mode 100644 index 000000000000..646fa7bb3a83 --- /dev/null +++ b/packages/google-auth/docs/reference/google.auth.credentials.rst @@ -0,0 +1,7 @@ +google.auth.credentials module +============================== + +.. automodule:: google.auth.credentials + :members: + :undoc-members: + :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.auth.rst b/packages/google-auth/docs/reference/google.auth.rst index ef0d68981acd..78f7786809e2 100644 --- a/packages/google-auth/docs/reference/google.auth.rst +++ b/packages/google-auth/docs/reference/google.auth.rst @@ -19,6 +19,7 @@ Submodules .. toctree:: + google.auth.credentials google.auth.crypt google.auth.exceptions google.auth.jwt diff --git a/packages/google-auth/google/auth/_helpers.py b/packages/google-auth/google/auth/_helpers.py index d4bf8f1b41d2..9c49e06f4305 100644 --- a/packages/google-auth/google/auth/_helpers.py +++ b/packages/google-auth/google/auth/_helpers.py @@ -14,7 +14,6 @@ """Helper functions for commonly used utilities.""" - import calendar import datetime @@ -135,3 +134,31 @@ def update_query(url, params, remove=None): # Unsplit the url. new_parts = parts._replace(query=new_query) return urllib.parse.urlunparse(new_parts) + + +def scopes_to_string(scopes): + """Converts scope value to a string suitable for sending to OAuth 2.0 + authorization servers. + + Args: + scopes (Sequence[str]): The sequence of scopes to convert. + + Returns: + str: The scopes formatted as a single string. + """ + return ' '.join(scopes) + + +def string_to_scopes(scopes): + """Converts stringifed scopes value to a list. + + Args: + scopes (Union[Sequence, str]): The string of space-separated scopes + to convert. + Returns: + Sequence(str): The separated scopes. + """ + if not scopes: + return [] + + return scopes.split(' ') diff --git a/packages/google-auth/google/auth/credentials.py b/packages/google-auth/google/auth/credentials.py new file mode 100644 index 000000000000..b10e63e14fa7 --- /dev/null +++ b/packages/google-auth/google/auth/credentials.py @@ -0,0 +1,205 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +"""Interfaces for credentials.""" + +import abc + +import six + +from google.auth import _helpers + + +@six.add_metaclass(abc.ABCMeta) +class Credentials(object): + """Base class for all credentials. + + All credentials have a :attr:`token` that is used for authentication and + may also optionally set an :attr:`expiry` to indicate when the token will + no longer be valid. + + Most credentials will be :attr:`invalid` until :meth:`refresh` is called. + Credentials can do this automatically before the first HTTP request in + :meth:`before_request`. + + Although the token and expiration will change as the credentials are + :meth:`refreshed ` and used, credentials should be considered + immutable. Various credentials will accept configuration such as private + keys, scopes, and other options. These options are not changeable after + construction. Some classes will provide mechanisms to copy the credentials + with modifications such as :meth:`ScopedCredentials.with_scopes`. + """ + def __init__(self): + self.token = None + """str: The bearer token that can be used in HTTP headers to make + authenticated requests.""" + self.expiry = None + """Optional[datetime]: When the token expires and is no longer valid. + If this is None, the token is assumed to never expire.""" + + @property + def expired(self): + """Checks if the credentials are expired. + + Note that credentials can be invalid but not expired becaue Credentials + with :attr:`expiry` set to None is considered to never expire. + """ + now = _helpers.utcnow() + return self.expiry is not None and self.expiry <= now + + @property + def valid(self): + """Checks the validity of the credentials. + + This is True if the credentials have a :attr:`token` and the token + is not :attr:`expired`. + """ + return self.token is not None and not self.expired + + @abc.abstractmethod + def refresh(self, request): + """Refreshes the access token. + + Args: + request (google.auth.transport.Request): The object used to make + HTTP requests. + + Raises: + google.auth.exceptions.RefreshError: If the credentials could + not be refreshed. + """ + # pylint: disable=missing-raises-doc + # (pylint doesn't recognize that this is abstract) + raise NotImplementedError('Refresh must be implemented') + + def apply(self, headers, token=None): + """Apply the token to the authentication header. + + Args: + headers (Mapping): The HTTP request headers. + token (Optional[str]): If specified, overrides the current access + token. + """ + headers['authorization'] = 'Bearer {}'.format( + _helpers.from_bytes(token or self.token)) + + def before_request(self, request, method, url, headers): + """Performs credential-specific before request logic. + + Refreshes the credentials if necessary, then calls :meth:`apply` to + apply the token to the authentication header. + + Args: + request (google.auth.transport.Request): the object used to make + HTTP requests. + method (str): The request's HTTP method. + url (str): The request's URI. + headers (Mapping): The request's headers. + """ + # pylint: disable=unused-argument + # (Subclasses may use these arguments to ascertain information about + # the http request.) + if not self.valid: + self.refresh(request) + self.apply(headers) + + +@six.add_metaclass(abc.ABCMeta) +class Scoped(object): + """Interface for scoped credentials. + + OAuth 2.0-based credentials allow limiting access using scopes as described + in `RFC6749 Section 3.3`_. + If a credential class implements this interface then the credentials either + use scopes in their implementation. + + Some credentials require scopes in order to obtain a token. You can check + if scoping is necessary with :attr:`requires_scopes`:: + + if credentials.requires_scopes: + # Scoping is required. + credentials = credentials.create_scoped(['one', 'two']) + + Credentials that require scopes must either be constructed with scopes:: + + credentials = SomeScopedCredentials(scopes=['one', 'two']) + + Or must copy an existing instance using :meth:`with_scopes`:: + + scoped_credentials = credentials.with_scopes(scopes=['one', 'two']) + + Some credentials have scopes but do not allow or require scopes to be set, + these credentials can be used as-is. + + .. _RFC6749 Section 3.3: https://tools.ietf.org/html/rfc6749#section-3.3 + """ + def __init__(self): + super(Scoped, self).__init__() + self._scopes = None + + @property + def scopes(self): + """Sequence[str]: the credentials' current set of scopes.""" + return self._scopes + + @abc.abstractproperty + def requires_scopes(self): + """True if these credentials require scopes to obtain an access token. + """ + return False + + @abc.abstractmethod + def with_scopes(self, scopes): + """Create a copy of these credentials with the specified scopes. + + Args: + scopes (Sequence[str]): The list of scopes to request. + + Raises: + NotImplementedError: If the credentials' scopes can not be changed. + This can be avoided by checking :attr:`requires_scopes` before + calling this method. + """ + raise NotImplementedError('This class does not require scoping.') + + def has_scopes(self, scopes): + """Checks if the credentials have the given scopes. + + .. warning: This method is not guaranteed to be accurate if the + credentials are :attr:`~Credentials.invalid`. + + Returns: + bool: True if the credentials have the given scopes. + """ + return set(scopes).issubset(set(self._scopes or [])) + + +@six.add_metaclass(abc.ABCMeta) +class Signing(object): + """Interface for credentials that can cryptographically sign messages.""" + + @abc.abstractmethod + def sign_bytes(self, message): + """Signs the given message. + + Args: + message (bytes): The message to sign. + + Returns: + bytes: The message's cryptographic signature. + """ + # pylint: disable=missing-raises-doc,redundant-returns-doc + # (pylint doesn't recognize that this is abstract) + raise NotImplementedError('Sign bytes must be implemented.') diff --git a/packages/google-auth/tests/test__helpers.py b/packages/google-auth/tests/test__helpers.py index d475fc4bcbd5..86cacefefe24 100644 --- a/packages/google-auth/tests/test__helpers.py +++ b/packages/google-auth/tests/test__helpers.py @@ -93,3 +93,30 @@ def test_update_query_remove_param(): uri = base_uri + '?x=a' updated = _helpers.update_query(uri, {'y': 'c'}, remove=['x']) _assert_query(updated, {'y': ['c']}) + + +def test_scopes_to_string(): + cases = [ + ('', ()), + ('', []), + ('', ('',)), + ('', ['', ]), + ('a', ('a',)), + ('b', ['b', ]), + ('a b', ['a', 'b']), + ('a b', ('a', 'b')), + ('a b', (s for s in ['a', 'b'])), + ] + for expected, case in cases: + assert _helpers.scopes_to_string(case) == expected + + +def test_string_to_scopes(): + cases = [ + ('', []), + ('a', ['a']), + ('a b c d e f', ['a', 'b', 'c', 'd', 'e', 'f']), + ] + + for case, expected in cases: + assert _helpers.string_to_scopes(case) == expected diff --git a/packages/google-auth/tests/test_credentials.py b/packages/google-auth/tests/test_credentials.py new file mode 100644 index 000000000000..d78c554d5402 --- /dev/null +++ b/packages/google-auth/tests/test_credentials.py @@ -0,0 +1,94 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import datetime + +from google.auth import credentials + + +class CredentialsImpl(credentials.Credentials): + def refresh(self, request): + self.token = request + + +def test_credentials_constructor(): + credentials = CredentialsImpl() + assert not credentials.token + assert not credentials.expiry + assert not credentials.expired + assert not credentials.valid + + +def test_expired_and_valid(): + credentials = CredentialsImpl() + credentials.token = 'token' + + assert credentials.valid + assert not credentials.expired + + credentials.expiry = ( + datetime.datetime.utcnow() - datetime.timedelta(seconds=60)) + + assert not credentials.valid + assert credentials.expired + + +def test_before_request(): + credentials = CredentialsImpl() + request = 'token' + headers = {} + + # First call should call refresh, setting the token. + credentials.before_request(request, 'http://example.com', 'GET', headers) + assert credentials.valid + assert credentials.token == 'token' + assert headers['authorization'] == 'Bearer token' + + request = 'token2' + headers = {} + + # Second call shouldn't call refresh. + credentials.before_request(request, 'http://example.com', 'GET', headers) + assert credentials.valid + assert credentials.token == 'token' + assert headers['authorization'] == 'Bearer token' + + +class ScopedCredentialsImpl(credentials.Scoped, CredentialsImpl): + @property + def requires_scopes(self): + return super(ScopedCredentialsImpl, self).requires_scopes + + def with_scopes(self, scopes): + raise NotImplementedError + + +def test_scoped_credentials_constructor(): + credentials = ScopedCredentialsImpl() + assert credentials._scopes is None + + +def test_scoped_credentials_scopes(): + credentials = ScopedCredentialsImpl() + credentials._scopes = ['one', 'two'] + assert credentials.scopes == ['one', 'two'] + assert credentials.has_scopes(['one']) + assert credentials.has_scopes(['two']) + assert credentials.has_scopes(['one', 'two']) + assert not credentials.has_scopes(['three']) + + +def test_scoped_credentials_requires_scopes(): + credentials = ScopedCredentialsImpl() + assert not credentials.requires_scopes From d43d87f389fc1f82d10e7ead38caf76a2749219a Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Fri, 14 Oct 2016 14:49:35 -0700 Subject: [PATCH 022/966] Add copy_docstring helper (#23) --- packages/google-auth/google/auth/_helpers.py | 14 +++++++++ packages/google-auth/tests/test__helpers.py | 32 ++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/packages/google-auth/google/auth/_helpers.py b/packages/google-auth/google/auth/_helpers.py index 9c49e06f4305..9fe2caa13027 100644 --- a/packages/google-auth/google/auth/_helpers.py +++ b/packages/google-auth/google/auth/_helpers.py @@ -21,6 +21,20 @@ from six.moves import urllib +def copy_docstring(source_class): + """Decorator that copies a method's docstring from another class.""" + def decorator(method): + """Decorator implementation.""" + if method.__doc__: + raise ValueError('Method already has a docstring.') + + source_method = getattr(source_class, method.__name__) + method.__doc__ = source_method.__doc__ + + return method + return decorator + + def utcnow(): """Returns the current UTC datetime. diff --git a/packages/google-auth/tests/test__helpers.py b/packages/google-auth/tests/test__helpers.py index 86cacefefe24..35c16d4f9aff 100644 --- a/packages/google-auth/tests/test__helpers.py +++ b/packages/google-auth/tests/test__helpers.py @@ -20,6 +20,38 @@ from google.auth import _helpers +class SourceClass(object): + def func(self): # pragma: NO COVER + """example docstring""" + pass + + +def test_copy_docstring_success(): + def func(): # pragma: NO COVER + pass + + _helpers.copy_docstring(SourceClass)(func) + + assert func.__doc__ == SourceClass.func.__doc__ + + +def test_copy_docstring_conflict(): + def func(): # pragma: NO COVER + """existing docstring""" + pass + + with pytest.raises(ValueError): + _helpers.copy_docstring(SourceClass)(func) + + +def test_copy_docstring_non_existing(): + def func2(): # pragma: NO COVER + pass + + with pytest.raises(AttributeError): + _helpers.copy_docstring(SourceClass)(func2) + + def test_utcnow(): assert isinstance(_helpers.utcnow(), datetime.datetime) From e4e3491457bbb34849988dbd7b78276c80e43823 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Fri, 14 Oct 2016 14:50:58 -0700 Subject: [PATCH 023/966] Fix some issues with copy_docstrings --- packages/google-auth/google/auth/_helpers.py | 22 ++++++++++++++++++-- packages/google-auth/tests/test__helpers.py | 1 - 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/packages/google-auth/google/auth/_helpers.py b/packages/google-auth/google/auth/_helpers.py index 9fe2caa13027..92c6c831741c 100644 --- a/packages/google-auth/google/auth/_helpers.py +++ b/packages/google-auth/google/auth/_helpers.py @@ -22,9 +22,27 @@ def copy_docstring(source_class): - """Decorator that copies a method's docstring from another class.""" + """Decorator that copies a method's docstring from another class. + + Args: + source_class (type): The class that has the documented method. + + Returns: + Callable: A decorator that will copy the docstring of the same + named method in the source class to the decorated method. + """ def decorator(method): - """Decorator implementation.""" + """Decorator implementation. + + Args: + method: The method to copy the docstring to. + + Returns: + Callable: the same method passed in with an updated docstring. + + Raises: + ValueError: if the method already has a docstring. + """ if method.__doc__: raise ValueError('Method already has a docstring.') diff --git a/packages/google-auth/tests/test__helpers.py b/packages/google-auth/tests/test__helpers.py index 35c16d4f9aff..d413adcabc29 100644 --- a/packages/google-auth/tests/test__helpers.py +++ b/packages/google-auth/tests/test__helpers.py @@ -23,7 +23,6 @@ class SourceClass(object): def func(self): # pragma: NO COVER """example docstring""" - pass def test_copy_docstring_success(): From d293883cbbaf943396c4f60838265de05727c1f9 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Fri, 14 Oct 2016 14:53:29 -0700 Subject: [PATCH 024/966] Fix some issues with copy_docstrings --- packages/google-auth/google/auth/_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/_helpers.py b/packages/google-auth/google/auth/_helpers.py index 92c6c831741c..5a6c3a86babd 100644 --- a/packages/google-auth/google/auth/_helpers.py +++ b/packages/google-auth/google/auth/_helpers.py @@ -35,7 +35,7 @@ def decorator(method): """Decorator implementation. Args: - method: The method to copy the docstring to. + method (Callable): The method to copy the docstring to. Returns: Callable: the same method passed in with an updated docstring. From b34162bbc4d434a71dc2ef1c5583a5c55c8b6778 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Mon, 17 Oct 2016 10:46:38 -0700 Subject: [PATCH 025/966] Add oauth2 credentials (#24) --- .../reference/google.oauth2.credentials.rst | 7 ++ .../google-auth/google/oauth2/credentials.py | 94 +++++++++++++++++++ .../tests/oauth2/test_credentials.py | 79 ++++++++++++++++ 3 files changed, 180 insertions(+) create mode 100644 packages/google-auth/docs/reference/google.oauth2.credentials.rst create mode 100644 packages/google-auth/google/oauth2/credentials.py create mode 100644 packages/google-auth/tests/oauth2/test_credentials.py diff --git a/packages/google-auth/docs/reference/google.oauth2.credentials.rst b/packages/google-auth/docs/reference/google.oauth2.credentials.rst new file mode 100644 index 000000000000..ca978d14822f --- /dev/null +++ b/packages/google-auth/docs/reference/google.oauth2.credentials.rst @@ -0,0 +1,7 @@ +google.oauth2.credentials module +================================ + +.. automodule:: google.oauth2.credentials + :members: + :inherited-members: + :show-inheritance: diff --git a/packages/google-auth/google/oauth2/credentials.py b/packages/google-auth/google/oauth2/credentials.py new file mode 100644 index 000000000000..9727e189ebd9 --- /dev/null +++ b/packages/google-auth/google/oauth2/credentials.py @@ -0,0 +1,94 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""OAuth 2.0 Credentials. + +This module provides credentials based on OAuth 2.0 access and refresh tokens. +These credentials usually access resources on behalf of a user (resource +owner). + +Specifically, this is intended to use access tokens acquired using the +`Authorization Code grant`_ and can refresh those tokens using a +optional `refresh token`_. + +Obtaining the initial access and refresh token is outside of the scope of this +module. Consult `rfc6749 section 4.1`_ for complete details on the +Authorization Code grant flow. + +.. _Authorization Code grant: https://tools.ietf.org/html/rfc6749#section-1.3.1 +.. _refresh token: https://tools.ietf.org/html/rfc6749#section-6 +.. _rfc6749 section 4.1: https://tools.ietf.org/html/rfc6749#section-4.1 +""" + +from google.auth import _helpers +from google.auth import credentials +from google.oauth2 import _client + + +class Credentials(credentials.Scoped, credentials.Credentials): + """Credentials using OAuth 2.0 access and refresh tokens.""" + + def __init__(self, token, refresh_token=None, token_uri=None, + client_id=None, client_secret=None, scopes=None): + """ + Args: + token (Optional(str)): The OAuth 2.0 access token. Can be None + if refresh information is provided. + refresh_token (str): The OAuth 2.0 refresh token. If specified, + credentials can be refreshed. + token_uri (str): The OAuth 2.0 authorization server's token + endpoint URI. Must be specified for refresh, can be left as + None if the token can not be refreshed. + client_id (str): The OAuth 2.0 client ID. Must be specified for + refresh, can be left as None if the token can not be refreshed. + client_secret(str): The OAuth 2.0 client secret. Must be specified + for refresh, can be left as None if the token can not be + refreshed. + scopes (Sequence[str]): The scopes that were originally used + to obtain authorization. This is a purely informative parameter + that can be used by :meth:`has_scopes`. OAuth 2.0 credentials + can not request additional scopes after authorization. + """ + super(Credentials, self).__init__() + self.token = token + self._refresh_token = refresh_token + self._scopes = scopes + self._token_uri = token_uri + self._client_id = client_id + self._client_secret = client_secret + + @property + def requires_scopes(self): + """False: OAuth 2.0 credentials have their scopes set when + the initial token is requested and can not be changed.""" + return False + + def with_scopes(self, scopes): + """Unavailable, OAuth 2.0 credentials can not be re-scoped. + + OAuth 2.0 credentials have their scopes set when the initial token is + requested and can not be changed. + """ + raise NotImplementedError( + 'OAuth 2.0 Credentials can not modify their scopes.') + + @_helpers.copy_docstring(credentials.Credentials) + def refresh(self, request): + access_token, refresh_token, expiry, _ = _client.refresh_grant( + request, self._token_uri, self._refresh_token, self._client_id, + self._client_secret) + + self.token = access_token + self.expiry = expiry + self._refresh_token = refresh_token diff --git a/packages/google-auth/tests/oauth2/test_credentials.py b/packages/google-auth/tests/oauth2/test_credentials.py new file mode 100644 index 000000000000..173bb00f7ff4 --- /dev/null +++ b/packages/google-auth/tests/oauth2/test_credentials.py @@ -0,0 +1,79 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import datetime + +import mock +import pytest + +from google.auth import _helpers +from google.oauth2 import credentials + + +class TestCredentials(object): + TOKEN_URI = 'https://example.com/oauth2/token' + REFRESH_TOKEN = 'refresh_token' + CLIENT_ID = 'client_id' + CLIENT_SECRET = 'client_secret' + + @pytest.fixture(autouse=True) + def credentials(self): + self.credentials = credentials.Credentials( + token=None, refresh_token=self.REFRESH_TOKEN, + token_uri=self.TOKEN_URI, client_id=self.CLIENT_ID, + client_secret=self.CLIENT_SECRET) + + def test_default_state(self): + assert not self.credentials.valid + # Expiration hasn't been set yet + assert not self.credentials.expired + # Scopes aren't required for these credentials + assert not self.credentials.requires_scopes + + def test_create_scoped(self): + with pytest.raises(NotImplementedError): + self.credentials.with_scopes(['email']) + + @mock.patch('google.oauth2._client.refresh_grant') + @mock.patch( + 'google.auth._helpers.utcnow', return_value=datetime.datetime.min) + def test_refresh_success(self, now_mock, refresh_grant_mock): + token = 'token' + expiry = _helpers.utcnow() + datetime.timedelta(seconds=500) + refresh_grant_mock.return_value = ( + # Access token + token, + # New refresh token + None, + # Expiry, + expiry, + # Extra data + {}) + request_mock = mock.Mock() + + # Refresh credentials + self.credentials.refresh(request_mock) + + # Check jwt grant call. + refresh_grant_mock.assert_called_with( + request_mock, self.TOKEN_URI, self.REFRESH_TOKEN, self.CLIENT_ID, + self.CLIENT_SECRET) + + # Check that the credentials have the token and expiry + assert self.credentials.token == token + assert self.credentials.expiry == expiry + + # Check that the credentials are valid (have a token and are not + # expired) + assert self.credentials.valid From 46f0479f61b71836108976aefabf6da2b63ec57b Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Mon, 17 Oct 2016 10:48:35 -0700 Subject: [PATCH 026/966] Add compute engine credentials (#22) --- .../google/auth/compute_engine/__init__.py | 9 ++ .../google/auth/compute_engine/credentials.py | 113 ++++++++++++++++++ .../google-auth/google/auth/credentials.py | 2 +- .../tests/compute_engine/test_credentials.py | 105 ++++++++++++++++ 4 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 packages/google-auth/google/auth/compute_engine/credentials.py create mode 100644 packages/google-auth/tests/compute_engine/test_credentials.py diff --git a/packages/google-auth/google/auth/compute_engine/__init__.py b/packages/google-auth/google/auth/compute_engine/__init__.py index e3a7f6c35405..3794be2f7d1f 100644 --- a/packages/google-auth/google/auth/compute_engine/__init__.py +++ b/packages/google-auth/google/auth/compute_engine/__init__.py @@ -11,3 +11,12 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + +"""Google Compute Engine authentication.""" + +from google.auth.compute_engine.credentials import Credentials + + +__all__ = [ + 'Credentials' +] diff --git a/packages/google-auth/google/auth/compute_engine/credentials.py b/packages/google-auth/google/auth/compute_engine/credentials.py new file mode 100644 index 000000000000..f0616d148b5b --- /dev/null +++ b/packages/google-auth/google/auth/compute_engine/credentials.py @@ -0,0 +1,113 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google Compute Engine credentials. + +This module provides authentication for application running on Google Compute +Engine using the Compute Engine metadata server. + +""" + +from google.auth import _helpers +from google.auth import credentials +from google.auth import exceptions +from google.auth.compute_engine import _metadata + + +class Credentials(credentials.Scoped, credentials.Credentials): + """Compute Engine Credentials. + + These credentials use the Google Compute Engine metadata server to obtain + OAuth 2.0 access tokens associated with the instance's service account. + + For more information about Compute Engine authentication, including how + to configure scopes, see the `Compute Engine authentication + documentation`_. + + .. note:: Compute Engine instances can be created with scopes and therefore + these credentials are considered to be 'scoped'. However, you can + not use :meth:`~google.auth.credentials.ScopedCredentials.with_scopes` + because it is not possible to change the scopes that the instance + has. Also note that + :meth:`~google.auth.credentials.ScopedCredentials.has_scopes` will not + work until the credentials have been refreshed. + + .. _Compute Engine authentication documentation: + https://cloud.google.com/compute/docs/authentication#using + """ + + def __init__(self, service_account_email='default'): + """ + Args: + service_account_email (str): The service account email to use, or + 'default'. A Compute Engine instance may have multiple service + accounts. + """ + super(Credentials, self).__init__() + self._service_account_email = service_account_email + + def _retrieve_info(self, request): + """Retrieve information about the service account. + + Updates the scopes and retrieves the full service account email. + + Args: + request (google.auth.transport.Request): The object used to make + HTTP requests. + """ + info = _metadata.get_service_account_info( + request, + service_account=self._service_account_email) + + self._service_account_email = info['email'] + self._scopes = _helpers.string_to_scopes(info['scopes']) + + def refresh(self, request): + """Refresh the access token and scopes. + + Args: + request (google.auth.transport.Request): The object used to make + HTTP requests. + + Raises: + google.auth.exceptions.RefreshError: If the Compute Engine metadata + service can't be reached if if the instance has not + credentials. + """ + try: + self._retrieve_info(request) + self.token, self.expiry = _metadata.get_service_account_token( + request, + service_account=self._service_account_email) + except exceptions.TransportError as exc: + raise exceptions.RefreshError(exc) + + @property + def requires_scopes(self): + """False: Compute Engine credentials can not be scoped.""" + return False + + def with_scopes(self, scopes): + """Unavailable, Compute Engine credentials can not be scoped. + + Scopes can only be set at Compute Engine instance creation time. + See the `Compute Engine authentication documentation`_ for details on + how to configure instance scopes. + + .. _Compute Engine authentication documentation: + https://cloud.google.com/compute/docs/authentication#using + """ + raise NotImplementedError( + 'Compute Engine credentials can not set scopes. Scopes must be ' + 'set when the Compute Engine instance is created.') diff --git a/packages/google-auth/google/auth/credentials.py b/packages/google-auth/google/auth/credentials.py index b10e63e14fa7..ef28bd7920ee 100644 --- a/packages/google-auth/google/auth/credentials.py +++ b/packages/google-auth/google/auth/credentials.py @@ -102,7 +102,7 @@ def before_request(self, request, method, url, headers): apply the token to the authentication header. Args: - request (google.auth.transport.Request): the object used to make + request (google.auth.transport.Request): The object used to make HTTP requests. method (str): The request's HTTP method. url (str): The request's URI. diff --git a/packages/google-auth/tests/compute_engine/test_credentials.py b/packages/google-auth/tests/compute_engine/test_credentials.py new file mode 100644 index 000000000000..90ce2fecec11 --- /dev/null +++ b/packages/google-auth/tests/compute_engine/test_credentials.py @@ -0,0 +1,105 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import datetime + +import mock +import pytest + +from google.auth import exceptions +from google.auth.compute_engine import credentials + + +class TestCredentials(object): + credentials = None + + @pytest.fixture(autouse=True) + def credentials_fixture(self): + self.credentials = credentials.Credentials() + + def test_default_state(self): + assert not self.credentials.valid + # Expiration hasn't been set yet + assert not self.credentials.expired + # Scopes aren't needed + assert not self.credentials.requires_scopes + + @mock.patch( + 'google.auth._helpers.utcnow', return_value=datetime.datetime.min) + @mock.patch('google.auth.compute_engine._metadata.get') + def test_refresh_success(self, get_mock, now_mock): + get_mock.side_effect = [{ + # First request is for sevice account info. + 'email': 'service-account@example.com', + 'scopes': 'one two' + }, { + # Second request is for the token. + 'access_token': 'token', + 'expires_in': 500 + }] + + # Refresh credentials + self.credentials.refresh(None) + + # Check that the credentials have the token and proper expiration + assert self.credentials.token == 'token' + assert self.credentials.expiry == ( + datetime.datetime.min + datetime.timedelta(seconds=500)) + + # Check the credential info + assert (self.credentials._service_account_email == + 'service-account@example.com') + assert self.credentials._scopes == ['one', 'two'] + + # Check that the credentials are valid (have a token and are not + # expired) + assert self.credentials.valid + + @mock.patch('google.auth.compute_engine._metadata.get') + def test_refresh_error(self, get_mock): + get_mock.side_effect = exceptions.TransportError('http error') + + with pytest.raises(exceptions.RefreshError) as excinfo: + self.credentials.refresh(None) + + assert excinfo.match(r'http error') + + @mock.patch('google.auth.compute_engine._metadata.get') + def test_before_request_refreshes(self, get_mock): + get_mock.side_effect = [{ + # First request is for sevice account info. + 'email': 'service-account@example.com', + 'scopes': 'one two' + }, { + # Second request is for the token. + 'access_token': 'token', + 'expires_in': 500 + }] + + # Credentials should start as invalid + assert not self.credentials.valid + + # before_request should cause a refresh + self.credentials.before_request( + mock.Mock(), 'GET', 'http://example.com?a=1#3', {}) + + # The refresh endpoint should've been called. + assert get_mock.called + + # Credentials should now be valid. + assert self.credentials.valid + + def test_with_scopes(self): + with pytest.raises(NotImplementedError): + self.credentials.with_scopes(['one', 'two']) From 56c4511fe10caf3a2aeb7a96191d1e9e10178afb Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Mon, 17 Oct 2016 10:49:57 -0700 Subject: [PATCH 027/966] Add service account credentials (#25) * Add service account credentials * Address review comments * Fix lint * Fix docstring * Address review comments --- .../google.oauth2.service_account.rst | 7 + .../google/oauth2/service_account.py | 278 ++++++++++++++++++ .../tests/data/service_account.json | 10 + .../tests/oauth2/test_service_account.py | 209 +++++++++++++ 4 files changed, 504 insertions(+) create mode 100644 packages/google-auth/docs/reference/google.oauth2.service_account.rst create mode 100644 packages/google-auth/google/oauth2/service_account.py create mode 100644 packages/google-auth/tests/data/service_account.json create mode 100644 packages/google-auth/tests/oauth2/test_service_account.py diff --git a/packages/google-auth/docs/reference/google.oauth2.service_account.rst b/packages/google-auth/docs/reference/google.oauth2.service_account.rst new file mode 100644 index 000000000000..cc4e43899d74 --- /dev/null +++ b/packages/google-auth/docs/reference/google.oauth2.service_account.rst @@ -0,0 +1,7 @@ +google.oauth2.service_account module +==================================== + +.. automodule:: google.oauth2.service_account + :members: + :inherited-members: + :show-inheritance: diff --git a/packages/google-auth/google/oauth2/service_account.py b/packages/google-auth/google/oauth2/service_account.py new file mode 100644 index 000000000000..4c4c1c03aede --- /dev/null +++ b/packages/google-auth/google/oauth2/service_account.py @@ -0,0 +1,278 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Service Accounts: JSON Web Token (JWT) Profile for OAuth 2.0 + +This module implements the JWT Profile for OAuth 2.0 Authorization Grants +as defined by `RFC 7523`_ with particular support for how this RFC is +implemented in Google's infrastructure. Google refers to these credentials +as *Service Accounts*. + +Service accounts are used for server-to-server communication, such as +interactions between a web application server and a Google service. The +service account belongs to your application instead of to an individual end +user. In contrast to other OAuth 2.0 profiles, no users are involved and your +application "acts" as the service account. + +Typically an application uses a service account when the application uses +Google APIs to work with its own data rather than a user's data. For example, +an application that uses Google Cloud Datastore for data persistence would use +a service account to authenticate its calls to the Google Cloud Datastore API. +However, an application that needs to access a user's Drive documents would +use the normal OAuth 2.0 profile. + +Additionally, Google Apps domain administrators can grant service accounts +`domain-wide delegation`_ authority to access user data on behalf of users in +the domain. + +This profile uses a JWT to acquire an OAuth 2.0 access token. The JWT is used +in place of the usual authorization token returned during the standard +OAuth 2.0 Authorization Code grant. The JWT is only used for this purpose, as +the acquired access token is used as the bearer token when making requests +using these credentials. + +This profile differs from normal OAuth 2.0 profile because no user consent +step is required. The use of the private key allows this profile to assert +identity directly. + +This profile also differs from the :mod:`google.auth.jwt` authentication +because the JWT credentials use the JWT directly as the bearer token. This +profile instead only uses the JWT to obtain an OAuth 2.0 access token. The +obtained OAuth 2.0 access token is used as the bearer token. + +Domain-wide delegation +---------------------- + +Domain-wide delegation allows a service account to access user data on +behalf of any user in a Google Apps domain without consent from the user. +For example, an application that uses the Google Calendar API to add events to +the calendars of all users in a Google Apps domain would use a service account +to access the Google Calendar API on behalf of users. + +The Google Apps administrator must explicitly authorize the service account to +do this. This authorization step is referred to as "delegating domain-wide +authority" to a service account. + +You can use domain-wise delegation by creating a set of credentials with a +specific subject using :meth:`~Credentials.with_subject`. + +.. _RFC 7523: https://tools.ietf.org/html/rfc7523 +""" + +import datetime +import io +import json + +from google.auth import _helpers +from google.auth import credentials +from google.auth import crypt +from google.auth import jwt +from google.oauth2 import _client + +_DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in sections + + +class Credentials(credentials.Signing, + credentials.Scoped, + credentials.Credentials): + """Service account credentials + + Usually, you'll create these credentials with one of the helper + constructors. To create credentials using a Google service account + private key JSON file:: + + credentials = service_account.Credentials.from_service_account_file( + 'service-account.json') + + Or if you already have the service account file loaded:: + + service_account_info = json.load(open('service_account.json')) + credentials = service_account.Credentials.from_service_account_info( + service_account_info) + + Both helper methods pass on arguments to the constructor, so you can + specify additional scopes and a subject if necessary:: + + credentials = service_account.Credentials.from_service_account_file( + 'service-account.json', + scopes=['email'], + subject='user@example.com') + + The credentials are considered immutable. If you want to modify the scopes + or the subject used for delegation, use :meth:`with_scopes` or + :meth:`with_subject`:: + + scoped_credentials = credentials.with_scopes(['email']) + delegated_credentials = credentials.with_subject(subject) + """ + + def __init__(self, signer, service_account_email, token_uri, scopes=None, + subject=None, additional_claims=None): + """ + Args: + signer (google.auth.crypt.Signer): The signer used to sign JWTs. + service_account_email (str): The service account's email. + scopes (Sequence[str]): Scopes to request during the authorization + grant. + token_uri (str): The OAuth 2.0 Token URI. + subject (str): For domain-wide delegation, the email address of the + user to for which to request delegated access. + additional_claims (Mapping[str, str]): Any additional claims for + the JWT assertion used in the authorization grant. + + .. note:: Typically one of the helper constructors + :meth:`from_service_account_file` or + :meth:`from_service_account_info` are used instead of calling the + constructor directly. + """ + super(Credentials, self).__init__() + + self._scopes = scopes + self._signer = signer + self._service_account_email = service_account_email + self._subject = subject + self._token_uri = token_uri + + if additional_claims is not None: + self._additional_claims = additional_claims + else: + self._additional_claims = {} + + @classmethod + def from_service_account_info(cls, info, **kwargs): + """Creates a Credentials instance from parsed service account info. + + Args: + info (Mapping[str, str]): The service account info in Google + format. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.service_account.Credentials: The constructed + credentials. + + Raises: + ValueError: If the info is not in the expected format. + """ + try: + email = info['client_email'] + key_id = info['private_key_id'] + private_key = info['private_key'] + token_uri = info['token_uri'] + except KeyError: + raise ValueError( + 'Service account info was not in the expected format.') + + signer = crypt.Signer.from_string(private_key, key_id) + + return cls( + signer, service_account_email=email, token_uri=token_uri, **kwargs) + + @classmethod + def from_service_account_file(cls, filename, **kwargs): + """Creates a Credentials instance from a service account json file. + + Args: + filename (str): The path to the service account json file. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.service_account.Credentials: The constructed + credentials. + """ + with io.open(filename, 'r', encoding='utf-8') as json_file: + info = json.load(json_file) + return cls.from_service_account_info(info, **kwargs) + + @property + def requires_scopes(self): + """Checks if the credentials requires scopes. + + Returns: + bool: True if there are no scopes set otherwise False. + """ + return True if not self._scopes else False + + @_helpers.copy_docstring(credentials.Scoped) + def with_scopes(self, scopes): + return Credentials( + self._signer, + service_account_email=self._service_account_email, + scopes=scopes, + token_uri=self._token_uri, + subject=self._subject, + additional_claims=self._additional_claims.copy()) + + def with_subject(self, subject): + """Create a copy of these credentials with the specified subject. + + Args: + subject (str): The subject claim. + + Returns: + google.auth.service_account.Credentials: A new credentials + instance. + """ + return Credentials( + self._signer, + service_account_email=self._service_account_email, + scopes=self._scopes, + token_uri=self._token_uri, + subject=subject, + additional_claims=self._additional_claims.copy()) + + def _make_authorization_grant_assertion(self): + """Create the OAuth 2.0 assertion. + + This assertion is used during the OAuth 2.0 grant to acquire an + access token. + + Returns: + bytes: The authorization grant assertion. + """ + now = _helpers.utcnow() + lifetime = datetime.timedelta(seconds=_DEFAULT_TOKEN_LIFETIME_SECS) + expiry = now + lifetime + + payload = { + 'iat': _helpers.datetime_to_secs(now), + 'exp': _helpers.datetime_to_secs(expiry), + # The issuer must be the service account email. + 'iss': self._service_account_email, + # The audience must be the auth token endpoint's URI + 'aud': self._token_uri, + 'scope': _helpers.scopes_to_string(self._scopes or ()) + } + + payload.update(self._additional_claims) + + # The subject can be a user email for domain-wide delegation. + if self._subject: + payload.setdefault('sub', self._subject) + + token = jwt.encode(self._signer, payload) + + return token + + @_helpers.copy_docstring(credentials.Credentials) + def refresh(self, request): + assertion = self._make_authorization_grant_assertion() + access_token, expiry, _ = _client.jwt_grant( + request, self._token_uri, assertion) + self.token = access_token + self.expiry = expiry + + @_helpers.copy_docstring(credentials.Signing) + def sign_bytes(self, message): + return self._signer.sign(message) diff --git a/packages/google-auth/tests/data/service_account.json b/packages/google-auth/tests/data/service_account.json new file mode 100644 index 000000000000..9e76f4d35581 --- /dev/null +++ b/packages/google-auth/tests/data/service_account.json @@ -0,0 +1,10 @@ +{ + "type": "service_account", + "project_id": "example-project", + "private_key_id": "1", + "private_key": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEA4ej0p7bQ7L/r4rVGUz9RN4VQWoej1Bg1mYWIDYslvKrk1gpj\n7wZgkdmM7oVK2OfgrSj/FCTkInKPqaCR0gD7K80q+mLBrN3PUkDrJQZpvRZIff3/\nxmVU1WeruQLFJjnFb2dqu0s/FY/2kWiJtBCakXvXEOb7zfbINuayL+MSsCGSdVYs\nSliS5qQpgyDap+8b5fpXZVJkq92hrcNtbkg7hCYUJczt8n9hcCTJCfUpApvaFQ18\npe+zpyl4+WzkP66I28hniMQyUlA1hBiskT7qiouq0m8IOodhv2fagSZKjOTTU2xk\nSBc//fy3ZpsL7WqgsZS7Q+0VRK8gKfqkxg5OYQIDAQABAoIBAQDGGHzQxGKX+ANk\nnQi53v/c6632dJKYXVJC+PDAz4+bzU800Y+n/bOYsWf/kCp94XcG4Lgsdd0Gx+Zq\nHD9CI1IcqqBRR2AFscsmmX6YzPLTuEKBGMW8twaYy3utlFxElMwoUEsrSWRcCA1y\nnHSDzTt871c7nxCXHxuZ6Nm/XCL7Bg8uidRTSC1sQrQyKgTPhtQdYrPQ4WZ1A4J9\nIisyDYmZodSNZe5P+LTJ6M1SCgH8KH9ZGIxv3diMwzNNpk3kxJc9yCnja4mjiGE2\nYCNusSycU5IhZwVeCTlhQGcNeV/skfg64xkiJE34c2y2ttFbdwBTPixStGaF09nU\nZ422D40BAoGBAPvVyRRsC3BF+qZdaSMFwI1yiXY7vQw5+JZh01tD28NuYdRFzjcJ\nvzT2n8LFpj5ZfZFvSMLMVEFVMgQvWnN0O6xdXvGov6qlRUSGaH9u+TCPNnIldjMP\nB8+xTwFMqI7uQr54wBB+Poq7dVRP+0oHb0NYAwUBXoEuvYo3c/nDoRcZAoGBAOWl\naLHjMv4CJbArzT8sPfic/8waSiLV9Ixs3Re5YREUTtnLq7LoymqB57UXJB3BNz/2\neCueuW71avlWlRtE/wXASj5jx6y5mIrlV4nZbVuyYff0QlcG+fgb6pcJQuO9DxMI\naqFGrWP3zye+LK87a6iR76dS9vRU+bHZpSVvGMKJAoGAFGt3TIKeQtJJyqeUWNSk\nklORNdcOMymYMIlqG+JatXQD1rR6ThgqOt8sgRyJqFCVT++YFMOAqXOBBLnaObZZ\nCFbh1fJ66BlSjoXff0W+SuOx5HuJJAa5+WtFHrPajwxeuRcNa8jwxUsB7n41wADu\nUqWWSRedVBg4Ijbw3nWwYDECgYB0pLew4z4bVuvdt+HgnJA9n0EuYowVdadpTEJg\nsoBjNHV4msLzdNqbjrAqgz6M/n8Ztg8D2PNHMNDNJPVHjJwcR7duSTA6w2p/4k28\nbvvk/45Ta3XmzlxZcZSOct3O31Cw0i2XDVc018IY5be8qendDYM08icNo7vQYkRH\n504kQQKBgQDjx60zpz8ozvm1XAj0wVhi7GwXe+5lTxiLi9Fxq721WDxPMiHDW2XL\nYXfFVy/9/GIMvEiGYdmarK1NW+VhWl1DC5xhDg0kvMfxplt4tynoq1uTsQTY31Mx\nBeF5CT/JuNYk3bEBF0H/Q3VGO1/ggVS+YezdFbLWIRoMnLj6XCFEGg==\n-----END RSA PRIVATE KEY-----\n", + "client_email": "service-account@example.com", + "client_id": "1234", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://accounts.google.com/o/oauth2/token" +} diff --git a/packages/google-auth/tests/oauth2/test_service_account.py b/packages/google-auth/tests/oauth2/test_service_account.py new file mode 100644 index 000000000000..473d440dac36 --- /dev/null +++ b/packages/google-auth/tests/oauth2/test_service_account.py @@ -0,0 +1,209 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import datetime +import json +import os + +import mock +import pytest + +from google.auth import _helpers +from google.auth import crypt +from google.auth import jwt +from google.oauth2 import service_account + + +DATA_DIR = os.path.join(os.path.dirname(__file__), '..', 'data') + +with open(os.path.join(DATA_DIR, 'privatekey.pem'), 'rb') as fh: + PRIVATE_KEY_BYTES = fh.read() + +with open(os.path.join(DATA_DIR, 'public_cert.pem'), 'rb') as fh: + PUBLIC_CERT_BYTES = fh.read() + +with open(os.path.join(DATA_DIR, 'other_cert.pem'), 'rb') as fh: + OTHER_CERT_BYTES = fh.read() + +SERVICE_ACCOUNT_JSON_FILE = os.path.join(DATA_DIR, 'service_account.json') + +with open(SERVICE_ACCOUNT_JSON_FILE, 'r') as fh: + SERVICE_ACCOUNT_INFO = json.load(fh) + + +@pytest.fixture(scope='module') +def signer(): + return crypt.Signer.from_string(PRIVATE_KEY_BYTES, '1') + + +class TestCredentials(object): + SERVICE_ACCOUNT_EMAIL = 'service-account@example.com' + TOKEN_URI = 'https://example.com/oauth2/token' + credentials = None + + @pytest.fixture(autouse=True) + def credentials_fixture(self, signer): + self.credentials = service_account.Credentials( + signer, self.SERVICE_ACCOUNT_EMAIL, self.TOKEN_URI) + + def test_from_service_account_info(self): + with open(SERVICE_ACCOUNT_JSON_FILE, 'r') as fh: + info = json.load(fh) + + credentials = service_account.Credentials.from_service_account_info( + info) + + assert credentials._signer.key_id == info['private_key_id'] + assert credentials._service_account_email == info['client_email'] + assert credentials._token_uri == info['token_uri'] + + def test_from_service_account_info_args(self): + info = SERVICE_ACCOUNT_INFO.copy() + scopes = ['email', 'profile'] + subject = 'subject' + additional_claims = {'meta': 'data'} + + credentials = service_account.Credentials.from_service_account_info( + info, scopes=scopes, subject=subject, + additional_claims=additional_claims) + + assert credentials._signer.key_id == info['private_key_id'] + assert credentials._service_account_email == info['client_email'] + assert credentials._token_uri == info['token_uri'] + assert credentials._scopes == scopes + assert credentials._subject == subject + assert credentials._additional_claims == additional_claims + + def test_from_service_account_bad_key(self): + info = SERVICE_ACCOUNT_INFO.copy() + info['private_key'] = 'garbage' + + with pytest.raises(ValueError) as excinfo: + service_account.Credentials.from_service_account_info(info) + + assert excinfo.match(r'No key could be detected') + + def test_from_service_account_bad_format(self): + with pytest.raises(ValueError): + service_account.Credentials.from_service_account_info({}) + + def test_from_service_account_file(self): + info = SERVICE_ACCOUNT_INFO.copy() + + credentials = service_account.Credentials.from_service_account_file( + SERVICE_ACCOUNT_JSON_FILE) + + assert credentials._signer.key_id == info['private_key_id'] + assert credentials._service_account_email == info['client_email'] + assert credentials._token_uri == info['token_uri'] + + def test_from_service_account_file_args(self): + info = SERVICE_ACCOUNT_INFO.copy() + scopes = ['email', 'profile'] + subject = 'subject' + additional_claims = {'meta': 'data'} + + credentials = service_account.Credentials.from_service_account_file( + SERVICE_ACCOUNT_JSON_FILE, subject=subject, + scopes=scopes, additional_claims=additional_claims) + + assert credentials._signer.key_id == info['private_key_id'] + assert credentials._service_account_email == info['client_email'] + assert credentials._token_uri == info['token_uri'] + assert credentials._scopes == scopes + assert credentials._subject == subject + assert credentials._additional_claims == additional_claims + + def test_default_state(self): + assert not self.credentials.valid + # Expiration hasn't been set yet + assert not self.credentials.expired + # Scopes haven't been specified yet + assert self.credentials.requires_scopes + + def test_sign_bytes(self): + to_sign = b'123' + signature = self.credentials.sign_bytes(to_sign) + assert crypt.verify_signature(to_sign, signature, PUBLIC_CERT_BYTES) + + def test_create_scoped(self): + scopes = ['email', 'profile'] + credentials = self.credentials.with_scopes(scopes) + assert credentials._scopes == scopes + + def test__make_authorization_grant_assertion(self): + token = self.credentials._make_authorization_grant_assertion() + payload = jwt.decode(token, PUBLIC_CERT_BYTES) + assert payload['iss'] == self.SERVICE_ACCOUNT_EMAIL + assert payload['aud'] == self.TOKEN_URI + + def test__make_authorization_grant_assertion_scoped(self): + scopes = ['email', 'profile'] + credentials = self.credentials.with_scopes(scopes) + token = credentials._make_authorization_grant_assertion() + payload = jwt.decode(token, PUBLIC_CERT_BYTES) + assert payload['scope'] == 'email profile' + + def test__make_authorization_grant_assertion_subject(self): + subject = 'user@example.com' + credentials = self.credentials.with_subject(subject) + token = credentials._make_authorization_grant_assertion() + payload = jwt.decode(token, PUBLIC_CERT_BYTES) + assert payload['sub'] == subject + + @mock.patch('google.oauth2._client.jwt_grant') + def test_refresh_success(self, jwt_grant_mock): + token = 'token' + jwt_grant_mock.return_value = ( + token, _helpers.utcnow() + datetime.timedelta(seconds=500), None) + request_mock = mock.Mock() + + # Refresh credentials + self.credentials.refresh(request_mock) + + # Check jwt grant call. + assert jwt_grant_mock.called + request, token_uri, assertion = jwt_grant_mock.call_args[0] + assert request == request_mock + assert token_uri == self.credentials._token_uri + assert jwt.decode(assertion, PUBLIC_CERT_BYTES) + # No further assertion done on the token, as there are separate tests + # for checking the authorization grant assertion. + + # Check that the credentials have the token. + assert self.credentials.token == token + + # Check that the credentials are valid (have a token and are not + # expired) + assert self.credentials.valid + + @mock.patch('google.oauth2._client.jwt_grant') + def test_before_request_refreshes(self, jwt_grant_mock): + token = 'token' + jwt_grant_mock.return_value = ( + token, _helpers.utcnow() + datetime.timedelta(seconds=500), None) + request_mock = mock.Mock() + + # Credentials should start as invalid + assert not self.credentials.valid + + # before_request should cause a refresh + self.credentials.before_request( + request_mock, 'GET', 'http://example.com?a=1#3', {}) + + # The refresh endpoint should've been called. + assert jwt_grant_mock.called + + # Credentials should now be valid. + assert self.credentials.valid From 1e3cecd2f0e86aa73f92c6305d98a1571e74c063 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Mon, 17 Oct 2016 11:23:47 -0700 Subject: [PATCH 028/966] Add JWT credentials (#21) --- packages/google-auth/google/auth/jwt.py | 242 ++++++++++++++++++++++++ packages/google-auth/tests/test_jwt.py | 151 +++++++++++++++ 2 files changed, 393 insertions(+) diff --git a/packages/google-auth/google/auth/jwt.py b/packages/google-auth/google/auth/jwt.py index f68fc4b4df9d..394a7d52829f 100644 --- a/packages/google-auth/google/auth/jwt.py +++ b/packages/google-auth/google/auth/jwt.py @@ -42,8 +42,13 @@ import base64 import collections +import datetime +import io import json +from six.moves import urllib + +from google.auth import credentials from google.auth import crypt from google.auth import _helpers @@ -234,3 +239,240 @@ def decode(token, certs=None, verify=True, audience=None): claim_audience, audience)) return payload + + +class Credentials(credentials.Signing, + credentials.Credentials): + """Credentials that use a JWT as the bearer token. + + These credentials require an "audience" claim. This claim identifies the + intended recipient of the bearer token. You can set the audience when + you construct these credentials, however, these credentials can also set + the audience claim automatically if not specified. In this case, whenever + a request is made the credentials will automatically generate a one-time + JWT with the request URI as the audience. + + The constructor arguments determine the claims for the JWT that is + sent with requests. Usually, you'll construct these credentials with + one of the helper constructors as shown in the next section. + + To create JWT credentials using a Google service account private key + JSON file:: + + credentials = jwt.Credentials.from_service_account_file( + 'service-account.json') + + If you already have the service account file loaded and parsed:: + + service_account_info = json.load(open('service_account.json')) + credentials = jwt.Credentials.from_service_account_info( + service_account_info) + + Both helper methods pass on arguments to the constructor, so you can + specify the JWT claims:: + + credentials = jwt.Credentials.from_service_account_file( + 'service-account.json', + audience='https://speech.googleapis.com', + additional_claims={'meta': 'data'}) + + You can also construct the credentials directly if you have a + :class:`~google.auth.crypt.Signer` instance:: + + credentials = jwt.Credentials( + signer, issuer='your-issuer', subject='your-subject') + + The claims are considered immutable. If you want to modify the claims, + you can easily create another instance using :meth:`with_claims`:: + + new_credentials = credentials.with_claims( + audience='https://vision.googleapis.com') + """ + + def __init__(self, signer, issuer=None, subject=None, audience=None, + additional_claims=None, + token_lifetime=_DEFAULT_TOKEN_LIFETIME_SECS): + """ + Args: + signer (google.auth.crypt.Signer): The signer used to sign JWTs. + issuer (str): The `iss` claim. + subject (str): The `sub` claim. + audience (str): the `aud` claim. The intended audience for the + credentials. If not specified, a new JWT will be generated for + every request and will use the request URI as the audience. + additional_claims (Mapping[str, str]): Any additional claims for + the JWT payload. + token_lifetime (int): The amount of time in seconds for + which the token is valid. Defaults to 1 hour. + """ + super(Credentials, self).__init__() + self._signer = signer + self._issuer = issuer + self._subject = subject + self._audience = audience + self._additional_claims = additional_claims or {} + self._token_lifetime = token_lifetime + + @classmethod + def from_service_account_info(cls, info, **kwargs): + """Creates a Credentials instance from parsed service account info. + + Args: + info (Mapping[str, str]): The service account info in Google + format. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.jwt.Credentials: The constructed credentials. + + Raises: + ValueError: If the info is not in the expected format. + """ + + try: + email = info['client_email'] + key_id = info['private_key_id'] + private_key = info['private_key'] + except KeyError: + raise ValueError( + 'Service account info was not in the expected format.') + + signer = crypt.Signer.from_string(private_key, key_id) + + kwargs.setdefault('subject', email) + return cls(signer, issuer=email, **kwargs) + + @classmethod + def from_service_account_file(cls, filename, **kwargs): + """Creates a Credentials instance from a service account json file. + + Args: + filename (str): The path to the service account json file. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.jwt.Credentials: The constructed credentials. + """ + with io.open(filename, 'r', encoding='utf-8') as json_file: + info = json.load(json_file) + return cls.from_service_account_info(info, **kwargs) + + def with_claims(self, issuer=None, subject=None, audience=None, + additional_claims=None): + """Returns a copy of these credentials with modified claims. + + Args: + issuer (str): The `iss` claim. If unspecified the current issuer + claim will be used. + subject (str): The `sub` claim. If unspecified the current subject + claim will be used. + audience (str): the `aud` claim. If not specified, a new + JWT will be generated for every request and will use + the request URI as the audience. + additional_claims (Mapping[str, str]): Any additional claims for + the JWT payload. This will be merged with the current + additional claims. + + Returns: + google.auth.jwt.Credentials: A new credentials instance. + """ + return Credentials( + self._signer, + issuer=issuer if issuer is not None else self._issuer, + subject=subject if subject is not None else self._subject, + audience=audience if audience is not None else self._audience, + additional_claims=self._additional_claims.copy().update( + additional_claims or {})) + + def _make_jwt(self, audience=None): + """Make a signed JWT. + + Args: + audience (str): Overrides the instance's current audience claim. + + Returns: + Tuple(bytes, datetime): The encoded JWT and the expiration. + """ + now = _helpers.utcnow() + lifetime = datetime.timedelta(seconds=self._token_lifetime) + expiry = now + lifetime + + payload = { + 'iss': self._issuer, + 'sub': self._subject or self._issuer, + 'iat': _helpers.datetime_to_secs(now), + 'exp': _helpers.datetime_to_secs(expiry), + 'aud': audience or self._audience, + } + + payload.update(self._additional_claims) + + jwt = encode(self._signer, payload) + + return jwt, expiry + + def _make_one_time_jwt(self, uri): + """Makes a one-off JWT with the URI as the audience. + + Args: + uri (str): The request URI. + + Returns: + bytes: The encoded JWT. + """ + parts = urllib.parse.urlsplit(uri) + # Strip query string and fragment + audience = urllib.parse.urlunsplit( + (parts.scheme, parts.netloc, parts.path, None, None)) + token, _ = self._make_jwt(audience=audience) + return token + + def refresh(self, request): + """Refreshes the access token. + + Args: + request (Any): Unused. + """ + # pylint: disable=unused-argument + # (pylint doesn't correctly recognize overridden methods.) + self.token, self.expiry = self._make_jwt() + + def sign_bytes(self, message): + """Signs the given message. + + Args: + message (bytes): The message to sign. + + Returns: + bytes: The message signature. + """ + return self._signer.sign(message) + + def before_request(self, request, method, url, headers): + """Performs credential-specific before request logic. + + If an audience is specified it will refresh the credentials if + necessary. If no audience is specified it will generate a one-time + token for the request URI. In either case, it will set the + authorization header in headers to the token. + + Args: + request (Any): Unused. + method (str): The request's HTTP method. + url (str): The request's URI. + headers (Mapping): The request's headers. + """ + # pylint: disable=unused-argument + # (pylint doesn't correctly recognize overridden methods.) + + # If this set of credentials has a pre-set audience, just ensure that + # there is a valid token and apply the auth headers. + if self._audience: + if not self.valid: + self.refresh(request) + self.apply(headers) + # Otherwise, generate a one-time token using the URL + # (without the query string and fragment) as the audience. + else: + token = self._make_one_time_jwt(url) + self.apply(headers, token=token) diff --git a/packages/google-auth/tests/test_jwt.py b/packages/google-auth/tests/test_jwt.py index 69628e5c9c96..b6c07df63072 100644 --- a/packages/google-auth/tests/test_jwt.py +++ b/packages/google-auth/tests/test_jwt.py @@ -14,8 +14,10 @@ import base64 import datetime +import json import os +import mock import pytest from google.auth import _helpers @@ -34,6 +36,11 @@ with open(os.path.join(DATA_DIR, 'other_cert.pem'), 'rb') as fh: OTHER_CERT_BYTES = fh.read() +SERVICE_ACCOUNT_JSON_FILE = os.path.join(DATA_DIR, 'service_account.json') + +with open(SERVICE_ACCOUNT_JSON_FILE, 'r') as fh: + SERVICE_ACCOUNT_INFO = json.load(fh) + @pytest.fixture def signer(): @@ -187,3 +194,147 @@ def test_roundtrip_explicit_key_id(token_factory): certs = {'2': OTHER_CERT_BYTES, '3': PUBLIC_CERT_BYTES} payload = jwt.decode(token, certs) assert payload['user'] == 'billy bob' + + +class TestCredentials: + SERVICE_ACCOUNT_EMAIL = 'service-account@example.com' + SUBJECT = 'subject' + AUDIENCE = 'audience' + ADDITIONAL_CLAIMS = {'meta': 'data'} + credentials = None + + @pytest.fixture(autouse=True) + def credentials_fixture(self, signer): + self.credentials = jwt.Credentials( + signer, self.SERVICE_ACCOUNT_EMAIL) + + def test_from_service_account_info(self): + with open(SERVICE_ACCOUNT_JSON_FILE, 'r') as fh: + info = json.load(fh) + + credentials = jwt.Credentials.from_service_account_info(info) + + assert credentials._signer.key_id == info['private_key_id'] + assert credentials._issuer == info['client_email'] + assert credentials._subject == info['client_email'] + + def test_from_service_account_info_args(self): + info = SERVICE_ACCOUNT_INFO.copy() + + credentials = jwt.Credentials.from_service_account_info( + info, subject=self.SUBJECT, audience=self.AUDIENCE, + additional_claims=self.ADDITIONAL_CLAIMS) + + assert credentials._signer.key_id == info['private_key_id'] + assert credentials._issuer == info['client_email'] + assert credentials._subject == self.SUBJECT + assert credentials._audience == self.AUDIENCE + assert credentials._additional_claims == self.ADDITIONAL_CLAIMS + + def test_from_service_account_bad_private_key(self): + info = SERVICE_ACCOUNT_INFO.copy() + info['private_key'] = 'garbage' + + with pytest.raises(ValueError) as excinfo: + jwt.Credentials.from_service_account_info(info) + + assert excinfo.match(r'No key could be detected') + + def test_from_service_account_bad_format(self): + with pytest.raises(ValueError): + jwt.Credentials.from_service_account_info({}) + + def test_from_service_account_file(self): + info = SERVICE_ACCOUNT_INFO.copy() + + credentials = jwt.Credentials.from_service_account_file( + SERVICE_ACCOUNT_JSON_FILE) + + assert credentials._signer.key_id == info['private_key_id'] + assert credentials._issuer == info['client_email'] + assert credentials._subject == info['client_email'] + + def test_from_service_account_file_args(self): + info = SERVICE_ACCOUNT_INFO.copy() + + credentials = jwt.Credentials.from_service_account_file( + SERVICE_ACCOUNT_JSON_FILE, subject=self.SUBJECT, + audience=self.AUDIENCE, additional_claims=self.ADDITIONAL_CLAIMS) + + assert credentials._signer.key_id == info['private_key_id'] + assert credentials._issuer == info['client_email'] + assert credentials._subject == self.SUBJECT + assert credentials._audience == self.AUDIENCE + assert credentials._additional_claims == self.ADDITIONAL_CLAIMS + + def test_default_state(self): + assert not self.credentials.valid + # Expiration hasn't been set yet + assert not self.credentials.expired + + def test_sign_bytes(self): + to_sign = b'123' + signature = self.credentials.sign_bytes(to_sign) + assert crypt.verify_signature(to_sign, signature, PUBLIC_CERT_BYTES) + + def _verify_token(self, token): + payload = jwt.decode(token, PUBLIC_CERT_BYTES) + assert payload['iss'] == self.SERVICE_ACCOUNT_EMAIL + return payload + + def test_refresh(self): + self.credentials.refresh(None) + assert self.credentials.valid + assert not self.credentials.expired + + def test_expired(self): + assert not self.credentials.expired + + self.credentials.refresh(None) + assert not self.credentials.expired + + with mock.patch('google.auth._helpers.utcnow') as now: + one_day = datetime.timedelta(days=1) + now.return_value = self.credentials.expiry + one_day + assert self.credentials.expired + + def test_before_request_one_time_token(self): + headers = {} + + self.credentials.refresh(None) + self.credentials.before_request( + mock.Mock(), 'GET', 'http://example.com?a=1#3', headers) + + header_value = headers['authorization'] + _, token = header_value.split(' ') + + # This should be a one-off token, so it shouldn't be the same as the + # credentials' stored token. + assert token != self.credentials.token + + payload = self._verify_token(token) + assert payload['aud'] == 'http://example.com' + + def test_before_request_with_preset_audience(self): + headers = {} + + credentials = self.credentials.with_claims(audience=self.AUDIENCE) + credentials.refresh(None) + credentials.before_request( + None, 'GET', 'http://example.com?a=1#3', headers) + + header_value = headers['authorization'] + _, token = header_value.split(' ') + + # Since the audience is set, it should use the existing token. + assert token.encode('utf-8') == credentials.token + + payload = self._verify_token(token) + assert payload['aud'] == self.AUDIENCE + + def test_before_request_refreshes(self): + credentials = self.credentials.with_claims(audience=self.AUDIENCE) + assert not credentials.valid + credentials.before_request( + None, 'GET', 'http://example.com?a=1#3', {}) + assert credentials.valid From 955b491446fe8fc0aa56fa6d9b7d928ad93cc3e0 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Mon, 17 Oct 2016 11:27:37 -0700 Subject: [PATCH 029/966] Fix import order --- packages/google-auth/google/auth/jwt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/jwt.py b/packages/google-auth/google/auth/jwt.py index 394a7d52829f..12c95eb2b26f 100644 --- a/packages/google-auth/google/auth/jwt.py +++ b/packages/google-auth/google/auth/jwt.py @@ -48,9 +48,9 @@ from six.moves import urllib +from google.auth import _helpers from google.auth import credentials from google.auth import crypt -from google.auth import _helpers _DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in sections From 2567dbc9f00594d5e4d49f7ce021a19b03550b21 Mon Sep 17 00:00:00 2001 From: Danny Hermes Date: Mon, 17 Oct 2016 13:15:07 -0700 Subject: [PATCH 030/966] Handle case: additional claims empty but mutable --- packages/google-auth/google/auth/jwt.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/jwt.py b/packages/google-auth/google/auth/jwt.py index 12c95eb2b26f..5db1fa2c0036 100644 --- a/packages/google-auth/google/auth/jwt.py +++ b/packages/google-auth/google/auth/jwt.py @@ -310,9 +310,13 @@ def __init__(self, signer, issuer=None, subject=None, audience=None, self._issuer = issuer self._subject = subject self._audience = audience - self._additional_claims = additional_claims or {} self._token_lifetime = token_lifetime + if additional_claims is not None: + self._additional_claims = additional_claims + else: + self._additional_claims = {} + @classmethod def from_service_account_info(cls, info, **kwargs): """Creates a Credentials instance from parsed service account info. From c3a6f1598f07c8abcd60bc28075c5fa13f618bf0 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Tue, 18 Oct 2016 09:38:26 -0700 Subject: [PATCH 031/966] Consolidate service account file loading logic. (#31) --- .../google/auth/_service_account_info.py | 77 +++++++++++++++++++ packages/google-auth/google/auth/jwt.py | 51 +++++++----- .../google/oauth2/service_account.py | 47 ++++++----- .../tests/oauth2/test_service_account.py | 13 ---- .../tests/test__service_account_info.py | 63 +++++++++++++++ packages/google-auth/tests/test_jwt.py | 13 ---- 6 files changed, 199 insertions(+), 65 deletions(-) create mode 100644 packages/google-auth/google/auth/_service_account_info.py create mode 100644 packages/google-auth/tests/test__service_account_info.py diff --git a/packages/google-auth/google/auth/_service_account_info.py b/packages/google-auth/google/auth/_service_account_info.py new file mode 100644 index 000000000000..b007abdedef7 --- /dev/null +++ b/packages/google-auth/google/auth/_service_account_info.py @@ -0,0 +1,77 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Helper functions for loading data from a Google service account file.""" + +import io +import json + +import six + +from google.auth import crypt + + +def from_dict(data, require=None): + """Validates a dictionary containing Google service account data. + + Creates and returns a :class:`google.auth.crypt.Signer` instance from the + private key specified in the data. + + Args: + data (Mapping[str, str]): The service account data + require (Sequence[str]): List of keys required to be present in the + info. + + Returns: + google.auth.crypt.Signer: A signer created from the private key in the + service account file. + + Raises: + ValueError: if the data was in the wrong format, or if one of the + required keys is missing. + """ + # Private key is always required. + keys_needed = set(('private_key',)) + if require is not None: + keys_needed.update(require) + + missing = keys_needed.difference(six.iterkeys(data)) + + if missing: + raise ValueError( + 'Service account info was not in the expected format, missing ' + 'fields {}.'.format(', '.join(missing))) + + # Create a signer. + signer = crypt.Signer.from_string( + data['private_key'], data.get('private_key_id')) + + return signer + + +def from_filename(filename, require=None): + """Reads a Google service account JSON file and returns its parsed info. + + Args: + filename (str): The path to the service account .json file. + require (Sequence[str]): List of keys required to be present in the + info. + + Returns: + Tuple[ Mapping[str, str], google.auth.crypt.Signer ]: The verified + info and a signer instance. + """ + with io.open(filename, 'r', encoding='utf-8') as json_file: + data = json.load(json_file) + return data, from_dict(data, require=require) diff --git a/packages/google-auth/google/auth/jwt.py b/packages/google-auth/google/auth/jwt.py index 5db1fa2c0036..69575a127746 100644 --- a/packages/google-auth/google/auth/jwt.py +++ b/packages/google-auth/google/auth/jwt.py @@ -43,12 +43,12 @@ import base64 import collections import datetime -import io import json from six.moves import urllib from google.auth import _helpers +from google.auth import _service_account_info from google.auth import credentials from google.auth import crypt @@ -318,12 +318,13 @@ def __init__(self, signer, issuer=None, subject=None, audience=None, self._additional_claims = {} @classmethod - def from_service_account_info(cls, info, **kwargs): - """Creates a Credentials instance from parsed service account info. + def _from_signer_and_info(cls, signer, info, **kwargs): + """Creates a Credentials instance from a signer and service account + info. Args: - info (Mapping[str, str]): The service account info in Google - format. + signer (google.auth.crypt.Signer): The signer used to sign JWTs. + info (Mapping[str, str]): The service account info. kwargs: Additional arguments to pass to the constructor. Returns: @@ -332,34 +333,44 @@ def from_service_account_info(cls, info, **kwargs): Raises: ValueError: If the info is not in the expected format. """ + kwargs.setdefault('subject', info['client_email']) + return cls(signer, issuer=info['client_email'], **kwargs) - try: - email = info['client_email'] - key_id = info['private_key_id'] - private_key = info['private_key'] - except KeyError: - raise ValueError( - 'Service account info was not in the expected format.') + @classmethod + def from_service_account_info(cls, info, **kwargs): + """Creates a Credentials instance from a dictionary containing service + account info in Google format. - signer = crypt.Signer.from_string(private_key, key_id) + Args: + info (Mapping[str, str]): The service account info in Google + format. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.jwt.Credentials: The constructed credentials. - kwargs.setdefault('subject', email) - return cls(signer, issuer=email, **kwargs) + Raises: + ValueError: If the info is not in the expected format. + """ + signer = _service_account_info.from_dict( + info, require=['client_email']) + return cls._from_signer_and_info(signer, info, **kwargs) @classmethod def from_service_account_file(cls, filename, **kwargs): - """Creates a Credentials instance from a service account json file. + """Creates a Credentials instance from a service account .json file + in Google format. Args: - filename (str): The path to the service account json file. + filename (str): The path to the service account .json file. kwargs: Additional arguments to pass to the constructor. Returns: google.auth.jwt.Credentials: The constructed credentials. """ - with io.open(filename, 'r', encoding='utf-8') as json_file: - info = json.load(json_file) - return cls.from_service_account_info(info, **kwargs) + info, signer = _service_account_info.from_filename( + filename, require=['client_email']) + return cls._from_signer_and_info(signer, info, **kwargs) def with_claims(self, issuer=None, subject=None, audience=None, additional_claims=None): diff --git a/packages/google-auth/google/oauth2/service_account.py b/packages/google-auth/google/oauth2/service_account.py index 4c4c1c03aede..dfbe352a79be 100644 --- a/packages/google-auth/google/oauth2/service_account.py +++ b/packages/google-auth/google/oauth2/service_account.py @@ -71,12 +71,10 @@ """ import datetime -import io -import json from google.auth import _helpers +from google.auth import _service_account_info from google.auth import credentials -from google.auth import crypt from google.auth import jwt from google.oauth2 import _client @@ -149,6 +147,27 @@ def __init__(self, signer, service_account_email, token_uri, scopes=None, else: self._additional_claims = {} + @classmethod + def _from_signer_and_info(cls, signer, info, **kwargs): + """Creates a Credentials instance from a signer and service account + info. + + Args: + signer (google.auth.crypt.Signer): The signer used to sign JWTs. + info (Mapping[str, str]): The service account info. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.jwt.Credentials: The constructed credentials. + + Raises: + ValueError: If the info is not in the expected format. + """ + return cls( + signer, + service_account_email=info['client_email'], + token_uri=info['token_uri'], **kwargs) + @classmethod def from_service_account_info(cls, info, **kwargs): """Creates a Credentials instance from parsed service account info. @@ -165,19 +184,9 @@ def from_service_account_info(cls, info, **kwargs): Raises: ValueError: If the info is not in the expected format. """ - try: - email = info['client_email'] - key_id = info['private_key_id'] - private_key = info['private_key'] - token_uri = info['token_uri'] - except KeyError: - raise ValueError( - 'Service account info was not in the expected format.') - - signer = crypt.Signer.from_string(private_key, key_id) - - return cls( - signer, service_account_email=email, token_uri=token_uri, **kwargs) + signer = _service_account_info.from_dict( + info, require=['client_email', 'token_uri']) + return cls._from_signer_and_info(signer, info, **kwargs) @classmethod def from_service_account_file(cls, filename, **kwargs): @@ -191,9 +200,9 @@ def from_service_account_file(cls, filename, **kwargs): google.auth.service_account.Credentials: The constructed credentials. """ - with io.open(filename, 'r', encoding='utf-8') as json_file: - info = json.load(json_file) - return cls.from_service_account_info(info, **kwargs) + info, signer = _service_account_info.from_filename( + filename, require=['client_email', 'token_uri']) + return cls._from_signer_and_info(signer, info, **kwargs) @property def requires_scopes(self): diff --git a/packages/google-auth/tests/oauth2/test_service_account.py b/packages/google-auth/tests/oauth2/test_service_account.py index 473d440dac36..2c2a0cf78e86 100644 --- a/packages/google-auth/tests/oauth2/test_service_account.py +++ b/packages/google-auth/tests/oauth2/test_service_account.py @@ -85,19 +85,6 @@ def test_from_service_account_info_args(self): assert credentials._subject == subject assert credentials._additional_claims == additional_claims - def test_from_service_account_bad_key(self): - info = SERVICE_ACCOUNT_INFO.copy() - info['private_key'] = 'garbage' - - with pytest.raises(ValueError) as excinfo: - service_account.Credentials.from_service_account_info(info) - - assert excinfo.match(r'No key could be detected') - - def test_from_service_account_bad_format(self): - with pytest.raises(ValueError): - service_account.Credentials.from_service_account_info({}) - def test_from_service_account_file(self): info = SERVICE_ACCOUNT_INFO.copy() diff --git a/packages/google-auth/tests/test__service_account_info.py b/packages/google-auth/tests/test__service_account_info.py new file mode 100644 index 000000000000..14f659adec6a --- /dev/null +++ b/packages/google-auth/tests/test__service_account_info.py @@ -0,0 +1,63 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import os + +import pytest +import six + +from google.auth import _service_account_info +from google.auth import crypt + + +DATA_DIR = os.path.join(os.path.dirname(__file__), 'data') +SERVICE_ACCOUNT_JSON_FILE = os.path.join(DATA_DIR, 'service_account.json') + +with open(SERVICE_ACCOUNT_JSON_FILE, 'r') as fh: + SERVICE_ACCOUNT_INFO = json.load(fh) + + +def test_from_dict(): + signer = _service_account_info.from_dict(SERVICE_ACCOUNT_INFO) + assert isinstance(signer, crypt.Signer) + assert signer.key_id == SERVICE_ACCOUNT_INFO['private_key_id'] + + +def test_from_dict_bad_private_key(): + info = SERVICE_ACCOUNT_INFO.copy() + info['private_key'] = 'garbage' + + with pytest.raises(ValueError) as excinfo: + _service_account_info.from_dict(info) + + assert excinfo.match(r'No key could be detected') + + +def test_from_dict_bad_format(): + with pytest.raises(ValueError) as excinfo: + _service_account_info.from_dict({}) + + assert excinfo.match(r'missing fields') + + +def test_from_filename(): + info, signer = _service_account_info.from_filename( + SERVICE_ACCOUNT_JSON_FILE) + + for key, value in six.iteritems(SERVICE_ACCOUNT_INFO): + assert info[key] == value + + assert isinstance(signer, crypt.Signer) + assert signer.key_id == SERVICE_ACCOUNT_INFO['private_key_id'] diff --git a/packages/google-auth/tests/test_jwt.py b/packages/google-auth/tests/test_jwt.py index b6c07df63072..2a4795acc5bd 100644 --- a/packages/google-auth/tests/test_jwt.py +++ b/packages/google-auth/tests/test_jwt.py @@ -231,19 +231,6 @@ def test_from_service_account_info_args(self): assert credentials._audience == self.AUDIENCE assert credentials._additional_claims == self.ADDITIONAL_CLAIMS - def test_from_service_account_bad_private_key(self): - info = SERVICE_ACCOUNT_INFO.copy() - info['private_key'] = 'garbage' - - with pytest.raises(ValueError) as excinfo: - jwt.Credentials.from_service_account_info(info) - - assert excinfo.match(r'No key could be detected') - - def test_from_service_account_bad_format(self): - with pytest.raises(ValueError): - jwt.Credentials.from_service_account_info({}) - def test_from_service_account_file(self): info = SERVICE_ACCOUNT_INFO.copy() From 0a1d391d9532fe439054e3701ce3d5c087616744 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Tue, 18 Oct 2016 10:06:02 -0700 Subject: [PATCH 032/966] Regenerate docs --- .../google.auth.compute_engine.credentials.rst | 7 +++++++ .../docs/reference/google.auth.compute_engine.rst | 9 ++++++++- .../docs/reference/google.auth.credentials.rst | 2 +- .../google-auth/docs/reference/google.auth.crypt.rst | 2 +- .../docs/reference/google.auth.exceptions.rst | 2 +- .../google-auth/docs/reference/google.auth.jwt.rst | 2 +- packages/google-auth/docs/reference/google.auth.rst | 2 +- .../docs/reference/google.auth.transport.rst | 2 +- .../docs/reference/google.auth.transport.urllib3.rst | 2 +- packages/google-auth/docs/reference/google.oauth2.rst | 10 +++++++++- packages/google-auth/docs/reference/google.rst | 2 +- 11 files changed, 32 insertions(+), 10 deletions(-) create mode 100644 packages/google-auth/docs/reference/google.auth.compute_engine.credentials.rst diff --git a/packages/google-auth/docs/reference/google.auth.compute_engine.credentials.rst b/packages/google-auth/docs/reference/google.auth.compute_engine.credentials.rst new file mode 100644 index 000000000000..ebe62c07cfd7 --- /dev/null +++ b/packages/google-auth/docs/reference/google.auth.compute_engine.credentials.rst @@ -0,0 +1,7 @@ +google.auth.compute_engine.credentials module +============================================= + +.. automodule:: google.auth.compute_engine.credentials + :members: + :inherited-members: + :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.auth.compute_engine.rst b/packages/google-auth/docs/reference/google.auth.compute_engine.rst index 9bf9866cad78..2e7b830db9e4 100644 --- a/packages/google-auth/docs/reference/google.auth.compute_engine.rst +++ b/packages/google-auth/docs/reference/google.auth.compute_engine.rst @@ -3,6 +3,13 @@ google.auth.compute_engine package .. automodule:: google.auth.compute_engine :members: - :undoc-members: + :inherited-members: :show-inheritance: +Submodules +---------- + +.. toctree:: + + google.auth.compute_engine.credentials + diff --git a/packages/google-auth/docs/reference/google.auth.credentials.rst b/packages/google-auth/docs/reference/google.auth.credentials.rst index 646fa7bb3a83..b710a8c8c301 100644 --- a/packages/google-auth/docs/reference/google.auth.credentials.rst +++ b/packages/google-auth/docs/reference/google.auth.credentials.rst @@ -3,5 +3,5 @@ google.auth.credentials module .. automodule:: google.auth.credentials :members: - :undoc-members: + :inherited-members: :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.auth.crypt.rst b/packages/google-auth/docs/reference/google.auth.crypt.rst index 7f8c0f837dc8..26b8b4ac4fcd 100644 --- a/packages/google-auth/docs/reference/google.auth.crypt.rst +++ b/packages/google-auth/docs/reference/google.auth.crypt.rst @@ -3,5 +3,5 @@ google.auth.crypt module .. automodule:: google.auth.crypt :members: - :undoc-members: + :inherited-members: :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.auth.exceptions.rst b/packages/google-auth/docs/reference/google.auth.exceptions.rst index c34e97332469..d603eaf8998e 100644 --- a/packages/google-auth/docs/reference/google.auth.exceptions.rst +++ b/packages/google-auth/docs/reference/google.auth.exceptions.rst @@ -3,5 +3,5 @@ google.auth.exceptions module .. automodule:: google.auth.exceptions :members: - :undoc-members: + :inherited-members: :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.auth.jwt.rst b/packages/google-auth/docs/reference/google.auth.jwt.rst index 79e3080d0811..c58be5fbd4ea 100644 --- a/packages/google-auth/docs/reference/google.auth.jwt.rst +++ b/packages/google-auth/docs/reference/google.auth.jwt.rst @@ -3,5 +3,5 @@ google.auth.jwt module .. automodule:: google.auth.jwt :members: - :undoc-members: + :inherited-members: :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.auth.rst b/packages/google-auth/docs/reference/google.auth.rst index 78f7786809e2..e26862f5f6db 100644 --- a/packages/google-auth/docs/reference/google.auth.rst +++ b/packages/google-auth/docs/reference/google.auth.rst @@ -3,7 +3,7 @@ google.auth package .. automodule:: google.auth :members: - :undoc-members: + :inherited-members: :show-inheritance: Subpackages diff --git a/packages/google-auth/docs/reference/google.auth.transport.rst b/packages/google-auth/docs/reference/google.auth.transport.rst index 706633473c8c..b9b444c2607c 100644 --- a/packages/google-auth/docs/reference/google.auth.transport.rst +++ b/packages/google-auth/docs/reference/google.auth.transport.rst @@ -3,7 +3,7 @@ google.auth.transport package .. automodule:: google.auth.transport :members: - :undoc-members: + :inherited-members: :show-inheritance: Submodules diff --git a/packages/google-auth/docs/reference/google.auth.transport.urllib3.rst b/packages/google-auth/docs/reference/google.auth.transport.urllib3.rst index 32de225c70a9..339c77ead3c3 100644 --- a/packages/google-auth/docs/reference/google.auth.transport.urllib3.rst +++ b/packages/google-auth/docs/reference/google.auth.transport.urllib3.rst @@ -3,5 +3,5 @@ google.auth.transport.urllib3 module .. automodule:: google.auth.transport.urllib3 :members: - :undoc-members: + :inherited-members: :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.oauth2.rst b/packages/google-auth/docs/reference/google.oauth2.rst index fb67d279ca6d..9126549b68c2 100644 --- a/packages/google-auth/docs/reference/google.oauth2.rst +++ b/packages/google-auth/docs/reference/google.oauth2.rst @@ -3,6 +3,14 @@ google.oauth2 package .. automodule:: google.oauth2 :members: - :undoc-members: + :inherited-members: :show-inheritance: +Submodules +---------- + +.. toctree:: + + google.oauth2.credentials + google.oauth2.service_account + diff --git a/packages/google-auth/docs/reference/google.rst b/packages/google-auth/docs/reference/google.rst index 58c6da449afa..fc63d1fdbd72 100644 --- a/packages/google-auth/docs/reference/google.rst +++ b/packages/google-auth/docs/reference/google.rst @@ -3,7 +3,7 @@ google package .. automodule:: google :members: - :undoc-members: + :inherited-members: :show-inheritance: Subpackages From 6e7f0d98d5aa883d5a04170b0f2c48281bedd310 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Tue, 18 Oct 2016 13:32:26 -0700 Subject: [PATCH 033/966] Add urllib3 to docs dependencies --- packages/google-auth/docs/requirements-docs.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/google-auth/docs/requirements-docs.txt b/packages/google-auth/docs/requirements-docs.txt index 5922c575897d..7af895f3ab90 100644 --- a/packages/google-auth/docs/requirements-docs.txt +++ b/packages/google-auth/docs/requirements-docs.txt @@ -1 +1,2 @@ sphinx-docstring-typing +urllib3 From 496958010bcfdce5f624c914a223ebea6c978196 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Wed, 19 Oct 2016 09:34:05 -0700 Subject: [PATCH 034/966] Implement application default credentials (#32) --- .../google.auth.environment_vars.rst | 7 + .../docs/reference/google.auth.rst | 1 + packages/google-auth/google/auth/__init__.py | 7 + .../google-auth/google/auth/_cloud_sdk.py | 135 +++++++++ packages/google-auth/google/auth/_default.py | 264 ++++++++++++++++++ .../google/auth/environment_vars.py | 32 +++ .../tests/data/authorized_user.json | 6 + packages/google-auth/tests/data/cloud_sdk.cfg | 2 + packages/google-auth/tests/test__cloud_sdk.py | 146 ++++++++++ packages/google-auth/tests/test__default.py | 263 +++++++++++++++++ 10 files changed, 863 insertions(+) create mode 100644 packages/google-auth/docs/reference/google.auth.environment_vars.rst create mode 100644 packages/google-auth/google/auth/_cloud_sdk.py create mode 100644 packages/google-auth/google/auth/_default.py create mode 100644 packages/google-auth/google/auth/environment_vars.py create mode 100644 packages/google-auth/tests/data/authorized_user.json create mode 100644 packages/google-auth/tests/data/cloud_sdk.cfg create mode 100644 packages/google-auth/tests/test__cloud_sdk.py create mode 100644 packages/google-auth/tests/test__default.py diff --git a/packages/google-auth/docs/reference/google.auth.environment_vars.rst b/packages/google-auth/docs/reference/google.auth.environment_vars.rst new file mode 100644 index 000000000000..fe34849ddbb6 --- /dev/null +++ b/packages/google-auth/docs/reference/google.auth.environment_vars.rst @@ -0,0 +1,7 @@ +google.auth.environment_vars module +=================================== + +.. automodule:: google.auth.environment_vars + :members: + :inherited-members: + :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.auth.rst b/packages/google-auth/docs/reference/google.auth.rst index e26862f5f6db..6769e1c22a0a 100644 --- a/packages/google-auth/docs/reference/google.auth.rst +++ b/packages/google-auth/docs/reference/google.auth.rst @@ -21,6 +21,7 @@ Submodules google.auth.credentials google.auth.crypt + google.auth.environment_vars google.auth.exceptions google.auth.jwt diff --git a/packages/google-auth/google/auth/__init__.py b/packages/google-auth/google/auth/__init__.py index 339b3d6bf92c..65e13951ca48 100644 --- a/packages/google-auth/google/auth/__init__.py +++ b/packages/google-auth/google/auth/__init__.py @@ -16,6 +16,13 @@ import logging +from google.auth._default import default + + +__all__ = [ + 'default', +] + # Set default logging handler to avoid "No handler found" warnings. logging.getLogger(__name__).addHandler(logging.NullHandler()) diff --git a/packages/google-auth/google/auth/_cloud_sdk.py b/packages/google-auth/google/auth/_cloud_sdk.py new file mode 100644 index 000000000000..f48f5a679519 --- /dev/null +++ b/packages/google-auth/google/auth/_cloud_sdk.py @@ -0,0 +1,135 @@ +# Copyright 2015 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Helpers for reading the Google Cloud SDK's configuration.""" + +import os + +import six +from six.moves import configparser + +from google.auth import environment_vars +import google.oauth2.credentials + +# The Google OAuth 2.0 token endpoint. Used for authorized user credentials. +_GOOGLE_OAUTH2_TOKEN_ENDPOINT = 'https://accounts.google.com/o/oauth2/token' + +# The ~/.config subdirectory containing gcloud credentials. +_CONFIG_DIRECTORY = 'gcloud' +# Windows systems store config at %APPDATA%\gcloud +_WINDOWS_CONFIG_ROOT_ENV_VAR = 'APPDATA' +# The name of the file in the Cloud SDK config that contains default +# credentials. +_CREDENTIALS_FILENAME = 'application_default_credentials.json' +# The name of the file in the Cloud SDK config that contains the +# active configuration. +_ACTIVE_CONFIG_FILENAME = os.path.join( + 'configurations', 'config_default') +# The config section and key for the project ID in the cloud SDK config. +_PROJECT_CONFIG_SECTION = 'core' +_PROJECT_CONFIG_KEY = 'project' + + +def get_config_path(): + """Returns the absolute path the the Cloud SDK's configuration directory. + + Returns: + str: The Cloud SDK config path. + """ + # If the path is explicitly set, return that. + try: + return os.environ[environment_vars.CLOUD_SDK_CONFIG_DIR] + except KeyError: + pass + + # Non-windows systems store this at ~/.config/gcloud + if os.name != 'nt': + return os.path.join( + os.path.expanduser('~'), '.config', _CONFIG_DIRECTORY) + # Windows systems store config at %APPDATA%\gcloud + else: + try: + return os.path.join( + os.environ[_WINDOWS_CONFIG_ROOT_ENV_VAR], + _CONFIG_DIRECTORY) + except KeyError: + # This should never happen unless someone is really + # messing with things, but we'll cover the case anyway. + drive = os.environ.get('SystemDrive', 'C:') + return os.path.join( + drive, '\\', _CONFIG_DIRECTORY) + + +def get_application_default_credentials_path(): + """Gets the path to the application default credentials file. + + The path may or may not exist. + + Returns: + str: The full path to application default credentials. + """ + config_path = get_config_path() + return os.path.join(config_path, _CREDENTIALS_FILENAME) + + +def get_project_id(): + """Gets the project ID from the Cloud SDK's configuration. + + Returns: + Optional[str]: The project ID. + """ + config_path = get_config_path() + config_file = os.path.join(config_path, _ACTIVE_CONFIG_FILENAME) + + if not os.path.isfile(config_file): + return None + + config = configparser.RawConfigParser() + + try: + config.read(config_file) + except configparser.Error: + return None + + if config.has_section(_PROJECT_CONFIG_SECTION): + return config.get( + _PROJECT_CONFIG_SECTION, _PROJECT_CONFIG_KEY) + + +def load_authorized_user_credentials(info): + """Loads an authorized user credential. + + Args: + info (Mapping[str, str]): The loaded file's data. + + Returns: + google.oauth2.credentials.Credentials: The constructed credentials. + + Raises: + ValueError: if the info is in the wrong format or missing data. + """ + keys_needed = set(('refresh_token', 'client_id', 'client_secret')) + missing = keys_needed.difference(six.iterkeys(info)) + + if missing: + raise ValueError( + 'Authorized user info was not in the expected format, missing ' + 'fields {}.'.format(', '.join(missing))) + + return google.oauth2.credentials.Credentials( + None, # No access token, must be refreshed. + refresh_token=info['refresh_token'], + token_uri=_GOOGLE_OAUTH2_TOKEN_ENDPOINT, + client_id=info['client_id'], + client_secret=info['client_secret']) diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py new file mode 100644 index 000000000000..3f6993a668cb --- /dev/null +++ b/packages/google-auth/google/auth/_default.py @@ -0,0 +1,264 @@ +# Copyright 2015 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Application default credentials. + +Implements application default credentials and project ID detection. +""" + +import io +import json +import logging +import os + +from google.auth import _cloud_sdk +from google.auth import compute_engine +from google.auth import environment_vars +from google.auth import exceptions +from google.auth.compute_engine import _metadata +import google.auth.transport._http_client +from google.oauth2 import service_account +import google.oauth2.credentials + +_LOGGER = logging.getLogger(__name__) + +# Valid types accepted for file-based credentials. +_AUTHORIZED_USER_TYPE = 'authorized_user' +_SERVICE_ACCOUNT_TYPE = 'service_account' +_VALID_TYPES = (_AUTHORIZED_USER_TYPE, _SERVICE_ACCOUNT_TYPE) + +# Help message when no credentials can be found. +_HELP_MESSAGE = """ +Could not automatically determine credentials. Please set {env} or +explicitly create credential and re-run the application. For more +information, please see +https://developers.google.com/accounts/docs/application-default-credentials. +""".format(env=environment_vars.CREDENTIALS).strip() + + +def _load_credentials_from_file(filename): + """Loads credentials from a file. + + The credentials file must be a service account key or stored authorized + user credentials. + + Args: + filename (str): The full path to the credentials file. + + Returns: + Tuple[google.auth.credentials.Credentials, Optional[str]]: Loaded + credentials and the project ID. Authorized user credentials do not + have the project ID information. + + Raises: + google.auth.exceptions.DefaultCredentialsError: if the file is in the + wrong format. + """ + with io.open(filename, 'r') as file_obj: + try: + info = json.load(file_obj) + except ValueError as exc: + raise exceptions.DefaultCredentialsError( + 'File {} is not a valid json file.'.format(filename), exc) + + # The type key should indicate that the file is either a service account + # credentials file or an authorized user credentials file. + credential_type = info.get('type') + + if credential_type == _AUTHORIZED_USER_TYPE: + try: + credentials = _cloud_sdk.load_authorized_user_credentials(info) + except ValueError as exc: + raise exceptions.DefaultCredentialsError( + 'Failed to load authorized user credentials from {}'.format( + filename), exc) + # Authorized user credentials do not contain the project ID. + return credentials, None + + elif credential_type == _SERVICE_ACCOUNT_TYPE: + try: + credentials = ( + service_account.Credentials.from_service_account_info(info)) + except ValueError as exc: + raise exceptions.DefaultCredentialsError( + 'Failed to load service account credentials from {}'.format( + filename), exc) + return credentials, info.get('project_id') + + else: + raise exceptions.DefaultCredentialsError( + 'The file {file} does not have a valid type. ' + 'Type is {type}, expected one of {valid_types}.'.format( + file=filename, type=credential_type, valid_types=_VALID_TYPES)) + + +def _get_gcloud_sdk_credentials(): + """Gets the credentials and project ID from the Cloud SDK.""" + # Check if application default credentials exist. + credentials_filename = ( + _cloud_sdk.get_application_default_credentials_path()) + + if not os.path.isfile(credentials_filename): + return None, None + + credentials, project_id = _load_credentials_from_file( + credentials_filename) + + if not project_id: + project_id = _cloud_sdk.get_project_id() + + if not project_id: + _LOGGER.warning( + 'No project ID could be determined from the Cloud SDK ' + 'configuration. Consider running `gcloud config set project` or ' + 'setting the %s environment variable', environment_vars.PROJECT) + + return credentials, project_id + + +def _get_explicit_environ_credentials(): + """Gets credentials from the GOOGLE_APPLICATION_CREDENTIALS environment + variable.""" + explicit_file = os.environ.get(environment_vars.CREDENTIALS) + + if explicit_file is not None: + credentials, project_id = _load_credentials_from_file( + os.environ[environment_vars.CREDENTIALS]) + + if not project_id: + _LOGGER.warning( + 'No project ID could be determined from the credentials at %s ' + 'Consider setting the %s environment variable', + environment_vars.CREDENTIALS, environment_vars.PROJECT) + + return credentials, project_id + + else: + return None, None + + +def _get_gae_credentials(): + """Gets Google App Engine App Identity credentials and project ID.""" + return None, None + + +def _get_gce_credentials(request=None): + """Gets credentials and project ID from the GCE Metadata Service.""" + # Ping requires a transport, but we want application default credentials + # to require no arguments. So, we'll use the _http_client transport which + # uses http.client. This is only acceptable because the metadata server + # doesn't do SSL and never requires proxies. + + if request is None: + request = google.auth.transport._http_client.Request() + + if _metadata.ping(request=request): + # Get the project ID. + try: + project_id = _metadata.get(request, 'project/project-id') + except exceptions.TransportError: + _LOGGER.warning( + 'No project ID could be determined from the Compute Engine ' + 'metadata service. Consider setting the %s environment ' + 'variable.', environment_vars.PROJECT) + project_id = None + + return compute_engine.Credentials(), project_id + else: + return None, None + + +def default(request=None): + """Gets the default credentials for the current environment. + + `Application Default Credentials`_ provides an easy way to obtain + credentials to call Google APIs for server-to-server or local applications. + This function acquires credentials from the environment in the following + order: + + 1. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set + to the path of a valid service account JSON private key file, then it is + loaded and returned. The project ID returned is the project ID defined + in the service account file if available (some older files do not + contain project ID information). + 2. If the `Google Cloud SDK`_ is installed and has application default + credentials set they are loaded and returned. + + To enable application default credentials with the Cloud SDK run:: + + gcloud auth application-default login + + If the Cloud SDK has an active project, the project ID is returned. The + active project can be set using:: + + gcloud config set project + + 3. If the application is running in the `App Engine standard environment`_ + then the credentials and project ID from the `App Identity Service`_ + are used. + 4. If the application is running in `Compute Engine`_ or the + `App Engine flexible environment`_ then the credentials and project ID + are obtained from the `Metadata Service`_. + 5. If no credentials are found, + :class:`~google.auth.exceptions.DefaultCredentialsError` will be raised. + + .. _Application Default Credentials: https://developers.google.com\ + /identity/protocols/application-default-credentials + .. _Google Cloud SDK: https://cloud.google.com/sdk + .. _App Engine standard environment: https://cloud.google.com/appengine + .. _App Identity Service: https://cloud.google.com/appengine/docs/python\ + /appidentity/ + .. _Compute Engine: https://cloud.google.com/compute + .. _App Engine flexible environment: https://cloud.google.com\ + /appengine/flexible + .. _Metadata Service: https://cloud.google.com/compute/docs\ + /storing-retrieving-metadata + + Example:: + + import google.auth + + credentials, project_id = google.auth.default() + + Args: + request (google.auth.transport.Request): An object used to make + HTTP requests. This is used to detect whether the application + is running on Compute Engine. If not specified, then it will + use the standard library http client to make requests. + + Returns: + Tuple[~google.auth.credentials.Credentials, Optional[str]]: + the current environment's credentials and project ID. Project ID + may be None, which indicates that the Project ID could not be + ascertained from the environment. + + Raises: + ~google.auth.exceptions.DefaultCredentialsError: + If no credentials were found, or if the credentials found were + invalid. + """ + explicit_project_id = os.environ.get(environment_vars.PROJECT) + + checkers = ( + _get_explicit_environ_credentials, + _get_gcloud_sdk_credentials, + _get_gae_credentials, + lambda: _get_gce_credentials(request)) + + for checker in checkers: + credentials, project_id = checker() + if credentials is not None: + return credentials, explicit_project_id or project_id + + raise exceptions.DefaultCredentialsError(_HELP_MESSAGE) diff --git a/packages/google-auth/google/auth/environment_vars.py b/packages/google-auth/google/auth/environment_vars.py new file mode 100644 index 000000000000..9785c346624e --- /dev/null +++ b/packages/google-auth/google/auth/environment_vars.py @@ -0,0 +1,32 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Environment variables used by :mod:`google.auth`.""" + + +PROJECT = 'GOOGLE_CLOUD_PROJECT' +"""Environment variable defining default project. + +This used by :func:`google.auth.default` to explicitly set a project ID. This +environment variable is also used by the Google Cloud Python Library. +""" + +CREDENTIALS = 'GOOGLE_APPLICATION_CREDENTIALS' +"""Environment variable defining the location of Google application default +credentials.""" + +# The environment variable name which can replace ~/.config if set. +CLOUD_SDK_CONFIG_DIR = 'CLOUDSDK_CONFIG' +"""Environment variable defines the location of Google Cloud SDK's config +files.""" diff --git a/packages/google-auth/tests/data/authorized_user.json b/packages/google-auth/tests/data/authorized_user.json new file mode 100644 index 000000000000..4787acee5764 --- /dev/null +++ b/packages/google-auth/tests/data/authorized_user.json @@ -0,0 +1,6 @@ +{ + "client_id": "123", + "client_secret": "secret", + "refresh_token": "alabalaportocala", + "type": "authorized_user" +} diff --git a/packages/google-auth/tests/data/cloud_sdk.cfg b/packages/google-auth/tests/data/cloud_sdk.cfg new file mode 100644 index 000000000000..089aac5a579d --- /dev/null +++ b/packages/google-auth/tests/data/cloud_sdk.cfg @@ -0,0 +1,2 @@ +[core] +project = example-project diff --git a/packages/google-auth/tests/test__cloud_sdk.py b/packages/google-auth/tests/test__cloud_sdk.py new file mode 100644 index 000000000000..35ee4269b5d4 --- /dev/null +++ b/packages/google-auth/tests/test__cloud_sdk.py @@ -0,0 +1,146 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import os + +import mock +import pytest + +from google.auth import _cloud_sdk +from google.auth import environment_vars +import google.oauth2.credentials + + +DATA_DIR = os.path.join(os.path.dirname(__file__), 'data') +AUTHORIZED_USER_FILE = os.path.join(DATA_DIR, 'authorized_user.json') + +with open(AUTHORIZED_USER_FILE) as fh: + AUTHORIZED_USER_FILE_DATA = json.load(fh) + +SERVICE_ACCOUNT_FILE = os.path.join(DATA_DIR, 'service_account.json') + +with open(SERVICE_ACCOUNT_FILE) as fh: + SERVICE_ACCOUNT_FILE_DATA = json.load(fh) + +with open(os.path.join(DATA_DIR, 'cloud_sdk.cfg')) as fh: + CLOUD_SDK_CONFIG_DATA = fh.read() + +CONFIG_PATH_PATCH = mock.patch('google.auth._cloud_sdk.get_config_path') + + +@pytest.fixture +def config_file(tmpdir): + config_dir = tmpdir.join( + '.config', _cloud_sdk._CONFIG_DIRECTORY) + config_file = config_dir.join( + _cloud_sdk._ACTIVE_CONFIG_FILENAME) + + with CONFIG_PATH_PATCH as mock_get_config_dir: + mock_get_config_dir.return_value = str(config_dir) + yield config_file + + +def test_get_project_id(config_file): + config_file.write(CLOUD_SDK_CONFIG_DATA, ensure=True) + project_id = _cloud_sdk.get_project_id() + assert project_id == 'example-project' + + +def test_get_project_id_non_existent(config_file): + project_id = _cloud_sdk.get_project_id() + assert project_id is None + + +def test_get_project_id_bad_file(config_file): + config_file.write('<< Date: Fri, 21 Oct 2016 14:31:06 -0700 Subject: [PATCH 035/966] Add python setup.py check to lint --- packages/google-auth/README.rst | 8 ++++---- packages/google-auth/setup.py | 1 + packages/google-auth/tox.ini | 2 ++ 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/google-auth/README.rst b/packages/google-auth/README.rst index 9609666e33dc..8dcfbe8a29db 100644 --- a/packages/google-auth/README.rst +++ b/packages/google-auth/README.rst @@ -39,13 +39,13 @@ Contributing Contributions to this library are always welcome and highly encouraged. -See `CONTRIBUTING`_ for more information on how to get started. +See `CONTRIBUTING.rst`_ for more information on how to get started. -.. _CONTRIBUTING: https://github.com/GoogleCloudPlatform/google-auth-library-python/blob/master/CONTRIBUTING.rst +.. _CONTRIBUTING.rst: https://github.com/GoogleCloudPlatform/google-auth-library-python/blob/master/CONTRIBUTING.rst License ------- -Apache 2.0 - See `LICENSE`_ for more information. +Apache 2.0 - See `the LICENSE`_ for more information. -.. _LICENSE: https://github.com/GoogleCloudPlatform/google-auth-library-python/blob/master/LICENSE +.. _the LICENSE: https://github.com/GoogleCloudPlatform/google-auth-library-python/blob/master/LICENSE diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 32c5d02119fd..615753d2ebb3 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -35,6 +35,7 @@ author_email='jonwayne+google-auth@google.com', description='Google Authentication Library', long_description=long_description, + url='https://github.com/GoogleCloudPlatform/google-auth-library-python', packages=find_packages(exclude='tests'), namespace_packages=('google',), install_requires=DEPENDENCIES, diff --git a/packages/google-auth/tox.ini b/packages/google-auth/tox.ini index 65178285b6a7..bc7d154065f9 100644 --- a/packages/google-auth/tox.ini +++ b/packages/google-auth/tox.ini @@ -43,6 +43,7 @@ commands = make -C docs html [testenv:lint] basepython = python3.5 commands = + python setup.py check --metadata --restructuredtext --strict flake8 \ --import-order-style=google \ --application-import-names="google,tests" \ @@ -53,3 +54,4 @@ deps = flake8 flake8-import-order pylint + docutils From 6ac044391d7e088f8f1249db5391a042d098b6d2 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Fri, 21 Oct 2016 15:05:37 -0700 Subject: [PATCH 036/966] Add httplib2 transport (#34) --- packages/google-auth/LICENSE | 210 ++++++++++++++-- .../httplib2_transport/.coveragerc | 13 + .../google-auth/httplib2_transport/LICENSE | 201 ++++++++++++++++ .../google-auth/httplib2_transport/README.rst | 33 +++ .../google_auth_httplib2.py | 225 ++++++++++++++++++ .../google-auth/httplib2_transport/pylintrc | 165 +++++++++++++ .../httplib2_transport/pylintrc.tests | 166 +++++++++++++ .../google-auth/httplib2_transport/setup.cfg | 2 + .../google-auth/httplib2_transport/setup.py | 58 +++++ .../httplib2_transport/tests/__init__.py | 0 .../httplib2_transport/tests/compliance.py | 92 +++++++ .../tests/test_google_auth_httplib2.py | 148 ++++++++++++ .../google-auth/httplib2_transport/tox.ini | 40 ++++ packages/google-auth/setup.py | 1 - 14 files changed, 1338 insertions(+), 16 deletions(-) create mode 100644 packages/google-auth/httplib2_transport/.coveragerc create mode 100644 packages/google-auth/httplib2_transport/LICENSE create mode 100644 packages/google-auth/httplib2_transport/README.rst create mode 100644 packages/google-auth/httplib2_transport/google_auth_httplib2.py create mode 100644 packages/google-auth/httplib2_transport/pylintrc create mode 100644 packages/google-auth/httplib2_transport/pylintrc.tests create mode 100644 packages/google-auth/httplib2_transport/setup.cfg create mode 100644 packages/google-auth/httplib2_transport/setup.py create mode 100644 packages/google-auth/httplib2_transport/tests/__init__.py create mode 100644 packages/google-auth/httplib2_transport/tests/compliance.py create mode 100644 packages/google-auth/httplib2_transport/tests/test_google_auth_httplib2.py create mode 100644 packages/google-auth/httplib2_transport/tox.ini diff --git a/packages/google-auth/LICENSE b/packages/google-auth/LICENSE index 0fd73eb55aa4..261eeb9e9f8b 100644 --- a/packages/google-auth/LICENSE +++ b/packages/google-auth/LICENSE @@ -1,21 +1,201 @@ - Copyright 2014 Google Inc. + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - http://www.apache.org/licenses/LICENSE-2.0 + 1. Definitions. - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. -Dependent Modules -================= + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. -This code has the following dependencies -above and beyond the Python standard library: + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. -httplib2 - MIT License + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/google-auth/httplib2_transport/.coveragerc b/packages/google-auth/httplib2_transport/.coveragerc new file mode 100644 index 000000000000..c44dd8eaa6c8 --- /dev/null +++ b/packages/google-auth/httplib2_transport/.coveragerc @@ -0,0 +1,13 @@ +[run] +branch = True + +[report] +omit = + */conftest.py +exclude_lines = + # Re-enable the standard pragma + pragma: NO COVER + # Ignore debug-only repr + def __repr__ + # Don't complain if tests don't hit defensive assertion code: + raise NotImplementedError diff --git a/packages/google-auth/httplib2_transport/LICENSE b/packages/google-auth/httplib2_transport/LICENSE new file mode 100644 index 000000000000..261eeb9e9f8b --- /dev/null +++ b/packages/google-auth/httplib2_transport/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/google-auth/httplib2_transport/README.rst b/packages/google-auth/httplib2_transport/README.rst new file mode 100644 index 000000000000..09405aea6b99 --- /dev/null +++ b/packages/google-auth/httplib2_transport/README.rst @@ -0,0 +1,33 @@ +Httplib2 Transport for Google Auth +================================== + +|pypi| + +This library provides an `httplib2`_ transport for `google-auth`_. + +.. note:: ``httplib`` has lots of problems such as lack of threadsafety and + and insecure usage of TLS. Using it is highly discouraged. This + library is intended to help existing users of ``oauth2client`` migrate to + ``google-auth``. + +.. |pypi| image:: https://img.shields.io/pypi/v/google-auth-httplib2.svg + :target: https://pypi.python.org/pypi/google-auth-httplib2 + +.. _httplib2: https://github.com/httplib2/httplib2 +.. _google-auth: https://github.com/GoogleCloudPlatform/google-auth + +Installing +---------- + +You can install using `pip`_:: + + $ pip install google-auth-httlib2 + +.. _pip: https://pip.pypa.io/en/stable/ + +License +------- + +Apache 2.0 - See `the LICENSE`_ for more information. + +.. _the LICENSE: https://github.com/GoogleCloudPlatform/google-auth-library-python/blob/master/LICENSE diff --git a/packages/google-auth/httplib2_transport/google_auth_httplib2.py b/packages/google-auth/httplib2_transport/google_auth_httplib2.py new file mode 100644 index 000000000000..598ccb3e4647 --- /dev/null +++ b/packages/google-auth/httplib2_transport/google_auth_httplib2.py @@ -0,0 +1,225 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Transport adapter for httplib2.""" + +from __future__ import absolute_import + +import logging + +from google.auth import exceptions +from google.auth import transport +import httplib2 + + +_LOGGER = logging.getLogger(__name__) +# Properties present in file-like streams / buffers. +_STREAM_PROPERTIES = ('read', 'seek', 'tell') + + +class _Response(transport.Response): + """httplib2 transport response adapter. + + Args: + response (httplib2.Response): The raw httplib2 response. + data (bytes): The response body. + """ + def __init__(self, response, data): + self._response = response + self._data = data + + @property + def status(self): + """int: The HTTP status code.""" + return self._response.status + + @property + def headers(self): + """Mapping[str, str]: The HTTP response headers.""" + return dict(self._response) + + @property + def data(self): + """bytes: The response body.""" + return self._data + + +class Request(transport.Request): + """httplib2 request adapter. + + This class is used internally for making requests using various transports + in a consistent way. If you use :class:`AuthorizedHttp` you do not need + to construct or use this class directly. + + This class can be useful if you want to manually refresh a + :class:`~google.auth.credentials.Credentials` instance:: + + import google.auth.transport.httplib2 + import httplib2 + + http = httplib2.Http() + request = google.auth.transport.httplib2.Request(http) + + credentials.refresh(request) + + Args: + http (httplib2.Http): The underlying http object to use to make + requests. + + .. automethod:: __call__ + """ + def __init__(self, http): + self.http = http + + def __call__(self, url, method='GET', body=None, headers=None, + timeout=None, **kwargs): + """Make an HTTP request using httplib2. + + Args: + url (str): The URI to be requested. + method (str): The HTTP method to use for the request. Defaults + to 'GET'. + body (bytes): The payload / body in HTTP request. + headers (Mapping[str, str]): Request headers. + timeout (Optional[int]): The number of seconds to wait for a + response from the server. This is ignored by httplib2 and will + issue a warning. + kwargs: Additional arguments passed throught to the underlying + :meth:`httplib2.Http.request` method. + + Returns: + google.auth.transport.Response: The HTTP response. + + Raises: + google.auth.exceptions.TransportError: If any exception occurred. + """ + if timeout is not None: + _LOGGER.warning( + 'httplib2 transport does not support per-request timeout. ' + 'Set the timeout when constructing the httplib2.Http instance.' + ) + + try: + _LOGGER.debug('Making request: %s %s', method, url) + response, data = self.http.request( + url, method=method, body=body, headers=headers, **kwargs) + return _Response(response, data) + except httplib2.HttpLib2Error as exc: + raise exceptions.TransportError(exc) + + +def _make_default_http(): + """Returns a default httplib2.Http instance.""" + return httplib2.Http() + + +class AuthorizedHttp(object): + """A httplib2 HTTP class with credentials. + + This class is used to perform requests to API endpoints that require + authorization:: + + from google.auth.transport._httplib2 import AuthorizedHttp + + authed_http = AuthorizedHttp(credentials) + + response = authed_http.request( + 'https://www.googleapis.com/storage/v1/b') + + This class implements :meth:`request` in the same way as + :class:`httplib2.Http` and can usually be used just like any other + instance of :class:``httplib2.Http`. + + The underlying :meth:`request` implementation handles adding the + credentials' headers to the request and refreshing credentials as needed. + """ + def __init__(self, credentials, http=None, + refresh_status_codes=transport.DEFAULT_REFRESH_STATUS_CODES, + max_refresh_attempts=transport.DEFAULT_MAX_REFRESH_ATTEMPTS): + """ + Args: + credentials (google.auth.credentials.Credentials): The credentials + to add to the request. + http (httplib2.Http): The underlying HTTP object to + use to make requests. If not specified, a + :class:`httplib2.Http` instance will be constructed. + refresh_status_codes (Sequence[int]): Which HTTP status codes + indicate that credentials should be refreshed and the request + should be retried. + max_refresh_attempts (int): The maximum number of times to attempt + to refresh the credentials and retry the request. + """ + + if http is None: + http = _make_default_http() + + self.http = http + self.credentials = credentials + self._refresh_status_codes = refresh_status_codes + self._max_refresh_attempts = max_refresh_attempts + # Request instance used by internal methods (for example, + # credentials.refresh). + self._request = Request(self.http) + + def request(self, uri, method='GET', body=None, headers=None, + **kwargs): + """Implementation of httplib2's Http.request.""" + + _credential_refresh_attempt = kwargs.pop( + '_credential_refresh_attempt', 0) + + # Make a copy of the headers. They will be modified by the credentials + # and we want to pass the original headers if we recurse. + request_headers = headers.copy() if headers is not None else {} + + self.credentials.before_request( + self._request, method, uri, request_headers) + + # Check if the body is a file-like stream, and if so, save the body + # stream position so that it can be restored in case of refresh. + body_stream_position = None + if all(getattr(body, stream_prop, None) for stream_prop in + _STREAM_PROPERTIES): + body_stream_position = body.tell() + + # Make the request. + response, content = self.http.request( + uri, method, body=body, headers=request_headers, **kwargs) + + # If the response indicated that the credentials needed to be + # refreshed, then refresh the credentials and re-attempt the + # request. + # A stored token may expire between the time it is retrieved and + # the time the request is made, so we may need to try twice. + if (response.status in self._refresh_status_codes + and _credential_refresh_attempt < self._max_refresh_attempts): + + _LOGGER.info( + 'Refreshing credentials due to a %s response. Attempt %s/%s.', + response.status, _credential_refresh_attempt + 1, + self._max_refresh_attempts) + + self.credentials.refresh(self._request) + + # Restore the body's stream position if needed. + if body_stream_position is not None: + body.seek(body_stream_position) + + # Recurse. Pass in the original headers, not our modified set. + return self.request( + uri, method, body=body, headers=headers, + _credential_refresh_attempt=_credential_refresh_attempt + 1, + **kwargs) + + return response, content diff --git a/packages/google-auth/httplib2_transport/pylintrc b/packages/google-auth/httplib2_transport/pylintrc new file mode 100644 index 000000000000..1f01cfa1d04f --- /dev/null +++ b/packages/google-auth/httplib2_transport/pylintrc @@ -0,0 +1,165 @@ +[MASTER] +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS,.git,.cache,.tox,.nox + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +# DEFAULT: load-plugins= +# RATIONALE: We want to make sure our docstrings match the objects +# they document. +load-plugins=pylint.extensions.check_docs + +[MESSAGES CONTROL] +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once).You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use"--disable=all --enable=classes +# --disable=W" +# +# RATIONALE: +# - maybe-no-member: bi-modal functions confuse pylint type inference. +# - no-member: indirections in protobuf-generated code +# - protected-access: helpers use '_foo' of classes from generated code. +# - similarities: 'Bucket' and 'Blob' define 'metageneration' and 'owner' with +# identical implementation but different docstrings. +# - star-args: standard Python idioms for varargs: +# ancestor = Query().filter(*order_props) +# - redefined-variable-type: This error is overzealous and complains at e.g. +# if some_prop: +# return int(value) +# else: +# return float(value) +# - import-error: imports are checked via tests. +# - wrong-import-position: This error is overzealous. It assumes imports are +# completed whenever something non-trivial is +# defined, e.g. +# try: +# from foo import Bar +# except ImportError: +# class Bar(object): +# """Hi everyone""" +# and thus causes subsequent imports to be +# diagnosed as out-of-order. +# - no-name-in-module: Error gives a lot of false positives for names which +# are defined dynamically. Also, any truly missing names +# will be detected by our 100% code coverage. +# - locally-disabled: Allow us to make exceptions in edge cases, notably where +# pylint doesn't recognize inherited properties and methods +# and gives unused-argument errors. +# TEMPORARILY DISABLE AND SHOULD BE REMOVED: +# - fixme: disabled until 1.0 +# +disable = + import-star-module-level, + old-octal-literal, + oct-method, + print-statement, + unpacking-in-except, + parameter-unpacking, + backtick, + old-raise-syntax, + old-ne-operator, + long-suffix, + dict-view-method, + dict-iter-method, + metaclass-assignment, + next-method-called, + raising-string, + indexing-exception, + raw_input-builtin, + long-builtin, + file-builtin, + execfile-builtin, + coerce-builtin, + cmp-builtin, + buffer-builtin, + basestring-builtin, + apply-builtin, + filter-builtin-not-iterating, + using-cmp-argument, + useless-suppression, + range-builtin-not-iterating, + suppressed-message, + no-absolute-import, + old-division, + cmp-method, + reload-builtin, + zip-builtin-not-iterating, + intern-builtin, + unichr-builtin, + reduce-builtin, + standarderror-builtin, + unicode-builtin, + xrange-builtin, + coerce-method, + delslice-method, + getslice-method, + setslice-method, + input-builtin, + round-builtin, + hex-method, + nonzero-method, + map-builtin-not-iterating, + maybe-no-member, + no-member, + protected-access, + similarities, + star-args, + redefined-variable-type, + import-error, + wrong-import-position, + no-name-in-module, + locally-disabled, + locally-enabled, + fixme + + +[REPORTS] +# Tells whether to display a full report or only the messages +# RATIONALE: noisy +reports=no + +[BASIC] +# Regular expression matching correct method names +# DEFAULT: method-rgx=[a-z_][a-z0-9_]{2,30}$ +# RATIONALE: Some methods have longer names to be more descriptive or precise, +# especially those that implemented wordy RFCs. +method-rgx=[a-z_][a-z0-9_]{2,40}$ + +# Regular expression matching correct function names +# DEFAULT function-rgx=[a-z_][a-z0-9_]{2,30}$ +# RATIONALE: Some methods have longer names to be more descriptive or precise, +# especially those that implemented wordy RFCs. +function-rgx=[a-z_][a-z0-9_]{2,40}$ + +[TYPECHECK] +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis. It +# supports qualified module names, as well as Unix pattern matching. +# DEFAULT: ignored-modules= +# RATIONALE: six aliases stuff for compatibility. +# google.protobuf fixes up namespace package "late". +ignored-modules = six, google.protobuf + + +[DESIGN] +# Minimum number of public methods for a class (see R0903). +# DEFAULT: min-public-methods=2 +# RATIONALE: context mgrs may have *no* public methods +min-public-methods=0 + +# Maximum number of arguments for function / method +# DEFAULT: max-args=5 +# RATIONALE: Many credentials classes take a lot of parameters. +max-args = 10 + +# Maximum number of attributes for a class (see R0902). +# DEFAULT: max-attributes=7 +# RATIONALE: Many credentials need to track lots of properties. +max-attributes=15 diff --git a/packages/google-auth/httplib2_transport/pylintrc.tests b/packages/google-auth/httplib2_transport/pylintrc.tests new file mode 100644 index 000000000000..09772c5f50f1 --- /dev/null +++ b/packages/google-auth/httplib2_transport/pylintrc.tests @@ -0,0 +1,166 @@ +[MASTER] +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS,.git,.cache,.tox,.nox + +[MESSAGES CONTROL] +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once).You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use"--disable=all --enable=classes +# --disable=W" +# +# RATIONALE: +# - maybe-no-member: bi-modal functions confuse pylint type inference. +# - no-member: indirections in protobuf-generated code +# - protected-access: helpers use '_foo' of classes from generated code. +# - similarities: 'Bucket' and 'Blob' define 'metageneration' and 'owner' with +# identical implementation but different docstrings. +# - star-args: standard Python idioms for varargs: +# ancestor = Query().filter(*order_props) +# - redefined-variable-type: This error is overzealous and complains at e.g. +# if some_prop: +# return int(value) +# else: +# return float(value) +# - import-error: imports are checked via tests. +# - wrong-import-position: This error is overzealous. It assumes imports are +# completed whenever something non-trivial is +# defined, e.g. +# try: +# from foo import Bar +# except ImportError: +# class Bar(object): +# """Hi everyone""" +# and thus causes subsequent imports to be +# diagnosed as out-of-order. +# - no-name-in-module: Error gives a lot of false positives for names which +# are defined dynamically. Also, any truly missing names +# will be detected by our 100% code coverage. +# - locally-disabled: Allow us to make exceptions in edge cases, notably where +# pylint doesn't recognize inherited properties and methods +# and gives unused-argument errors. +disable = + import-star-module-level, + old-octal-literal, + oct-method, + print-statement, + unpacking-in-except, + parameter-unpacking, + backtick, + old-raise-syntax, + old-ne-operator, + long-suffix, + dict-view-method, + dict-iter-method, + metaclass-assignment, + next-method-called, + raising-string, + indexing-exception, + raw_input-builtin, + long-builtin, + file-builtin, + execfile-builtin, + coerce-builtin, + cmp-builtin, + buffer-builtin, + basestring-builtin, + apply-builtin, + filter-builtin-not-iterating, + using-cmp-argument, + useless-suppression, + range-builtin-not-iterating, + suppressed-message, + no-absolute-import, + old-division, + cmp-method, + reload-builtin, + zip-builtin-not-iterating, + intern-builtin, + unichr-builtin, + reduce-builtin, + standarderror-builtin, + unicode-builtin, + xrange-builtin, + coerce-method, + delslice-method, + getslice-method, + setslice-method, + input-builtin, + round-builtin, + hex-method, + nonzero-method, + map-builtin-not-iterating, + maybe-no-member, + no-member, + protected-access, + similarities, + star-args, + redefined-variable-type, + import-error, + wrong-import-position, + no-name-in-module, + locally-disabled, + locally-enabled, + missing-docstring, + redefined-outer-name, + no-self-use, + too-many-locals, + too-many-public-methods, + abstract-method, + method-hidden, + unused-argument + + +[REPORTS] +# Tells whether to display a full report or only the messages +# RATIONALE: noisy +reports=no + +[BASIC] +# Good variable names which should always be accepted, separated by a comma +# DEFAULT: good-names=i,j,k,ex,Run,_ +# RATIONALE: 'fh' is a well-known file handle variable name. +good-names = i, j, k, ex, Run, _, + fh + +# Regular expression matching correct method names +# DEFAULT: method-rgx=[a-z_][a-z0-9_]{2,30}$ +# RATIONALE: Some methods have longer names to be precise against wordy RFCs. +method-rgx=[a-z_][a-z0-9_]{2,80}$ + +# Regular expression matching correct function names +# DEFAULT function-rgx=[a-z_][a-z0-9_]{2,30}$ +# RATIONALE: Some test methods have long descriptive names. +function-rgx=[a-z_][a-z0-9_]{2,80}$ + +[TYPECHECK] +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis. It +# supports qualified module names, as well as Unix pattern matching. +# DEFAULT: ignored-modules= +# RATIONALE: six aliases stuff for compatibility. +# google.protobuf fixes up namespace package "late". +ignored-modules = six, google.protobuf + + +[DESIGN] +# Minimum number of public methods for a class (see R0903). +# DEFAULT: min-public-methods=2 +# RATIONALE: context mgrs may have *no* public methods +min-public-methods=0 + +# Maximum number of arguments for function / method +# DEFAULT: max-args=5 +# RATIONALE: Many credentials classes take a lot of parameters. +max-args = 10 + +# Maximum number of attributes for a class (see R0902). +# DEFAULT: max-attributes=7 +# RATIONALE: Many credentials need to track lots of properties. +max-attributes=15 diff --git a/packages/google-auth/httplib2_transport/setup.cfg b/packages/google-auth/httplib2_transport/setup.cfg new file mode 100644 index 000000000000..2a9acf13daa9 --- /dev/null +++ b/packages/google-auth/httplib2_transport/setup.cfg @@ -0,0 +1,2 @@ +[bdist_wheel] +universal = 1 diff --git a/packages/google-auth/httplib2_transport/setup.py b/packages/google-auth/httplib2_transport/setup.py new file mode 100644 index 000000000000..110cce847506 --- /dev/null +++ b/packages/google-auth/httplib2_transport/setup.py @@ -0,0 +1,58 @@ +# Copyright 2014 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from setuptools import setup + + +DEPENDENCIES = ( + 'pyasn1>=0.1.7', + 'pyasn1-modules>=0.0.5', + 'rsa>=3.1.4', + 'six>=1.9.0', + 'google-auth' +) + + +with open('README.rst', 'r') as fh: + long_description = fh.read() + + +setup( + name='google-auth-httplib2', + version='0.0.1', + author='Google Cloud Platform', + author_email='jonwayne+google-auth@google.com', + description='Google Authentication Library', + long_description=long_description, + url='https://github.com/GoogleCloudPlatform/google-auth-library-python', + py_modules=['google_auth_httplib2'], + install_requires=DEPENDENCIES, + license='Apache 2.0', + keywords='google auth oauth client', + classifiers=( + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Development Status :: 3 - Alpha', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: Apache Software License', + 'Operating System :: POSIX', + 'Operating System :: Microsoft :: Windows', + 'Operating System :: MacOS :: MacOS X', + 'Operating System :: OS Independent', + 'Topic :: Internet :: WWW/HTTP', + ), +) diff --git a/packages/google-auth/httplib2_transport/tests/__init__.py b/packages/google-auth/httplib2_transport/tests/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/google-auth/httplib2_transport/tests/compliance.py b/packages/google-auth/httplib2_transport/tests/compliance.py new file mode 100644 index 000000000000..a97a11b82bef --- /dev/null +++ b/packages/google-auth/httplib2_transport/tests/compliance.py @@ -0,0 +1,92 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import flask +from google.auth import exceptions +import pytest +from pytest_localserver.http import WSGIServer +from six.moves import http_client + +# .invalid will never resolve, see https://tools.ietf.org/html/rfc2606 +NXDOMAIN = 'test.invalid' + + +class RequestResponseTests(object): + + @pytest.fixture(scope='module') + def server(self): + """Provides a test HTTP server. + + The test server is automatically created before + a test and destroyed at the end. The server is serving a test + application that can be used to verify requests. + """ + app = flask.Flask(__name__) + app.debug = True + + # pylint: disable=unused-variable + # (pylint thinks the flask routes are unusued.) + @app.route('/basic') + def index(): + header_value = flask.request.headers.get('x-test-header', 'value') + headers = {'X-Test-Header': header_value} + return 'Basic Content', http_client.OK, headers + + @app.route('/server_error') + def server_error(): + return 'Error', http_client.INTERNAL_SERVER_ERROR + # pylint: enable=unused-variable + + server = WSGIServer(application=app.wsgi_app) + server.start() + yield server + server.stop() + + def test_request_basic(self, server): + request = self.make_request() + response = request(url=server.url + '/basic', method='GET') + + assert response.status == http_client.OK + assert response.headers['x-test-header'] == 'value' + assert response.data == b'Basic Content' + + def test_request_timeout(self, server): + request = self.make_request() + response = request(url=server.url + '/basic', method='GET', timeout=2) + + assert response.status == http_client.OK + assert response.headers['x-test-header'] == 'value' + assert response.data == b'Basic Content' + + def test_request_headers(self, server): + request = self.make_request() + response = request( + url=server.url + '/basic', method='GET', headers={ + 'x-test-header': 'hello world'}) + + assert response.status == http_client.OK + assert response.headers['x-test-header'] == 'hello world' + assert response.data == b'Basic Content' + + def test_request_error(self, server): + request = self.make_request() + response = request(url=server.url + '/server_error', method='GET') + + assert response.status == http_client.INTERNAL_SERVER_ERROR + assert response.data == b'Error' + + def test_connection_error(self): + request = self.make_request() + with pytest.raises(exceptions.TransportError): + request(url='http://{}'.format(NXDOMAIN), method='GET') diff --git a/packages/google-auth/httplib2_transport/tests/test_google_auth_httplib2.py b/packages/google-auth/httplib2_transport/tests/test_google_auth_httplib2.py new file mode 100644 index 000000000000..9c2da7080748 --- /dev/null +++ b/packages/google-auth/httplib2_transport/tests/test_google_auth_httplib2.py @@ -0,0 +1,148 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import httplib2 +import mock +import six +from six.moves import http_client + +import google_auth_httplib2 +from tests import compliance + + +class MockHttp(object): + def __init__(self, responses, headers=None): + self.responses = responses + self.requests = [] + self.headers = headers or {} + + def request(self, url, method='GET', body=None, headers=None, **kwargs): + self.requests.append((method, url, body, headers, kwargs)) + return self.responses.pop(0) + + +class MockResponse(object): + def __init__(self, status=http_client.OK, data=b''): + self.status = status + self.data = data + + def __iter__(self): + yield self + yield self.data + + +class TestRequestResponse(compliance.RequestResponseTests): + def make_request(self): + http = httplib2.Http() + return google_auth_httplib2.Request(http) + + def test_timeout(self): + url = 'http://example.com' + http = MockHttp(responses=[MockResponse()]) + request = google_auth_httplib2.Request(http) + request(url=url, method='GET', timeout=5) + + assert http.requests[0] == ( + 'GET', url, None, None, {}) + + +def test__make_default_http(): + http = google_auth_httplib2._make_default_http() + assert isinstance(http, httplib2.Http) + + +class MockCredentials(object): + def __init__(self, token='token'): + self.token = token + + def apply(self, headers): + headers['authorization'] = self.token + + def before_request(self, request, method, url, headers): + self.apply(headers) + + def refresh(self, request): + self.token += '1' + + +class TestAuthorizedHttp(object): + TEST_URL = 'http://example.com' + + def test_authed_http_defaults(self): + authed_http = google_auth_httplib2.AuthorizedHttp( + mock.sentinel.credentials) + + assert authed_http.credentials == mock.sentinel.credentials + assert isinstance(authed_http.http, httplib2.Http) + + def test_request_no_refresh(self): + mock_credentials = mock.Mock(wraps=MockCredentials()) + mock_response = MockResponse() + mock_http = MockHttp([mock_response]) + + authed_http = google_auth_httplib2.AuthorizedHttp( + mock_credentials, http=mock_http) + + response, data = authed_http.request(self.TEST_URL) + + assert response == mock_response + assert data == mock_response.data + assert mock_credentials.before_request.called + assert not mock_credentials.refresh.called + assert mock_http.requests == [ + ('GET', self.TEST_URL, None, {'authorization': 'token'}, {})] + + def test_request_refresh(self): + mock_credentials = mock.Mock(wraps=MockCredentials()) + mock_final_response = MockResponse(status=http_client.OK) + # First request will 401, second request will succeed. + mock_http = MockHttp([ + MockResponse(status=http_client.UNAUTHORIZED), + mock_final_response]) + + authed_http = google_auth_httplib2.AuthorizedHttp( + mock_credentials, http=mock_http) + + response, data = authed_http.request(self.TEST_URL) + + assert response == mock_final_response + assert data == mock_final_response.data + assert mock_credentials.before_request.call_count == 2 + assert mock_credentials.refresh.called + assert mock_http.requests == [ + ('GET', self.TEST_URL, None, {'authorization': 'token'}, {}), + ('GET', self.TEST_URL, None, {'authorization': 'token1'}, {})] + + def test_request_stream_body(self): + mock_credentials = mock.Mock(wraps=MockCredentials()) + mock_response = MockResponse() + # Refresh is needed to cover the resetting of the body position. + mock_http = MockHttp([ + MockResponse(status=http_client.UNAUTHORIZED), + mock_response]) + + body = six.StringIO('body') + body.seek(1) + + authed_http = google_auth_httplib2.AuthorizedHttp( + mock_credentials, http=mock_http) + + response, data = authed_http.request( + self.TEST_URL, method='POST', body=body) + + assert response == mock_response + assert data == mock_response.data + assert mock_http.requests == [ + ('POST', self.TEST_URL, body, {'authorization': 'token'}, {}), + ('POST', self.TEST_URL, body, {'authorization': 'token1'}, {})] diff --git a/packages/google-auth/httplib2_transport/tox.ini b/packages/google-auth/httplib2_transport/tox.ini new file mode 100644 index 000000000000..4d54203d083e --- /dev/null +++ b/packages/google-auth/httplib2_transport/tox.ini @@ -0,0 +1,40 @@ +[tox] +envlist = lint,py27,py34,py35,pypy,cover + +[testenv] +deps = + flask + mock + pytest + pytest-cov + pytest-localserver + httplib2 + {toxinidir}/.. +# Always recreate because of the relative path in deps above. +recreate = True +commands = + py.test --cov=google_auth_httplib2 --cov=tests {posargs:tests} + +[testenv:cover] +basepython = python3.5 +commands = + py.test --cov=google_auth_httplib2 --cov=tests --cov-report= tests + coverage report --show-missing --fail-under=100 +deps = + {[testenv]deps} + +[testenv:lint] +basepython = python3.5 +commands = + python setup.py check --metadata --restructuredtext --strict + flake8 \ + --import-order-style=google \ + --application-import-names="google_auth_httplib2,tests" \ + google_auth_httplib2.py tests + pylint --rcfile pylintrc google_auth_httplib2.py + pylint --rcfile pylintrc.tests tests +deps = + flake8 + flake8-import-order + pylint + docutils diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 615753d2ebb3..26b520e4c8f4 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -27,7 +27,6 @@ with open('README.rst', 'r') as fh: long_description = fh.read() - setup( name='google-auth', version='0.0.1', From 27b9d76cde4c35305583be99a120be935a637da6 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Fri, 21 Oct 2016 15:58:00 -0700 Subject: [PATCH 037/966] Add service_account.Credentials.to_jwt_credentials (#45) --- .../google/oauth2/service_account.py | 27 +++++++++++++++++++ .../tests/oauth2/test_service_account.py | 24 ++++++++++++----- 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/packages/google-auth/google/oauth2/service_account.py b/packages/google-auth/google/oauth2/service_account.py index dfbe352a79be..df6f1b2aa673 100644 --- a/packages/google-auth/google/oauth2/service_account.py +++ b/packages/google-auth/google/oauth2/service_account.py @@ -204,6 +204,33 @@ def from_service_account_file(cls, filename, **kwargs): filename, require=['client_email', 'token_uri']) return cls._from_signer_and_info(signer, info, **kwargs) + def to_jwt_credentials(self): + """Creates a :cls:`google.auth.jwt.Credentials` instance from this + instance. + + The new instance will use the same private key as this instance and + will use this instance's service account email as the issuer and + subject. + + This is the same as calling + :meth:`jwt.Credentials.from_service_account_file` with the same + file used to create these credentials:: + + svc_creds = service_account.Credentials.from_service_account_file( + 'service_account.json') + jwt_from_svc = svc_credentials.to_jwt_credentials() + # is the same as: + jwt_creds = jwt.Credentials.from_service_account_file( + 'service_account.json') + + Returns: + google.auth.jwt.Credentials: A new Credentials instance. + """ + return jwt.Credentials( + self._signer, + issuer=self._service_account_email, + subject=self._service_account_email) + @property def requires_scopes(self): """Checks if the credentials requires scopes. diff --git a/packages/google-auth/tests/oauth2/test_service_account.py b/packages/google-auth/tests/oauth2/test_service_account.py index 2c2a0cf78e86..01234b5aee6c 100644 --- a/packages/google-auth/tests/oauth2/test_service_account.py +++ b/packages/google-auth/tests/oauth2/test_service_account.py @@ -58,15 +58,14 @@ def credentials_fixture(self, signer): signer, self.SERVICE_ACCOUNT_EMAIL, self.TOKEN_URI) def test_from_service_account_info(self): - with open(SERVICE_ACCOUNT_JSON_FILE, 'r') as fh: - info = json.load(fh) - credentials = service_account.Credentials.from_service_account_info( - info) + SERVICE_ACCOUNT_INFO) - assert credentials._signer.key_id == info['private_key_id'] - assert credentials._service_account_email == info['client_email'] - assert credentials._token_uri == info['token_uri'] + assert (credentials._signer.key_id == + SERVICE_ACCOUNT_INFO['private_key_id']) + assert (credentials._service_account_email == + SERVICE_ACCOUNT_INFO['client_email']) + assert credentials._token_uri == SERVICE_ACCOUNT_INFO['token_uri'] def test_from_service_account_info_args(self): info = SERVICE_ACCOUNT_INFO.copy() @@ -112,6 +111,17 @@ def test_from_service_account_file_args(self): assert credentials._subject == subject assert credentials._additional_claims == additional_claims + def test_to_jwt_credentials(self): + jwt_from_svc = self.credentials.to_jwt_credentials() + jwt_from_info = jwt.Credentials.from_service_account_info( + SERVICE_ACCOUNT_INFO) + + assert isinstance(jwt_from_svc, jwt.Credentials) + assert jwt_from_svc._signer.key_id == jwt_from_info._signer.key_id + assert jwt_from_svc._issuer == jwt_from_info._issuer + assert jwt_from_svc._subject == jwt_from_info._subject + assert jwt_from_svc._audience == jwt_from_info._audience + def test_default_state(self): assert not self.credentials.valid # Expiration hasn't been set yet From 29de3a2f00196158e7eec20e4a60529cc8e02148 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Mon, 24 Oct 2016 10:00:58 -0700 Subject: [PATCH 038/966] Add app engine credentials (#46) --- .../google-auth/google/auth/app_engine.py | 90 +++++++++++++++++++ packages/google-auth/tests/test_app_engine.py | 88 ++++++++++++++++++ 2 files changed, 178 insertions(+) create mode 100644 packages/google-auth/google/auth/app_engine.py create mode 100644 packages/google-auth/tests/test_app_engine.py diff --git a/packages/google-auth/google/auth/app_engine.py b/packages/google-auth/google/auth/app_engine.py new file mode 100644 index 000000000000..6f32b239b290 --- /dev/null +++ b/packages/google-auth/google/auth/app_engine.py @@ -0,0 +1,90 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google App Engine standard environment credentials. + +This module provides authentication for application running on App Engine in +the standard environment using the `App Identity API`_. + + +.. _App Identity API: + https://cloud.google.com/appengine/docs/python/appidentity/ +""" + +import datetime + +from google.auth import _helpers +from google.auth import credentials + +try: + from google.appengine.api import app_identity +except ImportError: + app_identity = None + + +class Credentials(credentials.Scoped, credentials.Signing, + credentials.Credentials): + """App Engine standard environment credentials. + + These credentials use the App Engine App Identity API to obtain access + tokens. + """ + + def __init__(self, scopes=None, service_account_id=None): + """ + Args: + scopes (Sequence[str]): Scopes to request from the App Identity + API. + service_account_id (str): The service account ID passed into + :func:`google.appengine.api.app_identity.get_access_token`. + If not specified, the default application service account + ID will be used. + + Raises: + EnvironmentError: If the App Engine APIs are unavailable. + """ + if app_identity is None: + raise EnvironmentError( + 'The App Engine APIs are not available.') + + super(Credentials, self).__init__() + self._scopes = scopes + self._service_account_id = service_account_id + + @_helpers.copy_docstring(credentials.Credentials) + def refresh(self, request): + # pylint: disable=unused-argument + token, ttl = app_identity.get_access_token( + self._scopes, self._service_account_id) + expiry = _helpers.utcnow() + datetime.timedelta(seconds=ttl) + + self.token, self.expiry = token, expiry + + @property + def requires_scopes(self): + """Checks if the credentials requires scopes. + + Returns: + bool: True if there are no scopes set otherwise False. + """ + return not self._scopes + + @_helpers.copy_docstring(credentials.Scoped) + def with_scopes(self, scopes): + return Credentials( + scopes=scopes, service_account_id=self._service_account_id) + + @_helpers.copy_docstring(credentials.Signing) + def sign_bytes(self, message): + return app_identity.sign_blob(message) diff --git a/packages/google-auth/tests/test_app_engine.py b/packages/google-auth/tests/test_app_engine.py new file mode 100644 index 000000000000..a9844b0805c9 --- /dev/null +++ b/packages/google-auth/tests/test_app_engine.py @@ -0,0 +1,88 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import datetime + +import mock +import pytest + +from google.auth import app_engine + + +@pytest.fixture +def app_identity_mock(monkeypatch): + """Mocks the app_identity module for google.auth.app_engine.""" + app_identity_mock = mock.Mock() + monkeypatch.setattr( + app_engine, 'app_identity', app_identity_mock) + yield app_identity_mock + + +class TestCredentials(object): + def test_missing_apis(self): + with pytest.raises(EnvironmentError) as excinfo: + app_engine.Credentials() + + assert excinfo.match(r'App Engine APIs are not available') + + def test_default_state(self, app_identity_mock): + credentials = app_engine.Credentials() + + # Not token acquired yet + assert not credentials.valid + # Expiration hasn't been set yet + assert not credentials.expired + # Scopes are required + assert not credentials.scopes + assert credentials.requires_scopes + + def test_with_scopes(self, app_identity_mock): + credentials = app_engine.Credentials() + + assert not credentials.scopes + assert credentials.requires_scopes + + scoped_credentials = credentials.with_scopes(['email']) + + assert scoped_credentials.has_scopes(['email']) + assert not scoped_credentials.requires_scopes + + @mock.patch( + 'google.auth._helpers.utcnow', + return_value=datetime.datetime.min) + def test_refresh(self, now_mock, app_identity_mock): + token = 'token' + ttl = 100 + app_identity_mock.get_access_token.return_value = token, ttl + credentials = app_engine.Credentials(scopes=['email']) + + credentials.refresh(None) + + app_identity_mock.get_access_token.assert_called_with( + credentials.scopes, credentials._service_account_id) + assert credentials.token == token + assert credentials.expiry == ( + datetime.datetime.min + datetime.timedelta(seconds=ttl)) + assert credentials.valid + assert not credentials.expired + + def test_sign_bytes(self, app_identity_mock): + app_identity_mock.sign_blob.return_value = mock.sentinel.signature + credentials = app_engine.Credentials() + to_sign = b'123' + + signature = credentials.sign_bytes(to_sign) + + assert signature == mock.sentinel.signature + app_identity_mock.sign_blob.assert_called_with(to_sign) From 0831083efcf98b8259a18dd918bbc9cf97e3cc41 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Mon, 24 Oct 2016 13:44:25 -0700 Subject: [PATCH 039/966] Add GAE to application default credentials. --- packages/google-auth/google/auth/_default.py | 8 ++++++- .../google-auth/google/auth/app_engine.py | 15 +++++++++++++ packages/google-auth/tests/test__default.py | 21 ++++++++++++++++++- packages/google-auth/tests/test_app_engine.py | 12 +++++++++++ 4 files changed, 54 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index 3f6993a668cb..74b96b3ad07d 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -23,6 +23,7 @@ import os from google.auth import _cloud_sdk +from google.auth import app_engine from google.auth import compute_engine from google.auth import environment_vars from google.auth import exceptions @@ -150,7 +151,12 @@ def _get_explicit_environ_credentials(): def _get_gae_credentials(): """Gets Google App Engine App Identity credentials and project ID.""" - return None, None + try: + credentials = app_engine.Credentials() + project_id = app_engine.get_project_id() + return credentials, project_id + except EnvironmentError: + return None, None def _get_gce_credentials(request=None): diff --git a/packages/google-auth/google/auth/app_engine.py b/packages/google-auth/google/auth/app_engine.py index 6f32b239b290..d20ddf64606a 100644 --- a/packages/google-auth/google/auth/app_engine.py +++ b/packages/google-auth/google/auth/app_engine.py @@ -33,6 +33,21 @@ app_identity = None +def get_project_id(): + """Gets the project ID for the current App Engine application. + + Returns: + str: The project ID + + Raises: + EnvironmentError: If the App Engine APIs are unavailable. + """ + if app_identity is None: + raise EnvironmentError( + 'The App Engine APIs are not available.') + return app_identity.get_application_id() + + class Credentials(credentials.Scoped, credentials.Signing, credentials.Credentials): """App Engine standard environment credentials. diff --git a/packages/google-auth/tests/test__default.py b/packages/google-auth/tests/test__default.py index 137fdcd405d5..747bfa461ce9 100644 --- a/packages/google-auth/tests/test__default.py +++ b/packages/google-auth/tests/test__default.py @@ -19,6 +19,7 @@ import pytest from google.auth import _default +from google.auth import app_engine from google.auth import compute_engine from google.auth import environment_vars from google.auth import exceptions @@ -188,7 +189,25 @@ def test__get_gcloud_sdk_credentials_no_project_id( assert project_id is None -def test__get_gae_credentials(): +@pytest.fixture +def app_identity_mock(monkeypatch): + """Mocks the app_identity module for google.auth.app_engine.""" + app_identity_mock = mock.Mock() + monkeypatch.setattr( + app_engine, 'app_identity', app_identity_mock) + yield app_identity_mock + + +def test__get_gae_credentials(app_identity_mock): + app_identity_mock.get_application_id.return_value = mock.sentinel.project + + credentials, project_id = _default._get_gae_credentials() + + assert isinstance(credentials, app_engine.Credentials) + assert project_id == mock.sentinel.project + + +def test__get_gae_credentials_no_apis(): assert _default._get_gae_credentials() == (None, None) diff --git a/packages/google-auth/tests/test_app_engine.py b/packages/google-auth/tests/test_app_engine.py index a9844b0805c9..e1189ed493f0 100644 --- a/packages/google-auth/tests/test_app_engine.py +++ b/packages/google-auth/tests/test_app_engine.py @@ -29,6 +29,18 @@ def app_identity_mock(monkeypatch): yield app_identity_mock +def test_get_project_id(app_identity_mock): + app_identity_mock.get_application_id.return_value = mock.sentinel.project + assert app_engine.get_project_id() == mock.sentinel.project + + +def test_get_project_id_missing_apis(): + with pytest.raises(EnvironmentError) as excinfo: + assert app_engine.get_project_id() + + assert excinfo.match(r'App Engine APIs are not available') + + class TestCredentials(object): def test_missing_apis(self): with pytest.raises(EnvironmentError) as excinfo: From e017a4afca5d3e663272d98fe27e3359888cc54d Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Mon, 24 Oct 2016 13:51:26 -0700 Subject: [PATCH 040/966] Add _metadata.get_project_id. --- packages/google-auth/google/auth/_default.py | 2 +- .../google/auth/compute_engine/_metadata.py | 17 +++++++++++++++++ .../tests/compute_engine/test__metadata.py | 14 ++++++++++++++ packages/google-auth/tests/test__default.py | 5 +++-- 4 files changed, 35 insertions(+), 3 deletions(-) diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index 74b96b3ad07d..356780bf1d87 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -172,7 +172,7 @@ def _get_gce_credentials(request=None): if _metadata.ping(request=request): # Get the project ID. try: - project_id = _metadata.get(request, 'project/project-id') + project_id = _metadata.get_project_id(request=request) except exceptions.TransportError: _LOGGER.warning( 'No project ID could be determined from the Compute Engine ' diff --git a/packages/google-auth/google/auth/compute_engine/_metadata.py b/packages/google-auth/google/auth/compute_engine/_metadata.py index 6d13a542a48f..a1070154be72 100644 --- a/packages/google-auth/google/auth/compute_engine/_metadata.py +++ b/packages/google-auth/google/auth/compute_engine/_metadata.py @@ -128,6 +128,23 @@ def get(request, path, root=_METADATA_ROOT, recursive=False): url, response.status, response.data), response) +def get_project_id(request): + """Get the Google Cloud Project ID from the metadata server. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + + Returns: + str: The project ID + + Raises: + google.auth.exceptions.TransportError: if an error occurred while + retrieving metadata. + """ + return get(request, 'project/project-id') + + def get_service_account_info(request, service_account='default'): """Get information about a service account from the metadata server. diff --git a/packages/google-auth/tests/compute_engine/test__metadata.py b/packages/google-auth/tests/compute_engine/test__metadata.py index 4cc3e55e3296..6a45c426df07 100644 --- a/packages/google-auth/tests/compute_engine/test__metadata.py +++ b/packages/google-auth/tests/compute_engine/test__metadata.py @@ -126,6 +126,20 @@ def test_get_failure_bad_json(mock_request): headers=_metadata._METADATA_HEADERS) +def test_get_project_id(mock_request): + project = 'example-project' + request_mock = mock_request( + project, headers={'content-type': 'text/plain'}) + + project_id = _metadata.get_project_id(request_mock) + + request_mock.assert_called_once_with( + method='GET', + url=_metadata._METADATA_ROOT + 'project/project-id', + headers=_metadata._METADATA_HEADERS) + assert project_id == project + + @mock.patch('google.auth._helpers.utcnow', return_value=datetime.datetime.min) def test_get_service_account_token(utcnow, mock_request): ttl = 500 diff --git a/packages/google-auth/tests/test__default.py b/packages/google-auth/tests/test__default.py index 747bfa461ce9..e244b3de2b3e 100644 --- a/packages/google-auth/tests/test__default.py +++ b/packages/google-auth/tests/test__default.py @@ -214,7 +214,8 @@ def test__get_gae_credentials_no_apis(): @mock.patch( 'google.auth.compute_engine._metadata.ping', return_value=True) @mock.patch( - 'google.auth.compute_engine._metadata.get', return_value='example-project') + 'google.auth.compute_engine._metadata.get_project_id', + return_value='example-project') def test__get_gce_credentials(get_mock, ping_mock): credentials, project_id = _default._get_gce_credentials() @@ -233,7 +234,7 @@ def test__get_gce_credentials_no_ping(ping_mock): @mock.patch( 'google.auth.compute_engine._metadata.ping', return_value=True) @mock.patch( - 'google.auth.compute_engine._metadata.get', + 'google.auth.compute_engine._metadata.get_project_id', side_effect=exceptions.TransportError()) def test__get_gce_credentials_no_project_id(get_mock, ping_mock): credentials, project_id = _default._get_gce_credentials() From 5642851466e4ddfd43326ff38f71b4366cac78fd Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Tue, 25 Oct 2016 09:32:25 -0700 Subject: [PATCH 041/966] Add system tests for service account credentials (#51) --- packages/google-auth/.travis.yml | 9 ++- .../google-auth/scripts/decrypt-secrets.sh | 27 +++++++ .../google-auth/scripts/encrypt-secrets.sh | 32 ++++++++ packages/google-auth/scripts/travis.sh | 36 +++++++++ packages/google-auth/setup.py | 2 +- packages/google-auth/system_tests/.gitignore | 2 + packages/google-auth/system_tests/__init__.py | 0 packages/google-auth/system_tests/conftest.py | 74 ++++++++++++++++++ .../google-auth/system_tests/secrets.tar.enc | Bin 0 -> 11808 bytes .../system_tests/test_service_account.py | 43 ++++++++++ packages/google-auth/tox.ini | 18 ++++- 11 files changed, 239 insertions(+), 4 deletions(-) create mode 100755 packages/google-auth/scripts/decrypt-secrets.sh create mode 100755 packages/google-auth/scripts/encrypt-secrets.sh create mode 100755 packages/google-auth/scripts/travis.sh create mode 100644 packages/google-auth/system_tests/.gitignore create mode 100644 packages/google-auth/system_tests/__init__.py create mode 100644 packages/google-auth/system_tests/conftest.py create mode 100644 packages/google-auth/system_tests/secrets.tar.enc create mode 100644 packages/google-auth/system_tests/test_service_account.py diff --git a/packages/google-auth/.travis.yml b/packages/google-auth/.travis.yml index 295f14cb5ab6..a56fb349b60e 100644 --- a/packages/google-auth/.travis.yml +++ b/packages/google-auth/.travis.yml @@ -16,13 +16,17 @@ matrix: env: TOXENV=pypy - python: 3.5 env: TOXENV=cover + - python: 3.5 + env: TOXENV=py35-system SYSTEM_TEST=1 + - python: 2.7 + env: TOXENV=py27-system SYSTEM_TEST=1 cache: directories: - ${HOME}/.cache install: - pip install --upgrade tox script: -- tox +- scripts/travis.sh deploy: provider: pypi user: google_opensource @@ -32,3 +36,6 @@ deploy: on: tags: true repo: GoogleCloudPlatform/google-auth-library-python +env: + global: + secure: KjV9daSNZsM3/jth2d87MEu4irHtwNwUcdIUVglz3QrIX7fAcbFqHu/2Ux3yGC+iPJd5/i2jZWWz9F6iyuKdhyMySz2WaBsbySmCs1pREcRYWcK5GL49yPb3ZOZucprD/n0VIer0+eublQeBQRMxNdxfJEs6tgkHxTeiOJ3mHaeLa/nE1PXW9Ih6fgS5NT/7CE7SO0fw1th9ZdLdRyo3a9fphDWqiGlt0oRURRnoJ7qctLYAZ7uXDn3c6oXJ/dZsio6Hjx16dPNjn0dpkpCBFYN3D7wXD02Ysm/7u+SGl2FjqA76FmmnJsqJ5Nog1QV4v7YpJdTtpfXBOuXAov4Fz9xHVssX4dYZkW5JKsfVFFIilo1vQbO4mBjYw58/gJswJygD5smwO2GawAfm62mGpdx4mFv2lwZoqJbLg1qcuI/qc8DqPrc3Y1nVNu3oGdMcts5q2IeuZgI1yzdb/Qz0gtkkIDtPzoyBlAZE9hsylBI7SdoP7VpmP+vIW21yC3GpE3TajbVD8p53c70fV4YH0LSX6pFF6RBuSFLwarG+WYtPTEy2k3d0ATMdH0gBaf19FJ04RTwsbYZPqAy328Dl3RLioZffm8BHAeKzuVsocrIiiHjJM6PqtL4II0UfbiKahxLcI7t1cGTWVhUtqrnZKZwJrbLYGd08aBvioTne/Nk= diff --git a/packages/google-auth/scripts/decrypt-secrets.sh b/packages/google-auth/scripts/decrypt-secrets.sh new file mode 100755 index 000000000000..e02bfc10bf95 --- /dev/null +++ b/packages/google-auth/scripts/decrypt-secrets.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# Copyright 2015 Google Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +ROOT=$( dirname "$DIR" ) + +# Work from the project root. +cd $ROOT + +openssl aes-256-cbc -k "$1" \ + -in system_tests/secrets.tar.enc \ + -out system_tests/secrets.tar -d +tar xvf system_tests/secrets.tar +rm system_tests/secrets.tar diff --git a/packages/google-auth/scripts/encrypt-secrets.sh b/packages/google-auth/scripts/encrypt-secrets.sh new file mode 100755 index 000000000000..c6291b7f3347 --- /dev/null +++ b/packages/google-auth/scripts/encrypt-secrets.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +# Copyright 2015 Google Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +ROOT=$( dirname "$DIR" ) + +# Work from the project root. +cd $ROOT + +read -s -p "Enter password for encryption: " PASSWORD +echo + +tar cvf system_tests/secrets.tar system_tests/data +openssl aes-256-cbc -k "$PASSWORD" \ + -in system_tests/secrets.tar \ + -out system_tests/secrets.tar.enc +rm system_tests/secrets.tar + +travis encrypt "SECRETS_PASSWORD=$PASSWORD" --add --override diff --git a/packages/google-auth/scripts/travis.sh b/packages/google-auth/scripts/travis.sh new file mode 100755 index 000000000000..84a227a82ca4 --- /dev/null +++ b/packages/google-auth/scripts/travis.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +# Copyright 2015 Google Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +ROOT=$( dirname "$DIR" ) + +# Work from the project root. +cd $ROOT + +# Decrypt secrets and run system tests if not on an external PR. +if [[ -n $SYSTEM_TEST ]]; then + if [[ $TRAVIS_SECURE_ENV_VARS == "true" ]]; then + echo 'Extracting secrets.' + scripts/decrypt-secrets.sh "$SECRETS_PASSWORD" + else + # This is an external PR, so just mark system tests as green. + echo 'In system test but secrets are not available, skipping.' + exit 0 + fi +fi + +# Run tox. +tox diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 26b520e4c8f4..5ee243fed962 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -35,7 +35,7 @@ description='Google Authentication Library', long_description=long_description, url='https://github.com/GoogleCloudPlatform/google-auth-library-python', - packages=find_packages(exclude='tests'), + packages=find_packages(exclude=('tests', 'system_tests')), namespace_packages=('google',), install_requires=DEPENDENCIES, license='Apache 2.0', diff --git a/packages/google-auth/system_tests/.gitignore b/packages/google-auth/system_tests/.gitignore new file mode 100644 index 000000000000..f6bf39d14b0c --- /dev/null +++ b/packages/google-auth/system_tests/.gitignore @@ -0,0 +1,2 @@ +data +secrets.tar diff --git a/packages/google-auth/system_tests/__init__.py b/packages/google-auth/system_tests/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/google-auth/system_tests/conftest.py b/packages/google-auth/system_tests/conftest.py new file mode 100644 index 000000000000..066f80531c9d --- /dev/null +++ b/packages/google-auth/system_tests/conftest.py @@ -0,0 +1,74 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import os + +from google.auth import _helpers +import google.auth.transport.urllib3 +import pytest +import urllib3 + + +HERE = os.path.dirname(__file__) +DATA_DIR = os.path.join(HERE, 'data') +HTTP = urllib3.PoolManager() +TOKEN_INFO_URL = 'https://www.googleapis.com/oauth2/v3/tokeninfo' + + +@pytest.fixture +def service_account_file(): + """The full path to a valid service account key file.""" + yield os.path.join(DATA_DIR, 'service_account.json') + + +@pytest.fixture +def request(): + """A transport.request object.""" + yield google.auth.transport.urllib3.Request(HTTP) + + +@pytest.fixture +def token_info(request): + """Returns a function that obtains OAuth2 token info.""" + def _token_info(access_token=None, id_token=None): + query_params = {} + + if access_token is not None: + query_params['access_token'] = access_token + elif id_token is not None: + query_params['id_token'] = id_token + else: + raise ValueError('No token specified.') + + url = _helpers.update_query(TOKEN_INFO_URL, query_params) + + response = request(url=url, method='GET') + + return json.loads(response.data.decode('utf-8')) + + yield _token_info + + +def verify_environment(): + """Checks to make sure that requisite data files are available.""" + if not os.path.isdir(DATA_DIR): + raise EnvironmentError( + 'In order to run system tests, test data must exist in ' + 'system_tests/data. See CONTRIBUTING.rst for details.') + + +def pytest_configure(config): + """Pytest hook that runs before Pytest collects any tests.""" + verify_environment() diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc new file mode 100644 index 0000000000000000000000000000000000000000..bbe290fc27fb0556b6b4032b0a5e1d78482471dc GIT binary patch literal 11808 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 OdGC0ssEfhEg0G~Cf#2Q$ literal 0 HcmV?d00001 diff --git a/packages/google-auth/system_tests/test_service_account.py b/packages/google-auth/system_tests/test_service_account.py new file mode 100644 index 000000000000..e897c6f84074 --- /dev/null +++ b/packages/google-auth/system_tests/test_service_account.py @@ -0,0 +1,43 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from google.auth import exceptions +from google.oauth2 import service_account +import pytest + + +@pytest.fixture +def credentials(service_account_file): + yield service_account.Credentials.from_service_account_file( + service_account_file) + + +def test_refresh_no_scopes(request, credentials): + with pytest.raises(exceptions.RefreshError): + credentials.refresh(request) + + +def test_refresh_success(request, credentials, token_info): + credentials = credentials.with_scopes(['email', 'profile']) + + credentials.refresh(request) + + assert credentials.token + + info = token_info(credentials.token) + + assert info['email'] == credentials._service_account_email + assert info['scope'] == ( + 'https://www.googleapis.com/auth/userinfo.email ' + 'https://www.googleapis.com/auth/userinfo.profile') diff --git a/packages/google-auth/tox.ini b/packages/google-auth/tox.ini index bc7d154065f9..10f7d9946891 100644 --- a/packages/google-auth/tox.ini +++ b/packages/google-auth/tox.ini @@ -21,6 +21,20 @@ commands = deps = {[testenv]deps} +[testenv:py35-system] +basepython = python3.5 +commands = + py.test system_tests +deps = + {[testenv]deps} + +[testenv:py27-system] +basepython = python2.7 +commands = + py.test system_tests +deps = + {[testenv]deps} + [testenv:docgen] basepython = python3.5 deps = @@ -46,10 +60,10 @@ commands = python setup.py check --metadata --restructuredtext --strict flake8 \ --import-order-style=google \ - --application-import-names="google,tests" \ + --application-import-names="google,tests,system_tests" \ google tests pylint --rcfile pylintrc google - pylint --rcfile pylintrc.tests tests + pylint --rcfile pylintrc.tests tests system_tests deps = flake8 flake8-import-order From 6c7f5d9adc4ae6e0ca7ee29c4db98080e9def0e6 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Tue, 25 Oct 2016 14:20:52 -0700 Subject: [PATCH 042/966] Add compute engine system tests (#54) --- .../google/auth/compute_engine/_metadata.py | 2 +- .../google/auth/compute_engine/credentials.py | 3 +- .../system_tests/test_compute_engine.py | 38 +++++++++++++++++++ .../system_tests/test_service_account.py | 11 ++++-- .../tests/compute_engine/test_credentials.py | 2 +- 5 files changed, 48 insertions(+), 8 deletions(-) create mode 100644 packages/google-auth/system_tests/test_compute_engine.py diff --git a/packages/google-auth/google/auth/compute_engine/_metadata.py b/packages/google-auth/google/auth/compute_engine/_metadata.py index a1070154be72..e5abf2097248 100644 --- a/packages/google-auth/google/auth/compute_engine/_metadata.py +++ b/packages/google-auth/google/auth/compute_engine/_metadata.py @@ -35,7 +35,7 @@ # This is used to ping the metadata server, it avoids the cost of a DNS # lookup. _METADATA_IP_ROOT = 'http://169.254.169.254' -_METADATA_FLAVOR_HEADER = 'metdata-flavor' +_METADATA_FLAVOR_HEADER = 'metadata-flavor' _METADATA_FLAVOR_VALUE = 'Google' _METADATA_HEADERS = {_METADATA_FLAVOR_HEADER: _METADATA_FLAVOR_VALUE} diff --git a/packages/google-auth/google/auth/compute_engine/credentials.py b/packages/google-auth/google/auth/compute_engine/credentials.py index f0616d148b5b..cd215c57093f 100644 --- a/packages/google-auth/google/auth/compute_engine/credentials.py +++ b/packages/google-auth/google/auth/compute_engine/credentials.py @@ -19,7 +19,6 @@ """ -from google.auth import _helpers from google.auth import credentials from google.auth import exceptions from google.auth.compute_engine import _metadata @@ -71,7 +70,7 @@ def _retrieve_info(self, request): service_account=self._service_account_email) self._service_account_email = info['email'] - self._scopes = _helpers.string_to_scopes(info['scopes']) + self._scopes = info['scopes'] def refresh(self, request): """Refresh the access token and scopes. diff --git a/packages/google-auth/system_tests/test_compute_engine.py b/packages/google-auth/system_tests/test_compute_engine.py new file mode 100644 index 000000000000..c4b0c7b30813 --- /dev/null +++ b/packages/google-auth/system_tests/test_compute_engine.py @@ -0,0 +1,38 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from google.auth import _helpers +from google.auth import compute_engine +from google.auth.compute_engine import _metadata + + +@pytest.fixture(autouse=True) +def check_gce_environment(request): + if not _metadata.ping(request): + pytest.skip('Compute Engine metadata service is not available.') + + +def test_refresh(request, token_info): + credentials = compute_engine.Credentials() + + credentials.refresh(request) + + assert credentials.token is not None + assert credentials._service_account_email is not None + + info = token_info(credentials.token) + info_scopes = _helpers.string_to_scopes(info['scope']) + assert set(info_scopes) == set(credentials.scopes) diff --git a/packages/google-auth/system_tests/test_service_account.py b/packages/google-auth/system_tests/test_service_account.py index e897c6f84074..4ac0b439eb54 100644 --- a/packages/google-auth/system_tests/test_service_account.py +++ b/packages/google-auth/system_tests/test_service_account.py @@ -12,9 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. +import pytest + +from google.auth import _helpers from google.auth import exceptions from google.oauth2 import service_account -import pytest @pytest.fixture @@ -38,6 +40,7 @@ def test_refresh_success(request, credentials, token_info): info = token_info(credentials.token) assert info['email'] == credentials._service_account_email - assert info['scope'] == ( - 'https://www.googleapis.com/auth/userinfo.email ' - 'https://www.googleapis.com/auth/userinfo.profile') + info_scopes = _helpers.string_to_scopes(info['scope']) + assert set(info_scopes) == set([ + 'https://www.googleapis.com/auth/userinfo.email', + 'https://www.googleapis.com/auth/userinfo.profile']) diff --git a/packages/google-auth/tests/compute_engine/test_credentials.py b/packages/google-auth/tests/compute_engine/test_credentials.py index 90ce2fecec11..daa61fecfba9 100644 --- a/packages/google-auth/tests/compute_engine/test_credentials.py +++ b/packages/google-auth/tests/compute_engine/test_credentials.py @@ -42,7 +42,7 @@ def test_refresh_success(self, get_mock, now_mock): get_mock.side_effect = [{ # First request is for sevice account info. 'email': 'service-account@example.com', - 'scopes': 'one two' + 'scopes': ['one', 'two'] }, { # Second request is for the token. 'access_token': 'token', From 12bf8cf98882c7e5a26b154b346531b0dc8eb4b3 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Wed, 26 Oct 2016 09:41:00 -0700 Subject: [PATCH 043/966] Add oauth2.credentials system tests (#56) * Add script to generate user auth tokens. * Add oauth2.credentials system tests --- packages/google-auth/.travis.yml | 2 +- .../google-auth/scripts/obtain_user_auth.py | 65 ++++++++++++++++++ packages/google-auth/system_tests/conftest.py | 6 ++ .../google-auth/system_tests/secrets.tar.enc | Bin 11808 -> 18464 bytes .../system_tests/test_oauth2_credentials.py | 43 ++++++++++++ 5 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 packages/google-auth/scripts/obtain_user_auth.py create mode 100644 packages/google-auth/system_tests/test_oauth2_credentials.py diff --git a/packages/google-auth/.travis.yml b/packages/google-auth/.travis.yml index a56fb349b60e..315f7c14ccb8 100644 --- a/packages/google-auth/.travis.yml +++ b/packages/google-auth/.travis.yml @@ -38,4 +38,4 @@ deploy: repo: GoogleCloudPlatform/google-auth-library-python env: global: - secure: KjV9daSNZsM3/jth2d87MEu4irHtwNwUcdIUVglz3QrIX7fAcbFqHu/2Ux3yGC+iPJd5/i2jZWWz9F6iyuKdhyMySz2WaBsbySmCs1pREcRYWcK5GL49yPb3ZOZucprD/n0VIer0+eublQeBQRMxNdxfJEs6tgkHxTeiOJ3mHaeLa/nE1PXW9Ih6fgS5NT/7CE7SO0fw1th9ZdLdRyo3a9fphDWqiGlt0oRURRnoJ7qctLYAZ7uXDn3c6oXJ/dZsio6Hjx16dPNjn0dpkpCBFYN3D7wXD02Ysm/7u+SGl2FjqA76FmmnJsqJ5Nog1QV4v7YpJdTtpfXBOuXAov4Fz9xHVssX4dYZkW5JKsfVFFIilo1vQbO4mBjYw58/gJswJygD5smwO2GawAfm62mGpdx4mFv2lwZoqJbLg1qcuI/qc8DqPrc3Y1nVNu3oGdMcts5q2IeuZgI1yzdb/Qz0gtkkIDtPzoyBlAZE9hsylBI7SdoP7VpmP+vIW21yC3GpE3TajbVD8p53c70fV4YH0LSX6pFF6RBuSFLwarG+WYtPTEy2k3d0ATMdH0gBaf19FJ04RTwsbYZPqAy328Dl3RLioZffm8BHAeKzuVsocrIiiHjJM6PqtL4II0UfbiKahxLcI7t1cGTWVhUtqrnZKZwJrbLYGd08aBvioTne/Nk= + secure: s6GdhJklftl8w/9WoETwLtvtKL4ledPA/TuBuqCXQxSuYWaPuTdRVcvoejGkHJpp7i/7v2T/0etYl+5koyskKm5+QZZweaaL7MAyjPGp+hmIaIlWQRz6w481NOf3i9uSmoQycssT0mNmwScNIqo+igbA2y14mr/e9aBuOcxNNzNzFQp2vaRMEju6q7xZMjYdcudUWL48vq9CoNa3X2ZArpqjkApR/TfYlG7glOj43NxuVDN4z9wIyUjaMHBfPgEhjaOaRyEFgEYITRwX1qDoXqcZdTVIq4Cn0uCH+Mvrz6Y+oUJGTJqH1k7N/DhzbSN9lJnVYaQW/yuvGHiGAwbb6Tcxiq2UqqhA9MfbPpmstDECs46v9Z3BT252KvYEQY7Q1v9g2gFhHvFGWISUxs80rnnPhEYfa11JoLvj2t8cowkE4pvj4OH32Eoyvc5H07hW3F5xpuF7Jt7N09TNZkUrpmiRJEhfrVNgjsrWO77/q5h8mXGd+9vYmz++yzKu+63x8x1MpeigGCG73Dpu9Otm5eydOZfpJ39ZfZWUb7G2JahgHaGweM9dmnpJtzHQgijmHjjfAx9jgnQ8IQz9nkFmyMI8H7HouwalnrJtpSSbvMqOQ0kiZhMzdBKH5pD3tjLgSlgA0pKelBwlooY6jGlj4LrtbDAxa6cZyXiFoqWpT1w= diff --git a/packages/google-auth/scripts/obtain_user_auth.py b/packages/google-auth/scripts/obtain_user_auth.py new file mode 100644 index 000000000000..fd2afd8781a0 --- /dev/null +++ b/packages/google-auth/scripts/obtain_user_auth.py @@ -0,0 +1,65 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""This program obtains a set of user credentials. + +These credentials are needed to run the system test for OAuth2 credentials. +It's expected that a developer will run this program manually once to obtain +a refresh token. It's highly recommended to use a Google account created +specifically created for testing. +""" + +import json +import os + +from oauth2client import client +from oauth2client import tools + +HERE = os.path.dirname(__file__) +CLIENT_SECRETS_PATH = os.path.abspath(os.path.join( + HERE, '..', 'system_tests', 'data', 'client_secret.json')) +AUTHORIZED_USER_PATH = os.path.abspath(os.path.join( + HERE, '..', 'system_tests', 'data', 'authorized_user.json')) +SCOPES = ['email', 'profile'] + + +class NullStorage(client.Storage): + """Null storage implementation to prevent oauth2client from failing + on storage.put.""" + def locked_put(self, credentials): + pass + + +def main(): + flow = client.flow_from_clientsecrets(CLIENT_SECRETS_PATH, SCOPES) + + print('Starting credentials flow...') + credentials = tools.run_flow(flow, NullStorage()) + + # Save the credentials in the same format as the Cloud SDK's authorized + # user file. + data = { + 'type': 'authorized_user', + 'client_id': flow.client_id, + 'client_secret': flow.client_secret, + 'refresh_token': credentials.refresh_token + } + + with open(AUTHORIZED_USER_PATH, 'w') as fh: + json.dump(data, fh, indent=4) + + print('Created {}.'.format(AUTHORIZED_USER_PATH)) + +if __name__ == '__main__': + main() diff --git a/packages/google-auth/system_tests/conftest.py b/packages/google-auth/system_tests/conftest.py index 066f80531c9d..ebbae4842248 100644 --- a/packages/google-auth/system_tests/conftest.py +++ b/packages/google-auth/system_tests/conftest.py @@ -33,6 +33,12 @@ def service_account_file(): yield os.path.join(DATA_DIR, 'service_account.json') +@pytest.fixture +def authorized_user_file(): + """The full path to a valid authorized user file.""" + yield os.path.join(DATA_DIR, 'authorized_user.json') + + @pytest.fixture def request(): """A transport.request object.""" diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index bbe290fc27fb0556b6b4032b0a5e1d78482471dc..e61707e5aac970b93b67a68c63927aa862875efc 100644 GIT binary patch literal 18464 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 T?j`+JzSN(>jVck@!DaJx-Xsx3 literal 11808 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 OdGC0ssEfhEg0G~Cf#2Q$ diff --git a/packages/google-auth/system_tests/test_oauth2_credentials.py b/packages/google-auth/system_tests/test_oauth2_credentials.py new file mode 100644 index 000000000000..e268519b5646 --- /dev/null +++ b/packages/google-auth/system_tests/test_oauth2_credentials.py @@ -0,0 +1,43 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json + +from google.auth import _helpers +import google.oauth2.credentials + +GOOGLE_OAUTH2_TOKEN_ENDPOINT = 'https://accounts.google.com/o/oauth2/token' + + +def test_refresh(authorized_user_file, request, token_info): + with open(authorized_user_file, 'r') as fh: + info = json.load(fh) + + credentials = google.oauth2.credentials.Credentials( + None, # No access token, must be refreshed. + refresh_token=info['refresh_token'], + token_uri=GOOGLE_OAUTH2_TOKEN_ENDPOINT, + client_id=info['client_id'], + client_secret=info['client_secret']) + + credentials.refresh(request) + + assert credentials.token + + info = token_info(credentials.token) + + info_scopes = _helpers.string_to_scopes(info['scope']) + assert set(info_scopes) == set([ + 'https://www.googleapis.com/auth/userinfo.email', + 'https://www.googleapis.com/auth/userinfo.profile']) From 78791ddb780e7ad37fcdc985abcea3646974ffb2 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Wed, 26 Oct 2016 10:42:38 -0700 Subject: [PATCH 044/966] Fix typo --- packages/google-auth/scripts/obtain_user_auth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/scripts/obtain_user_auth.py b/packages/google-auth/scripts/obtain_user_auth.py index fd2afd8781a0..ae864666a5ec 100644 --- a/packages/google-auth/scripts/obtain_user_auth.py +++ b/packages/google-auth/scripts/obtain_user_auth.py @@ -17,7 +17,7 @@ These credentials are needed to run the system test for OAuth2 credentials. It's expected that a developer will run this program manually once to obtain a refresh token. It's highly recommended to use a Google account created -specifically created for testing. +specifically for testing. """ import json From 2fd54d0370aa2e17db2feb47d735975366890e10 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Wed, 26 Oct 2016 10:47:18 -0700 Subject: [PATCH 045/966] Read cloud sdk active_config file to determine the right config file to read (#57) --- .../google-auth/google/auth/_cloud_sdk.py | 42 ++++++++++++++++--- packages/google-auth/tests/test__cloud_sdk.py | 28 +++++++++++-- 2 files changed, 61 insertions(+), 9 deletions(-) diff --git a/packages/google-auth/google/auth/_cloud_sdk.py b/packages/google-auth/google/auth/_cloud_sdk.py index f48f5a679519..f51d825aadbe 100644 --- a/packages/google-auth/google/auth/_cloud_sdk.py +++ b/packages/google-auth/google/auth/_cloud_sdk.py @@ -14,6 +14,7 @@ """Helpers for reading the Google Cloud SDK's configuration.""" +import io import os import six @@ -32,10 +33,6 @@ # The name of the file in the Cloud SDK config that contains default # credentials. _CREDENTIALS_FILENAME = 'application_default_credentials.json' -# The name of the file in the Cloud SDK config that contains the -# active configuration. -_ACTIVE_CONFIG_FILENAME = os.path.join( - 'configurations', 'config_default') # The config section and key for the project ID in the cloud SDK config. _PROJECT_CONFIG_SECTION = 'core' _PROJECT_CONFIG_KEY = 'project' @@ -83,6 +80,40 @@ def get_application_default_credentials_path(): return os.path.join(config_path, _CREDENTIALS_FILENAME) +def _get_active_config(config_path): + """Gets the active config for the Cloud SDK. + + Args: + config_path (str): The Cloud SDK's config path. + + Returns: + str: The active configuration name. + """ + active_config_filename = os.path.join(config_path, 'active_config') + + if not os.path.isfile(active_config_filename): + return 'default' + + with io.open(active_config_filename, 'r', encoding='utf-8') as file_obj: + active_config_name = file_obj.read().strip() + + return active_config_name + + +def _get_config_file(config_path, config_name): + """Returns the full path to a configuration's config file. + + Args: + config_path (str): The Cloud SDK's config path. + config_name (str): The configuration name. + + Returns: + str: The config file path. + """ + return os.path.join( + config_path, 'configurations', 'config_{}'.format(config_name)) + + def get_project_id(): """Gets the project ID from the Cloud SDK's configuration. @@ -90,7 +121,8 @@ def get_project_id(): Optional[str]: The project ID. """ config_path = get_config_path() - config_file = os.path.join(config_path, _ACTIVE_CONFIG_FILENAME) + active_config = _get_active_config(config_path) + config_file = _get_config_file(config_path, active_config) if not os.path.isfile(config_file): return None diff --git a/packages/google-auth/tests/test__cloud_sdk.py b/packages/google-auth/tests/test__cloud_sdk.py index 35ee4269b5d4..86f69a15fff9 100644 --- a/packages/google-auth/tests/test__cloud_sdk.py +++ b/packages/google-auth/tests/test__cloud_sdk.py @@ -16,6 +16,7 @@ import os import mock +import py import pytest from google.auth import _cloud_sdk @@ -41,15 +42,20 @@ @pytest.fixture -def config_file(tmpdir): +def config_dir(tmpdir): config_dir = tmpdir.join( '.config', _cloud_sdk._CONFIG_DIRECTORY) - config_file = config_dir.join( - _cloud_sdk._ACTIVE_CONFIG_FILENAME) with CONFIG_PATH_PATCH as mock_get_config_dir: mock_get_config_dir.return_value = str(config_dir) - yield config_file + yield config_dir + + +@pytest.fixture +def config_file(config_dir): + config_file = py.path.local(_cloud_sdk._get_config_file( + str(config_dir), 'default')) + yield config_file def test_get_project_id(config_file): @@ -75,6 +81,20 @@ def test_get_project_id_no_section(config_file): assert project_id is None +def test_get_project_id_non_default_config(config_dir): + active_config = config_dir.join('active_config') + test_config = py.path.local(_cloud_sdk._get_config_file( + str(config_dir), 'test')) + + # Create an active config file that points to the 'test' config. + active_config.write('test', ensure=True) + test_config.write(CLOUD_SDK_CONFIG_DATA, ensure=True) + + project_id = _cloud_sdk.get_project_id() + + assert project_id == 'example-project' + + @CONFIG_PATH_PATCH def test_get_application_default_credentials_path(mock_get_config_dir): config_path = 'config_path' From d89142f3865854dc09cb00f75eaabd78bc19f2fe Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Wed, 26 Oct 2016 21:05:16 -0700 Subject: [PATCH 046/966] Add ADC system tests (#58) --- packages/google-auth/system_tests/conftest.py | 6 +- .../system_tests/test_compute_engine.py | 8 +- .../google-auth/system_tests/test_default.py | 121 ++++++++++++++++++ .../system_tests/test_oauth2_credentials.py | 4 +- .../system_tests/test_service_account.py | 8 +- 5 files changed, 134 insertions(+), 13 deletions(-) create mode 100644 packages/google-auth/system_tests/test_default.py diff --git a/packages/google-auth/system_tests/conftest.py b/packages/google-auth/system_tests/conftest.py index ebbae4842248..5969beeecd4d 100644 --- a/packages/google-auth/system_tests/conftest.py +++ b/packages/google-auth/system_tests/conftest.py @@ -40,13 +40,13 @@ def authorized_user_file(): @pytest.fixture -def request(): +def http_request(): """A transport.request object.""" yield google.auth.transport.urllib3.Request(HTTP) @pytest.fixture -def token_info(request): +def token_info(http_request): """Returns a function that obtains OAuth2 token info.""" def _token_info(access_token=None, id_token=None): query_params = {} @@ -60,7 +60,7 @@ def _token_info(access_token=None, id_token=None): url = _helpers.update_query(TOKEN_INFO_URL, query_params) - response = request(url=url, method='GET') + response = http_request(url=url, method='GET') return json.loads(response.data.decode('utf-8')) diff --git a/packages/google-auth/system_tests/test_compute_engine.py b/packages/google-auth/system_tests/test_compute_engine.py index c4b0c7b30813..7fd31b454515 100644 --- a/packages/google-auth/system_tests/test_compute_engine.py +++ b/packages/google-auth/system_tests/test_compute_engine.py @@ -20,15 +20,15 @@ @pytest.fixture(autouse=True) -def check_gce_environment(request): - if not _metadata.ping(request): +def check_gce_environment(http_request): + if not _metadata.ping(http_request): pytest.skip('Compute Engine metadata service is not available.') -def test_refresh(request, token_info): +def test_refresh(http_request, token_info): credentials = compute_engine.Credentials() - credentials.refresh(request) + credentials.refresh(http_request) assert credentials.token is not None assert credentials._service_account_email is not None diff --git a/packages/google-auth/system_tests/test_default.py b/packages/google-auth/system_tests/test_default.py new file mode 100644 index 000000000000..02242e94f820 --- /dev/null +++ b/packages/google-auth/system_tests/test_default.py @@ -0,0 +1,121 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os + +import py + +import google.auth +from google.auth import environment_vars +import google.oauth2.credentials +from google.oauth2 import service_account + + +def validate_refresh(credentials, http_request): + if credentials.requires_scopes: + credentials = credentials.with_scopes(['email', 'profile']) + + credentials.refresh(http_request) + + assert credentials.token + assert credentials.valid + + +def test_explicit_credentials_service_account( + monkeypatch, service_account_file, http_request): + monkeypatch.setitem( + os.environ, environment_vars.CREDENTIALS, service_account_file) + + credentials, project_id = google.auth.default() + + assert isinstance(credentials, service_account.Credentials) + assert project_id is not None + + validate_refresh(credentials, http_request) + + +def test_explicit_credentials_authorized_user( + monkeypatch, authorized_user_file, http_request): + monkeypatch.setitem( + os.environ, environment_vars.CREDENTIALS, authorized_user_file) + + credentials, project_id = google.auth.default() + + assert isinstance(credentials, google.oauth2.credentials.Credentials) + assert project_id is None + + validate_refresh(credentials, http_request) + + +def test_explicit_credentials_explicit_project_id( + monkeypatch, service_account_file, http_request): + project = 'system-test-project' + monkeypatch.setitem( + os.environ, environment_vars.CREDENTIALS, service_account_file) + monkeypatch.setitem( + os.environ, environment_vars.PROJECT, project) + + _, project_id = google.auth.default() + + assert project_id == project + + +def generate_cloud_sdk_config( + tmpdir, credentials_file, active_config='default', project=None): + tmpdir.join('active_config').write( + '{}\n'.format(active_config), ensure=True) + + if project is not None: + config_file = tmpdir.join( + 'configurations', 'config_{}'.format(active_config)) + config_file.write( + '[core]\nproject = {}'.format(project), ensure=True) + + py.path.local(credentials_file).copy( + tmpdir.join('application_default_credentials.json')) + + +def test_cloud_sdk_credentials_service_account( + tmpdir, monkeypatch, service_account_file, http_request): + # Create the Cloud SDK configuration tree + project = 'system-test-project' + generate_cloud_sdk_config(tmpdir, service_account_file, project=project) + monkeypatch.setitem( + os.environ, environment_vars.CLOUD_SDK_CONFIG_DIR, str(tmpdir)) + + credentials, project_id = google.auth.default() + + assert isinstance(credentials, service_account.Credentials) + assert project_id is not None + # The project ID should be the project ID specified in the the service + # account file, not the project in the config. + assert project_id is not project + + validate_refresh(credentials, http_request) + + +def test_cloud_sdk_credentials_authorized_user( + tmpdir, monkeypatch, authorized_user_file, http_request): + # Create the Cloud SDK configuration tree + project = 'system-test-project' + generate_cloud_sdk_config(tmpdir, authorized_user_file, project=project) + monkeypatch.setitem( + os.environ, environment_vars.CLOUD_SDK_CONFIG_DIR, str(tmpdir)) + + credentials, project_id = google.auth.default() + + assert isinstance(credentials, google.oauth2.credentials.Credentials) + assert project_id == project + + validate_refresh(credentials, http_request) diff --git a/packages/google-auth/system_tests/test_oauth2_credentials.py b/packages/google-auth/system_tests/test_oauth2_credentials.py index e268519b5646..ded0630d4534 100644 --- a/packages/google-auth/system_tests/test_oauth2_credentials.py +++ b/packages/google-auth/system_tests/test_oauth2_credentials.py @@ -20,7 +20,7 @@ GOOGLE_OAUTH2_TOKEN_ENDPOINT = 'https://accounts.google.com/o/oauth2/token' -def test_refresh(authorized_user_file, request, token_info): +def test_refresh(authorized_user_file, http_request, token_info): with open(authorized_user_file, 'r') as fh: info = json.load(fh) @@ -31,7 +31,7 @@ def test_refresh(authorized_user_file, request, token_info): client_id=info['client_id'], client_secret=info['client_secret']) - credentials.refresh(request) + credentials.refresh(http_request) assert credentials.token diff --git a/packages/google-auth/system_tests/test_service_account.py b/packages/google-auth/system_tests/test_service_account.py index 4ac0b439eb54..6b41ca225a97 100644 --- a/packages/google-auth/system_tests/test_service_account.py +++ b/packages/google-auth/system_tests/test_service_account.py @@ -25,15 +25,15 @@ def credentials(service_account_file): service_account_file) -def test_refresh_no_scopes(request, credentials): +def test_refresh_no_scopes(http_request, credentials): with pytest.raises(exceptions.RefreshError): - credentials.refresh(request) + credentials.refresh(http_request) -def test_refresh_success(request, credentials, token_info): +def test_refresh_success(http_request, credentials, token_info): credentials = credentials.with_scopes(['email', 'profile']) - credentials.refresh(request) + credentials.refresh(http_request) assert credentials.token From 13bbdc578679bec29abad5d73209f6dd5a646e65 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Thu, 27 Oct 2016 13:25:59 -0700 Subject: [PATCH 047/966] Add App Engine system tests (#59) --- packages/google-auth/.travis.yml | 4 +- .../system_tests/app_engine/app/.gitignore | 1 + .../system_tests/app_engine/app/app.yaml | 12 ++ .../app_engine/app/appengine_config.py | 29 ++++ .../system_tests/app_engine/app/main.py | 129 ++++++++++++++++++ .../app_engine/app/requirements.txt | 3 + .../app_engine/test_app_engine.py | 58 ++++++++ packages/google-auth/tox.ini | 8 +- 8 files changed, 240 insertions(+), 4 deletions(-) create mode 100644 packages/google-auth/system_tests/app_engine/app/.gitignore create mode 100644 packages/google-auth/system_tests/app_engine/app/app.yaml create mode 100644 packages/google-auth/system_tests/app_engine/app/appengine_config.py create mode 100644 packages/google-auth/system_tests/app_engine/app/main.py create mode 100644 packages/google-auth/system_tests/app_engine/app/requirements.txt create mode 100644 packages/google-auth/system_tests/app_engine/test_app_engine.py diff --git a/packages/google-auth/.travis.yml b/packages/google-auth/.travis.yml index 315f7c14ccb8..aa39f9a4a5c0 100644 --- a/packages/google-auth/.travis.yml +++ b/packages/google-auth/.travis.yml @@ -17,9 +17,9 @@ matrix: - python: 3.5 env: TOXENV=cover - python: 3.5 - env: TOXENV=py35-system SYSTEM_TEST=1 + env: TOXENV=py35-system SYSTEM_TEST=1 SKIP_APP_ENGINE_SYSTEM_TEST=1 - python: 2.7 - env: TOXENV=py27-system SYSTEM_TEST=1 + env: TOXENV=py27-system SYSTEM_TEST=1 SKIP_APP_ENGINE_SYSTEM_TEST=1 cache: directories: - ${HOME}/.cache diff --git a/packages/google-auth/system_tests/app_engine/app/.gitignore b/packages/google-auth/system_tests/app_engine/app/.gitignore new file mode 100644 index 000000000000..a65b41774ad5 --- /dev/null +++ b/packages/google-auth/system_tests/app_engine/app/.gitignore @@ -0,0 +1 @@ +lib diff --git a/packages/google-auth/system_tests/app_engine/app/app.yaml b/packages/google-auth/system_tests/app_engine/app/app.yaml new file mode 100644 index 000000000000..872efb37b6c9 --- /dev/null +++ b/packages/google-auth/system_tests/app_engine/app/app.yaml @@ -0,0 +1,12 @@ +api_version: 1 +service: google-auth-system-tests +runtime: python27 +threadsafe: true + +handlers: +- url: .* + script: main.app + +libraries: +- name: ssl + version: 2.7.11 diff --git a/packages/google-auth/system_tests/app_engine/app/appengine_config.py b/packages/google-auth/system_tests/app_engine/app/appengine_config.py new file mode 100644 index 000000000000..da02e10d0691 --- /dev/null +++ b/packages/google-auth/system_tests/app_engine/app/appengine_config.py @@ -0,0 +1,29 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from google.appengine.ext import vendor + +# Add any libraries installed in the "lib" folder. +vendor.add('lib') + + +# Patch os.path.expanduser. This should be fixed in GAE +# versions released after Nov 2016. +import os.path + + +def patched_expanduser(path): + return path + +os.path.expanduser = patched_expanduser diff --git a/packages/google-auth/system_tests/app_engine/app/main.py b/packages/google-auth/system_tests/app_engine/app/main.py new file mode 100644 index 000000000000..d19567d46c07 --- /dev/null +++ b/packages/google-auth/system_tests/app_engine/app/main.py @@ -0,0 +1,129 @@ +# Copyright 2016 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""App Engine standard application that runs basic system tests for +google.auth.app_engine. + +This application has to run tests manually instead of using py.test because +py.test currently doesn't work on App Engine standard. +""" + +import contextlib +import json +import sys +from StringIO import StringIO +import traceback + +from google.appengine.api import app_identity +import google.auth +from google.auth import _helpers +from google.auth import app_engine +import google.auth.transport.urllib3 +import urllib3.contrib.appengine +import webapp2 + +TOKEN_INFO_URL = 'https://www.googleapis.com/oauth2/v3/tokeninfo' +EMAIL_SCOPE = 'https://www.googleapis.com/auth/userinfo.email' +HTTP = urllib3.contrib.appengine.AppEngineManager() +HTTP_REQUEST = google.auth.transport.urllib3.Request(HTTP) + + +def test_credentials(): + credentials = app_engine.Credentials() + scoped_credentials = credentials.with_scopes([EMAIL_SCOPE]) + + scoped_credentials.refresh(None) + + assert scoped_credentials.valid + assert scoped_credentials.token is not None + + # Get token info and verify scope + url = _helpers.update_query(TOKEN_INFO_URL, { + 'access_token': scoped_credentials.token, + }) + response = HTTP_REQUEST(url=url, method='GET') + token_info = json.loads(response.data.decode('utf-8')) + + assert token_info['scope'] == EMAIL_SCOPE + + +def test_default(): + credentials, project_id = google.auth.default() + + assert isinstance(credentials, app_engine.Credentials) + assert project_id == app_identity.get_application_id() + + +@contextlib.contextmanager +def capture(): + """Context manager that captures stderr and stdout.""" + oldout, olderr = sys.stdout, sys.stderr + try: + out = StringIO() + sys.stdout, sys.stderr = out, out + yield out + finally: + sys.stdout, sys.stderr = oldout, olderr + + +def run_test_func(func): + with capture() as capsys: + try: + func() + return True, '' + except Exception as exc: + output = ( + 'Test {} failed: {}\n\n' + 'Stacktrace:\n{}\n\n' + 'Captured output:\n{}').format( + func.func_name, exc, traceback.format_exc(), + capsys.getvalue()) + return False, output + + +def run_tests(): + """Runs all tests. + + Returns: + Tuple[bool, str]: A tuple containing True if all tests pass, False + otherwise, and any captured output from the tests. + """ + status = True + output = '' + + tests = (test_credentials, test_default) + + for test in tests: + test_status, test_output = run_test_func(test) + status = status and test_status + output += test_output + + return status, output + + +class MainHandler(webapp2.RequestHandler): + def get(self): + self.response.headers['content-type'] = 'text/plain' + + status, output = run_tests() + + if not status: + self.response.status = 500 + + self.response.write(output) + + +app = webapp2.WSGIApplication([ + ('/', MainHandler), +], debug=True) diff --git a/packages/google-auth/system_tests/app_engine/app/requirements.txt b/packages/google-auth/system_tests/app_engine/app/requirements.txt new file mode 100644 index 000000000000..bd5c476ab2b4 --- /dev/null +++ b/packages/google-auth/system_tests/app_engine/app/requirements.txt @@ -0,0 +1,3 @@ +urllib3 +# Relative path to google-auth-python's source. +../../.. diff --git a/packages/google-auth/system_tests/app_engine/test_app_engine.py b/packages/google-auth/system_tests/app_engine/test_app_engine.py new file mode 100644 index 000000000000..a6ccb718fa6b --- /dev/null +++ b/packages/google-auth/system_tests/app_engine/test_app_engine.py @@ -0,0 +1,58 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import subprocess + +from google.auth import _cloud_sdk +import pytest + +SKIP_TEST_ENV = 'SKIP_APP_ENGINE_SYSTEM_TEST' +HERE = os.path.dirname(__file__) +TEST_APP_DIR = os.path.join(HERE, 'app') +TEST_APP_SERVICE = 'google-auth-system-tests' + + +def vendor_app_dependencies(): + """Vendors in the test application's third-party dependencies.""" + subprocess.check_call( + ['pip', 'install', '--target', 'lib', '-r', 'requirements.txt']) + + +def deploy_app(): + """Deploys the test application using gcloud.""" + subprocess.check_call( + ['gcloud', 'app', 'deploy', '-q', 'app.yaml']) + + +@pytest.fixture +def app(monkeypatch): + monkeypatch.chdir(TEST_APP_DIR) + + vendor_app_dependencies() + deploy_app() + + application_id = _cloud_sdk.get_project_id() + application_url = 'https://{}-dot-{}.appspot.com'.format( + TEST_APP_SERVICE, application_id) + + yield application_url + + +@pytest.mark.skipif( + SKIP_TEST_ENV in os.environ, + reason='Explicitly skipping App Engine system tests.') +def test_live_application(app, http_request): + response = http_request(method='GET', url=app) + assert response.status == 200, response.data.decode('utf-8') diff --git a/packages/google-auth/tox.ini b/packages/google-auth/tox.ini index 10f7d9946891..6864ddee74a8 100644 --- a/packages/google-auth/tox.ini +++ b/packages/google-auth/tox.ini @@ -24,16 +24,20 @@ deps = [testenv:py35-system] basepython = python3.5 commands = - py.test system_tests + py.test --ignore system_tests/app_engine/app {posargs:system_tests} deps = {[testenv]deps} +passenv = + SKIP_APP_ENGINE_SYSTEM_TEST [testenv:py27-system] basepython = python2.7 commands = - py.test system_tests + py.test --ignore system_tests/app_engine/app {posargs:system_tests} deps = {[testenv]deps} +passenv = + SKIP_APP_ENGINE_SYSTEM_TEST [testenv:docgen] basepython = python3.5 From 0ddcad417f0049505802ddd92242c879ac9d2267 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Thu, 27 Oct 2016 13:25:33 -0700 Subject: [PATCH 048/966] Fix template string in App Engine system tests. --- .../system_tests/app_engine/app/main.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/google-auth/system_tests/app_engine/app/main.py b/packages/google-auth/system_tests/app_engine/app/main.py index d19567d46c07..c7b89ab8cb93 100644 --- a/packages/google-auth/system_tests/app_engine/app/main.py +++ b/packages/google-auth/system_tests/app_engine/app/main.py @@ -33,6 +33,15 @@ import urllib3.contrib.appengine import webapp2 +FAILED_TEST_TMPL = """ +Test {} failed: {} + +Stacktrace: +{} + +Captured output: +{} +""" TOKEN_INFO_URL = 'https://www.googleapis.com/oauth2/v3/tokeninfo' EMAIL_SCOPE = 'https://www.googleapis.com/auth/userinfo.email' HTTP = urllib3.contrib.appengine.AppEngineManager() @@ -83,12 +92,9 @@ def run_test_func(func): func() return True, '' except Exception as exc: - output = ( - 'Test {} failed: {}\n\n' - 'Stacktrace:\n{}\n\n' - 'Captured output:\n{}').format( - func.func_name, exc, traceback.format_exc(), - capsys.getvalue()) + output = FAILED_TEST_TMPL.format( + func.func_name, exc, traceback.format_exc(), + capsys.getvalue()) return False, output From f193ad24084bf593fd1bfd55cccd7ed67a06854d Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Thu, 27 Oct 2016 23:12:39 -0700 Subject: [PATCH 049/966] Nox system test refactor (#60) * Add basic noxfile to orchestrate system tests * Move explicit system tests to nox * Add cloud sdk environment automation --- packages/google-auth/.gitignore | 1 + packages/google-auth/.travis.yml | 3 +- .../google-auth/google/auth/_cloud_sdk.py | 9 +- packages/google-auth/system_tests/conftest.py | 23 +- packages/google-auth/system_tests/nox.py | 211 ++++++++++++++++++ .../system_tests/test_compute_engine.py | 2 +- .../google-auth/system_tests/test_default.py | 105 +-------- packages/google-auth/tox.ini | 10 +- 8 files changed, 255 insertions(+), 109 deletions(-) create mode 100644 packages/google-auth/system_tests/nox.py diff --git a/packages/google-auth/.gitignore b/packages/google-auth/.gitignore index cd35d1544c66..2c7b7226d5aa 100644 --- a/packages/google-auth/.gitignore +++ b/packages/google-auth/.gitignore @@ -9,6 +9,7 @@ dist/ docs/_build # Test files +.nox/ .tox/ .cache/ diff --git a/packages/google-auth/.travis.yml b/packages/google-auth/.travis.yml index aa39f9a4a5c0..642101b6d9dd 100644 --- a/packages/google-auth/.travis.yml +++ b/packages/google-auth/.travis.yml @@ -38,4 +38,5 @@ deploy: repo: GoogleCloudPlatform/google-auth-library-python env: global: - secure: s6GdhJklftl8w/9WoETwLtvtKL4ledPA/TuBuqCXQxSuYWaPuTdRVcvoejGkHJpp7i/7v2T/0etYl+5koyskKm5+QZZweaaL7MAyjPGp+hmIaIlWQRz6w481NOf3i9uSmoQycssT0mNmwScNIqo+igbA2y14mr/e9aBuOcxNNzNzFQp2vaRMEju6q7xZMjYdcudUWL48vq9CoNa3X2ZArpqjkApR/TfYlG7glOj43NxuVDN4z9wIyUjaMHBfPgEhjaOaRyEFgEYITRwX1qDoXqcZdTVIq4Cn0uCH+Mvrz6Y+oUJGTJqH1k7N/DhzbSN9lJnVYaQW/yuvGHiGAwbb6Tcxiq2UqqhA9MfbPpmstDECs46v9Z3BT252KvYEQY7Q1v9g2gFhHvFGWISUxs80rnnPhEYfa11JoLvj2t8cowkE4pvj4OH32Eoyvc5H07hW3F5xpuF7Jt7N09TNZkUrpmiRJEhfrVNgjsrWO77/q5h8mXGd+9vYmz++yzKu+63x8x1MpeigGCG73Dpu9Otm5eydOZfpJ39ZfZWUb7G2JahgHaGweM9dmnpJtzHQgijmHjjfAx9jgnQ8IQz9nkFmyMI8H7HouwalnrJtpSSbvMqOQ0kiZhMzdBKH5pD3tjLgSlgA0pKelBwlooY6jGlj4LrtbDAxa6cZyXiFoqWpT1w= + - secure: s6GdhJklftl8w/9WoETwLtvtKL4ledPA/TuBuqCXQxSuYWaPuTdRVcvoejGkHJpp7i/7v2T/0etYl+5koyskKm5+QZZweaaL7MAyjPGp+hmIaIlWQRz6w481NOf3i9uSmoQycssT0mNmwScNIqo+igbA2y14mr/e9aBuOcxNNzNzFQp2vaRMEju6q7xZMjYdcudUWL48vq9CoNa3X2ZArpqjkApR/TfYlG7glOj43NxuVDN4z9wIyUjaMHBfPgEhjaOaRyEFgEYITRwX1qDoXqcZdTVIq4Cn0uCH+Mvrz6Y+oUJGTJqH1k7N/DhzbSN9lJnVYaQW/yuvGHiGAwbb6Tcxiq2UqqhA9MfbPpmstDECs46v9Z3BT252KvYEQY7Q1v9g2gFhHvFGWISUxs80rnnPhEYfa11JoLvj2t8cowkE4pvj4OH32Eoyvc5H07hW3F5xpuF7Jt7N09TNZkUrpmiRJEhfrVNgjsrWO77/q5h8mXGd+9vYmz++yzKu+63x8x1MpeigGCG73Dpu9Otm5eydOZfpJ39ZfZWUb7G2JahgHaGweM9dmnpJtzHQgijmHjjfAx9jgnQ8IQz9nkFmyMI8H7HouwalnrJtpSSbvMqOQ0kiZhMzdBKH5pD3tjLgSlgA0pKelBwlooY6jGlj4LrtbDAxa6cZyXiFoqWpT1w= + - CLOUD_SDK_ROOT: ${HOME}/.cache/cloud-sdk diff --git a/packages/google-auth/google/auth/_cloud_sdk.py b/packages/google-auth/google/auth/_cloud_sdk.py index f51d825aadbe..1e851c87defb 100644 --- a/packages/google-auth/google/auth/_cloud_sdk.py +++ b/packages/google-auth/google/auth/_cloud_sdk.py @@ -131,13 +131,14 @@ def get_project_id(): try: config.read(config_file) + + if config.has_section(_PROJECT_CONFIG_SECTION): + return config.get( + _PROJECT_CONFIG_SECTION, _PROJECT_CONFIG_KEY) + except configparser.Error: return None - if config.has_section(_PROJECT_CONFIG_SECTION): - return config.get( - _PROJECT_CONFIG_SECTION, _PROJECT_CONFIG_KEY) - def load_authorized_user_credentials(info): """Loads an authorized user credential. diff --git a/packages/google-auth/system_tests/conftest.py b/packages/google-auth/system_tests/conftest.py index 5969beeecd4d..afa78545ec47 100644 --- a/packages/google-auth/system_tests/conftest.py +++ b/packages/google-auth/system_tests/conftest.py @@ -23,20 +23,22 @@ HERE = os.path.dirname(__file__) DATA_DIR = os.path.join(HERE, 'data') -HTTP = urllib3.PoolManager() +SERVICE_ACCOUNT_FILE = os.path.join(DATA_DIR, 'service_account.json') +AUTHORIZED_USER_FILE = os.path.join(DATA_DIR, 'authorized_user.json') +HTTP = urllib3.PoolManager(retries=False) TOKEN_INFO_URL = 'https://www.googleapis.com/oauth2/v3/tokeninfo' @pytest.fixture def service_account_file(): """The full path to a valid service account key file.""" - yield os.path.join(DATA_DIR, 'service_account.json') + yield SERVICE_ACCOUNT_FILE @pytest.fixture def authorized_user_file(): """The full path to a valid authorized user file.""" - yield os.path.join(DATA_DIR, 'authorized_user.json') + yield AUTHORIZED_USER_FILE @pytest.fixture @@ -67,6 +69,21 @@ def _token_info(access_token=None, id_token=None): yield _token_info +@pytest.fixture +def verify_refresh(http_request): + """Returns a function that verifies that credentials can be refreshed.""" + def _verify_refresh(credentials): + if credentials.requires_scopes: + credentials = credentials.with_scopes(['email', 'profile']) + + credentials.refresh(http_request) + + assert credentials.token + assert credentials.valid + + yield _verify_refresh + + def verify_environment(): """Checks to make sure that requisite data files are available.""" if not os.path.isdir(DATA_DIR): diff --git a/packages/google-auth/system_tests/nox.py b/packages/google-auth/system_tests/nox.py new file mode 100644 index 000000000000..67f99004d99a --- /dev/null +++ b/packages/google-auth/system_tests/nox.py @@ -0,0 +1,211 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Noxfile for automating system tests. + +This file handles setting up environments needed by the system tests. This +separates the tests from their environment configuration. + +See the `nox docs`_ for details on how this file works: + +.. _nox docs: http://nox.readthedocs.io/en/latest/ +""" + +import os + +from nox.command import which +import py.path + + +HERE = os.path.dirname(__file__) +DATA_DIR = os.path.join(HERE, 'data') +SERVICE_ACCOUNT_FILE = os.path.join(DATA_DIR, 'service_account.json') +AUTHORIZED_USER_FILE = os.path.join(DATA_DIR, 'authorized_user.json') +EXPLICIT_CREDENTIALS_ENV = 'GOOGLE_APPLICATION_CREDENTIALS' +EXPLICIT_PROJECT_ENV = 'GOOGLE_CLOUD_PROJECT' +EXPECT_PROJECT_ENV = 'EXPECT_PROJECT_ID' + +# The download location for the Cloud SDK +CLOUD_SDK_DIST_FILENAME = 'google-cloud-sdk.tar.gz' +CLOUD_SDK_DOWNLOAD_URL = ( + 'https://dl.google.com/dl/cloudsdk/release/{}'.format( + CLOUD_SDK_DIST_FILENAME)) + +# This environment variable is recognized by the Cloud SDK and overrides +# the location of the SDK's configuration files (which is usually at +# ${HOME}/.config). +CLOUD_SDK_CONFIG_ENV = 'CLOUDSDK_CONFIG' + +# If set, this is where the environment setup will install the Cloud SDK. +# If unset, it will download the SDK to a temporary directory. +CLOUD_SDK_ROOT = os.environ.get('CLOUD_SDK_ROOT') + +if CLOUD_SDK_ROOT is not None: + CLOUD_SDK_ROOT = py.path.local(CLOUD_SDK_ROOT) + CLOUD_SDK_ROOT.ensure(dir=True) # Makes sure the directory exists. +else: + CLOUD_SDK_ROOT = py.path.local.mkdtemp() + +# The full path the cloud sdk install directory +CLOUD_SDK_INSTALL_DIR = CLOUD_SDK_ROOT.join('google-cloud-sdk') + +# The full path to the gcloud cli executable. +GCLOUD = str(CLOUD_SDK_INSTALL_DIR.join('bin', 'gcloud')) + +# gcloud requires Python 2 and doesn't work on 3, so we need to tell it +# where to find 2 when we're running in a 3 environment. +CLOUD_SDK_PYTHON_ENV = 'CLOUDSDK_PYTHON' +CLOUD_SDK_PYTHON = which('python2', None) + +# Cloud SDK helpers + + +def install_cloud_sdk(session): + """Downloads and installs the Google Cloud SDK.""" + # Configure environment variables needed by the SDK. + # This sets the config root to the tests' config root. This prevents + # our tests from clobbering a developer's configuration when running + # these tests locally. + session.env[CLOUD_SDK_CONFIG_ENV] = str(CLOUD_SDK_ROOT) + # This tells gcloud which Python interpreter to use (always use 2.7) + session.env[CLOUD_SDK_PYTHON_ENV] = CLOUD_SDK_PYTHON + + # If the glcoud already exists, we don't need to do anything else. + # Note that because of this we do not attempt to update the sdk - + # if the CLOUD_SDK_ROOT is cached, it will need to be periodically cleared. + if py.path.local(GCLOUD).exists(): + return + + tar_path = CLOUD_SDK_ROOT.join(CLOUD_SDK_DIST_FILENAME) + + # Download the release. + session.run( + 'wget', CLOUD_SDK_DOWNLOAD_URL, '-O', str(tar_path), silent=True) + + # Extract the release. + session.run( + 'tar', 'xzf', str(tar_path), '-C', str(CLOUD_SDK_ROOT)) + session.run(tar_path.remove) + + # Run the install script. + session.run( + str(CLOUD_SDK_INSTALL_DIR.join('install.sh')), + '--usage-reporting', 'false', + '--path-update', 'false', + '--command-completion', 'false', + silent=True) + + +def copy_credentials(credentials_path): + """Copies credentials into the SDK root as the application default + credentials.""" + dest = CLOUD_SDK_ROOT.join('application_default_credentials.json') + if dest.exists(): + dest.remove() + py.path.local(credentials_path).copy(dest) + + +def configure_cloud_sdk( + session, application_default_credentials, project=False): + """Installs and configures the Cloud SDK with the given application default + credentials. + + If project is True, then a project will be set in the active config. + If it is false, this will ensure no project is set. + """ + install_cloud_sdk(session) + + if project: + session.run(GCLOUD, 'config', 'set', 'project', 'example-project') + else: + session.run(GCLOUD, 'config', 'unset', 'project') + + # Copy the credentials file to the config root. This is needed because + # unfortunately gcloud doesn't provide a clean way to tell it to use + # a particular set of credentials. However, this does verify that gcloud + # also considers the credentials valid by calling application-default + # print-access-token + session.run(copy_credentials, application_default_credentials) + + # Calling this forces the Cloud SDK to read the credentials we just wrote + # and obtain a new access token with those credentials. This validates + # that our credentials matches the format expected by gcloud. + # Silent is set to True to prevent leaking secrets in test logs. + session.run( + GCLOUD, 'auth', 'application-default', 'print-access-token', + silent=True) + + +# Test sesssions + + +def session_service_account(session): + session.virtualenv = False + session.run('pytest', 'test_service_account.py') + + +def session_oauth2_credentials(session): + session.virtualenv = False + session.run('pytest', 'test_oauth2_credentials.py') + + +def session_default_explicit_service_account(session): + session.virtualenv = False + session.env[EXPLICIT_CREDENTIALS_ENV] = SERVICE_ACCOUNT_FILE + session.env[EXPECT_PROJECT_ENV] = '1' + session.run('pytest', 'test_default.py') + + +def session_default_explicit_authorized_user(session): + session.virtualenv = False + session.env[EXPLICIT_CREDENTIALS_ENV] = AUTHORIZED_USER_FILE + session.run('pytest', 'test_default.py') + + +def session_default_explicit_authorized_user_explicit_project(session): + session.virtualenv = False + session.env[EXPLICIT_CREDENTIALS_ENV] = AUTHORIZED_USER_FILE + session.env[EXPLICIT_PROJECT_ENV] = 'example-project' + session.env[EXPECT_PROJECT_ENV] = '1' + session.run('pytest', 'test_default.py') + + +def session_default_cloud_sdk_service_account(session): + session.virtualenv = False + configure_cloud_sdk(session, SERVICE_ACCOUNT_FILE) + session.env[EXPECT_PROJECT_ENV] = '1' + session.run('pytest', 'test_default.py') + + +def session_default_cloud_sdk_authorized_user(session): + session.virtualenv = False + configure_cloud_sdk(session, AUTHORIZED_USER_FILE) + session.run('pytest', 'test_default.py') + + +def session_default_cloud_sdk_authorized_user_configured_project(session): + session.virtualenv = False + configure_cloud_sdk(session, AUTHORIZED_USER_FILE, project=True) + session.env[EXPECT_PROJECT_ENV] = '1' + session.run('pytest', 'test_default.py') + + +def session_compute_engine(session): + session.virtualenv = False + session.run('pytest', 'test_compute_engine.py') + + +def session_app_engine(session): + session.virtualenv = False + session.run('pytest', 'app_engine/test_app_engine.py') diff --git a/packages/google-auth/system_tests/test_compute_engine.py b/packages/google-auth/system_tests/test_compute_engine.py index 7fd31b454515..ceba0f787636 100644 --- a/packages/google-auth/system_tests/test_compute_engine.py +++ b/packages/google-auth/system_tests/test_compute_engine.py @@ -21,7 +21,7 @@ @pytest.fixture(autouse=True) def check_gce_environment(http_request): - if not _metadata.ping(http_request): + if not _metadata.ping(http_request, timeout=1): pytest.skip('Compute Engine metadata service is not available.') diff --git a/packages/google-auth/system_tests/test_default.py b/packages/google-auth/system_tests/test_default.py index 02242e94f820..0e0bcc6c0ebb 100644 --- a/packages/google-auth/system_tests/test_default.py +++ b/packages/google-auth/system_tests/test_default.py @@ -14,108 +14,17 @@ import os -import py - import google.auth -from google.auth import environment_vars -import google.oauth2.credentials -from google.oauth2 import service_account - - -def validate_refresh(credentials, http_request): - if credentials.requires_scopes: - credentials = credentials.with_scopes(['email', 'profile']) - - credentials.refresh(http_request) - - assert credentials.token - assert credentials.valid - - -def test_explicit_credentials_service_account( - monkeypatch, service_account_file, http_request): - monkeypatch.setitem( - os.environ, environment_vars.CREDENTIALS, service_account_file) - - credentials, project_id = google.auth.default() - - assert isinstance(credentials, service_account.Credentials) - assert project_id is not None - - validate_refresh(credentials, http_request) - - -def test_explicit_credentials_authorized_user( - monkeypatch, authorized_user_file, http_request): - monkeypatch.setitem( - os.environ, environment_vars.CREDENTIALS, authorized_user_file) - - credentials, project_id = google.auth.default() - - assert isinstance(credentials, google.oauth2.credentials.Credentials) - assert project_id is None - - validate_refresh(credentials, http_request) - - -def test_explicit_credentials_explicit_project_id( - monkeypatch, service_account_file, http_request): - project = 'system-test-project' - monkeypatch.setitem( - os.environ, environment_vars.CREDENTIALS, service_account_file) - monkeypatch.setitem( - os.environ, environment_vars.PROJECT, project) - - _, project_id = google.auth.default() - - assert project_id == project - - -def generate_cloud_sdk_config( - tmpdir, credentials_file, active_config='default', project=None): - tmpdir.join('active_config').write( - '{}\n'.format(active_config), ensure=True) - - if project is not None: - config_file = tmpdir.join( - 'configurations', 'config_{}'.format(active_config)) - config_file.write( - '[core]\nproject = {}'.format(project), ensure=True) - - py.path.local(credentials_file).copy( - tmpdir.join('application_default_credentials.json')) - - -def test_cloud_sdk_credentials_service_account( - tmpdir, monkeypatch, service_account_file, http_request): - # Create the Cloud SDK configuration tree - project = 'system-test-project' - generate_cloud_sdk_config(tmpdir, service_account_file, project=project) - monkeypatch.setitem( - os.environ, environment_vars.CLOUD_SDK_CONFIG_DIR, str(tmpdir)) - - credentials, project_id = google.auth.default() - - assert isinstance(credentials, service_account.Credentials) - assert project_id is not None - # The project ID should be the project ID specified in the the service - # account file, not the project in the config. - assert project_id is not project - - validate_refresh(credentials, http_request) +EXPECT_PROJECT_ID = os.environ.get('EXPECT_PROJECT_ID') -def test_cloud_sdk_credentials_authorized_user( - tmpdir, monkeypatch, authorized_user_file, http_request): - # Create the Cloud SDK configuration tree - project = 'system-test-project' - generate_cloud_sdk_config(tmpdir, authorized_user_file, project=project) - monkeypatch.setitem( - os.environ, environment_vars.CLOUD_SDK_CONFIG_DIR, str(tmpdir)) +def test_explicit_credentials(verify_refresh): credentials, project_id = google.auth.default() - assert isinstance(credentials, google.oauth2.credentials.Credentials) - assert project_id == project + if EXPECT_PROJECT_ID is not None: + assert project_id is not None + else: + assert project_id is None - validate_refresh(credentials, http_request) + verify_refresh(credentials) diff --git a/packages/google-auth/tox.ini b/packages/google-auth/tox.ini index 6864ddee74a8..427f2b7266a5 100644 --- a/packages/google-auth/tox.ini +++ b/packages/google-auth/tox.ini @@ -23,21 +23,27 @@ deps = [testenv:py35-system] basepython = python3.5 +changedir = {toxinidir}/system_tests commands = - py.test --ignore system_tests/app_engine/app {posargs:system_tests} + nox {posargs} deps = {[testenv]deps} + nox-automation passenv = SKIP_APP_ENGINE_SYSTEM_TEST + CLOUD_SDK_ROOT [testenv:py27-system] basepython = python2.7 +changedir = {toxinidir}/system_tests commands = - py.test --ignore system_tests/app_engine/app {posargs:system_tests} + nox {posargs} deps = {[testenv]deps} + nox-automation passenv = SKIP_APP_ENGINE_SYSTEM_TEST + CLOUD_SDK_ROOT [testenv:docgen] basepython = python3.5 From 5bd72fa9153f656f63cf7e07e16ca64724a19eda Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Fri, 28 Oct 2016 12:35:15 -0700 Subject: [PATCH 050/966] Move GAE environment setup to nox (#61) * Move GAE environment setup to nox * Re-organize GAE test files. --- .../app_engine/test_app_engine.py | 58 ------------------- .../app => app_engine_test_app}/.gitignore | 0 .../app => app_engine_test_app}/app.yaml | 0 .../appengine_config.py | 0 .../app => app_engine_test_app}/main.py | 0 .../requirements.txt | 0 packages/google-auth/system_tests/nox.py | 45 +++++++++++++- .../system_tests/test_app_engine.py | 22 +++++++ 8 files changed, 64 insertions(+), 61 deletions(-) delete mode 100644 packages/google-auth/system_tests/app_engine/test_app_engine.py rename packages/google-auth/system_tests/{app_engine/app => app_engine_test_app}/.gitignore (100%) rename packages/google-auth/system_tests/{app_engine/app => app_engine_test_app}/app.yaml (100%) rename packages/google-auth/system_tests/{app_engine/app => app_engine_test_app}/appengine_config.py (100%) rename packages/google-auth/system_tests/{app_engine/app => app_engine_test_app}/main.py (100%) rename packages/google-auth/system_tests/{app_engine/app => app_engine_test_app}/requirements.txt (100%) create mode 100644 packages/google-auth/system_tests/test_app_engine.py diff --git a/packages/google-auth/system_tests/app_engine/test_app_engine.py b/packages/google-auth/system_tests/app_engine/test_app_engine.py deleted file mode 100644 index a6ccb718fa6b..000000000000 --- a/packages/google-auth/system_tests/app_engine/test_app_engine.py +++ /dev/null @@ -1,58 +0,0 @@ -# Copyright 2016 Google Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -import subprocess - -from google.auth import _cloud_sdk -import pytest - -SKIP_TEST_ENV = 'SKIP_APP_ENGINE_SYSTEM_TEST' -HERE = os.path.dirname(__file__) -TEST_APP_DIR = os.path.join(HERE, 'app') -TEST_APP_SERVICE = 'google-auth-system-tests' - - -def vendor_app_dependencies(): - """Vendors in the test application's third-party dependencies.""" - subprocess.check_call( - ['pip', 'install', '--target', 'lib', '-r', 'requirements.txt']) - - -def deploy_app(): - """Deploys the test application using gcloud.""" - subprocess.check_call( - ['gcloud', 'app', 'deploy', '-q', 'app.yaml']) - - -@pytest.fixture -def app(monkeypatch): - monkeypatch.chdir(TEST_APP_DIR) - - vendor_app_dependencies() - deploy_app() - - application_id = _cloud_sdk.get_project_id() - application_url = 'https://{}-dot-{}.appspot.com'.format( - TEST_APP_SERVICE, application_id) - - yield application_url - - -@pytest.mark.skipif( - SKIP_TEST_ENV in os.environ, - reason='Explicitly skipping App Engine system tests.') -def test_live_application(app, http_request): - response = http_request(method='GET', url=app) - assert response.status == 200, response.data.decode('utf-8') diff --git a/packages/google-auth/system_tests/app_engine/app/.gitignore b/packages/google-auth/system_tests/app_engine_test_app/.gitignore similarity index 100% rename from packages/google-auth/system_tests/app_engine/app/.gitignore rename to packages/google-auth/system_tests/app_engine_test_app/.gitignore diff --git a/packages/google-auth/system_tests/app_engine/app/app.yaml b/packages/google-auth/system_tests/app_engine_test_app/app.yaml similarity index 100% rename from packages/google-auth/system_tests/app_engine/app/app.yaml rename to packages/google-auth/system_tests/app_engine_test_app/app.yaml diff --git a/packages/google-auth/system_tests/app_engine/app/appengine_config.py b/packages/google-auth/system_tests/app_engine_test_app/appengine_config.py similarity index 100% rename from packages/google-auth/system_tests/app_engine/app/appengine_config.py rename to packages/google-auth/system_tests/app_engine_test_app/appengine_config.py diff --git a/packages/google-auth/system_tests/app_engine/app/main.py b/packages/google-auth/system_tests/app_engine_test_app/main.py similarity index 100% rename from packages/google-auth/system_tests/app_engine/app/main.py rename to packages/google-auth/system_tests/app_engine_test_app/main.py diff --git a/packages/google-auth/system_tests/app_engine/app/requirements.txt b/packages/google-auth/system_tests/app_engine_test_app/requirements.txt similarity index 100% rename from packages/google-auth/system_tests/app_engine/app/requirements.txt rename to packages/google-auth/system_tests/app_engine_test_app/requirements.txt diff --git a/packages/google-auth/system_tests/nox.py b/packages/google-auth/system_tests/nox.py index 67f99004d99a..6429f7b667e8 100644 --- a/packages/google-auth/system_tests/nox.py +++ b/packages/google-auth/system_tests/nox.py @@ -23,12 +23,13 @@ """ import os +import subprocess from nox.command import which import py.path -HERE = os.path.dirname(__file__) +HERE = os.path.abspath(os.path.dirname(__file__)) DATA_DIR = os.path.join(HERE, 'data') SERVICE_ACCOUNT_FILE = os.path.join(DATA_DIR, 'service_account.json') AUTHORIZED_USER_FILE = os.path.join(DATA_DIR, 'authorized_user.json') @@ -36,6 +37,10 @@ EXPLICIT_PROJECT_ENV = 'GOOGLE_CLOUD_PROJECT' EXPECT_PROJECT_ENV = 'EXPECT_PROJECT_ID' +SKIP_GAE_TEST_ENV = 'SKIP_APP_ENGINE_SYSTEM_TEST' +GAE_APP_URL_TMPL = 'https://{}-dot-{}.appspot.com' +GAE_TEST_APP_SERVICE = 'google-auth-system-tests' + # The download location for the Cloud SDK CLOUD_SDK_DIST_FILENAME = 'google-cloud-sdk.tar.gz' CLOUD_SDK_DOWNLOAD_URL = ( @@ -81,7 +86,8 @@ def install_cloud_sdk(session): # This tells gcloud which Python interpreter to use (always use 2.7) session.env[CLOUD_SDK_PYTHON_ENV] = CLOUD_SDK_PYTHON - # If the glcoud already exists, we don't need to do anything else. + # If gcloud cli executable already exists, we don't need to do anything + # else. # Note that because of this we do not attempt to update the sdk - # if the CLOUD_SDK_ROOT is cached, it will need to be periodically cleared. if py.path.local(GCLOUD).exists(): @@ -208,4 +214,37 @@ def session_compute_engine(session): def session_app_engine(session): session.virtualenv = False - session.run('pytest', 'app_engine/test_app_engine.py') + + if SKIP_GAE_TEST_ENV in os.environ: + session.log('Skipping App Engine tests.') + return + + # Unlike the default tests above, the App Engine system test require a + # 'real' gcloud sdk installation that is configured to deploy to an + # app engine project. + # Grab the project ID from the cloud sdk. + project_id = subprocess.check_output([ + 'gcloud', 'config', 'list', 'project', '--format', + 'value(core.project)']).strip() + + if not project_id: + session.error( + 'The Cloud SDK must be installed and configured to deploy to App ' + 'Engine.') + + application_url = GAE_APP_URL_TMPL.format( + GAE_TEST_APP_SERVICE, project_id) + + # Vendor in the test application's dependencies + session.chdir(os.path.join(HERE, 'app_engine_test_app')) + session.run( + 'pip', 'install', '--target', 'lib', '-r', 'requirements.txt', + silent=True) + + # Deploy the application. + session.run('gcloud', 'app', 'deploy', '-q', 'app.yaml') + + # Run the tests + session.env['TEST_APP_URL'] = application_url + session.chdir(HERE) + session.run('pytest', 'test_app_engine.py') diff --git a/packages/google-auth/system_tests/test_app_engine.py b/packages/google-auth/system_tests/test_app_engine.py new file mode 100644 index 000000000000..834f9c87021c --- /dev/null +++ b/packages/google-auth/system_tests/test_app_engine.py @@ -0,0 +1,22 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os + +TEST_APP_URL = os.environ['TEST_APP_URL'] + + +def test_live_application(http_request): + response = http_request(method='GET', url=TEST_APP_URL) + assert response.status == 200, response.data.decode('utf-8') From 63f6761ecfd54c22e2cd1f21d08ce2c40ec669cb Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Fri, 28 Oct 2016 12:39:20 -0700 Subject: [PATCH 051/966] Add test for GCE default credentials (#62) --- packages/google-auth/system_tests/test_compute_engine.py | 9 +++++++++ packages/google-auth/system_tests/test_default.py | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/system_tests/test_compute_engine.py b/packages/google-auth/system_tests/test_compute_engine.py index ceba0f787636..2d6f42cc8413 100644 --- a/packages/google-auth/system_tests/test_compute_engine.py +++ b/packages/google-auth/system_tests/test_compute_engine.py @@ -14,6 +14,7 @@ import pytest +import google.auth from google.auth import _helpers from google.auth import compute_engine from google.auth.compute_engine import _metadata @@ -36,3 +37,11 @@ def test_refresh(http_request, token_info): info = token_info(credentials.token) info_scopes = _helpers.string_to_scopes(info['scope']) assert set(info_scopes) == set(credentials.scopes) + + +def test_default(verify_refresh): + credentials, project_id = google.auth.default() + + assert project_id is not None + assert isinstance(credentials, compute_engine.Credentials) + verify_refresh(credentials) diff --git a/packages/google-auth/system_tests/test_default.py b/packages/google-auth/system_tests/test_default.py index 0e0bcc6c0ebb..c64a8266ed61 100644 --- a/packages/google-auth/system_tests/test_default.py +++ b/packages/google-auth/system_tests/test_default.py @@ -19,7 +19,7 @@ EXPECT_PROJECT_ID = os.environ.get('EXPECT_PROJECT_ID') -def test_explicit_credentials(verify_refresh): +def test_application_default_credentials(verify_refresh): credentials, project_id = google.auth.default() if EXPECT_PROJECT_ID is not None: From 6b7f130e5bcef6f938966484dcfb253fb1be698d Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Fri, 28 Oct 2016 13:02:14 -0700 Subject: [PATCH 052/966] Release v0.1.0 (#64) --- packages/google-auth/CHANGELOG.rst | 17 +++++++++++++++++ packages/google-auth/setup.py | 4 ++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/CHANGELOG.rst b/packages/google-auth/CHANGELOG.rst index 6299f68bf03e..f34fe0c64693 100644 --- a/packages/google-auth/CHANGELOG.rst +++ b/packages/google-auth/CHANGELOG.rst @@ -1,6 +1,23 @@ Changelog ========= +v0.1.0 +------ + +First release with core functionality available. This version is ready for +initial usage and testing. + +- Added ``google.auth.credentials``, public interfaces for Credential types. (#8) +- Added ``google.oauth2.credentials``, credentials that use OAuth 2.0 access and refresh tokens (#24) +- Added ``google.oauth2.service_account``, credentials that use Service Account private keys to obtain OAuth 2.0 access tokens. (#25) +- Added ``google.auth.compute_engine``, credentials that use the Compute Engine metadata service to obtain OAuth 2.0 access tokens. (#22) +- Added ``google.auth.jwt.Credentials``, credentials that use a JWT as a bearer token. +- Added ``google.auth.app_engine``, credentials that use the Google App Engine App Identity service to obtain OAuth 2.0 access tokens. (#46) +- Added ``google.auth.default()``, an implementation of Google Application Default Credentials that supports automatic Project ID detection. (#32) +- Added system tests for all credential types. (#51, #54, #56, #58, #59, #60, #61, #62) +- Added ``google.auth.transports.urllib3.AuthorizedHttp``, an HTTP client that includes authentication provided by credentials. (#19) +- Documentation style and formatting updates. + v0.0.1 ------ diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 5ee243fed962..d9d903ba3129 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -29,7 +29,7 @@ setup( name='google-auth', - version='0.0.1', + version='0.1.0', author='Google Cloud Platform', author_email='jonwayne+google-auth@google.com', description='Google Authentication Library', @@ -46,7 +46,7 @@ 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', - 'Development Status :: 3 - Alpha', + 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', 'License :: OSI Approved :: Apache Software License', 'Operating System :: POSIX', From 4213101b7eab5b6a852901c61c6d287d73eadf6c Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Mon, 31 Oct 2016 10:52:57 -0700 Subject: [PATCH 053/966] Add with_scopes_if_required helper (#65) --- .../google-auth/google/auth/credentials.py | 24 ++++++++++++++ .../google-auth/tests/test_credentials.py | 31 +++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/packages/google-auth/google/auth/credentials.py b/packages/google-auth/google/auth/credentials.py index ef28bd7920ee..59d4ffea99e8 100644 --- a/packages/google-auth/google/auth/credentials.py +++ b/packages/google-auth/google/auth/credentials.py @@ -186,6 +186,30 @@ def has_scopes(self, scopes): return set(scopes).issubset(set(self._scopes or [])) +def with_scopes_if_required(credentials, scopes): + """Creates a copy of the credentials with scopes if scoping is required. + + This helper function is useful when you do not know (or care to know) the + specific type of credentials you are using (such as when you use + :func:`google.auth.default`). This function will call + :meth:`Scoped.with_scopes` if the credentials are scoped credentials and if + the credentials require scoping. Otherwise, it will return the credentials + as-is. + + Args: + credentials (Credentials): The credentials to scope if necessary. + scopes (Sequence[str]): The list of scopes to use. + + Returns: + Credentials: Either a new set of scoped credentials, or the passed in + credentials instance if no scoping was required. + """ + if isinstance(credentials, Scoped) and credentials.requires_scopes: + return credentials.with_scopes(scopes) + else: + return credentials + + @six.add_metaclass(abc.ABCMeta) class Signing(object): """Interface for credentials that can cryptographically sign messages.""" diff --git a/packages/google-auth/tests/test_credentials.py b/packages/google-auth/tests/test_credentials.py index d78c554d5402..814369e43903 100644 --- a/packages/google-auth/tests/test_credentials.py +++ b/packages/google-auth/tests/test_credentials.py @@ -92,3 +92,34 @@ def test_scoped_credentials_scopes(): def test_scoped_credentials_requires_scopes(): credentials = ScopedCredentialsImpl() assert not credentials.requires_scopes + + +class RequiresScopedCredentialsImpl(credentials.Scoped, CredentialsImpl): + def __init__(self, scopes=None): + super(RequiresScopedCredentialsImpl, self).__init__() + self._scopes = scopes + + @property + def requires_scopes(self): + return not self.scopes + + def with_scopes(self, scopes): + return RequiresScopedCredentialsImpl(scopes=scopes) + + +def test_create_scoped_if_required_scoped(): + unscoped_credentials = RequiresScopedCredentialsImpl() + scoped_credentials = credentials.with_scopes_if_required( + unscoped_credentials, ['one', 'two']) + + assert scoped_credentials is not unscoped_credentials + assert not scoped_credentials.requires_scopes + assert scoped_credentials.has_scopes(['one', 'two']) + + +def test_create_scoped_if_required_not_scopes(): + unscoped_credentials = CredentialsImpl() + scoped_credentials = credentials.with_scopes_if_required( + unscoped_credentials, ['one', 'two']) + + assert scoped_credentials is unscoped_credentials From 7afda438984b66e168ff0c498104d178856947bc Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Mon, 31 Oct 2016 12:45:02 -0700 Subject: [PATCH 054/966] Regenerate and fix docs --- .../google-auth/docs/reference/google.auth.app_engine.rst | 7 +++++++ packages/google-auth/docs/reference/google.auth.rst | 1 + packages/google-auth/google/oauth2/service_account.py | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 packages/google-auth/docs/reference/google.auth.app_engine.rst diff --git a/packages/google-auth/docs/reference/google.auth.app_engine.rst b/packages/google-auth/docs/reference/google.auth.app_engine.rst new file mode 100644 index 000000000000..4525c089e35a --- /dev/null +++ b/packages/google-auth/docs/reference/google.auth.app_engine.rst @@ -0,0 +1,7 @@ +google.auth.app_engine module +============================= + +.. automodule:: google.auth.app_engine + :members: + :inherited-members: + :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.auth.rst b/packages/google-auth/docs/reference/google.auth.rst index 6769e1c22a0a..2d50672415da 100644 --- a/packages/google-auth/docs/reference/google.auth.rst +++ b/packages/google-auth/docs/reference/google.auth.rst @@ -19,6 +19,7 @@ Submodules .. toctree:: + google.auth.app_engine google.auth.credentials google.auth.crypt google.auth.environment_vars diff --git a/packages/google-auth/google/oauth2/service_account.py b/packages/google-auth/google/oauth2/service_account.py index df6f1b2aa673..615930102846 100644 --- a/packages/google-auth/google/oauth2/service_account.py +++ b/packages/google-auth/google/oauth2/service_account.py @@ -205,7 +205,7 @@ def from_service_account_file(cls, filename, **kwargs): return cls._from_signer_and_info(signer, info, **kwargs) def to_jwt_credentials(self): - """Creates a :cls:`google.auth.jwt.Credentials` instance from this + """Creates a :class:`google.auth.jwt.Credentials` instance from this instance. The new instance will use the same private key as this instance and From e3d6069164239f357dfc14aaf9920dfc98fff54b Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Mon, 31 Oct 2016 14:59:52 -0700 Subject: [PATCH 055/966] Add requests transport (#66) * Add requests transport * Parametrize http_request so that both urllib3 and requests are exercised in system tests. --- packages/google-auth/docs/conf.py | 3 +- .../google.auth.transport.requests.rst | 7 + .../docs/reference/google.auth.transport.rst | 1 + .../google/auth/transport/requests.py | 194 ++++++++++++++++++ packages/google-auth/system_tests/conftest.py | 15 +- .../tests/transport/test_requests.py | 118 +++++++++++ packages/google-auth/tox.ini | 1 + 7 files changed, 334 insertions(+), 5 deletions(-) create mode 100644 packages/google-auth/docs/reference/google.auth.transport.requests.rst create mode 100644 packages/google-auth/google/auth/transport/requests.py create mode 100644 packages/google-auth/tests/transport/test_requests.py diff --git a/packages/google-auth/docs/conf.py b/packages/google-auth/docs/conf.py index bd64578d596e..cfd452f54a73 100644 --- a/packages/google-auth/docs/conf.py +++ b/packages/google-auth/docs/conf.py @@ -366,7 +366,8 @@ # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = { 'python': ('https://docs.python.org/3.5', None), - 'urllib3': ('https://urllib3.readthedocs.io/en/latest', None), + 'urllib3': ('https://urllib3.readthedocs.io/en/stable', None), + 'requests': ('http://docs.python-requests.org/en/stable', None), } # Autodoc config diff --git a/packages/google-auth/docs/reference/google.auth.transport.requests.rst b/packages/google-auth/docs/reference/google.auth.transport.requests.rst new file mode 100644 index 000000000000..f31830c56ba5 --- /dev/null +++ b/packages/google-auth/docs/reference/google.auth.transport.requests.rst @@ -0,0 +1,7 @@ +google.auth.transport.requests module +===================================== + +.. automodule:: google.auth.transport.requests + :members: + :inherited-members: + :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.auth.transport.rst b/packages/google-auth/docs/reference/google.auth.transport.rst index b9b444c2607c..7767daf35138 100644 --- a/packages/google-auth/docs/reference/google.auth.transport.rst +++ b/packages/google-auth/docs/reference/google.auth.transport.rst @@ -11,5 +11,6 @@ Submodules .. toctree:: + google.auth.transport.requests google.auth.transport.urllib3 diff --git a/packages/google-auth/google/auth/transport/requests.py b/packages/google-auth/google/auth/transport/requests.py new file mode 100644 index 000000000000..e2110902471a --- /dev/null +++ b/packages/google-auth/google/auth/transport/requests.py @@ -0,0 +1,194 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Transport adapter for Requests.""" + +from __future__ import absolute_import + +import logging + + +import requests +import requests.exceptions + +from google.auth import exceptions +from google.auth import transport + +_LOGGER = logging.getLogger(__name__) + + +class _Response(transport.Response): + """Requests transport response adapter. + + Args: + response (requests.Response): The raw Requests response. + """ + def __init__(self, response): + self._response = response + + @property + def status(self): + return self._response.status_code + + @property + def headers(self): + return self._response.headers + + @property + def data(self): + return self._response.content + + +class Request(transport.Request): + """Requests request adapter. + + This class is used internally for making requests using various transports + in a consistent way. If you use :class:`AuthorizedSession` you do not need + to construct or use this class directly. + + This class can be useful if you want to manually refresh a + :class:`~google.auth.credentials.Credentials` instance:: + + import google.auth.transport.requests + import requests + + request = google.auth.transport.requests.Request() + + credentials.refresh(request) + + Args: + session (requests.Session): An instance :class:`requests.Session` used + to make HTTP requests. If not specified, a session will be created. + + .. automethod:: __call__ + """ + def __init__(self, session=None): + if not session: + session = requests.Session() + + self.session = session + + def __call__(self, url, method='GET', body=None, headers=None, + timeout=None, **kwargs): + """Make an HTTP request using requests. + + Args: + url (str): The URI to be requested. + method (str): The HTTP method to use for the request. Defaults + to 'GET'. + body (bytes): The payload / body in HTTP request. + headers (Mapping[str, str]): Request headers. + timeout (Optional[int]): The number of seconds to wait for a + response from the server. If not specified or if None, the + requests default timeout will be used. + kwargs: Additional arguments passed through to the underlying + requests :meth:`~requests.Session.request` method. + + Returns: + google.auth.transport.Response: The HTTP response. + + Raises: + google.auth.exceptions.TransportError: If any exception occurred. + """ + try: + _LOGGER.debug('Making request: %s %s', method, url) + response = self.session.request( + method, url, data=body, headers=headers, timeout=timeout, + **kwargs) + return _Response(response) + except requests.exceptions.RequestException as exc: + raise exceptions.TransportError(exc) + + +class AuthorizedSession(requests.Session): + """A Requests Session class with credentials. + + This class is used to perform requests to API endpoints that require + authorization:: + + from google.auth.transport.requests import AuthorizedSession + + authed_session = AuthorizedSession(credentials) + + response = authed_session.request( + 'GET', 'https://www.googleapis.com/storage/v1/b') + + The underlying :meth:`request` implementation handles adding the + credentials' headers to the request and refreshing credentials as needed. + + Args: + credentials (google.auth.credentials.Credentials): The credentials to + add to the request. + refresh_status_codes (Sequence[int]): Which HTTP status codes indicate + that credentials should be refreshed and the request should be + retried. + max_refresh_attempts (int): The maximum number of times to attempt to + refresh the credentials and retry the request. + kwargs: Additional arguments passed to the :class:`requests.Session` + constructor. + """ + def __init__(self, credentials, + refresh_status_codes=transport.DEFAULT_REFRESH_STATUS_CODES, + max_refresh_attempts=transport.DEFAULT_MAX_REFRESH_ATTEMPTS, + **kwargs): + super(AuthorizedSession, self).__init__(**kwargs) + self.credentials = credentials + self._refresh_status_codes = refresh_status_codes + self._max_refresh_attempts = max_refresh_attempts + # Request instance used by internal methods (for example, + # credentials.refresh). + # Do not pass `self` as the session here, as it can lead to infinite + # recursion. + self._auth_request = Request() + + def request(self, method, url, data=None, headers=None, **kwargs): + """Implementation of Requests' request.""" + + # Use a kwarg for this instead of an attribute to maintain + # thread-safety. + _credential_refresh_attempt = kwargs.pop( + '_credential_refresh_attempt', 0) + + # Make a copy of the headers. They will be modified by the credentials + # and we want to pass the original headers if we recurse. + request_headers = headers.copy() if headers is not None else {} + + self.credentials.before_request( + self._auth_request, method, url, request_headers) + + response = super(AuthorizedSession, self).request( + method, url, data=data, headers=request_headers, **kwargs) + + # If the response indicated that the credentials needed to be + # refreshed, then refresh the credentials and re-attempt the + # request. + # A stored token may expire between the time it is retrieved and + # the time the request is made, so we may need to try twice. + if (response.status_code in self._refresh_status_codes + and _credential_refresh_attempt < self._max_refresh_attempts): + + _LOGGER.info( + 'Refreshing credentials due to a %s response. Attempt %s/%s.', + response.status_code, _credential_refresh_attempt + 1, + self._max_refresh_attempts) + + self.credentials.refresh(self._auth_request) + + # Recurse. Pass in the original headers, not our modified set. + return self.request( + method, url, data=data, headers=headers, + _credential_refresh_attempt=_credential_refresh_attempt + 1, + **kwargs) + + return response diff --git a/packages/google-auth/system_tests/conftest.py b/packages/google-auth/system_tests/conftest.py index afa78545ec47..d0f7cc02317b 100644 --- a/packages/google-auth/system_tests/conftest.py +++ b/packages/google-auth/system_tests/conftest.py @@ -16,8 +16,10 @@ import os from google.auth import _helpers +import google.auth.transport.requests import google.auth.transport.urllib3 import pytest +import requests import urllib3 @@ -25,7 +27,9 @@ DATA_DIR = os.path.join(HERE, 'data') SERVICE_ACCOUNT_FILE = os.path.join(DATA_DIR, 'service_account.json') AUTHORIZED_USER_FILE = os.path.join(DATA_DIR, 'authorized_user.json') -HTTP = urllib3.PoolManager(retries=False) +URLLIB3_HTTP = urllib3.PoolManager(retries=False) +REQUESTS_SESSION = requests.Session() +REQUESTS_SESSION.verify = False TOKEN_INFO_URL = 'https://www.googleapis.com/oauth2/v3/tokeninfo' @@ -41,10 +45,13 @@ def authorized_user_file(): yield AUTHORIZED_USER_FILE -@pytest.fixture -def http_request(): +@pytest.fixture(params=['urllib3', 'requests']) +def http_request(request): """A transport.request object.""" - yield google.auth.transport.urllib3.Request(HTTP) + if request.param == 'urllib3': + yield google.auth.transport.urllib3.Request(URLLIB3_HTTP) + elif request.param == 'requests': + yield google.auth.transport.requests.Request(REQUESTS_SESSION) @pytest.fixture diff --git a/packages/google-auth/tests/transport/test_requests.py b/packages/google-auth/tests/transport/test_requests.py new file mode 100644 index 000000000000..4be466870e85 --- /dev/null +++ b/packages/google-auth/tests/transport/test_requests.py @@ -0,0 +1,118 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import mock +import requests +import requests.adapters +from six.moves import http_client + +import google.auth.transport.requests +from tests.transport import compliance + + +class TestRequestResponse(compliance.RequestResponseTests): + def make_request(self): + return google.auth.transport.requests.Request() + + def test_timeout(self): + http = mock.Mock() + request = google.auth.transport.requests.Request(http) + request(url='http://example.com', method='GET', timeout=5) + + assert http.request.call_args[1]['timeout'] == 5 + + +class MockCredentials(object): + def __init__(self, token='token'): + self.token = token + + def apply(self, headers): + headers['authorization'] = self.token + + def before_request(self, request, method, url, headers): + self.apply(headers) + + def refresh(self, request): + self.token += '1' + + +class MockAdapter(requests.adapters.BaseAdapter): + def __init__(self, responses, headers=None): + self.responses = responses + self.requests = [] + self.headers = headers or {} + + def send(self, request, **kwargs): + self.requests.append(request) + return self.responses.pop(0) + + +def make_response(status=http_client.OK, data=None): + response = requests.Response() + response.status_code = status + response._content = data + return response + + +class TestAuthorizedHttp(object): + TEST_URL = 'http://example.com/' + + def test_constructor(self): + authed_session = google.auth.transport.requests.AuthorizedSession( + mock.sentinel.credentials) + + assert authed_session.credentials == mock.sentinel.credentials + + def test_request_no_refresh(self): + mock_credentials = mock.Mock(wraps=MockCredentials()) + mock_response = make_response() + mock_adapter = MockAdapter([mock_response]) + + authed_session = google.auth.transport.requests.AuthorizedSession( + mock_credentials) + authed_session.mount(self.TEST_URL, mock_adapter) + + response = authed_session.request('GET', self.TEST_URL) + + assert response == mock_response + assert mock_credentials.before_request.called + assert not mock_credentials.refresh.called + assert len(mock_adapter.requests) == 1 + assert mock_adapter.requests[0].url == self.TEST_URL + assert mock_adapter.requests[0].headers['authorization'] == 'token' + + def test_request_refresh(self): + mock_credentials = mock.Mock(wraps=MockCredentials()) + mock_final_response = make_response(status=http_client.OK) + # First request will 401, second request will succeed. + mock_adapter = MockAdapter([ + make_response(status=http_client.UNAUTHORIZED), + mock_final_response]) + + authed_session = google.auth.transport.requests.AuthorizedSession( + mock_credentials) + authed_session.mount(self.TEST_URL, mock_adapter) + + response = authed_session.request('GET', self.TEST_URL) + + assert response == mock_final_response + assert mock_credentials.before_request.call_count == 2 + assert mock_credentials.refresh.called + assert len(mock_adapter.requests) == 2 + + assert mock_adapter.requests[0].url == self.TEST_URL + assert mock_adapter.requests[0].headers['authorization'] == 'token' + + assert mock_adapter.requests[1].url == self.TEST_URL + assert mock_adapter.requests[1].headers['authorization'] == 'token1' diff --git a/packages/google-auth/tox.ini b/packages/google-auth/tox.ini index 427f2b7266a5..1ef803cf99bc 100644 --- a/packages/google-auth/tox.ini +++ b/packages/google-auth/tox.ini @@ -10,6 +10,7 @@ deps = pytest-localserver urllib3 certifi + requests commands = py.test --cov=google.auth --cov=google.oauth2 --cov=tests {posargs:tests} From f95da8bfdfb25a1d1f10494268e629d5bf6844b4 Mon Sep 17 00:00:00 2001 From: Danny Hermes Date: Tue, 1 Nov 2016 12:43:01 -0700 Subject: [PATCH 056/966] Use a python script to define Pylint config rather than an ini file (#69) --- .../google-auth/httplib2_transport/pylintrc | 165 ----------- .../httplib2_transport/pylintrc.tests | 166 ------------ .../httplib2_transport/scripts/.gitignore | 3 + .../httplib2_transport/scripts/run_pylint.py | 256 ++++++++++++++++++ .../google-auth/httplib2_transport/tox.ini | 3 +- packages/google-auth/pylintrc | 165 ----------- packages/google-auth/pylintrc.tests | 166 ------------ packages/google-auth/scripts/.gitignore | 3 + packages/google-auth/scripts/run_pylint.py | 251 +++++++++++++++++ .../tests/oauth2/test_credentials.py | 3 +- packages/google-auth/tox.ini | 3 +- 11 files changed, 517 insertions(+), 667 deletions(-) delete mode 100644 packages/google-auth/httplib2_transport/pylintrc delete mode 100644 packages/google-auth/httplib2_transport/pylintrc.tests create mode 100644 packages/google-auth/httplib2_transport/scripts/.gitignore create mode 100644 packages/google-auth/httplib2_transport/scripts/run_pylint.py delete mode 100644 packages/google-auth/pylintrc delete mode 100644 packages/google-auth/pylintrc.tests create mode 100644 packages/google-auth/scripts/.gitignore create mode 100644 packages/google-auth/scripts/run_pylint.py diff --git a/packages/google-auth/httplib2_transport/pylintrc b/packages/google-auth/httplib2_transport/pylintrc deleted file mode 100644 index 1f01cfa1d04f..000000000000 --- a/packages/google-auth/httplib2_transport/pylintrc +++ /dev/null @@ -1,165 +0,0 @@ -[MASTER] -# Add files or directories to the blacklist. They should be base names, not -# paths. -ignore=CVS,.git,.cache,.tox,.nox - -# List of plugins (as comma separated values of python modules names) to load, -# usually to register additional checkers. -# DEFAULT: load-plugins= -# RATIONALE: We want to make sure our docstrings match the objects -# they document. -load-plugins=pylint.extensions.check_docs - -[MESSAGES CONTROL] -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once).You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use"--disable=all --enable=classes -# --disable=W" -# -# RATIONALE: -# - maybe-no-member: bi-modal functions confuse pylint type inference. -# - no-member: indirections in protobuf-generated code -# - protected-access: helpers use '_foo' of classes from generated code. -# - similarities: 'Bucket' and 'Blob' define 'metageneration' and 'owner' with -# identical implementation but different docstrings. -# - star-args: standard Python idioms for varargs: -# ancestor = Query().filter(*order_props) -# - redefined-variable-type: This error is overzealous and complains at e.g. -# if some_prop: -# return int(value) -# else: -# return float(value) -# - import-error: imports are checked via tests. -# - wrong-import-position: This error is overzealous. It assumes imports are -# completed whenever something non-trivial is -# defined, e.g. -# try: -# from foo import Bar -# except ImportError: -# class Bar(object): -# """Hi everyone""" -# and thus causes subsequent imports to be -# diagnosed as out-of-order. -# - no-name-in-module: Error gives a lot of false positives for names which -# are defined dynamically. Also, any truly missing names -# will be detected by our 100% code coverage. -# - locally-disabled: Allow us to make exceptions in edge cases, notably where -# pylint doesn't recognize inherited properties and methods -# and gives unused-argument errors. -# TEMPORARILY DISABLE AND SHOULD BE REMOVED: -# - fixme: disabled until 1.0 -# -disable = - import-star-module-level, - old-octal-literal, - oct-method, - print-statement, - unpacking-in-except, - parameter-unpacking, - backtick, - old-raise-syntax, - old-ne-operator, - long-suffix, - dict-view-method, - dict-iter-method, - metaclass-assignment, - next-method-called, - raising-string, - indexing-exception, - raw_input-builtin, - long-builtin, - file-builtin, - execfile-builtin, - coerce-builtin, - cmp-builtin, - buffer-builtin, - basestring-builtin, - apply-builtin, - filter-builtin-not-iterating, - using-cmp-argument, - useless-suppression, - range-builtin-not-iterating, - suppressed-message, - no-absolute-import, - old-division, - cmp-method, - reload-builtin, - zip-builtin-not-iterating, - intern-builtin, - unichr-builtin, - reduce-builtin, - standarderror-builtin, - unicode-builtin, - xrange-builtin, - coerce-method, - delslice-method, - getslice-method, - setslice-method, - input-builtin, - round-builtin, - hex-method, - nonzero-method, - map-builtin-not-iterating, - maybe-no-member, - no-member, - protected-access, - similarities, - star-args, - redefined-variable-type, - import-error, - wrong-import-position, - no-name-in-module, - locally-disabled, - locally-enabled, - fixme - - -[REPORTS] -# Tells whether to display a full report or only the messages -# RATIONALE: noisy -reports=no - -[BASIC] -# Regular expression matching correct method names -# DEFAULT: method-rgx=[a-z_][a-z0-9_]{2,30}$ -# RATIONALE: Some methods have longer names to be more descriptive or precise, -# especially those that implemented wordy RFCs. -method-rgx=[a-z_][a-z0-9_]{2,40}$ - -# Regular expression matching correct function names -# DEFAULT function-rgx=[a-z_][a-z0-9_]{2,30}$ -# RATIONALE: Some methods have longer names to be more descriptive or precise, -# especially those that implemented wordy RFCs. -function-rgx=[a-z_][a-z0-9_]{2,40}$ - -[TYPECHECK] -# List of module names for which member attributes should not be checked -# (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis. It -# supports qualified module names, as well as Unix pattern matching. -# DEFAULT: ignored-modules= -# RATIONALE: six aliases stuff for compatibility. -# google.protobuf fixes up namespace package "late". -ignored-modules = six, google.protobuf - - -[DESIGN] -# Minimum number of public methods for a class (see R0903). -# DEFAULT: min-public-methods=2 -# RATIONALE: context mgrs may have *no* public methods -min-public-methods=0 - -# Maximum number of arguments for function / method -# DEFAULT: max-args=5 -# RATIONALE: Many credentials classes take a lot of parameters. -max-args = 10 - -# Maximum number of attributes for a class (see R0902). -# DEFAULT: max-attributes=7 -# RATIONALE: Many credentials need to track lots of properties. -max-attributes=15 diff --git a/packages/google-auth/httplib2_transport/pylintrc.tests b/packages/google-auth/httplib2_transport/pylintrc.tests deleted file mode 100644 index 09772c5f50f1..000000000000 --- a/packages/google-auth/httplib2_transport/pylintrc.tests +++ /dev/null @@ -1,166 +0,0 @@ -[MASTER] -# Add files or directories to the blacklist. They should be base names, not -# paths. -ignore=CVS,.git,.cache,.tox,.nox - -[MESSAGES CONTROL] -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once).You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use"--disable=all --enable=classes -# --disable=W" -# -# RATIONALE: -# - maybe-no-member: bi-modal functions confuse pylint type inference. -# - no-member: indirections in protobuf-generated code -# - protected-access: helpers use '_foo' of classes from generated code. -# - similarities: 'Bucket' and 'Blob' define 'metageneration' and 'owner' with -# identical implementation but different docstrings. -# - star-args: standard Python idioms for varargs: -# ancestor = Query().filter(*order_props) -# - redefined-variable-type: This error is overzealous and complains at e.g. -# if some_prop: -# return int(value) -# else: -# return float(value) -# - import-error: imports are checked via tests. -# - wrong-import-position: This error is overzealous. It assumes imports are -# completed whenever something non-trivial is -# defined, e.g. -# try: -# from foo import Bar -# except ImportError: -# class Bar(object): -# """Hi everyone""" -# and thus causes subsequent imports to be -# diagnosed as out-of-order. -# - no-name-in-module: Error gives a lot of false positives for names which -# are defined dynamically. Also, any truly missing names -# will be detected by our 100% code coverage. -# - locally-disabled: Allow us to make exceptions in edge cases, notably where -# pylint doesn't recognize inherited properties and methods -# and gives unused-argument errors. -disable = - import-star-module-level, - old-octal-literal, - oct-method, - print-statement, - unpacking-in-except, - parameter-unpacking, - backtick, - old-raise-syntax, - old-ne-operator, - long-suffix, - dict-view-method, - dict-iter-method, - metaclass-assignment, - next-method-called, - raising-string, - indexing-exception, - raw_input-builtin, - long-builtin, - file-builtin, - execfile-builtin, - coerce-builtin, - cmp-builtin, - buffer-builtin, - basestring-builtin, - apply-builtin, - filter-builtin-not-iterating, - using-cmp-argument, - useless-suppression, - range-builtin-not-iterating, - suppressed-message, - no-absolute-import, - old-division, - cmp-method, - reload-builtin, - zip-builtin-not-iterating, - intern-builtin, - unichr-builtin, - reduce-builtin, - standarderror-builtin, - unicode-builtin, - xrange-builtin, - coerce-method, - delslice-method, - getslice-method, - setslice-method, - input-builtin, - round-builtin, - hex-method, - nonzero-method, - map-builtin-not-iterating, - maybe-no-member, - no-member, - protected-access, - similarities, - star-args, - redefined-variable-type, - import-error, - wrong-import-position, - no-name-in-module, - locally-disabled, - locally-enabled, - missing-docstring, - redefined-outer-name, - no-self-use, - too-many-locals, - too-many-public-methods, - abstract-method, - method-hidden, - unused-argument - - -[REPORTS] -# Tells whether to display a full report or only the messages -# RATIONALE: noisy -reports=no - -[BASIC] -# Good variable names which should always be accepted, separated by a comma -# DEFAULT: good-names=i,j,k,ex,Run,_ -# RATIONALE: 'fh' is a well-known file handle variable name. -good-names = i, j, k, ex, Run, _, - fh - -# Regular expression matching correct method names -# DEFAULT: method-rgx=[a-z_][a-z0-9_]{2,30}$ -# RATIONALE: Some methods have longer names to be precise against wordy RFCs. -method-rgx=[a-z_][a-z0-9_]{2,80}$ - -# Regular expression matching correct function names -# DEFAULT function-rgx=[a-z_][a-z0-9_]{2,30}$ -# RATIONALE: Some test methods have long descriptive names. -function-rgx=[a-z_][a-z0-9_]{2,80}$ - -[TYPECHECK] -# List of module names for which member attributes should not be checked -# (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis. It -# supports qualified module names, as well as Unix pattern matching. -# DEFAULT: ignored-modules= -# RATIONALE: six aliases stuff for compatibility. -# google.protobuf fixes up namespace package "late". -ignored-modules = six, google.protobuf - - -[DESIGN] -# Minimum number of public methods for a class (see R0903). -# DEFAULT: min-public-methods=2 -# RATIONALE: context mgrs may have *no* public methods -min-public-methods=0 - -# Maximum number of arguments for function / method -# DEFAULT: max-args=5 -# RATIONALE: Many credentials classes take a lot of parameters. -max-args = 10 - -# Maximum number of attributes for a class (see R0902). -# DEFAULT: max-attributes=7 -# RATIONALE: Many credentials need to track lots of properties. -max-attributes=15 diff --git a/packages/google-auth/httplib2_transport/scripts/.gitignore b/packages/google-auth/httplib2_transport/scripts/.gitignore new file mode 100644 index 000000000000..3596d32ac39c --- /dev/null +++ b/packages/google-auth/httplib2_transport/scripts/.gitignore @@ -0,0 +1,3 @@ +# Generated files +pylintrc +pylintrc.test diff --git a/packages/google-auth/httplib2_transport/scripts/run_pylint.py b/packages/google-auth/httplib2_transport/scripts/run_pylint.py new file mode 100644 index 000000000000..643957aa531e --- /dev/null +++ b/packages/google-auth/httplib2_transport/scripts/run_pylint.py @@ -0,0 +1,256 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""This script runs Pylint on the specified source. + +Before running Pylint, it generates a Pylint configuration on +the fly based on programmatic defaults. + +.. note:: + + This file **really** breaks DRY, but is provided here as + **mostly** a copy of the true source file: + ``httplib2_transport/../scripts/run_pylint.py``. + This is done so that the ``httplib2_transport`` sub-package + can be self-contained. +""" + +from __future__ import print_function + +import collections +import copy +import io +import os +import subprocess +import sys + +import six + + +_SCRIPTS_DIR = os.path.abspath(os.path.dirname(__file__)) +PRODUCTION_RC = os.path.join(_SCRIPTS_DIR, 'pylintrc') +TEST_RC = os.path.join(_SCRIPTS_DIR, 'pylintrc.test') + +_PRODUCTION_RC_ADDITIONS = { + 'MESSAGES CONTROL': { + 'disable': [ + 'I', + 'import-error', + ], + }, +} +_PRODUCTION_RC_REPLACEMENTS = { + 'MASTER': { + 'ignore': ['CVS', '.git', '.cache', '.tox', '.nox'], + 'load-plugins': 'pylint.extensions.check_docs', + }, + 'REPORTS': { + 'reports': 'no', + }, + 'BASIC': { + 'method-rgx': '[a-z_][a-z0-9_]{2,40}$', + 'function-rgx': '[a-z_][a-z0-9_]{2,40}$', + }, + 'TYPECHECK': { + 'ignored-modules': ['six', 'google.protobuf'], + }, + 'DESIGN': { + 'min-public-methods': '0', + 'max-args': '10', + 'max-attributes': '15', + }, +} +_TEST_RC_ADDITIONS = copy.deepcopy(_PRODUCTION_RC_ADDITIONS) +_TEST_RC_ADDITIONS['MESSAGES CONTROL']['disable'].extend([ + 'missing-docstring', + 'no-member', + 'no-self-use', + 'protected-access', + 'unused-argument', +]) +_TEST_RC_REPLACEMENTS = copy.deepcopy(_PRODUCTION_RC_REPLACEMENTS) +_TEST_RC_REPLACEMENTS.setdefault('BASIC', {}) +_TEST_RC_REPLACEMENTS['BASIC'].update({ + 'good-names': ['i', 'j', 'k', 'ex', 'Run', '_', 'fh'], + 'method-rgx': '[a-z_][a-z0-9_]{2,80}$', + 'function-rgx': '[a-z_][a-z0-9_]{2,80}$', +}) +IGNORED_FILES = () + +_ERROR_TEMPLATE = 'Pylint failed on {} with status {:d}.' +_LINT_FILESET_MSG = ( + 'Keyword arguments rc_filename and description are both ' + 'required. No other keyword arguments are allowed.') + + +def get_default_config(): + """Get the default Pylint configuration. + + .. note:: + + The output of this function varies based on the current version of + Pylint installed. + + Returns: + str: The default Pylint configuration. + """ + # Swallow STDERR if it says + # "No config file found, using default configuration" + result = subprocess.check_output(['pylint', '--generate-rcfile'], + stderr=subprocess.PIPE) + # On Python 3, this returns bytes (from STDOUT), so we + # convert to a string. + return result.decode('utf-8') + + +def read_config(contents): + """Reads pylintrc config into native ConfigParser object. + + Args: + contents (str): The contents of the file containing the INI config. + + Returns + ConfigParser.ConfigParser: The parsed configuration. + """ + file_obj = io.StringIO(contents) + config = six.moves.configparser.ConfigParser() + config.readfp(file_obj) + return config + + +def _transform_opt(opt_val): + """Transform a config option value to a string. + + If already a string, do nothing. If an iterable, then + combine into a string by joining on ",". + + Args: + opt_val (Union[str, list]): A config option's value. + + Returns: + str: The option value converted to a string. + """ + if isinstance(opt_val, (list, tuple)): + return ','.join(opt_val) + else: + return opt_val + + +def lint_fileset(*paths, **kwargs): + """Lints a group of files using a given rcfile. + + Keyword arguments are + + * ``rc_filename`` (``str``): The name of the Pylint config RC file. + * ``description`` (``str``): A description of the files and configuration + currently being run. + + Args: + paths (tuple): Directories or filenames to run Pylint in/on. + kwargs: The keyword arguments. The only keyword arguments + are ``rc_filename`` and ``description`` and both + are required. + + Raises: + KeyError: If the wrong keyword arguments are used. + """ + try: + rc_filename = kwargs['rc_filename'] + description = kwargs['description'] + if len(kwargs) != 2: + raise KeyError + except KeyError: + raise KeyError(_LINT_FILESET_MSG) + + pylint_shell_command = ['pylint', '--rcfile', rc_filename] + pylint_shell_command.extend(paths) + status_code = subprocess.call(pylint_shell_command) + if status_code != 0: + error_message = _ERROR_TEMPLATE.format(description, status_code) + print(error_message, file=sys.stderr) + sys.exit(status_code) + + +def make_rc(base_cfg, target_filename, + additions=None, replacements=None): + """Combines a base rc and additions into single file. + + Args: + base_cfg (ConfigParser.ConfigParser): The configuration we are + merging into. + target_filename (str): The filename where the new configuration + will be saved. + additions (dict): (Optional) The values added to the configuration. + replacements (dict): (Optional) The wholesale replacements for + the new configuration. + + Raises: + KeyError: if one of the additions or replacements does not + already exist in the current config. + """ + # Set-up the mutable default values. + if additions is None: + additions = {} + if replacements is None: + replacements = {} + + # Create fresh config, which must extend the base one. + new_cfg = six.moves.configparser.ConfigParser() + # pylint: disable=protected-access + new_cfg._sections = copy.deepcopy(base_cfg._sections) + new_sections = new_cfg._sections + # pylint: enable=protected-access + + for section, opts in additions.items(): + curr_section = new_sections.setdefault( + section, collections.OrderedDict()) + for opt, opt_val in opts.items(): + curr_val = curr_section.get(opt) + if curr_val is None: + raise KeyError('Expected to be adding to existing option.') + curr_val = curr_val.rstrip(',') + opt_val = _transform_opt(opt_val) + curr_section[opt] = '%s, %s' % (curr_val, opt_val) + + for section, opts in replacements.items(): + curr_section = new_sections.setdefault( + section, collections.OrderedDict()) + for opt, opt_val in opts.items(): + curr_val = curr_section.get(opt) + if curr_val is None: + raise KeyError('Expected to be replacing existing option.') + opt_val = _transform_opt(opt_val) + curr_section[opt] = '%s' % (opt_val,) + + with open(target_filename, 'w') as file_obj: + new_cfg.write(file_obj) + + +def main(): + """Script entry point. Lints both sets of files.""" + default_config = read_config(get_default_config()) + make_rc(default_config, PRODUCTION_RC, + additions=_PRODUCTION_RC_ADDITIONS, + replacements=_PRODUCTION_RC_REPLACEMENTS) + make_rc(default_config, TEST_RC, + additions=_TEST_RC_ADDITIONS, + replacements=_TEST_RC_REPLACEMENTS) + lint_fileset('google_auth_httplib2.py', rc_filename=PRODUCTION_RC, + description='Library') + lint_fileset('tests', rc_filename=TEST_RC, + description='Test') + + +if __name__ == '__main__': + main() diff --git a/packages/google-auth/httplib2_transport/tox.ini b/packages/google-auth/httplib2_transport/tox.ini index 4d54203d083e..5d7457efa4b1 100644 --- a/packages/google-auth/httplib2_transport/tox.ini +++ b/packages/google-auth/httplib2_transport/tox.ini @@ -31,8 +31,7 @@ commands = --import-order-style=google \ --application-import-names="google_auth_httplib2,tests" \ google_auth_httplib2.py tests - pylint --rcfile pylintrc google_auth_httplib2.py - pylint --rcfile pylintrc.tests tests + python {toxinidir}/scripts/run_pylint.py deps = flake8 flake8-import-order diff --git a/packages/google-auth/pylintrc b/packages/google-auth/pylintrc deleted file mode 100644 index 1f01cfa1d04f..000000000000 --- a/packages/google-auth/pylintrc +++ /dev/null @@ -1,165 +0,0 @@ -[MASTER] -# Add files or directories to the blacklist. They should be base names, not -# paths. -ignore=CVS,.git,.cache,.tox,.nox - -# List of plugins (as comma separated values of python modules names) to load, -# usually to register additional checkers. -# DEFAULT: load-plugins= -# RATIONALE: We want to make sure our docstrings match the objects -# they document. -load-plugins=pylint.extensions.check_docs - -[MESSAGES CONTROL] -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once).You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use"--disable=all --enable=classes -# --disable=W" -# -# RATIONALE: -# - maybe-no-member: bi-modal functions confuse pylint type inference. -# - no-member: indirections in protobuf-generated code -# - protected-access: helpers use '_foo' of classes from generated code. -# - similarities: 'Bucket' and 'Blob' define 'metageneration' and 'owner' with -# identical implementation but different docstrings. -# - star-args: standard Python idioms for varargs: -# ancestor = Query().filter(*order_props) -# - redefined-variable-type: This error is overzealous and complains at e.g. -# if some_prop: -# return int(value) -# else: -# return float(value) -# - import-error: imports are checked via tests. -# - wrong-import-position: This error is overzealous. It assumes imports are -# completed whenever something non-trivial is -# defined, e.g. -# try: -# from foo import Bar -# except ImportError: -# class Bar(object): -# """Hi everyone""" -# and thus causes subsequent imports to be -# diagnosed as out-of-order. -# - no-name-in-module: Error gives a lot of false positives for names which -# are defined dynamically. Also, any truly missing names -# will be detected by our 100% code coverage. -# - locally-disabled: Allow us to make exceptions in edge cases, notably where -# pylint doesn't recognize inherited properties and methods -# and gives unused-argument errors. -# TEMPORARILY DISABLE AND SHOULD BE REMOVED: -# - fixme: disabled until 1.0 -# -disable = - import-star-module-level, - old-octal-literal, - oct-method, - print-statement, - unpacking-in-except, - parameter-unpacking, - backtick, - old-raise-syntax, - old-ne-operator, - long-suffix, - dict-view-method, - dict-iter-method, - metaclass-assignment, - next-method-called, - raising-string, - indexing-exception, - raw_input-builtin, - long-builtin, - file-builtin, - execfile-builtin, - coerce-builtin, - cmp-builtin, - buffer-builtin, - basestring-builtin, - apply-builtin, - filter-builtin-not-iterating, - using-cmp-argument, - useless-suppression, - range-builtin-not-iterating, - suppressed-message, - no-absolute-import, - old-division, - cmp-method, - reload-builtin, - zip-builtin-not-iterating, - intern-builtin, - unichr-builtin, - reduce-builtin, - standarderror-builtin, - unicode-builtin, - xrange-builtin, - coerce-method, - delslice-method, - getslice-method, - setslice-method, - input-builtin, - round-builtin, - hex-method, - nonzero-method, - map-builtin-not-iterating, - maybe-no-member, - no-member, - protected-access, - similarities, - star-args, - redefined-variable-type, - import-error, - wrong-import-position, - no-name-in-module, - locally-disabled, - locally-enabled, - fixme - - -[REPORTS] -# Tells whether to display a full report or only the messages -# RATIONALE: noisy -reports=no - -[BASIC] -# Regular expression matching correct method names -# DEFAULT: method-rgx=[a-z_][a-z0-9_]{2,30}$ -# RATIONALE: Some methods have longer names to be more descriptive or precise, -# especially those that implemented wordy RFCs. -method-rgx=[a-z_][a-z0-9_]{2,40}$ - -# Regular expression matching correct function names -# DEFAULT function-rgx=[a-z_][a-z0-9_]{2,30}$ -# RATIONALE: Some methods have longer names to be more descriptive or precise, -# especially those that implemented wordy RFCs. -function-rgx=[a-z_][a-z0-9_]{2,40}$ - -[TYPECHECK] -# List of module names for which member attributes should not be checked -# (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis. It -# supports qualified module names, as well as Unix pattern matching. -# DEFAULT: ignored-modules= -# RATIONALE: six aliases stuff for compatibility. -# google.protobuf fixes up namespace package "late". -ignored-modules = six, google.protobuf - - -[DESIGN] -# Minimum number of public methods for a class (see R0903). -# DEFAULT: min-public-methods=2 -# RATIONALE: context mgrs may have *no* public methods -min-public-methods=0 - -# Maximum number of arguments for function / method -# DEFAULT: max-args=5 -# RATIONALE: Many credentials classes take a lot of parameters. -max-args = 10 - -# Maximum number of attributes for a class (see R0902). -# DEFAULT: max-attributes=7 -# RATIONALE: Many credentials need to track lots of properties. -max-attributes=15 diff --git a/packages/google-auth/pylintrc.tests b/packages/google-auth/pylintrc.tests deleted file mode 100644 index 09772c5f50f1..000000000000 --- a/packages/google-auth/pylintrc.tests +++ /dev/null @@ -1,166 +0,0 @@ -[MASTER] -# Add files or directories to the blacklist. They should be base names, not -# paths. -ignore=CVS,.git,.cache,.tox,.nox - -[MESSAGES CONTROL] -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once).You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use"--disable=all --enable=classes -# --disable=W" -# -# RATIONALE: -# - maybe-no-member: bi-modal functions confuse pylint type inference. -# - no-member: indirections in protobuf-generated code -# - protected-access: helpers use '_foo' of classes from generated code. -# - similarities: 'Bucket' and 'Blob' define 'metageneration' and 'owner' with -# identical implementation but different docstrings. -# - star-args: standard Python idioms for varargs: -# ancestor = Query().filter(*order_props) -# - redefined-variable-type: This error is overzealous and complains at e.g. -# if some_prop: -# return int(value) -# else: -# return float(value) -# - import-error: imports are checked via tests. -# - wrong-import-position: This error is overzealous. It assumes imports are -# completed whenever something non-trivial is -# defined, e.g. -# try: -# from foo import Bar -# except ImportError: -# class Bar(object): -# """Hi everyone""" -# and thus causes subsequent imports to be -# diagnosed as out-of-order. -# - no-name-in-module: Error gives a lot of false positives for names which -# are defined dynamically. Also, any truly missing names -# will be detected by our 100% code coverage. -# - locally-disabled: Allow us to make exceptions in edge cases, notably where -# pylint doesn't recognize inherited properties and methods -# and gives unused-argument errors. -disable = - import-star-module-level, - old-octal-literal, - oct-method, - print-statement, - unpacking-in-except, - parameter-unpacking, - backtick, - old-raise-syntax, - old-ne-operator, - long-suffix, - dict-view-method, - dict-iter-method, - metaclass-assignment, - next-method-called, - raising-string, - indexing-exception, - raw_input-builtin, - long-builtin, - file-builtin, - execfile-builtin, - coerce-builtin, - cmp-builtin, - buffer-builtin, - basestring-builtin, - apply-builtin, - filter-builtin-not-iterating, - using-cmp-argument, - useless-suppression, - range-builtin-not-iterating, - suppressed-message, - no-absolute-import, - old-division, - cmp-method, - reload-builtin, - zip-builtin-not-iterating, - intern-builtin, - unichr-builtin, - reduce-builtin, - standarderror-builtin, - unicode-builtin, - xrange-builtin, - coerce-method, - delslice-method, - getslice-method, - setslice-method, - input-builtin, - round-builtin, - hex-method, - nonzero-method, - map-builtin-not-iterating, - maybe-no-member, - no-member, - protected-access, - similarities, - star-args, - redefined-variable-type, - import-error, - wrong-import-position, - no-name-in-module, - locally-disabled, - locally-enabled, - missing-docstring, - redefined-outer-name, - no-self-use, - too-many-locals, - too-many-public-methods, - abstract-method, - method-hidden, - unused-argument - - -[REPORTS] -# Tells whether to display a full report or only the messages -# RATIONALE: noisy -reports=no - -[BASIC] -# Good variable names which should always be accepted, separated by a comma -# DEFAULT: good-names=i,j,k,ex,Run,_ -# RATIONALE: 'fh' is a well-known file handle variable name. -good-names = i, j, k, ex, Run, _, - fh - -# Regular expression matching correct method names -# DEFAULT: method-rgx=[a-z_][a-z0-9_]{2,30}$ -# RATIONALE: Some methods have longer names to be precise against wordy RFCs. -method-rgx=[a-z_][a-z0-9_]{2,80}$ - -# Regular expression matching correct function names -# DEFAULT function-rgx=[a-z_][a-z0-9_]{2,30}$ -# RATIONALE: Some test methods have long descriptive names. -function-rgx=[a-z_][a-z0-9_]{2,80}$ - -[TYPECHECK] -# List of module names for which member attributes should not be checked -# (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis. It -# supports qualified module names, as well as Unix pattern matching. -# DEFAULT: ignored-modules= -# RATIONALE: six aliases stuff for compatibility. -# google.protobuf fixes up namespace package "late". -ignored-modules = six, google.protobuf - - -[DESIGN] -# Minimum number of public methods for a class (see R0903). -# DEFAULT: min-public-methods=2 -# RATIONALE: context mgrs may have *no* public methods -min-public-methods=0 - -# Maximum number of arguments for function / method -# DEFAULT: max-args=5 -# RATIONALE: Many credentials classes take a lot of parameters. -max-args = 10 - -# Maximum number of attributes for a class (see R0902). -# DEFAULT: max-attributes=7 -# RATIONALE: Many credentials need to track lots of properties. -max-attributes=15 diff --git a/packages/google-auth/scripts/.gitignore b/packages/google-auth/scripts/.gitignore new file mode 100644 index 000000000000..3596d32ac39c --- /dev/null +++ b/packages/google-auth/scripts/.gitignore @@ -0,0 +1,3 @@ +# Generated files +pylintrc +pylintrc.test diff --git a/packages/google-auth/scripts/run_pylint.py b/packages/google-auth/scripts/run_pylint.py new file mode 100644 index 000000000000..ed994ffd0b07 --- /dev/null +++ b/packages/google-auth/scripts/run_pylint.py @@ -0,0 +1,251 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""This script runs Pylint on the specified source. + +Before running Pylint, it generates a Pylint configuration on +the fly based on programmatic defaults. +""" + +from __future__ import print_function + +import collections +import copy +import io +import os +import subprocess +import sys + +import six + + +_SCRIPTS_DIR = os.path.abspath(os.path.dirname(__file__)) +PRODUCTION_RC = os.path.join(_SCRIPTS_DIR, 'pylintrc') +TEST_RC = os.path.join(_SCRIPTS_DIR, 'pylintrc.test') + +_PRODUCTION_RC_ADDITIONS = { + 'MESSAGES CONTROL': { + 'disable': [ + 'I', + 'import-error', + 'no-member', + 'protected-access', + 'redefined-variable-type', + 'similarities', + ], + }, +} +_PRODUCTION_RC_REPLACEMENTS = { + 'MASTER': { + 'ignore': ['CVS', '.git', '.cache', '.tox', '.nox'], + 'load-plugins': 'pylint.extensions.check_docs', + }, + 'REPORTS': { + 'reports': 'no', + }, + 'BASIC': { + 'method-rgx': '[a-z_][a-z0-9_]{2,40}$', + 'function-rgx': '[a-z_][a-z0-9_]{2,40}$', + }, + 'TYPECHECK': { + 'ignored-modules': ['six', 'google.protobuf'], + }, + 'DESIGN': { + 'min-public-methods': '0', + 'max-args': '10', + 'max-attributes': '15', + }, +} +_TEST_RC_ADDITIONS = copy.deepcopy(_PRODUCTION_RC_ADDITIONS) +_TEST_RC_ADDITIONS['MESSAGES CONTROL']['disable'].extend([ + 'missing-docstring', + 'no-self-use', + 'redefined-outer-name', + 'unused-argument', +]) +_TEST_RC_REPLACEMENTS = copy.deepcopy(_PRODUCTION_RC_REPLACEMENTS) +_TEST_RC_REPLACEMENTS.setdefault('BASIC', {}) +_TEST_RC_REPLACEMENTS['BASIC'].update({ + 'good-names': ['i', 'j', 'k', 'ex', 'Run', '_', 'fh'], + 'method-rgx': '[a-z_][a-z0-9_]{2,80}$', + 'function-rgx': '[a-z_][a-z0-9_]{2,80}$', +}) +IGNORED_FILES = () + +_ERROR_TEMPLATE = 'Pylint failed on {} with status {:d}.' +_LINT_FILESET_MSG = ( + 'Keyword arguments rc_filename and description are both ' + 'required. No other keyword arguments are allowed.') + + +def get_default_config(): + """Get the default Pylint configuration. + + .. note:: + + The output of this function varies based on the current version of + Pylint installed. + + Returns: + str: The default Pylint configuration. + """ + # Swallow STDERR if it says + # "No config file found, using default configuration" + result = subprocess.check_output(['pylint', '--generate-rcfile'], + stderr=subprocess.PIPE) + # On Python 3, this returns bytes (from STDOUT), so we + # convert to a string. + return result.decode('utf-8') + + +def read_config(contents): + """Reads pylintrc config into native ConfigParser object. + + Args: + contents (str): The contents of the file containing the INI config. + + Returns + ConfigParser.ConfigParser: The parsed configuration. + """ + file_obj = io.StringIO(contents) + config = six.moves.configparser.ConfigParser() + config.readfp(file_obj) + return config + + +def _transform_opt(opt_val): + """Transform a config option value to a string. + + If already a string, do nothing. If an iterable, then + combine into a string by joining on ",". + + Args: + opt_val (Union[str, list]): A config option's value. + + Returns: + str: The option value converted to a string. + """ + if isinstance(opt_val, (list, tuple)): + return ','.join(opt_val) + else: + return opt_val + + +def lint_fileset(*dirnames, **kwargs): + """Lints a group of files using a given rcfile. + + Keyword arguments are + + * ``rc_filename`` (``str``): The name of the Pylint config RC file. + * ``description`` (``str``): A description of the files and configuration + currently being run. + + Args: + dirnames (tuple): Directories to run Pylint in. + kwargs: The keyword arguments. The only keyword arguments + are ``rc_filename`` and ``description`` and both + are required. + + Raises: + KeyError: If the wrong keyword arguments are used. + """ + try: + rc_filename = kwargs['rc_filename'] + description = kwargs['description'] + if len(kwargs) != 2: + raise KeyError + except KeyError: + raise KeyError(_LINT_FILESET_MSG) + + pylint_shell_command = ['pylint', '--rcfile', rc_filename] + pylint_shell_command.extend(dirnames) + status_code = subprocess.call(pylint_shell_command) + if status_code != 0: + error_message = _ERROR_TEMPLATE.format(description, status_code) + print(error_message, file=sys.stderr) + sys.exit(status_code) + + +def make_rc(base_cfg, target_filename, + additions=None, replacements=None): + """Combines a base rc and additions into single file. + + Args: + base_cfg (ConfigParser.ConfigParser): The configuration we are + merging into. + target_filename (str): The filename where the new configuration + will be saved. + additions (dict): (Optional) The values added to the configuration. + replacements (dict): (Optional) The wholesale replacements for + the new configuration. + + Raises: + KeyError: if one of the additions or replacements does not + already exist in the current config. + """ + # Set-up the mutable default values. + if additions is None: + additions = {} + if replacements is None: + replacements = {} + + # Create fresh config, which must extend the base one. + new_cfg = six.moves.configparser.ConfigParser() + # pylint: disable=protected-access + new_cfg._sections = copy.deepcopy(base_cfg._sections) + new_sections = new_cfg._sections + # pylint: enable=protected-access + + for section, opts in additions.items(): + curr_section = new_sections.setdefault( + section, collections.OrderedDict()) + for opt, opt_val in opts.items(): + curr_val = curr_section.get(opt) + if curr_val is None: + raise KeyError('Expected to be adding to existing option.') + curr_val = curr_val.rstrip(',') + opt_val = _transform_opt(opt_val) + curr_section[opt] = '%s, %s' % (curr_val, opt_val) + + for section, opts in replacements.items(): + curr_section = new_sections.setdefault( + section, collections.OrderedDict()) + for opt, opt_val in opts.items(): + curr_val = curr_section.get(opt) + if curr_val is None: + raise KeyError('Expected to be replacing existing option.') + opt_val = _transform_opt(opt_val) + curr_section[opt] = '%s' % (opt_val,) + + with open(target_filename, 'w') as file_obj: + new_cfg.write(file_obj) + + +def main(): + """Script entry point. Lints both sets of files.""" + default_config = read_config(get_default_config()) + make_rc(default_config, PRODUCTION_RC, + additions=_PRODUCTION_RC_ADDITIONS, + replacements=_PRODUCTION_RC_REPLACEMENTS) + make_rc(default_config, TEST_RC, + additions=_TEST_RC_ADDITIONS, + replacements=_TEST_RC_REPLACEMENTS) + lint_fileset('google', rc_filename=PRODUCTION_RC, + description='Library') + lint_fileset('tests', 'system_tests', rc_filename=TEST_RC, + description='Test') + + +if __name__ == '__main__': + main() diff --git a/packages/google-auth/tests/oauth2/test_credentials.py b/packages/google-auth/tests/oauth2/test_credentials.py index 173bb00f7ff4..5c76cb202905 100644 --- a/packages/google-auth/tests/oauth2/test_credentials.py +++ b/packages/google-auth/tests/oauth2/test_credentials.py @@ -26,9 +26,10 @@ class TestCredentials(object): REFRESH_TOKEN = 'refresh_token' CLIENT_ID = 'client_id' CLIENT_SECRET = 'client_secret' + credentials = None @pytest.fixture(autouse=True) - def credentials(self): + def credentials_fixture(self): self.credentials = credentials.Credentials( token=None, refresh_token=self.REFRESH_TOKEN, token_uri=self.TOKEN_URI, client_id=self.CLIENT_ID, diff --git a/packages/google-auth/tox.ini b/packages/google-auth/tox.ini index 1ef803cf99bc..f41ffd10b733 100644 --- a/packages/google-auth/tox.ini +++ b/packages/google-auth/tox.ini @@ -73,8 +73,7 @@ commands = --import-order-style=google \ --application-import-names="google,tests,system_tests" \ google tests - pylint --rcfile pylintrc google - pylint --rcfile pylintrc.tests tests system_tests + python {toxinidir}/scripts/run_pylint.py deps = flake8 flake8-import-order From d1f6f9467efaf501a23b6ebda5b19a16c3be8807 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Wed, 2 Nov 2016 20:31:14 -0700 Subject: [PATCH 057/966] Add grpc transport (#67) --- packages/google-auth/docs/conf.py | 1 + .../reference/google.auth.transport.grpc.rst | 7 + .../docs/reference/google.auth.transport.rst | 1 + .../google-auth/docs/requirements-docs.txt | 1 + .../google-auth/google/auth/transport/grpc.py | 118 ++++++++++++++++ packages/google-auth/scripts/run_pylint.py | 3 +- packages/google-auth/system_tests/nox.py | 6 + .../google-auth/system_tests/test_grpc.py | 40 ++++++ .../google-auth/tests/transport/test_grpc.py | 129 ++++++++++++++++++ packages/google-auth/tox.ini | 3 + 10 files changed, 308 insertions(+), 1 deletion(-) create mode 100644 packages/google-auth/docs/reference/google.auth.transport.grpc.rst create mode 100644 packages/google-auth/google/auth/transport/grpc.py create mode 100644 packages/google-auth/system_tests/test_grpc.py create mode 100644 packages/google-auth/tests/transport/test_grpc.py diff --git a/packages/google-auth/docs/conf.py b/packages/google-auth/docs/conf.py index cfd452f54a73..7b820b8f6a19 100644 --- a/packages/google-auth/docs/conf.py +++ b/packages/google-auth/docs/conf.py @@ -373,3 +373,4 @@ # Autodoc config autoclass_content = 'both' autodoc_member_order = 'bysource' +autodoc_mock_imports = ['grpc'] diff --git a/packages/google-auth/docs/reference/google.auth.transport.grpc.rst b/packages/google-auth/docs/reference/google.auth.transport.grpc.rst new file mode 100644 index 000000000000..e16d1d853cbe --- /dev/null +++ b/packages/google-auth/docs/reference/google.auth.transport.grpc.rst @@ -0,0 +1,7 @@ +google.auth.transport.grpc module +================================= + +.. automodule:: google.auth.transport.grpc + :members: + :inherited-members: + :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.auth.transport.rst b/packages/google-auth/docs/reference/google.auth.transport.rst index 7767daf35138..1f802c3b5ed8 100644 --- a/packages/google-auth/docs/reference/google.auth.transport.rst +++ b/packages/google-auth/docs/reference/google.auth.transport.rst @@ -11,6 +11,7 @@ Submodules .. toctree:: + google.auth.transport.grpc google.auth.transport.requests google.auth.transport.urllib3 diff --git a/packages/google-auth/docs/requirements-docs.txt b/packages/google-auth/docs/requirements-docs.txt index 7af895f3ab90..aa96755e2f70 100644 --- a/packages/google-auth/docs/requirements-docs.txt +++ b/packages/google-auth/docs/requirements-docs.txt @@ -1,2 +1,3 @@ sphinx-docstring-typing urllib3 +requests diff --git a/packages/google-auth/google/auth/transport/grpc.py b/packages/google-auth/google/auth/transport/grpc.py new file mode 100644 index 000000000000..dfb288304c6e --- /dev/null +++ b/packages/google-auth/google/auth/transport/grpc.py @@ -0,0 +1,118 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Authorization support for gRPC.""" + +from __future__ import absolute_import + +import grpc + + +class AuthMetadataPlugin(grpc.AuthMetadataPlugin): + """A `gRPC AuthMetadataPlugin`_ that inserts the credentials into each + request. + + .. _gRPC AuthMetadataPlugin: + http://www.grpc.io/grpc/python/grpc.html#grpc.AuthMetadataPlugin + + Args: + credentials (google.auth.credentials.Credentials): The credentials to + add to requests. + request (google.auth.transport.Request): A HTTP transport request + object used to refresh credentials as needed. + """ + def __init__(self, credentials, request): + self._credentials = credentials + self._request = request + + def _get_authorization_headers(self): + """Gets the authorization headers for a request. + + Returns: + Sequence[Tuple[str, str]]: A list of request headers (key, value) + to add to the request. + """ + if self._credentials.expired or not self._credentials.valid: + self._credentials.refresh(self._request) + + return [ + ('authorization', 'Bearer {}'.format(self._credentials.token)) + ] + + def __call__(self, context, callback): + """Passes authorization metadata into the given callback. + + Args: + context (grpc.AuthMetadataContext): The RPC context. + callback (grpc.AuthMetadataPluginCallback): The callback that will + be invoked to pass in the authorization metadata. + """ + callback(self._get_authorization_headers(), None) + + +def secure_authorized_channel( + credentials, target, request, ssl_credentials=None): + """Creates a secure authorized gRPC channel. + + This creates a channel with SSL and :class:`AuthMetadataPlugin`. This + channel can be used to create a stub that can make authorized requests. + + Example:: + + import google.auth + import google.auth.transport.grpc + import google.auth.transport.requests + from google.cloud.speech.v1 import cloud_speech_pb2 + + # Get credentials. + credentials, _ = google.auth.default() + + # Get an HTTP request function to refresh credentials. + request = google.auth.transport.requests.Request() + + # Create a channel. + channel = google.auth.transport.grpc.secure_authorized_channel( + credentials, 'speech.googleapis.com:443', request) + + # Use the channel to create a stub. + cloud_speech.create_Speech_stub(channel) + + Args: + credentials (google.auth.credentials.Credentials): The credentials to + add to requests. + target (str): The host and port of the service. + request (google.auth.transport.Request): A HTTP transport request + object used to refresh credentials as needed. Even though gRPC + is a separate transport, there's no way to refresh the credentials + without using a standard http transport. + ssl_credentials (grpc.ChannelCredentials): Optional SSL channel + credentials. This can be used to specify different certificates. + + Returns: + grpc.Channel: The created gRPC channel. + """ + # Create the metadata plugin for inserting the authorization header. + metadata_plugin = AuthMetadataPlugin(credentials, request) + + # Create a set of grpc.CallCredentials using the metadata plugin. + google_auth_credentials = grpc.metadata_call_credentials(metadata_plugin) + + if ssl_credentials is None: + ssl_credentials = grpc.ssl_channel_credentials() + + # Combine the ssl credentials and the authorization credentials. + composite_credentials = grpc.composite_channel_credentials( + ssl_credentials, google_auth_credentials) + + return grpc.secure_channel(target, composite_credentials) diff --git a/packages/google-auth/scripts/run_pylint.py b/packages/google-auth/scripts/run_pylint.py index ed994ffd0b07..b06a98c42819 100644 --- a/packages/google-auth/scripts/run_pylint.py +++ b/packages/google-auth/scripts/run_pylint.py @@ -73,11 +73,12 @@ 'no-self-use', 'redefined-outer-name', 'unused-argument', + 'no-name-in-module', ]) _TEST_RC_REPLACEMENTS = copy.deepcopy(_PRODUCTION_RC_REPLACEMENTS) _TEST_RC_REPLACEMENTS.setdefault('BASIC', {}) _TEST_RC_REPLACEMENTS['BASIC'].update({ - 'good-names': ['i', 'j', 'k', 'ex', 'Run', '_', 'fh'], + 'good-names': ['i', 'j', 'k', 'ex', 'Run', '_', 'fh', 'pytestmark'], 'method-rgx': '[a-z_][a-z0-9_]{2,80}$', 'function-rgx': '[a-z_][a-z0-9_]{2,80}$', }) diff --git a/packages/google-auth/system_tests/nox.py b/packages/google-auth/system_tests/nox.py index 6429f7b667e8..5df72c81b175 100644 --- a/packages/google-auth/system_tests/nox.py +++ b/packages/google-auth/system_tests/nox.py @@ -248,3 +248,9 @@ def session_app_engine(session): session.env['TEST_APP_URL'] = application_url session.chdir(HERE) session.run('pytest', 'test_app_engine.py') + + +def session_grpc(session): + session.virtualenv = False + session.env[EXPLICIT_CREDENTIALS_ENV] = SERVICE_ACCOUNT_FILE + session.run('pytest', 'test_grpc.py') diff --git a/packages/google-auth/system_tests/test_grpc.py b/packages/google-auth/system_tests/test_grpc.py new file mode 100644 index 000000000000..0db9f1dd5642 --- /dev/null +++ b/packages/google-auth/system_tests/test_grpc.py @@ -0,0 +1,40 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import google.auth +import google.auth.credentials +import google.auth.transport.grpc +from google.cloud.gapic.pubsub.v1 import publisher_api + + +def test_grpc_request(http_request): + credentials, project_id = google.auth.default() + credentials = google.auth.credentials.with_scopes_if_required( + credentials, ['https://www.googleapis.com/auth/pubsub']) + + target = '{}:{}'.format( + publisher_api.PublisherApi.SERVICE_ADDRESS, + publisher_api.PublisherApi.DEFAULT_SERVICE_PORT) + + channel = google.auth.transport.grpc.secure_authorized_channel( + credentials, target, http_request) + + # Create a pub/sub client. + client = publisher_api.PublisherApi(channel=channel) + + # list the topics and drain the iterator to test that an authorized API + # call works. + list_topics_iter = client.list_topics( + project='projects/{}'.format(project_id)) + list(list_topics_iter) diff --git a/packages/google-auth/tests/transport/test_grpc.py b/packages/google-auth/tests/transport/test_grpc.py new file mode 100644 index 000000000000..2b214a725610 --- /dev/null +++ b/packages/google-auth/tests/transport/test_grpc.py @@ -0,0 +1,129 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import mock + +import pytest + +try: + import google.auth.transport.grpc + HAS_GRPC = True +except ImportError: # pragma: NO COVER + HAS_GRPC = False + + +pytestmark = pytest.mark.skipif(not HAS_GRPC, reason='gRPC is unavailable.') + + +class MockCredentials(object): + def __init__(self, token='token'): + self.token = token + self.valid = True + self.expired = False + + def refresh(self, request): + self.token += '1' + + +class TestAuthMetadataPlugin(object): + def test_call_no_refresh(self): + credentials = MockCredentials() + request = mock.Mock() + + plugin = google.auth.transport.grpc.AuthMetadataPlugin( + credentials, request) + + context = mock.Mock() + callback = mock.Mock() + + plugin(context, callback) + + assert callback.called_once_with( + [('authorization', 'Bearer {}'.format(credentials.token))], None) + + def test_call_refresh(self): + credentials = MockCredentials() + credentials.expired = True + request = mock.Mock() + + plugin = google.auth.transport.grpc.AuthMetadataPlugin( + credentials, request) + + context = mock.Mock() + callback = mock.Mock() + + plugin(context, callback) + + assert credentials.token == 'token1' + assert callback.called_once_with( + [('authorization', 'Bearer {}'.format(credentials.token))], None) + + +@mock.patch('grpc.composite_channel_credentials') +@mock.patch('grpc.metadata_call_credentials') +@mock.patch('grpc.ssl_channel_credentials') +@mock.patch('grpc.secure_channel') +def test_secure_authorized_channel( + secure_channel, ssl_channel_credentials, metadata_call_credentials, + composite_channel_credentials): + credentials = mock.Mock() + request = mock.Mock() + target = 'example.com:80' + + channel = google.auth.transport.grpc.secure_authorized_channel( + credentials, target, request) + + # Check the auth plugin construction. + auth_plugin = metadata_call_credentials.call_args[0][0] + assert isinstance( + auth_plugin, google.auth.transport.grpc.AuthMetadataPlugin) + assert auth_plugin._credentials == credentials + assert auth_plugin._request == request + + # Check the ssl channel call. + assert ssl_channel_credentials.called + + # Check the composite credentials call. + composite_channel_credentials.assert_called_once_with( + ssl_channel_credentials.return_value, + metadata_call_credentials.return_value) + + # Check the channel call. + secure_channel.assert_called_once_with( + target, composite_channel_credentials.return_value) + assert channel == secure_channel.return_value + + +@mock.patch('grpc.composite_channel_credentials') +@mock.patch('grpc.metadata_call_credentials') +@mock.patch('grpc.ssl_channel_credentials') +@mock.patch('grpc.secure_channel') +def test_secure_authorized_channel_explicit_ssl( + secure_channel, ssl_channel_credentials, metadata_call_credentials, + composite_channel_credentials): + credentials = mock.Mock() + request = mock.Mock() + target = 'example.com:80' + ssl_credentials = mock.Mock() + + google.auth.transport.grpc.secure_authorized_channel( + credentials, target, request, ssl_credentials=ssl_credentials) + + # Check the ssl channel call. + assert not ssl_channel_credentials.called + + # Check the composite credentials call. + composite_channel_credentials.assert_called_once_with( + ssl_credentials, + metadata_call_credentials.return_value) diff --git a/packages/google-auth/tox.ini b/packages/google-auth/tox.ini index f41ffd10b733..c77487c15d4d 100644 --- a/packages/google-auth/tox.ini +++ b/packages/google-auth/tox.ini @@ -11,6 +11,7 @@ deps = urllib3 certifi requests + grpcio; platform_python_implementation != 'PyPy' commands = py.test --cov=google.auth --cov=google.oauth2 --cov=tests {posargs:tests} @@ -30,6 +31,7 @@ commands = deps = {[testenv]deps} nox-automation + gapic-google-pubsub-v1==0.11.1 passenv = SKIP_APP_ENGINE_SYSTEM_TEST CLOUD_SDK_ROOT @@ -42,6 +44,7 @@ commands = deps = {[testenv]deps} nox-automation + gapic-google-pubsub-v1==0.11.1 passenv = SKIP_APP_ENGINE_SYSTEM_TEST CLOUD_SDK_ROOT From e042572c8fda3e5496cde5427bf70d37fe01db3f Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Wed, 2 Nov 2016 23:42:51 -0700 Subject: [PATCH 058/966] Add google.auth._oauth2client - helpers for oauth2client migration (#70) --- .../google-auth/google/auth/_oauth2client.py | 166 ++++++++++++++++++ packages/google-auth/tests/conftest.py | 37 ++++ .../google-auth/tests/test__oauth2client.py | 157 +++++++++++++++++ packages/google-auth/tox.ini | 1 + 4 files changed, 361 insertions(+) create mode 100644 packages/google-auth/google/auth/_oauth2client.py create mode 100644 packages/google-auth/tests/conftest.py create mode 100644 packages/google-auth/tests/test__oauth2client.py diff --git a/packages/google-auth/google/auth/_oauth2client.py b/packages/google-auth/google/auth/_oauth2client.py new file mode 100644 index 000000000000..312326e18190 --- /dev/null +++ b/packages/google-auth/google/auth/_oauth2client.py @@ -0,0 +1,166 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Helpers for transitioning from oauth2client to google-auth. + +.. warning:: + This module is private as it is intended to assist first-party downstream + clients with the transition from oauth2client to google-auth. +""" + +from __future__ import absolute_import + +from google.auth import _helpers +import google.auth.app_engine +import google.oauth2.credentials +import google.oauth2.service_account + +try: + import oauth2client.client + import oauth2client.contrib.gce + import oauth2client.service_account +except ImportError: + raise ImportError('oauth2client is not installed.') + +try: + import oauth2client.contrib.appengine + _HAS_APPENGINE = True +except ImportError: + _HAS_APPENGINE = False + + +_CONVERT_ERROR_TMPL = ( + 'Unable to convert {} to a google-auth credentials class.') + + +def _convert_oauth2_credentials(credentials): + """Converts to :class:`google.oauth2.credentials.Credentials`. + + Args: + credentials (Union[oauth2client.client.OAuth2Credentials, + oauth2client.client.GoogleCredentials]): The credentials to + convert. + + Returns: + google.oauth2.credentials.Credentials: The converted credentials. + """ + new_credentials = google.oauth2.credentials.Credentials( + token=credentials.access_token, + refresh_token=credentials.refresh_token, + token_uri=credentials.token_uri, + client_id=credentials.client_id, + client_secret=credentials.client_secret, + scopes=credentials.scopes) + + new_credentials._expires = credentials.token_expiry + + return new_credentials + + +def _convert_service_account_credentials(credentials): + """Converts to :class:`google.oauth2.service_account.Credentials`. + + Args: + credentials (Union[ + oauth2client.service_account.ServiceAccountCredentials, + oauth2client.service_account._JWTAccessCredentials]): The + credentials to convert. + + Returns: + google.oauth2.service_account.Credentials: The converted credentials. + """ + info = credentials.serialization_data.copy() + info['token_uri'] = credentials.token_uri + return google.oauth2.service_account.Credentials.from_service_account_info( + info) + + +def _convert_gce_app_assertion_credentials(credentials): + """Converts to :class:`google.auth.compute_engine.Credentials`. + + Args: + credentials (oauth2client.contrib.gce.AppAssertionCredentials): The + credentials to convert. + + Returns: + google.oauth2.service_account.Credentials: The converted credentials. + """ + return google.auth.compute_engine.Credentials( + service_account_email=credentials.service_account_email) + + +def _convert_appengine_app_assertion_credentials(credentials): + """Converts to :class:`google.auth.app_engine.Credentials`. + + Args: + credentials (oauth2client.contrib.app_engine.AppAssertionCredentials): + The credentials to convert. + + Returns: + google.oauth2.service_account.Credentials: The converted credentials. + """ + # pylint: disable=invalid-name + return google.auth.app_engine.Credentials( + scopes=_helpers.string_to_scopes(credentials.scope), + service_account_id=credentials.service_account_id) + + +_CLASS_CONVERSION_MAP = { + oauth2client.client.OAuth2Credentials: _convert_oauth2_credentials, + oauth2client.client.GoogleCredentials: _convert_oauth2_credentials, + oauth2client.service_account.ServiceAccountCredentials: + _convert_service_account_credentials, + oauth2client.service_account._JWTAccessCredentials: + _convert_service_account_credentials, + oauth2client.contrib.gce.AppAssertionCredentials: + _convert_gce_app_assertion_credentials, +} + +if _HAS_APPENGINE: + _CLASS_CONVERSION_MAP[ + oauth2client.contrib.appengine.AppAssertionCredentials] = ( + _convert_appengine_app_assertion_credentials) + + +def convert(credentials): + """Convert oauth2client credentials to google-auth credentials. + + This class converts: + + - :class:`oauth2client.client.OAuth2Credentials` to + :class:`google.oauth2.credentials.Credentials`. + - :class:`oauth2client.client.GoogleCredentials` to + :class:`google.oauth2.credentials.Credentials`. + - :class:`oauth2client.service_account.ServiceAccountCredentials` to + :class:`google.oauth2.service_account.Credentials`. + - :class:`oauth2client.service_account._JWTAccessCredentials` to + :class:`google.oauth2.service_account.Credentials`. + - :class:`oauth2client.contrib.gce.AppAssertionCredentials` to + :class:`google.auth.compute_engine.Credentials`. + - :class:`oauth2client.contrib.appengine.AppAssertionCredentials` to + :class:`google.auth.app_engine.Credentials`. + + Returns: + google.auth.credentials.Credentials: The converted credentials. + + Raises: + ValueError: If the credentials could not be converted. + """ + + credentials_class = type(credentials) + + try: + return _CLASS_CONVERSION_MAP[credentials_class](credentials) + except KeyError: + raise ValueError(_CONVERT_ERROR_TMPL.format(credentials_class)) diff --git a/packages/google-auth/tests/conftest.py b/packages/google-auth/tests/conftest.py new file mode 100644 index 000000000000..c9e3f8492b67 --- /dev/null +++ b/packages/google-auth/tests/conftest.py @@ -0,0 +1,37 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys + +import mock +import pytest + + +@pytest.fixture +def mock_non_existent_module(monkeypatch): + """Mocks a non-existing module in sys.modules. + + Additionally mocks any non-existing modules specified in the dotted path. + """ + def _mock_non_existent_module(path): + parts = path.split('.') + partial = [] + for part in parts: + partial.append(part) + current_module = '.'.join(partial) + if current_module not in sys.modules: + monkeypatch.setitem( + sys.modules, current_module, mock.MagicMock()) + + return _mock_non_existent_module diff --git a/packages/google-auth/tests/test__oauth2client.py b/packages/google-auth/tests/test__oauth2client.py new file mode 100644 index 000000000000..9478406fa0ee --- /dev/null +++ b/packages/google-auth/tests/test__oauth2client.py @@ -0,0 +1,157 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import datetime +import os +import sys + +import mock +import oauth2client.client +import oauth2client.contrib.gce +import oauth2client.service_account +import pytest +from six.moves import reload_module + +from google.auth import _oauth2client + + +DATA_DIR = os.path.join(os.path.dirname(__file__), 'data') +SERVICE_ACCOUNT_JSON_FILE = os.path.join(DATA_DIR, 'service_account.json') + + +def test__convert_oauth2_credentials(): + old_credentials = oauth2client.client.OAuth2Credentials( + 'access_token', 'client_id', 'client_secret', 'refresh_token', + datetime.datetime.min, 'token_uri', 'user_agent', scopes='one two') + + new_credentials = _oauth2client._convert_oauth2_credentials( + old_credentials) + + assert new_credentials.token == old_credentials.access_token + assert new_credentials._refresh_token == old_credentials.refresh_token + assert new_credentials._client_id == old_credentials.client_id + assert new_credentials._client_secret == old_credentials.client_secret + assert new_credentials._token_uri == old_credentials.token_uri + assert new_credentials.scopes == old_credentials.scopes + + +def test__convert_service_account_credentials(): + old_class = oauth2client.service_account.ServiceAccountCredentials + old_credentials = old_class.from_json_keyfile_name( + SERVICE_ACCOUNT_JSON_FILE) + + new_credentials = _oauth2client._convert_service_account_credentials( + old_credentials) + + assert (new_credentials._service_account_email == + old_credentials.service_account_email) + assert new_credentials._signer.key_id == old_credentials._private_key_id + assert new_credentials._token_uri == old_credentials.token_uri + + +def test__convert_service_account_credentials_with_jwt(): + old_class = oauth2client.service_account._JWTAccessCredentials + old_credentials = old_class.from_json_keyfile_name( + SERVICE_ACCOUNT_JSON_FILE) + + new_credentials = _oauth2client._convert_service_account_credentials( + old_credentials) + + assert (new_credentials._service_account_email == + old_credentials.service_account_email) + assert new_credentials._signer.key_id == old_credentials._private_key_id + assert new_credentials._token_uri == old_credentials.token_uri + + +def test__convert_gce_app_assertion_credentials(): + old_credentials = oauth2client.contrib.gce.AppAssertionCredentials( + email='some_email') + + new_credentials = _oauth2client._convert_gce_app_assertion_credentials( + old_credentials) + + assert (new_credentials._service_account_email == + old_credentials.service_account_email) + + +@pytest.fixture +def mock_oauth2client_gae_imports(mock_non_existent_module): + mock_non_existent_module('google.appengine.api.app_identity') + mock_non_existent_module('google.appengine.ext.ndb') + mock_non_existent_module('google.appengine.ext.webapp.util') + mock_non_existent_module('webapp2') + + +@mock.patch('google.auth.app_engine.app_identity') +def test__convert_appengine_app_assertion_credentials( + app_identity, mock_oauth2client_gae_imports): + + import oauth2client.contrib.appengine + + service_account_id = 'service_account_id' + old_credentials = oauth2client.contrib.appengine.AppAssertionCredentials( + scope='one two', service_account_id=service_account_id) + + new_credentials = ( + _oauth2client._convert_appengine_app_assertion_credentials( + old_credentials)) + + assert new_credentials.scopes == ['one', 'two'] + assert (new_credentials._service_account_id == + old_credentials.service_account_id) + + +class MockCredentials(object): + pass + + +def test_convert_success(): + convert_function = mock.Mock() + conversion_map_patch = mock.patch.object( + _oauth2client, '_CLASS_CONVERSION_MAP', + {MockCredentials: convert_function}) + credentials = MockCredentials() + + with conversion_map_patch: + result = _oauth2client.convert(credentials) + + convert_function.assert_called_once_with(credentials) + assert result == convert_function.return_value + + +def test_convert_not_found(): + with pytest.raises(ValueError) as excinfo: + _oauth2client.convert('a string is not a real credentials class') + + assert excinfo.match('Unable to convert') + + +@pytest.fixture +def reset__oauth2client_module(): + """Reloads the _oauth2client module after a test.""" + reload_module(_oauth2client) + + +def test_import_has_app_engine( + mock_oauth2client_gae_imports, reset__oauth2client_module): + reload_module(_oauth2client) + assert _oauth2client._HAS_APPENGINE + + +def test_import_without_oauth2client(monkeypatch, reset__oauth2client_module): + monkeypatch.setitem(sys.modules, 'oauth2client', None) + with pytest.raises(ImportError) as excinfo: + reload_module(_oauth2client) + + assert excinfo.match('oauth2client') diff --git a/packages/google-auth/tox.ini b/packages/google-auth/tox.ini index c77487c15d4d..ad760bd662f0 100644 --- a/packages/google-auth/tox.ini +++ b/packages/google-auth/tox.ini @@ -11,6 +11,7 @@ deps = urllib3 certifi requests + oauth2client grpcio; platform_python_implementation != 'PyPy' commands = py.test --cov=google.auth --cov=google.oauth2 --cov=tests {posargs:tests} From c230296f98d34980041c5ff3b63b2f42a5867d14 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Wed, 2 Nov 2016 23:48:41 -0700 Subject: [PATCH 059/966] Release v0.2.0 --- packages/google-auth/CHANGELOG.rst | 8 ++++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.rst b/packages/google-auth/CHANGELOG.rst index f34fe0c64693..f96a9dd83d0b 100644 --- a/packages/google-auth/CHANGELOG.rst +++ b/packages/google-auth/CHANGELOG.rst @@ -1,6 +1,14 @@ Changelog ========= +v0.2.0 +------ + +- Added gRPC support. (#67) +- Added Requests support. (#66) +- Added ``google.auth.credentials.with_scopes_if_required`` helper. (#65) +- Added private helper for oauth2client migration. (#70) + v0.1.0 ------ diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index d9d903ba3129..a0f5ec75d105 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -29,7 +29,7 @@ setup( name='google-auth', - version='0.1.0', + version='0.2.0', author='Google Cloud Platform', author_email='jonwayne+google-auth@google.com', description='Google Authentication Library', From 3d5087ff90814bce2aa32a273d7f6276d0ddd974 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Mon, 7 Nov 2016 16:41:42 -0800 Subject: [PATCH 060/966] Add support for the GCLOUD_PROJECT environment variable (#73) --- packages/google-auth/google/auth/_default.py | 4 +++- packages/google-auth/google/auth/environment_vars.py | 7 +++++++ packages/google-auth/tests/test__default.py | 9 +++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index 356780bf1d87..b6014e7440d5 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -254,7 +254,9 @@ def default(request=None): If no credentials were found, or if the credentials found were invalid. """ - explicit_project_id = os.environ.get(environment_vars.PROJECT) + explicit_project_id = os.environ.get( + environment_vars.PROJECT, + os.environ.get(environment_vars.LEGACY_PROJECT)) checkers = ( _get_explicit_environ_credentials, diff --git a/packages/google-auth/google/auth/environment_vars.py b/packages/google-auth/google/auth/environment_vars.py index 9785c346624e..b4ed2b28ae17 100644 --- a/packages/google-auth/google/auth/environment_vars.py +++ b/packages/google-auth/google/auth/environment_vars.py @@ -22,6 +22,13 @@ environment variable is also used by the Google Cloud Python Library. """ +LEGACY_PROJECT = 'GCLOUD_PROJECT' +"""Previously used environment variable defining the default project. + +This environment variable is used instead of the current one in some +situations (such as Google App Engine). +""" + CREDENTIALS = 'GOOGLE_APPLICATION_CREDENTIALS' """Environment variable defining the location of Google application default credentials.""" diff --git a/packages/google-auth/tests/test__default.py b/packages/google-auth/tests/test__default.py index e244b3de2b3e..c33db13671e0 100644 --- a/packages/google-auth/tests/test__default.py +++ b/packages/google-auth/tests/test__default.py @@ -266,6 +266,15 @@ def test_default_explict_project_id(get_mock, monkeypatch): mock.sentinel.credentials, 'explicit-env') +@mock.patch( + 'google.auth._default._get_explicit_environ_credentials', + return_value=(mock.sentinel.credentials, mock.sentinel.project_id)) +def test_default_explict_legacy_project_id(get_mock, monkeypatch): + monkeypatch.setenv(environment_vars.LEGACY_PROJECT, 'explicit-env') + assert _default.default() == ( + mock.sentinel.credentials, 'explicit-env') + + @mock.patch( 'google.auth._default._get_explicit_environ_credentials', return_value=(None, None)) From 0542db2d537f4b1c6b97835ad9c56192d9c0a7c8 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Mon, 7 Nov 2016 16:45:17 -0800 Subject: [PATCH 061/966] Add scope argument to default (#75) --- packages/google-auth/google/auth/_default.py | 8 +++++++- packages/google-auth/tests/test__default.py | 16 ++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index b6014e7440d5..f9829d44e914 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -28,6 +28,7 @@ from google.auth import environment_vars from google.auth import exceptions from google.auth.compute_engine import _metadata +import google.auth.credentials import google.auth.transport._http_client from google.oauth2 import service_account import google.oauth2.credentials @@ -185,7 +186,7 @@ def _get_gce_credentials(request=None): return None, None -def default(request=None): +def default(scopes=None, request=None): """Gets the default credentials for the current environment. `Application Default Credentials`_ provides an easy way to obtain @@ -238,6 +239,9 @@ def default(request=None): credentials, project_id = google.auth.default() Args: + scopes (Sequence[str]): The list of scopes for the credentials. If + specified, the credentials will automatically be scoped if + necessary. request (google.auth.transport.Request): An object used to make HTTP requests. This is used to detect whether the application is running on Compute Engine. If not specified, then it will @@ -267,6 +271,8 @@ def default(request=None): for checker in checkers: credentials, project_id = checker() if credentials is not None: + credentials = google.auth.credentials.with_scopes_if_required( + credentials, scopes) return credentials, explicit_project_id or project_id raise exceptions.DefaultCredentialsError(_HELP_MESSAGE) diff --git a/packages/google-auth/tests/test__default.py b/packages/google-auth/tests/test__default.py index c33db13671e0..bfb0c394d1ec 100644 --- a/packages/google-auth/tests/test__default.py +++ b/packages/google-auth/tests/test__default.py @@ -290,3 +290,19 @@ def test_default_explict_legacy_project_id(get_mock, monkeypatch): def test_default_fail(unused_gce, unused_gae, unused_sdk, unused_explicit): with pytest.raises(exceptions.DefaultCredentialsError): assert _default.default() + + +@mock.patch( + 'google.auth._default._get_explicit_environ_credentials', + return_value=(mock.sentinel.credentials, mock.sentinel.project_id)) +@mock.patch( + 'google.auth.credentials.with_scopes_if_required') +def test_default_scoped(with_scopes_mock, get_mock): + scopes = ['one', 'two'] + + credentials, project_id = _default.default(scopes=scopes) + + assert credentials == with_scopes_mock.return_value + assert project_id == mock.sentinel.project_id + with_scopes_mock.assert_called_once_with( + mock.sentinel.credentials, scopes) From ce0a5c21b9252ba69f149c120641fb0e1f3bb1a3 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Tue, 8 Nov 2016 09:30:30 -0800 Subject: [PATCH 062/966] Make service_account_email public, read-only (#76) --- packages/google-auth/google/auth/app_engine.py | 7 +++++++ .../google/auth/compute_engine/credentials.py | 9 +++++++++ .../google/oauth2/service_account.py | 5 +++++ .../system_tests/test_compute_engine.py | 2 +- .../system_tests/test_service_account.py | 2 +- .../tests/compute_engine/test_credentials.py | 4 +++- .../tests/oauth2/test_service_account.py | 8 ++++---- .../google-auth/tests/test__oauth2client.py | 6 +++--- packages/google-auth/tests/test_app_engine.py | 17 +++++++++++++++++ 9 files changed, 50 insertions(+), 10 deletions(-) diff --git a/packages/google-auth/google/auth/app_engine.py b/packages/google-auth/google/auth/app_engine.py index d20ddf64606a..566475e9f903 100644 --- a/packages/google-auth/google/auth/app_engine.py +++ b/packages/google-auth/google/auth/app_engine.py @@ -86,6 +86,13 @@ def refresh(self, request): self.token, self.expiry = token, expiry + @property + def service_account_email(self): + """The service account email.""" + if self._service_account_id is None: + self._service_account_id = app_identity.get_service_account_name() + return self._service_account_id + @property def requires_scopes(self): """Checks if the credentials requires scopes. diff --git a/packages/google-auth/google/auth/compute_engine/credentials.py b/packages/google-auth/google/auth/compute_engine/credentials.py index cd215c57093f..572995690624 100644 --- a/packages/google-auth/google/auth/compute_engine/credentials.py +++ b/packages/google-auth/google/auth/compute_engine/credentials.py @@ -92,6 +92,15 @@ def refresh(self, request): except exceptions.TransportError as exc: raise exceptions.RefreshError(exc) + @property + def service_account_email(self): + """The service account email. + + .. note: This is not guaranteed to be set until :meth`refresh` has been + called. + """ + return self._service_account_email + @property def requires_scopes(self): """False: Compute Engine credentials can not be scoped.""" diff --git a/packages/google-auth/google/oauth2/service_account.py b/packages/google-auth/google/oauth2/service_account.py index 615930102846..24c852b2a2eb 100644 --- a/packages/google-auth/google/oauth2/service_account.py +++ b/packages/google-auth/google/oauth2/service_account.py @@ -231,6 +231,11 @@ def to_jwt_credentials(self): issuer=self._service_account_email, subject=self._service_account_email) + @property + def service_account_email(self): + """The service account email.""" + return self._service_account_email + @property def requires_scopes(self): """Checks if the credentials requires scopes. diff --git a/packages/google-auth/system_tests/test_compute_engine.py b/packages/google-auth/system_tests/test_compute_engine.py index 2d6f42cc8413..e828cff17c0c 100644 --- a/packages/google-auth/system_tests/test_compute_engine.py +++ b/packages/google-auth/system_tests/test_compute_engine.py @@ -32,7 +32,7 @@ def test_refresh(http_request, token_info): credentials.refresh(http_request) assert credentials.token is not None - assert credentials._service_account_email is not None + assert credentials.service_account_email is not None info = token_info(credentials.token) info_scopes = _helpers.string_to_scopes(info['scope']) diff --git a/packages/google-auth/system_tests/test_service_account.py b/packages/google-auth/system_tests/test_service_account.py index 6b41ca225a97..aad1497e9bd3 100644 --- a/packages/google-auth/system_tests/test_service_account.py +++ b/packages/google-auth/system_tests/test_service_account.py @@ -39,7 +39,7 @@ def test_refresh_success(http_request, credentials, token_info): info = token_info(credentials.token) - assert info['email'] == credentials._service_account_email + assert info['email'] == credentials.service_account_email info_scopes = _helpers.string_to_scopes(info['scope']) assert set(info_scopes) == set([ 'https://www.googleapis.com/auth/userinfo.email', diff --git a/packages/google-auth/tests/compute_engine/test_credentials.py b/packages/google-auth/tests/compute_engine/test_credentials.py index daa61fecfba9..87f7472666a8 100644 --- a/packages/google-auth/tests/compute_engine/test_credentials.py +++ b/packages/google-auth/tests/compute_engine/test_credentials.py @@ -34,6 +34,8 @@ def test_default_state(self): assert not self.credentials.expired # Scopes aren't needed assert not self.credentials.requires_scopes + # Service account email hasn't been populated + assert self.credentials.service_account_email == 'default' @mock.patch( 'google.auth._helpers.utcnow', return_value=datetime.datetime.min) @@ -58,7 +60,7 @@ def test_refresh_success(self, get_mock, now_mock): datetime.datetime.min + datetime.timedelta(seconds=500)) # Check the credential info - assert (self.credentials._service_account_email == + assert (self.credentials.service_account_email == 'service-account@example.com') assert self.credentials._scopes == ['one', 'two'] diff --git a/packages/google-auth/tests/oauth2/test_service_account.py b/packages/google-auth/tests/oauth2/test_service_account.py index 01234b5aee6c..f07f79d818f3 100644 --- a/packages/google-auth/tests/oauth2/test_service_account.py +++ b/packages/google-auth/tests/oauth2/test_service_account.py @@ -63,7 +63,7 @@ def test_from_service_account_info(self): assert (credentials._signer.key_id == SERVICE_ACCOUNT_INFO['private_key_id']) - assert (credentials._service_account_email == + assert (credentials.service_account_email == SERVICE_ACCOUNT_INFO['client_email']) assert credentials._token_uri == SERVICE_ACCOUNT_INFO['token_uri'] @@ -77,8 +77,8 @@ def test_from_service_account_info_args(self): info, scopes=scopes, subject=subject, additional_claims=additional_claims) + assert credentials.service_account_email == info['client_email'] assert credentials._signer.key_id == info['private_key_id'] - assert credentials._service_account_email == info['client_email'] assert credentials._token_uri == info['token_uri'] assert credentials._scopes == scopes assert credentials._subject == subject @@ -90,8 +90,8 @@ def test_from_service_account_file(self): credentials = service_account.Credentials.from_service_account_file( SERVICE_ACCOUNT_JSON_FILE) + assert credentials.service_account_email == info['client_email'] assert credentials._signer.key_id == info['private_key_id'] - assert credentials._service_account_email == info['client_email'] assert credentials._token_uri == info['token_uri'] def test_from_service_account_file_args(self): @@ -104,8 +104,8 @@ def test_from_service_account_file_args(self): SERVICE_ACCOUNT_JSON_FILE, subject=subject, scopes=scopes, additional_claims=additional_claims) + assert credentials.service_account_email == info['client_email'] assert credentials._signer.key_id == info['private_key_id'] - assert credentials._service_account_email == info['client_email'] assert credentials._token_uri == info['token_uri'] assert credentials._scopes == scopes assert credentials._subject == subject diff --git a/packages/google-auth/tests/test__oauth2client.py b/packages/google-auth/tests/test__oauth2client.py index 9478406fa0ee..1d7a4a805f84 100644 --- a/packages/google-auth/tests/test__oauth2client.py +++ b/packages/google-auth/tests/test__oauth2client.py @@ -54,7 +54,7 @@ def test__convert_service_account_credentials(): new_credentials = _oauth2client._convert_service_account_credentials( old_credentials) - assert (new_credentials._service_account_email == + assert (new_credentials.service_account_email == old_credentials.service_account_email) assert new_credentials._signer.key_id == old_credentials._private_key_id assert new_credentials._token_uri == old_credentials.token_uri @@ -68,7 +68,7 @@ def test__convert_service_account_credentials_with_jwt(): new_credentials = _oauth2client._convert_service_account_credentials( old_credentials) - assert (new_credentials._service_account_email == + assert (new_credentials.service_account_email == old_credentials.service_account_email) assert new_credentials._signer.key_id == old_credentials._private_key_id assert new_credentials._token_uri == old_credentials.token_uri @@ -81,7 +81,7 @@ def test__convert_gce_app_assertion_credentials(): new_credentials = _oauth2client._convert_gce_app_assertion_credentials( old_credentials) - assert (new_credentials._service_account_email == + assert (new_credentials.service_account_email == old_credentials.service_account_email) diff --git a/packages/google-auth/tests/test_app_engine.py b/packages/google-auth/tests/test_app_engine.py index e1189ed493f0..94f528d1d429 100644 --- a/packages/google-auth/tests/test_app_engine.py +++ b/packages/google-auth/tests/test_app_engine.py @@ -70,6 +70,23 @@ def test_with_scopes(self, app_identity_mock): assert scoped_credentials.has_scopes(['email']) assert not scoped_credentials.requires_scopes + def test_service_account_email_implicit(self, app_identity_mock): + app_identity_mock.get_service_account_name.return_value = ( + mock.sentinel.service_account_email) + credentials = app_engine.Credentials() + + assert (credentials.service_account_email == + mock.sentinel.service_account_email) + assert app_identity_mock.get_service_account_name.called + + def test_service_account_email_explicit(self, app_identity_mock): + credentials = app_engine.Credentials( + service_account_id=mock.sentinel.service_account_email) + + assert (credentials.service_account_email == + mock.sentinel.service_account_email) + assert not app_identity_mock.get_service_account_name.called + @mock.patch( 'google.auth._helpers.utcnow', return_value=datetime.datetime.min) From 001bc57c708b73af0a9f32f6556f6185d050571b Mon Sep 17 00:00:00 2001 From: Danny Hermes Date: Tue, 8 Nov 2016 09:30:44 -0800 Subject: [PATCH 063/966] Fixing Tuple[...] type annotation in docstring. (#77) --- packages/google-auth/google/auth/jwt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/google/auth/jwt.py b/packages/google-auth/google/auth/jwt.py index 69575a127746..7abe4c2eea70 100644 --- a/packages/google-auth/google/auth/jwt.py +++ b/packages/google-auth/google/auth/jwt.py @@ -110,7 +110,7 @@ def _unverified_decode(token): token (Union[str, bytes]): The encoded JWT. Returns: - Tuple(str, str, str, str): header, payload, signed_section, and + Tuple[str, str, str, str]: header, payload, signed_section, and signature. Raises: @@ -406,7 +406,7 @@ def _make_jwt(self, audience=None): audience (str): Overrides the instance's current audience claim. Returns: - Tuple(bytes, datetime): The encoded JWT and the expiration. + Tuple[bytes, datetime]: The encoded JWT and the expiration. """ now = _helpers.utcnow() lifetime = datetime.timedelta(seconds=self._token_lifetime) From f7500318bfbea3be9417390f424126818f8c25d8 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Thu, 10 Nov 2016 10:44:30 -0800 Subject: [PATCH 064/966] Add basic user guide (#79) --- packages/google-auth/docs/index.rst | 66 ++++-- packages/google-auth/docs/user-guide.rst | 276 +++++++++++++++++++++++ 2 files changed, 329 insertions(+), 13 deletions(-) create mode 100644 packages/google-auth/docs/user-guide.rst diff --git a/packages/google-auth/docs/index.rst b/packages/google-auth/docs/index.rst index e7a17e3302aa..c0cbdbfab4c4 100644 --- a/packages/google-auth/docs/index.rst +++ b/packages/google-auth/docs/index.rst @@ -1,22 +1,62 @@ -Welcome to ``google-auth``'s documentation! -=========================================== - -Contents: +google-auth +=========== .. toctree:: + :hidden: :maxdepth: 2 - reference/modules + user-guide + Reference + +google-auth is the Google authentication library for Python. This library +provides the ability to authenticate to Google APIs using various methods. It +also provides integration with several HTTP libraries. + +- Support for Google :func:`Application Default Credentials `. +- Support for signing and verifying :mod:`JWTs `. +- Support for Google :mod:`Service Account credentials `. +- Support for :mod:`Google Compute Engine credentials `. +- Support for :mod:`Google App Engine standard credentials `. +- Support for various transports, including + :mod:`Requests `, + :mod:`urllib3 `, and + :mod:`gRPC `. + +Installing +---------- + +google-auth can be installed with `pip`_:: + + $ pip install --upgrade google-auth + +google-auth is open-source, so you can alternatively grab the source code from +`GitHub`_ and install from source. + +.. _pip: https://pip.pypa.io +.. _GitHub: https://github.com/GoogleCloudPlatform/google-auth-library-python + +Usage +----- + +The :doc:`user-guide` is the place to go to learn how to use the library and +accomplish common tasks. + +The :doc:`Module Reference ` documentation provides API-level documentation. + +License +------- -Reference documentation -======================= +google-auth is made available under the Apache License, Version 2.0. For more +details, see `LICENSE`_ -The :doc:`reference documentation ` details the complete API for :mod:`google.auth` and :mod:`google.oauth2`. +.. _LICENSE: + https://github.com/GoogleCloudPlatform/google-auth-library-python/LICENSE -Indices and tables -================== +Contributing +------------ -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` +We happily welcome contributions, please see our `contributing`_ documentation +for details. +.. _contributing: + https://github.com/GoogleCloudPlatform/google-auth-library-python/CONTRIBUTING.rst diff --git a/packages/google-auth/docs/user-guide.rst b/packages/google-auth/docs/user-guide.rst new file mode 100644 index 000000000000..3d91d08546f6 --- /dev/null +++ b/packages/google-auth/docs/user-guide.rst @@ -0,0 +1,276 @@ +User Guide +========== + +.. currentmodule:: google.auth + +Credentials and account types +----------------------------- + +:class:`~credentials.Credentials` are the means of identifying an application or +user to a service or API. Credentials can be obtained with two different types +of accounts: *service accounts* and *user accounts*. + +Credentials from service accounts identify a particular application. These types +of credentials are used in server-to-server use cases, such as accessing a +database. This library primarily focuses on service account credentials. + +Credentials from user accounts are obtained by asking the user to authorize +access to their data. These types of credentials are used in cases where your +application needs access to a user's data in another service, such as accessing +a user's documents in Google Drive. This library provides no support for +obtaining user credentials, but does provide limited support for using user +credentials. + +Obtaining credentials +--------------------- + +.. _application-default: + +Application default credentials ++++++++++++++++++++++++++++++++ + +`Google Application Default Credentials`_ abstracts authentication across the +different Google Cloud Platform hosting environments. When running on any Google +Cloud hosting environment or when running locally with the `Google Cloud SDK`_ +installed, :func:`default` can automatically determine the credentials from the +environment:: + + import google.auth + + credentials, project = google.auth.default() + +If your application requires specific scopes:: + + credentials, project = google.auth.default( + scopes=['https://www.googleapis.com/auth/cloud-platform']) + +.. _Google Application Default Credentials: + https://developers.google.com/identity/protocols/ + application-default-credentials +.. _Google Cloud SDK: https://cloud.google.com/sdk + + +Service account private key files ++++++++++++++++++++++++++++++++++ + +A service account private key file can be used to obtain credentials for a +service account. You can create a private key using the `Credentials page of the +Google Cloud Console`_. Once you have a private key you can either obtain +credentials one of two ways: + +1. Set the ``GOOGLE_APPLICATION_CREDENTIALS`` environment variable to the full + path to your service account private key file + + .. code-block:: bash + + $ export GOOGLE_APPLICATION_CREDENTIALS=/path/to/key.json + + Then, use :ref:`application default credentials `. + :func:`default` checks for the ``GOOGLE_APPLICATION_CREDENTIALS`` + environment variable before all other checks, so this will always use the + credentials you explicitly specify. + +2. Use :meth:`service_account.Credentials.from_service_account_file + `:: + + from google.oauth2 import service_account + + credentials = service_account.Credentials.from_service_account_file( + '/path/to/key.json') + + scoped_credentials = credentials.with_scopes( + ['https://www.googleapis.com/auth/cloud-platform']) + +.. warning:: Private keys must be kept secret. If you expose your private key it + is recommended to revoke it immediately from the Google Cloud Console. + +.. _Credentials page of the Google Cloud Console: + https://console.cloud.google.com/apis/credentials + +Compute Engine, Container Engine, and the App Engine flexible environment ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +Applications running on `Compute Engine`_, `Container Engine`_, or the `App +Engine flexible environment`_ can obtain credentials provided by `Compute +Engine service accounts`_. When running on these platforms you can obtain +credentials for the service account one of two ways: + +1. Use :ref:`application default credentials `. + :func:`default` will automatically detect if these credentials are available. + +2. Use :class:`compute_engine.Credentials`:: + + from google.auth import compute_engine + + credentials = compute_engine.Credentials() + +.. _Compute Engine: https://cloud.google.com/compute +.. _Container Engine: https://cloud.google.com/container-engine +.. _App Engine flexible environment: + https://cloud.google.com/appengine/docs/flexible/ +.. _Compute Engine service accounts: + https://cloud.google.com/compute/docs/access/service-accounts + +The App Engine standard environment ++++++++++++++++++++++++++++++++++++ + +Applications running on the `App Engine standard environment`_ can obtain +credentials provided by the `App Engine App Identity API`_. You can obtain +credentials one of two ways: + +1. Use :ref:`application default credentials `. + :func:`default` will automatically detect if these credentials are available. + +2. Use :class:`app_engine.Credentials`:: + + from google.auth import app_engine + + credentials = app_engine.Credentials() + +.. _App Engine standard environment: + https://cloud.google.com/appengine/docs/python +.. _App Engine App Identity API: + https://cloud.google.com/appengine/docs/python/appidentity/ + +User credentials +++++++++++++++++ + +User credentials are typically obtained via `OAuth 2.0`_. This library does not +provide any support for *obtaining* user credentials, however, you can use user +credentials with this library. You can use libraries such as `oauthlib`_ or +`oauth2client`_ to obtain the access token. After you have an access token, you +can create a :class:`google.oauth2.credentials.Credentials` instance:: + + import google.oauth2.credentials + + credentials = google.oauth2.credentials.Credentials( + 'access_token') + +If you obtain a refresh token, you can also specify the refresh token and token +URI to allow the credentials to be automatically refreshed:: + + credentials = google.oauth2.credentials.Credentials( + 'access_token', + refresh_token='refresh_token', + token_uri='token_uri', + client_id='client_id', + client_secret='client_secret') + +.. _OAuth 2.0: + https://developers.google.com/identity/protocols/OAuth2 +.. _oauthlib: + https://oauthlib.readthedocs.io/en/latest/ +.. _oauth2client: + https://oauth2client.readthedocs.org + +Making authenticated requests +----------------------------- + +Once you have credentials you can attach them to a *transport*. You can then +use this transport to make authenticated requests to APIs. google-auth supports +several different transports. Typically, it's up to your application or an +opinionated client library to decide which transport to use. + +Requests +++++++++ + +The recommended HTTP transport is :mod:`google.auth.transport.requests` which +uses the `Requests`_ library. To make authenticated requests using Requests +you use a custom `Session`_ object:: + + from google.auth.transport.requests import AuthorizedSession + + authed_session = AuthorizedSession(credentials) + + response = authed_session.get( + 'https://www.googleapis.com/storage/v1/b') + +.. _Requests: http://docs.python-requests.org/en/master/ +.. _Session: http://docs.python-requests.org/en/master/user/advanced/#session-objects + +urllib3 ++++++++ + +:mod:`urllib3` is the underlying HTTP library used by Requests and can also be +used with google-auth. urllib3's interface isn't as high-level as Requests but +it can be useful in situations where you need more control over how HTTP +requests are made. To make authenticated requests using urllib3 create an +instance of :class:`google.auth.transport.urllib3.AuthorizedHttp`:: + + from google.auth.transport.urllib3 import AuthorizedHttp + + authed_http = AuthorizedHttp(credentials) + + response = authed_http.request( + 'GET', 'https://www.googleapis.com/storage/v1/b') + +You can also construct your own :class:`urllib3.PoolManager` instance and pass +it to :class:`~google.auth.transport.urllib3.AuthorizedHttp`:: + + import urllib3 + + http = urllib3.PoolManager() + authed_http = AuthorizedHttp(credentials, http) + +gRPC +++++ + +`gRPC`_ is an RPC framework that uses `Protocol Buffers`_ over `HTTP 2.0`_. +google-auth can provide `Call Credentials`_ for gRPC. The easiest way to do +this is to use google-auth to create the gRPC channel:: + + import google.auth.transport.grpc + import google.auth.transport.requests + + http_request = google.auth.transport.requests.Request() + + channel = google.auth.transport.grpc.secure_authorized_channel( + credentials, 'pubsub.googleapis.com:443', http_request) + +.. note:: Even though gRPC is its own transport, you still need to use one of + the other HTTP transports with gRPC. The reason is that most credential + types need to make HTTP requests in order to refresh their access token. + The sample above uses the Requests transport, but any HTTP transport can + be used. Additionally, if you know that your credentials do not need to + make HTTP requests in order to refresh (as is the case with + :class:`jwt.Credentials`) then you can specify ``None``. + +Alternatively, you can create the channel yourself and use +:class:`google.auth.transport.grpc.AuthMetadataPlugin`:: + + import grpc + + metadata_plugin = AuthMetadataPlugin(credentials, http_request) + + # Create a set of grpc.CallCredentials using the metadata plugin. + google_auth_credentials = grpc.metadata_call_credentials( + metadata_plugin) + + # Create SSL channel credentials. + ssl_credentials = grpc.ssl_channel_credentials() + + # Combine the ssl credentials and the authorization credentials. + composite_credentials = grpc.composite_channel_credentials( + ssl_credentials, google_auth_credentials) + + channel = grpc.secure_channel( + 'pubsub.googleapis.com:443', composite_credentials) + +You can use this channel to make a gRPC stub that makes authenticated requests +to a gRPC service:: + + from google.pubsub.v1 import pubsub_pb2 + + pubsub = pubsub_pb2.PublisherStub(channel) + + response = pubsub.ListTopics( + pubsub_pb2.ListTopicsRequest(project='your-project')) + + +.. _gRPC: http://www.grpc.io/ +.. _Protocol Buffers: + https://developers.google.com/protocol-buffers/docs/overview +.. _HTTP 2.0: + http://www.grpc.io/docs/guides/wire.html +.. _Call Credentials: + http://www.grpc.io/docs/guides/auth.html From 163b420c99e8b0e7d53dd41d5595b23d4b4503aa Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Thu, 10 Nov 2016 12:07:51 -0800 Subject: [PATCH 065/966] Swap target, request for request, target in secure_authorized_channel's argument order. (#81) --- packages/google-auth/docs/user-guide.rst | 2 +- packages/google-auth/google/auth/transport/grpc.py | 4 ++-- packages/google-auth/system_tests/test_grpc.py | 2 +- packages/google-auth/tests/transport/test_grpc.py | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/google-auth/docs/user-guide.rst b/packages/google-auth/docs/user-guide.rst index 3d91d08546f6..0e7f90f0fac8 100644 --- a/packages/google-auth/docs/user-guide.rst +++ b/packages/google-auth/docs/user-guide.rst @@ -225,7 +225,7 @@ this is to use google-auth to create the gRPC channel:: http_request = google.auth.transport.requests.Request() channel = google.auth.transport.grpc.secure_authorized_channel( - credentials, 'pubsub.googleapis.com:443', http_request) + credentials, http_request, 'pubsub.googleapis.com:443') .. note:: Even though gRPC is its own transport, you still need to use one of the other HTTP transports with gRPC. The reason is that most credential diff --git a/packages/google-auth/google/auth/transport/grpc.py b/packages/google-auth/google/auth/transport/grpc.py index dfb288304c6e..1e02b383049c 100644 --- a/packages/google-auth/google/auth/transport/grpc.py +++ b/packages/google-auth/google/auth/transport/grpc.py @@ -62,7 +62,7 @@ def __call__(self, context, callback): def secure_authorized_channel( - credentials, target, request, ssl_credentials=None): + credentials, request, target, ssl_credentials=None): """Creates a secure authorized gRPC channel. This creates a channel with SSL and :class:`AuthMetadataPlugin`. This @@ -91,11 +91,11 @@ def secure_authorized_channel( Args: credentials (google.auth.credentials.Credentials): The credentials to add to requests. - target (str): The host and port of the service. request (google.auth.transport.Request): A HTTP transport request object used to refresh credentials as needed. Even though gRPC is a separate transport, there's no way to refresh the credentials without using a standard http transport. + target (str): The host and port of the service. ssl_credentials (grpc.ChannelCredentials): Optional SSL channel credentials. This can be used to specify different certificates. diff --git a/packages/google-auth/system_tests/test_grpc.py b/packages/google-auth/system_tests/test_grpc.py index 0db9f1dd5642..7d436c563877 100644 --- a/packages/google-auth/system_tests/test_grpc.py +++ b/packages/google-auth/system_tests/test_grpc.py @@ -28,7 +28,7 @@ def test_grpc_request(http_request): publisher_api.PublisherApi.DEFAULT_SERVICE_PORT) channel = google.auth.transport.grpc.secure_authorized_channel( - credentials, target, http_request) + credentials, http_request, target) # Create a pub/sub client. client = publisher_api.PublisherApi(channel=channel) diff --git a/packages/google-auth/tests/transport/test_grpc.py b/packages/google-auth/tests/transport/test_grpc.py index 2b214a725610..5faa80781778 100644 --- a/packages/google-auth/tests/transport/test_grpc.py +++ b/packages/google-auth/tests/transport/test_grpc.py @@ -82,7 +82,7 @@ def test_secure_authorized_channel( target = 'example.com:80' channel = google.auth.transport.grpc.secure_authorized_channel( - credentials, target, request) + credentials, request, target) # Check the auth plugin construction. auth_plugin = metadata_call_credentials.call_args[0][0] @@ -118,7 +118,7 @@ def test_secure_authorized_channel_explicit_ssl( ssl_credentials = mock.Mock() google.auth.transport.grpc.secure_authorized_channel( - credentials, target, request, ssl_credentials=ssl_credentials) + credentials, request, target, ssl_credentials=ssl_credentials) # Check the ssl channel call. assert not ssl_channel_credentials.called From bf177c30e1a6d32197713a55d6422dc410308e67 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Thu, 10 Nov 2016 12:42:50 -0800 Subject: [PATCH 066/966] Fix doc warnings --- packages/google-auth/google/auth/credentials.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/google-auth/google/auth/credentials.py b/packages/google-auth/google/auth/credentials.py index 59d4ffea99e8..3dd80e4b0920 100644 --- a/packages/google-auth/google/auth/credentials.py +++ b/packages/google-auth/google/auth/credentials.py @@ -197,12 +197,14 @@ def with_scopes_if_required(credentials, scopes): as-is. Args: - credentials (Credentials): The credentials to scope if necessary. + credentials (~google.auth.credentials.Credentials): The credentials to + scope if necessary. scopes (Sequence[str]): The list of scopes to use. Returns: - Credentials: Either a new set of scoped credentials, or the passed in - credentials instance if no scoping was required. + ~google.auth.credentials.Credentials: Either a new set of scoped + credentials, or the passed in credentials instance if no scoping + was required. """ if isinstance(credentials, Scoped) and credentials.requires_scopes: return credentials.with_scopes(scopes) From c5c572b9813204a77ce5ef7ffddb0d72e1e10e03 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Thu, 10 Nov 2016 12:53:55 -0800 Subject: [PATCH 067/966] Use autospec where appropriate --- .../tests/compute_engine/test_credentials.py | 4 +- .../tests/oauth2/test_credentials.py | 2 +- .../tests/oauth2/test_service_account.py | 4 +- packages/google-auth/tests/test__cloud_sdk.py | 3 +- packages/google-auth/tests/test__default.py | 54 ++++++++++++------- packages/google-auth/tests/test_crypt.py | 19 ++++--- .../google-auth/tests/transport/test_grpc.py | 16 +++--- 7 files changed, 62 insertions(+), 40 deletions(-) diff --git a/packages/google-auth/tests/compute_engine/test_credentials.py b/packages/google-auth/tests/compute_engine/test_credentials.py index 87f7472666a8..568aec72e975 100644 --- a/packages/google-auth/tests/compute_engine/test_credentials.py +++ b/packages/google-auth/tests/compute_engine/test_credentials.py @@ -68,7 +68,7 @@ def test_refresh_success(self, get_mock, now_mock): # expired) assert self.credentials.valid - @mock.patch('google.auth.compute_engine._metadata.get') + @mock.patch('google.auth.compute_engine._metadata.get', autospec=True) def test_refresh_error(self, get_mock): get_mock.side_effect = exceptions.TransportError('http error') @@ -77,7 +77,7 @@ def test_refresh_error(self, get_mock): assert excinfo.match(r'http error') - @mock.patch('google.auth.compute_engine._metadata.get') + @mock.patch('google.auth.compute_engine._metadata.get', autospec=True) def test_before_request_refreshes(self, get_mock): get_mock.side_effect = [{ # First request is for sevice account info. diff --git a/packages/google-auth/tests/oauth2/test_credentials.py b/packages/google-auth/tests/oauth2/test_credentials.py index 5c76cb202905..b53b188d9ccc 100644 --- a/packages/google-auth/tests/oauth2/test_credentials.py +++ b/packages/google-auth/tests/oauth2/test_credentials.py @@ -46,7 +46,7 @@ def test_create_scoped(self): with pytest.raises(NotImplementedError): self.credentials.with_scopes(['email']) - @mock.patch('google.oauth2._client.refresh_grant') + @mock.patch('google.oauth2._client.refresh_grant', autospec=True) @mock.patch( 'google.auth._helpers.utcnow', return_value=datetime.datetime.min) def test_refresh_success(self, now_mock, refresh_grant_mock): diff --git a/packages/google-auth/tests/oauth2/test_service_account.py b/packages/google-auth/tests/oauth2/test_service_account.py index f07f79d818f3..678e6a3a64f8 100644 --- a/packages/google-auth/tests/oauth2/test_service_account.py +++ b/packages/google-auth/tests/oauth2/test_service_account.py @@ -159,7 +159,7 @@ def test__make_authorization_grant_assertion_subject(self): payload = jwt.decode(token, PUBLIC_CERT_BYTES) assert payload['sub'] == subject - @mock.patch('google.oauth2._client.jwt_grant') + @mock.patch('google.oauth2._client.jwt_grant', autospec=True) def test_refresh_success(self, jwt_grant_mock): token = 'token' jwt_grant_mock.return_value = ( @@ -185,7 +185,7 @@ def test_refresh_success(self, jwt_grant_mock): # expired) assert self.credentials.valid - @mock.patch('google.oauth2._client.jwt_grant') + @mock.patch('google.oauth2._client.jwt_grant', autospec=True) def test_before_request_refreshes(self, jwt_grant_mock): token = 'token' jwt_grant_mock.return_value = ( diff --git a/packages/google-auth/tests/test__cloud_sdk.py b/packages/google-auth/tests/test__cloud_sdk.py index 86f69a15fff9..ba72072d43c7 100644 --- a/packages/google-auth/tests/test__cloud_sdk.py +++ b/packages/google-auth/tests/test__cloud_sdk.py @@ -38,7 +38,8 @@ with open(os.path.join(DATA_DIR, 'cloud_sdk.cfg')) as fh: CLOUD_SDK_CONFIG_DATA = fh.read() -CONFIG_PATH_PATCH = mock.patch('google.auth._cloud_sdk.get_config_path') +CONFIG_PATH_PATCH = mock.patch( + 'google.auth._cloud_sdk.get_config_path', autospec=True) @pytest.fixture diff --git a/packages/google-auth/tests/test__default.py b/packages/google-auth/tests/test__default.py index bfb0c394d1ec..a317e0ac19e0 100644 --- a/packages/google-auth/tests/test__default.py +++ b/packages/google-auth/tests/test__default.py @@ -43,7 +43,7 @@ LOAD_FILE_PATCH = mock.patch( 'google.auth._default._load_credentials_from_file', return_value=( - mock.sentinel.credentials, mock.sentinel.project_id)) + mock.sentinel.credentials, mock.sentinel.project_id), autospec=True) def test__load_credentials_from_file_invalid_json(tmpdir): @@ -131,7 +131,9 @@ def test__get_explicit_environ_credentials_no_project_id( @LOAD_FILE_PATCH -@mock.patch('google.auth._cloud_sdk.get_application_default_credentials_path') +@mock.patch( + 'google.auth._cloud_sdk.get_application_default_credentials_path', + autospec=True) def test__get_gcloud_sdk_credentials( mock_get_adc_path, mock_load): mock_get_adc_path.return_value = SERVICE_ACCOUNT_FILE @@ -143,7 +145,9 @@ def test__get_gcloud_sdk_credentials( mock_load.assert_called_with(SERVICE_ACCOUNT_FILE) -@mock.patch('google.auth._cloud_sdk.get_application_default_credentials_path') +@mock.patch( + 'google.auth._cloud_sdk.get_application_default_credentials_path', + autospec=True) def test__get_gcloud_sdk_credentials_non_existent(mock_get_adc_path, tmpdir): non_existent = tmpdir.join('non-existent') mock_get_adc_path.return_value = str(non_existent) @@ -156,7 +160,7 @@ def test__get_gcloud_sdk_credentials_non_existent(mock_get_adc_path, tmpdir): @mock.patch( 'google.auth._cloud_sdk.get_project_id', - return_value=mock.sentinel.project_id) + return_value=mock.sentinel.project_id, autospec=True) @mock.patch('os.path.isfile', return_value=True) @LOAD_FILE_PATCH def test__get_gcloud_sdk_credentials_project_id( @@ -174,7 +178,7 @@ def test__get_gcloud_sdk_credentials_project_id( @mock.patch( 'google.auth._cloud_sdk.get_project_id', - return_value=None) + return_value=None, autospec=True) @mock.patch('os.path.isfile', return_value=True) @LOAD_FILE_PATCH def test__get_gcloud_sdk_credentials_no_project_id( @@ -212,10 +216,11 @@ def test__get_gae_credentials_no_apis(): @mock.patch( - 'google.auth.compute_engine._metadata.ping', return_value=True) + 'google.auth.compute_engine._metadata.ping', return_value=True, + autospec=True) @mock.patch( 'google.auth.compute_engine._metadata.get_project_id', - return_value='example-project') + return_value='example-project', autospec=True) def test__get_gce_credentials(get_mock, ping_mock): credentials, project_id = _default._get_gce_credentials() @@ -223,7 +228,9 @@ def test__get_gce_credentials(get_mock, ping_mock): assert project_id == 'example-project' -@mock.patch('google.auth.compute_engine._metadata.ping', return_value=False) +@mock.patch( + 'google.auth.compute_engine._metadata.ping', return_value=False, + autospec=True) def test__get_gce_credentials_no_ping(ping_mock): credentials, project_id = _default._get_gce_credentials() @@ -232,10 +239,11 @@ def test__get_gce_credentials_no_ping(ping_mock): @mock.patch( - 'google.auth.compute_engine._metadata.ping', return_value=True) + 'google.auth.compute_engine._metadata.ping', return_value=True, + autospec=True) @mock.patch( 'google.auth.compute_engine._metadata.get_project_id', - side_effect=exceptions.TransportError()) + side_effect=exceptions.TransportError(), autospec=True) def test__get_gce_credentials_no_project_id(get_mock, ping_mock): credentials, project_id = _default._get_gce_credentials() @@ -243,7 +251,9 @@ def test__get_gce_credentials_no_project_id(get_mock, ping_mock): assert project_id is None -@mock.patch('google.auth.compute_engine._metadata.ping', return_value=False) +@mock.patch( + 'google.auth.compute_engine._metadata.ping', return_value=False, + autospec=True) def test__get_gce_credentials_explicit_request(ping_mock): _default._get_gce_credentials(mock.sentinel.request) ping_mock.assert_called_with(request=mock.sentinel.request) @@ -251,7 +261,8 @@ def test__get_gce_credentials_explicit_request(ping_mock): @mock.patch( 'google.auth._default._get_explicit_environ_credentials', - return_value=(mock.sentinel.credentials, mock.sentinel.project_id)) + return_value=(mock.sentinel.credentials, mock.sentinel.project_id), + autospec=True) def test_default_early_out(get_mock): assert _default.default() == ( mock.sentinel.credentials, mock.sentinel.project_id) @@ -259,7 +270,8 @@ def test_default_early_out(get_mock): @mock.patch( 'google.auth._default._get_explicit_environ_credentials', - return_value=(mock.sentinel.credentials, mock.sentinel.project_id)) + return_value=(mock.sentinel.credentials, mock.sentinel.project_id), + autospec=True) def test_default_explict_project_id(get_mock, monkeypatch): monkeypatch.setenv(environment_vars.PROJECT, 'explicit-env') assert _default.default() == ( @@ -268,7 +280,8 @@ def test_default_explict_project_id(get_mock, monkeypatch): @mock.patch( 'google.auth._default._get_explicit_environ_credentials', - return_value=(mock.sentinel.credentials, mock.sentinel.project_id)) + return_value=(mock.sentinel.credentials, mock.sentinel.project_id), + autospec=True) def test_default_explict_legacy_project_id(get_mock, monkeypatch): monkeypatch.setenv(environment_vars.LEGACY_PROJECT, 'explicit-env') assert _default.default() == ( @@ -277,16 +290,16 @@ def test_default_explict_legacy_project_id(get_mock, monkeypatch): @mock.patch( 'google.auth._default._get_explicit_environ_credentials', - return_value=(None, None)) + return_value=(None, None), autospec=True) @mock.patch( 'google.auth._default._get_gcloud_sdk_credentials', - return_value=(None, None)) + return_value=(None, None), autospec=True) @mock.patch( 'google.auth._default._get_gae_credentials', - return_value=(None, None)) + return_value=(None, None), autospec=True) @mock.patch( 'google.auth._default._get_gce_credentials', - return_value=(None, None)) + return_value=(None, None), autospec=True) def test_default_fail(unused_gce, unused_gae, unused_sdk, unused_explicit): with pytest.raises(exceptions.DefaultCredentialsError): assert _default.default() @@ -294,9 +307,10 @@ def test_default_fail(unused_gce, unused_gae, unused_sdk, unused_explicit): @mock.patch( 'google.auth._default._get_explicit_environ_credentials', - return_value=(mock.sentinel.credentials, mock.sentinel.project_id)) + return_value=(mock.sentinel.credentials, mock.sentinel.project_id), + autospec=True) @mock.patch( - 'google.auth.credentials.with_scopes_if_required') + 'google.auth.credentials.with_scopes_if_required', autospec=True) def test_default_scoped(with_scopes_mock, get_mock): scopes = ['one', 'two'] diff --git a/packages/google-auth/tests/test_crypt.py b/packages/google-auth/tests/test_crypt.py index 90692166dcaf..33105e414fc0 100644 --- a/packages/google-auth/tests/test_crypt.py +++ b/packages/google-auth/tests/test_crypt.py @@ -131,8 +131,11 @@ def test_from_string_pub_cert_unicode(self): def test_from_string_pub_cert_failure(self): cert_bytes = PUBLIC_CERT_BYTES true_der = rsa.pem.load_pem(cert_bytes, 'CERTIFICATE') - with mock.patch('rsa.pem.load_pem', - return_value=true_der + b'extra') as load_pem: + load_pem_patch = mock.patch( + 'rsa.pem.load_pem', return_value=true_der + b'extra', + autospec=True) + + with load_pem_patch as load_pem: with pytest.raises(ValueError): crypt.Verifier.from_string(cert_bytes) load_pem.assert_called_once_with(cert_bytes, 'CERTIFICATE') @@ -161,13 +164,17 @@ def test_from_string_pkcs8_extra_bytes(self): six.StringIO(_helpers.from_bytes(key_bytes)), crypt._PKCS8_MARKER) - with mock.patch('pyasn1.codec.der.decoder.decode') as mock_decode: - key_info, remaining = None, 'extra' - mock_decode.return_value = (key_info, remaining) + key_info, remaining = None, 'extra' + decode_patch = mock.patch( + 'pyasn1.codec.der.decoder.decode', + return_value=(key_info, remaining), + autospec=True) + + with decode_patch as decode: with pytest.raises(ValueError): crypt.Signer.from_string(key_bytes) # Verify mock was called. - mock_decode.assert_called_once_with( + decode.assert_called_once_with( pem_bytes, asn1Spec=crypt._PKCS8_SPEC) def test_from_string_pkcs8_unicode(self): diff --git a/packages/google-auth/tests/transport/test_grpc.py b/packages/google-auth/tests/transport/test_grpc.py index 5faa80781778..84e4b56598a8 100644 --- a/packages/google-auth/tests/transport/test_grpc.py +++ b/packages/google-auth/tests/transport/test_grpc.py @@ -70,10 +70,10 @@ def test_call_refresh(self): [('authorization', 'Bearer {}'.format(credentials.token))], None) -@mock.patch('grpc.composite_channel_credentials') -@mock.patch('grpc.metadata_call_credentials') -@mock.patch('grpc.ssl_channel_credentials') -@mock.patch('grpc.secure_channel') +@mock.patch('grpc.composite_channel_credentials', autospec=True) +@mock.patch('grpc.metadata_call_credentials', autospec=True) +@mock.patch('grpc.ssl_channel_credentials', autospec=True) +@mock.patch('grpc.secure_channel', autospec=True) def test_secure_authorized_channel( secure_channel, ssl_channel_credentials, metadata_call_credentials, composite_channel_credentials): @@ -105,10 +105,10 @@ def test_secure_authorized_channel( assert channel == secure_channel.return_value -@mock.patch('grpc.composite_channel_credentials') -@mock.patch('grpc.metadata_call_credentials') -@mock.patch('grpc.ssl_channel_credentials') -@mock.patch('grpc.secure_channel') +@mock.patch('grpc.composite_channel_credentials', autospec=True) +@mock.patch('grpc.metadata_call_credentials', autospec=True) +@mock.patch('grpc.ssl_channel_credentials', autospec=True) +@mock.patch('grpc.secure_channel', autospec=True) def test_secure_authorized_channel_explicit_ssl( secure_channel, ssl_channel_credentials, metadata_call_credentials, composite_channel_credentials): From e354485bb27143200ed7f97b37f567be2d9aa340 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Thu, 10 Nov 2016 15:00:29 -0800 Subject: [PATCH 068/966] Fixing lint --- packages/google-auth/google/auth/credentials.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/google/auth/credentials.py b/packages/google-auth/google/auth/credentials.py index 3dd80e4b0920..470f1a4215b9 100644 --- a/packages/google-auth/google/auth/credentials.py +++ b/packages/google-auth/google/auth/credentials.py @@ -197,12 +197,12 @@ def with_scopes_if_required(credentials, scopes): as-is. Args: - credentials (~google.auth.credentials.Credentials): The credentials to + credentials (google.auth.credentials.Credentials): The credentials to scope if necessary. scopes (Sequence[str]): The list of scopes to use. Returns: - ~google.auth.credentials.Credentials: Either a new set of scoped + google.auth.credentials.Credentials: Either a new set of scoped credentials, or the passed in credentials instance if no scoping was required. """ From da9eb64b1b3137117b0e950d2227d675635838c5 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Thu, 10 Nov 2016 15:11:48 -0800 Subject: [PATCH 069/966] Add ID token verification helpers. (#82) --- .../docs/reference/google.oauth2.id_token.rst | 7 ++ .../docs/reference/google.oauth2.rst | 1 + .../google-auth/google/oauth2/id_token.py | 115 ++++++++++++++++++ .../google-auth/tests/oauth2/test_id_token.py | 112 +++++++++++++++++ 4 files changed, 235 insertions(+) create mode 100644 packages/google-auth/docs/reference/google.oauth2.id_token.rst create mode 100644 packages/google-auth/google/oauth2/id_token.py create mode 100644 packages/google-auth/tests/oauth2/test_id_token.py diff --git a/packages/google-auth/docs/reference/google.oauth2.id_token.rst b/packages/google-auth/docs/reference/google.oauth2.id_token.rst new file mode 100644 index 000000000000..db38b6085516 --- /dev/null +++ b/packages/google-auth/docs/reference/google.oauth2.id_token.rst @@ -0,0 +1,7 @@ +google.oauth2.id_token module +============================= + +.. automodule:: google.oauth2.id_token + :members: + :inherited-members: + :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.oauth2.rst b/packages/google-auth/docs/reference/google.oauth2.rst index 9126549b68c2..adb9403efe28 100644 --- a/packages/google-auth/docs/reference/google.oauth2.rst +++ b/packages/google-auth/docs/reference/google.oauth2.rst @@ -12,5 +12,6 @@ Submodules .. toctree:: google.oauth2.credentials + google.oauth2.id_token google.oauth2.service_account diff --git a/packages/google-auth/google/oauth2/id_token.py b/packages/google-auth/google/oauth2/id_token.py new file mode 100644 index 000000000000..968f27142730 --- /dev/null +++ b/packages/google-auth/google/oauth2/id_token.py @@ -0,0 +1,115 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google ID Token helpers.""" + +import json + +from six.moves import http_client + +from google.auth import exceptions +from google.auth import jwt + +# The URL that provides public certificates for verifying ID tokens issued +# by Google's OAuth 2.0 authorization server. +_GOOGLE_OAUTH2_CERTS_URL = 'https://www.googleapis.com/oauth2/v1/certs' + +# The URL that provides public certificates for verifying ID tokens issued +# by Firebase and the Google APIs infrastructure +_GOOGLE_APIS_CERTS_URL = ( + 'https://www.googleapis.com/robot/v1/metadata/x509' + '/securetoken@system.gserviceaccount.com') + + +def _fetch_certs(request, certs_url): + """Fetches certificates. + + Google-style cerificate endpoints return JSON in the format of + ``{'key id': 'x509 certificate'}``. + + Args: + request (google.auth.transport.Request): The object used to make + HTTP requests. + certs_url (str): The certificate endpoint URL. + + Returns: + Mapping[str, str]: A mapping of public key ID to x.509 certificate + data. + """ + response = request('GET', certs_url) + + if response.status != http_client.OK: + raise exceptions.TransportError( + 'Could not fetch certificates at {}'.format(certs_url)) + + return json.loads(response.data.decode('utf-8')) + + +def verify_token(id_token, request, audience=None, + certs_url=_GOOGLE_OAUTH2_CERTS_URL): + """Verifies an ID token and returns the decoded token. + + Args: + id_token (Union[str, bytes]): The encoded token. + request (google.auth.transport.Request): The object used to make + HTTP requests. + audience (str): The audience that this token is intended for. If None + then the audience is not verified. + certs_url (str): The URL that specifies the certificates to use to + verify the token. This URL should return JSON in the format of + ``{'key id': 'x509 certificate'}``. + + Returns: + Mapping[str, Any]: The decoded token. + """ + certs = _fetch_certs(request, certs_url) + + return jwt.decode(id_token, certs=certs, audience=audience) + + +def verify_oauth2_token(id_token, request, audience=None): + """Verifies an ID Token issued by Google's OAuth 2.0 authorization server. + + Args: + id_token (Union[str, bytes]): The encoded token. + request (google.auth.transport.Request): The object used to make + HTTP requests. + audience (str): The audience that this token is intended for. This is + typically your application's OAuth 2.0 client ID. If None then the + audience is not verified. + + Returns: + Mapping[str, Any]: The decoded token. + """ + return verify_token( + id_token, request, audience=audience, + certs_url=_GOOGLE_OAUTH2_CERTS_URL) + + +def verify_firebase_token(id_token, request, audience=None): + """Verifies an ID Token issued by Firebase Authentication. + + Args: + id_token (Union[str, bytes]): The encoded token. + request (google.auth.transport.Request): The object used to make + HTTP requests. + audience (str): The audience that this token is intended for. This is + typically your Firebase application ID. If None then the audience + is not verified. + + Returns: + Mapping[str, Any]: The decoded token. + """ + return verify_token( + id_token, request, audience=audience, certs_url=_GOOGLE_APIS_CERTS_URL) diff --git a/packages/google-auth/tests/oauth2/test_id_token.py b/packages/google-auth/tests/oauth2/test_id_token.py new file mode 100644 index 000000000000..f3da6632d496 --- /dev/null +++ b/packages/google-auth/tests/oauth2/test_id_token.py @@ -0,0 +1,112 @@ +# Copyright 2014 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json + +import mock +import pytest + +from google.auth import exceptions +from google.oauth2 import id_token + + +def make_request(status, data=None): + response = mock.Mock() + response.status = status + + if data is not None: + response.data = json.dumps(data).encode('utf-8') + + return mock.Mock(return_value=response) + + +def test__fetch_certs_success(): + certs = {'1': 'cert'} + request = make_request(200, certs) + + returned_certs = id_token._fetch_certs(request, mock.sentinel.cert_url) + + request.assert_called_once_with('GET', mock.sentinel.cert_url) + assert returned_certs == certs + + +def test__fetch_certs_failure(): + request = make_request(404) + + with pytest.raises(exceptions.TransportError): + id_token._fetch_certs(request, mock.sentinel.cert_url) + + request.assert_called_once_with('GET', mock.sentinel.cert_url) + + +@mock.patch('google.auth.jwt.decode', autospec=True) +@mock.patch('google.oauth2.id_token._fetch_certs', autospec=True) +def test_verify_token(_fetch_certs, decode): + result = id_token.verify_token(mock.sentinel.token, mock.sentinel.request) + + assert result == decode.return_value + _fetch_certs.assert_called_once_with( + mock.sentinel.request, id_token._GOOGLE_OAUTH2_CERTS_URL) + decode.assert_called_once_with( + mock.sentinel.token, + certs=_fetch_certs.return_value, + audience=None) + + +@mock.patch('google.auth.jwt.decode', autospec=True) +@mock.patch('google.oauth2.id_token._fetch_certs', autospec=True) +def test_verify_token_args(_fetch_certs, decode): + result = id_token.verify_token( + mock.sentinel.token, + mock.sentinel.request, + audience=mock.sentinel.audience, + certs_url=mock.sentinel.certs_url) + + assert result == decode.return_value + _fetch_certs.assert_called_once_with( + mock.sentinel.request, mock.sentinel.certs_url) + decode.assert_called_once_with( + mock.sentinel.token, + certs=_fetch_certs.return_value, + audience=mock.sentinel.audience) + + +@mock.patch('google.oauth2.id_token.verify_token', autospec=True) +def test_verify_oauth2_token(verify_token): + result = id_token.verify_oauth2_token( + mock.sentinel.token, + mock.sentinel.request, + audience=mock.sentinel.audience) + + assert result == verify_token.return_value + verify_token.assert_called_once_with( + mock.sentinel.token, + mock.sentinel.request, + audience=mock.sentinel.audience, + certs_url=id_token._GOOGLE_OAUTH2_CERTS_URL) + + +@mock.patch('google.oauth2.id_token.verify_token', autospec=True) +def test_verify_firebase_token(verify_token): + result = id_token.verify_firebase_token( + mock.sentinel.token, + mock.sentinel.request, + audience=mock.sentinel.audience) + + assert result == verify_token.return_value + verify_token.assert_called_once_with( + mock.sentinel.token, + mock.sentinel.request, + audience=mock.sentinel.audience, + certs_url=id_token._GOOGLE_APIS_CERTS_URL) From 304ed9f32e0ac9ba2e1b4944ab070dd1aebfe71f Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Fri, 11 Nov 2016 10:27:42 -0800 Subject: [PATCH 070/966] Release v0.3.0 (#83) --- packages/google-auth/.travis.yml | 9 +++++---- packages/google-auth/CHANGELOG.rst | 10 ++++++++++ packages/google-auth/setup.py | 2 +- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/packages/google-auth/.travis.yml b/packages/google-auth/.travis.yml index 642101b6d9dd..9fb1c171dab1 100644 --- a/packages/google-auth/.travis.yml +++ b/packages/google-auth/.travis.yml @@ -30,13 +30,14 @@ script: deploy: provider: pypi user: google_opensource - distributions: sdist bdist_wheel password: - secure: Pbj0eXpOrBvRcBAevX86T1gS54d19yRUv/3e1DEj4+3KKiOP6JH5OLXNCUrd+rqZrdg/3bC1PLtIUWh3oecy7Ngi9ya6kHk3QCTQ6fnl0CfvUlCMPM+Y6tGwA4aSNl2qZQnZSubaMiMUEg92UIPclfrq92fnQvCML+pqedh8BCZgEaPnOJKOtS8FRUrXRZHZ/fKEZpw71gXoWCYj5gzZ1HSoXQkfnqKukAes9mqMax84MKy0NPY7FHxejfH3O5BqJ+InSKG2F6EVZ/9uKQ4NxnUjMcNTU0YBVNkEhjagmSE+7+Xs3EiZbcZMseZXbVTeZSBLBX9+25eW2naMIHlzh7atelYktGnlwjlWi/lf8V8JJ4oY9K8Z2/Lau/5Cdtlq9mmyeKEJt9ltRI8Ll0619EKiJtc/Racg0F9qRR+C+yliPsIEZyopnm8bQVIfDr7RmSYzwOkP9eM+YRerD3cvGprLMkq+t/56zM2YUXxGKoqAve1Cu4oj91TjK4DxJ9Lm4fO9fosLhb8I719mBtZifDFQm1TLgOdyHJ5/+APqaVrWnKrbpCPxt+sYyqXVH1dXEULIDNqm3AwgLWjqAA9dxEV5Uats1LDoM6LaT1CgKc/O/FAQgTTJHPV2cWCVntdXGsZDCs/G9IcxBoXzBqz8TdQ5BEyOS2A8Ws/yjVk7tpw= + secure: jJFzvoeepJmeSN9MELiTyXEwV8bY4xtmXniL9KSdLewVLVV6c9cBoBQDs37PdeaM7O/Zg/EnI29mKo+VuXDVOSNjyuPnBej9ehD6g1s57RlnYippInxsi5MLYPwpHgQGkPJ3FkLeG98xSeoDABgULqv5FNvSJSVe/mLLWi1PtciW/MGoui1ZcxrOzoLdCtVnaR3d5WDjbIgtNqG9TqVo3R57PrQXtK8TmQ8yaH5CBs6bAJpHt71gZEmvXYL3LSPsLcs/Dbs2FAc8xwU8qyDPyYSxPhKrJPJlrTw5RqEMZ5gvRh3fTPsa6WAtTWFfQP1ZZXJp48QUoIu7ziXBIir24SUIMYTQxH4WgF8O4o6KRgkELHswk5KD1rSsycXJ9Di87ch2ba4qG8Qt8sDTJqzTNyhzSpF0HRgZF+/Tq/+c2alf0wGGoBnNk6aNWDcgFE178A0RTmHHgtqjCLB8igTR+pouZYn5RRFVMOKbG4r+8PV5kf1cQ+U68x63aJkq1DTCpmeDpfVQVB4bimQt+Pm9VICQbnby8RojAXNy5MGJ8bthb7prGCWZxkJJS31RBG/NI7Sz09jlLx5ObWMIg1LV2JRQEmPm5JTDhBviY8WDA2ifqA4d/uNFejCn6RpHH/4fZD0QZWroP64+kYI4y3106uUk3QipBOJQy1OsKja8l8c= on: tags: true + distributions: sdist bdist_wheel repo: GoogleCloudPlatform/google-auth-library-python + condition: "$TOXENV = \"cover\"" env: global: - - secure: s6GdhJklftl8w/9WoETwLtvtKL4ledPA/TuBuqCXQxSuYWaPuTdRVcvoejGkHJpp7i/7v2T/0etYl+5koyskKm5+QZZweaaL7MAyjPGp+hmIaIlWQRz6w481NOf3i9uSmoQycssT0mNmwScNIqo+igbA2y14mr/e9aBuOcxNNzNzFQp2vaRMEju6q7xZMjYdcudUWL48vq9CoNa3X2ZArpqjkApR/TfYlG7glOj43NxuVDN4z9wIyUjaMHBfPgEhjaOaRyEFgEYITRwX1qDoXqcZdTVIq4Cn0uCH+Mvrz6Y+oUJGTJqH1k7N/DhzbSN9lJnVYaQW/yuvGHiGAwbb6Tcxiq2UqqhA9MfbPpmstDECs46v9Z3BT252KvYEQY7Q1v9g2gFhHvFGWISUxs80rnnPhEYfa11JoLvj2t8cowkE4pvj4OH32Eoyvc5H07hW3F5xpuF7Jt7N09TNZkUrpmiRJEhfrVNgjsrWO77/q5h8mXGd+9vYmz++yzKu+63x8x1MpeigGCG73Dpu9Otm5eydOZfpJ39ZfZWUb7G2JahgHaGweM9dmnpJtzHQgijmHjjfAx9jgnQ8IQz9nkFmyMI8H7HouwalnrJtpSSbvMqOQ0kiZhMzdBKH5pD3tjLgSlgA0pKelBwlooY6jGlj4LrtbDAxa6cZyXiFoqWpT1w= - - CLOUD_SDK_ROOT: ${HOME}/.cache/cloud-sdk + - secure: s6GdhJklftl8w/9WoETwLtvtKL4ledPA/TuBuqCXQxSuYWaPuTdRVcvoejGkHJpp7i/7v2T/0etYl+5koyskKm5+QZZweaaL7MAyjPGp+hmIaIlWQRz6w481NOf3i9uSmoQycssT0mNmwScNIqo+igbA2y14mr/e9aBuOcxNNzNzFQp2vaRMEju6q7xZMjYdcudUWL48vq9CoNa3X2ZArpqjkApR/TfYlG7glOj43NxuVDN4z9wIyUjaMHBfPgEhjaOaRyEFgEYITRwX1qDoXqcZdTVIq4Cn0uCH+Mvrz6Y+oUJGTJqH1k7N/DhzbSN9lJnVYaQW/yuvGHiGAwbb6Tcxiq2UqqhA9MfbPpmstDECs46v9Z3BT252KvYEQY7Q1v9g2gFhHvFGWISUxs80rnnPhEYfa11JoLvj2t8cowkE4pvj4OH32Eoyvc5H07hW3F5xpuF7Jt7N09TNZkUrpmiRJEhfrVNgjsrWO77/q5h8mXGd+9vYmz++yzKu+63x8x1MpeigGCG73Dpu9Otm5eydOZfpJ39ZfZWUb7G2JahgHaGweM9dmnpJtzHQgijmHjjfAx9jgnQ8IQz9nkFmyMI8H7HouwalnrJtpSSbvMqOQ0kiZhMzdBKH5pD3tjLgSlgA0pKelBwlooY6jGlj4LrtbDAxa6cZyXiFoqWpT1w= + - CLOUD_SDK_ROOT: ${HOME}/.cache/cloud-sdk diff --git a/packages/google-auth/CHANGELOG.rst b/packages/google-auth/CHANGELOG.rst index f96a9dd83d0b..5c05b8530490 100644 --- a/packages/google-auth/CHANGELOG.rst +++ b/packages/google-auth/CHANGELOG.rst @@ -1,6 +1,16 @@ Changelog ========= +v0.3.0 +------ + +- Added Google ID token verification helpers. (#82) +- Swapped the ``target`` and ``request`` argument order for ``grpc.secure_authorized_channel``. (#81) +- Added a user's guide. (#79) +- Made ``service_account_email`` a public property on several credential classes. (#76) +- Added a ``scope`` argument to ``google.auth.default``. (#75) +- Added support for the ``GCLOUD_PROJECT`` environment variable. (#73) + v0.2.0 ------ diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index a0f5ec75d105..b0075ceb9fc0 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -29,7 +29,7 @@ setup( name='google-auth', - version='0.2.0', + version='0.3.0', author='Google Cloud Platform', author_email='jonwayne+google-auth@google.com', description='Google Authentication Library', From d14d4d7c050fdc38f31fa4df3ab104947321f5d2 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Thu, 17 Nov 2016 09:43:16 -0800 Subject: [PATCH 071/966] Fix ID token verification (#87) --- packages/google-auth/google/auth/_helpers.py | 17 +++++++++++++++++ packages/google-auth/google/auth/jwt.py | 4 ++-- packages/google-auth/google/oauth2/id_token.py | 2 +- .../google-auth/tests/oauth2/test_id_token.py | 7 ++++--- packages/google-auth/tests/test__helpers.py | 16 ++++++++++++++++ 5 files changed, 40 insertions(+), 6 deletions(-) diff --git a/packages/google-auth/google/auth/_helpers.py b/packages/google-auth/google/auth/_helpers.py index 5a6c3a86babd..de86beeb8455 100644 --- a/packages/google-auth/google/auth/_helpers.py +++ b/packages/google-auth/google/auth/_helpers.py @@ -14,6 +14,7 @@ """Helper functions for commonly used utilities.""" +import base64 import calendar import datetime @@ -194,3 +195,19 @@ def string_to_scopes(scopes): return [] return scopes.split(' ') + + +def padded_urlsafe_b64decode(value): + """Decodes base64 strings lacking padding characters. + + Google infrastructure tends to omit the base64 padding characters. + + Args: + value (Union[str, bytes]): The encoded value. + + Returns: + bytes: The decoded value + """ + b64string = to_bytes(value) + padded = b64string + b'=' * (-len(b64string) % 4) + return base64.urlsafe_b64decode(padded) diff --git a/packages/google-auth/google/auth/jwt.py b/packages/google-auth/google/auth/jwt.py index 7abe4c2eea70..0884b3ddb1cc 100644 --- a/packages/google-auth/google/auth/jwt.py +++ b/packages/google-auth/google/auth/jwt.py @@ -96,7 +96,7 @@ def encode(signer, payload, header=None, key_id=None): def _decode_jwt_segment(encoded_section): """Decodes a single JWT segment.""" - section_bytes = base64.urlsafe_b64decode(encoded_section) + section_bytes = _helpers.padded_urlsafe_b64decode(encoded_section) try: return json.loads(section_bytes.decode('utf-8')) except ValueError: @@ -124,7 +124,7 @@ def _unverified_decode(token): encoded_header, encoded_payload, signature = token.split(b'.') signed_section = encoded_header + b'.' + encoded_payload - signature = base64.urlsafe_b64decode(signature) + signature = _helpers.padded_urlsafe_b64decode(signature) # Parse segments header = _decode_jwt_segment(encoded_header) diff --git a/packages/google-auth/google/oauth2/id_token.py b/packages/google-auth/google/oauth2/id_token.py index 968f27142730..fa96fc0321b6 100644 --- a/packages/google-auth/google/oauth2/id_token.py +++ b/packages/google-auth/google/oauth2/id_token.py @@ -47,7 +47,7 @@ def _fetch_certs(request, certs_url): Mapping[str, str]: A mapping of public key ID to x.509 certificate data. """ - response = request('GET', certs_url) + response = request(certs_url, method='GET') if response.status != http_client.OK: raise exceptions.TransportError( diff --git a/packages/google-auth/tests/oauth2/test_id_token.py b/packages/google-auth/tests/oauth2/test_id_token.py index f3da6632d496..7f89547fb6e3 100644 --- a/packages/google-auth/tests/oauth2/test_id_token.py +++ b/packages/google-auth/tests/oauth2/test_id_token.py @@ -18,6 +18,7 @@ import pytest from google.auth import exceptions +import google.auth.transport from google.oauth2 import id_token @@ -28,7 +29,7 @@ def make_request(status, data=None): if data is not None: response.data = json.dumps(data).encode('utf-8') - return mock.Mock(return_value=response) + return mock.Mock(return_value=response, spec=google.auth.transport.Request) def test__fetch_certs_success(): @@ -37,7 +38,7 @@ def test__fetch_certs_success(): returned_certs = id_token._fetch_certs(request, mock.sentinel.cert_url) - request.assert_called_once_with('GET', mock.sentinel.cert_url) + request.assert_called_once_with(mock.sentinel.cert_url, method='GET') assert returned_certs == certs @@ -47,7 +48,7 @@ def test__fetch_certs_failure(): with pytest.raises(exceptions.TransportError): id_token._fetch_certs(request, mock.sentinel.cert_url) - request.assert_called_once_with('GET', mock.sentinel.cert_url) + request.assert_called_once_with(mock.sentinel.cert_url, method='GET') @mock.patch('google.auth.jwt.decode', autospec=True) diff --git a/packages/google-auth/tests/test__helpers.py b/packages/google-auth/tests/test__helpers.py index d413adcabc29..b067faabf3ce 100644 --- a/packages/google-auth/tests/test__helpers.py +++ b/packages/google-auth/tests/test__helpers.py @@ -151,3 +151,19 @@ def test_string_to_scopes(): for case, expected in cases: assert _helpers.string_to_scopes(case) == expected + + +def test_padded_urlsafe_b64decode(): + cases = [ + ('YQ==', b'a'), + ('YQ', b'a'), + ('YWE=', b'aa'), + ('YWE', b'aa'), + ('YWFhYQ==', b'aaaa'), + ('YWFhYQ', b'aaaa'), + ('YWFhYWE=', b'aaaaa'), + ('YWFhYWE', b'aaaaa'), + ] + + for case, expected in cases: + assert _helpers.padded_urlsafe_b64decode(case) == expected From 85b9b6cc20306a51e209ac0a706c29a14043e0c1 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Thu, 17 Nov 2016 10:17:59 -0800 Subject: [PATCH 072/966] Release v0.3.1 --- packages/google-auth/CHANGELOG.rst | 6 ++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.rst b/packages/google-auth/CHANGELOG.rst index 5c05b8530490..510a2be25b60 100644 --- a/packages/google-auth/CHANGELOG.rst +++ b/packages/google-auth/CHANGELOG.rst @@ -1,6 +1,12 @@ Changelog ========= +v0.3.1 +------ + +- Fixed a bug where non-padded base64 encoded strings were not accepted. (#87) +- Fixed a bug where ID token verification did not correctly call the HTTP request function. (#87) + v0.3.0 ------ diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index b0075ceb9fc0..1796e4a85594 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -29,7 +29,7 @@ setup( name='google-auth', - version='0.3.0', + version='0.3.1', author='Google Cloud Platform', author_email='jonwayne+google-auth@google.com', description='Google Authentication Library', From 19dcd9baba2751fefaaefd45aa14b61f3dadda94 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Thu, 1 Dec 2016 15:34:59 -0800 Subject: [PATCH 073/966] Fix circular import when importing google.oauth2 before google.auth (#88) --- packages/google-auth/google/auth/_default.py | 22 ++++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index f9829d44e914..1e91368f5fa1 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -22,16 +22,9 @@ import logging import os -from google.auth import _cloud_sdk -from google.auth import app_engine -from google.auth import compute_engine from google.auth import environment_vars from google.auth import exceptions -from google.auth.compute_engine import _metadata -import google.auth.credentials import google.auth.transport._http_client -from google.oauth2 import service_account -import google.oauth2.credentials _LOGGER = logging.getLogger(__name__) @@ -79,6 +72,8 @@ def _load_credentials_from_file(filename): credential_type = info.get('type') if credential_type == _AUTHORIZED_USER_TYPE: + from google.auth import _cloud_sdk + try: credentials = _cloud_sdk.load_authorized_user_credentials(info) except ValueError as exc: @@ -89,6 +84,8 @@ def _load_credentials_from_file(filename): return credentials, None elif credential_type == _SERVICE_ACCOUNT_TYPE: + from google.oauth2 import service_account + try: credentials = ( service_account.Credentials.from_service_account_info(info)) @@ -107,6 +104,8 @@ def _load_credentials_from_file(filename): def _get_gcloud_sdk_credentials(): """Gets the credentials and project ID from the Cloud SDK.""" + from google.auth import _cloud_sdk + # Check if application default credentials exist. credentials_filename = ( _cloud_sdk.get_application_default_credentials_path()) @@ -152,6 +151,8 @@ def _get_explicit_environ_credentials(): def _get_gae_credentials(): """Gets Google App Engine App Identity credentials and project ID.""" + from google.auth import app_engine + try: credentials = app_engine.Credentials() project_id = app_engine.get_project_id() @@ -166,6 +167,8 @@ def _get_gce_credentials(request=None): # to require no arguments. So, we'll use the _http_client transport which # uses http.client. This is only acceptable because the metadata server # doesn't do SSL and never requires proxies. + from google.auth import compute_engine + from google.auth.compute_engine import _metadata if request is None: request = google.auth.transport._http_client.Request() @@ -258,6 +261,8 @@ def default(scopes=None, request=None): If no credentials were found, or if the credentials found were invalid. """ + from google.auth.credentials import with_scopes_if_required + explicit_project_id = os.environ.get( environment_vars.PROJECT, os.environ.get(environment_vars.LEGACY_PROJECT)) @@ -271,8 +276,7 @@ def default(scopes=None, request=None): for checker in checkers: credentials, project_id = checker() if credentials is not None: - credentials = google.auth.credentials.with_scopes_if_required( - credentials, scopes) + credentials = with_scopes_if_required(credentials, scopes) return credentials, explicit_project_id or project_id raise exceptions.DefaultCredentialsError(_HELP_MESSAGE) From abed08a0298862a5aa177dd6d5fa0f0006279536 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Thu, 1 Dec 2016 15:36:38 -0800 Subject: [PATCH 074/966] Release v0.3.2 --- packages/google-auth/CHANGELOG.rst | 5 +++++ packages/google-auth/docs/conf.py | 4 ++-- packages/google-auth/setup.py | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/google-auth/CHANGELOG.rst b/packages/google-auth/CHANGELOG.rst index 510a2be25b60..4d24b85f5307 100644 --- a/packages/google-auth/CHANGELOG.rst +++ b/packages/google-auth/CHANGELOG.rst @@ -1,6 +1,11 @@ Changelog ========= +v0.3.2 +------ + +- Fixed an issue where an `ImportError` would occur if `google.oauth2` was imported before `google.auth`. (#88) + v0.3.1 ------ diff --git a/packages/google-auth/docs/conf.py b/packages/google-auth/docs/conf.py index 7b820b8f6a19..97e2f96cdd80 100644 --- a/packages/google-auth/docs/conf.py +++ b/packages/google-auth/docs/conf.py @@ -64,9 +64,9 @@ # built documents. # # The short X.Y version. -version = '0.0.1a' +version = '0.3.2' # The full version, including alpha/beta/rc tags. -release = '0.0.1a' +release = '0.3.2' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 1796e4a85594..0dabdb6f7f7c 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -29,7 +29,7 @@ setup( name='google-auth', - version='0.3.1', + version='0.3.2', author='Google Cloud Platform', author_email='jonwayne+google-auth@google.com', description='Google Authentication Library', From 0b518709e6820c11917c06f2d25632868f12a70a Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Fri, 2 Dec 2016 14:20:22 -0800 Subject: [PATCH 075/966] Make secure_authorized_channel pass kwargs to grpc.secure_channel (#90) --- packages/google-auth/google/auth/transport/grpc.py | 5 +++-- packages/google-auth/tests/transport/test_grpc.py | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/google-auth/google/auth/transport/grpc.py b/packages/google-auth/google/auth/transport/grpc.py index 1e02b383049c..4062972aa742 100644 --- a/packages/google-auth/google/auth/transport/grpc.py +++ b/packages/google-auth/google/auth/transport/grpc.py @@ -62,7 +62,7 @@ def __call__(self, context, callback): def secure_authorized_channel( - credentials, request, target, ssl_credentials=None): + credentials, request, target, ssl_credentials=None, **kwargs): """Creates a secure authorized gRPC channel. This creates a channel with SSL and :class:`AuthMetadataPlugin`. This @@ -98,6 +98,7 @@ def secure_authorized_channel( target (str): The host and port of the service. ssl_credentials (grpc.ChannelCredentials): Optional SSL channel credentials. This can be used to specify different certificates. + kwargs: Additional arguments to pass to :func:`grpc.secure_channel`. Returns: grpc.Channel: The created gRPC channel. @@ -115,4 +116,4 @@ def secure_authorized_channel( composite_credentials = grpc.composite_channel_credentials( ssl_credentials, google_auth_credentials) - return grpc.secure_channel(target, composite_credentials) + return grpc.secure_channel(target, composite_credentials, **kwargs) diff --git a/packages/google-auth/tests/transport/test_grpc.py b/packages/google-auth/tests/transport/test_grpc.py index 84e4b56598a8..15a301fa3aad 100644 --- a/packages/google-auth/tests/transport/test_grpc.py +++ b/packages/google-auth/tests/transport/test_grpc.py @@ -82,7 +82,7 @@ def test_secure_authorized_channel( target = 'example.com:80' channel = google.auth.transport.grpc.secure_authorized_channel( - credentials, request, target) + credentials, request, target, options=mock.sentinel.options) # Check the auth plugin construction. auth_plugin = metadata_call_credentials.call_args[0][0] @@ -101,7 +101,8 @@ def test_secure_authorized_channel( # Check the channel call. secure_channel.assert_called_once_with( - target, composite_channel_credentials.return_value) + target, composite_channel_credentials.return_value, + options=mock.sentinel.options) assert channel == secure_channel.return_value From fbcf72c89ae4b73b0876ae07a0368a209bf868b3 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Fri, 2 Dec 2016 14:26:33 -0800 Subject: [PATCH 076/966] Add Signing.singer_email (#89) --- packages/google-auth/google/auth/app_engine.py | 5 +++++ packages/google-auth/google/auth/credentials.py | 7 +++++++ packages/google-auth/google/auth/jwt.py | 5 +++++ packages/google-auth/google/oauth2/service_account.py | 5 +++++ packages/google-auth/tests/oauth2/test_service_account.py | 3 +++ packages/google-auth/tests/test_app_engine.py | 4 ++++ packages/google-auth/tests/test_jwt.py | 4 ++++ 7 files changed, 33 insertions(+) diff --git a/packages/google-auth/google/auth/app_engine.py b/packages/google-auth/google/auth/app_engine.py index 566475e9f903..608651d848cf 100644 --- a/packages/google-auth/google/auth/app_engine.py +++ b/packages/google-auth/google/auth/app_engine.py @@ -110,3 +110,8 @@ def with_scopes(self, scopes): @_helpers.copy_docstring(credentials.Signing) def sign_bytes(self, message): return app_identity.sign_blob(message) + + @property + @_helpers.copy_docstring(credentials.Signing) + def signer_email(self): + return self.service_account_email diff --git a/packages/google-auth/google/auth/credentials.py b/packages/google-auth/google/auth/credentials.py index 470f1a4215b9..360dc0e93727 100644 --- a/packages/google-auth/google/auth/credentials.py +++ b/packages/google-auth/google/auth/credentials.py @@ -229,3 +229,10 @@ def sign_bytes(self, message): # pylint: disable=missing-raises-doc,redundant-returns-doc # (pylint doesn't recognize that this is abstract) raise NotImplementedError('Sign bytes must be implemented.') + + @abc.abstractproperty + def signer_email(self): + """Optional[str]: An email address that identifies the signer.""" + # pylint: disable=missing-raises-doc + # (pylint doesn't recognize that this is abstract) + raise NotImplementedError('Signer email must be implemented.') diff --git a/packages/google-auth/google/auth/jwt.py b/packages/google-auth/google/auth/jwt.py index 0884b3ddb1cc..dfaf2e684bf2 100644 --- a/packages/google-auth/google/auth/jwt.py +++ b/packages/google-auth/google/auth/jwt.py @@ -463,6 +463,11 @@ def sign_bytes(self, message): """ return self._signer.sign(message) + @property + @_helpers.copy_docstring(credentials.Signing) + def signer_email(self): + return self._issuer + def before_request(self, request, method, url, headers): """Performs credential-specific before request logic. diff --git a/packages/google-auth/google/oauth2/service_account.py b/packages/google-auth/google/oauth2/service_account.py index 24c852b2a2eb..48b537de192f 100644 --- a/packages/google-auth/google/oauth2/service_account.py +++ b/packages/google-auth/google/oauth2/service_account.py @@ -317,3 +317,8 @@ def refresh(self, request): @_helpers.copy_docstring(credentials.Signing) def sign_bytes(self, message): return self._signer.sign(message) + + @property + @_helpers.copy_docstring(credentials.Signing) + def signer_email(self): + return self._service_account_email diff --git a/packages/google-auth/tests/oauth2/test_service_account.py b/packages/google-auth/tests/oauth2/test_service_account.py index 678e6a3a64f8..e6ce63133565 100644 --- a/packages/google-auth/tests/oauth2/test_service_account.py +++ b/packages/google-auth/tests/oauth2/test_service_account.py @@ -134,6 +134,9 @@ def test_sign_bytes(self): signature = self.credentials.sign_bytes(to_sign) assert crypt.verify_signature(to_sign, signature, PUBLIC_CERT_BYTES) + def test_signer_email(self): + assert self.credentials.signer_email == self.SERVICE_ACCOUNT_EMAIL + def test_create_scoped(self): scopes = ['email', 'profile'] credentials = self.credentials.with_scopes(scopes) diff --git a/packages/google-auth/tests/test_app_engine.py b/packages/google-auth/tests/test_app_engine.py index 94f528d1d429..117533ebfcb0 100644 --- a/packages/google-auth/tests/test_app_engine.py +++ b/packages/google-auth/tests/test_app_engine.py @@ -115,3 +115,7 @@ def test_sign_bytes(self, app_identity_mock): assert signature == mock.sentinel.signature app_identity_mock.sign_blob.assert_called_with(to_sign) + + def test_signer_email(self, app_identity_mock): + credentials = app_engine.Credentials() + assert credentials.signer_email == credentials.service_account_email diff --git a/packages/google-auth/tests/test_jwt.py b/packages/google-auth/tests/test_jwt.py index 2a4795acc5bd..3959260e205f 100644 --- a/packages/google-auth/tests/test_jwt.py +++ b/packages/google-auth/tests/test_jwt.py @@ -264,6 +264,10 @@ def test_sign_bytes(self): signature = self.credentials.sign_bytes(to_sign) assert crypt.verify_signature(to_sign, signature, PUBLIC_CERT_BYTES) + def test_signer_email(self): + assert (self.credentials.signer_email == + SERVICE_ACCOUNT_INFO['client_email']) + def _verify_token(self, token): payload = jwt.decode(token, PUBLIC_CERT_BYTES) assert payload['iss'] == self.SERVICE_ACCOUNT_EMAIL From bb2f446992a2ca4d08f72afc0050e552678cc537 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Fri, 2 Dec 2016 14:27:18 -0800 Subject: [PATCH 077/966] Add proxy to httplib2.Http.connections (#91) --- .../httplib2_transport/google_auth_httplib2.py | 10 ++++++++++ packages/google-auth/httplib2_transport/setup.py | 2 +- .../tests/test_google_auth_httplib2.py | 9 +++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/httplib2_transport/google_auth_httplib2.py b/packages/google-auth/httplib2_transport/google_auth_httplib2.py index 598ccb3e4647..866841b2e6b6 100644 --- a/packages/google-auth/httplib2_transport/google_auth_httplib2.py +++ b/packages/google-auth/httplib2_transport/google_auth_httplib2.py @@ -223,3 +223,13 @@ def request(self, uri, method='GET', body=None, headers=None, **kwargs) return response, content + + @property + def connections(self): + """Proxy to httplib2.Http.connections.""" + return self.http.connections + + @connections.setter + def connections(self, value): + """Proxy to httplib2.Http.connections.""" + self.http.connections = value diff --git a/packages/google-auth/httplib2_transport/setup.py b/packages/google-auth/httplib2_transport/setup.py index 110cce847506..e85c309e729b 100644 --- a/packages/google-auth/httplib2_transport/setup.py +++ b/packages/google-auth/httplib2_transport/setup.py @@ -30,7 +30,7 @@ setup( name='google-auth-httplib2', - version='0.0.1', + version='0.0.2', author='Google Cloud Platform', author_email='jonwayne+google-auth@google.com', description='Google Authentication Library', diff --git a/packages/google-auth/httplib2_transport/tests/test_google_auth_httplib2.py b/packages/google-auth/httplib2_transport/tests/test_google_auth_httplib2.py index 9c2da7080748..635965f56909 100644 --- a/packages/google-auth/httplib2_transport/tests/test_google_auth_httplib2.py +++ b/packages/google-auth/httplib2_transport/tests/test_google_auth_httplib2.py @@ -86,6 +86,15 @@ def test_authed_http_defaults(self): assert authed_http.credentials == mock.sentinel.credentials assert isinstance(authed_http.http, httplib2.Http) + def test_connections(self): + authed_http = google_auth_httplib2.AuthorizedHttp( + mock.sentinel.credentials) + + assert authed_http.connections == authed_http.http.connections + + authed_http.connections = mock.sentinel.connections + assert authed_http.http.connections == mock.sentinel.connections + def test_request_no_refresh(self): mock_credentials = mock.Mock(wraps=MockCredentials()) mock_response = MockResponse() From 0c554eea01cfc47e5280f3f2e278be99b30b6522 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Fri, 2 Dec 2016 14:31:26 -0800 Subject: [PATCH 078/966] Release v0.4.0 --- packages/google-auth/CHANGELOG.rst | 9 ++++++++- packages/google-auth/docs/conf.py | 4 ++-- packages/google-auth/setup.py | 2 +- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/google-auth/CHANGELOG.rst b/packages/google-auth/CHANGELOG.rst index 4d24b85f5307..90c526dad40e 100644 --- a/packages/google-auth/CHANGELOG.rst +++ b/packages/google-auth/CHANGELOG.rst @@ -1,10 +1,17 @@ Changelog ========= +v0.4.0 +------ + +- ``transports.grpc.secure_authorized_channel`` now passes ``kwargs`` to ``grpc.secure_channel``. (#90) +- Added new property ``credentials.Singing.signer_email`` which can be used to identify the signer of a message. (#89) +- (google_auth_httplib2) Added a proxy to ``httplib2.Http.connections``. + v0.3.2 ------ -- Fixed an issue where an `ImportError` would occur if `google.oauth2` was imported before `google.auth`. (#88) +- Fixed an issue where an ``ImportError`` would occur if ``google.oauth2`` was imported before ``google.auth``. (#88) v0.3.1 ------ diff --git a/packages/google-auth/docs/conf.py b/packages/google-auth/docs/conf.py index 97e2f96cdd80..140fe51d25d9 100644 --- a/packages/google-auth/docs/conf.py +++ b/packages/google-auth/docs/conf.py @@ -64,9 +64,9 @@ # built documents. # # The short X.Y version. -version = '0.3.2' +version = '0.4.0' # The full version, including alpha/beta/rc tags. -release = '0.3.2' +release = '0.4.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 0dabdb6f7f7c..1baabd89fad7 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -29,7 +29,7 @@ setup( name='google-auth', - version='0.3.2', + version='0.4.0', author='Google Cloud Platform', author_email='jonwayne+google-auth@google.com', description='Google Authentication Library', From 311abff648cc2ef0b61a356836eb38de413d2e9c Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Wed, 7 Dec 2016 14:25:51 -0800 Subject: [PATCH 079/966] Fix app engine system test --- .../system_tests/app_engine_test_app/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/system_tests/app_engine_test_app/requirements.txt b/packages/google-auth/system_tests/app_engine_test_app/requirements.txt index bd5c476ab2b4..e390e141f07d 100644 --- a/packages/google-auth/system_tests/app_engine_test_app/requirements.txt +++ b/packages/google-auth/system_tests/app_engine_test_app/requirements.txt @@ -1,3 +1,3 @@ urllib3 # Relative path to google-auth-python's source. -../../.. +../.. From 9aa60477c3f3715ee74bd902a81721a43a960c68 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Wed, 7 Dec 2016 14:33:25 -0800 Subject: [PATCH 080/966] Fix app engine system test --- packages/google-auth/system_tests/nox.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/system_tests/nox.py b/packages/google-auth/system_tests/nox.py index 5df72c81b175..8179ca11e339 100644 --- a/packages/google-auth/system_tests/nox.py +++ b/packages/google-auth/system_tests/nox.py @@ -225,7 +225,7 @@ def session_app_engine(session): # Grab the project ID from the cloud sdk. project_id = subprocess.check_output([ 'gcloud', 'config', 'list', 'project', '--format', - 'value(core.project)']).strip() + 'value(core.project)']).decode('utf-8').strip() if not project_id: session.error( From 447d10f6186d60a37a1e86c1e6af906d1530e626 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Tue, 13 Dec 2016 14:53:25 -0800 Subject: [PATCH 081/966] Fix TypeError if error description is None (#96) --- packages/google-auth/google/oauth2/_client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/google/oauth2/_client.py b/packages/google-auth/google/oauth2/_client.py index 1b26549b75cb..468cb7e889c4 100644 --- a/packages/google-auth/google/oauth2/_client.py +++ b/packages/google-auth/google/oauth2/_client.py @@ -48,9 +48,9 @@ def _handle_error_response(response_body): """ try: error_data = json.loads(response_body) - error_details = ': '.join([ + error_details = '{}: {}'.format( error_data['error'], - error_data.get('error_description')]) + error_data.get('error_description')) # If no details could be extracted, use the response data. except (KeyError, ValueError): error_details = response_body From 4171a1eea7062fd9db3d3f5cc020d2e7ebf847f4 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Tue, 13 Dec 2016 16:02:40 -0800 Subject: [PATCH 082/966] Add crypt.Signer.from_service_account_file (#95) --- packages/google-auth/google/auth/crypt.py | 15 +++++++++++++++ packages/google-auth/tests/test_crypt.py | 14 ++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/packages/google-auth/google/auth/crypt.py b/packages/google-auth/google/auth/crypt.py index d347600f09be..8d5ac7c747ea 100644 --- a/packages/google-auth/google/auth/crypt.py +++ b/packages/google-auth/google/auth/crypt.py @@ -227,3 +227,18 @@ def from_string(cls, key, key_id=None): raise ValueError('No key could be detected.') return cls(private_key, key_id=key_id) + + @classmethod + def from_service_account_file(cls, filename): + """Creates a Signer instance from a service account .json file + in Google format. + + Args: + filename (str): The path to the service account .json file. + + Returns: + Signer: The constructed signer. + """ + from google.auth import _service_account_info + _, signer = _service_account_info.from_filename(filename) + return signer diff --git a/packages/google-auth/tests/test_crypt.py b/packages/google-auth/tests/test_crypt.py index 33105e414fc0..fd70f4bb8456 100644 --- a/packages/google-auth/tests/test_crypt.py +++ b/packages/google-auth/tests/test_crypt.py @@ -13,6 +13,7 @@ # limitations under the License. import os +import json import mock from pyasn1_modules import pem @@ -59,6 +60,12 @@ with open(os.path.join(DATA_DIR, 'privatekey.p12'), 'rb') as fh: PKCS12_KEY_BYTES = fh.read() +# The service account JSON file can be generated from the Google Cloud Console. +SERVICE_ACCOUNT_JSON_FILE = os.path.join(DATA_DIR, 'service_account.json') + +with open(SERVICE_ACCOUNT_JSON_FILE, 'r') as fh: + SERVICE_ACCOUNT_INFO = json.load(fh) + def test_verify_signature(): to_sign = b'foo' @@ -191,3 +198,10 @@ def test_from_string_bogus_key(self): key_bytes = 'bogus-key' with pytest.raises(ValueError): crypt.Signer.from_string(key_bytes) + + def test_from_service_account_file(self): + signer = crypt.Signer.from_service_account_file( + SERVICE_ACCOUNT_JSON_FILE) + + assert signer.key_id == SERVICE_ACCOUNT_INFO['private_key_id'] + assert isinstance(signer._key, rsa.key.PrivateKey) From 30976b3c33c3fac8c6a5382a07495f084bf718b7 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Tue, 13 Dec 2016 16:12:02 -0800 Subject: [PATCH 083/966] Add app_engine.Signer (#97) --- .../google-auth/google/auth/app_engine.py | 31 ++++++++++++++++--- packages/google-auth/google/auth/crypt.py | 2 +- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/packages/google-auth/google/auth/app_engine.py b/packages/google-auth/google/auth/app_engine.py index 608651d848cf..846bcec08b49 100644 --- a/packages/google-auth/google/auth/app_engine.py +++ b/packages/google-auth/google/auth/app_engine.py @@ -12,10 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Google App Engine standard environment credentials. +"""Google App Engine standard environment support. -This module provides authentication for application running on App Engine in -the standard environment using the `App Identity API`_. +This module provides authentication and signing for applications running on App +Engine in the standard environment using the `App Identity API`_. .. _App Identity API: @@ -33,6 +33,29 @@ app_identity = None +class Signer(object): + """Signs messages using the App Engine app identity service. + + This can be used in place of :class:`google.auth.crypt.Signer` when + running in the App Engine standard environment. + """ + def __init__(self): + self.key_id = None + + @staticmethod + def sign(message): + """Signs a message. + + Args: + message (Union[str, bytes]): The message to be signed. + + Returns: + bytes: The signature of the message. + """ + message = _helpers.to_bytes(message) + return app_identity.sign_blob(message) + + def get_project_id(): """Gets the project ID for the current App Engine application. @@ -109,7 +132,7 @@ def with_scopes(self, scopes): @_helpers.copy_docstring(credentials.Signing) def sign_bytes(self, message): - return app_identity.sign_blob(message) + return Signer().sign(message) @property @_helpers.copy_docstring(credentials.Signing) diff --git a/packages/google-auth/google/auth/crypt.py b/packages/google-auth/google/auth/crypt.py index 8d5ac7c747ea..618afe594aa6 100644 --- a/packages/google-auth/google/auth/crypt.py +++ b/packages/google-auth/google/auth/crypt.py @@ -186,7 +186,7 @@ def sign(self, message): message (Union[str, bytes]): The message to be signed. Returns: - bytes: The signature of the message for the given key. + bytes: The signature of the message. """ message = _helpers.to_bytes(message) return rsa.pkcs1.sign(message, self._key, 'SHA-256') From 14d1d3acdc74ab1f3d2fd890d870b0acea7c2757 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Wed, 14 Dec 2016 12:20:24 -0800 Subject: [PATCH 084/966] Fix cyclic import and add Signer.from_service_account_info (#99) --- .../google/auth/_service_account_info.py | 8 ++--- packages/google-auth/google/auth/crypt.py | 35 +++++++++++++++++-- .../tests/test__service_account_info.py | 2 +- packages/google-auth/tests/test_crypt.py | 18 ++++++++-- 4 files changed, 51 insertions(+), 12 deletions(-) diff --git a/packages/google-auth/google/auth/_service_account_info.py b/packages/google-auth/google/auth/_service_account_info.py index b007abdedef7..989101113e1a 100644 --- a/packages/google-auth/google/auth/_service_account_info.py +++ b/packages/google-auth/google/auth/_service_account_info.py @@ -41,10 +41,7 @@ def from_dict(data, require=None): ValueError: if the data was in the wrong format, or if one of the required keys is missing. """ - # Private key is always required. - keys_needed = set(('private_key',)) - if require is not None: - keys_needed.update(require) + keys_needed = set(require if require is not None else []) missing = keys_needed.difference(six.iterkeys(data)) @@ -54,8 +51,7 @@ def from_dict(data, require=None): 'fields {}.'.format(', '.join(missing))) # Create a signer. - signer = crypt.Signer.from_string( - data['private_key'], data.get('private_key_id')) + signer = crypt.Signer.from_service_account_info(data) return signer diff --git a/packages/google-auth/google/auth/crypt.py b/packages/google-auth/google/auth/crypt.py index 618afe594aa6..c425045ccc6c 100644 --- a/packages/google-auth/google/auth/crypt.py +++ b/packages/google-auth/google/auth/crypt.py @@ -38,6 +38,8 @@ signature = signer.sign(message) """ +import io +import json from pyasn1.codec.der import decoder from pyasn1_modules import pem @@ -55,6 +57,8 @@ _PKCS8_MARKER = ('-----BEGIN PRIVATE KEY-----', '-----END PRIVATE KEY-----') _PKCS8_SPEC = PrivateKeyInfo() +_JSON_FILE_PRIVATE_KEY = 'private_key' +_JSON_FILE_PRIVATE_KEY_ID = 'private_key_id' def _bit_list_to_bytes(bit_list): @@ -228,6 +232,30 @@ def from_string(cls, key, key_id=None): return cls(private_key, key_id=key_id) + @classmethod + def from_service_account_info(cls, info): + """Creates a Signer instance instance from a dictionary containing + service account info in Google format. + + Args: + info (Mapping[str, str]): The service account info in Google + format. + + Returns: + Signer: The constructed signer. + + Raises: + ValueError: If the info is not in the expected format. + """ + if _JSON_FILE_PRIVATE_KEY not in info: + raise ValueError( + 'The private_key field was not found in the service account ' + 'info.') + + return cls.from_string( + info[_JSON_FILE_PRIVATE_KEY], + info.get(_JSON_FILE_PRIVATE_KEY_ID)) + @classmethod def from_service_account_file(cls, filename): """Creates a Signer instance from a service account .json file @@ -239,6 +267,7 @@ def from_service_account_file(cls, filename): Returns: Signer: The constructed signer. """ - from google.auth import _service_account_info - _, signer = _service_account_info.from_filename(filename) - return signer + with io.open(filename, 'r', encoding='utf-8') as json_file: + data = json.load(json_file) + + return cls.from_service_account_info(data) diff --git a/packages/google-auth/tests/test__service_account_info.py b/packages/google-auth/tests/test__service_account_info.py index 14f659adec6a..4caea95d30e0 100644 --- a/packages/google-auth/tests/test__service_account_info.py +++ b/packages/google-auth/tests/test__service_account_info.py @@ -47,7 +47,7 @@ def test_from_dict_bad_private_key(): def test_from_dict_bad_format(): with pytest.raises(ValueError) as excinfo: - _service_account_info.from_dict({}) + _service_account_info.from_dict({}, require=('meep',)) assert excinfo.match(r'missing fields') diff --git a/packages/google-auth/tests/test_crypt.py b/packages/google-auth/tests/test_crypt.py index fd70f4bb8456..9671230c622d 100644 --- a/packages/google-auth/tests/test_crypt.py +++ b/packages/google-auth/tests/test_crypt.py @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os import json +import os import mock from pyasn1_modules import pem @@ -199,9 +199,23 @@ def test_from_string_bogus_key(self): with pytest.raises(ValueError): crypt.Signer.from_string(key_bytes) + def test_from_service_account_info(self): + signer = crypt.Signer.from_service_account_info(SERVICE_ACCOUNT_INFO) + + assert signer.key_id == SERVICE_ACCOUNT_INFO[ + crypt._JSON_FILE_PRIVATE_KEY_ID] + assert isinstance(signer._key, rsa.key.PrivateKey) + + def test_from_service_account_info_missing_key(self): + with pytest.raises(ValueError) as excinfo: + crypt.Signer.from_service_account_info({}) + + assert excinfo.match(crypt._JSON_FILE_PRIVATE_KEY) + def test_from_service_account_file(self): signer = crypt.Signer.from_service_account_file( SERVICE_ACCOUNT_JSON_FILE) - assert signer.key_id == SERVICE_ACCOUNT_INFO['private_key_id'] + assert signer.key_id == SERVICE_ACCOUNT_INFO[ + crypt._JSON_FILE_PRIVATE_KEY_ID] assert isinstance(signer._key, rsa.key.PrivateKey) From 88bba4a3c4808d6089e967d75002376ee5c26c3f Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Wed, 14 Dec 2016 12:43:30 -0800 Subject: [PATCH 085/966] Release v0.5.0 (#98) --- packages/google-auth/CHANGELOG.rst | 8 ++++++++ packages/google-auth/README.rst | 4 ++-- packages/google-auth/docs/conf.py | 6 ++++-- packages/google-auth/google/auth/crypt.py | 6 +++--- packages/google-auth/setup.py | 2 +- packages/google-auth/tox.ini | 2 +- 6 files changed, 19 insertions(+), 9 deletions(-) diff --git a/packages/google-auth/CHANGELOG.rst b/packages/google-auth/CHANGELOG.rst index 90c526dad40e..b54447435d8a 100644 --- a/packages/google-auth/CHANGELOG.rst +++ b/packages/google-auth/CHANGELOG.rst @@ -1,6 +1,14 @@ Changelog ========= +v0.5.0 +------ + +- Added ``app_engine.Signer``. (#97) +- Added ``crypt.Signer.from_service_account_file``. (#95) +- Fixed error handling in the oauth2 client. (#96) +- Fixed the App Engine system tests. + v0.4.0 ------ diff --git a/packages/google-auth/README.rst b/packages/google-auth/README.rst index 8dcfbe8a29db..5767d87f90af 100644 --- a/packages/google-auth/README.rst +++ b/packages/google-auth/README.rst @@ -7,10 +7,10 @@ This library simplifies using Google's various server-to-server authentication mechanisms to access Google APIs. .. |build| image:: https://travis-ci.org/GoogleCloudPlatform/google-auth-library-python.svg?branch=master - :target: https://travis-ci.org/GoogleCloudPlatform//google-auth-library-python + :target: https://travis-ci.org/GoogleCloudPlatform/google-auth-library-python .. |docs| image:: https://readthedocs.org/projects/google-auth/badge/?version=latest :target: https://google-auth.readthedocs.io/en/latest/ -.. |pypi| image:: https://img.shields.io/pypi/v//google-auth.svg +.. |pypi| image:: https://img.shields.io/pypi/v/google-auth.svg :target: https://pypi.python.org/pypi/google-auth Installing diff --git a/packages/google-auth/docs/conf.py b/packages/google-auth/docs/conf.py index 140fe51d25d9..b045c8ca3595 100644 --- a/packages/google-auth/docs/conf.py +++ b/packages/google-auth/docs/conf.py @@ -13,6 +13,8 @@ # All configuration values have a default; values that are commented out # serve to show the default. +import pkg_resources + # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. @@ -64,9 +66,9 @@ # built documents. # # The short X.Y version. -version = '0.4.0' +version = pkg_resources.get_distribution('google-auth').version # The full version, including alpha/beta/rc tags. -release = '0.4.0' +release = version # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/packages/google-auth/google/auth/crypt.py b/packages/google-auth/google/auth/crypt.py index c425045ccc6c..6151b2921eda 100644 --- a/packages/google-auth/google/auth/crypt.py +++ b/packages/google-auth/google/auth/crypt.py @@ -204,7 +204,7 @@ def from_string(cls, key, key_id=None): key_id (str): An optional key id used to identify the private key. Returns: - Signer: The constructed signer. + ~google.auth.crypt.Signer: The constructed signer. Raises: ValueError: If the key cannot be parsed as PKCS#1 or PKCS#8 in @@ -242,7 +242,7 @@ def from_service_account_info(cls, info): format. Returns: - Signer: The constructed signer. + ~google.auth.crypt.Signer: The constructed signer. Raises: ValueError: If the info is not in the expected format. @@ -265,7 +265,7 @@ def from_service_account_file(cls, filename): filename (str): The path to the service account .json file. Returns: - Signer: The constructed signer. + ~google.auth.crypt.Signer: The constructed signer. """ with io.open(filename, 'r', encoding='utf-8') as json_file: data = json.load(json_file) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 1baabd89fad7..ca3184f88066 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -29,7 +29,7 @@ setup( name='google-auth', - version='0.4.0', + version='0.5.0', author='Google Cloud Platform', author_email='jonwayne+google-auth@google.com', description='Google Authentication Library', diff --git a/packages/google-auth/tox.ini b/packages/google-auth/tox.ini index ad760bd662f0..2347bc1fe277 100644 --- a/packages/google-auth/tox.ini +++ b/packages/google-auth/tox.ini @@ -70,7 +70,7 @@ deps = commands = make -C docs html [testenv:lint] -basepython = python3.5 +basepython = python3.4 commands = python setup.py check --metadata --restructuredtext --strict flake8 \ From 3404e9add2bc87a784ec0a92759436d47b971b8a Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Wed, 14 Dec 2016 12:56:44 -0800 Subject: [PATCH 086/966] Restore lint to Python 3.5 --- packages/google-auth/tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/tox.ini b/packages/google-auth/tox.ini index 2347bc1fe277..ad760bd662f0 100644 --- a/packages/google-auth/tox.ini +++ b/packages/google-auth/tox.ini @@ -70,7 +70,7 @@ deps = commands = make -C docs html [testenv:lint] -basepython = python3.4 +basepython = python3.5 commands = python setup.py check --metadata --restructuredtext --strict flake8 \ From 51f41f770aa3f5481d169b070bd8888ab2281e38 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Mon, 19 Dec 2016 10:21:23 -0800 Subject: [PATCH 087/966] Fix a few lint errors --- packages/google-auth/google/auth/app_engine.py | 6 ++++++ packages/google-auth/google/auth/crypt.py | 6 +++--- packages/google-auth/google/auth/transport/grpc.py | 4 ++++ packages/google-auth/scripts/run_pylint.py | 1 + 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/packages/google-auth/google/auth/app_engine.py b/packages/google-auth/google/auth/app_engine.py index 846bcec08b49..ec89dc0ea4c4 100644 --- a/packages/google-auth/google/auth/app_engine.py +++ b/packages/google-auth/google/auth/app_engine.py @@ -65,6 +65,9 @@ def get_project_id(): Raises: EnvironmentError: If the App Engine APIs are unavailable. """ + # pylint: disable=missing-raises-doc + # Pylint rightfully thinks EnvironmentError is OSError, but doesn't + # realize it's a valid alias. if app_identity is None: raise EnvironmentError( 'The App Engine APIs are not available.') @@ -92,6 +95,9 @@ def __init__(self, scopes=None, service_account_id=None): Raises: EnvironmentError: If the App Engine APIs are unavailable. """ + # pylint: disable=missing-raises-doc + # Pylint rightfully thinks EnvironmentError is OSError, but doesn't + # realize it's a valid alias. if app_identity is None: raise EnvironmentError( 'The App Engine APIs are not available.') diff --git a/packages/google-auth/google/auth/crypt.py b/packages/google-auth/google/auth/crypt.py index 6151b2921eda..1305cc8436be 100644 --- a/packages/google-auth/google/auth/crypt.py +++ b/packages/google-auth/google/auth/crypt.py @@ -204,7 +204,7 @@ def from_string(cls, key, key_id=None): key_id (str): An optional key id used to identify the private key. Returns: - ~google.auth.crypt.Signer: The constructed signer. + google.auth.crypt.Signer: The constructed signer. Raises: ValueError: If the key cannot be parsed as PKCS#1 or PKCS#8 in @@ -242,7 +242,7 @@ def from_service_account_info(cls, info): format. Returns: - ~google.auth.crypt.Signer: The constructed signer. + google.auth.crypt.Signer: The constructed signer. Raises: ValueError: If the info is not in the expected format. @@ -265,7 +265,7 @@ def from_service_account_file(cls, filename): filename (str): The path to the service account .json file. Returns: - ~google.auth.crypt.Signer: The constructed signer. + google.auth.crypt.Signer: The constructed signer. """ with io.open(filename, 'r', encoding='utf-8') as json_file: data = json.load(json_file) diff --git a/packages/google-auth/google/auth/transport/grpc.py b/packages/google-auth/google/auth/transport/grpc.py index 4062972aa742..e6a5eb776556 100644 --- a/packages/google-auth/google/auth/transport/grpc.py +++ b/packages/google-auth/google/auth/transport/grpc.py @@ -33,6 +33,10 @@ class AuthMetadataPlugin(grpc.AuthMetadataPlugin): object used to refresh credentials as needed. """ def __init__(self, credentials, request): + # pylint: disable=no-value-for-parameter + # pylint doesn't realize that the super method takes no arguments + # because this class is the same name as the superclass. + super(AuthMetadataPlugin, self).__init__() self._credentials = credentials self._request = request diff --git a/packages/google-auth/scripts/run_pylint.py b/packages/google-auth/scripts/run_pylint.py index b06a98c42819..e9ac7ec779d5 100644 --- a/packages/google-auth/scripts/run_pylint.py +++ b/packages/google-auth/scripts/run_pylint.py @@ -43,6 +43,7 @@ 'protected-access', 'redefined-variable-type', 'similarities', + 'no-else-return', ], }, } From fa2d9e5a1c6d4e586b74ec56b0774b12fd0081fc Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Tue, 10 Jan 2017 13:35:51 -0800 Subject: [PATCH 088/966] Add google.oauth2.flow - utility for doing OAuth 2.0 Authorization Flow (#100) --- packages/google-auth/docs/_static/custom.css | 4 + packages/google-auth/docs/conf.py | 2 + .../docs/reference/google.oauth2.flow.rst | 7 + .../docs/reference/google.oauth2.rst | 1 + packages/google-auth/google/oauth2/flow.py | 250 ++++++++++++++++++ packages/google-auth/setup.py | 7 + .../tests/data/client_secrets.json | 14 + .../google-auth/tests/oauth2/test_flow.py | 139 ++++++++++ packages/google-auth/tox.ini | 1 + 9 files changed, 425 insertions(+) create mode 100644 packages/google-auth/docs/reference/google.oauth2.flow.rst create mode 100644 packages/google-auth/google/oauth2/flow.py create mode 100644 packages/google-auth/tests/data/client_secrets.json create mode 100644 packages/google-auth/tests/oauth2/test_flow.py diff --git a/packages/google-auth/docs/_static/custom.css b/packages/google-auth/docs/_static/custom.css index cd83aa861c80..b54dd24b06d6 100644 --- a/packages/google-auth/docs/_static/custom.css +++ b/packages/google-auth/docs/_static/custom.css @@ -1,3 +1,7 @@ +div.document { + width: 1040px; +} + code.descname { color: #4885ed; } diff --git a/packages/google-auth/docs/conf.py b/packages/google-auth/docs/conf.py index b045c8ca3595..5dbac8790ac6 100644 --- a/packages/google-auth/docs/conf.py +++ b/packages/google-auth/docs/conf.py @@ -370,6 +370,8 @@ 'python': ('https://docs.python.org/3.5', None), 'urllib3': ('https://urllib3.readthedocs.io/en/stable', None), 'requests': ('http://docs.python-requests.org/en/stable', None), + 'requests-oauthlib': ( + 'http://requests-oauthlib.readthedocs.io/en/stable', None), } # Autodoc config diff --git a/packages/google-auth/docs/reference/google.oauth2.flow.rst b/packages/google-auth/docs/reference/google.oauth2.flow.rst new file mode 100644 index 000000000000..bae54084b951 --- /dev/null +++ b/packages/google-auth/docs/reference/google.oauth2.flow.rst @@ -0,0 +1,7 @@ +google.oauth2.flow module +========================= + +.. automodule:: google.oauth2.flow + :members: + :inherited-members: + :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.oauth2.rst b/packages/google-auth/docs/reference/google.oauth2.rst index adb9403efe28..5dc24068fcaa 100644 --- a/packages/google-auth/docs/reference/google.oauth2.rst +++ b/packages/google-auth/docs/reference/google.oauth2.rst @@ -12,6 +12,7 @@ Submodules .. toctree:: google.oauth2.credentials + google.oauth2.flow google.oauth2.id_token google.oauth2.service_account diff --git a/packages/google-auth/google/oauth2/flow.py b/packages/google-auth/google/oauth2/flow.py new file mode 100644 index 000000000000..69e73ffd7d6e --- /dev/null +++ b/packages/google-auth/google/oauth2/flow.py @@ -0,0 +1,250 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""OAuth 2.0 Authorization Flow + +This module provides integration with `requests-oauthlib`_ for running the +`OAuth 2.0 Authorization Flow`_ and acquiring user credentials. + +Here's an example of using the flow with the installed application +authorization flow:: + + import google.oauth2.flow + + # Create the flow using the client secrets file from the Google API + # Console. + flow = google.oauth2.flow.Flow.from_client_secrets_file( + 'path/to/client_secrets.json', + scopes=['profile', 'email'], + redirect_uri='urn:ietf:wg:oauth:2.0:oob') + + # Tell the user to go to the authorization URL. + auth_url, _ = flow.authorization_url(prompt='consent') + + print('Please go to this URL: {}'.format(auth_url)) + + # The user will get an authorization code. This code is used to get the + # access token. + code = input('Enter the authorization code: ') + flow.fetch_token(code=code) + + # You can use flow.credentials, or you can just get a requests session + # using flow.authorized_session. + session = flow.authorized_session() + print(session.get('https://www.googleapis.com/userinfo/v2/me').json()) + +.. _requests-oauthlib: http://requests-oauthlib.readthedocs.io/en/stable/ +.. _OAuth 2.0 Authorization Flow: + https://tools.ietf.org/html/rfc6749#section-1.2 +""" + +import json + +import requests_oauthlib + +import google.auth.transport.requests +import google.oauth2.credentials + +_REQUIRED_CONFIG_KEYS = frozenset(('auth_uri', 'token_uri', 'client_id')) + + +class Flow(object): + """OAuth 2.0 Authorization Flow + + This class uses a :class:`requests_oauthlib.OAuth2Session` instance at + :attr:`oauth2session` to perform all of the OAuth 2.0 logic. This class + just provides convenience methods and sane defaults for doing Google's + particular flavors of OAuth 2.0. + + Typically you'll construct an instance of this flow using + :meth:`from_client_secrets_file` and a `client secrets file`_ obtained + from the `Google API Console`_. + + .. _client secrets file: + https://developers.google.com/identity/protocols/OAuth2WebServer + #creatingcred + .. _Google API Console: + https://console.developers.google.com/apis/credentials + """ + + def __init__(self, client_config, scopes, **kwargs): + """ + Args: + client_config (Mapping[str, Any]): The client + configuration in the Google `client secrets`_ format. + scopes (Sequence[str]): The list of scopes to request during the + flow. + kwargs: Any additional parameters passed to + :class:`requests_oauthlib.OAuth2Session` + + Raises: + ValueError: If the client configuration is not in the correct + format. + + .. _client secrets: + https://developers.google.com/api-client-library/python/guide + /aaa_client_secrets + """ + self.client_config = None + """Mapping[str, Any]: The OAuth 2.0 client configuration.""" + self.client_type = None + """str: The client type, either ``'web'`` or ``'installed'``""" + + if 'web' in client_config: + self.client_config = client_config['web'] + self.client_type = 'web' + elif 'installed' in client_config: + self.client_config = client_config['installed'] + self.client_type = 'installed' + else: + raise ValueError( + 'Client secrets must be for a web or installed app.') + + if not _REQUIRED_CONFIG_KEYS.issubset(self.client_config.keys()): + raise ValueError('Client secrets is not in the correct format.') + + self.oauth2session = requests_oauthlib.OAuth2Session( + client_id=self.client_config['client_id'], + scope=scopes, + **kwargs) + """requests_oauthlib.OAuth2Session: The OAuth 2.0 session.""" + + @classmethod + def from_client_secrets_file(cls, client_secrets_file, scopes, **kwargs): + """Creates a :class:`Flow` instance from a Google client secrets file. + + Args: + client_secrets_file (str): The path to the client secrets .json + file. + scopes (Sequence[str]): The list of scopes to request during the + flow. + kwargs: Any additional parameters passed to + :class:`requests_oauthlib.OAuth2Session` + + Returns: + Flow: The constructed Flow instance. + """ + with open(client_secrets_file, 'r') as json_file: + client_config = json.load(json_file) + + return cls(client_config, scopes=scopes, **kwargs) + + @property + def redirect_uri(self): + """The OAuth 2.0 redirect URI. Pass-through to + ``self.oauth2session.redirect_uri``.""" + return self.oauth2session.redirect_uri + + @redirect_uri.setter + def redirect_uri(self, value): + self.oauth2session.redirect_uri = value + + def authorization_url(self, **kwargs): + """Generates an authorization URL. + + This is the first step in the OAuth 2.0 Authorization Flow. The user's + browser should be redirected to the returned URL. + + This method calls + :meth:`requests_oauthlib.OAuth2Session.authorization_url` + and specifies the client configuration's authorization URI (usually + Google's authorization server) and specifies that "offline" access is + desired. This is required in order to obtain a refresh token. + + Args: + kwargs: Additional arguments passed through to + :meth:`requests_oauthlib.OAuth2Session.authorization_url` + + Returns: + Tuple[str, str]: The generated authorization URL and state. The + user must visit the URL to complete the flow. The state is used + when completing the flow to verify that the request originated + from your application. If your application is using a different + :class:`Flow` instance to obtain the token, you will need to + specify the ``state`` when constructing the :class:`Flow`. + """ + url, state = self.oauth2session.authorization_url( + self.client_config['auth_uri'], + access_type='offline', **kwargs) + + return url, state + + def fetch_token(self, **kwargs): + """Completes the Authorization Flow and obtains an access token. + + This is the final step in the OAuth 2.0 Authorization Flow. This is + called after the user consents. + + This method calls + :meth:`requests_oauthlib.OAuth2Session.fetch_token` + and specifies the client configuration's token URI (usually Google's + token server). + + Args: + kwargs: Arguments passed through to + :meth:`requests_oauthlib.OAuth2Session.fetch_token`. At least + one of ``code`` or ``authorization_response`` must be + specified. + + Returns: + Mapping[str, str]: The obtained tokens. Typically, you will not use + return value of this function and instead and use + :meth:`credentials` to obtain a + :class:`~google.auth.credentials.Credentials` instance. + """ + return self.oauth2session.fetch_token( + self.client_config['token_uri'], + client_secret=self.client_config['client_secret'], + **kwargs) + + @property + def credentials(self): + """Returns credentials from the OAuth 2.0 session. + + :meth:`fetch_token` must be called before accessing this. This method + constructs a :class:`google.oauth2.credentials.Credentials` class using + the session's token and the client config. + + Returns: + google.oauth2.credentials.Credentials: The constructed credentials. + + Raises: + ValueError: If there is no access token in the session. + """ + if not self.oauth2session.token: + raise ValueError( + 'There is no access token for this session, did you call ' + 'fetch_token?') + + return google.oauth2.credentials.Credentials( + self.oauth2session.token['access_token'], + refresh_token=self.oauth2session.token['refresh_token'], + token_uri=self.client_config['token_uri'], + client_id=self.client_config['client_id'], + client_secret=self.client_config['client_secret'], + scopes=self.oauth2session.scope) + + def authorized_session(self): + """Returns a :class:`requests.Session` authorized with credentials. + + :meth:`fetch_token` must be called before this method. This method + constructs a :class:`google.auth.transport.requests.AuthorizedSession` + class using this flow's :attr:`credentials`. + + Returns: + google.auth.transport.requests.AuthorizedSession: The constructed + session. + """ + return google.auth.transport.requests.AuthorizedSession( + self.credentials) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index ca3184f88066..8dc9e717d591 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -23,6 +23,10 @@ 'six>=1.9.0', ) +EXTRA_OAUTHLIB_DEPENDENCIES = ( + 'requests-oauthlib>=0.7.0', +) + with open('README.rst', 'r') as fh: long_description = fh.read() @@ -38,6 +42,9 @@ packages=find_packages(exclude=('tests', 'system_tests')), namespace_packages=('google',), install_requires=DEPENDENCIES, + extras_require={ + 'oauthlib': EXTRA_OAUTHLIB_DEPENDENCIES, + }, license='Apache 2.0', keywords='google auth oauth client', classifiers=( diff --git a/packages/google-auth/tests/data/client_secrets.json b/packages/google-auth/tests/data/client_secrets.json new file mode 100644 index 000000000000..1baa4995aff5 --- /dev/null +++ b/packages/google-auth/tests/data/client_secrets.json @@ -0,0 +1,14 @@ +{ + "web": { + "client_id": "example.apps.googleusercontent.com", + "project_id": "example", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://accounts.google.com/o/oauth2/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_secret": "itsasecrettoeveryone", + "redirect_uris": [ + "urn:ietf:wg:oauth:2.0:oob", + "http://localhost" + ] + } +} diff --git a/packages/google-auth/tests/oauth2/test_flow.py b/packages/google-auth/tests/oauth2/test_flow.py new file mode 100644 index 000000000000..7fc268ccb9a7 --- /dev/null +++ b/packages/google-auth/tests/oauth2/test_flow.py @@ -0,0 +1,139 @@ +# Copyright 2017 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import os + +import mock +import pytest + +from google.oauth2 import flow + +DATA_DIR = os.path.join(os.path.dirname(__file__), '..', 'data') +CLIENT_SECRETS_FILE = os.path.join(DATA_DIR, 'client_secrets.json') + +with open(CLIENT_SECRETS_FILE, 'r') as fh: + CLIENT_SECRETS_INFO = json.load(fh) + + +def test_constructor_web(): + instance = flow.Flow(CLIENT_SECRETS_INFO, scopes=mock.sentinel.scopes) + assert instance.client_config == CLIENT_SECRETS_INFO['web'] + assert (instance.oauth2session.client_id == + CLIENT_SECRETS_INFO['web']['client_id']) + assert instance.oauth2session.scope == mock.sentinel.scopes + + +def test_constructor_installed(): + info = {'installed': CLIENT_SECRETS_INFO['web']} + instance = flow.Flow(info, scopes=mock.sentinel.scopes) + assert instance.client_config == info['installed'] + assert instance.oauth2session.client_id == info['installed']['client_id'] + assert instance.oauth2session.scope == mock.sentinel.scopes + + +def test_constructor_bad_format(): + with pytest.raises(ValueError): + flow.Flow({}, scopes=[]) + + +def test_constructor_missing_keys(): + with pytest.raises(ValueError): + flow.Flow({'web': {}}, scopes=[]) + + +def test_from_client_secrets_file(): + instance = flow.Flow.from_client_secrets_file( + CLIENT_SECRETS_FILE, scopes=mock.sentinel.scopes) + assert instance.client_config == CLIENT_SECRETS_INFO['web'] + assert (instance.oauth2session.client_id == + CLIENT_SECRETS_INFO['web']['client_id']) + assert instance.oauth2session.scope == mock.sentinel.scopes + + +@pytest.fixture +def instance(): + yield flow.Flow(CLIENT_SECRETS_INFO, scopes=mock.sentinel.scopes) + + +def test_redirect_uri(instance): + instance.redirect_uri = mock.sentinel.redirect_uri + assert (instance.redirect_uri == + instance.oauth2session.redirect_uri == + mock.sentinel.redirect_uri) + + +def test_authorization_url(instance): + scope = 'scope_one' + instance.oauth2session.scope = [scope] + authorization_url_patch = mock.patch.object( + instance.oauth2session, 'authorization_url', + wraps=instance.oauth2session.authorization_url) + + with authorization_url_patch as authorization_url_spy: + url, _ = instance.authorization_url(prompt='consent') + + assert CLIENT_SECRETS_INFO['web']['auth_uri'] in url + assert scope in url + authorization_url_spy.assert_called_with( + CLIENT_SECRETS_INFO['web']['auth_uri'], + access_type='offline', + prompt='consent') + + +def test_fetch_token(instance): + fetch_token_patch = mock.patch.object( + instance.oauth2session, 'fetch_token', autospec=True, + return_value=mock.sentinel.token) + + with fetch_token_patch as fetch_token_mock: + token = instance.fetch_token(code=mock.sentinel.code) + + assert token == mock.sentinel.token + fetch_token_mock.assert_called_with( + CLIENT_SECRETS_INFO['web']['token_uri'], + client_secret=CLIENT_SECRETS_INFO['web']['client_secret'], + code=mock.sentinel.code) + + +def test_credentials(instance): + instance.oauth2session.token = { + 'access_token': mock.sentinel.access_token, + 'refresh_token': mock.sentinel.refresh_token + } + + credentials = instance.credentials + + assert credentials.token == mock.sentinel.access_token + assert credentials._refresh_token == mock.sentinel.refresh_token + assert credentials._client_id == CLIENT_SECRETS_INFO['web']['client_id'] + assert (credentials._client_secret == + CLIENT_SECRETS_INFO['web']['client_secret']) + assert credentials._token_uri == CLIENT_SECRETS_INFO['web']['token_uri'] + + +def test_bad_credentials(instance): + with pytest.raises(ValueError): + assert instance.credentials + + +def test_authorized_session(instance): + instance.oauth2session.token = { + 'access_token': mock.sentinel.access_token, + 'refresh_token': mock.sentinel.refresh_token + } + + session = instance.authorized_session() + + assert session.credentials.token == mock.sentinel.access_token diff --git a/packages/google-auth/tox.ini b/packages/google-auth/tox.ini index ad760bd662f0..0465200595a3 100644 --- a/packages/google-auth/tox.ini +++ b/packages/google-auth/tox.ini @@ -11,6 +11,7 @@ deps = urllib3 certifi requests + requests-oauthlib oauth2client grpcio; platform_python_implementation != 'PyPy' commands = From 67c729bbc0ff27e1a65da7141e639bdc7ecbf806 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Wed, 11 Jan 2017 13:29:02 -0800 Subject: [PATCH 089/966] Remove experimental tag --- packages/google-auth/README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/README.rst b/packages/google-auth/README.rst index 5767d87f90af..5a6b188abde4 100644 --- a/packages/google-auth/README.rst +++ b/packages/google-auth/README.rst @@ -1,5 +1,5 @@ -Google Auth Python Library (Experimental) -========================================= +Google Auth Python Library +========================== |build| |docs| |pypi| From d95954191b3d96b0d698f50d08f2446996bc0a06 Mon Sep 17 00:00:00 2001 From: Jakob Holmelund Date: Tue, 31 Jan 2017 21:36:18 +0100 Subject: [PATCH 090/966] Fixing typo in google_auth_httplib2 README (#103) --- packages/google-auth/httplib2_transport/README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/httplib2_transport/README.rst b/packages/google-auth/httplib2_transport/README.rst index 09405aea6b99..15477d0a6199 100644 --- a/packages/google-auth/httplib2_transport/README.rst +++ b/packages/google-auth/httplib2_transport/README.rst @@ -21,7 +21,7 @@ Installing You can install using `pip`_:: - $ pip install google-auth-httlib2 + $ pip install google-auth-httplib2 .. _pip: https://pip.pypa.io/en/stable/ From 25d1288bedfbe10bd768822320843cb93877039b Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Wed, 8 Feb 2017 13:14:02 -0800 Subject: [PATCH 091/966] Add warning about oauth2.flow (#105) --- packages/google-auth/google/oauth2/flow.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/google-auth/google/oauth2/flow.py b/packages/google-auth/google/oauth2/flow.py index 69e73ffd7d6e..a4dcfca77ff6 100644 --- a/packages/google-auth/google/oauth2/flow.py +++ b/packages/google-auth/google/oauth2/flow.py @@ -14,6 +14,10 @@ """OAuth 2.0 Authorization Flow +.. warning:: + This module is experimental and is subject to change signficantly + within major version releases. + This module provides integration with `requests-oauthlib`_ for running the `OAuth 2.0 Authorization Flow`_ and acquiring user credentials. From 960060cdb1ff34299d9af1c8e10daf4298da3baa Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Wed, 8 Feb 2017 14:43:38 -0800 Subject: [PATCH 092/966] Refactor common code from google.oauth2.flow to google.oauth2.oauthlib (#106) --- .../docs/reference/google.oauth2.oauthlib.rst | 7 + .../docs/reference/google.oauth2.rst | 1 + packages/google-auth/google/oauth2/flow.py | 72 ++++----- .../google-auth/google/oauth2/oauthlib.py | 142 ++++++++++++++++++ .../google-auth/tests/oauth2/test_flow.py | 43 ++---- .../google-auth/tests/oauth2/test_oauthlib.py | 92 ++++++++++++ 6 files changed, 294 insertions(+), 63 deletions(-) create mode 100644 packages/google-auth/docs/reference/google.oauth2.oauthlib.rst create mode 100644 packages/google-auth/google/oauth2/oauthlib.py create mode 100644 packages/google-auth/tests/oauth2/test_oauthlib.py diff --git a/packages/google-auth/docs/reference/google.oauth2.oauthlib.rst b/packages/google-auth/docs/reference/google.oauth2.oauthlib.rst new file mode 100644 index 000000000000..c687490a4099 --- /dev/null +++ b/packages/google-auth/docs/reference/google.oauth2.oauthlib.rst @@ -0,0 +1,7 @@ +google.oauth2.oauthlib module +============================= + +.. automodule:: google.oauth2.oauthlib + :members: + :inherited-members: + :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.oauth2.rst b/packages/google-auth/docs/reference/google.oauth2.rst index 5dc24068fcaa..4dd1ebd97bd2 100644 --- a/packages/google-auth/docs/reference/google.oauth2.rst +++ b/packages/google-auth/docs/reference/google.oauth2.rst @@ -14,5 +14,6 @@ Submodules google.oauth2.credentials google.oauth2.flow google.oauth2.id_token + google.oauth2.oauthlib google.oauth2.service_account diff --git a/packages/google-auth/google/oauth2/flow.py b/packages/google-auth/google/oauth2/flow.py index a4dcfca77ff6..e70ee3dd3258 100644 --- a/packages/google-auth/google/oauth2/flow.py +++ b/packages/google-auth/google/oauth2/flow.py @@ -55,12 +55,9 @@ import json -import requests_oauthlib - import google.auth.transport.requests import google.oauth2.credentials - -_REQUIRED_CONFIG_KEYS = frozenset(('auth_uri', 'token_uri', 'client_id')) +import google.oauth2.oauthlib class Flow(object): @@ -82,8 +79,32 @@ class Flow(object): https://console.developers.google.com/apis/credentials """ - def __init__(self, client_config, scopes, **kwargs): + def __init__(self, oauth2session, client_type, client_config): """ + Args: + oauth2session (requests_oauthlib.OAuth2Session): + The OAuth 2.0 session from ``requests-oauthlib``. + client_type (str): The client type, either ``web`` or + ``installed``. + client_config (Mapping[str, Any]): The client + configuration in the Google `client secrets`_ format. + + .. _client secrets: + https://developers.google.com/api-client-library/python/guide + /aaa_client_secrets + """ + self.client_type = client_type + """str: The client type, either ``'web'`` or ``'installed'``""" + self.client_config = client_config[client_type] + """Mapping[str, Any]: The OAuth 2.0 client configuration.""" + self.oauth2session = oauth2session + """requests_oauthlib.OAuth2Session: The OAuth 2.0 session.""" + + @classmethod + def from_client_config(cls, client_config, scopes, **kwargs): + """Creates a :class:`requests_oauthlib.OAuth2Session` from client + configuration loaded from a Google-format client secrets file. + Args: client_config (Mapping[str, Any]): The client configuration in the Google `client secrets`_ format. @@ -92,6 +113,9 @@ def __init__(self, client_config, scopes, **kwargs): kwargs: Any additional parameters passed to :class:`requests_oauthlib.OAuth2Session` + Returns: + Flow: The constructed Flow instance. + Raises: ValueError: If the client configuration is not in the correct format. @@ -100,29 +124,19 @@ def __init__(self, client_config, scopes, **kwargs): https://developers.google.com/api-client-library/python/guide /aaa_client_secrets """ - self.client_config = None - """Mapping[str, Any]: The OAuth 2.0 client configuration.""" - self.client_type = None - """str: The client type, either ``'web'`` or ``'installed'``""" - if 'web' in client_config: - self.client_config = client_config['web'] - self.client_type = 'web' + client_type = 'web' elif 'installed' in client_config: - self.client_config = client_config['installed'] - self.client_type = 'installed' + client_type = 'installed' else: raise ValueError( 'Client secrets must be for a web or installed app.') - if not _REQUIRED_CONFIG_KEYS.issubset(self.client_config.keys()): - raise ValueError('Client secrets is not in the correct format.') + session, client_config = ( + google.oauth2.oauthlib.session_from_client_config( + client_config, scopes, **kwargs)) - self.oauth2session = requests_oauthlib.OAuth2Session( - client_id=self.client_config['client_id'], - scope=scopes, - **kwargs) - """requests_oauthlib.OAuth2Session: The OAuth 2.0 session.""" + return cls(session, client_type, client_config) @classmethod def from_client_secrets_file(cls, client_secrets_file, scopes, **kwargs): @@ -142,7 +156,7 @@ def from_client_secrets_file(cls, client_secrets_file, scopes, **kwargs): with open(client_secrets_file, 'r') as json_file: client_config = json.load(json_file) - return cls(client_config, scopes=scopes, **kwargs) + return cls.from_client_config(client_config, scopes=scopes, **kwargs) @property def redirect_uri(self): @@ -226,18 +240,8 @@ def credentials(self): Raises: ValueError: If there is no access token in the session. """ - if not self.oauth2session.token: - raise ValueError( - 'There is no access token for this session, did you call ' - 'fetch_token?') - - return google.oauth2.credentials.Credentials( - self.oauth2session.token['access_token'], - refresh_token=self.oauth2session.token['refresh_token'], - token_uri=self.client_config['token_uri'], - client_id=self.client_config['client_id'], - client_secret=self.client_config['client_secret'], - scopes=self.oauth2session.scope) + return google.oauth2.oauthlib.credentials_from_session( + self.oauth2session, self.client_config) def authorized_session(self): """Returns a :class:`requests.Session` authorized with credentials. diff --git a/packages/google-auth/google/oauth2/oauthlib.py b/packages/google-auth/google/oauth2/oauthlib.py new file mode 100644 index 000000000000..8f5c10575c34 --- /dev/null +++ b/packages/google-auth/google/oauth2/oauthlib.py @@ -0,0 +1,142 @@ +# Copyright 2017 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Integration with oauthlib + +.. warning:: + This module is experimental and is subject to change signficantly + within major version releases. + +This module provides helpers for integrating with `requests-oauthlib`_. +Typically, you'll want to use the higher-level helpers in +:mod:`google.oauth2.flow`. + +.. _requests-oauthlib: http://requests-oauthlib.readthedocs.io/en/stable/ +""" + +import json + +import requests_oauthlib + +import google.oauth2.credentials + +_REQUIRED_CONFIG_KEYS = frozenset(('auth_uri', 'token_uri', 'client_id')) + + +def session_from_client_config(client_config, scopes, **kwargs): + """Creates a :class:`requests_oauthlib.OAuth2Session` from client + configuration loaded from a Google-format client secrets file. + + Args: + client_config (Mapping[str, Any]): The client + configuration in the Google `client secrets`_ format. + scopes (Sequence[str]): The list of scopes to request during the + flow. + kwargs: Any additional parameters passed to + :class:`requests_oauthlib.OAuth2Session` + + Raises: + ValueError: If the client configuration is not in the correct + format. + + Returns: + Tuple[requests_oauthlib.OAuth2Session, Mapping[str, Any]]: The new + oauthlib session and the validated client configuration. + + .. _client secrets: + https://developers.google.com/api-client-library/python/guide + /aaa_client_secrets + """ + + if 'web' in client_config: + config = client_config['web'] + elif 'installed' in client_config: + config = client_config['installed'] + else: + raise ValueError( + 'Client secrets must be for a web or installed app.') + + if not _REQUIRED_CONFIG_KEYS.issubset(config.keys()): + raise ValueError('Client secrets is not in the correct format.') + + session = requests_oauthlib.OAuth2Session( + client_id=config['client_id'], + scope=scopes, + **kwargs) + + return session, client_config + + +def session_from_client_secrets_file(client_secrets_file, scopes, **kwargs): + """Creates a :class:`requests_oauthlib.OAuth2Session` instance from a + Google-format client secrets file. + + Args: + client_secrets_file (str): The path to the `client secrets`_ .json + file. + scopes (Sequence[str]): The list of scopes to request during the + flow. + kwargs: Any additional parameters passed to + :class:`requests_oauthlib.OAuth2Session` + + Returns: + Tuple[requests_oauthlib.OAuth2Session, Mapping[str, Any]]: The new + oauthlib session and the validated client configuration. + + .. _client secrets: + https://developers.google.com/api-client-library/python/guide + /aaa_client_secrets + """ + with open(client_secrets_file, 'r') as json_file: + client_config = json.load(json_file) + + return session_from_client_config(client_config, scopes, **kwargs) + + +def credentials_from_session(session, client_config=None): + """Creates :class:`google.oauth2.credentials.Credentials` from a + :class:`requests_oauthlib.OAuth2Session`. + + :meth:`fetch_token` must be called on the session before before calling + this. This uses the session's auth token and the provided client + configuration to create :class:`google.oauth2.credentials.Credentials`. + This allows you to use the credentials from the session with Google + API client libraries. + + Args: + session (requests_oauthlib.OAuth2Session): The OAuth 2.0 session. + client_config (Mapping[str, Any]): The subset of the client + configuration to use. For example, if you have a web client + you would pass in `client_config['web']`. + + Returns: + google.oauth2.credentials.Credentials: The constructed credentials. + + Raises: + ValueError: If there is no access token in the session. + """ + client_config = client_config if client_config is not None else {} + + if not session.token: + raise ValueError( + 'There is no access token for this session, did you call ' + 'fetch_token?') + + return google.oauth2.credentials.Credentials( + session.token['access_token'], + refresh_token=session.token.get('refresh_token'), + token_uri=client_config.get('token_uri'), + client_id=client_config.get('client_id'), + client_secret=client_config.get('client_secret'), + scopes=session.scope) diff --git a/packages/google-auth/tests/oauth2/test_flow.py b/packages/google-auth/tests/oauth2/test_flow.py index 7fc268ccb9a7..e5d108fd5282 100644 --- a/packages/google-auth/tests/oauth2/test_flow.py +++ b/packages/google-auth/tests/oauth2/test_flow.py @@ -27,44 +27,34 @@ CLIENT_SECRETS_INFO = json.load(fh) -def test_constructor_web(): - instance = flow.Flow(CLIENT_SECRETS_INFO, scopes=mock.sentinel.scopes) +def test_from_client_secrets_file(): + instance = flow.Flow.from_client_secrets_file( + CLIENT_SECRETS_FILE, scopes=mock.sentinel.scopes) assert instance.client_config == CLIENT_SECRETS_INFO['web'] assert (instance.oauth2session.client_id == CLIENT_SECRETS_INFO['web']['client_id']) assert instance.oauth2session.scope == mock.sentinel.scopes -def test_constructor_installed(): - info = {'installed': CLIENT_SECRETS_INFO['web']} - instance = flow.Flow(info, scopes=mock.sentinel.scopes) - assert instance.client_config == info['installed'] - assert instance.oauth2session.client_id == info['installed']['client_id'] +def test_from_client_config_installed(): + client_config = {'installed': CLIENT_SECRETS_INFO['web']} + instance = flow.Flow.from_client_config( + client_config, scopes=mock.sentinel.scopes) + assert instance.client_config == client_config['installed'] + assert (instance.oauth2session.client_id == + client_config['installed']['client_id']) assert instance.oauth2session.scope == mock.sentinel.scopes -def test_constructor_bad_format(): - with pytest.raises(ValueError): - flow.Flow({}, scopes=[]) - - -def test_constructor_missing_keys(): +def test_from_client_config_bad_format(): with pytest.raises(ValueError): - flow.Flow({'web': {}}, scopes=[]) - - -def test_from_client_secrets_file(): - instance = flow.Flow.from_client_secrets_file( - CLIENT_SECRETS_FILE, scopes=mock.sentinel.scopes) - assert instance.client_config == CLIENT_SECRETS_INFO['web'] - assert (instance.oauth2session.client_id == - CLIENT_SECRETS_INFO['web']['client_id']) - assert instance.oauth2session.scope == mock.sentinel.scopes + flow.Flow.from_client_config({}, scopes=mock.sentinel.scopes) @pytest.fixture def instance(): - yield flow.Flow(CLIENT_SECRETS_INFO, scopes=mock.sentinel.scopes) + yield flow.Flow.from_client_config( + CLIENT_SECRETS_INFO, scopes=mock.sentinel.scopes) def test_redirect_uri(instance): @@ -123,11 +113,6 @@ def test_credentials(instance): assert credentials._token_uri == CLIENT_SECRETS_INFO['web']['token_uri'] -def test_bad_credentials(instance): - with pytest.raises(ValueError): - assert instance.credentials - - def test_authorized_session(instance): instance.oauth2session.token = { 'access_token': mock.sentinel.access_token, diff --git a/packages/google-auth/tests/oauth2/test_oauthlib.py b/packages/google-auth/tests/oauth2/test_oauthlib.py new file mode 100644 index 000000000000..a16c9044cb13 --- /dev/null +++ b/packages/google-auth/tests/oauth2/test_oauthlib.py @@ -0,0 +1,92 @@ +# Copyright 2017 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import os + +import mock +import pytest + +from google.oauth2 import oauthlib + +DATA_DIR = os.path.join(os.path.dirname(__file__), '..', 'data') +CLIENT_SECRETS_FILE = os.path.join(DATA_DIR, 'client_secrets.json') + +with open(CLIENT_SECRETS_FILE, 'r') as fh: + CLIENT_SECRETS_INFO = json.load(fh) + + +def test_session_from_client_config_web(): + session, config = oauthlib.session_from_client_config( + CLIENT_SECRETS_INFO, scopes=mock.sentinel.scopes) + + assert config == CLIENT_SECRETS_INFO + assert session.client_id == CLIENT_SECRETS_INFO['web']['client_id'] + assert session.scope == mock.sentinel.scopes + + +def test_session_from_client_config_installed(): + info = {'installed': CLIENT_SECRETS_INFO['web']} + session, config = oauthlib.session_from_client_config( + info, scopes=mock.sentinel.scopes) + assert config == info + assert session.client_id == info['installed']['client_id'] + assert session.scope == mock.sentinel.scopes + + +def test_session_from_client_config_bad_format(): + with pytest.raises(ValueError): + oauthlib.session_from_client_config({}, scopes=[]) + + +def test_session_from_client_config_missing_keys(): + with pytest.raises(ValueError): + oauthlib.session_from_client_config({'web': {}}, scopes=[]) + + +def test_session_from_client_secrets_file(): + session, config = oauthlib.session_from_client_secrets_file( + CLIENT_SECRETS_FILE, scopes=mock.sentinel.scopes) + assert config == CLIENT_SECRETS_INFO + assert session.client_id == CLIENT_SECRETS_INFO['web']['client_id'] + assert session.scope == mock.sentinel.scopes + + +@pytest.fixture +def session(): + session, _ = oauthlib.session_from_client_config( + CLIENT_SECRETS_INFO, scopes=mock.sentinel.scopes) + yield session + + +def test_credentials_from_session(session): + session.token = { + 'access_token': mock.sentinel.access_token, + 'refresh_token': mock.sentinel.refresh_token + } + + credentials = oauthlib.credentials_from_session( + session, CLIENT_SECRETS_INFO['web']) + + assert credentials.token == mock.sentinel.access_token + assert credentials._refresh_token == mock.sentinel.refresh_token + assert credentials._client_id == CLIENT_SECRETS_INFO['web']['client_id'] + assert (credentials._client_secret == + CLIENT_SECRETS_INFO['web']['client_secret']) + assert credentials._token_uri == CLIENT_SECRETS_INFO['web']['token_uri'] + + +def test_bad_credentials(session): + with pytest.raises(ValueError): + oauthlib.credentials_from_session(session) From bd6fd211033a47841efd7ffaac88c50f675db9f1 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Wed, 8 Feb 2017 14:52:13 -0800 Subject: [PATCH 093/966] Release v0.6.0 (#107) --- packages/google-auth/CHANGELOG.rst | 6 ++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.rst b/packages/google-auth/CHANGELOG.rst index b54447435d8a..3cd54808b484 100644 --- a/packages/google-auth/CHANGELOG.rst +++ b/packages/google-auth/CHANGELOG.rst @@ -1,6 +1,12 @@ Changelog ========= +v0.6.0 +------ + +- Added experimental integration with ``requests-oauthlib`` in ``google.oauth2.oauthlib`` and ``google.oauth2.flow``. (#100, #105, #106) +- Fixed typo in ``google_auth_httplib2``'s README. (#105) + v0.5.0 ------ diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 8dc9e717d591..644c52dd9904 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -33,7 +33,7 @@ setup( name='google-auth', - version='0.5.0', + version='0.6.0', author='Google Cloud Platform', author_email='jonwayne+google-auth@google.com', description='Google Authentication Library', From 0f0973625471f41bb943405b191e5ba31fc16f34 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Wed, 8 Feb 2017 14:58:29 -0800 Subject: [PATCH 094/966] Fix missing documentation dependency --- packages/google-auth/docs/requirements-docs.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/google-auth/docs/requirements-docs.txt b/packages/google-auth/docs/requirements-docs.txt index aa96755e2f70..8dabaf9d6315 100644 --- a/packages/google-auth/docs/requirements-docs.txt +++ b/packages/google-auth/docs/requirements-docs.txt @@ -1,3 +1,4 @@ sphinx-docstring-typing urllib3 requests +requests-oauthlib From d7c7a5b876d1fd23af22120ccc6db04b44b7b865 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Wed, 8 Feb 2017 14:59:54 -0800 Subject: [PATCH 095/966] Use same dependencies for tox -e docs and readthedocs --- packages/google-auth/tox.ini | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/google-auth/tox.ini b/packages/google-auth/tox.ini index 0465200595a3..0a98945c1458 100644 --- a/packages/google-auth/tox.ini +++ b/packages/google-auth/tox.ini @@ -65,7 +65,6 @@ commands = [testenv:docs] basepython = python3.5 deps = - {[testenv]deps} sphinx -r{toxinidir}/docs/requirements-docs.txt commands = make -C docs html From 07a35dcb7b9f59ebd99b58bb634cc571999d9b36 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Wed, 15 Feb 2017 16:43:23 -0800 Subject: [PATCH 096/966] Add IAM signer (#108) --- .../docs/reference/google.auth.iam.rst | 7 ++ .../docs/reference/google.auth.rst | 1 + packages/google-auth/google/auth/crypt.py | 1 + packages/google-auth/google/auth/iam.py | 117 ++++++++++++++++++ packages/google-auth/tests/test_iam.py | 100 +++++++++++++++ 5 files changed, 226 insertions(+) create mode 100644 packages/google-auth/docs/reference/google.auth.iam.rst create mode 100644 packages/google-auth/google/auth/iam.py create mode 100644 packages/google-auth/tests/test_iam.py diff --git a/packages/google-auth/docs/reference/google.auth.iam.rst b/packages/google-auth/docs/reference/google.auth.iam.rst new file mode 100644 index 000000000000..8a5edb450ee4 --- /dev/null +++ b/packages/google-auth/docs/reference/google.auth.iam.rst @@ -0,0 +1,7 @@ +google.auth.iam module +====================== + +.. automodule:: google.auth.iam + :members: + :inherited-members: + :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.auth.rst b/packages/google-auth/docs/reference/google.auth.rst index 2d50672415da..244d0bbd3b39 100644 --- a/packages/google-auth/docs/reference/google.auth.rst +++ b/packages/google-auth/docs/reference/google.auth.rst @@ -24,5 +24,6 @@ Submodules google.auth.crypt google.auth.environment_vars google.auth.exceptions + google.auth.iam google.auth.jwt diff --git a/packages/google-auth/google/auth/crypt.py b/packages/google-auth/google/auth/crypt.py index 1305cc8436be..05839b46f691 100644 --- a/packages/google-auth/google/auth/crypt.py +++ b/packages/google-auth/google/auth/crypt.py @@ -182,6 +182,7 @@ class Signer(object): def __init__(self, private_key, key_id=None): self._key = private_key self.key_id = key_id + """Optional[str]: The key ID used to identify this private key.""" def sign(self, message): """Signs a message. diff --git a/packages/google-auth/google/auth/iam.py b/packages/google-auth/google/auth/iam.py new file mode 100644 index 000000000000..efa3127100b1 --- /dev/null +++ b/packages/google-auth/google/auth/iam.py @@ -0,0 +1,117 @@ +# Copyright 2017 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tools for using the Google `Cloud Identity and Access Management (IAM) +API`_'s auth-related functionality. + +.. _Cloud Identity and Access Management (IAM) API: + https://cloud.google.com/iam/docs/ +""" + +import base64 +import json + +from six.moves import http_client + +from google.auth import _helpers +from google.auth import exceptions + +_IAM_API_ROOT_URI = 'https://iam.googleapis.com/v1' +_SIGN_BLOB_URI = ( + _IAM_API_ROOT_URI + '/projects/-/serviceAccounts/{}:signBlob?alt=json') + + +class Signer(object): + """Signs messages using the IAM `signBlob API`_. + + This is useful when you need to sign bytes but do not have access to the + credential's private key file. + + .. warning:: + The IAM API signs bytes using Google-managed keys. Because of this + it's possible that the key used to sign bytes will change. In some + cases this change can occur between successive calls to :attr:`key_id` + and :meth:`sign`. This could result in a signature that was signed + with a different key than the one indicated by :attr:`key_id`. It's + recommended that if you use this in your code that you account for + this behavior by building in retry logic. + + .. _signBlob API: + https://cloud.google.com/iam/reference/rest/v1/projects.serviceAccounts + /signBlob + """ + + def __init__(self, request, credentials, service_account_email): + """ + Args: + request (google.auth.transport.Request): The object used to make + HTTP requests. + credentials (google.auth.credentials.Credentials): The credentials + that will be used to authenticate the request to the IAM API. + The credentials must have of one the following scopes: + + - https://www.googleapis.com/auth/iam + - https://www.googleapis.com/auth/cloud-platform + service_account_email (str): The service account email identifying + which service account to use to sign bytes. Often, this can + be the same as the service account email in the given + credentials. + """ + self._request = request + self._credentials = credentials + self._service_account_email = service_account_email + + def _make_signing_request(self, message): + """Makes a request to the API signBlob API.""" + message = _helpers.to_bytes(message) + + method = 'POST' + url = _SIGN_BLOB_URI.format(self._service_account_email) + headers = {} + body = json.dumps({ + 'bytesToSign': base64.b64encode(message).decode('utf-8'), + }) + + self._credentials.before_request(self._request, method, url, headers) + response = self._request( + url=url, method=method, body=body, headers=headers) + + if response.status != http_client.OK: + raise exceptions.TransportError( + 'Error calling the IAM signBytes API: {}'.format( + response.data)) + + return json.loads(response.data.decode('utf-8')) + + @property + def key_id(self): + """Optional[str]: The key ID used to identify this private key. + + .. note:: + This makes an API request to the IAM API. + """ + response = self._make_signing_request('') + return response['keyId'] + + def sign(self, message): + """Signs a message. + + Args: + message (Union[str, bytes]): The message to be signed. + + Returns: + bytes: The signature of the message. + """ + response = self._make_signing_request(message) + return base64.b64decode(response['signature']) diff --git a/packages/google-auth/tests/test_iam.py b/packages/google-auth/tests/test_iam.py new file mode 100644 index 000000000000..5ac991168839 --- /dev/null +++ b/packages/google-auth/tests/test_iam.py @@ -0,0 +1,100 @@ +# Copyright 2017 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import base64 +import datetime +import json + +import mock +import pytest +from six.moves import http_client + +from google.auth import exceptions +from google.auth import iam +from google.auth import transport +import google.auth.credentials + + +def make_request(status, data=None): + response = mock.Mock(spec=transport.Response) + response.status = status + + if data is not None: + response.data = json.dumps(data).encode('utf-8') + + return mock.Mock(return_value=response, spec=transport.Request) + + +def make_credentials(): + class CredentialsImpl(google.auth.credentials.Credentials): + def __init__(self): + super(CredentialsImpl, self).__init__() + self.token = 'token' + # Force refresh + self.expiry = datetime.datetime.min + + def refresh(self, request): + pass + + return CredentialsImpl() + + +class TestSigner(object): + def test_constructor(self): + request = mock.sentinel.request + credentials = mock.Mock(spec=google.auth.credentials.Credentials) + + signer = iam.Signer( + request, credentials, mock.sentinel.service_account_email) + + assert signer._request == mock.sentinel.request + assert signer._credentials == credentials + assert (signer._service_account_email == + mock.sentinel.service_account_email) + + def test_key_id(self): + key_id = '123' + request = make_request(http_client.OK, data={'keyId': key_id}) + credentials = make_credentials() + + signer = iam.Signer( + request, credentials, mock.sentinel.service_account_email) + + assert signer.key_id == '123' + auth_header = request.call_args[1]['headers']['authorization'] + assert auth_header == 'Bearer token' + + def test_sign_bytes(self): + signature = b'DEADBEEF' + encoded_signature = base64.b64encode(signature).decode('utf-8') + request = make_request( + http_client.OK, data={'signature': encoded_signature}) + credentials = make_credentials() + + signer = iam.Signer( + request, credentials, mock.sentinel.service_account_email) + + returned_signature = signer.sign('123') + + assert returned_signature == signature + + def test_sign_bytes_failure(self): + request = make_request(http_client.UNAUTHORIZED) + credentials = make_credentials() + + signer = iam.Signer( + request, credentials, mock.sentinel.service_account_email) + + with pytest.raises(exceptions.TransportError): + signer.sign('123') From 244f33878781103473bb8772ecb9dfd97d5910ba Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Wed, 15 Feb 2017 16:44:00 -0800 Subject: [PATCH 097/966] Fix issue where GAE Signer erroneously returns a tuple from sign() (#109) --- .../google-auth/google/auth/app_engine.py | 26 ++++++++++++++++--- packages/google-auth/tests/test_app_engine.py | 25 +++++++++++++++++- 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/packages/google-auth/google/auth/app_engine.py b/packages/google-auth/google/auth/app_engine.py index ec89dc0ea4c4..8b17e76ce690 100644 --- a/packages/google-auth/google/auth/app_engine.py +++ b/packages/google-auth/google/auth/app_engine.py @@ -34,13 +34,30 @@ class Signer(object): - """Signs messages using the App Engine app identity service. + """Signs messages using the App Engine App Identity service. This can be used in place of :class:`google.auth.crypt.Signer` when running in the App Engine standard environment. + + .. warning:: + The App Identity service signs bytes using Google-managed keys. + Because of this it's possible that the key used to sign bytes will + change. In some cases this change can occur between successive calls + to :attr:`key_id` and :meth:`sign`. This could result in a signature + that was signed with a different key than the one indicated by + :attr:`key_id`. It's recommended that if you use this in your code + that you account for this behavior by building in retry logic. """ - def __init__(self): - self.key_id = None + + @property + def key_id(self): + """Optional[str]: The key ID used to identify this private key. + + .. note:: + This makes a request to the App Identity service. + """ + key_id, _ = app_identity.sign_blob(b'') + return key_id @staticmethod def sign(message): @@ -53,7 +70,8 @@ def sign(message): bytes: The signature of the message. """ message = _helpers.to_bytes(message) - return app_identity.sign_blob(message) + _, signature = app_identity.sign_blob(message) + return signature def get_project_id(): diff --git a/packages/google-auth/tests/test_app_engine.py b/packages/google-auth/tests/test_app_engine.py index 117533ebfcb0..dd410d9d2558 100644 --- a/packages/google-auth/tests/test_app_engine.py +++ b/packages/google-auth/tests/test_app_engine.py @@ -41,6 +41,28 @@ def test_get_project_id_missing_apis(): assert excinfo.match(r'App Engine APIs are not available') +class TestSigner(object): + def test_key_id(self, app_identity_mock): + app_identity_mock.sign_blob.return_value = ( + mock.sentinel.key_id, mock.sentinel.signature) + + signer = app_engine.Signer() + + assert signer.key_id == mock.sentinel.key_id + + def test_sign(self, app_identity_mock): + app_identity_mock.sign_blob.return_value = ( + mock.sentinel.key_id, mock.sentinel.signature) + + signer = app_engine.Signer() + to_sign = b'123' + + signature = signer.sign(to_sign) + + assert signature == mock.sentinel.signature + app_identity_mock.sign_blob.assert_called_with(to_sign) + + class TestCredentials(object): def test_missing_apis(self): with pytest.raises(EnvironmentError) as excinfo: @@ -107,7 +129,8 @@ def test_refresh(self, now_mock, app_identity_mock): assert not credentials.expired def test_sign_bytes(self, app_identity_mock): - app_identity_mock.sign_blob.return_value = mock.sentinel.signature + app_identity_mock.sign_blob.return_value = ( + mock.sentinel.key_id, mock.sentinel.signature) credentials = app_engine.Credentials() to_sign = b'123' From f2d32a57934a2b164dc519521c00739bfb94ec35 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Thu, 16 Feb 2017 09:05:11 -0800 Subject: [PATCH 098/966] Add public property google.auth.credentials.Signing.signer (#110) --- packages/google-auth/google/auth/app_engine.py | 8 +++++++- packages/google-auth/google/auth/credentials.py | 7 +++++++ packages/google-auth/google/auth/jwt.py | 5 +++++ packages/google-auth/google/oauth2/service_account.py | 5 +++++ packages/google-auth/tests/oauth2/test_service_account.py | 3 +++ packages/google-auth/tests/test_app_engine.py | 4 ++++ packages/google-auth/tests/test_jwt.py | 3 +++ 7 files changed, 34 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/app_engine.py b/packages/google-auth/google/auth/app_engine.py index 8b17e76ce690..e6e84d26add1 100644 --- a/packages/google-auth/google/auth/app_engine.py +++ b/packages/google-auth/google/auth/app_engine.py @@ -123,6 +123,7 @@ def __init__(self, scopes=None, service_account_id=None): super(Credentials, self).__init__() self._scopes = scopes self._service_account_id = service_account_id + self._signer = Signer() @_helpers.copy_docstring(credentials.Credentials) def refresh(self, request): @@ -156,9 +157,14 @@ def with_scopes(self, scopes): @_helpers.copy_docstring(credentials.Signing) def sign_bytes(self, message): - return Signer().sign(message) + return self._signer.sign(message) @property @_helpers.copy_docstring(credentials.Signing) def signer_email(self): return self.service_account_email + + @property + @_helpers.copy_docstring(credentials.Signing) + def signer(self): + return self._signer diff --git a/packages/google-auth/google/auth/credentials.py b/packages/google-auth/google/auth/credentials.py index 360dc0e93727..2358b1d93165 100644 --- a/packages/google-auth/google/auth/credentials.py +++ b/packages/google-auth/google/auth/credentials.py @@ -236,3 +236,10 @@ def signer_email(self): # pylint: disable=missing-raises-doc # (pylint doesn't recognize that this is abstract) raise NotImplementedError('Signer email must be implemented.') + + @abc.abstractproperty + def signer(self): + """google.auth.crypt.Signer: The signer used to sign bytes.""" + # pylint: disable=missing-raises-doc + # (pylint doesn't recognize that this is abstract) + raise NotImplementedError('Signer must be implemented.') diff --git a/packages/google-auth/google/auth/jwt.py b/packages/google-auth/google/auth/jwt.py index dfaf2e684bf2..418be6ba83ea 100644 --- a/packages/google-auth/google/auth/jwt.py +++ b/packages/google-auth/google/auth/jwt.py @@ -468,6 +468,11 @@ def sign_bytes(self, message): def signer_email(self): return self._issuer + @property + @_helpers.copy_docstring(credentials.Signing) + def signer(self): + return self._signer + def before_request(self, request, method, url, headers): """Performs credential-specific before request logic. diff --git a/packages/google-auth/google/oauth2/service_account.py b/packages/google-auth/google/oauth2/service_account.py index 48b537de192f..b71b8acc88b9 100644 --- a/packages/google-auth/google/oauth2/service_account.py +++ b/packages/google-auth/google/oauth2/service_account.py @@ -318,6 +318,11 @@ def refresh(self, request): def sign_bytes(self, message): return self._signer.sign(message) + @property + @_helpers.copy_docstring(credentials.Signing) + def signer(self): + return self._signer + @property @_helpers.copy_docstring(credentials.Signing) def signer_email(self): diff --git a/packages/google-auth/tests/oauth2/test_service_account.py b/packages/google-auth/tests/oauth2/test_service_account.py index e6ce63133565..f40ebf22446e 100644 --- a/packages/google-auth/tests/oauth2/test_service_account.py +++ b/packages/google-auth/tests/oauth2/test_service_account.py @@ -134,6 +134,9 @@ def test_sign_bytes(self): signature = self.credentials.sign_bytes(to_sign) assert crypt.verify_signature(to_sign, signature, PUBLIC_CERT_BYTES) + def test_signer(self): + assert isinstance(self.credentials.signer, crypt.Signer) + def test_signer_email(self): assert self.credentials.signer_email == self.SERVICE_ACCOUNT_EMAIL diff --git a/packages/google-auth/tests/test_app_engine.py b/packages/google-auth/tests/test_app_engine.py index dd410d9d2558..af60bcfdb640 100644 --- a/packages/google-auth/tests/test_app_engine.py +++ b/packages/google-auth/tests/test_app_engine.py @@ -139,6 +139,10 @@ def test_sign_bytes(self, app_identity_mock): assert signature == mock.sentinel.signature app_identity_mock.sign_blob.assert_called_with(to_sign) + def test_signer(self, app_identity_mock): + credentials = app_engine.Credentials() + assert isinstance(credentials.signer, app_engine.Signer) + def test_signer_email(self, app_identity_mock): credentials = app_engine.Credentials() assert credentials.signer_email == credentials.service_account_email diff --git a/packages/google-auth/tests/test_jwt.py b/packages/google-auth/tests/test_jwt.py index 3959260e205f..e4a9a0a28973 100644 --- a/packages/google-auth/tests/test_jwt.py +++ b/packages/google-auth/tests/test_jwt.py @@ -264,6 +264,9 @@ def test_sign_bytes(self): signature = self.credentials.sign_bytes(to_sign) assert crypt.verify_signature(to_sign, signature, PUBLIC_CERT_BYTES) + def test_signer(self): + assert isinstance(self.credentials.signer, crypt.Signer) + def test_signer_email(self): assert (self.credentials.signer_email == SERVICE_ACCOUNT_INFO['client_email']) From 4106eda301aaa1dcde5e2ac3e4a2b0612ec1d663 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Thu, 16 Feb 2017 09:35:49 -0800 Subject: [PATCH 099/966] Release v0.7.0 (#111) --- packages/google-auth/.travis.yml | 2 +- packages/google-auth/CHANGELOG.rst | 7 +++++++ packages/google-auth/MANIFEST.in | 1 - packages/google-auth/setup.py | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/google-auth/.travis.yml b/packages/google-auth/.travis.yml index 9fb1c171dab1..917c4fc86300 100644 --- a/packages/google-auth/.travis.yml +++ b/packages/google-auth/.travis.yml @@ -31,7 +31,7 @@ deploy: provider: pypi user: google_opensource password: - secure: jJFzvoeepJmeSN9MELiTyXEwV8bY4xtmXniL9KSdLewVLVV6c9cBoBQDs37PdeaM7O/Zg/EnI29mKo+VuXDVOSNjyuPnBej9ehD6g1s57RlnYippInxsi5MLYPwpHgQGkPJ3FkLeG98xSeoDABgULqv5FNvSJSVe/mLLWi1PtciW/MGoui1ZcxrOzoLdCtVnaR3d5WDjbIgtNqG9TqVo3R57PrQXtK8TmQ8yaH5CBs6bAJpHt71gZEmvXYL3LSPsLcs/Dbs2FAc8xwU8qyDPyYSxPhKrJPJlrTw5RqEMZ5gvRh3fTPsa6WAtTWFfQP1ZZXJp48QUoIu7ziXBIir24SUIMYTQxH4WgF8O4o6KRgkELHswk5KD1rSsycXJ9Di87ch2ba4qG8Qt8sDTJqzTNyhzSpF0HRgZF+/Tq/+c2alf0wGGoBnNk6aNWDcgFE178A0RTmHHgtqjCLB8igTR+pouZYn5RRFVMOKbG4r+8PV5kf1cQ+U68x63aJkq1DTCpmeDpfVQVB4bimQt+Pm9VICQbnby8RojAXNy5MGJ8bthb7prGCWZxkJJS31RBG/NI7Sz09jlLx5ObWMIg1LV2JRQEmPm5JTDhBviY8WDA2ifqA4d/uNFejCn6RpHH/4fZD0QZWroP64+kYI4y3106uUk3QipBOJQy1OsKja8l8c= + secure: bThsMsG/1fg2NlAygLI8GkDc/kGD3TmXUrqmPU9cc6Tym8sMzPYMiDpJk76ttLAV+gfEI++ROvcul4cgW6Jz5d5xJp4sbQAN4eZOyO3Q3hiVPvhuu6wPMM22j8YfT7rmh710moyBujxJccVDCNAJo/3nAbAQVScP8l89YDW5LF9ckgLPLKG0gDEfwLk8ZVlJg8lRsJ7PTWnAp7nfrvXRiBNPEe8yie654wcxV/xzQwAHLSJhAfpBdTHxvUoFazynn+DvGF/zF5R3n3XSPvctGT6tQNr7GDmdp1EqsOL7y2NbunzIDEoN/wUKlmTRcJVownVyLdISNBo3GjbsyOZGwsj86bKpVIgwhFrkZe7BOV9Fqq5wBl7kTvVf5j/FnLrsQaPyGlgHHxNqXSRqjUosT7BGnuTErmhO130Z6q6iXErrCNfBM+sbuLv+LF+vlMuVSdeU/TBGjf5j0ODmsGGF3EuKtudgD64O8L81/ybKo1CxcRoXbK7+pJhvhSAmkFdZ99A22+wiLNe7B1FVmu6ZfO0WO0GPLKsE+1Xn3xxcrX0wO6rE+uXH4CSQEgLza/CltKNL671dZZMJAUsNAVanwKIKCsNu7viOpUVejESyK7I1SA6B5yX74iA/yhunLLnPuPLANbkug7Ob/i5Z1IruWzpEyqm0CC4HfMPvg7FA+Kc= on: tags: true distributions: sdist bdist_wheel diff --git a/packages/google-auth/CHANGELOG.rst b/packages/google-auth/CHANGELOG.rst index 3cd54808b484..a0f8378212db 100644 --- a/packages/google-auth/CHANGELOG.rst +++ b/packages/google-auth/CHANGELOG.rst @@ -1,6 +1,13 @@ Changelog ========= +v0.7.0 +------ + +- Added ``google.auth.iam.Signer``. (#108) +- Fixed issue where ``google.auth.app_engine.Signer`` erroneously returns a tuple from ``sign()``. (#109) +- Added public property ``google.auth.credentials.Signing.signer``. (#110) + v0.6.0 ------ diff --git a/packages/google-auth/MANIFEST.in b/packages/google-auth/MANIFEST.in index d4dadec1a695..d807701d4339 100644 --- a/packages/google-auth/MANIFEST.in +++ b/packages/google-auth/MANIFEST.in @@ -1,4 +1,3 @@ include README.rst graft google -graft tests global-exclude *.pyc __pycache__ diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 644c52dd9904..324d72c67c38 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -33,7 +33,7 @@ setup( name='google-auth', - version='0.6.0', + version='0.7.0', author='Google Cloud Platform', author_email='jonwayne+google-auth@google.com', description='Google Authentication Library', From 64152b34fa7aa4f653f6a96129d300fdc4cfc003 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Fri, 17 Feb 2017 15:54:59 -0800 Subject: [PATCH 100/966] Fix some style issues in the docs --- packages/google-auth/docs/_static/custom.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/google-auth/docs/_static/custom.css b/packages/google-auth/docs/_static/custom.css index b54dd24b06d6..2bb5995d7ec9 100644 --- a/packages/google-auth/docs/_static/custom.css +++ b/packages/google-auth/docs/_static/custom.css @@ -5,3 +5,8 @@ div.document { code.descname { color: #4885ed; } + +th.field-name { + min-width: 100px; + color: #3cba54; +} From 1422ce24816c3d0b6395a1251c0958e8f99d8eb9 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Wed, 22 Feb 2017 09:27:32 -0800 Subject: [PATCH 101/966] Fix gRPC to call credentials.before_request (#116) --- .../google-auth/google/auth/credentials.py | 5 +-- .../google-auth/google/auth/transport/grpc.py | 17 +++++----- .../google-auth/system_tests/test_grpc.py | 31 ++++++++++++++----- .../google-auth/tests/transport/test_grpc.py | 12 ++++--- packages/google-auth/tox.ini | 4 +-- 5 files changed, 46 insertions(+), 23 deletions(-) diff --git a/packages/google-auth/google/auth/credentials.py b/packages/google-auth/google/auth/credentials.py index 2358b1d93165..8570957cc07e 100644 --- a/packages/google-auth/google/auth/credentials.py +++ b/packages/google-auth/google/auth/credentials.py @@ -104,8 +104,9 @@ def before_request(self, request, method, url, headers): Args: request (google.auth.transport.Request): The object used to make HTTP requests. - method (str): The request's HTTP method. - url (str): The request's URI. + method (str): The request's HTTP method or the RPC method being + invoked. + url (str): The request's URI or the RPC service's URI. headers (Mapping): The request's headers. """ # pylint: disable=unused-argument diff --git a/packages/google-auth/google/auth/transport/grpc.py b/packages/google-auth/google/auth/transport/grpc.py index e6a5eb776556..81d565865157 100644 --- a/packages/google-auth/google/auth/transport/grpc.py +++ b/packages/google-auth/google/auth/transport/grpc.py @@ -17,6 +17,7 @@ from __future__ import absolute_import import grpc +import six class AuthMetadataPlugin(grpc.AuthMetadataPlugin): @@ -40,19 +41,21 @@ def __init__(self, credentials, request): self._credentials = credentials self._request = request - def _get_authorization_headers(self): + def _get_authorization_headers(self, context): """Gets the authorization headers for a request. Returns: Sequence[Tuple[str, str]]: A list of request headers (key, value) to add to the request. """ - if self._credentials.expired or not self._credentials.valid: - self._credentials.refresh(self._request) + headers = {} + self._credentials.before_request( + self._request, + context.method_name, + context.service_url, + headers) - return [ - ('authorization', 'Bearer {}'.format(self._credentials.token)) - ] + return list(six.iteritems(headers)) def __call__(self, context, callback): """Passes authorization metadata into the given callback. @@ -62,7 +65,7 @@ def __call__(self, context, callback): callback (grpc.AuthMetadataPluginCallback): The callback that will be invoked to pass in the authorization metadata. """ - callback(self._get_authorization_headers(), None) + callback(self._get_authorization_headers(context), None) def secure_authorized_channel( diff --git a/packages/google-auth/system_tests/test_grpc.py b/packages/google-auth/system_tests/test_grpc.py index 7d436c563877..73467fe7aef7 100644 --- a/packages/google-auth/system_tests/test_grpc.py +++ b/packages/google-auth/system_tests/test_grpc.py @@ -15,23 +15,40 @@ import google.auth import google.auth.credentials import google.auth.transport.grpc -from google.cloud.gapic.pubsub.v1 import publisher_api +from google.cloud.gapic.pubsub.v1 import publisher_client -def test_grpc_request(http_request): +def test_grpc_request_with_regular_credentials(http_request): credentials, project_id = google.auth.default() credentials = google.auth.credentials.with_scopes_if_required( credentials, ['https://www.googleapis.com/auth/pubsub']) - target = '{}:{}'.format( - publisher_api.PublisherApi.SERVICE_ADDRESS, - publisher_api.PublisherApi.DEFAULT_SERVICE_PORT) + channel = google.auth.transport.grpc.secure_authorized_channel( + credentials, + http_request, + publisher_client.PublisherClient.SERVICE_ADDRESS) + + # Create a pub/sub client. + client = publisher_client.PublisherClient(channel=channel) + + # list the topics and drain the iterator to test that an authorized API + # call works. + list_topics_iter = client.list_topics( + project='projects/{}'.format(project_id)) + list(list_topics_iter) + + +def test_grpc_request_with_jwt_credentials(http_request): + credentials, project_id = google.auth.default() + credentials = credentials.to_jwt_credentials() channel = google.auth.transport.grpc.secure_authorized_channel( - credentials, http_request, target) + credentials, + http_request, + publisher_client.PublisherClient.SERVICE_ADDRESS) # Create a pub/sub client. - client = publisher_api.PublisherApi(channel=channel) + client = publisher_client.PublisherClient(channel=channel) # list the topics and drain the iterator to test that an authorized API # call works. diff --git a/packages/google-auth/tests/transport/test_grpc.py b/packages/google-auth/tests/transport/test_grpc.py index 15a301fa3aad..7a3cc0a7f7b7 100644 --- a/packages/google-auth/tests/transport/test_grpc.py +++ b/packages/google-auth/tests/transport/test_grpc.py @@ -12,10 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -import mock +import datetime +import mock import pytest +from google.auth import credentials try: import google.auth.transport.grpc HAS_GRPC = True @@ -26,11 +28,11 @@ pytestmark = pytest.mark.skipif(not HAS_GRPC, reason='gRPC is unavailable.') -class MockCredentials(object): +class MockCredentials(credentials.Credentials): def __init__(self, token='token'): + super(MockCredentials, self).__init__() self.token = token - self.valid = True - self.expired = False + self.expiry = None def refresh(self, request): self.token += '1' @@ -54,7 +56,7 @@ def test_call_no_refresh(self): def test_call_refresh(self): credentials = MockCredentials() - credentials.expired = True + credentials.expiry = datetime.datetime.min request = mock.Mock() plugin = google.auth.transport.grpc.AuthMetadataPlugin( diff --git a/packages/google-auth/tox.ini b/packages/google-auth/tox.ini index 0a98945c1458..a2f73628c4ad 100644 --- a/packages/google-auth/tox.ini +++ b/packages/google-auth/tox.ini @@ -33,7 +33,7 @@ commands = deps = {[testenv]deps} nox-automation - gapic-google-pubsub-v1==0.11.1 + gapic-google-cloud-pubsub-v1==0.15.0 passenv = SKIP_APP_ENGINE_SYSTEM_TEST CLOUD_SDK_ROOT @@ -46,7 +46,7 @@ commands = deps = {[testenv]deps} nox-automation - gapic-google-pubsub-v1==0.11.1 + gapic-google-cloud-pubsub-v1==0.15.0 passenv = SKIP_APP_ENGINE_SYSTEM_TEST CLOUD_SDK_ROOT From b1ed4a8f1fe8ebdd0a41a2d0a9c31cbc064de891 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Wed, 22 Feb 2017 14:37:31 -0800 Subject: [PATCH 102/966] Create abstract Verifier and Signer, remove key_id hack from App Engine and IAM signers (#115) --- .../google/auth/_service_account_info.py | 2 +- .../google-auth/google/auth/app_engine.py | 32 ++----- packages/google-auth/google/auth/crypt.py | 90 +++++++++++++------ packages/google-auth/google/auth/iam.py | 29 ++---- .../tests/oauth2/test_service_account.py | 2 +- .../tests/test__service_account_info.py | 4 +- packages/google-auth/tests/test_app_engine.py | 2 +- packages/google-auth/tests/test_crypt.py | 65 +++++++------- packages/google-auth/tests/test_iam.py | 12 +-- packages/google-auth/tests/test_jwt.py | 6 +- 10 files changed, 122 insertions(+), 122 deletions(-) diff --git a/packages/google-auth/google/auth/_service_account_info.py b/packages/google-auth/google/auth/_service_account_info.py index 989101113e1a..dd39ea7b253b 100644 --- a/packages/google-auth/google/auth/_service_account_info.py +++ b/packages/google-auth/google/auth/_service_account_info.py @@ -51,7 +51,7 @@ def from_dict(data, require=None): 'fields {}.'.format(', '.join(missing))) # Create a signer. - signer = crypt.Signer.from_service_account_info(data) + signer = crypt.RSASigner.from_service_account_info(data) return signer diff --git a/packages/google-auth/google/auth/app_engine.py b/packages/google-auth/google/auth/app_engine.py index e6e84d26add1..6dc871256ba1 100644 --- a/packages/google-auth/google/auth/app_engine.py +++ b/packages/google-auth/google/auth/app_engine.py @@ -26,6 +26,7 @@ from google.auth import _helpers from google.auth import credentials +from google.auth import crypt try: from google.appengine.api import app_identity @@ -33,42 +34,25 @@ app_identity = None -class Signer(object): +class Signer(crypt.Signer): """Signs messages using the App Engine App Identity service. This can be used in place of :class:`google.auth.crypt.Signer` when running in the App Engine standard environment. - - .. warning:: - The App Identity service signs bytes using Google-managed keys. - Because of this it's possible that the key used to sign bytes will - change. In some cases this change can occur between successive calls - to :attr:`key_id` and :meth:`sign`. This could result in a signature - that was signed with a different key than the one indicated by - :attr:`key_id`. It's recommended that if you use this in your code - that you account for this behavior by building in retry logic. """ @property def key_id(self): """Optional[str]: The key ID used to identify this private key. - .. note:: - This makes a request to the App Identity service. + .. warning:: + This is always ``None``. The key ID used by App Engine can not + be reliably determined ahead of time. """ - key_id, _ = app_identity.sign_blob(b'') - return key_id - - @staticmethod - def sign(message): - """Signs a message. - - Args: - message (Union[str, bytes]): The message to be signed. + return None - Returns: - bytes: The signature of the message. - """ + @_helpers.copy_docstring(crypt.Signer) + def sign(self, message): message = _helpers.to_bytes(message) _, signature = app_identity.sign_blob(message) return signature diff --git a/packages/google-auth/google/auth/crypt.py b/packages/google-auth/google/auth/crypt.py index 05839b46f691..65bf37f22600 100644 --- a/packages/google-auth/google/auth/crypt.py +++ b/packages/google-auth/google/auth/crypt.py @@ -24,20 +24,21 @@ valid = crypt.verify_signature(message, signature, cert) If you're going to verify many messages with the same certificate, you can use -:class:`Verifier`:: +:class:`RSAVerifier`:: cert = open('certs.pem').read() - verifier = crypt.Verifier.from_string(cert) + verifier = crypt.RSAVerifier.from_string(cert) valid = verifier.verify(message, signature) -To sign messages use :class:`Signer` with a private key:: +To sign messages use :class:`RSASigner` with a private key:: private_key = open('private_key.pem').read() - signer = crypt.Signer(private_key) + signer = crypt.RSASigner(private_key) signature = signer.sign(message) """ +import abc import io import json @@ -77,23 +78,17 @@ def _bit_list_to_bytes(bit_list): byte_vals = bytearray() for start in six.moves.xrange(0, num_bits, 8): curr_bits = bit_list[start:start + 8] - char_val = sum(val * digit - for val, digit in six.moves.zip(_POW2, curr_bits)) + char_val = sum( + val * digit for val, digit in six.moves.zip(_POW2, curr_bits)) byte_vals.append(char_val) return bytes(byte_vals) +@six.add_metaclass(abc.ABCMeta) class Verifier(object): - """This object is used to verify cryptographic signatures. - - Args: - public_key (rsa.key.PublicKey): The public key used to verify - signatures. - """ - - def __init__(self, public_key): - self._pubkey = public_key + """Abstract base class for crytographic signature verifiers.""" + @abc.abstractmethod def verify(self, message, signature): """Verifies a message against a cryptographic signature. @@ -105,6 +100,24 @@ def verify(self, message, signature): bool: True if message was signed by the private key associated with the public key that this object was constructed with. """ + # pylint: disable=missing-raises-doc,redundant-returns-doc + # (pylint doesn't recognize that this is abstract) + raise NotImplementedError('Verify must be implemented') + + +class RSAVerifier(Verifier): + """Verifies RSA cryptographic signatures using public keys. + + Args: + public_key (rsa.key.PublicKey): The public key used to verify + signatures. + """ + + def __init__(self, public_key): + self._pubkey = public_key + + @_helpers.copy_docstring(Verifier) + def verify(self, message, signature): message = _helpers.to_bytes(message) try: return rsa.pkcs1.verify(message, signature, self._pubkey) @@ -145,7 +158,7 @@ def from_string(cls, public_key): def verify_signature(message, signature, certs): - """Verify a cryptographic signature. + """Verify an RSA cryptographic signature. Checks that the provided ``signature`` was generated from ``bytes`` using the private key associated with the ``cert``. @@ -163,27 +176,22 @@ def verify_signature(message, signature, certs): certs = [certs] for cert in certs: - verifier = Verifier.from_string(cert) + verifier = RSAVerifier.from_string(cert) if verifier.verify(message, signature): return True return False +@six.add_metaclass(abc.ABCMeta) class Signer(object): - """Signs messages with a private key. - - Args: - private_key (rsa.key.PrivateKey): The private key to sign with. - key_id (str): Optional key ID used to identify this private key. This - can be useful to associate the private key with its associated - public key or certificate. - """ + """Abstract base class for cryptographic signers.""" - def __init__(self, private_key, key_id=None): - self._key = private_key - self.key_id = key_id + @abc.abstractproperty + def key_id(self): """Optional[str]: The key ID used to identify this private key.""" + raise NotImplementedError('Key id must be implemented') + @abc.abstractmethod def sign(self, message): """Signs a message. @@ -193,6 +201,32 @@ def sign(self, message): Returns: bytes: The signature of the message. """ + # pylint: disable=missing-raises-doc,redundant-returns-doc + # (pylint doesn't recognize that this is abstract) + raise NotImplementedError('Sign must be implemented') + + +class RSASigner(Signer): + """Signs messages with an RSA private key. + + Args: + private_key (rsa.key.PrivateKey): The private key to sign with. + key_id (str): Optional key ID used to identify this private key. This + can be useful to associate the private key with its associated + public key or certificate. + """ + + def __init__(self, private_key, key_id=None): + self._key = private_key + self._key_id = key_id + + @property + @_helpers.copy_docstring(Signer) + def key_id(self): + return self._key_id + + @_helpers.copy_docstring(Signer) + def sign(self, message): message = _helpers.to_bytes(message) return rsa.pkcs1.sign(message, self._key, 'SHA-256') diff --git a/packages/google-auth/google/auth/iam.py b/packages/google-auth/google/auth/iam.py index efa3127100b1..e091e47f371d 100644 --- a/packages/google-auth/google/auth/iam.py +++ b/packages/google-auth/google/auth/iam.py @@ -25,6 +25,7 @@ from six.moves import http_client from google.auth import _helpers +from google.auth import crypt from google.auth import exceptions _IAM_API_ROOT_URI = 'https://iam.googleapis.com/v1' @@ -32,21 +33,12 @@ _IAM_API_ROOT_URI + '/projects/-/serviceAccounts/{}:signBlob?alt=json') -class Signer(object): +class Signer(crypt.Signer): """Signs messages using the IAM `signBlob API`_. This is useful when you need to sign bytes but do not have access to the credential's private key file. - .. warning:: - The IAM API signs bytes using Google-managed keys. Because of this - it's possible that the key used to sign bytes will change. In some - cases this change can occur between successive calls to :attr:`key_id` - and :meth:`sign`. This could result in a signature that was signed - with a different key than the one indicated by :attr:`key_id`. It's - recommended that if you use this in your code that you account for - this behavior by building in retry logic. - .. _signBlob API: https://cloud.google.com/iam/reference/rest/v1/projects.serviceAccounts /signBlob @@ -98,20 +90,13 @@ def _make_signing_request(self, message): def key_id(self): """Optional[str]: The key ID used to identify this private key. - .. note:: - This makes an API request to the IAM API. + .. warning:: + This is always ``None``. The key ID used by IAM can not + be reliably determined ahead of time. """ - response = self._make_signing_request('') - return response['keyId'] + return None + @_helpers.copy_docstring(crypt.Signer) def sign(self, message): - """Signs a message. - - Args: - message (Union[str, bytes]): The message to be signed. - - Returns: - bytes: The signature of the message. - """ response = self._make_signing_request(message) return base64.b64decode(response['signature']) diff --git a/packages/google-auth/tests/oauth2/test_service_account.py b/packages/google-auth/tests/oauth2/test_service_account.py index f40ebf22446e..8ae545a1fd11 100644 --- a/packages/google-auth/tests/oauth2/test_service_account.py +++ b/packages/google-auth/tests/oauth2/test_service_account.py @@ -44,7 +44,7 @@ @pytest.fixture(scope='module') def signer(): - return crypt.Signer.from_string(PRIVATE_KEY_BYTES, '1') + return crypt.RSASigner.from_string(PRIVATE_KEY_BYTES, '1') class TestCredentials(object): diff --git a/packages/google-auth/tests/test__service_account_info.py b/packages/google-auth/tests/test__service_account_info.py index 4caea95d30e0..546686530458 100644 --- a/packages/google-auth/tests/test__service_account_info.py +++ b/packages/google-auth/tests/test__service_account_info.py @@ -31,7 +31,7 @@ def test_from_dict(): signer = _service_account_info.from_dict(SERVICE_ACCOUNT_INFO) - assert isinstance(signer, crypt.Signer) + assert isinstance(signer, crypt.RSASigner) assert signer.key_id == SERVICE_ACCOUNT_INFO['private_key_id'] @@ -59,5 +59,5 @@ def test_from_filename(): for key, value in six.iteritems(SERVICE_ACCOUNT_INFO): assert info[key] == value - assert isinstance(signer, crypt.Signer) + assert isinstance(signer, crypt.RSASigner) assert signer.key_id == SERVICE_ACCOUNT_INFO['private_key_id'] diff --git a/packages/google-auth/tests/test_app_engine.py b/packages/google-auth/tests/test_app_engine.py index af60bcfdb640..d3a79d5c1481 100644 --- a/packages/google-auth/tests/test_app_engine.py +++ b/packages/google-auth/tests/test_app_engine.py @@ -48,7 +48,7 @@ def test_key_id(self, app_identity_mock): signer = app_engine.Signer() - assert signer.key_id == mock.sentinel.key_id + assert signer.key_id is None def test_sign(self, app_identity_mock): app_identity_mock.sign_blob.return_value = ( diff --git a/packages/google-auth/tests/test_crypt.py b/packages/google-auth/tests/test_crypt.py index 9671230c622d..56612dae31e0 100644 --- a/packages/google-auth/tests/test_crypt.py +++ b/packages/google-auth/tests/test_crypt.py @@ -69,7 +69,7 @@ def test_verify_signature(): to_sign = b'foo' - signer = crypt.Signer.from_string(PRIVATE_KEY_BYTES) + signer = crypt.RSASigner.from_string(PRIVATE_KEY_BYTES) signature = signer.sign(to_sign) assert crypt.verify_signature( @@ -82,57 +82,57 @@ def test_verify_signature(): def test_verify_signature_failure(): to_sign = b'foo' - signer = crypt.Signer.from_string(PRIVATE_KEY_BYTES) + signer = crypt.RSASigner.from_string(PRIVATE_KEY_BYTES) signature = signer.sign(to_sign) assert not crypt.verify_signature( to_sign, signature, OTHER_CERT_BYTES) -class TestVerifier(object): +class TestRSAVerifier(object): def test_verify_success(self): to_sign = b'foo' - signer = crypt.Signer.from_string(PRIVATE_KEY_BYTES) + signer = crypt.RSASigner.from_string(PRIVATE_KEY_BYTES) actual_signature = signer.sign(to_sign) - verifier = crypt.Verifier.from_string(PUBLIC_KEY_BYTES) + verifier = crypt.RSAVerifier.from_string(PUBLIC_KEY_BYTES) assert verifier.verify(to_sign, actual_signature) def test_verify_unicode_success(self): to_sign = u'foo' - signer = crypt.Signer.from_string(PRIVATE_KEY_BYTES) + signer = crypt.RSASigner.from_string(PRIVATE_KEY_BYTES) actual_signature = signer.sign(to_sign) - verifier = crypt.Verifier.from_string(PUBLIC_KEY_BYTES) + verifier = crypt.RSAVerifier.from_string(PUBLIC_KEY_BYTES) assert verifier.verify(to_sign, actual_signature) def test_verify_failure(self): - verifier = crypt.Verifier.from_string(PUBLIC_KEY_BYTES) + verifier = crypt.RSAVerifier.from_string(PUBLIC_KEY_BYTES) bad_signature1 = b'' assert not verifier.verify(b'foo', bad_signature1) bad_signature2 = b'a' assert not verifier.verify(b'foo', bad_signature2) def test_from_string_pub_key(self): - verifier = crypt.Verifier.from_string(PUBLIC_KEY_BYTES) - assert isinstance(verifier, crypt.Verifier) + verifier = crypt.RSAVerifier.from_string(PUBLIC_KEY_BYTES) + assert isinstance(verifier, crypt.RSAVerifier) assert isinstance(verifier._pubkey, rsa.key.PublicKey) def test_from_string_pub_key_unicode(self): public_key = _helpers.from_bytes(PUBLIC_KEY_BYTES) - verifier = crypt.Verifier.from_string(public_key) - assert isinstance(verifier, crypt.Verifier) + verifier = crypt.RSAVerifier.from_string(public_key) + assert isinstance(verifier, crypt.RSAVerifier) assert isinstance(verifier._pubkey, rsa.key.PublicKey) def test_from_string_pub_cert(self): - verifier = crypt.Verifier.from_string(PUBLIC_CERT_BYTES) - assert isinstance(verifier, crypt.Verifier) + verifier = crypt.RSAVerifier.from_string(PUBLIC_CERT_BYTES) + assert isinstance(verifier, crypt.RSAVerifier) assert isinstance(verifier._pubkey, rsa.key.PublicKey) def test_from_string_pub_cert_unicode(self): public_cert = _helpers.from_bytes(PUBLIC_CERT_BYTES) - verifier = crypt.Verifier.from_string(public_cert) - assert isinstance(verifier, crypt.Verifier) + verifier = crypt.RSAVerifier.from_string(public_cert) + assert isinstance(verifier, crypt.RSAVerifier) assert isinstance(verifier._pubkey, rsa.key.PublicKey) def test_from_string_pub_cert_failure(self): @@ -144,25 +144,25 @@ def test_from_string_pub_cert_failure(self): with load_pem_patch as load_pem: with pytest.raises(ValueError): - crypt.Verifier.from_string(cert_bytes) + crypt.RSAVerifier.from_string(cert_bytes) load_pem.assert_called_once_with(cert_bytes, 'CERTIFICATE') -class TestSigner(object): +class TestRSASigner(object): def test_from_string_pkcs1(self): - signer = crypt.Signer.from_string(PKCS1_KEY_BYTES) - assert isinstance(signer, crypt.Signer) + signer = crypt.RSASigner.from_string(PKCS1_KEY_BYTES) + assert isinstance(signer, crypt.RSASigner) assert isinstance(signer._key, rsa.key.PrivateKey) def test_from_string_pkcs1_unicode(self): key_bytes = _helpers.from_bytes(PKCS1_KEY_BYTES) - signer = crypt.Signer.from_string(key_bytes) - assert isinstance(signer, crypt.Signer) + signer = crypt.RSASigner.from_string(key_bytes) + assert isinstance(signer, crypt.RSASigner) assert isinstance(signer._key, rsa.key.PrivateKey) def test_from_string_pkcs8(self): - signer = crypt.Signer.from_string(PKCS8_KEY_BYTES) - assert isinstance(signer, crypt.Signer) + signer = crypt.RSASigner.from_string(PKCS8_KEY_BYTES) + assert isinstance(signer, crypt.RSASigner) assert isinstance(signer._key, rsa.key.PrivateKey) def test_from_string_pkcs8_extra_bytes(self): @@ -179,28 +179,29 @@ def test_from_string_pkcs8_extra_bytes(self): with decode_patch as decode: with pytest.raises(ValueError): - crypt.Signer.from_string(key_bytes) + crypt.RSASigner.from_string(key_bytes) # Verify mock was called. decode.assert_called_once_with( pem_bytes, asn1Spec=crypt._PKCS8_SPEC) def test_from_string_pkcs8_unicode(self): key_bytes = _helpers.from_bytes(PKCS8_KEY_BYTES) - signer = crypt.Signer.from_string(key_bytes) - assert isinstance(signer, crypt.Signer) + signer = crypt.RSASigner.from_string(key_bytes) + assert isinstance(signer, crypt.RSASigner) assert isinstance(signer._key, rsa.key.PrivateKey) def test_from_string_pkcs12(self): with pytest.raises(ValueError): - crypt.Signer.from_string(PKCS12_KEY_BYTES) + crypt.RSASigner.from_string(PKCS12_KEY_BYTES) def test_from_string_bogus_key(self): key_bytes = 'bogus-key' with pytest.raises(ValueError): - crypt.Signer.from_string(key_bytes) + crypt.RSASigner.from_string(key_bytes) def test_from_service_account_info(self): - signer = crypt.Signer.from_service_account_info(SERVICE_ACCOUNT_INFO) + signer = crypt.RSASigner.from_service_account_info( + SERVICE_ACCOUNT_INFO) assert signer.key_id == SERVICE_ACCOUNT_INFO[ crypt._JSON_FILE_PRIVATE_KEY_ID] @@ -208,12 +209,12 @@ def test_from_service_account_info(self): def test_from_service_account_info_missing_key(self): with pytest.raises(ValueError) as excinfo: - crypt.Signer.from_service_account_info({}) + crypt.RSASigner.from_service_account_info({}) assert excinfo.match(crypt._JSON_FILE_PRIVATE_KEY) def test_from_service_account_file(self): - signer = crypt.Signer.from_service_account_file( + signer = crypt.RSASigner.from_service_account_file( SERVICE_ACCOUNT_JSON_FILE) assert signer.key_id == SERVICE_ACCOUNT_INFO[ diff --git a/packages/google-auth/tests/test_iam.py b/packages/google-auth/tests/test_iam.py index 5ac991168839..f7767887bfa7 100644 --- a/packages/google-auth/tests/test_iam.py +++ b/packages/google-auth/tests/test_iam.py @@ -64,16 +64,12 @@ def test_constructor(self): mock.sentinel.service_account_email) def test_key_id(self): - key_id = '123' - request = make_request(http_client.OK, data={'keyId': key_id}) - credentials = make_credentials() - signer = iam.Signer( - request, credentials, mock.sentinel.service_account_email) + mock.sentinel.request, + mock.sentinel.credentials, + mock.sentinel.service_account_email) - assert signer.key_id == '123' - auth_header = request.call_args[1]['headers']['authorization'] - assert auth_header == 'Bearer token' + assert signer.key_id is None def test_sign_bytes(self): signature = b'DEADBEEF' diff --git a/packages/google-auth/tests/test_jwt.py b/packages/google-auth/tests/test_jwt.py index e4a9a0a28973..df09ece61bbc 100644 --- a/packages/google-auth/tests/test_jwt.py +++ b/packages/google-auth/tests/test_jwt.py @@ -44,7 +44,7 @@ @pytest.fixture def signer(): - return crypt.Signer.from_string(PRIVATE_KEY_BYTES, '1') + return crypt.RSASigner.from_string(PRIVATE_KEY_BYTES, '1') def test_encode_basic(signer): @@ -78,7 +78,7 @@ def factory(claims=None, key_id=None): # False is specified to remove the signer's key id for testing # headers without key ids. if key_id is False: - signer.key_id = None + signer._key_id = None key_id = None return jwt.encode(signer, payload, key_id=key_id) @@ -265,7 +265,7 @@ def test_sign_bytes(self): assert crypt.verify_signature(to_sign, signature, PUBLIC_CERT_BYTES) def test_signer(self): - assert isinstance(self.credentials.signer, crypt.Signer) + assert isinstance(self.credentials.signer, crypt.RSASigner) def test_signer_email(self): assert (self.credentials.signer_email == From 7ff8a6aef1ac9b954a7b986502726b969f8335d4 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Thu, 23 Feb 2017 09:20:14 -0800 Subject: [PATCH 103/966] Remove one-time token behavior of JWT Credentials (#117) --- packages/google-auth/google/auth/jwt.py | 95 ++++--------------- .../google/oauth2/service_account.py | 9 +- .../google-auth/system_tests/test_grpc.py | 5 +- .../tests/oauth2/test_service_account.py | 6 +- packages/google-auth/tests/test_jwt.py | 51 +++++----- 5 files changed, 58 insertions(+), 108 deletions(-) diff --git a/packages/google-auth/google/auth/jwt.py b/packages/google-auth/google/auth/jwt.py index 418be6ba83ea..087dbd9e98af 100644 --- a/packages/google-auth/google/auth/jwt.py +++ b/packages/google-auth/google/auth/jwt.py @@ -45,8 +45,6 @@ import datetime import json -from six.moves import urllib - from google.auth import _helpers from google.auth import _service_account_info from google.auth import credentials @@ -246,11 +244,7 @@ class Credentials(credentials.Signing, """Credentials that use a JWT as the bearer token. These credentials require an "audience" claim. This claim identifies the - intended recipient of the bearer token. You can set the audience when - you construct these credentials, however, these credentials can also set - the audience claim automatically if not specified. In this case, whenever - a request is made the credentials will automatically generate a one-time - JWT with the request URI as the audience. + intended recipient of the bearer token. The constructor arguments determine the claims for the JWT that is sent with requests. Usually, you'll construct these credentials with @@ -260,13 +254,15 @@ class Credentials(credentials.Signing, JSON file:: credentials = jwt.Credentials.from_service_account_file( - 'service-account.json') + 'service-account.json', + audience='https://speech.googleapis.com') If you already have the service account file loaded and parsed:: service_account_info = json.load(open('service_account.json')) credentials = jwt.Credentials.from_service_account_info( - service_account_info) + service_account_info, + audience='https://speech.googleapis.com') Both helper methods pass on arguments to the constructor, so you can specify the JWT claims:: @@ -280,7 +276,10 @@ class Credentials(credentials.Signing, :class:`~google.auth.crypt.Signer` instance:: credentials = jwt.Credentials( - signer, issuer='your-issuer', subject='your-subject') + signer, + issuer='your-issuer', + subject='your-subject', + audience=''https://speech.googleapis.com'') The claims are considered immutable. If you want to modify the claims, you can easily create another instance using :meth:`with_claims`:: @@ -289,7 +288,7 @@ class Credentials(credentials.Signing, audience='https://vision.googleapis.com') """ - def __init__(self, signer, issuer=None, subject=None, audience=None, + def __init__(self, signer, issuer, subject, audience, additional_claims=None, token_lifetime=_DEFAULT_TOKEN_LIFETIME_SECS): """ @@ -298,8 +297,7 @@ def __init__(self, signer, issuer=None, subject=None, audience=None, issuer (str): The `iss` claim. subject (str): The `sub` claim. audience (str): the `aud` claim. The intended audience for the - credentials. If not specified, a new JWT will be generated for - every request and will use the request URI as the audience. + credentials. additional_claims (Mapping[str, str]): Any additional claims for the JWT payload. token_lifetime (int): The amount of time in seconds for @@ -334,7 +332,8 @@ def _from_signer_and_info(cls, signer, info, **kwargs): ValueError: If the info is not in the expected format. """ kwargs.setdefault('subject', info['client_email']) - return cls(signer, issuer=info['client_email'], **kwargs) + kwargs.setdefault('issuer', info['client_email']) + return cls(signer, **kwargs) @classmethod def from_service_account_info(cls, info, **kwargs): @@ -381,9 +380,8 @@ def with_claims(self, issuer=None, subject=None, audience=None, claim will be used. subject (str): The `sub` claim. If unspecified the current subject claim will be used. - audience (str): the `aud` claim. If not specified, a new - JWT will be generated for every request and will use - the request URI as the audience. + audience (str): the `aud` claim. If unspecified the current + audience claim will be used. additional_claims (Mapping[str, str]): Any additional claims for the JWT payload. This will be merged with the current additional claims. @@ -399,12 +397,9 @@ def with_claims(self, issuer=None, subject=None, audience=None, additional_claims=self._additional_claims.copy().update( additional_claims or {})) - def _make_jwt(self, audience=None): + def _make_jwt(self): """Make a signed JWT. - Args: - audience (str): Overrides the instance's current audience claim. - Returns: Tuple[bytes, datetime]: The encoded JWT and the expiration. """ @@ -414,10 +409,10 @@ def _make_jwt(self, audience=None): payload = { 'iss': self._issuer, - 'sub': self._subject or self._issuer, + 'sub': self._subject, 'iat': _helpers.datetime_to_secs(now), 'exp': _helpers.datetime_to_secs(expiry), - 'aud': audience or self._audience, + 'aud': self._audience, } payload.update(self._additional_claims) @@ -426,22 +421,6 @@ def _make_jwt(self, audience=None): return jwt, expiry - def _make_one_time_jwt(self, uri): - """Makes a one-off JWT with the URI as the audience. - - Args: - uri (str): The request URI. - - Returns: - bytes: The encoded JWT. - """ - parts = urllib.parse.urlsplit(uri) - # Strip query string and fragment - audience = urllib.parse.urlunsplit( - (parts.scheme, parts.netloc, parts.path, None, None)) - token, _ = self._make_jwt(audience=audience) - return token - def refresh(self, request): """Refreshes the access token. @@ -452,15 +431,8 @@ def refresh(self, request): # (pylint doesn't correctly recognize overridden methods.) self.token, self.expiry = self._make_jwt() + @_helpers.copy_docstring(credentials.Signing) def sign_bytes(self, message): - """Signs the given message. - - Args: - message (bytes): The message to sign. - - Returns: - bytes: The message signature. - """ return self._signer.sign(message) @property @@ -472,32 +444,3 @@ def signer_email(self): @_helpers.copy_docstring(credentials.Signing) def signer(self): return self._signer - - def before_request(self, request, method, url, headers): - """Performs credential-specific before request logic. - - If an audience is specified it will refresh the credentials if - necessary. If no audience is specified it will generate a one-time - token for the request URI. In either case, it will set the - authorization header in headers to the token. - - Args: - request (Any): Unused. - method (str): The request's HTTP method. - url (str): The request's URI. - headers (Mapping): The request's headers. - """ - # pylint: disable=unused-argument - # (pylint doesn't correctly recognize overridden methods.) - - # If this set of credentials has a pre-set audience, just ensure that - # there is a valid token and apply the auth headers. - if self._audience: - if not self.valid: - self.refresh(request) - self.apply(headers) - # Otherwise, generate a one-time token using the URL - # (without the query string and fragment) as the audience. - else: - token = self._make_one_time_jwt(url) - self.apply(headers, token=token) diff --git a/packages/google-auth/google/oauth2/service_account.py b/packages/google-auth/google/oauth2/service_account.py index b71b8acc88b9..a4fc1b6c14f2 100644 --- a/packages/google-auth/google/oauth2/service_account.py +++ b/packages/google-auth/google/oauth2/service_account.py @@ -204,7 +204,7 @@ def from_service_account_file(cls, filename, **kwargs): filename, require=['client_email', 'token_uri']) return cls._from_signer_and_info(signer, info, **kwargs) - def to_jwt_credentials(self): + def to_jwt_credentials(self, audience): """Creates a :class:`google.auth.jwt.Credentials` instance from this instance. @@ -223,13 +223,18 @@ def to_jwt_credentials(self): jwt_creds = jwt.Credentials.from_service_account_file( 'service_account.json') + Args: + audience (str): the `aud` claim. The intended audience for the + credentials. + Returns: google.auth.jwt.Credentials: A new Credentials instance. """ return jwt.Credentials( self._signer, issuer=self._service_account_email, - subject=self._service_account_email) + subject=self._service_account_email, + audience=audience) @property def service_account_email(self): diff --git a/packages/google-auth/system_tests/test_grpc.py b/packages/google-auth/system_tests/test_grpc.py index 73467fe7aef7..8a4419a90a0f 100644 --- a/packages/google-auth/system_tests/test_grpc.py +++ b/packages/google-auth/system_tests/test_grpc.py @@ -40,7 +40,10 @@ def test_grpc_request_with_regular_credentials(http_request): def test_grpc_request_with_jwt_credentials(http_request): credentials, project_id = google.auth.default() - credentials = credentials.to_jwt_credentials() + audience = 'https://{}/google.pubsub.v1.Publisher'.format( + publisher_client.PublisherClient.SERVICE_ADDRESS) + credentials = credentials.to_jwt_credentials( + audience=audience) channel = google.auth.transport.grpc.secure_authorized_channel( credentials, diff --git a/packages/google-auth/tests/oauth2/test_service_account.py b/packages/google-auth/tests/oauth2/test_service_account.py index 8ae545a1fd11..1bce670296fe 100644 --- a/packages/google-auth/tests/oauth2/test_service_account.py +++ b/packages/google-auth/tests/oauth2/test_service_account.py @@ -112,9 +112,11 @@ def test_from_service_account_file_args(self): assert credentials._additional_claims == additional_claims def test_to_jwt_credentials(self): - jwt_from_svc = self.credentials.to_jwt_credentials() + jwt_from_svc = self.credentials.to_jwt_credentials( + audience=mock.sentinel.audience) jwt_from_info = jwt.Credentials.from_service_account_info( - SERVICE_ACCOUNT_INFO) + SERVICE_ACCOUNT_INFO, + audience=mock.sentinel.audience) assert isinstance(jwt_from_svc, jwt.Credentials) assert jwt_from_svc._signer.key_id == jwt_from_info._signer.key_id diff --git a/packages/google-auth/tests/test_jwt.py b/packages/google-auth/tests/test_jwt.py index df09ece61bbc..0716fc369405 100644 --- a/packages/google-auth/tests/test_jwt.py +++ b/packages/google-auth/tests/test_jwt.py @@ -206,17 +206,20 @@ class TestCredentials: @pytest.fixture(autouse=True) def credentials_fixture(self, signer): self.credentials = jwt.Credentials( - signer, self.SERVICE_ACCOUNT_EMAIL) + signer, self.SERVICE_ACCOUNT_EMAIL, self.SERVICE_ACCOUNT_EMAIL, + self.AUDIENCE) def test_from_service_account_info(self): with open(SERVICE_ACCOUNT_JSON_FILE, 'r') as fh: info = json.load(fh) - credentials = jwt.Credentials.from_service_account_info(info) + credentials = jwt.Credentials.from_service_account_info( + info, audience=self.AUDIENCE) assert credentials._signer.key_id == info['private_key_id'] assert credentials._issuer == info['client_email'] assert credentials._subject == info['client_email'] + assert credentials._audience == self.AUDIENCE def test_from_service_account_info_args(self): info = SERVICE_ACCOUNT_INFO.copy() @@ -235,11 +238,12 @@ def test_from_service_account_file(self): info = SERVICE_ACCOUNT_INFO.copy() credentials = jwt.Credentials.from_service_account_file( - SERVICE_ACCOUNT_JSON_FILE) + SERVICE_ACCOUNT_JSON_FILE, audience=self.AUDIENCE) assert credentials._signer.key_id == info['private_key_id'] assert credentials._issuer == info['client_email'] assert credentials._subject == info['client_email'] + assert credentials._audience == self.AUDIENCE def test_from_service_account_file_args(self): info = SERVICE_ACCOUNT_INFO.copy() @@ -259,6 +263,18 @@ def test_default_state(self): # Expiration hasn't been set yet assert not self.credentials.expired + def test_with_claims(self): + new_audience = 'new_audience' + new_credentials = self.credentials.with_claims( + audience=new_audience) + + assert new_credentials._signer == self.credentials._signer + assert new_credentials._issuer == self.credentials._issuer + assert new_credentials._subject == self.credentials._subject + assert new_credentials._audience == new_audience + assert (new_credentials._additional_claims == + self.credentials._additional_claims) + def test_sign_bytes(self): to_sign = b'123' signature = self.credentials.sign_bytes(to_sign) @@ -292,43 +308,24 @@ def test_expired(self): now.return_value = self.credentials.expiry + one_day assert self.credentials.expired - def test_before_request_one_time_token(self): + def test_before_request(self): headers = {} self.credentials.refresh(None) self.credentials.before_request( - mock.Mock(), 'GET', 'http://example.com?a=1#3', headers) - - header_value = headers['authorization'] - _, token = header_value.split(' ') - - # This should be a one-off token, so it shouldn't be the same as the - # credentials' stored token. - assert token != self.credentials.token - - payload = self._verify_token(token) - assert payload['aud'] == 'http://example.com' - - def test_before_request_with_preset_audience(self): - headers = {} - - credentials = self.credentials.with_claims(audience=self.AUDIENCE) - credentials.refresh(None) - credentials.before_request( None, 'GET', 'http://example.com?a=1#3', headers) header_value = headers['authorization'] _, token = header_value.split(' ') # Since the audience is set, it should use the existing token. - assert token.encode('utf-8') == credentials.token + assert token.encode('utf-8') == self.credentials.token payload = self._verify_token(token) assert payload['aud'] == self.AUDIENCE def test_before_request_refreshes(self): - credentials = self.credentials.with_claims(audience=self.AUDIENCE) - assert not credentials.valid - credentials.before_request( + assert not self.credentials.valid + self.credentials.before_request( None, 'GET', 'http://example.com?a=1#3', {}) - assert credentials.valid + assert self.credentials.valid From b27358b7feaf4569dfb589a49d2bc8bded930d56 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Thu, 23 Feb 2017 15:45:18 -0800 Subject: [PATCH 104/966] Release v0.8.0 (#119) --- packages/google-auth/CHANGELOG.rst | 7 +++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.rst b/packages/google-auth/CHANGELOG.rst index a0f8378212db..5390245a8bef 100644 --- a/packages/google-auth/CHANGELOG.rst +++ b/packages/google-auth/CHANGELOG.rst @@ -1,6 +1,13 @@ Changelog ========= +v0.8.0 +------ + +- Removed one-time token behavior from ``jwt.Credentials``, audience claim is now required and fixed. (#117) +- ``crypt.Signer`` and ``crypt.Verifier`` are now abstract base classes. The concrete implementations have been renamed to ``crypt.RSASigner`` and ``crypt.RSAVerifier``. ``app_engine.Signer`` and ``iam.Signer`` now inherit from ``crypt.Signer``. (#115) +- ``transport.grpc`` now correctly calls ``Credentials.before_request``. (#116) + v0.7.0 ------ diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 324d72c67c38..5f72e00a32aa 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -33,7 +33,7 @@ setup( name='google-auth', - version='0.7.0', + version='0.8.0', author='Google Cloud Platform', author_email='jonwayne+google-auth@google.com', description='Google Authentication Library', From 8d1f653511d9d0d84a0c3c679c8c1afc9da1ff6e Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Fri, 24 Feb 2017 09:03:24 -0800 Subject: [PATCH 105/966] Add jwt.Credentials.from_signing_credentials, remove serivce_account.Credentials.to_jwt_credentials (#120) --- packages/google-auth/google/auth/jwt.py | 61 +++++++++++++++---- .../google/oauth2/service_account.py | 32 ---------- .../google-auth/system_tests/test_grpc.py | 4 +- .../tests/oauth2/test_service_account.py | 13 ---- packages/google-auth/tests/test_jwt.py | 14 +++++ 5 files changed, 66 insertions(+), 58 deletions(-) diff --git a/packages/google-auth/google/auth/jwt.py b/packages/google-auth/google/auth/jwt.py index 087dbd9e98af..7a9bdd59a93c 100644 --- a/packages/google-auth/google/auth/jwt.py +++ b/packages/google-auth/google/auth/jwt.py @@ -47,8 +47,8 @@ from google.auth import _helpers from google.auth import _service_account_info -from google.auth import credentials from google.auth import crypt +import google.auth.credentials _DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in sections @@ -239,8 +239,8 @@ def decode(token, certs=None, verify=True, audience=None): return payload -class Credentials(credentials.Signing, - credentials.Credentials): +class Credentials(google.auth.credentials.Signing, + google.auth.credentials.Credentials): """Credentials that use a JWT as the bearer token. These credentials require an "audience" claim. This claim identifies the @@ -253,23 +253,24 @@ class Credentials(credentials.Signing, To create JWT credentials using a Google service account private key JSON file:: + audience = 'https://pubsub.googleapis.com/google.pubsub.v1.Publisher' credentials = jwt.Credentials.from_service_account_file( 'service-account.json', - audience='https://speech.googleapis.com') + audience=audience) If you already have the service account file loaded and parsed:: service_account_info = json.load(open('service_account.json')) credentials = jwt.Credentials.from_service_account_info( service_account_info, - audience='https://speech.googleapis.com') + audience=audience) Both helper methods pass on arguments to the constructor, so you can specify the JWT claims:: credentials = jwt.Credentials.from_service_account_file( 'service-account.json', - audience='https://speech.googleapis.com', + audience=audience, additional_claims={'meta': 'data'}) You can also construct the credentials directly if you have a @@ -279,13 +280,14 @@ class Credentials(credentials.Signing, signer, issuer='your-issuer', subject='your-subject', - audience=''https://speech.googleapis.com'') + audience=audience) The claims are considered immutable. If you want to modify the claims, you can easily create another instance using :meth:`with_claims`:: - new_credentials = credentials.with_claims( - audience='https://vision.googleapis.com') + new_audience = ( + 'https://pubsub.googleapis.com/google.pubsub.v1.Subscriber') + new_credentials = credentials.with_claims(audience=new_audience) """ def __init__(self, signer, issuer, subject, audience, @@ -371,6 +373,41 @@ def from_service_account_file(cls, filename, **kwargs): filename, require=['client_email']) return cls._from_signer_and_info(signer, info, **kwargs) + @classmethod + def from_signing_credentials(cls, credentials, audience, **kwargs): + """Creates a new :class:`google.auth.jwt.Credentials` instance from an + existing :class:`google.auth.credentials.Signing` instance. + + The new instance will use the same signer as the existing instance and + will use the existing instance's signer email as the issuer and + subject by default. + + Example:: + + svc_creds = service_account.Credentials.from_service_account_file( + 'service_account.json') + audience = ( + 'https://pubsub.googleapis.com/google.pubsub.v1.Publisher') + jwt_creds = jwt.Credentials.from_signing_credentials( + svc_creds, audience=audience) + + Args: + credentials (google.auth.credentials.Signing): The credentials to + use to construct the new credentials. + audience (str): the `aud` claim. The intended audience for the + credentials. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.jwt.Credentials: A new Credentials instance. + """ + kwargs.setdefault('issuer', credentials.signer_email) + kwargs.setdefault('subject', credentials.signer_email) + return cls( + credentials.signer, + audience=audience, + **kwargs) + def with_claims(self, issuer=None, subject=None, audience=None, additional_claims=None): """Returns a copy of these credentials with modified claims. @@ -431,16 +468,16 @@ def refresh(self, request): # (pylint doesn't correctly recognize overridden methods.) self.token, self.expiry = self._make_jwt() - @_helpers.copy_docstring(credentials.Signing) + @_helpers.copy_docstring(google.auth.credentials.Signing) def sign_bytes(self, message): return self._signer.sign(message) @property - @_helpers.copy_docstring(credentials.Signing) + @_helpers.copy_docstring(google.auth.credentials.Signing) def signer_email(self): return self._issuer @property - @_helpers.copy_docstring(credentials.Signing) + @_helpers.copy_docstring(google.auth.credentials.Signing) def signer(self): return self._signer diff --git a/packages/google-auth/google/oauth2/service_account.py b/packages/google-auth/google/oauth2/service_account.py index a4fc1b6c14f2..58580e2188a0 100644 --- a/packages/google-auth/google/oauth2/service_account.py +++ b/packages/google-auth/google/oauth2/service_account.py @@ -204,38 +204,6 @@ def from_service_account_file(cls, filename, **kwargs): filename, require=['client_email', 'token_uri']) return cls._from_signer_and_info(signer, info, **kwargs) - def to_jwt_credentials(self, audience): - """Creates a :class:`google.auth.jwt.Credentials` instance from this - instance. - - The new instance will use the same private key as this instance and - will use this instance's service account email as the issuer and - subject. - - This is the same as calling - :meth:`jwt.Credentials.from_service_account_file` with the same - file used to create these credentials:: - - svc_creds = service_account.Credentials.from_service_account_file( - 'service_account.json') - jwt_from_svc = svc_credentials.to_jwt_credentials() - # is the same as: - jwt_creds = jwt.Credentials.from_service_account_file( - 'service_account.json') - - Args: - audience (str): the `aud` claim. The intended audience for the - credentials. - - Returns: - google.auth.jwt.Credentials: A new Credentials instance. - """ - return jwt.Credentials( - self._signer, - issuer=self._service_account_email, - subject=self._service_account_email, - audience=audience) - @property def service_account_email(self): """The service account email.""" diff --git a/packages/google-auth/system_tests/test_grpc.py b/packages/google-auth/system_tests/test_grpc.py index 8a4419a90a0f..4bf1c5ba5917 100644 --- a/packages/google-auth/system_tests/test_grpc.py +++ b/packages/google-auth/system_tests/test_grpc.py @@ -14,6 +14,7 @@ import google.auth import google.auth.credentials +import google.auth.jwt import google.auth.transport.grpc from google.cloud.gapic.pubsub.v1 import publisher_client @@ -42,7 +43,8 @@ def test_grpc_request_with_jwt_credentials(http_request): credentials, project_id = google.auth.default() audience = 'https://{}/google.pubsub.v1.Publisher'.format( publisher_client.PublisherClient.SERVICE_ADDRESS) - credentials = credentials.to_jwt_credentials( + credentials = google.auth.jwt.Credentials.from_signing_credentials( + credentials, audience=audience) channel = google.auth.transport.grpc.secure_authorized_channel( diff --git a/packages/google-auth/tests/oauth2/test_service_account.py b/packages/google-auth/tests/oauth2/test_service_account.py index 1bce670296fe..c4c1d1b8a60c 100644 --- a/packages/google-auth/tests/oauth2/test_service_account.py +++ b/packages/google-auth/tests/oauth2/test_service_account.py @@ -111,19 +111,6 @@ def test_from_service_account_file_args(self): assert credentials._subject == subject assert credentials._additional_claims == additional_claims - def test_to_jwt_credentials(self): - jwt_from_svc = self.credentials.to_jwt_credentials( - audience=mock.sentinel.audience) - jwt_from_info = jwt.Credentials.from_service_account_info( - SERVICE_ACCOUNT_INFO, - audience=mock.sentinel.audience) - - assert isinstance(jwt_from_svc, jwt.Credentials) - assert jwt_from_svc._signer.key_id == jwt_from_info._signer.key_id - assert jwt_from_svc._issuer == jwt_from_info._issuer - assert jwt_from_svc._subject == jwt_from_info._subject - assert jwt_from_svc._audience == jwt_from_info._audience - def test_default_state(self): assert not self.credentials.valid # Expiration hasn't been set yet diff --git a/packages/google-auth/tests/test_jwt.py b/packages/google-auth/tests/test_jwt.py index 0716fc369405..59769de2ed84 100644 --- a/packages/google-auth/tests/test_jwt.py +++ b/packages/google-auth/tests/test_jwt.py @@ -258,6 +258,20 @@ def test_from_service_account_file_args(self): assert credentials._audience == self.AUDIENCE assert credentials._additional_claims == self.ADDITIONAL_CLAIMS + def test_from_signing_credentials(self): + jwt_from_signing = self.credentials.from_signing_credentials( + self.credentials, + audience=mock.sentinel.new_audience) + jwt_from_info = jwt.Credentials.from_service_account_info( + SERVICE_ACCOUNT_INFO, + audience=mock.sentinel.new_audience) + + assert isinstance(jwt_from_signing, jwt.Credentials) + assert jwt_from_signing._signer.key_id == jwt_from_info._signer.key_id + assert jwt_from_signing._issuer == jwt_from_info._issuer + assert jwt_from_signing._subject == jwt_from_info._subject + assert jwt_from_signing._audience == jwt_from_info._audience + def test_default_state(self): assert not self.credentials.valid # Expiration hasn't been set yet From ddefb2282ffd1aa145a3dec3f8489b36e4c36a68 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Fri, 24 Feb 2017 09:39:22 -0800 Subject: [PATCH 106/966] Test with Python 3.6 (#102) --- packages/google-auth/.travis.yml | 10 ++++++---- packages/google-auth/scripts/travis.sh | 3 +++ packages/google-auth/tox.ini | 12 ++++++------ 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/packages/google-auth/.travis.yml b/packages/google-auth/.travis.yml index 917c4fc86300..8fc00d538b2a 100644 --- a/packages/google-auth/.travis.yml +++ b/packages/google-auth/.travis.yml @@ -4,7 +4,7 @@ matrix: include: - python: 3.5 env: TOXENV=lint - - python: 3.5 + - python: 3.6 env: TOXENV=docs - python: 2.7 env: TOXENV=py27 @@ -12,12 +12,14 @@ matrix: env: TOXENV=py34 - python: 3.5 env: TOXENV=py35 + - python: 3.6 + env: TOXENV=py36 - python: pypy env: TOXENV=pypy - - python: 3.5 + - python: 3.6 env: TOXENV=cover - - python: 3.5 - env: TOXENV=py35-system SYSTEM_TEST=1 SKIP_APP_ENGINE_SYSTEM_TEST=1 + - python: 3.6 + env: TOXENV=py36-system SYSTEM_TEST=1 SKIP_APP_ENGINE_SYSTEM_TEST=1 - python: 2.7 env: TOXENV=py27-system SYSTEM_TEST=1 SKIP_APP_ENGINE_SYSTEM_TEST=1 cache: diff --git a/packages/google-auth/scripts/travis.sh b/packages/google-auth/scripts/travis.sh index 84a227a82ca4..7359a0cbe4be 100755 --- a/packages/google-auth/scripts/travis.sh +++ b/packages/google-auth/scripts/travis.sh @@ -25,6 +25,9 @@ if [[ -n $SYSTEM_TEST ]]; then if [[ $TRAVIS_SECURE_ENV_VARS == "true" ]]; then echo 'Extracting secrets.' scripts/decrypt-secrets.sh "$SECRETS_PASSWORD" + # Prevent build failures from leaking our password. + # looking at you, Tox. + export SECRETS_PASSWORD="" else # This is an external PR, so just mark system tests as green. echo 'In system test but secrets are not available, skipping.' diff --git a/packages/google-auth/tox.ini b/packages/google-auth/tox.ini index a2f73628c4ad..528806cbc41e 100644 --- a/packages/google-auth/tox.ini +++ b/packages/google-auth/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = lint,py27,py34,py35,pypy,cover +envlist = lint,py27,py34,py35,py36,pypy,cover [testenv] deps = @@ -18,15 +18,15 @@ commands = py.test --cov=google.auth --cov=google.oauth2 --cov=tests {posargs:tests} [testenv:cover] -basepython = python3.5 +basepython = python3.6 commands = py.test --cov=google.auth --cov=google.oauth2 --cov=tests --cov-report= tests coverage report --show-missing --fail-under=100 deps = {[testenv]deps} -[testenv:py35-system] -basepython = python3.5 +[testenv:py36-system] +basepython = python3.6 changedir = {toxinidir}/system_tests commands = nox {posargs} @@ -52,7 +52,7 @@ passenv = CLOUD_SDK_ROOT [testenv:docgen] -basepython = python3.5 +basepython = python3.6 deps = {[testenv]deps} sphinx @@ -63,7 +63,7 @@ commands = sphinx-apidoc --output-dir docs/reference --separate --module-first google [testenv:docs] -basepython = python3.5 +basepython = python3.6 deps = sphinx -r{toxinidir}/docs/requirements-docs.txt From a1e8e6f6fd3b3f2ae9137dd2fdb9a8b42047cdaf Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Wed, 1 Mar 2017 09:27:16 -0800 Subject: [PATCH 107/966] Add public properties to google.oauth2.credentials.Credentials (#124) --- .../google-auth/google/oauth2/credentials.py | 21 +++++++++++++++++++ .../tests/oauth2/test_credentials.py | 5 +++++ 2 files changed, 26 insertions(+) diff --git a/packages/google-auth/google/oauth2/credentials.py b/packages/google-auth/google/oauth2/credentials.py index 9727e189ebd9..077a95f73fb6 100644 --- a/packages/google-auth/google/oauth2/credentials.py +++ b/packages/google-auth/google/oauth2/credentials.py @@ -68,6 +68,27 @@ def __init__(self, token, refresh_token=None, token_uri=None, self._client_id = client_id self._client_secret = client_secret + @property + def refresh_token(self): + """Optional[str]: The OAuth 2.0 refresh token.""" + return self._refresh_token + + @property + def token_uri(self): + """Optional[str]: The OAuth 2.0 authorization server's token endpoint + URI.""" + return self._token_uri + + @property + def client_id(self): + """Optional[str]: The OAuth 2.0 client ID.""" + return self._client_id + + @property + def client_secret(self): + """Optional[str]: The OAuth 2.0 client secret.""" + return self._client_secret + @property def requires_scopes(self): """False: OAuth 2.0 credentials have their scopes set when diff --git a/packages/google-auth/tests/oauth2/test_credentials.py b/packages/google-auth/tests/oauth2/test_credentials.py index b53b188d9ccc..fa86a16d5b02 100644 --- a/packages/google-auth/tests/oauth2/test_credentials.py +++ b/packages/google-auth/tests/oauth2/test_credentials.py @@ -41,6 +41,11 @@ def test_default_state(self): assert not self.credentials.expired # Scopes aren't required for these credentials assert not self.credentials.requires_scopes + # Test properties + assert self.credentials.refresh_token == self.REFRESH_TOKEN + assert self.credentials.token_uri == self.TOKEN_URI + assert self.credentials.client_id == self.CLIENT_ID + assert self.credentials.client_secret == self.CLIENT_SECRET def test_create_scoped(self): with pytest.raises(NotImplementedError): From f2d29eb8d60df28f77bf87220651ff6913b4948f Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Wed, 1 Mar 2017 09:28:15 -0800 Subject: [PATCH 108/966] Update gcloud in system tests (#123) --- packages/google-auth/system_tests/nox.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/google-auth/system_tests/nox.py b/packages/google-auth/system_tests/nox.py index 8179ca11e339..0d1116d66de3 100644 --- a/packages/google-auth/system_tests/nox.py +++ b/packages/google-auth/system_tests/nox.py @@ -86,11 +86,9 @@ def install_cloud_sdk(session): # This tells gcloud which Python interpreter to use (always use 2.7) session.env[CLOUD_SDK_PYTHON_ENV] = CLOUD_SDK_PYTHON - # If gcloud cli executable already exists, we don't need to do anything - # else. - # Note that because of this we do not attempt to update the sdk - - # if the CLOUD_SDK_ROOT is cached, it will need to be periodically cleared. + # If gcloud cli executable already exists, just update it. if py.path.local(GCLOUD).exists(): + session.run(GCLOUD, 'components', 'update', '-q') return tar_path = CLOUD_SDK_ROOT.join(CLOUD_SDK_DIST_FILENAME) From ae634ad4efc268b9f656c833809ca5dfe528f27a Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Wed, 1 Mar 2017 14:03:38 -0800 Subject: [PATCH 109/966] Add details about oauthlib integration to the user guide (#126) --- packages/google-auth/docs/user-guide.rst | 25 +++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/packages/google-auth/docs/user-guide.rst b/packages/google-auth/docs/user-guide.rst index 0e7f90f0fac8..f4cf8309795c 100644 --- a/packages/google-auth/docs/user-guide.rst +++ b/packages/google-auth/docs/user-guide.rst @@ -136,9 +136,9 @@ User credentials ++++++++++++++++ User credentials are typically obtained via `OAuth 2.0`_. This library does not -provide any support for *obtaining* user credentials, however, you can use user -credentials with this library. You can use libraries such as `oauthlib`_ or -`oauth2client`_ to obtain the access token. After you have an access token, you +provide any direct support for *obtaining* user credentials, however, you can +use user credentials with this library. You can use libraries such as +`oauthlib`_ to obtain the access token. After you have an access token, you can create a :class:`google.oauth2.credentials.Credentials` instance:: import google.oauth2.credentials @@ -156,12 +156,27 @@ URI to allow the credentials to be automatically refreshed:: client_id='client_id', client_secret='client_secret') + +This library has some helpers for integrating with `requests-oauthlib`_ to +provide support for obtaining user credentials. You can use +:func:`google.oauth2.oauthlib.credentials_from_session` to obtain +:class:`google.oauth2.credentials.Credentials` from a +:class:`requests_oauthlib.OAuth2Session` as above:: + + import google.oauth2.oauthlib + + google_auth_credentials = google.oauth2.oauthlib.credentials_from_session( + oauth2session) + +You can also use :class:`google.oauth2.flow.Flow` to perform the OAuth 2.0 +Authorization Grant Flow to obtain credentials using `requests-oauthlib`_. + .. _OAuth 2.0: https://developers.google.com/identity/protocols/OAuth2 .. _oauthlib: https://oauthlib.readthedocs.io/en/latest/ -.. _oauth2client: - https://oauth2client.readthedocs.org +.. _requests-oauthlib: + https://requests-oauthlib.readthedocs.io/en/latest/ Making authenticated requests ----------------------------- From d263f8147449c246708b9b32a4c2671b7e6cc953 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Mon, 6 Mar 2017 12:26:10 -0800 Subject: [PATCH 110/966] Add helpful error messages when importing optional dependencies (#125) --- packages/google-auth/google/auth/transport/grpc.py | 7 ++++++- packages/google-auth/google/auth/transport/requests.py | 8 ++++++-- packages/google-auth/google/auth/transport/urllib3.py | 7 ++++++- packages/google-auth/google/oauth2/oauthlib.py | 7 ++++++- 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/packages/google-auth/google/auth/transport/grpc.py b/packages/google-auth/google/auth/transport/grpc.py index 81d565865157..8554ffa36a98 100644 --- a/packages/google-auth/google/auth/transport/grpc.py +++ b/packages/google-auth/google/auth/transport/grpc.py @@ -16,7 +16,12 @@ from __future__ import absolute_import -import grpc +try: + import grpc +except ImportError: # pragma: NO COVER + raise ImportError( + 'gRPC is not installed, please install the grpcio package to use the ' + 'gRPC transport.') import six diff --git a/packages/google-auth/google/auth/transport/requests.py b/packages/google-auth/google/auth/transport/requests.py index e2110902471a..0c66fdd4198b 100644 --- a/packages/google-auth/google/auth/transport/requests.py +++ b/packages/google-auth/google/auth/transport/requests.py @@ -18,8 +18,12 @@ import logging - -import requests +try: + import requests +except ImportError: # pragma: NO COVER + raise ImportError( + 'The requests library is not installed, please install the requests ' + 'package to use the requests transport.') import requests.exceptions from google.auth import exceptions diff --git a/packages/google-auth/google/auth/transport/urllib3.py b/packages/google-auth/google/auth/transport/urllib3.py index 9d417b5b6e66..a08f4275c571 100644 --- a/packages/google-auth/google/auth/transport/urllib3.py +++ b/packages/google-auth/google/auth/transport/urllib3.py @@ -30,7 +30,12 @@ except ImportError: # pragma: NO COVER certifi = None -import urllib3 +try: + import urllib3 +except ImportError: # pragma: NO COVER + raise ImportError( + 'The urllib3 library is not installed, please install the urllib3 ' + 'package to use the urllib3 transport.') import urllib3.exceptions from google.auth import exceptions diff --git a/packages/google-auth/google/oauth2/oauthlib.py b/packages/google-auth/google/oauth2/oauthlib.py index 8f5c10575c34..e1c6089721d1 100644 --- a/packages/google-auth/google/oauth2/oauthlib.py +++ b/packages/google-auth/google/oauth2/oauthlib.py @@ -27,7 +27,12 @@ import json -import requests_oauthlib +try: + import requests_oauthlib +except ImportError: # pragma: NO COVER + raise ImportError( + 'The requests-oauthlib library is not installed, please install the ' + 'requests-oauthlib package to use google.oauth2.oauthlib.') import google.oauth2.credentials From 46cff1fd595ad1028265e36dc42d4f08f79c1920 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Thu, 16 Mar 2017 13:16:59 -0700 Subject: [PATCH 111/966] Fix inclusion of tests package in setup.py (#131) --- packages/google-auth/MANIFEST.in | 5 ++--- packages/google-auth/setup.py | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/google-auth/MANIFEST.in b/packages/google-auth/MANIFEST.in index d807701d4339..401787fe7bf3 100644 --- a/packages/google-auth/MANIFEST.in +++ b/packages/google-auth/MANIFEST.in @@ -1,3 +1,2 @@ -include README.rst -graft google -global-exclude *.pyc __pycache__ +include README.rst LICENSE CHANGELOG.rst +recursive-include tests * diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 5f72e00a32aa..389dafffc82b 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -39,7 +39,7 @@ description='Google Authentication Library', long_description=long_description, url='https://github.com/GoogleCloudPlatform/google-auth-library-python', - packages=find_packages(exclude=('tests', 'system_tests')), + packages=find_packages(exclude=('tests*', 'system_tests*')), namespace_packages=('google',), install_requires=DEPENDENCIES, extras_require={ From e96d07a9db4de68391f05a0ba7d96b4bc66dd79c Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Tue, 21 Mar 2017 19:42:54 -0700 Subject: [PATCH 112/966] Stylesheet updates --- packages/google-auth/docs/_static/custom.css | 8 ++++++-- packages/google-auth/docs/_templates/fonts.html | 2 -- packages/google-auth/docs/conf.py | 1 - 3 files changed, 6 insertions(+), 5 deletions(-) delete mode 100644 packages/google-auth/docs/_templates/fonts.html diff --git a/packages/google-auth/docs/_static/custom.css b/packages/google-auth/docs/_static/custom.css index 2bb5995d7ec9..3d0319dd337c 100644 --- a/packages/google-auth/docs/_static/custom.css +++ b/packages/google-auth/docs/_static/custom.css @@ -1,5 +1,9 @@ -div.document { - width: 1040px; +@import url('https://fonts.googleapis.com/css?family=Roboto|Roboto+Mono'); + +@media screen and (min-width: 1080px) { + div.document { + width: 1040px; + } } code.descname { diff --git a/packages/google-auth/docs/_templates/fonts.html b/packages/google-auth/docs/_templates/fonts.html deleted file mode 100644 index 5fc0470c8c15..000000000000 --- a/packages/google-auth/docs/_templates/fonts.html +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/packages/google-auth/docs/conf.py b/packages/google-auth/docs/conf.py index 5dbac8790ac6..2ac5f3428ede 100644 --- a/packages/google-auth/docs/conf.py +++ b/packages/google-auth/docs/conf.py @@ -199,7 +199,6 @@ 'navigation.html', 'relations.html', 'searchbox.html', - 'fonts.html' ] } From b9cb3489f41831ed748f25a4228fd70461e71317 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Tue, 21 Mar 2017 20:18:34 -0700 Subject: [PATCH 113/966] Test httplib2 transport package on travis (#135) --- packages/google-auth/httplib2_transport/MANIFEST.in | 2 ++ packages/google-auth/httplib2_transport/tox.ini | 4 ++-- packages/google-auth/scripts/travis.sh | 10 ++++++++++ 3 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 packages/google-auth/httplib2_transport/MANIFEST.in diff --git a/packages/google-auth/httplib2_transport/MANIFEST.in b/packages/google-auth/httplib2_transport/MANIFEST.in new file mode 100644 index 000000000000..aac7fe13adac --- /dev/null +++ b/packages/google-auth/httplib2_transport/MANIFEST.in @@ -0,0 +1,2 @@ +include README.rst LICENSE +recursive-include tests * diff --git a/packages/google-auth/httplib2_transport/tox.ini b/packages/google-auth/httplib2_transport/tox.ini index 5d7457efa4b1..84635e83c0ec 100644 --- a/packages/google-auth/httplib2_transport/tox.ini +++ b/packages/google-auth/httplib2_transport/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = lint,py27,py34,py35,pypy,cover +envlist = lint,py27,py34,py35,py36,pypy,cover [testenv] deps = @@ -16,7 +16,7 @@ commands = py.test --cov=google_auth_httplib2 --cov=tests {posargs:tests} [testenv:cover] -basepython = python3.5 +basepython = python3.6 commands = py.test --cov=google_auth_httplib2 --cov=tests --cov-report= tests coverage report --show-missing --fail-under=100 diff --git a/packages/google-auth/scripts/travis.sh b/packages/google-auth/scripts/travis.sh index 7359a0cbe4be..9e11de85fc73 100755 --- a/packages/google-auth/scripts/travis.sh +++ b/packages/google-auth/scripts/travis.sh @@ -14,6 +14,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +set -eo pipefail + DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" ROOT=$( dirname "$DIR" ) @@ -36,4 +38,12 @@ if [[ -n $SYSTEM_TEST ]]; then fi # Run tox. +echo "Running tox..." tox + +# Run tox for sub-packages. +if [[ $TOXENV != "docs" && -z $SYSTEM_TEST ]]; then + echo "Running tox for httplib2_transport..." + cd httplib2_transport + tox +fi From d411d8869f2fce68b64bf075ebdcb3c80dbe61bb Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Wed, 22 Mar 2017 10:10:32 -0700 Subject: [PATCH 114/966] Use io.open instead of open in setup.py --- packages/google-auth/setup.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 389dafffc82b..8c5193230bae 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +import io + from setuptools import find_packages from setuptools import setup @@ -28,7 +30,7 @@ ) -with open('README.rst', 'r') as fh: +with io.open('README.rst', 'r') as fh: long_description = fh.read() setup( From e839bb308d1bece46e3982acaf8550cde17506a9 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Wed, 22 Mar 2017 12:59:41 -0700 Subject: [PATCH 115/966] Separate oauthlib integration into its own package (#137) * Centralize the run_pylint script. * Use io.open instead of open in setup.py for httplib2_transport * Move httplib2_transport -> additional_packages/google_auth_httplib2 * Remove unneeded dependencies in google_auth_httplib2 * Update classifiers to note 3.6 support. * Don't install the HEAD version of google-auth when testing google-auth-httplib2. * Add google-auth-oauthlib package. * Remove google.oauth2.oauthlib and google.oauth2.flow and associated tests. * Make travis run google-auth-oauthlib's tox. * Specify tox workdir. --- .../google-auth/additional_packages/README.md | 4 + .../google_auth_httplib2}/.coveragerc | 0 .../google_auth_httplib2}/LICENSE | 0 .../google_auth_httplib2}/MANIFEST.in | 0 .../google_auth_httplib2}/README.rst | 0 .../google_auth_httplib2.py | 0 .../google_auth_httplib2}/setup.cfg | 0 .../google_auth_httplib2}/setup.py | 9 +- .../google_auth_httplib2}/tests/__init__.py | 0 .../google_auth_httplib2}/tests/compliance.py | 0 .../tests/test_google_auth_httplib2.py | 0 .../google_auth_httplib2}/tox.ini | 7 +- .../google_auth_oauthlib/.coveragerc | 13 + .../google_auth_oauthlib/CHANGELOG.rst | 4 + .../google_auth_oauthlib/LICENSE | 201 ++++++++++++++ .../google_auth_oauthlib/MANIFEST.in | 2 + .../google_auth_oauthlib/README.rst | 28 ++ .../google_auth_oauthlib/__init__.py | 0 .../google_auth_oauthlib}/flow.py | 15 +- .../google_auth_oauthlib/helpers.py} | 8 +- .../google_auth_oauthlib/setup.cfg | 2 + .../google_auth_oauthlib/setup.py | 59 ++++ .../google_auth_oauthlib/tests/__init__.py | 0 .../tests/data/client_secrets.json | 14 + .../google_auth_oauthlib/tests}/test_flow.py | 4 +- .../tests/test_helpers.py} | 20 +- .../google_auth_oauthlib/tox.ini | 35 +++ .../httplib2_transport/scripts/.gitignore | 3 - .../httplib2_transport/scripts/run_pylint.py | 256 ------------------ packages/google-auth/scripts/run_pylint.py | 13 +- packages/google-auth/scripts/travis.sh | 13 +- packages/google-auth/setup.py | 8 +- packages/google-auth/tox.ini | 4 +- 33 files changed, 413 insertions(+), 309 deletions(-) create mode 100644 packages/google-auth/additional_packages/README.md rename packages/google-auth/{httplib2_transport => additional_packages/google_auth_httplib2}/.coveragerc (100%) rename packages/google-auth/{httplib2_transport => additional_packages/google_auth_httplib2}/LICENSE (100%) rename packages/google-auth/{httplib2_transport => additional_packages/google_auth_httplib2}/MANIFEST.in (100%) rename packages/google-auth/{httplib2_transport => additional_packages/google_auth_httplib2}/README.rst (100%) rename packages/google-auth/{httplib2_transport => additional_packages/google_auth_httplib2}/google_auth_httplib2.py (100%) rename packages/google-auth/{httplib2_transport => additional_packages/google_auth_httplib2}/setup.cfg (100%) rename packages/google-auth/{httplib2_transport => additional_packages/google_auth_httplib2}/setup.py (93%) rename packages/google-auth/{httplib2_transport => additional_packages/google_auth_httplib2}/tests/__init__.py (100%) rename packages/google-auth/{httplib2_transport => additional_packages/google_auth_httplib2}/tests/compliance.py (100%) rename packages/google-auth/{httplib2_transport => additional_packages/google_auth_httplib2}/tests/test_google_auth_httplib2.py (100%) rename packages/google-auth/{httplib2_transport => additional_packages/google_auth_httplib2}/tox.ini (83%) create mode 100644 packages/google-auth/additional_packages/google_auth_oauthlib/.coveragerc create mode 100644 packages/google-auth/additional_packages/google_auth_oauthlib/CHANGELOG.rst create mode 100644 packages/google-auth/additional_packages/google_auth_oauthlib/LICENSE create mode 100644 packages/google-auth/additional_packages/google_auth_oauthlib/MANIFEST.in create mode 100644 packages/google-auth/additional_packages/google_auth_oauthlib/README.rst create mode 100644 packages/google-auth/additional_packages/google_auth_oauthlib/google_auth_oauthlib/__init__.py rename packages/google-auth/{google/oauth2 => additional_packages/google_auth_oauthlib/google_auth_oauthlib}/flow.py (96%) rename packages/google-auth/{google/oauth2/oauthlib.py => additional_packages/google_auth_oauthlib/google_auth_oauthlib/helpers.py} (96%) create mode 100644 packages/google-auth/additional_packages/google_auth_oauthlib/setup.cfg create mode 100644 packages/google-auth/additional_packages/google_auth_oauthlib/setup.py create mode 100644 packages/google-auth/additional_packages/google_auth_oauthlib/tests/__init__.py create mode 100644 packages/google-auth/additional_packages/google_auth_oauthlib/tests/data/client_secrets.json rename packages/google-auth/{tests/oauth2 => additional_packages/google_auth_oauthlib/tests}/test_flow.py (97%) rename packages/google-auth/{tests/oauth2/test_oauthlib.py => additional_packages/google_auth_oauthlib/tests/test_helpers.py} (81%) create mode 100644 packages/google-auth/additional_packages/google_auth_oauthlib/tox.ini delete mode 100644 packages/google-auth/httplib2_transport/scripts/.gitignore delete mode 100644 packages/google-auth/httplib2_transport/scripts/run_pylint.py diff --git a/packages/google-auth/additional_packages/README.md b/packages/google-auth/additional_packages/README.md new file mode 100644 index 000000000000..827c54808bb4 --- /dev/null +++ b/packages/google-auth/additional_packages/README.md @@ -0,0 +1,4 @@ +# Additional packages for Google Auth Library Python + +This folder contains seperately distributed auxilliary packages for use +with google-auth. diff --git a/packages/google-auth/httplib2_transport/.coveragerc b/packages/google-auth/additional_packages/google_auth_httplib2/.coveragerc similarity index 100% rename from packages/google-auth/httplib2_transport/.coveragerc rename to packages/google-auth/additional_packages/google_auth_httplib2/.coveragerc diff --git a/packages/google-auth/httplib2_transport/LICENSE b/packages/google-auth/additional_packages/google_auth_httplib2/LICENSE similarity index 100% rename from packages/google-auth/httplib2_transport/LICENSE rename to packages/google-auth/additional_packages/google_auth_httplib2/LICENSE diff --git a/packages/google-auth/httplib2_transport/MANIFEST.in b/packages/google-auth/additional_packages/google_auth_httplib2/MANIFEST.in similarity index 100% rename from packages/google-auth/httplib2_transport/MANIFEST.in rename to packages/google-auth/additional_packages/google_auth_httplib2/MANIFEST.in diff --git a/packages/google-auth/httplib2_transport/README.rst b/packages/google-auth/additional_packages/google_auth_httplib2/README.rst similarity index 100% rename from packages/google-auth/httplib2_transport/README.rst rename to packages/google-auth/additional_packages/google_auth_httplib2/README.rst diff --git a/packages/google-auth/httplib2_transport/google_auth_httplib2.py b/packages/google-auth/additional_packages/google_auth_httplib2/google_auth_httplib2.py similarity index 100% rename from packages/google-auth/httplib2_transport/google_auth_httplib2.py rename to packages/google-auth/additional_packages/google_auth_httplib2/google_auth_httplib2.py diff --git a/packages/google-auth/httplib2_transport/setup.cfg b/packages/google-auth/additional_packages/google_auth_httplib2/setup.cfg similarity index 100% rename from packages/google-auth/httplib2_transport/setup.cfg rename to packages/google-auth/additional_packages/google_auth_httplib2/setup.cfg diff --git a/packages/google-auth/httplib2_transport/setup.py b/packages/google-auth/additional_packages/google_auth_httplib2/setup.py similarity index 93% rename from packages/google-auth/httplib2_transport/setup.py rename to packages/google-auth/additional_packages/google_auth_httplib2/setup.py index e85c309e729b..ec048813a4f3 100644 --- a/packages/google-auth/httplib2_transport/setup.py +++ b/packages/google-auth/additional_packages/google_auth_httplib2/setup.py @@ -12,19 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. +import io + from setuptools import setup DEPENDENCIES = ( - 'pyasn1>=0.1.7', - 'pyasn1-modules>=0.0.5', - 'rsa>=3.1.4', - 'six>=1.9.0', 'google-auth' ) -with open('README.rst', 'r') as fh: +with io.open('README.rst', 'r') as fh: long_description = fh.read() @@ -46,6 +44,7 @@ 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', 'Development Status :: 3 - Alpha', 'Intended Audience :: Developers', 'License :: OSI Approved :: Apache Software License', diff --git a/packages/google-auth/httplib2_transport/tests/__init__.py b/packages/google-auth/additional_packages/google_auth_httplib2/tests/__init__.py similarity index 100% rename from packages/google-auth/httplib2_transport/tests/__init__.py rename to packages/google-auth/additional_packages/google_auth_httplib2/tests/__init__.py diff --git a/packages/google-auth/httplib2_transport/tests/compliance.py b/packages/google-auth/additional_packages/google_auth_httplib2/tests/compliance.py similarity index 100% rename from packages/google-auth/httplib2_transport/tests/compliance.py rename to packages/google-auth/additional_packages/google_auth_httplib2/tests/compliance.py diff --git a/packages/google-auth/httplib2_transport/tests/test_google_auth_httplib2.py b/packages/google-auth/additional_packages/google_auth_httplib2/tests/test_google_auth_httplib2.py similarity index 100% rename from packages/google-auth/httplib2_transport/tests/test_google_auth_httplib2.py rename to packages/google-auth/additional_packages/google_auth_httplib2/tests/test_google_auth_httplib2.py diff --git a/packages/google-auth/httplib2_transport/tox.ini b/packages/google-auth/additional_packages/google_auth_httplib2/tox.ini similarity index 83% rename from packages/google-auth/httplib2_transport/tox.ini rename to packages/google-auth/additional_packages/google_auth_httplib2/tox.ini index 84635e83c0ec..95f34542780b 100644 --- a/packages/google-auth/httplib2_transport/tox.ini +++ b/packages/google-auth/additional_packages/google_auth_httplib2/tox.ini @@ -9,9 +9,6 @@ deps = pytest-cov pytest-localserver httplib2 - {toxinidir}/.. -# Always recreate because of the relative path in deps above. -recreate = True commands = py.test --cov=google_auth_httplib2 --cov=tests {posargs:tests} @@ -31,7 +28,9 @@ commands = --import-order-style=google \ --application-import-names="google_auth_httplib2,tests" \ google_auth_httplib2.py tests - python {toxinidir}/scripts/run_pylint.py + python {toxinidir}/../../scripts/run_pylint.py \ + --library-filesets google_auth_httplib2.py \ + --test-filesets tests deps = flake8 flake8-import-order diff --git a/packages/google-auth/additional_packages/google_auth_oauthlib/.coveragerc b/packages/google-auth/additional_packages/google_auth_oauthlib/.coveragerc new file mode 100644 index 000000000000..c44dd8eaa6c8 --- /dev/null +++ b/packages/google-auth/additional_packages/google_auth_oauthlib/.coveragerc @@ -0,0 +1,13 @@ +[run] +branch = True + +[report] +omit = + */conftest.py +exclude_lines = + # Re-enable the standard pragma + pragma: NO COVER + # Ignore debug-only repr + def __repr__ + # Don't complain if tests don't hit defensive assertion code: + raise NotImplementedError diff --git a/packages/google-auth/additional_packages/google_auth_oauthlib/CHANGELOG.rst b/packages/google-auth/additional_packages/google_auth_oauthlib/CHANGELOG.rst new file mode 100644 index 000000000000..dbb95df40aee --- /dev/null +++ b/packages/google-auth/additional_packages/google_auth_oauthlib/CHANGELOG.rst @@ -0,0 +1,4 @@ +v0.0.1 +------ + +Initial release. This package contains the functionality previously located in `google.oauth2.oauthlib` and `google.oauth2.flows`. diff --git a/packages/google-auth/additional_packages/google_auth_oauthlib/LICENSE b/packages/google-auth/additional_packages/google_auth_oauthlib/LICENSE new file mode 100644 index 000000000000..261eeb9e9f8b --- /dev/null +++ b/packages/google-auth/additional_packages/google_auth_oauthlib/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/google-auth/additional_packages/google_auth_oauthlib/MANIFEST.in b/packages/google-auth/additional_packages/google_auth_oauthlib/MANIFEST.in new file mode 100644 index 000000000000..aac7fe13adac --- /dev/null +++ b/packages/google-auth/additional_packages/google_auth_oauthlib/MANIFEST.in @@ -0,0 +1,2 @@ +include README.rst LICENSE +recursive-include tests * diff --git a/packages/google-auth/additional_packages/google_auth_oauthlib/README.rst b/packages/google-auth/additional_packages/google_auth_oauthlib/README.rst new file mode 100644 index 000000000000..44c478b165b1 --- /dev/null +++ b/packages/google-auth/additional_packages/google_auth_oauthlib/README.rst @@ -0,0 +1,28 @@ +oauthlib integration for Google Auth +==================================== + +|pypi| + +This library provides `oauthlib`_ integration with `google-auth`_. + +.. |pypi| image:: https://img.shields.io/pypi/v/google-auth-oauthlib.svg + :target: https://pypi.python.org/pypi/google-auth-oauthlib + +.. _oauthlib: https://github.com/idan/oauthlib +.. _google-auth: https://github.com/GoogleCloudPlatform/google-auth + +Installing +---------- + +You can install using `pip`_:: + + $ pip install google-auth-oauthlib + +.. _pip: https://pip.pypa.io/en/stable/ + +License +------- + +Apache 2.0 - See `the LICENSE`_ for more information. + +.. _the LICENSE: https://github.com/GoogleCloudPlatform/google-auth-library-python/blob/master/LICENSE diff --git a/packages/google-auth/additional_packages/google_auth_oauthlib/google_auth_oauthlib/__init__.py b/packages/google-auth/additional_packages/google_auth_oauthlib/google_auth_oauthlib/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/google-auth/google/oauth2/flow.py b/packages/google-auth/additional_packages/google_auth_oauthlib/google_auth_oauthlib/flow.py similarity index 96% rename from packages/google-auth/google/oauth2/flow.py rename to packages/google-auth/additional_packages/google_auth_oauthlib/google_auth_oauthlib/flow.py index e70ee3dd3258..a0406594a15c 100644 --- a/packages/google-auth/google/oauth2/flow.py +++ b/packages/google-auth/additional_packages/google_auth_oauthlib/google_auth_oauthlib/flow.py @@ -14,21 +14,17 @@ """OAuth 2.0 Authorization Flow -.. warning:: - This module is experimental and is subject to change signficantly - within major version releases. - This module provides integration with `requests-oauthlib`_ for running the `OAuth 2.0 Authorization Flow`_ and acquiring user credentials. Here's an example of using the flow with the installed application authorization flow:: - import google.oauth2.flow + from google_auth_oauthlib.flow import Flow # Create the flow using the client secrets file from the Google API # Console. - flow = google.oauth2.flow.Flow.from_client_secrets_file( + flow = Flow.from_client_secrets_file( 'path/to/client_secrets.json', scopes=['profile', 'email'], redirect_uri='urn:ietf:wg:oauth:2.0:oob') @@ -57,7 +53,8 @@ import google.auth.transport.requests import google.oauth2.credentials -import google.oauth2.oauthlib + +import google_auth_oauthlib.helpers class Flow(object): @@ -133,7 +130,7 @@ def from_client_config(cls, client_config, scopes, **kwargs): 'Client secrets must be for a web or installed app.') session, client_config = ( - google.oauth2.oauthlib.session_from_client_config( + google_auth_oauthlib.helpers.session_from_client_config( client_config, scopes, **kwargs)) return cls(session, client_type, client_config) @@ -240,7 +237,7 @@ def credentials(self): Raises: ValueError: If there is no access token in the session. """ - return google.oauth2.oauthlib.credentials_from_session( + return google_auth_oauthlib.helpers.credentials_from_session( self.oauth2session, self.client_config) def authorized_session(self): diff --git a/packages/google-auth/google/oauth2/oauthlib.py b/packages/google-auth/additional_packages/google_auth_oauthlib/google_auth_oauthlib/helpers.py similarity index 96% rename from packages/google-auth/google/oauth2/oauthlib.py rename to packages/google-auth/additional_packages/google_auth_oauthlib/google_auth_oauthlib/helpers.py index e1c6089721d1..901423bde387 100644 --- a/packages/google-auth/google/oauth2/oauthlib.py +++ b/packages/google-auth/additional_packages/google_auth_oauthlib/google_auth_oauthlib/helpers.py @@ -12,15 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Integration with oauthlib - -.. warning:: - This module is experimental and is subject to change signficantly - within major version releases. +"""Integration helpers. This module provides helpers for integrating with `requests-oauthlib`_. Typically, you'll want to use the higher-level helpers in -:mod:`google.oauth2.flow`. +:mod:`google_auth_oauthlib.flow`. .. _requests-oauthlib: http://requests-oauthlib.readthedocs.io/en/stable/ """ diff --git a/packages/google-auth/additional_packages/google_auth_oauthlib/setup.cfg b/packages/google-auth/additional_packages/google_auth_oauthlib/setup.cfg new file mode 100644 index 000000000000..2a9acf13daa9 --- /dev/null +++ b/packages/google-auth/additional_packages/google_auth_oauthlib/setup.cfg @@ -0,0 +1,2 @@ +[bdist_wheel] +universal = 1 diff --git a/packages/google-auth/additional_packages/google_auth_oauthlib/setup.py b/packages/google-auth/additional_packages/google_auth_oauthlib/setup.py new file mode 100644 index 000000000000..36524706fab7 --- /dev/null +++ b/packages/google-auth/additional_packages/google_auth_oauthlib/setup.py @@ -0,0 +1,59 @@ +# Copyright 2014 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import io + +from setuptools import find_packages +from setuptools import setup + + +DEPENDENCIES = ( + 'google-auth', + 'requests-oauthlib>=0.7.0', +) + + +with io.open('README.rst', 'r') as fh: + long_description = fh.read() + + +setup( + name='google-auth-oauthlib', + version='0.0.1', + author='Google Cloud Platform', + author_email='jonwayne+google-auth@google.com', + description='Google Authentication Library', + long_description=long_description, + url='https://github.com/GoogleCloudPlatform/google-auth-library-python', + packages=find_packages(exclude=('tests*',)), + install_requires=DEPENDENCIES, + license='Apache 2.0', + keywords='google auth oauth client oauthlib', + classifiers=( + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Development Status :: 3 - Alpha', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: Apache Software License', + 'Operating System :: POSIX', + 'Operating System :: Microsoft :: Windows', + 'Operating System :: MacOS :: MacOS X', + 'Operating System :: OS Independent', + 'Topic :: Internet :: WWW/HTTP', + ), +) diff --git a/packages/google-auth/additional_packages/google_auth_oauthlib/tests/__init__.py b/packages/google-auth/additional_packages/google_auth_oauthlib/tests/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/google-auth/additional_packages/google_auth_oauthlib/tests/data/client_secrets.json b/packages/google-auth/additional_packages/google_auth_oauthlib/tests/data/client_secrets.json new file mode 100644 index 000000000000..1baa4995aff5 --- /dev/null +++ b/packages/google-auth/additional_packages/google_auth_oauthlib/tests/data/client_secrets.json @@ -0,0 +1,14 @@ +{ + "web": { + "client_id": "example.apps.googleusercontent.com", + "project_id": "example", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://accounts.google.com/o/oauth2/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_secret": "itsasecrettoeveryone", + "redirect_uris": [ + "urn:ietf:wg:oauth:2.0:oob", + "http://localhost" + ] + } +} diff --git a/packages/google-auth/tests/oauth2/test_flow.py b/packages/google-auth/additional_packages/google_auth_oauthlib/tests/test_flow.py similarity index 97% rename from packages/google-auth/tests/oauth2/test_flow.py rename to packages/google-auth/additional_packages/google_auth_oauthlib/tests/test_flow.py index e5d108fd5282..663a02f9aa14 100644 --- a/packages/google-auth/tests/oauth2/test_flow.py +++ b/packages/google-auth/additional_packages/google_auth_oauthlib/tests/test_flow.py @@ -18,9 +18,9 @@ import mock import pytest -from google.oauth2 import flow +from google_auth_oauthlib import flow -DATA_DIR = os.path.join(os.path.dirname(__file__), '..', 'data') +DATA_DIR = os.path.join(os.path.dirname(__file__), 'data') CLIENT_SECRETS_FILE = os.path.join(DATA_DIR, 'client_secrets.json') with open(CLIENT_SECRETS_FILE, 'r') as fh: diff --git a/packages/google-auth/tests/oauth2/test_oauthlib.py b/packages/google-auth/additional_packages/google_auth_oauthlib/tests/test_helpers.py similarity index 81% rename from packages/google-auth/tests/oauth2/test_oauthlib.py rename to packages/google-auth/additional_packages/google_auth_oauthlib/tests/test_helpers.py index a16c9044cb13..faae76bc4e80 100644 --- a/packages/google-auth/tests/oauth2/test_oauthlib.py +++ b/packages/google-auth/additional_packages/google_auth_oauthlib/tests/test_helpers.py @@ -18,9 +18,9 @@ import mock import pytest -from google.oauth2 import oauthlib +from google_auth_oauthlib import helpers -DATA_DIR = os.path.join(os.path.dirname(__file__), '..', 'data') +DATA_DIR = os.path.join(os.path.dirname(__file__), 'data') CLIENT_SECRETS_FILE = os.path.join(DATA_DIR, 'client_secrets.json') with open(CLIENT_SECRETS_FILE, 'r') as fh: @@ -28,7 +28,7 @@ def test_session_from_client_config_web(): - session, config = oauthlib.session_from_client_config( + session, config = helpers.session_from_client_config( CLIENT_SECRETS_INFO, scopes=mock.sentinel.scopes) assert config == CLIENT_SECRETS_INFO @@ -38,7 +38,7 @@ def test_session_from_client_config_web(): def test_session_from_client_config_installed(): info = {'installed': CLIENT_SECRETS_INFO['web']} - session, config = oauthlib.session_from_client_config( + session, config = helpers.session_from_client_config( info, scopes=mock.sentinel.scopes) assert config == info assert session.client_id == info['installed']['client_id'] @@ -47,16 +47,16 @@ def test_session_from_client_config_installed(): def test_session_from_client_config_bad_format(): with pytest.raises(ValueError): - oauthlib.session_from_client_config({}, scopes=[]) + helpers.session_from_client_config({}, scopes=[]) def test_session_from_client_config_missing_keys(): with pytest.raises(ValueError): - oauthlib.session_from_client_config({'web': {}}, scopes=[]) + helpers.session_from_client_config({'web': {}}, scopes=[]) def test_session_from_client_secrets_file(): - session, config = oauthlib.session_from_client_secrets_file( + session, config = helpers.session_from_client_secrets_file( CLIENT_SECRETS_FILE, scopes=mock.sentinel.scopes) assert config == CLIENT_SECRETS_INFO assert session.client_id == CLIENT_SECRETS_INFO['web']['client_id'] @@ -65,7 +65,7 @@ def test_session_from_client_secrets_file(): @pytest.fixture def session(): - session, _ = oauthlib.session_from_client_config( + session, _ = helpers.session_from_client_config( CLIENT_SECRETS_INFO, scopes=mock.sentinel.scopes) yield session @@ -76,7 +76,7 @@ def test_credentials_from_session(session): 'refresh_token': mock.sentinel.refresh_token } - credentials = oauthlib.credentials_from_session( + credentials = helpers.credentials_from_session( session, CLIENT_SECRETS_INFO['web']) assert credentials.token == mock.sentinel.access_token @@ -89,4 +89,4 @@ def test_credentials_from_session(session): def test_bad_credentials(session): with pytest.raises(ValueError): - oauthlib.credentials_from_session(session) + helpers.credentials_from_session(session) diff --git a/packages/google-auth/additional_packages/google_auth_oauthlib/tox.ini b/packages/google-auth/additional_packages/google_auth_oauthlib/tox.ini new file mode 100644 index 000000000000..3cac0895e7a9 --- /dev/null +++ b/packages/google-auth/additional_packages/google_auth_oauthlib/tox.ini @@ -0,0 +1,35 @@ +[tox] +envlist = lint,py27,py34,py35,py36,pypy,cover + +[testenv] +deps = + mock + pytest + pytest-cov +commands = + py.test --cov=google_auth_oauthlib --cov=tests {posargs:tests} + +[testenv:cover] +basepython = python3.6 +commands = + py.test --cov=google_auth_oauthlib --cov=tests --cov-report= tests + coverage report --show-missing --fail-under=100 +deps = + {[testenv]deps} + +[testenv:lint] +basepython = python3.5 +commands = + python setup.py check --metadata --restructuredtext --strict + flake8 \ + --import-order-style=google \ + --application-import-names="google_auth_oauthlib,tests" \ + google_auth_oauthlib tests + python {toxinidir}/../../scripts/run_pylint.py \ + --library-filesets google_auth_oauthlib \ + --test-filesets tests +deps = + flake8 + flake8-import-order + pylint + docutils diff --git a/packages/google-auth/httplib2_transport/scripts/.gitignore b/packages/google-auth/httplib2_transport/scripts/.gitignore deleted file mode 100644 index 3596d32ac39c..000000000000 --- a/packages/google-auth/httplib2_transport/scripts/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Generated files -pylintrc -pylintrc.test diff --git a/packages/google-auth/httplib2_transport/scripts/run_pylint.py b/packages/google-auth/httplib2_transport/scripts/run_pylint.py deleted file mode 100644 index 643957aa531e..000000000000 --- a/packages/google-auth/httplib2_transport/scripts/run_pylint.py +++ /dev/null @@ -1,256 +0,0 @@ -# Copyright 2016 Google Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""This script runs Pylint on the specified source. - -Before running Pylint, it generates a Pylint configuration on -the fly based on programmatic defaults. - -.. note:: - - This file **really** breaks DRY, but is provided here as - **mostly** a copy of the true source file: - ``httplib2_transport/../scripts/run_pylint.py``. - This is done so that the ``httplib2_transport`` sub-package - can be self-contained. -""" - -from __future__ import print_function - -import collections -import copy -import io -import os -import subprocess -import sys - -import six - - -_SCRIPTS_DIR = os.path.abspath(os.path.dirname(__file__)) -PRODUCTION_RC = os.path.join(_SCRIPTS_DIR, 'pylintrc') -TEST_RC = os.path.join(_SCRIPTS_DIR, 'pylintrc.test') - -_PRODUCTION_RC_ADDITIONS = { - 'MESSAGES CONTROL': { - 'disable': [ - 'I', - 'import-error', - ], - }, -} -_PRODUCTION_RC_REPLACEMENTS = { - 'MASTER': { - 'ignore': ['CVS', '.git', '.cache', '.tox', '.nox'], - 'load-plugins': 'pylint.extensions.check_docs', - }, - 'REPORTS': { - 'reports': 'no', - }, - 'BASIC': { - 'method-rgx': '[a-z_][a-z0-9_]{2,40}$', - 'function-rgx': '[a-z_][a-z0-9_]{2,40}$', - }, - 'TYPECHECK': { - 'ignored-modules': ['six', 'google.protobuf'], - }, - 'DESIGN': { - 'min-public-methods': '0', - 'max-args': '10', - 'max-attributes': '15', - }, -} -_TEST_RC_ADDITIONS = copy.deepcopy(_PRODUCTION_RC_ADDITIONS) -_TEST_RC_ADDITIONS['MESSAGES CONTROL']['disable'].extend([ - 'missing-docstring', - 'no-member', - 'no-self-use', - 'protected-access', - 'unused-argument', -]) -_TEST_RC_REPLACEMENTS = copy.deepcopy(_PRODUCTION_RC_REPLACEMENTS) -_TEST_RC_REPLACEMENTS.setdefault('BASIC', {}) -_TEST_RC_REPLACEMENTS['BASIC'].update({ - 'good-names': ['i', 'j', 'k', 'ex', 'Run', '_', 'fh'], - 'method-rgx': '[a-z_][a-z0-9_]{2,80}$', - 'function-rgx': '[a-z_][a-z0-9_]{2,80}$', -}) -IGNORED_FILES = () - -_ERROR_TEMPLATE = 'Pylint failed on {} with status {:d}.' -_LINT_FILESET_MSG = ( - 'Keyword arguments rc_filename and description are both ' - 'required. No other keyword arguments are allowed.') - - -def get_default_config(): - """Get the default Pylint configuration. - - .. note:: - - The output of this function varies based on the current version of - Pylint installed. - - Returns: - str: The default Pylint configuration. - """ - # Swallow STDERR if it says - # "No config file found, using default configuration" - result = subprocess.check_output(['pylint', '--generate-rcfile'], - stderr=subprocess.PIPE) - # On Python 3, this returns bytes (from STDOUT), so we - # convert to a string. - return result.decode('utf-8') - - -def read_config(contents): - """Reads pylintrc config into native ConfigParser object. - - Args: - contents (str): The contents of the file containing the INI config. - - Returns - ConfigParser.ConfigParser: The parsed configuration. - """ - file_obj = io.StringIO(contents) - config = six.moves.configparser.ConfigParser() - config.readfp(file_obj) - return config - - -def _transform_opt(opt_val): - """Transform a config option value to a string. - - If already a string, do nothing. If an iterable, then - combine into a string by joining on ",". - - Args: - opt_val (Union[str, list]): A config option's value. - - Returns: - str: The option value converted to a string. - """ - if isinstance(opt_val, (list, tuple)): - return ','.join(opt_val) - else: - return opt_val - - -def lint_fileset(*paths, **kwargs): - """Lints a group of files using a given rcfile. - - Keyword arguments are - - * ``rc_filename`` (``str``): The name of the Pylint config RC file. - * ``description`` (``str``): A description of the files and configuration - currently being run. - - Args: - paths (tuple): Directories or filenames to run Pylint in/on. - kwargs: The keyword arguments. The only keyword arguments - are ``rc_filename`` and ``description`` and both - are required. - - Raises: - KeyError: If the wrong keyword arguments are used. - """ - try: - rc_filename = kwargs['rc_filename'] - description = kwargs['description'] - if len(kwargs) != 2: - raise KeyError - except KeyError: - raise KeyError(_LINT_FILESET_MSG) - - pylint_shell_command = ['pylint', '--rcfile', rc_filename] - pylint_shell_command.extend(paths) - status_code = subprocess.call(pylint_shell_command) - if status_code != 0: - error_message = _ERROR_TEMPLATE.format(description, status_code) - print(error_message, file=sys.stderr) - sys.exit(status_code) - - -def make_rc(base_cfg, target_filename, - additions=None, replacements=None): - """Combines a base rc and additions into single file. - - Args: - base_cfg (ConfigParser.ConfigParser): The configuration we are - merging into. - target_filename (str): The filename where the new configuration - will be saved. - additions (dict): (Optional) The values added to the configuration. - replacements (dict): (Optional) The wholesale replacements for - the new configuration. - - Raises: - KeyError: if one of the additions or replacements does not - already exist in the current config. - """ - # Set-up the mutable default values. - if additions is None: - additions = {} - if replacements is None: - replacements = {} - - # Create fresh config, which must extend the base one. - new_cfg = six.moves.configparser.ConfigParser() - # pylint: disable=protected-access - new_cfg._sections = copy.deepcopy(base_cfg._sections) - new_sections = new_cfg._sections - # pylint: enable=protected-access - - for section, opts in additions.items(): - curr_section = new_sections.setdefault( - section, collections.OrderedDict()) - for opt, opt_val in opts.items(): - curr_val = curr_section.get(opt) - if curr_val is None: - raise KeyError('Expected to be adding to existing option.') - curr_val = curr_val.rstrip(',') - opt_val = _transform_opt(opt_val) - curr_section[opt] = '%s, %s' % (curr_val, opt_val) - - for section, opts in replacements.items(): - curr_section = new_sections.setdefault( - section, collections.OrderedDict()) - for opt, opt_val in opts.items(): - curr_val = curr_section.get(opt) - if curr_val is None: - raise KeyError('Expected to be replacing existing option.') - opt_val = _transform_opt(opt_val) - curr_section[opt] = '%s' % (opt_val,) - - with open(target_filename, 'w') as file_obj: - new_cfg.write(file_obj) - - -def main(): - """Script entry point. Lints both sets of files.""" - default_config = read_config(get_default_config()) - make_rc(default_config, PRODUCTION_RC, - additions=_PRODUCTION_RC_ADDITIONS, - replacements=_PRODUCTION_RC_REPLACEMENTS) - make_rc(default_config, TEST_RC, - additions=_TEST_RC_ADDITIONS, - replacements=_TEST_RC_REPLACEMENTS) - lint_fileset('google_auth_httplib2.py', rc_filename=PRODUCTION_RC, - description='Library') - lint_fileset('tests', rc_filename=TEST_RC, - description='Test') - - -if __name__ == '__main__': - main() diff --git a/packages/google-auth/scripts/run_pylint.py b/packages/google-auth/scripts/run_pylint.py index e9ac7ec779d5..c33a53e1e96b 100644 --- a/packages/google-auth/scripts/run_pylint.py +++ b/packages/google-auth/scripts/run_pylint.py @@ -20,6 +20,7 @@ from __future__ import print_function +import argparse import collections import copy import io @@ -236,6 +237,12 @@ def make_rc(base_cfg, target_filename, def main(): """Script entry point. Lints both sets of files.""" + parser = argparse.ArgumentParser() + parser.add_argument('--library-filesets', nargs='+', default=[]) + parser.add_argument('--test-filesets', nargs='+', default=[]) + + args = parser.parse_args() + default_config = read_config(get_default_config()) make_rc(default_config, PRODUCTION_RC, additions=_PRODUCTION_RC_ADDITIONS, @@ -243,11 +250,11 @@ def main(): make_rc(default_config, TEST_RC, additions=_TEST_RC_ADDITIONS, replacements=_TEST_RC_REPLACEMENTS) - lint_fileset('google', rc_filename=PRODUCTION_RC, + + lint_fileset(*args.library_filesets, rc_filename=PRODUCTION_RC, description='Library') - lint_fileset('tests', 'system_tests', rc_filename=TEST_RC, + lint_fileset(*args.test_filesets, rc_filename=TEST_RC, description='Test') - if __name__ == '__main__': main() diff --git a/packages/google-auth/scripts/travis.sh b/packages/google-auth/scripts/travis.sh index 9e11de85fc73..5acb4c2a5ccd 100755 --- a/packages/google-auth/scripts/travis.sh +++ b/packages/google-auth/scripts/travis.sh @@ -43,7 +43,14 @@ tox # Run tox for sub-packages. if [[ $TOXENV != "docs" && -z $SYSTEM_TEST ]]; then - echo "Running tox for httplib2_transport..." - cd httplib2_transport - tox + echo "Running tox for google_auth_httplib2..." + cd additional_packages/google_auth_httplib2 + # --workdir is specified to avoid path names being too long, which + # causes subprocess calls to hit the execve character limit. + # See https://github.com/pypa/virtualenv/issues/596 + tox --workdir ~/.tox-httplib2 + cd $ROOT + echo "Running tox for google_auth_oauthlib..." + cd additional_packages/google_auth_oauthlib + tox --workdir ~/.tox-oauthlib fi diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 8c5193230bae..76bceb8bc96e 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -25,10 +25,6 @@ 'six>=1.9.0', ) -EXTRA_OAUTHLIB_DEPENDENCIES = ( - 'requests-oauthlib>=0.7.0', -) - with io.open('README.rst', 'r') as fh: long_description = fh.read() @@ -44,9 +40,6 @@ packages=find_packages(exclude=('tests*', 'system_tests*')), namespace_packages=('google',), install_requires=DEPENDENCIES, - extras_require={ - 'oauthlib': EXTRA_OAUTHLIB_DEPENDENCIES, - }, license='Apache 2.0', keywords='google auth oauth client', classifiers=( @@ -55,6 +48,7 @@ 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', 'License :: OSI Approved :: Apache Software License', diff --git a/packages/google-auth/tox.ini b/packages/google-auth/tox.ini index 528806cbc41e..27f7d87bd304 100644 --- a/packages/google-auth/tox.ini +++ b/packages/google-auth/tox.ini @@ -77,7 +77,9 @@ commands = --import-order-style=google \ --application-import-names="google,tests,system_tests" \ google tests - python {toxinidir}/scripts/run_pylint.py + python {toxinidir}/scripts/run_pylint.py \ + --library-filesets google \ + --test-filesets tests system_tests deps = flake8 flake8-import-order From 6e60a3c8b9c29ccd8773962bd81b9fb57ccbccc5 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Wed, 22 Mar 2017 13:43:48 -0700 Subject: [PATCH 116/966] Add InstalledAppFlow (#128) --- .../google_auth_oauthlib/flow.py | 205 ++++++++++++++- .../google_auth_oauthlib/tests/test_flow.py | 245 ++++++++++++------ .../google_auth_oauthlib/tox.ini | 1 + packages/google-auth/tox.ini | 6 +- 4 files changed, 371 insertions(+), 86 deletions(-) diff --git a/packages/google-auth/additional_packages/google_auth_oauthlib/google_auth_oauthlib/flow.py b/packages/google-auth/additional_packages/google_auth_oauthlib/google_auth_oauthlib/flow.py index a0406594a15c..0df96c0c0489 100644 --- a/packages/google-auth/additional_packages/google_auth_oauthlib/google_auth_oauthlib/flow.py +++ b/packages/google-auth/additional_packages/google_auth_oauthlib/google_auth_oauthlib/flow.py @@ -17,7 +17,7 @@ This module provides integration with `requests-oauthlib`_ for running the `OAuth 2.0 Authorization Flow`_ and acquiring user credentials. -Here's an example of using the flow with the installed application +Here's an example of using :class:`Flow` with the installed application authorization flow:: from google_auth_oauthlib.flow import Flow @@ -44,19 +44,30 @@ session = flow.authorized_session() print(session.get('https://www.googleapis.com/userinfo/v2/me').json()) +This particular flow can be handled entirely by using +:class:`InstalledAppFlow`. + .. _requests-oauthlib: http://requests-oauthlib.readthedocs.io/en/stable/ .. _OAuth 2.0 Authorization Flow: https://tools.ietf.org/html/rfc6749#section-1.2 """ import json +import logging +import webbrowser +import wsgiref.simple_server +import wsgiref.util import google.auth.transport.requests import google.oauth2.credentials +from six.moves import input import google_auth_oauthlib.helpers +_LOGGER = logging.getLogger(__name__) + + class Flow(object): """OAuth 2.0 Authorization Flow @@ -253,3 +264,195 @@ class using this flow's :attr:`credentials`. """ return google.auth.transport.requests.AuthorizedSession( self.credentials) + + +class InstalledAppFlow(Flow): + """Authorization flow helper for installed applications. + + This :class:`Flow` subclass makes it easier to perform the + `Installed Application Authorization Flow`_. This flow is useful for + local development or applications that are installed on a desktop operating + system. + + This flow has two strategies: The console strategy provided by + :meth:`run_console` and the local server strategy provided by + :meth:`run_local_server`. + + Example:: + + from google_auth_oauthlib.flow import InstalledAppFlow + + flow = InstalledAppFlow.from_client_secrets_file( + 'client_secrets.json', + scopes=['profile', 'email']) + + flow.run_local_server() + + session = flow.authorized_session() + + profile_info = session.get( + 'https://www.googleapis.com/userinfo/v2/me').json() + + print(profile_info) + # {'name': '...', 'email': '...', ...} + + + Note that these aren't the only two ways to accomplish the installed + application flow, they are just the most common ways. You can use the + :class:`Flow` class to perform the same flow with different methods of + presenting the authorization URL to the user or obtaining the authorization + response, such as using an embedded web view. + + .. _Installed Application Authorization Flow: + https://developers.google.com/api-client-library/python/auth + /installed-app + """ + _OOB_REDIRECT_URI = 'urn:ietf:wg:oauth:2.0:oob' + + _DEFAULT_AUTH_PROMPT_MESSAGE = ( + 'Please visit this URL to authorize this application: {url}') + """str: The message to display when prompting the user for + authorization.""" + _DEFAULT_AUTH_CODE_MESSAGE = ( + 'Enter the authorization code: ') + """str: The message to display when prompting the user for the + authorization code. Used only by the console strategy.""" + + _DEFAULT_WEB_SUCCESS_MESSAGE = ( + 'The authentication flow has completed, you may close this window.') + + def run_console( + self, + authorization_prompt_message=_DEFAULT_AUTH_PROMPT_MESSAGE, + authorization_code_message=_DEFAULT_AUTH_CODE_MESSAGE, + **kwargs): + """Run the flow using the console strategy. + + The console strategy instructs the user to open the authorization URL + in their browser. Once the authorization is complete the authorization + server will give the user a code. The user then must copy & paste this + code into the application. The code is then exchanged for a token. + + Args: + authorization_prompt_message (str): The message to display to tell + the user to navigate to the authorization URL. + authorization_code_message (str): The message to display when + prompting the user for the authorization code. + kwargs: Additional keyword arguments passed through to + :meth:`authorization_url`. + + Returns: + google.oauth2.credentials.Credentials: The OAuth 2.0 credentials + for the user. + """ + kwargs.setdefault('prompt', 'consent') + + self.redirect_uri = self._OOB_REDIRECT_URI + + auth_url, _ = self.authorization_url(**kwargs) + + print(authorization_prompt_message.format(url=auth_url)) + + code = input(authorization_code_message) + + self.fetch_token(code=code) + + return self.credentials + + def run_local_server( + self, host='localhost', port=8080, + authorization_prompt_message=_DEFAULT_AUTH_PROMPT_MESSAGE, + success_message=_DEFAULT_WEB_SUCCESS_MESSAGE, + open_browser=True, + **kwargs): + """Run the flow using the server strategy. + + The server strategy instructs the user to open the authorization URL in + their browser and will attempt to automatically open the URL for them. + It will start a local web server to listen for the authorization + response. Once authorization is complete the authorization server will + redirect the user's browser to the local web server. The web server + will get the authorization code from the response and shutdown. The + code is then exchanged for a token. + + Args: + host (str): The hostname for the local redirect server. This will + be served over http, not https. + port (int): The port for the local redirect server. + authorization_prompt_message (str): The message to display to tell + the user to navigate to the authorization URL. + success_message (str): The message to display in the web browser + the authorization flow is complete. + open_browser (bool): Whether or not to open the authorization URL + in the user's browser. + kwargs: Additional keyword arguments passed through to + :meth:`authorization_url`. + + Returns: + google.oauth2.credentials.Credentials: The OAuth 2.0 credentials + for the user. + """ + self.redirect_uri = 'http://{}:{}/'.format(host, port) + + auth_url, _ = self.authorization_url(**kwargs) + + wsgi_app = _RedirectWSGIApp(success_message) + local_server = wsgiref.simple_server.make_server( + host, port, wsgi_app, handler_class=_WSGIRequestHandler) + + if open_browser: + webbrowser.open(auth_url, new=1, autoraise=True) + + print(authorization_prompt_message.format(url=auth_url)) + + local_server.handle_request() + + # Note: using https here because oauthlib is very picky that + # OAuth 2.0 should only occur over https. + authorization_response = wsgi_app.last_request_uri.replace( + 'http', 'https') + self.fetch_token(authorization_response=authorization_response) + + return self.credentials + + +class _WSGIRequestHandler(wsgiref.simple_server.WSGIRequestHandler): + """Custom WSGIRequestHandler. + + Uses a named logger instead of printing to stderr. + """ + def log_message(self, format, *args, **kwargs): + # pylint: disable=redefined-builtin + # (format is the argument name defined in the superclass.) + _LOGGER.info(format, *args, **kwargs) + + +class _RedirectWSGIApp(object): + """WSGI app to handle the authorization redirect. + + Stores the request URI and displays the given success message. + """ + + def __init__(self, success_message): + """ + Args: + success_message (str): The message to display in the web browser + the authorization flow is complete. + """ + self.last_request_uri = None + self._success_message = success_message + + def __call__(self, environ, start_response): + """WSGI Callable. + + Args: + environ (Mapping[str, Any]): The WSGI environment. + start_response (Callable[str, list]): The WSGI start_response + callable. + + Returns: + Iterable[bytes]: The response body. + """ + start_response('200 OK', [('Content-type', 'text/plain')]) + self.last_request_uri = wsgiref.util.request_uri(environ) + return [self._success_message.encode('utf-8')] diff --git a/packages/google-auth/additional_packages/google_auth_oauthlib/tests/test_flow.py b/packages/google-auth/additional_packages/google_auth_oauthlib/tests/test_flow.py index 663a02f9aa14..88d283dfd9a5 100644 --- a/packages/google-auth/additional_packages/google_auth_oauthlib/tests/test_flow.py +++ b/packages/google-auth/additional_packages/google_auth_oauthlib/tests/test_flow.py @@ -12,11 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. +import concurrent.futures import json import os import mock import pytest +import requests +from six.moves import urllib from google_auth_oauthlib import flow @@ -27,98 +30,176 @@ CLIENT_SECRETS_INFO = json.load(fh) -def test_from_client_secrets_file(): - instance = flow.Flow.from_client_secrets_file( - CLIENT_SECRETS_FILE, scopes=mock.sentinel.scopes) - assert instance.client_config == CLIENT_SECRETS_INFO['web'] - assert (instance.oauth2session.client_id == - CLIENT_SECRETS_INFO['web']['client_id']) - assert instance.oauth2session.scope == mock.sentinel.scopes - - -def test_from_client_config_installed(): - client_config = {'installed': CLIENT_SECRETS_INFO['web']} - instance = flow.Flow.from_client_config( - client_config, scopes=mock.sentinel.scopes) - assert instance.client_config == client_config['installed'] - assert (instance.oauth2session.client_id == - client_config['installed']['client_id']) - assert instance.oauth2session.scope == mock.sentinel.scopes - - -def test_from_client_config_bad_format(): - with pytest.raises(ValueError): - flow.Flow.from_client_config({}, scopes=mock.sentinel.scopes) - - -@pytest.fixture -def instance(): - yield flow.Flow.from_client_config( - CLIENT_SECRETS_INFO, scopes=mock.sentinel.scopes) - - -def test_redirect_uri(instance): - instance.redirect_uri = mock.sentinel.redirect_uri - assert (instance.redirect_uri == - instance.oauth2session.redirect_uri == - mock.sentinel.redirect_uri) - - -def test_authorization_url(instance): - scope = 'scope_one' - instance.oauth2session.scope = [scope] - authorization_url_patch = mock.patch.object( - instance.oauth2session, 'authorization_url', - wraps=instance.oauth2session.authorization_url) +class TestFlow(object): + def test_from_client_secrets_file(self): + instance = flow.Flow.from_client_secrets_file( + CLIENT_SECRETS_FILE, scopes=mock.sentinel.scopes) + assert instance.client_config == CLIENT_SECRETS_INFO['web'] + assert (instance.oauth2session.client_id == + CLIENT_SECRETS_INFO['web']['client_id']) + assert instance.oauth2session.scope == mock.sentinel.scopes + + def test_from_client_config_installed(self): + client_config = {'installed': CLIENT_SECRETS_INFO['web']} + instance = flow.Flow.from_client_config( + client_config, scopes=mock.sentinel.scopes) + assert instance.client_config == client_config['installed'] + assert (instance.oauth2session.client_id == + client_config['installed']['client_id']) + assert instance.oauth2session.scope == mock.sentinel.scopes + + def test_from_client_config_bad_format(self): + with pytest.raises(ValueError): + flow.Flow.from_client_config({}, scopes=mock.sentinel.scopes) + + @pytest.fixture + def instance(self): + yield flow.Flow.from_client_config( + CLIENT_SECRETS_INFO, scopes=mock.sentinel.scopes) + + def test_redirect_uri(self, instance): + instance.redirect_uri = mock.sentinel.redirect_uri + assert (instance.redirect_uri == + instance.oauth2session.redirect_uri == + mock.sentinel.redirect_uri) + + def test_authorization_url(self, instance): + scope = 'scope_one' + instance.oauth2session.scope = [scope] + authorization_url_patch = mock.patch.object( + instance.oauth2session, 'authorization_url', + wraps=instance.oauth2session.authorization_url) + + with authorization_url_patch as authorization_url_spy: + url, _ = instance.authorization_url(prompt='consent') + + assert CLIENT_SECRETS_INFO['web']['auth_uri'] in url + assert scope in url + authorization_url_spy.assert_called_with( + CLIENT_SECRETS_INFO['web']['auth_uri'], + access_type='offline', + prompt='consent') + + def test_fetch_token(self, instance): + fetch_token_patch = mock.patch.object( + instance.oauth2session, 'fetch_token', autospec=True, + return_value=mock.sentinel.token) + + with fetch_token_patch as fetch_token_mock: + token = instance.fetch_token(code=mock.sentinel.code) + + assert token == mock.sentinel.token + fetch_token_mock.assert_called_with( + CLIENT_SECRETS_INFO['web']['token_uri'], + client_secret=CLIENT_SECRETS_INFO['web']['client_secret'], + code=mock.sentinel.code) + + def test_credentials(self, instance): + instance.oauth2session.token = { + 'access_token': mock.sentinel.access_token, + 'refresh_token': mock.sentinel.refresh_token + } + + credentials = instance.credentials + + assert credentials.token == mock.sentinel.access_token + assert credentials._refresh_token == mock.sentinel.refresh_token + assert (credentials._client_id == + CLIENT_SECRETS_INFO['web']['client_id']) + assert (credentials._client_secret == + CLIENT_SECRETS_INFO['web']['client_secret']) + assert (credentials._token_uri == + CLIENT_SECRETS_INFO['web']['token_uri']) + + def test_authorized_session(self, instance): + instance.oauth2session.token = { + 'access_token': mock.sentinel.access_token, + 'refresh_token': mock.sentinel.refresh_token + } + + session = instance.authorized_session() + + assert session.credentials.token == mock.sentinel.access_token + + +class TestInstalledAppFlow(object): + SCOPES = ['email', 'profile'] + REDIRECT_REQUEST_PATH = '/?code=code&state=state' + + @pytest.fixture + def instance(self): + yield flow.InstalledAppFlow.from_client_config( + CLIENT_SECRETS_INFO, scopes=self.SCOPES) + + @pytest.fixture + def mock_fetch_token(self, instance): + def set_token(*args, **kwargs): + instance.oauth2session.token = { + 'access_token': mock.sentinel.access_token, + 'refresh_token': mock.sentinel.refresh_token + } + + fetch_token_patch = mock.patch.object( + instance.oauth2session, 'fetch_token', autospec=True, + side_effect=set_token) + + with fetch_token_patch as fetch_token_mock: + yield fetch_token_mock + + @mock.patch('google_auth_oauthlib.flow.input', autospec=True) + def test_run_console(self, input_mock, instance, mock_fetch_token): + input_mock.return_value = mock.sentinel.code + + credentials = instance.run_console() + + assert credentials.token == mock.sentinel.access_token + assert credentials._refresh_token == mock.sentinel.refresh_token + + mock_fetch_token.assert_called_with( + CLIENT_SECRETS_INFO['web']['token_uri'], + client_secret=CLIENT_SECRETS_INFO['web']['client_secret'], + code=mock.sentinel.code) - with authorization_url_patch as authorization_url_spy: - url, _ = instance.authorization_url(prompt='consent') + @mock.patch('google_auth_oauthlib.flow.webbrowser', autospec=True) + def test_run_local_server( + self, webbrowser_mock, instance, mock_fetch_token): + auth_redirect_url = urllib.parse.urljoin( + 'http://localhost:8080', + self.REDIRECT_REQUEST_PATH) - assert CLIENT_SECRETS_INFO['web']['auth_uri'] in url - assert scope in url - authorization_url_spy.assert_called_with( - CLIENT_SECRETS_INFO['web']['auth_uri'], - access_type='offline', - prompt='consent') + with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool: + future = pool.submit(instance.run_local_server) + while not future.done(): + try: + requests.get(auth_redirect_url) + except requests.ConnectionError: # pragma: NO COVER + pass -def test_fetch_token(instance): - fetch_token_patch = mock.patch.object( - instance.oauth2session, 'fetch_token', autospec=True, - return_value=mock.sentinel.token) + credentials = future.result() - with fetch_token_patch as fetch_token_mock: - token = instance.fetch_token(code=mock.sentinel.code) + assert credentials.token == mock.sentinel.access_token + assert credentials._refresh_token == mock.sentinel.refresh_token + assert webbrowser_mock.open.called - assert token == mock.sentinel.token - fetch_token_mock.assert_called_with( + expected_auth_response = auth_redirect_url.replace('http', 'https') + mock_fetch_token.assert_called_with( CLIENT_SECRETS_INFO['web']['token_uri'], client_secret=CLIENT_SECRETS_INFO['web']['client_secret'], - code=mock.sentinel.code) - - -def test_credentials(instance): - instance.oauth2session.token = { - 'access_token': mock.sentinel.access_token, - 'refresh_token': mock.sentinel.refresh_token - } - - credentials = instance.credentials + authorization_response=expected_auth_response) - assert credentials.token == mock.sentinel.access_token - assert credentials._refresh_token == mock.sentinel.refresh_token - assert credentials._client_id == CLIENT_SECRETS_INFO['web']['client_id'] - assert (credentials._client_secret == - CLIENT_SECRETS_INFO['web']['client_secret']) - assert credentials._token_uri == CLIENT_SECRETS_INFO['web']['token_uri'] + @mock.patch('google_auth_oauthlib.flow.webbrowser', autospec=True) + @mock.patch('wsgiref.simple_server.make_server', autospec=True) + def test_run_local_server_no_browser( + self, make_server_mock, webbrowser_mock, instance, + mock_fetch_token): + def assign_last_request_uri(host, port, wsgi_app, **kwargs): + wsgi_app.last_request_uri = self.REDIRECT_REQUEST_PATH + return mock.Mock() -def test_authorized_session(instance): - instance.oauth2session.token = { - 'access_token': mock.sentinel.access_token, - 'refresh_token': mock.sentinel.refresh_token - } + make_server_mock.side_effect = assign_last_request_uri - session = instance.authorized_session() + instance.run_local_server(open_browser=False) - assert session.credentials.token == mock.sentinel.access_token + assert not webbrowser_mock.open.called diff --git a/packages/google-auth/additional_packages/google_auth_oauthlib/tox.ini b/packages/google-auth/additional_packages/google_auth_oauthlib/tox.ini index 3cac0895e7a9..c0983f601207 100644 --- a/packages/google-auth/additional_packages/google_auth_oauthlib/tox.ini +++ b/packages/google-auth/additional_packages/google_auth_oauthlib/tox.ini @@ -6,6 +6,7 @@ deps = mock pytest pytest-cov + futures commands = py.test --cov=google_auth_oauthlib --cov=tests {posargs:tests} diff --git a/packages/google-auth/tox.ini b/packages/google-auth/tox.ini index 27f7d87bd304..0a0d80647279 100644 --- a/packages/google-auth/tox.ini +++ b/packages/google-auth/tox.ini @@ -3,16 +3,16 @@ envlist = lint,py27,py34,py35,py36,pypy,cover [testenv] deps = + certifi flask mock + oauth2client pytest pytest-cov pytest-localserver - urllib3 - certifi requests requests-oauthlib - oauth2client + urllib3 grpcio; platform_python_implementation != 'PyPy' commands = py.test --cov=google.auth --cov=google.oauth2 --cov=tests {posargs:tests} From 73554a13c052eb4713a55cd1cd3b22ab4e582d4f Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Wed, 22 Mar 2017 13:45:26 -0700 Subject: [PATCH 117/966] Remove oauthlib modules from documentation, update user guide (#139) --- .../docs/reference/google.oauth2.flow.rst | 7 ------- .../docs/reference/google.oauth2.oauthlib.rst | 7 ------- .../docs/reference/google.oauth2.rst | 2 -- packages/google-auth/docs/user-guide.rst | 18 ++++++++++-------- 4 files changed, 10 insertions(+), 24 deletions(-) delete mode 100644 packages/google-auth/docs/reference/google.oauth2.flow.rst delete mode 100644 packages/google-auth/docs/reference/google.oauth2.oauthlib.rst diff --git a/packages/google-auth/docs/reference/google.oauth2.flow.rst b/packages/google-auth/docs/reference/google.oauth2.flow.rst deleted file mode 100644 index bae54084b951..000000000000 --- a/packages/google-auth/docs/reference/google.oauth2.flow.rst +++ /dev/null @@ -1,7 +0,0 @@ -google.oauth2.flow module -========================= - -.. automodule:: google.oauth2.flow - :members: - :inherited-members: - :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.oauth2.oauthlib.rst b/packages/google-auth/docs/reference/google.oauth2.oauthlib.rst deleted file mode 100644 index c687490a4099..000000000000 --- a/packages/google-auth/docs/reference/google.oauth2.oauthlib.rst +++ /dev/null @@ -1,7 +0,0 @@ -google.oauth2.oauthlib module -============================= - -.. automodule:: google.oauth2.oauthlib - :members: - :inherited-members: - :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.oauth2.rst b/packages/google-auth/docs/reference/google.oauth2.rst index 4dd1ebd97bd2..adb9403efe28 100644 --- a/packages/google-auth/docs/reference/google.oauth2.rst +++ b/packages/google-auth/docs/reference/google.oauth2.rst @@ -12,8 +12,6 @@ Submodules .. toctree:: google.oauth2.credentials - google.oauth2.flow google.oauth2.id_token - google.oauth2.oauthlib google.oauth2.service_account diff --git a/packages/google-auth/docs/user-guide.rst b/packages/google-auth/docs/user-guide.rst index f4cf8309795c..76740c7d56a6 100644 --- a/packages/google-auth/docs/user-guide.rst +++ b/packages/google-auth/docs/user-guide.rst @@ -157,24 +157,26 @@ URI to allow the credentials to be automatically refreshed:: client_secret='client_secret') -This library has some helpers for integrating with `requests-oauthlib`_ to -provide support for obtaining user credentials. You can use -:func:`google.oauth2.oauthlib.credentials_from_session` to obtain +There is a separate library, `google-auth-oauthlib`_, that has some helpers +for integrating with `requests-oauthlib`_ to provide support for obtaining +user credentials. You can use +:func:`google_auth_oauthlib.helpers.credentials_from_session` to obtain :class:`google.oauth2.credentials.Credentials` from a :class:`requests_oauthlib.OAuth2Session` as above:: - import google.oauth2.oauthlib + from google_auth_oauthlib.helpers import credentials_from_session - google_auth_credentials = google.oauth2.oauthlib.credentials_from_session( - oauth2session) + google_auth_credentials = credentials_from_session(oauth2session) -You can also use :class:`google.oauth2.flow.Flow` to perform the OAuth 2.0 -Authorization Grant Flow to obtain credentials using `requests-oauthlib`_. +You can also use :class:`google_auth_oauthlib.flow.Flow` to perform the OAuth +2.0 Authorization Grant Flow to obtain credentials using `requests-oauthlib`_. .. _OAuth 2.0: https://developers.google.com/identity/protocols/OAuth2 .. _oauthlib: https://oauthlib.readthedocs.io/en/latest/ +.. _google-auth-oauthlib: + https://pypi.python.org/pypi/google-auth-oauthlib .. _requests-oauthlib: https://requests-oauthlib.readthedocs.io/en/latest/ From 7aeb88b60be709b8946ef488a199f7f2926e4103 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Thu, 23 Mar 2017 13:14:53 -0700 Subject: [PATCH 118/966] Add service_account.Credentials.with_claims (#140) --- packages/google-auth/google/auth/jwt.py | 7 ++++-- .../google/oauth2/service_account.py | 24 +++++++++++++++++++ .../tests/oauth2/test_service_account.py | 4 ++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/google/auth/jwt.py b/packages/google-auth/google/auth/jwt.py index 7a9bdd59a93c..506ba0ec8501 100644 --- a/packages/google-auth/google/auth/jwt.py +++ b/packages/google-auth/google/auth/jwt.py @@ -42,6 +42,7 @@ import base64 import collections +import copy import datetime import json @@ -426,13 +427,15 @@ def with_claims(self, issuer=None, subject=None, audience=None, Returns: google.auth.jwt.Credentials: A new credentials instance. """ + new_additional_claims = copy.deepcopy(self._additional_claims) + new_additional_claims.update(additional_claims or {}) + return Credentials( self._signer, issuer=issuer if issuer is not None else self._issuer, subject=subject if subject is not None else self._subject, audience=audience if audience is not None else self._audience, - additional_claims=self._additional_claims.copy().update( - additional_claims or {})) + additional_claims=new_additional_claims) def _make_jwt(self): """Make a signed JWT. diff --git a/packages/google-auth/google/oauth2/service_account.py b/packages/google-auth/google/oauth2/service_account.py index 58580e2188a0..f8a27bfa7ba7 100644 --- a/packages/google-auth/google/oauth2/service_account.py +++ b/packages/google-auth/google/oauth2/service_account.py @@ -70,6 +70,7 @@ .. _RFC 7523: https://tools.ietf.org/html/rfc7523 """ +import copy import datetime from google.auth import _helpers @@ -246,6 +247,29 @@ def with_subject(self, subject): subject=subject, additional_claims=self._additional_claims.copy()) + def with_claims(self, additional_claims): + """Returns a copy of these credentials with modified claims. + + Args: + additional_claims (Mapping[str, str]): Any additional claims for + the JWT payload. This will be merged with the current + additional claims. + + Returns: + google.auth.service_account.Credentials: A new credentials + instance. + """ + new_additional_claims = copy.deepcopy(self._additional_claims) + new_additional_claims.update(additional_claims or {}) + + return Credentials( + self._signer, + service_account_email=self._service_account_email, + scopes=self._scopes, + token_uri=self._token_uri, + subject=self._subject, + additional_claims=new_additional_claims) + def _make_authorization_grant_assertion(self): """Create the OAuth 2.0 assertion. diff --git a/packages/google-auth/tests/oauth2/test_service_account.py b/packages/google-auth/tests/oauth2/test_service_account.py index c4c1d1b8a60c..774b977eef51 100644 --- a/packages/google-auth/tests/oauth2/test_service_account.py +++ b/packages/google-auth/tests/oauth2/test_service_account.py @@ -134,6 +134,10 @@ def test_create_scoped(self): credentials = self.credentials.with_scopes(scopes) assert credentials._scopes == scopes + def test_with_claims(self): + new_credentials = self.credentials.with_claims({'meep': 'moop'}) + assert new_credentials._additional_claims == {'meep': 'moop'} + def test__make_authorization_grant_assertion(self): token = self.credentials._make_authorization_grant_assertion() payload = jwt.decode(token, PUBLIC_CERT_BYTES) From 7f0bf7571aed0a0e0c2dbfd0cd2235d1d6b1b5f2 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Thu, 23 Mar 2017 13:34:09 -0700 Subject: [PATCH 119/966] Release v0.9.0 (#143) --- packages/google-auth/CHANGELOG.rst | 12 ++++++++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.rst b/packages/google-auth/CHANGELOG.rst index 5390245a8bef..e8b61c083636 100644 --- a/packages/google-auth/CHANGELOG.rst +++ b/packages/google-auth/CHANGELOG.rst @@ -1,6 +1,18 @@ Changelog ========= +v0.9.0 +------ + +- Added ``service_account.Credentials.with_claims``. (#140) +- Moved ``google.auth.oauthlib`` and ``google.auth.flow`` to a new separate package ``google_auth_oauthlib``. (#137, #139, #135, #126) +- Added ``InstalledAppFlow`` to ``google_auth_oauthlib``. (#128) +- Fixed some packaging and documentation issues. (#131) +- Added a helpful error message when importing optional dependencies. (#125) +- Made all properties required to reconstruct ``google.oauth2.credentials.Credentials`` public. (#124) +- Added official Python 3.6 support. (#102) +- Added ``jwt.Credentials.from_signing_credentials`` and removed ``service_account.Credentials.to_jwt_credentials``. (#120) + v0.8.0 ------ diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 76bceb8bc96e..aaa13de4a52c 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -31,7 +31,7 @@ setup( name='google-auth', - version='0.8.0', + version='0.9.0', author='Google Cloud Platform', author_email='jonwayne+google-auth@google.com', description='Google Authentication Library', From f4c94edd0d0443c9543f1ba9795f7855e5950bf7 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Thu, 23 Mar 2017 14:16:42 -0700 Subject: [PATCH 120/966] Remove unneeded import guard (#144) --- .../google_auth_oauthlib/google_auth_oauthlib/helpers.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/google-auth/additional_packages/google_auth_oauthlib/google_auth_oauthlib/helpers.py b/packages/google-auth/additional_packages/google_auth_oauthlib/google_auth_oauthlib/helpers.py index 901423bde387..41866d678266 100644 --- a/packages/google-auth/additional_packages/google_auth_oauthlib/google_auth_oauthlib/helpers.py +++ b/packages/google-auth/additional_packages/google_auth_oauthlib/google_auth_oauthlib/helpers.py @@ -23,14 +23,8 @@ import json -try: - import requests_oauthlib -except ImportError: # pragma: NO COVER - raise ImportError( - 'The requests-oauthlib library is not installed, please install the ' - 'requests-oauthlib package to use google.oauth2.oauthlib.') - import google.oauth2.credentials +import requests_oauthlib _REQUIRED_CONFIG_KEYS = frozenset(('auth_uri', 'token_uri', 'client_id')) From 64d05c9c57c1dea3559143afc2f15c4cd6d17571 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Thu, 23 Mar 2017 16:00:24 -0700 Subject: [PATCH 121/966] Add a consistent 5 minute clock skew accomodation (#145) --- packages/google-auth/google/auth/_helpers.py | 4 ++++ packages/google-auth/google/auth/credentials.py | 6 ++++-- packages/google-auth/google/auth/jwt.py | 17 ++++++++++------- .../tests/compute_engine/test_credentials.py | 6 ++++-- .../tests/oauth2/test_credentials.py | 3 ++- packages/google-auth/tests/test_app_engine.py | 5 +++-- packages/google-auth/tests/test_credentials.py | 16 +++++++++++++++- 7 files changed, 42 insertions(+), 15 deletions(-) diff --git a/packages/google-auth/google/auth/_helpers.py b/packages/google-auth/google/auth/_helpers.py index de86beeb8455..860b82719a2a 100644 --- a/packages/google-auth/google/auth/_helpers.py +++ b/packages/google-auth/google/auth/_helpers.py @@ -22,6 +22,10 @@ from six.moves import urllib +CLOCK_SKEW_SECS = 300 # 5 minutes in seconds +CLOCK_SKEW = datetime.timedelta(seconds=CLOCK_SKEW_SECS) + + def copy_docstring(source_class): """Decorator that copies a method's docstring from another class. diff --git a/packages/google-auth/google/auth/credentials.py b/packages/google-auth/google/auth/credentials.py index 8570957cc07e..6fb89d8302e1 100644 --- a/packages/google-auth/google/auth/credentials.py +++ b/packages/google-auth/google/auth/credentials.py @@ -56,8 +56,10 @@ def expired(self): Note that credentials can be invalid but not expired becaue Credentials with :attr:`expiry` set to None is considered to never expire. """ - now = _helpers.utcnow() - return self.expiry is not None and self.expiry <= now + # Err on the side of reporting expiration early so that we avoid + # the 403-refresh-retry loop. + adjusted_now = _helpers.utcnow() - _helpers.CLOCK_SKEW + return self.expiry is not None and self.expiry <= adjusted_now @property def valid(self): diff --git a/packages/google-auth/google/auth/jwt.py b/packages/google-auth/google/auth/jwt.py index 506ba0ec8501..412f122e347f 100644 --- a/packages/google-auth/google/auth/jwt.py +++ b/packages/google-auth/google/auth/jwt.py @@ -52,8 +52,7 @@ import google.auth.credentials -_DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in sections -_CLOCK_SKEW_SECS = 300 # 5 minutes in seconds +_DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds def encode(signer, payload, header=None, key_id=None): @@ -161,21 +160,25 @@ def _verify_iat_and_exp(payload): """ now = _helpers.datetime_to_secs(_helpers.utcnow()) - # Make sure the iat and exp claims are present + # Make sure the iat and exp claims are present. for key in ('iat', 'exp'): if key not in payload: raise ValueError( 'Token does not contain required claim {}'.format(key)) - # Make sure the token wasn't issued in the future + # Make sure the token wasn't issued in the future. iat = payload['iat'] - earliest = iat - _CLOCK_SKEW_SECS + # Err on the side of accepting a token that is slightly early to account + # for clock skew. + earliest = iat - _helpers.CLOCK_SKEW_SECS if now < earliest: raise ValueError('Token used too early, {} < {}'.format(now, iat)) - # Make sure the token wasn't issue in the past + # Make sure the token wasn't issued in the past. exp = payload['exp'] - latest = exp + _CLOCK_SKEW_SECS + # Err on the side of accepting a token that is slightly out of date + # to account for clow skew. + latest = exp + _helpers.CLOCK_SKEW_SECS if latest < now: raise ValueError('Token expired, {} < {}'.format(latest, now)) diff --git a/packages/google-auth/tests/compute_engine/test_credentials.py b/packages/google-auth/tests/compute_engine/test_credentials.py index 568aec72e975..2564da961352 100644 --- a/packages/google-auth/tests/compute_engine/test_credentials.py +++ b/packages/google-auth/tests/compute_engine/test_credentials.py @@ -17,6 +17,7 @@ import mock import pytest +from google.auth import _helpers from google.auth import exceptions from google.auth.compute_engine import credentials @@ -38,7 +39,8 @@ def test_default_state(self): assert self.credentials.service_account_email == 'default' @mock.patch( - 'google.auth._helpers.utcnow', return_value=datetime.datetime.min) + 'google.auth._helpers.utcnow', + return_value=datetime.datetime.min + _helpers.CLOCK_SKEW) @mock.patch('google.auth.compute_engine._metadata.get') def test_refresh_success(self, get_mock, now_mock): get_mock.side_effect = [{ @@ -57,7 +59,7 @@ def test_refresh_success(self, get_mock, now_mock): # Check that the credentials have the token and proper expiration assert self.credentials.token == 'token' assert self.credentials.expiry == ( - datetime.datetime.min + datetime.timedelta(seconds=500)) + now_mock() + datetime.timedelta(seconds=500)) # Check the credential info assert (self.credentials.service_account_email == diff --git a/packages/google-auth/tests/oauth2/test_credentials.py b/packages/google-auth/tests/oauth2/test_credentials.py index fa86a16d5b02..b117ad4b78f7 100644 --- a/packages/google-auth/tests/oauth2/test_credentials.py +++ b/packages/google-auth/tests/oauth2/test_credentials.py @@ -53,7 +53,8 @@ def test_create_scoped(self): @mock.patch('google.oauth2._client.refresh_grant', autospec=True) @mock.patch( - 'google.auth._helpers.utcnow', return_value=datetime.datetime.min) + 'google.auth._helpers.utcnow', + return_value=datetime.datetime.min + _helpers.CLOCK_SKEW) def test_refresh_success(self, now_mock, refresh_grant_mock): token = 'token' expiry = _helpers.utcnow() + datetime.timedelta(seconds=500) diff --git a/packages/google-auth/tests/test_app_engine.py b/packages/google-auth/tests/test_app_engine.py index d3a79d5c1481..136a914c9933 100644 --- a/packages/google-auth/tests/test_app_engine.py +++ b/packages/google-auth/tests/test_app_engine.py @@ -17,6 +17,7 @@ import mock import pytest +from google.auth import _helpers from google.auth import app_engine @@ -111,7 +112,7 @@ def test_service_account_email_explicit(self, app_identity_mock): @mock.patch( 'google.auth._helpers.utcnow', - return_value=datetime.datetime.min) + return_value=datetime.datetime.min + _helpers.CLOCK_SKEW) def test_refresh(self, now_mock, app_identity_mock): token = 'token' ttl = 100 @@ -124,7 +125,7 @@ def test_refresh(self, now_mock, app_identity_mock): credentials.scopes, credentials._service_account_id) assert credentials.token == token assert credentials.expiry == ( - datetime.datetime.min + datetime.timedelta(seconds=ttl)) + now_mock() + datetime.timedelta(seconds=ttl)) assert credentials.valid assert not credentials.expired diff --git a/packages/google-auth/tests/test_credentials.py b/packages/google-auth/tests/test_credentials.py index 814369e43903..c2ff844a4441 100644 --- a/packages/google-auth/tests/test_credentials.py +++ b/packages/google-auth/tests/test_credentials.py @@ -14,6 +14,7 @@ import datetime +from google.auth import _helpers from google.auth import credentials @@ -37,8 +38,21 @@ def test_expired_and_valid(): assert credentials.valid assert not credentials.expired + # Set the expiration in the past, but because of clock skew accomodation + # the credentials should still be valid. credentials.expiry = ( - datetime.datetime.utcnow() - datetime.timedelta(seconds=60)) + datetime.datetime.utcnow() - + datetime.timedelta(seconds=1)) + + assert credentials.valid + assert not credentials.expired + + # Set the credentials far enough in the past to exceed the clock skew + # accomodation. They should now be expired. + credentials.expiry = ( + datetime.datetime.utcnow() - + _helpers.CLOCK_SKEW - + datetime.timedelta(seconds=1)) assert not credentials.valid assert credentials.expired From d235b95d3be979cc5a3e7ee2818085e377638909 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Fri, 24 Mar 2017 12:10:44 -0700 Subject: [PATCH 122/966] Use "gcloud config config-helper" to read the project ID from the Google Cloud SDK (#147) --- .../google-auth/google/auth/_cloud_sdk.py | 95 ++++++------------- packages/google-auth/system_tests/nox.py | 13 +++ packages/google-auth/tests/data/cloud_sdk.cfg | 2 - .../tests/data/cloud_sdk_config.json | 19 ++++ packages/google-auth/tests/test__cloud_sdk.py | 71 +++++--------- packages/google-auth/tests/test__default.py | 3 - 6 files changed, 86 insertions(+), 117 deletions(-) delete mode 100644 packages/google-auth/tests/data/cloud_sdk.cfg create mode 100644 packages/google-auth/tests/data/cloud_sdk_config.json diff --git a/packages/google-auth/google/auth/_cloud_sdk.py b/packages/google-auth/google/auth/_cloud_sdk.py index 1e851c87defb..428b612c664d 100644 --- a/packages/google-auth/google/auth/_cloud_sdk.py +++ b/packages/google-auth/google/auth/_cloud_sdk.py @@ -14,11 +14,11 @@ """Helpers for reading the Google Cloud SDK's configuration.""" -import io +import json import os +import subprocess import six -from six.moves import configparser from google.auth import environment_vars import google.oauth2.credentials @@ -33,9 +33,9 @@ # The name of the file in the Cloud SDK config that contains default # credentials. _CREDENTIALS_FILENAME = 'application_default_credentials.json' -# The config section and key for the project ID in the cloud SDK config. -_PROJECT_CONFIG_SECTION = 'core' -_PROJECT_CONFIG_KEY = 'project' +# The command to get the Cloud SDK configuration +_CLOUD_SDK_CONFIG_COMMAND = ( + 'gcloud', 'config', 'config-helper', '--format', 'json') def get_config_path(): @@ -80,66 +80,6 @@ def get_application_default_credentials_path(): return os.path.join(config_path, _CREDENTIALS_FILENAME) -def _get_active_config(config_path): - """Gets the active config for the Cloud SDK. - - Args: - config_path (str): The Cloud SDK's config path. - - Returns: - str: The active configuration name. - """ - active_config_filename = os.path.join(config_path, 'active_config') - - if not os.path.isfile(active_config_filename): - return 'default' - - with io.open(active_config_filename, 'r', encoding='utf-8') as file_obj: - active_config_name = file_obj.read().strip() - - return active_config_name - - -def _get_config_file(config_path, config_name): - """Returns the full path to a configuration's config file. - - Args: - config_path (str): The Cloud SDK's config path. - config_name (str): The configuration name. - - Returns: - str: The config file path. - """ - return os.path.join( - config_path, 'configurations', 'config_{}'.format(config_name)) - - -def get_project_id(): - """Gets the project ID from the Cloud SDK's configuration. - - Returns: - Optional[str]: The project ID. - """ - config_path = get_config_path() - active_config = _get_active_config(config_path) - config_file = _get_config_file(config_path, active_config) - - if not os.path.isfile(config_file): - return None - - config = configparser.RawConfigParser() - - try: - config.read(config_file) - - if config.has_section(_PROJECT_CONFIG_SECTION): - return config.get( - _PROJECT_CONFIG_SECTION, _PROJECT_CONFIG_KEY) - - except configparser.Error: - return None - - def load_authorized_user_credentials(info): """Loads an authorized user credential. @@ -166,3 +106,28 @@ def load_authorized_user_credentials(info): token_uri=_GOOGLE_OAUTH2_TOKEN_ENDPOINT, client_id=info['client_id'], client_secret=info['client_secret']) + + +def get_project_id(): + """Gets the project ID from the Cloud SDK. + + Returns: + Optional[str]: The project ID. + """ + + try: + output = subprocess.check_output( + _CLOUD_SDK_CONFIG_COMMAND, + stderr=subprocess.STDOUT) + except (subprocess.CalledProcessError, OSError, IOError): + return None + + try: + configuration = json.loads(output.decode('utf-8')) + except ValueError: + return None + + try: + return configuration['configuration']['properties']['core']['project'] + except KeyError: + return None diff --git a/packages/google-auth/system_tests/nox.py b/packages/google-auth/system_tests/nox.py index 0d1116d66de3..fa0422ad2c2a 100644 --- a/packages/google-auth/system_tests/nox.py +++ b/packages/google-auth/system_tests/nox.py @@ -85,6 +85,11 @@ def install_cloud_sdk(session): session.env[CLOUD_SDK_CONFIG_ENV] = str(CLOUD_SDK_ROOT) # This tells gcloud which Python interpreter to use (always use 2.7) session.env[CLOUD_SDK_PYTHON_ENV] = CLOUD_SDK_PYTHON + # This set the $PATH for the subprocesses so they can find the gcloud + # executable. + session.env['PATH'] = ( + str(CLOUD_SDK_INSTALL_DIR.join('bin')) + os.pathsep + + os.environ['PATH']) # If gcloud cli executable already exists, just update it. if py.path.local(GCLOUD).exists(): @@ -130,6 +135,14 @@ def configure_cloud_sdk( """ install_cloud_sdk(session) + # Setup the service account as the default user account. This is + # needed for the project ID detection to work. Note that this doesn't + # change the application default credentials file, which is user + # credentials instead of service account credentials sometimes. + session.run( + GCLOUD, 'auth', 'activate-service-account', '--key-file', + SERVICE_ACCOUNT_FILE) + if project: session.run(GCLOUD, 'config', 'set', 'project', 'example-project') else: diff --git a/packages/google-auth/tests/data/cloud_sdk.cfg b/packages/google-auth/tests/data/cloud_sdk.cfg deleted file mode 100644 index 089aac5a579d..000000000000 --- a/packages/google-auth/tests/data/cloud_sdk.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[core] -project = example-project diff --git a/packages/google-auth/tests/data/cloud_sdk_config.json b/packages/google-auth/tests/data/cloud_sdk_config.json new file mode 100644 index 000000000000..a5fe4a9a475e --- /dev/null +++ b/packages/google-auth/tests/data/cloud_sdk_config.json @@ -0,0 +1,19 @@ +{ + "configuration": { + "active_configuration": "default", + "properties": { + "core": { + "account": "user@example.com", + "disable_usage_reporting": "False", + "project": "example-project" + } + } + }, + "credential": { + "access_token": "don't use me", + "token_expiry": "2017-03-23T23:09:49Z" + }, + "sentinels": { + "config_sentinel": "/Users/example/.config/gcloud/config_sentinel" + } +} diff --git a/packages/google-auth/tests/test__cloud_sdk.py b/packages/google-auth/tests/test__cloud_sdk.py index ba72072d43c7..482a7ed51a81 100644 --- a/packages/google-auth/tests/test__cloud_sdk.py +++ b/packages/google-auth/tests/test__cloud_sdk.py @@ -12,11 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. +import io import json import os +import subprocess import mock -import py import pytest from google.auth import _cloud_sdk @@ -27,76 +28,52 @@ DATA_DIR = os.path.join(os.path.dirname(__file__), 'data') AUTHORIZED_USER_FILE = os.path.join(DATA_DIR, 'authorized_user.json') -with open(AUTHORIZED_USER_FILE) as fh: +with io.open(AUTHORIZED_USER_FILE) as fh: AUTHORIZED_USER_FILE_DATA = json.load(fh) SERVICE_ACCOUNT_FILE = os.path.join(DATA_DIR, 'service_account.json') -with open(SERVICE_ACCOUNT_FILE) as fh: +with io.open(SERVICE_ACCOUNT_FILE) as fh: SERVICE_ACCOUNT_FILE_DATA = json.load(fh) -with open(os.path.join(DATA_DIR, 'cloud_sdk.cfg')) as fh: - CLOUD_SDK_CONFIG_DATA = fh.read() +with io.open(os.path.join(DATA_DIR, 'cloud_sdk_config.json'), 'rb') as fh: + CLOUD_SDK_CONFIG_FILE_DATA = fh.read() -CONFIG_PATH_PATCH = mock.patch( - 'google.auth._cloud_sdk.get_config_path', autospec=True) - - -@pytest.fixture -def config_dir(tmpdir): - config_dir = tmpdir.join( - '.config', _cloud_sdk._CONFIG_DIRECTORY) - - with CONFIG_PATH_PATCH as mock_get_config_dir: - mock_get_config_dir.return_value = str(config_dir) - yield config_dir - -@pytest.fixture -def config_file(config_dir): - config_file = py.path.local(_cloud_sdk._get_config_file( - str(config_dir), 'default')) - yield config_file - - -def test_get_project_id(config_file): - config_file.write(CLOUD_SDK_CONFIG_DATA, ensure=True) +@mock.patch( + 'subprocess.check_output', autospec=True, + return_value=CLOUD_SDK_CONFIG_FILE_DATA) +def test_get_project_id(check_output_mock): project_id = _cloud_sdk.get_project_id() assert project_id == 'example-project' -def test_get_project_id_non_existent(config_file): +@mock.patch( + 'subprocess.check_output', autospec=True, + side_effect=subprocess.CalledProcessError(-1, None)) +def test_get_project_id_call_error(check_output_mock): project_id = _cloud_sdk.get_project_id() assert project_id is None -def test_get_project_id_bad_file(config_file): - config_file.write('<< Date: Fri, 24 Mar 2017 15:36:04 -0700 Subject: [PATCH 123/966] Allow customizing the GCE metadata service address via an env var. (#148) The goal here is to make it possible for a user of a binary that depends on this library (eg the google cloud SDK) to be able to customize where it looks for the GCE metadata service. (An adventurous user can already customize the GCE metadata service location via the existing global vars in this library.) --- .../google/auth/compute_engine/_metadata.py | 7 ++- .../google/auth/environment_vars.py | 10 +++++ .../tests/compute_engine/test__metadata.py | 43 +++++++++++++++++++ 3 files changed, 58 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/google/auth/compute_engine/_metadata.py b/packages/google-auth/google/auth/compute_engine/_metadata.py index e5abf2097248..b35775a23c74 100644 --- a/packages/google-auth/google/auth/compute_engine/_metadata.py +++ b/packages/google-auth/google/auth/compute_engine/_metadata.py @@ -26,15 +26,18 @@ from six.moves.urllib import parse as urlparse from google.auth import _helpers +from google.auth import environment_vars from google.auth import exceptions _LOGGER = logging.getLogger(__name__) -_METADATA_ROOT = 'http://metadata.google.internal/computeMetadata/v1/' +_METADATA_ROOT = 'http://{}/computeMetadata/v1/'.format( + os.getenv(environment_vars.GCE_METADATA_ROOT, 'metadata.google.internal')) # This is used to ping the metadata server, it avoids the cost of a DNS # lookup. -_METADATA_IP_ROOT = 'http://169.254.169.254' +_METADATA_IP_ROOT = 'http://{}'.format( + os.getenv(environment_vars.GCE_METADATA_IP, '169.254.169.254')) _METADATA_FLAVOR_HEADER = 'metadata-flavor' _METADATA_FLAVOR_VALUE = 'Google' _METADATA_HEADERS = {_METADATA_FLAVOR_HEADER: _METADATA_FLAVOR_VALUE} diff --git a/packages/google-auth/google/auth/environment_vars.py b/packages/google-auth/google/auth/environment_vars.py index b4ed2b28ae17..0110e6a3c5bc 100644 --- a/packages/google-auth/google/auth/environment_vars.py +++ b/packages/google-auth/google/auth/environment_vars.py @@ -37,3 +37,13 @@ CLOUD_SDK_CONFIG_DIR = 'CLOUDSDK_CONFIG' """Environment variable defines the location of Google Cloud SDK's config files.""" + +# These two variables allow for customization of the addresses used when +# contacting the GCE metadata service. +GCE_METADATA_ROOT = 'GCE_METADATA_ROOT' +"""Environment variable providing an alternate hostname or host:port to be +used for GCE metadata requests.""" + +GCE_METADATA_IP = 'GCE_METADATA_IP' +"""Environment variable providing an alternate ip:port to be used for ip-only +GCE metadata requests.""" diff --git a/packages/google-auth/tests/compute_engine/test__metadata.py b/packages/google-auth/tests/compute_engine/test__metadata.py index 6a45c426df07..0adf882525bf 100644 --- a/packages/google-auth/tests/compute_engine/test__metadata.py +++ b/packages/google-auth/tests/compute_engine/test__metadata.py @@ -14,12 +14,15 @@ import datetime import json +import os import mock import pytest from six.moves import http_client +from six.moves import reload_module from google.auth import _helpers +from google.auth import environment_vars from google.auth import exceptions from google.auth.compute_engine import _metadata @@ -67,6 +70,26 @@ def test_ping_failure_connection_failed(mock_request): assert not _metadata.ping(request_mock) +def test_ping_success_custom_root(mock_request): + request_mock = mock_request('', headers=_metadata._METADATA_HEADERS) + + fake_ip = '1.2.3.4' + os.environ[environment_vars.GCE_METADATA_IP] = fake_ip + reload_module(_metadata) + + try: + assert _metadata.ping(request_mock) + finally: + del os.environ[environment_vars.GCE_METADATA_IP] + reload_module(_metadata) + + request_mock.assert_called_once_with( + method='GET', + url='http://' + fake_ip, + headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT) + + def test_get_success_json(mock_request): key, value = 'foo', 'bar' @@ -96,6 +119,26 @@ def test_get_success_text(mock_request): assert result == data +def test_get_success_custom_root(mock_request): + request_mock = mock_request( + '{}', headers={'content-type': 'application/json'}) + + fake_root = 'another.metadata.service' + os.environ[environment_vars.GCE_METADATA_ROOT] = fake_root + reload_module(_metadata) + + try: + _metadata.get(request_mock, PATH) + finally: + del os.environ[environment_vars.GCE_METADATA_ROOT] + reload_module(_metadata) + + request_mock.assert_called_once_with( + method='GET', + url='http://{}/computeMetadata/v1/{}'.format(fake_root, PATH), + headers=_metadata._METADATA_HEADERS) + + def test_get_failure(mock_request): request_mock = mock_request( 'Metadata error', status=http_client.NOT_FOUND) From 8d3375907416b70e54f3e2d21453eabac4dafada Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Mon, 27 Mar 2017 09:36:42 -0700 Subject: [PATCH 124/966] Fix system tests when running on GCE The new project ID logic for Cloud SDK invokes Cloud SDK directly. Cloud SDK helpfully falls back to the GCE project ID if the project ID is unset in the configuration. This breaks one of our previous expectations. --- packages/google-auth/system_tests/test_default.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/google-auth/system_tests/test_default.py b/packages/google-auth/system_tests/test_default.py index c64a8266ed61..23f65435113c 100644 --- a/packages/google-auth/system_tests/test_default.py +++ b/packages/google-auth/system_tests/test_default.py @@ -24,7 +24,5 @@ def test_application_default_credentials(verify_refresh): if EXPECT_PROJECT_ID is not None: assert project_id is not None - else: - assert project_id is None verify_refresh(credentials) From f9cf93b664b5d746fca8ac63a648148d1cde119b Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Tue, 28 Mar 2017 13:03:11 -0700 Subject: [PATCH 125/966] Add jwt.OnDemandCredentials (#142) --- packages/google-auth/google/auth/jwt.py | 280 +++++++++++++++++- packages/google-auth/setup.py | 1 + .../google-auth/system_tests/test_grpc.py | 24 +- packages/google-auth/tests/test_jwt.py | 135 ++++++++- 4 files changed, 430 insertions(+), 10 deletions(-) diff --git a/packages/google-auth/google/auth/jwt.py b/packages/google-auth/google/auth/jwt.py index 412f122e347f..b1eb5fb91e95 100644 --- a/packages/google-auth/google/auth/jwt.py +++ b/packages/google-auth/google/auth/jwt.py @@ -46,13 +46,17 @@ import datetime import json +import cachetools +from six.moves import urllib + from google.auth import _helpers from google.auth import _service_account_info from google.auth import crypt +from google.auth import exceptions import google.auth.credentials - _DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds +_DEFAULT_MAX_CACHE_SIZE = 10 def encode(signer, payload, header=None, key_id=None): @@ -316,10 +320,10 @@ def __init__(self, signer, issuer, subject, audience, self._audience = audience self._token_lifetime = token_lifetime - if additional_claims is not None: - self._additional_claims = additional_claims - else: - self._additional_claims = {} + if additional_claims is None: + additional_claims = {} + + self._additional_claims = additional_claims @classmethod def _from_signer_and_info(cls, signer, info, **kwargs): @@ -343,8 +347,7 @@ def _from_signer_and_info(cls, signer, info, **kwargs): @classmethod def from_service_account_info(cls, info, **kwargs): - """Creates a Credentials instance from a dictionary containing service - account info in Google format. + """Creates an Credentials instance from a dictionary. Args: info (Mapping[str, str]): The service account info in Google @@ -487,3 +490,266 @@ def signer_email(self): @_helpers.copy_docstring(google.auth.credentials.Signing) def signer(self): return self._signer + + +class OnDemandCredentials( + google.auth.credentials.Signing, + google.auth.credentials.Credentials): + """On-demand JWT credentials. + + Like :class:`Credentials`, this class uses a JWT as the bearer token for + authentication. However, this class does not require the audience at + construction time. Instead, it will generate a new token on-demand for + each request using the request URI as the audience. It caches tokens + so that multiple requests to the same URI do not incur the overhead + of generating a new token every time. + + This behavior is especially useful for `gRPC`_ clients. A gRPC service may + have multiple audience and gRPC clients may not know all of the audiences + required for accessing a particular service. With these credentials, + no knowledge of the audiences is required ahead of time. + + .. _grpc: http://www.grpc.io/ + """ + + def __init__(self, signer, issuer, subject, + additional_claims=None, + token_lifetime=_DEFAULT_TOKEN_LIFETIME_SECS, + max_cache_size=_DEFAULT_MAX_CACHE_SIZE): + """ + Args: + signer (google.auth.crypt.Signer): The signer used to sign JWTs. + issuer (str): The `iss` claim. + subject (str): The `sub` claim. + additional_claims (Mapping[str, str]): Any additional claims for + the JWT payload. + token_lifetime (int): The amount of time in seconds for + which the token is valid. Defaults to 1 hour. + max_cache_size (int): The maximum number of JWT tokens to keep in + cache. Tokens are cached using :class:`cachetools.LRUCache`. + """ + super(OnDemandCredentials, self).__init__() + self._signer = signer + self._issuer = issuer + self._subject = subject + self._token_lifetime = token_lifetime + + if additional_claims is None: + additional_claims = {} + + self._additional_claims = additional_claims + self._cache = cachetools.LRUCache(maxsize=max_cache_size) + + @classmethod + def _from_signer_and_info(cls, signer, info, **kwargs): + """Creates an OnDemandCredentials instance from a signer and service + account info. + + Args: + signer (google.auth.crypt.Signer): The signer used to sign JWTs. + info (Mapping[str, str]): The service account info. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.jwt.OnDemandCredentials: The constructed credentials. + + Raises: + ValueError: If the info is not in the expected format. + """ + kwargs.setdefault('subject', info['client_email']) + kwargs.setdefault('issuer', info['client_email']) + return cls(signer, **kwargs) + + @classmethod + def from_service_account_info(cls, info, **kwargs): + """Creates an OnDemandCredentials instance from a dictionary. + + Args: + info (Mapping[str, str]): The service account info in Google + format. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.jwt.OnDemandCredentials: The constructed credentials. + + Raises: + ValueError: If the info is not in the expected format. + """ + signer = _service_account_info.from_dict( + info, require=['client_email']) + return cls._from_signer_and_info(signer, info, **kwargs) + + @classmethod + def from_service_account_file(cls, filename, **kwargs): + """Creates an OnDemandCredentials instance from a service account .json + file in Google format. + + Args: + filename (str): The path to the service account .json file. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.jwt.OnDemandCredentials: The constructed credentials. + """ + info, signer = _service_account_info.from_filename( + filename, require=['client_email']) + return cls._from_signer_and_info(signer, info, **kwargs) + + @classmethod + def from_signing_credentials(cls, credentials, **kwargs): + """Creates a new :class:`google.auth.jwt.OnDemandCredentials` instance + from an existing :class:`google.auth.credentials.Signing` instance. + + The new instance will use the same signer as the existing instance and + will use the existing instance's signer email as the issuer and + subject by default. + + Example:: + + svc_creds = service_account.Credentials.from_service_account_file( + 'service_account.json') + jwt_creds = jwt.OnDemandCredentials.from_signing_credentials( + svc_creds) + + Args: + credentials (google.auth.credentials.Signing): The credentials to + use to construct the new credentials. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.jwt.Credentials: A new Credentials instance. + """ + kwargs.setdefault('issuer', credentials.signer_email) + kwargs.setdefault('subject', credentials.signer_email) + return cls(credentials.signer, **kwargs) + + def with_claims(self, issuer=None, subject=None, additional_claims=None): + """Returns a copy of these credentials with modified claims. + + Args: + issuer (str): The `iss` claim. If unspecified the current issuer + claim will be used. + subject (str): The `sub` claim. If unspecified the current subject + claim will be used. + additional_claims (Mapping[str, str]): Any additional claims for + the JWT payload. This will be merged with the current + additional claims. + + Returns: + google.auth.jwt.OnDemandCredentials: A new credentials instance. + """ + new_additional_claims = copy.deepcopy(self._additional_claims) + new_additional_claims.update(additional_claims or {}) + + return OnDemandCredentials( + self._signer, + issuer=issuer if issuer is not None else self._issuer, + subject=subject if subject is not None else self._subject, + additional_claims=new_additional_claims, + max_cache_size=self._cache.maxsize) + + @property + def valid(self): + """Checks the validity of the credentials. + + These credentials are always valid because it generates tokens on + demand. + """ + return True + + def _make_jwt_for_audience(self, audience): + """Make a new JWT for the given audience. + + Args: + audience (str): The intended audience. + + Returns: + Tuple[bytes, datetime]: The encoded JWT and the expiration. + """ + now = _helpers.utcnow() + lifetime = datetime.timedelta(seconds=self._token_lifetime) + expiry = now + lifetime + + payload = { + 'iss': self._issuer, + 'sub': self._subject, + 'iat': _helpers.datetime_to_secs(now), + 'exp': _helpers.datetime_to_secs(expiry), + 'aud': audience, + } + + payload.update(self._additional_claims) + + jwt = encode(self._signer, payload) + + return jwt, expiry + + def _get_jwt_for_audience(self, audience): + """Get a JWT For a given audience. + + If there is already an existing, non-expired token in the cache for + the audience, that token is used. Otherwise, a new token will be + created. + + Args: + audience (str): The intended audience. + + Returns: + bytes: The encoded JWT. + """ + token, expiry = self._cache.get(audience, (None, None)) + + if token is None or expiry < _helpers.utcnow(): + token, expiry = self._make_jwt_for_audience(audience) + self._cache[audience] = token, expiry + + return token + + def refresh(self, request): + """Raises an exception, these credentials can not be directly + refreshed. + + Args: + request (Any): Unused. + + Raises: + google.auth.RefreshError + """ + # pylint: disable=unused-argument + # (pylint doesn't correctly recognize overridden methods.) + raise exceptions.RefreshError( + 'OnDemandCredentials can not be directly refreshed.') + + def before_request(self, request, method, url, headers): + """Performs credential-specific before request logic. + + Args: + request (Any): Unused. JWT credentials do not need to make an + HTTP request to refresh. + method (str): The request's HTTP method. + url (str): The request's URI. This is used as the audience claim + when generating the JWT. + headers (Mapping): The request's headers. + """ + # pylint: disable=unused-argument + # (pylint doesn't correctly recognize overridden methods.) + parts = urllib.parse.urlsplit(url) + # Strip query string and fragment + audience = urllib.parse.urlunsplit( + (parts.scheme, parts.netloc, parts.path, None, None)) + token = self._get_jwt_for_audience(audience) + self.apply(headers, token=token) + + @_helpers.copy_docstring(google.auth.credentials.Signing) + def sign_bytes(self, message): + return self._signer.sign(message) + + @property + @_helpers.copy_docstring(google.auth.credentials.Signing) + def signer_email(self): + return self._issuer + + @property + @_helpers.copy_docstring(google.auth.credentials.Signing) + def signer(self): + return self._signer diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index aaa13de4a52c..bad634a6a5b4 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -23,6 +23,7 @@ 'pyasn1-modules>=0.0.5', 'rsa>=3.1.4', 'six>=1.9.0', + 'cachetools>=2.0.0', ) diff --git a/packages/google-auth/system_tests/test_grpc.py b/packages/google-auth/system_tests/test_grpc.py index 4bf1c5ba5917..365bc91d3481 100644 --- a/packages/google-auth/system_tests/test_grpc.py +++ b/packages/google-auth/system_tests/test_grpc.py @@ -39,7 +39,7 @@ def test_grpc_request_with_regular_credentials(http_request): list(list_topics_iter) -def test_grpc_request_with_jwt_credentials(http_request): +def test_grpc_request_with_jwt_credentials(): credentials, project_id = google.auth.default() audience = 'https://{}/google.pubsub.v1.Publisher'.format( publisher_client.PublisherClient.SERVICE_ADDRESS) @@ -49,7 +49,27 @@ def test_grpc_request_with_jwt_credentials(http_request): channel = google.auth.transport.grpc.secure_authorized_channel( credentials, - http_request, + None, + publisher_client.PublisherClient.SERVICE_ADDRESS) + + # Create a pub/sub client. + client = publisher_client.PublisherClient(channel=channel) + + # list the topics and drain the iterator to test that an authorized API + # call works. + list_topics_iter = client.list_topics( + project='projects/{}'.format(project_id)) + list(list_topics_iter) + + +def test_grpc_request_with_on_demand_jwt_credentials(): + credentials, project_id = google.auth.default() + credentials = google.auth.jwt.OnDemandCredentials.from_signing_credentials( + credentials) + + channel = google.auth.transport.grpc.secure_authorized_channel( + credentials, + None, publisher_client.PublisherClient.SERVICE_ADDRESS) # Create a pub/sub client. diff --git a/packages/google-auth/tests/test_jwt.py b/packages/google-auth/tests/test_jwt.py index 59769de2ed84..22c5bc538649 100644 --- a/packages/google-auth/tests/test_jwt.py +++ b/packages/google-auth/tests/test_jwt.py @@ -22,6 +22,7 @@ from google.auth import _helpers from google.auth import crypt +from google.auth import exceptions from google.auth import jwt @@ -196,7 +197,7 @@ def test_roundtrip_explicit_key_id(token_factory): assert payload['user'] == 'billy bob' -class TestCredentials: +class TestCredentials(object): SERVICE_ACCOUNT_EMAIL = 'service-account@example.com' SUBJECT = 'subject' AUDIENCE = 'audience' @@ -343,3 +344,135 @@ def test_before_request_refreshes(self): self.credentials.before_request( None, 'GET', 'http://example.com?a=1#3', {}) assert self.credentials.valid + + +class TestOnDemandCredentials(object): + SERVICE_ACCOUNT_EMAIL = 'service-account@example.com' + SUBJECT = 'subject' + ADDITIONAL_CLAIMS = {'meta': 'data'} + credentials = None + + @pytest.fixture(autouse=True) + def credentials_fixture(self, signer): + self.credentials = jwt.OnDemandCredentials( + signer, self.SERVICE_ACCOUNT_EMAIL, self.SERVICE_ACCOUNT_EMAIL, + max_cache_size=2) + + def test_from_service_account_info(self): + with open(SERVICE_ACCOUNT_JSON_FILE, 'r') as fh: + info = json.load(fh) + + credentials = jwt.OnDemandCredentials.from_service_account_info(info) + + assert credentials._signer.key_id == info['private_key_id'] + assert credentials._issuer == info['client_email'] + assert credentials._subject == info['client_email'] + + def test_from_service_account_info_args(self): + info = SERVICE_ACCOUNT_INFO.copy() + + credentials = jwt.OnDemandCredentials.from_service_account_info( + info, subject=self.SUBJECT, + additional_claims=self.ADDITIONAL_CLAIMS) + + assert credentials._signer.key_id == info['private_key_id'] + assert credentials._issuer == info['client_email'] + assert credentials._subject == self.SUBJECT + assert credentials._additional_claims == self.ADDITIONAL_CLAIMS + + def test_from_service_account_file(self): + info = SERVICE_ACCOUNT_INFO.copy() + + credentials = jwt.OnDemandCredentials.from_service_account_file( + SERVICE_ACCOUNT_JSON_FILE) + + assert credentials._signer.key_id == info['private_key_id'] + assert credentials._issuer == info['client_email'] + assert credentials._subject == info['client_email'] + + def test_from_service_account_file_args(self): + info = SERVICE_ACCOUNT_INFO.copy() + + credentials = jwt.OnDemandCredentials.from_service_account_file( + SERVICE_ACCOUNT_JSON_FILE, subject=self.SUBJECT, + additional_claims=self.ADDITIONAL_CLAIMS) + + assert credentials._signer.key_id == info['private_key_id'] + assert credentials._issuer == info['client_email'] + assert credentials._subject == self.SUBJECT + assert credentials._additional_claims == self.ADDITIONAL_CLAIMS + + def test_from_signing_credentials(self): + jwt_from_signing = self.credentials.from_signing_credentials( + self.credentials) + jwt_from_info = jwt.OnDemandCredentials.from_service_account_info( + SERVICE_ACCOUNT_INFO) + + assert isinstance(jwt_from_signing, jwt.OnDemandCredentials) + assert jwt_from_signing._signer.key_id == jwt_from_info._signer.key_id + assert jwt_from_signing._issuer == jwt_from_info._issuer + assert jwt_from_signing._subject == jwt_from_info._subject + + def test_default_state(self): + # Credentials are *always* valid. + assert self.credentials.valid + # Credentials *never* expire. + assert not self.credentials.expired + + def test_with_claims(self): + new_claims = {'meep': 'moop'} + new_credentials = self.credentials.with_claims( + additional_claims=new_claims) + + assert new_credentials._signer == self.credentials._signer + assert new_credentials._issuer == self.credentials._issuer + assert new_credentials._subject == self.credentials._subject + assert new_credentials._additional_claims == new_claims + + def test_sign_bytes(self): + to_sign = b'123' + signature = self.credentials.sign_bytes(to_sign) + assert crypt.verify_signature(to_sign, signature, PUBLIC_CERT_BYTES) + + def test_signer(self): + assert isinstance(self.credentials.signer, crypt.RSASigner) + + def test_signer_email(self): + assert (self.credentials.signer_email == + SERVICE_ACCOUNT_INFO['client_email']) + + def _verify_token(self, token): + payload = jwt.decode(token, PUBLIC_CERT_BYTES) + assert payload['iss'] == self.SERVICE_ACCOUNT_EMAIL + return payload + + def test_refresh(self): + with pytest.raises(exceptions.RefreshError): + self.credentials.refresh(None) + + def test_before_request(self): + headers = {} + + self.credentials.before_request( + None, 'GET', 'http://example.com?a=1#3', headers) + + _, token = headers['authorization'].split(' ') + payload = self._verify_token(token) + + assert payload['aud'] == 'http://example.com' + + # Making another request should re-use the same token. + self.credentials.before_request( + None, 'GET', 'http://example.com?b=2', headers) + + _, new_token = headers['authorization'].split(' ') + + assert new_token == token + + def test_expired_token(self): + self.credentials._cache['audience'] = ( + mock.sentinel.token, datetime.datetime.min) + + token = self.credentials._get_jwt_for_audience('audience') + + assert token != mock.sentinel.token From d613312b300117a1964fbe45039446f8618513a7 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Tue, 28 Mar 2017 13:03:33 -0700 Subject: [PATCH 126/966] Expose id_token in OAuth 2.0 credentials (#150) --- .../google-auth/google/oauth2/credentials.py | 26 +++++++++++++++---- .../tests/oauth2/test_credentials.py | 4 ++- .../tests/oauth2/test_service_account.py | 4 ++- 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/packages/google-auth/google/oauth2/credentials.py b/packages/google-auth/google/oauth2/credentials.py index 077a95f73fb6..6a635ddad99b 100644 --- a/packages/google-auth/google/oauth2/credentials.py +++ b/packages/google-auth/google/oauth2/credentials.py @@ -39,14 +39,16 @@ class Credentials(credentials.Scoped, credentials.Credentials): """Credentials using OAuth 2.0 access and refresh tokens.""" - def __init__(self, token, refresh_token=None, token_uri=None, - client_id=None, client_secret=None, scopes=None): + def __init__(self, token, refresh_token=None, id_token=None, + token_uri=None, client_id=None, client_secret=None, + scopes=None): """ Args: token (Optional(str)): The OAuth 2.0 access token. Can be None if refresh information is provided. refresh_token (str): The OAuth 2.0 refresh token. If specified, credentials can be refreshed. + id_token (str): The Open ID Connect ID Token. token_uri (str): The OAuth 2.0 authorization server's token endpoint URI. Must be specified for refresh, can be left as None if the token can not be refreshed. @@ -63,6 +65,7 @@ def __init__(self, token, refresh_token=None, token_uri=None, super(Credentials, self).__init__() self.token = token self._refresh_token = refresh_token + self._id_token = id_token self._scopes = scopes self._token_uri = token_uri self._client_id = client_id @@ -79,6 +82,17 @@ def token_uri(self): URI.""" return self._token_uri + @property + def id_token(self): + """Optional[str]: The Open ID Connect ID Token. + + Depending on the authorization server and the scopes requested, this + may be populated when credentials are obtained and updated when + :meth:`refresh` is called. This token is a JWT. It can be verified + and decoded using :func:`google.oauth2.id_token.verify_oauth2_token`. + """ + return self._id_token + @property def client_id(self): """Optional[str]: The OAuth 2.0 client ID.""" @@ -106,10 +120,12 @@ def with_scopes(self, scopes): @_helpers.copy_docstring(credentials.Credentials) def refresh(self, request): - access_token, refresh_token, expiry, _ = _client.refresh_grant( - request, self._token_uri, self._refresh_token, self._client_id, - self._client_secret) + access_token, refresh_token, expiry, grant_response = ( + _client.refresh_grant( + request, self._token_uri, self._refresh_token, self._client_id, + self._client_secret)) self.token = access_token self.expiry = expiry self._refresh_token = refresh_token + self._id_token = grant_response.get('id_token') diff --git a/packages/google-auth/tests/oauth2/test_credentials.py b/packages/google-auth/tests/oauth2/test_credentials.py index b117ad4b78f7..e15766a89e9b 100644 --- a/packages/google-auth/tests/oauth2/test_credentials.py +++ b/packages/google-auth/tests/oauth2/test_credentials.py @@ -58,6 +58,7 @@ def test_create_scoped(self): def test_refresh_success(self, now_mock, refresh_grant_mock): token = 'token' expiry = _helpers.utcnow() + datetime.timedelta(seconds=500) + grant_response = {'id_token': mock.sentinel.id_token} refresh_grant_mock.return_value = ( # Access token token, @@ -66,7 +67,7 @@ def test_refresh_success(self, now_mock, refresh_grant_mock): # Expiry, expiry, # Extra data - {}) + grant_response) request_mock = mock.Mock() # Refresh credentials @@ -80,6 +81,7 @@ def test_refresh_success(self, now_mock, refresh_grant_mock): # Check that the credentials have the token and expiry assert self.credentials.token == token assert self.credentials.expiry == expiry + assert self.credentials.id_token == mock.sentinel.id_token # Check that the credentials are valid (have a token and are not # expired) diff --git a/packages/google-auth/tests/oauth2/test_service_account.py b/packages/google-auth/tests/oauth2/test_service_account.py index 774b977eef51..e80b7d497678 100644 --- a/packages/google-auth/tests/oauth2/test_service_account.py +++ b/packages/google-auth/tests/oauth2/test_service_account.py @@ -162,7 +162,9 @@ def test__make_authorization_grant_assertion_subject(self): def test_refresh_success(self, jwt_grant_mock): token = 'token' jwt_grant_mock.return_value = ( - token, _helpers.utcnow() + datetime.timedelta(seconds=500), None) + token, + _helpers.utcnow() + datetime.timedelta(seconds=500), + {}) request_mock = mock.Mock() # Refresh credentials From 0ce02f3b788b2c86faf2c83e6f8da5be504bff01 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Wed, 29 Mar 2017 13:24:57 -0700 Subject: [PATCH 127/966] Release v0.10.0 (#151) --- packages/google-auth/CHANGELOG.rst | 9 +++++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.rst b/packages/google-auth/CHANGELOG.rst index e8b61c083636..43e282eede51 100644 --- a/packages/google-auth/CHANGELOG.rst +++ b/packages/google-auth/CHANGELOG.rst @@ -1,6 +1,15 @@ Changelog ========= +v0.10.0 +------- + +- Added ``jwt.OnDemandCredentials``. (#142) +- Added new public property ``id_token`` to ``oauth2.credentials.Credentials``. (#150) +- Added the ability to set the address used to communicate with the Compute Engine metadata server via the ``GCE_METADATA_ROOT`` and ``GCE_METADATA_IP`` environment variables. (#148) +- Changed the way cloud project IDs are ascertained from the Google Cloud SDK. (#147) +- Modified expiration logic to add a 5 minute clock skew accommodation. (#145) + v0.9.0 ------ diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index bad634a6a5b4..9ec7bfed4135 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -32,7 +32,7 @@ setup( name='google-auth', - version='0.9.0', + version='0.10.0', author='Google Cloud Platform', author_email='jonwayne+google-auth@google.com', description='Google Authentication Library', From dd53a7b9a4739ec5156faed65db71b5a9a8256a0 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Wed, 12 Apr 2017 15:33:15 -0700 Subject: [PATCH 128/966] Move run_pylint to gcp-devrel-py-tools (#153) --- packages/google-auth/.gitignore | 4 + packages/google-auth/pylint.config.py | 71 +++++ packages/google-auth/scripts/.gitignore | 3 - packages/google-auth/scripts/run_pylint.py | 260 ------------------ packages/google-auth/scripts/travis.sh | 14 - .../tests/transport/test_requests.py | 6 + packages/google-auth/tox.ini | 4 +- 7 files changed, 84 insertions(+), 278 deletions(-) create mode 100644 packages/google-auth/pylint.config.py delete mode 100644 packages/google-auth/scripts/.gitignore delete mode 100644 packages/google-auth/scripts/run_pylint.py diff --git a/packages/google-auth/.gitignore b/packages/google-auth/.gitignore index 2c7b7226d5aa..1f65cf324447 100644 --- a/packages/google-auth/.gitignore +++ b/packages/google-auth/.gitignore @@ -30,3 +30,7 @@ tests/data/user-key.json # PyCharm configuration: .idea + +# Generated files +pylintrc +pylintrc.test diff --git a/packages/google-auth/pylint.config.py b/packages/google-auth/pylint.config.py new file mode 100644 index 000000000000..d63f3376e591 --- /dev/null +++ b/packages/google-auth/pylint.config.py @@ -0,0 +1,71 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""This module is used to config gcp-devrel-py-tools run-pylint.""" + +import copy + +library_additions = { + 'MESSAGES CONTROL': { + 'disable': [ + 'I', + 'import-error', + 'no-member', + 'protected-access', + 'redefined-variable-type', + 'similarities', + 'no-else-return', + ], + }, +} + +library_replacements = { + 'MASTER': { + 'ignore': ['CVS', '.git', '.cache', '.tox', '.nox'], + 'load-plugins': 'pylint.extensions.check_docs', + }, + 'REPORTS': { + 'reports': 'no', + }, + 'BASIC': { + 'method-rgx': '[a-z_][a-z0-9_]{2,40}$', + 'function-rgx': '[a-z_][a-z0-9_]{2,40}$', + }, + 'TYPECHECK': { + 'ignored-modules': ['six', 'google.protobuf'], + }, + 'DESIGN': { + 'min-public-methods': '0', + 'max-args': '10', + 'max-attributes': '15', + }, +} + +test_additions = copy.deepcopy(library_additions) +test_additions['MESSAGES CONTROL']['disable'].extend([ + 'missing-docstring', + 'no-self-use', + 'redefined-outer-name', + 'unused-argument', + 'no-name-in-module', +]) +test_replacements = copy.deepcopy(library_replacements) +test_replacements.setdefault('BASIC', {}) +test_replacements['BASIC'].update({ + 'good-names': ['i', 'j', 'k', 'ex', 'Run', '_', 'fh', 'pytestmark'], + 'method-rgx': '[a-z_][a-z0-9_]{2,80}$', + 'function-rgx': '[a-z_][a-z0-9_]{2,80}$', +}) + +ignored_files = () diff --git a/packages/google-auth/scripts/.gitignore b/packages/google-auth/scripts/.gitignore deleted file mode 100644 index 3596d32ac39c..000000000000 --- a/packages/google-auth/scripts/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Generated files -pylintrc -pylintrc.test diff --git a/packages/google-auth/scripts/run_pylint.py b/packages/google-auth/scripts/run_pylint.py deleted file mode 100644 index c33a53e1e96b..000000000000 --- a/packages/google-auth/scripts/run_pylint.py +++ /dev/null @@ -1,260 +0,0 @@ -# Copyright 2016 Google Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""This script runs Pylint on the specified source. - -Before running Pylint, it generates a Pylint configuration on -the fly based on programmatic defaults. -""" - -from __future__ import print_function - -import argparse -import collections -import copy -import io -import os -import subprocess -import sys - -import six - - -_SCRIPTS_DIR = os.path.abspath(os.path.dirname(__file__)) -PRODUCTION_RC = os.path.join(_SCRIPTS_DIR, 'pylintrc') -TEST_RC = os.path.join(_SCRIPTS_DIR, 'pylintrc.test') - -_PRODUCTION_RC_ADDITIONS = { - 'MESSAGES CONTROL': { - 'disable': [ - 'I', - 'import-error', - 'no-member', - 'protected-access', - 'redefined-variable-type', - 'similarities', - 'no-else-return', - ], - }, -} -_PRODUCTION_RC_REPLACEMENTS = { - 'MASTER': { - 'ignore': ['CVS', '.git', '.cache', '.tox', '.nox'], - 'load-plugins': 'pylint.extensions.check_docs', - }, - 'REPORTS': { - 'reports': 'no', - }, - 'BASIC': { - 'method-rgx': '[a-z_][a-z0-9_]{2,40}$', - 'function-rgx': '[a-z_][a-z0-9_]{2,40}$', - }, - 'TYPECHECK': { - 'ignored-modules': ['six', 'google.protobuf'], - }, - 'DESIGN': { - 'min-public-methods': '0', - 'max-args': '10', - 'max-attributes': '15', - }, -} -_TEST_RC_ADDITIONS = copy.deepcopy(_PRODUCTION_RC_ADDITIONS) -_TEST_RC_ADDITIONS['MESSAGES CONTROL']['disable'].extend([ - 'missing-docstring', - 'no-self-use', - 'redefined-outer-name', - 'unused-argument', - 'no-name-in-module', -]) -_TEST_RC_REPLACEMENTS = copy.deepcopy(_PRODUCTION_RC_REPLACEMENTS) -_TEST_RC_REPLACEMENTS.setdefault('BASIC', {}) -_TEST_RC_REPLACEMENTS['BASIC'].update({ - 'good-names': ['i', 'j', 'k', 'ex', 'Run', '_', 'fh', 'pytestmark'], - 'method-rgx': '[a-z_][a-z0-9_]{2,80}$', - 'function-rgx': '[a-z_][a-z0-9_]{2,80}$', -}) -IGNORED_FILES = () - -_ERROR_TEMPLATE = 'Pylint failed on {} with status {:d}.' -_LINT_FILESET_MSG = ( - 'Keyword arguments rc_filename and description are both ' - 'required. No other keyword arguments are allowed.') - - -def get_default_config(): - """Get the default Pylint configuration. - - .. note:: - - The output of this function varies based on the current version of - Pylint installed. - - Returns: - str: The default Pylint configuration. - """ - # Swallow STDERR if it says - # "No config file found, using default configuration" - result = subprocess.check_output(['pylint', '--generate-rcfile'], - stderr=subprocess.PIPE) - # On Python 3, this returns bytes (from STDOUT), so we - # convert to a string. - return result.decode('utf-8') - - -def read_config(contents): - """Reads pylintrc config into native ConfigParser object. - - Args: - contents (str): The contents of the file containing the INI config. - - Returns - ConfigParser.ConfigParser: The parsed configuration. - """ - file_obj = io.StringIO(contents) - config = six.moves.configparser.ConfigParser() - config.readfp(file_obj) - return config - - -def _transform_opt(opt_val): - """Transform a config option value to a string. - - If already a string, do nothing. If an iterable, then - combine into a string by joining on ",". - - Args: - opt_val (Union[str, list]): A config option's value. - - Returns: - str: The option value converted to a string. - """ - if isinstance(opt_val, (list, tuple)): - return ','.join(opt_val) - else: - return opt_val - - -def lint_fileset(*dirnames, **kwargs): - """Lints a group of files using a given rcfile. - - Keyword arguments are - - * ``rc_filename`` (``str``): The name of the Pylint config RC file. - * ``description`` (``str``): A description of the files and configuration - currently being run. - - Args: - dirnames (tuple): Directories to run Pylint in. - kwargs: The keyword arguments. The only keyword arguments - are ``rc_filename`` and ``description`` and both - are required. - - Raises: - KeyError: If the wrong keyword arguments are used. - """ - try: - rc_filename = kwargs['rc_filename'] - description = kwargs['description'] - if len(kwargs) != 2: - raise KeyError - except KeyError: - raise KeyError(_LINT_FILESET_MSG) - - pylint_shell_command = ['pylint', '--rcfile', rc_filename] - pylint_shell_command.extend(dirnames) - status_code = subprocess.call(pylint_shell_command) - if status_code != 0: - error_message = _ERROR_TEMPLATE.format(description, status_code) - print(error_message, file=sys.stderr) - sys.exit(status_code) - - -def make_rc(base_cfg, target_filename, - additions=None, replacements=None): - """Combines a base rc and additions into single file. - - Args: - base_cfg (ConfigParser.ConfigParser): The configuration we are - merging into. - target_filename (str): The filename where the new configuration - will be saved. - additions (dict): (Optional) The values added to the configuration. - replacements (dict): (Optional) The wholesale replacements for - the new configuration. - - Raises: - KeyError: if one of the additions or replacements does not - already exist in the current config. - """ - # Set-up the mutable default values. - if additions is None: - additions = {} - if replacements is None: - replacements = {} - - # Create fresh config, which must extend the base one. - new_cfg = six.moves.configparser.ConfigParser() - # pylint: disable=protected-access - new_cfg._sections = copy.deepcopy(base_cfg._sections) - new_sections = new_cfg._sections - # pylint: enable=protected-access - - for section, opts in additions.items(): - curr_section = new_sections.setdefault( - section, collections.OrderedDict()) - for opt, opt_val in opts.items(): - curr_val = curr_section.get(opt) - if curr_val is None: - raise KeyError('Expected to be adding to existing option.') - curr_val = curr_val.rstrip(',') - opt_val = _transform_opt(opt_val) - curr_section[opt] = '%s, %s' % (curr_val, opt_val) - - for section, opts in replacements.items(): - curr_section = new_sections.setdefault( - section, collections.OrderedDict()) - for opt, opt_val in opts.items(): - curr_val = curr_section.get(opt) - if curr_val is None: - raise KeyError('Expected to be replacing existing option.') - opt_val = _transform_opt(opt_val) - curr_section[opt] = '%s' % (opt_val,) - - with open(target_filename, 'w') as file_obj: - new_cfg.write(file_obj) - - -def main(): - """Script entry point. Lints both sets of files.""" - parser = argparse.ArgumentParser() - parser.add_argument('--library-filesets', nargs='+', default=[]) - parser.add_argument('--test-filesets', nargs='+', default=[]) - - args = parser.parse_args() - - default_config = read_config(get_default_config()) - make_rc(default_config, PRODUCTION_RC, - additions=_PRODUCTION_RC_ADDITIONS, - replacements=_PRODUCTION_RC_REPLACEMENTS) - make_rc(default_config, TEST_RC, - additions=_TEST_RC_ADDITIONS, - replacements=_TEST_RC_REPLACEMENTS) - - lint_fileset(*args.library_filesets, rc_filename=PRODUCTION_RC, - description='Library') - lint_fileset(*args.test_filesets, rc_filename=TEST_RC, - description='Test') - -if __name__ == '__main__': - main() diff --git a/packages/google-auth/scripts/travis.sh b/packages/google-auth/scripts/travis.sh index 5acb4c2a5ccd..28bf5faab468 100755 --- a/packages/google-auth/scripts/travis.sh +++ b/packages/google-auth/scripts/travis.sh @@ -40,17 +40,3 @@ fi # Run tox. echo "Running tox..." tox - -# Run tox for sub-packages. -if [[ $TOXENV != "docs" && -z $SYSTEM_TEST ]]; then - echo "Running tox for google_auth_httplib2..." - cd additional_packages/google_auth_httplib2 - # --workdir is specified to avoid path names being too long, which - # causes subprocess calls to hit the execve character limit. - # See https://github.com/pypa/virtualenv/issues/596 - tox --workdir ~/.tox-httplib2 - cd $ROOT - echo "Running tox for google_auth_oauthlib..." - cd additional_packages/google_auth_oauthlib - tox --workdir ~/.tox-oauthlib -fi diff --git a/packages/google-auth/tests/transport/test_requests.py b/packages/google-auth/tests/transport/test_requests.py index 4be466870e85..ac2a024082f5 100644 --- a/packages/google-auth/tests/transport/test_requests.py +++ b/packages/google-auth/tests/transport/test_requests.py @@ -49,6 +49,7 @@ def refresh(self, request): class MockAdapter(requests.adapters.BaseAdapter): def __init__(self, responses, headers=None): + super(MockAdapter, self).__init__() self.responses = responses self.requests = [] self.headers = headers or {} @@ -57,6 +58,11 @@ def send(self, request, **kwargs): self.requests.append(request) return self.responses.pop(0) + def close(self): # pragma: NO COVER + # pylint wants this to be here because it's abstract in the base + # class, but requests never actually calls it. + return + def make_response(status=http_client.OK, data=None): response = requests.Response() diff --git a/packages/google-auth/tox.ini b/packages/google-auth/tox.ini index 0a0d80647279..ddc6430692fd 100644 --- a/packages/google-auth/tox.ini +++ b/packages/google-auth/tox.ini @@ -77,7 +77,8 @@ commands = --import-order-style=google \ --application-import-names="google,tests,system_tests" \ google tests - python {toxinidir}/scripts/run_pylint.py \ + gcp-devrel-py-tools run-pylint \ + --config pylint.config.py \ --library-filesets google \ --test-filesets tests system_tests deps = @@ -85,3 +86,4 @@ deps = flake8-import-order pylint docutils + gcp-devrel-py-tools From 79247fb2f94ccd03ec68f62f5c9d77392a29c5fd Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Thu, 13 Apr 2017 10:37:03 -0700 Subject: [PATCH 129/966] Fix lint for latest pylint (#155) --- packages/google-auth/google/auth/transport/requests.py | 4 ++++ packages/google-auth/tests/transport/test_requests.py | 3 +++ 2 files changed, 7 insertions(+) diff --git a/packages/google-auth/google/auth/transport/requests.py b/packages/google-auth/google/auth/transport/requests.py index 0c66fdd4198b..6fc395e27066 100644 --- a/packages/google-auth/google/auth/transport/requests.py +++ b/packages/google-auth/google/auth/transport/requests.py @@ -158,6 +158,10 @@ def __init__(self, credentials, def request(self, method, url, data=None, headers=None, **kwargs): """Implementation of Requests' request.""" + # pylint: disable=arguments-differ + # Requests has a ton of arguments to request, but only two + # (method, url) are required. We pass through all of the other + # arguments to super, so no need to exhaustively list them here. # Use a kwarg for this instead of an attribute to maintain # thread-safety. diff --git a/packages/google-auth/tests/transport/test_requests.py b/packages/google-auth/tests/transport/test_requests.py index ac2a024082f5..8c3a4239c7ef 100644 --- a/packages/google-auth/tests/transport/test_requests.py +++ b/packages/google-auth/tests/transport/test_requests.py @@ -55,6 +55,9 @@ def __init__(self, responses, headers=None): self.headers = headers or {} def send(self, request, **kwargs): + # pylint: disable=arguments-differ + # request is the only required argument here and the only argument + # we care about. self.requests.append(request) return self.responses.pop(0) From 4b670fe89bc08137c74b3647c7958c709443eecc Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Thu, 13 Apr 2017 10:42:58 -0700 Subject: [PATCH 130/966] Remove additional packages (#154) --- .../google-auth/additional_packages/README.md | 4 - .../google_auth_httplib2/.coveragerc | 13 - .../google_auth_httplib2/LICENSE | 201 -------- .../google_auth_httplib2/MANIFEST.in | 2 - .../google_auth_httplib2/README.rst | 33 -- .../google_auth_httplib2.py | 235 --------- .../google_auth_httplib2/setup.cfg | 2 - .../google_auth_httplib2/setup.py | 57 --- .../google_auth_httplib2/tests/__init__.py | 0 .../google_auth_httplib2/tests/compliance.py | 92 ---- .../tests/test_google_auth_httplib2.py | 157 ------ .../google_auth_httplib2/tox.ini | 38 -- .../google_auth_oauthlib/.coveragerc | 13 - .../google_auth_oauthlib/CHANGELOG.rst | 4 - .../google_auth_oauthlib/LICENSE | 201 -------- .../google_auth_oauthlib/MANIFEST.in | 2 - .../google_auth_oauthlib/README.rst | 28 -- .../google_auth_oauthlib/__init__.py | 0 .../google_auth_oauthlib/flow.py | 458 ------------------ .../google_auth_oauthlib/helpers.py | 137 ------ .../google_auth_oauthlib/setup.cfg | 2 - .../google_auth_oauthlib/setup.py | 59 --- .../google_auth_oauthlib/tests/__init__.py | 0 .../tests/data/client_secrets.json | 14 - .../google_auth_oauthlib/tests/test_flow.py | 205 -------- .../tests/test_helpers.py | 92 ---- .../google_auth_oauthlib/tox.ini | 36 -- 27 files changed, 2085 deletions(-) delete mode 100644 packages/google-auth/additional_packages/README.md delete mode 100644 packages/google-auth/additional_packages/google_auth_httplib2/.coveragerc delete mode 100644 packages/google-auth/additional_packages/google_auth_httplib2/LICENSE delete mode 100644 packages/google-auth/additional_packages/google_auth_httplib2/MANIFEST.in delete mode 100644 packages/google-auth/additional_packages/google_auth_httplib2/README.rst delete mode 100644 packages/google-auth/additional_packages/google_auth_httplib2/google_auth_httplib2.py delete mode 100644 packages/google-auth/additional_packages/google_auth_httplib2/setup.cfg delete mode 100644 packages/google-auth/additional_packages/google_auth_httplib2/setup.py delete mode 100644 packages/google-auth/additional_packages/google_auth_httplib2/tests/__init__.py delete mode 100644 packages/google-auth/additional_packages/google_auth_httplib2/tests/compliance.py delete mode 100644 packages/google-auth/additional_packages/google_auth_httplib2/tests/test_google_auth_httplib2.py delete mode 100644 packages/google-auth/additional_packages/google_auth_httplib2/tox.ini delete mode 100644 packages/google-auth/additional_packages/google_auth_oauthlib/.coveragerc delete mode 100644 packages/google-auth/additional_packages/google_auth_oauthlib/CHANGELOG.rst delete mode 100644 packages/google-auth/additional_packages/google_auth_oauthlib/LICENSE delete mode 100644 packages/google-auth/additional_packages/google_auth_oauthlib/MANIFEST.in delete mode 100644 packages/google-auth/additional_packages/google_auth_oauthlib/README.rst delete mode 100644 packages/google-auth/additional_packages/google_auth_oauthlib/google_auth_oauthlib/__init__.py delete mode 100644 packages/google-auth/additional_packages/google_auth_oauthlib/google_auth_oauthlib/flow.py delete mode 100644 packages/google-auth/additional_packages/google_auth_oauthlib/google_auth_oauthlib/helpers.py delete mode 100644 packages/google-auth/additional_packages/google_auth_oauthlib/setup.cfg delete mode 100644 packages/google-auth/additional_packages/google_auth_oauthlib/setup.py delete mode 100644 packages/google-auth/additional_packages/google_auth_oauthlib/tests/__init__.py delete mode 100644 packages/google-auth/additional_packages/google_auth_oauthlib/tests/data/client_secrets.json delete mode 100644 packages/google-auth/additional_packages/google_auth_oauthlib/tests/test_flow.py delete mode 100644 packages/google-auth/additional_packages/google_auth_oauthlib/tests/test_helpers.py delete mode 100644 packages/google-auth/additional_packages/google_auth_oauthlib/tox.ini diff --git a/packages/google-auth/additional_packages/README.md b/packages/google-auth/additional_packages/README.md deleted file mode 100644 index 827c54808bb4..000000000000 --- a/packages/google-auth/additional_packages/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Additional packages for Google Auth Library Python - -This folder contains seperately distributed auxilliary packages for use -with google-auth. diff --git a/packages/google-auth/additional_packages/google_auth_httplib2/.coveragerc b/packages/google-auth/additional_packages/google_auth_httplib2/.coveragerc deleted file mode 100644 index c44dd8eaa6c8..000000000000 --- a/packages/google-auth/additional_packages/google_auth_httplib2/.coveragerc +++ /dev/null @@ -1,13 +0,0 @@ -[run] -branch = True - -[report] -omit = - */conftest.py -exclude_lines = - # Re-enable the standard pragma - pragma: NO COVER - # Ignore debug-only repr - def __repr__ - # Don't complain if tests don't hit defensive assertion code: - raise NotImplementedError diff --git a/packages/google-auth/additional_packages/google_auth_httplib2/LICENSE b/packages/google-auth/additional_packages/google_auth_httplib2/LICENSE deleted file mode 100644 index 261eeb9e9f8b..000000000000 --- a/packages/google-auth/additional_packages/google_auth_httplib2/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/packages/google-auth/additional_packages/google_auth_httplib2/MANIFEST.in b/packages/google-auth/additional_packages/google_auth_httplib2/MANIFEST.in deleted file mode 100644 index aac7fe13adac..000000000000 --- a/packages/google-auth/additional_packages/google_auth_httplib2/MANIFEST.in +++ /dev/null @@ -1,2 +0,0 @@ -include README.rst LICENSE -recursive-include tests * diff --git a/packages/google-auth/additional_packages/google_auth_httplib2/README.rst b/packages/google-auth/additional_packages/google_auth_httplib2/README.rst deleted file mode 100644 index 15477d0a6199..000000000000 --- a/packages/google-auth/additional_packages/google_auth_httplib2/README.rst +++ /dev/null @@ -1,33 +0,0 @@ -Httplib2 Transport for Google Auth -================================== - -|pypi| - -This library provides an `httplib2`_ transport for `google-auth`_. - -.. note:: ``httplib`` has lots of problems such as lack of threadsafety and - and insecure usage of TLS. Using it is highly discouraged. This - library is intended to help existing users of ``oauth2client`` migrate to - ``google-auth``. - -.. |pypi| image:: https://img.shields.io/pypi/v/google-auth-httplib2.svg - :target: https://pypi.python.org/pypi/google-auth-httplib2 - -.. _httplib2: https://github.com/httplib2/httplib2 -.. _google-auth: https://github.com/GoogleCloudPlatform/google-auth - -Installing ----------- - -You can install using `pip`_:: - - $ pip install google-auth-httplib2 - -.. _pip: https://pip.pypa.io/en/stable/ - -License -------- - -Apache 2.0 - See `the LICENSE`_ for more information. - -.. _the LICENSE: https://github.com/GoogleCloudPlatform/google-auth-library-python/blob/master/LICENSE diff --git a/packages/google-auth/additional_packages/google_auth_httplib2/google_auth_httplib2.py b/packages/google-auth/additional_packages/google_auth_httplib2/google_auth_httplib2.py deleted file mode 100644 index 866841b2e6b6..000000000000 --- a/packages/google-auth/additional_packages/google_auth_httplib2/google_auth_httplib2.py +++ /dev/null @@ -1,235 +0,0 @@ -# Copyright 2016 Google Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Transport adapter for httplib2.""" - -from __future__ import absolute_import - -import logging - -from google.auth import exceptions -from google.auth import transport -import httplib2 - - -_LOGGER = logging.getLogger(__name__) -# Properties present in file-like streams / buffers. -_STREAM_PROPERTIES = ('read', 'seek', 'tell') - - -class _Response(transport.Response): - """httplib2 transport response adapter. - - Args: - response (httplib2.Response): The raw httplib2 response. - data (bytes): The response body. - """ - def __init__(self, response, data): - self._response = response - self._data = data - - @property - def status(self): - """int: The HTTP status code.""" - return self._response.status - - @property - def headers(self): - """Mapping[str, str]: The HTTP response headers.""" - return dict(self._response) - - @property - def data(self): - """bytes: The response body.""" - return self._data - - -class Request(transport.Request): - """httplib2 request adapter. - - This class is used internally for making requests using various transports - in a consistent way. If you use :class:`AuthorizedHttp` you do not need - to construct or use this class directly. - - This class can be useful if you want to manually refresh a - :class:`~google.auth.credentials.Credentials` instance:: - - import google.auth.transport.httplib2 - import httplib2 - - http = httplib2.Http() - request = google.auth.transport.httplib2.Request(http) - - credentials.refresh(request) - - Args: - http (httplib2.Http): The underlying http object to use to make - requests. - - .. automethod:: __call__ - """ - def __init__(self, http): - self.http = http - - def __call__(self, url, method='GET', body=None, headers=None, - timeout=None, **kwargs): - """Make an HTTP request using httplib2. - - Args: - url (str): The URI to be requested. - method (str): The HTTP method to use for the request. Defaults - to 'GET'. - body (bytes): The payload / body in HTTP request. - headers (Mapping[str, str]): Request headers. - timeout (Optional[int]): The number of seconds to wait for a - response from the server. This is ignored by httplib2 and will - issue a warning. - kwargs: Additional arguments passed throught to the underlying - :meth:`httplib2.Http.request` method. - - Returns: - google.auth.transport.Response: The HTTP response. - - Raises: - google.auth.exceptions.TransportError: If any exception occurred. - """ - if timeout is not None: - _LOGGER.warning( - 'httplib2 transport does not support per-request timeout. ' - 'Set the timeout when constructing the httplib2.Http instance.' - ) - - try: - _LOGGER.debug('Making request: %s %s', method, url) - response, data = self.http.request( - url, method=method, body=body, headers=headers, **kwargs) - return _Response(response, data) - except httplib2.HttpLib2Error as exc: - raise exceptions.TransportError(exc) - - -def _make_default_http(): - """Returns a default httplib2.Http instance.""" - return httplib2.Http() - - -class AuthorizedHttp(object): - """A httplib2 HTTP class with credentials. - - This class is used to perform requests to API endpoints that require - authorization:: - - from google.auth.transport._httplib2 import AuthorizedHttp - - authed_http = AuthorizedHttp(credentials) - - response = authed_http.request( - 'https://www.googleapis.com/storage/v1/b') - - This class implements :meth:`request` in the same way as - :class:`httplib2.Http` and can usually be used just like any other - instance of :class:``httplib2.Http`. - - The underlying :meth:`request` implementation handles adding the - credentials' headers to the request and refreshing credentials as needed. - """ - def __init__(self, credentials, http=None, - refresh_status_codes=transport.DEFAULT_REFRESH_STATUS_CODES, - max_refresh_attempts=transport.DEFAULT_MAX_REFRESH_ATTEMPTS): - """ - Args: - credentials (google.auth.credentials.Credentials): The credentials - to add to the request. - http (httplib2.Http): The underlying HTTP object to - use to make requests. If not specified, a - :class:`httplib2.Http` instance will be constructed. - refresh_status_codes (Sequence[int]): Which HTTP status codes - indicate that credentials should be refreshed and the request - should be retried. - max_refresh_attempts (int): The maximum number of times to attempt - to refresh the credentials and retry the request. - """ - - if http is None: - http = _make_default_http() - - self.http = http - self.credentials = credentials - self._refresh_status_codes = refresh_status_codes - self._max_refresh_attempts = max_refresh_attempts - # Request instance used by internal methods (for example, - # credentials.refresh). - self._request = Request(self.http) - - def request(self, uri, method='GET', body=None, headers=None, - **kwargs): - """Implementation of httplib2's Http.request.""" - - _credential_refresh_attempt = kwargs.pop( - '_credential_refresh_attempt', 0) - - # Make a copy of the headers. They will be modified by the credentials - # and we want to pass the original headers if we recurse. - request_headers = headers.copy() if headers is not None else {} - - self.credentials.before_request( - self._request, method, uri, request_headers) - - # Check if the body is a file-like stream, and if so, save the body - # stream position so that it can be restored in case of refresh. - body_stream_position = None - if all(getattr(body, stream_prop, None) for stream_prop in - _STREAM_PROPERTIES): - body_stream_position = body.tell() - - # Make the request. - response, content = self.http.request( - uri, method, body=body, headers=request_headers, **kwargs) - - # If the response indicated that the credentials needed to be - # refreshed, then refresh the credentials and re-attempt the - # request. - # A stored token may expire between the time it is retrieved and - # the time the request is made, so we may need to try twice. - if (response.status in self._refresh_status_codes - and _credential_refresh_attempt < self._max_refresh_attempts): - - _LOGGER.info( - 'Refreshing credentials due to a %s response. Attempt %s/%s.', - response.status, _credential_refresh_attempt + 1, - self._max_refresh_attempts) - - self.credentials.refresh(self._request) - - # Restore the body's stream position if needed. - if body_stream_position is not None: - body.seek(body_stream_position) - - # Recurse. Pass in the original headers, not our modified set. - return self.request( - uri, method, body=body, headers=headers, - _credential_refresh_attempt=_credential_refresh_attempt + 1, - **kwargs) - - return response, content - - @property - def connections(self): - """Proxy to httplib2.Http.connections.""" - return self.http.connections - - @connections.setter - def connections(self, value): - """Proxy to httplib2.Http.connections.""" - self.http.connections = value diff --git a/packages/google-auth/additional_packages/google_auth_httplib2/setup.cfg b/packages/google-auth/additional_packages/google_auth_httplib2/setup.cfg deleted file mode 100644 index 2a9acf13daa9..000000000000 --- a/packages/google-auth/additional_packages/google_auth_httplib2/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[bdist_wheel] -universal = 1 diff --git a/packages/google-auth/additional_packages/google_auth_httplib2/setup.py b/packages/google-auth/additional_packages/google_auth_httplib2/setup.py deleted file mode 100644 index ec048813a4f3..000000000000 --- a/packages/google-auth/additional_packages/google_auth_httplib2/setup.py +++ /dev/null @@ -1,57 +0,0 @@ -# Copyright 2014 Google Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import io - -from setuptools import setup - - -DEPENDENCIES = ( - 'google-auth' -) - - -with io.open('README.rst', 'r') as fh: - long_description = fh.read() - - -setup( - name='google-auth-httplib2', - version='0.0.2', - author='Google Cloud Platform', - author_email='jonwayne+google-auth@google.com', - description='Google Authentication Library', - long_description=long_description, - url='https://github.com/GoogleCloudPlatform/google-auth-library-python', - py_modules=['google_auth_httplib2'], - install_requires=DEPENDENCIES, - license='Apache 2.0', - keywords='google auth oauth client', - classifiers=( - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Development Status :: 3 - Alpha', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: Apache Software License', - 'Operating System :: POSIX', - 'Operating System :: Microsoft :: Windows', - 'Operating System :: MacOS :: MacOS X', - 'Operating System :: OS Independent', - 'Topic :: Internet :: WWW/HTTP', - ), -) diff --git a/packages/google-auth/additional_packages/google_auth_httplib2/tests/__init__.py b/packages/google-auth/additional_packages/google_auth_httplib2/tests/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/packages/google-auth/additional_packages/google_auth_httplib2/tests/compliance.py b/packages/google-auth/additional_packages/google_auth_httplib2/tests/compliance.py deleted file mode 100644 index a97a11b82bef..000000000000 --- a/packages/google-auth/additional_packages/google_auth_httplib2/tests/compliance.py +++ /dev/null @@ -1,92 +0,0 @@ -# Copyright 2016 Google Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import flask -from google.auth import exceptions -import pytest -from pytest_localserver.http import WSGIServer -from six.moves import http_client - -# .invalid will never resolve, see https://tools.ietf.org/html/rfc2606 -NXDOMAIN = 'test.invalid' - - -class RequestResponseTests(object): - - @pytest.fixture(scope='module') - def server(self): - """Provides a test HTTP server. - - The test server is automatically created before - a test and destroyed at the end. The server is serving a test - application that can be used to verify requests. - """ - app = flask.Flask(__name__) - app.debug = True - - # pylint: disable=unused-variable - # (pylint thinks the flask routes are unusued.) - @app.route('/basic') - def index(): - header_value = flask.request.headers.get('x-test-header', 'value') - headers = {'X-Test-Header': header_value} - return 'Basic Content', http_client.OK, headers - - @app.route('/server_error') - def server_error(): - return 'Error', http_client.INTERNAL_SERVER_ERROR - # pylint: enable=unused-variable - - server = WSGIServer(application=app.wsgi_app) - server.start() - yield server - server.stop() - - def test_request_basic(self, server): - request = self.make_request() - response = request(url=server.url + '/basic', method='GET') - - assert response.status == http_client.OK - assert response.headers['x-test-header'] == 'value' - assert response.data == b'Basic Content' - - def test_request_timeout(self, server): - request = self.make_request() - response = request(url=server.url + '/basic', method='GET', timeout=2) - - assert response.status == http_client.OK - assert response.headers['x-test-header'] == 'value' - assert response.data == b'Basic Content' - - def test_request_headers(self, server): - request = self.make_request() - response = request( - url=server.url + '/basic', method='GET', headers={ - 'x-test-header': 'hello world'}) - - assert response.status == http_client.OK - assert response.headers['x-test-header'] == 'hello world' - assert response.data == b'Basic Content' - - def test_request_error(self, server): - request = self.make_request() - response = request(url=server.url + '/server_error', method='GET') - - assert response.status == http_client.INTERNAL_SERVER_ERROR - assert response.data == b'Error' - - def test_connection_error(self): - request = self.make_request() - with pytest.raises(exceptions.TransportError): - request(url='http://{}'.format(NXDOMAIN), method='GET') diff --git a/packages/google-auth/additional_packages/google_auth_httplib2/tests/test_google_auth_httplib2.py b/packages/google-auth/additional_packages/google_auth_httplib2/tests/test_google_auth_httplib2.py deleted file mode 100644 index 635965f56909..000000000000 --- a/packages/google-auth/additional_packages/google_auth_httplib2/tests/test_google_auth_httplib2.py +++ /dev/null @@ -1,157 +0,0 @@ -# Copyright 2016 Google Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import httplib2 -import mock -import six -from six.moves import http_client - -import google_auth_httplib2 -from tests import compliance - - -class MockHttp(object): - def __init__(self, responses, headers=None): - self.responses = responses - self.requests = [] - self.headers = headers or {} - - def request(self, url, method='GET', body=None, headers=None, **kwargs): - self.requests.append((method, url, body, headers, kwargs)) - return self.responses.pop(0) - - -class MockResponse(object): - def __init__(self, status=http_client.OK, data=b''): - self.status = status - self.data = data - - def __iter__(self): - yield self - yield self.data - - -class TestRequestResponse(compliance.RequestResponseTests): - def make_request(self): - http = httplib2.Http() - return google_auth_httplib2.Request(http) - - def test_timeout(self): - url = 'http://example.com' - http = MockHttp(responses=[MockResponse()]) - request = google_auth_httplib2.Request(http) - request(url=url, method='GET', timeout=5) - - assert http.requests[0] == ( - 'GET', url, None, None, {}) - - -def test__make_default_http(): - http = google_auth_httplib2._make_default_http() - assert isinstance(http, httplib2.Http) - - -class MockCredentials(object): - def __init__(self, token='token'): - self.token = token - - def apply(self, headers): - headers['authorization'] = self.token - - def before_request(self, request, method, url, headers): - self.apply(headers) - - def refresh(self, request): - self.token += '1' - - -class TestAuthorizedHttp(object): - TEST_URL = 'http://example.com' - - def test_authed_http_defaults(self): - authed_http = google_auth_httplib2.AuthorizedHttp( - mock.sentinel.credentials) - - assert authed_http.credentials == mock.sentinel.credentials - assert isinstance(authed_http.http, httplib2.Http) - - def test_connections(self): - authed_http = google_auth_httplib2.AuthorizedHttp( - mock.sentinel.credentials) - - assert authed_http.connections == authed_http.http.connections - - authed_http.connections = mock.sentinel.connections - assert authed_http.http.connections == mock.sentinel.connections - - def test_request_no_refresh(self): - mock_credentials = mock.Mock(wraps=MockCredentials()) - mock_response = MockResponse() - mock_http = MockHttp([mock_response]) - - authed_http = google_auth_httplib2.AuthorizedHttp( - mock_credentials, http=mock_http) - - response, data = authed_http.request(self.TEST_URL) - - assert response == mock_response - assert data == mock_response.data - assert mock_credentials.before_request.called - assert not mock_credentials.refresh.called - assert mock_http.requests == [ - ('GET', self.TEST_URL, None, {'authorization': 'token'}, {})] - - def test_request_refresh(self): - mock_credentials = mock.Mock(wraps=MockCredentials()) - mock_final_response = MockResponse(status=http_client.OK) - # First request will 401, second request will succeed. - mock_http = MockHttp([ - MockResponse(status=http_client.UNAUTHORIZED), - mock_final_response]) - - authed_http = google_auth_httplib2.AuthorizedHttp( - mock_credentials, http=mock_http) - - response, data = authed_http.request(self.TEST_URL) - - assert response == mock_final_response - assert data == mock_final_response.data - assert mock_credentials.before_request.call_count == 2 - assert mock_credentials.refresh.called - assert mock_http.requests == [ - ('GET', self.TEST_URL, None, {'authorization': 'token'}, {}), - ('GET', self.TEST_URL, None, {'authorization': 'token1'}, {})] - - def test_request_stream_body(self): - mock_credentials = mock.Mock(wraps=MockCredentials()) - mock_response = MockResponse() - # Refresh is needed to cover the resetting of the body position. - mock_http = MockHttp([ - MockResponse(status=http_client.UNAUTHORIZED), - mock_response]) - - body = six.StringIO('body') - body.seek(1) - - authed_http = google_auth_httplib2.AuthorizedHttp( - mock_credentials, http=mock_http) - - response, data = authed_http.request( - self.TEST_URL, method='POST', body=body) - - assert response == mock_response - assert data == mock_response.data - assert mock_http.requests == [ - ('POST', self.TEST_URL, body, {'authorization': 'token'}, {}), - ('POST', self.TEST_URL, body, {'authorization': 'token1'}, {})] diff --git a/packages/google-auth/additional_packages/google_auth_httplib2/tox.ini b/packages/google-auth/additional_packages/google_auth_httplib2/tox.ini deleted file mode 100644 index 95f34542780b..000000000000 --- a/packages/google-auth/additional_packages/google_auth_httplib2/tox.ini +++ /dev/null @@ -1,38 +0,0 @@ -[tox] -envlist = lint,py27,py34,py35,py36,pypy,cover - -[testenv] -deps = - flask - mock - pytest - pytest-cov - pytest-localserver - httplib2 -commands = - py.test --cov=google_auth_httplib2 --cov=tests {posargs:tests} - -[testenv:cover] -basepython = python3.6 -commands = - py.test --cov=google_auth_httplib2 --cov=tests --cov-report= tests - coverage report --show-missing --fail-under=100 -deps = - {[testenv]deps} - -[testenv:lint] -basepython = python3.5 -commands = - python setup.py check --metadata --restructuredtext --strict - flake8 \ - --import-order-style=google \ - --application-import-names="google_auth_httplib2,tests" \ - google_auth_httplib2.py tests - python {toxinidir}/../../scripts/run_pylint.py \ - --library-filesets google_auth_httplib2.py \ - --test-filesets tests -deps = - flake8 - flake8-import-order - pylint - docutils diff --git a/packages/google-auth/additional_packages/google_auth_oauthlib/.coveragerc b/packages/google-auth/additional_packages/google_auth_oauthlib/.coveragerc deleted file mode 100644 index c44dd8eaa6c8..000000000000 --- a/packages/google-auth/additional_packages/google_auth_oauthlib/.coveragerc +++ /dev/null @@ -1,13 +0,0 @@ -[run] -branch = True - -[report] -omit = - */conftest.py -exclude_lines = - # Re-enable the standard pragma - pragma: NO COVER - # Ignore debug-only repr - def __repr__ - # Don't complain if tests don't hit defensive assertion code: - raise NotImplementedError diff --git a/packages/google-auth/additional_packages/google_auth_oauthlib/CHANGELOG.rst b/packages/google-auth/additional_packages/google_auth_oauthlib/CHANGELOG.rst deleted file mode 100644 index dbb95df40aee..000000000000 --- a/packages/google-auth/additional_packages/google_auth_oauthlib/CHANGELOG.rst +++ /dev/null @@ -1,4 +0,0 @@ -v0.0.1 ------- - -Initial release. This package contains the functionality previously located in `google.oauth2.oauthlib` and `google.oauth2.flows`. diff --git a/packages/google-auth/additional_packages/google_auth_oauthlib/LICENSE b/packages/google-auth/additional_packages/google_auth_oauthlib/LICENSE deleted file mode 100644 index 261eeb9e9f8b..000000000000 --- a/packages/google-auth/additional_packages/google_auth_oauthlib/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/packages/google-auth/additional_packages/google_auth_oauthlib/MANIFEST.in b/packages/google-auth/additional_packages/google_auth_oauthlib/MANIFEST.in deleted file mode 100644 index aac7fe13adac..000000000000 --- a/packages/google-auth/additional_packages/google_auth_oauthlib/MANIFEST.in +++ /dev/null @@ -1,2 +0,0 @@ -include README.rst LICENSE -recursive-include tests * diff --git a/packages/google-auth/additional_packages/google_auth_oauthlib/README.rst b/packages/google-auth/additional_packages/google_auth_oauthlib/README.rst deleted file mode 100644 index 44c478b165b1..000000000000 --- a/packages/google-auth/additional_packages/google_auth_oauthlib/README.rst +++ /dev/null @@ -1,28 +0,0 @@ -oauthlib integration for Google Auth -==================================== - -|pypi| - -This library provides `oauthlib`_ integration with `google-auth`_. - -.. |pypi| image:: https://img.shields.io/pypi/v/google-auth-oauthlib.svg - :target: https://pypi.python.org/pypi/google-auth-oauthlib - -.. _oauthlib: https://github.com/idan/oauthlib -.. _google-auth: https://github.com/GoogleCloudPlatform/google-auth - -Installing ----------- - -You can install using `pip`_:: - - $ pip install google-auth-oauthlib - -.. _pip: https://pip.pypa.io/en/stable/ - -License -------- - -Apache 2.0 - See `the LICENSE`_ for more information. - -.. _the LICENSE: https://github.com/GoogleCloudPlatform/google-auth-library-python/blob/master/LICENSE diff --git a/packages/google-auth/additional_packages/google_auth_oauthlib/google_auth_oauthlib/__init__.py b/packages/google-auth/additional_packages/google_auth_oauthlib/google_auth_oauthlib/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/packages/google-auth/additional_packages/google_auth_oauthlib/google_auth_oauthlib/flow.py b/packages/google-auth/additional_packages/google_auth_oauthlib/google_auth_oauthlib/flow.py deleted file mode 100644 index 0df96c0c0489..000000000000 --- a/packages/google-auth/additional_packages/google_auth_oauthlib/google_auth_oauthlib/flow.py +++ /dev/null @@ -1,458 +0,0 @@ -# Copyright 2016 Google Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""OAuth 2.0 Authorization Flow - -This module provides integration with `requests-oauthlib`_ for running the -`OAuth 2.0 Authorization Flow`_ and acquiring user credentials. - -Here's an example of using :class:`Flow` with the installed application -authorization flow:: - - from google_auth_oauthlib.flow import Flow - - # Create the flow using the client secrets file from the Google API - # Console. - flow = Flow.from_client_secrets_file( - 'path/to/client_secrets.json', - scopes=['profile', 'email'], - redirect_uri='urn:ietf:wg:oauth:2.0:oob') - - # Tell the user to go to the authorization URL. - auth_url, _ = flow.authorization_url(prompt='consent') - - print('Please go to this URL: {}'.format(auth_url)) - - # The user will get an authorization code. This code is used to get the - # access token. - code = input('Enter the authorization code: ') - flow.fetch_token(code=code) - - # You can use flow.credentials, or you can just get a requests session - # using flow.authorized_session. - session = flow.authorized_session() - print(session.get('https://www.googleapis.com/userinfo/v2/me').json()) - -This particular flow can be handled entirely by using -:class:`InstalledAppFlow`. - -.. _requests-oauthlib: http://requests-oauthlib.readthedocs.io/en/stable/ -.. _OAuth 2.0 Authorization Flow: - https://tools.ietf.org/html/rfc6749#section-1.2 -""" - -import json -import logging -import webbrowser -import wsgiref.simple_server -import wsgiref.util - -import google.auth.transport.requests -import google.oauth2.credentials -from six.moves import input - -import google_auth_oauthlib.helpers - - -_LOGGER = logging.getLogger(__name__) - - -class Flow(object): - """OAuth 2.0 Authorization Flow - - This class uses a :class:`requests_oauthlib.OAuth2Session` instance at - :attr:`oauth2session` to perform all of the OAuth 2.0 logic. This class - just provides convenience methods and sane defaults for doing Google's - particular flavors of OAuth 2.0. - - Typically you'll construct an instance of this flow using - :meth:`from_client_secrets_file` and a `client secrets file`_ obtained - from the `Google API Console`_. - - .. _client secrets file: - https://developers.google.com/identity/protocols/OAuth2WebServer - #creatingcred - .. _Google API Console: - https://console.developers.google.com/apis/credentials - """ - - def __init__(self, oauth2session, client_type, client_config): - """ - Args: - oauth2session (requests_oauthlib.OAuth2Session): - The OAuth 2.0 session from ``requests-oauthlib``. - client_type (str): The client type, either ``web`` or - ``installed``. - client_config (Mapping[str, Any]): The client - configuration in the Google `client secrets`_ format. - - .. _client secrets: - https://developers.google.com/api-client-library/python/guide - /aaa_client_secrets - """ - self.client_type = client_type - """str: The client type, either ``'web'`` or ``'installed'``""" - self.client_config = client_config[client_type] - """Mapping[str, Any]: The OAuth 2.0 client configuration.""" - self.oauth2session = oauth2session - """requests_oauthlib.OAuth2Session: The OAuth 2.0 session.""" - - @classmethod - def from_client_config(cls, client_config, scopes, **kwargs): - """Creates a :class:`requests_oauthlib.OAuth2Session` from client - configuration loaded from a Google-format client secrets file. - - Args: - client_config (Mapping[str, Any]): The client - configuration in the Google `client secrets`_ format. - scopes (Sequence[str]): The list of scopes to request during the - flow. - kwargs: Any additional parameters passed to - :class:`requests_oauthlib.OAuth2Session` - - Returns: - Flow: The constructed Flow instance. - - Raises: - ValueError: If the client configuration is not in the correct - format. - - .. _client secrets: - https://developers.google.com/api-client-library/python/guide - /aaa_client_secrets - """ - if 'web' in client_config: - client_type = 'web' - elif 'installed' in client_config: - client_type = 'installed' - else: - raise ValueError( - 'Client secrets must be for a web or installed app.') - - session, client_config = ( - google_auth_oauthlib.helpers.session_from_client_config( - client_config, scopes, **kwargs)) - - return cls(session, client_type, client_config) - - @classmethod - def from_client_secrets_file(cls, client_secrets_file, scopes, **kwargs): - """Creates a :class:`Flow` instance from a Google client secrets file. - - Args: - client_secrets_file (str): The path to the client secrets .json - file. - scopes (Sequence[str]): The list of scopes to request during the - flow. - kwargs: Any additional parameters passed to - :class:`requests_oauthlib.OAuth2Session` - - Returns: - Flow: The constructed Flow instance. - """ - with open(client_secrets_file, 'r') as json_file: - client_config = json.load(json_file) - - return cls.from_client_config(client_config, scopes=scopes, **kwargs) - - @property - def redirect_uri(self): - """The OAuth 2.0 redirect URI. Pass-through to - ``self.oauth2session.redirect_uri``.""" - return self.oauth2session.redirect_uri - - @redirect_uri.setter - def redirect_uri(self, value): - self.oauth2session.redirect_uri = value - - def authorization_url(self, **kwargs): - """Generates an authorization URL. - - This is the first step in the OAuth 2.0 Authorization Flow. The user's - browser should be redirected to the returned URL. - - This method calls - :meth:`requests_oauthlib.OAuth2Session.authorization_url` - and specifies the client configuration's authorization URI (usually - Google's authorization server) and specifies that "offline" access is - desired. This is required in order to obtain a refresh token. - - Args: - kwargs: Additional arguments passed through to - :meth:`requests_oauthlib.OAuth2Session.authorization_url` - - Returns: - Tuple[str, str]: The generated authorization URL and state. The - user must visit the URL to complete the flow. The state is used - when completing the flow to verify that the request originated - from your application. If your application is using a different - :class:`Flow` instance to obtain the token, you will need to - specify the ``state`` when constructing the :class:`Flow`. - """ - url, state = self.oauth2session.authorization_url( - self.client_config['auth_uri'], - access_type='offline', **kwargs) - - return url, state - - def fetch_token(self, **kwargs): - """Completes the Authorization Flow and obtains an access token. - - This is the final step in the OAuth 2.0 Authorization Flow. This is - called after the user consents. - - This method calls - :meth:`requests_oauthlib.OAuth2Session.fetch_token` - and specifies the client configuration's token URI (usually Google's - token server). - - Args: - kwargs: Arguments passed through to - :meth:`requests_oauthlib.OAuth2Session.fetch_token`. At least - one of ``code`` or ``authorization_response`` must be - specified. - - Returns: - Mapping[str, str]: The obtained tokens. Typically, you will not use - return value of this function and instead and use - :meth:`credentials` to obtain a - :class:`~google.auth.credentials.Credentials` instance. - """ - return self.oauth2session.fetch_token( - self.client_config['token_uri'], - client_secret=self.client_config['client_secret'], - **kwargs) - - @property - def credentials(self): - """Returns credentials from the OAuth 2.0 session. - - :meth:`fetch_token` must be called before accessing this. This method - constructs a :class:`google.oauth2.credentials.Credentials` class using - the session's token and the client config. - - Returns: - google.oauth2.credentials.Credentials: The constructed credentials. - - Raises: - ValueError: If there is no access token in the session. - """ - return google_auth_oauthlib.helpers.credentials_from_session( - self.oauth2session, self.client_config) - - def authorized_session(self): - """Returns a :class:`requests.Session` authorized with credentials. - - :meth:`fetch_token` must be called before this method. This method - constructs a :class:`google.auth.transport.requests.AuthorizedSession` - class using this flow's :attr:`credentials`. - - Returns: - google.auth.transport.requests.AuthorizedSession: The constructed - session. - """ - return google.auth.transport.requests.AuthorizedSession( - self.credentials) - - -class InstalledAppFlow(Flow): - """Authorization flow helper for installed applications. - - This :class:`Flow` subclass makes it easier to perform the - `Installed Application Authorization Flow`_. This flow is useful for - local development or applications that are installed on a desktop operating - system. - - This flow has two strategies: The console strategy provided by - :meth:`run_console` and the local server strategy provided by - :meth:`run_local_server`. - - Example:: - - from google_auth_oauthlib.flow import InstalledAppFlow - - flow = InstalledAppFlow.from_client_secrets_file( - 'client_secrets.json', - scopes=['profile', 'email']) - - flow.run_local_server() - - session = flow.authorized_session() - - profile_info = session.get( - 'https://www.googleapis.com/userinfo/v2/me').json() - - print(profile_info) - # {'name': '...', 'email': '...', ...} - - - Note that these aren't the only two ways to accomplish the installed - application flow, they are just the most common ways. You can use the - :class:`Flow` class to perform the same flow with different methods of - presenting the authorization URL to the user or obtaining the authorization - response, such as using an embedded web view. - - .. _Installed Application Authorization Flow: - https://developers.google.com/api-client-library/python/auth - /installed-app - """ - _OOB_REDIRECT_URI = 'urn:ietf:wg:oauth:2.0:oob' - - _DEFAULT_AUTH_PROMPT_MESSAGE = ( - 'Please visit this URL to authorize this application: {url}') - """str: The message to display when prompting the user for - authorization.""" - _DEFAULT_AUTH_CODE_MESSAGE = ( - 'Enter the authorization code: ') - """str: The message to display when prompting the user for the - authorization code. Used only by the console strategy.""" - - _DEFAULT_WEB_SUCCESS_MESSAGE = ( - 'The authentication flow has completed, you may close this window.') - - def run_console( - self, - authorization_prompt_message=_DEFAULT_AUTH_PROMPT_MESSAGE, - authorization_code_message=_DEFAULT_AUTH_CODE_MESSAGE, - **kwargs): - """Run the flow using the console strategy. - - The console strategy instructs the user to open the authorization URL - in their browser. Once the authorization is complete the authorization - server will give the user a code. The user then must copy & paste this - code into the application. The code is then exchanged for a token. - - Args: - authorization_prompt_message (str): The message to display to tell - the user to navigate to the authorization URL. - authorization_code_message (str): The message to display when - prompting the user for the authorization code. - kwargs: Additional keyword arguments passed through to - :meth:`authorization_url`. - - Returns: - google.oauth2.credentials.Credentials: The OAuth 2.0 credentials - for the user. - """ - kwargs.setdefault('prompt', 'consent') - - self.redirect_uri = self._OOB_REDIRECT_URI - - auth_url, _ = self.authorization_url(**kwargs) - - print(authorization_prompt_message.format(url=auth_url)) - - code = input(authorization_code_message) - - self.fetch_token(code=code) - - return self.credentials - - def run_local_server( - self, host='localhost', port=8080, - authorization_prompt_message=_DEFAULT_AUTH_PROMPT_MESSAGE, - success_message=_DEFAULT_WEB_SUCCESS_MESSAGE, - open_browser=True, - **kwargs): - """Run the flow using the server strategy. - - The server strategy instructs the user to open the authorization URL in - their browser and will attempt to automatically open the URL for them. - It will start a local web server to listen for the authorization - response. Once authorization is complete the authorization server will - redirect the user's browser to the local web server. The web server - will get the authorization code from the response and shutdown. The - code is then exchanged for a token. - - Args: - host (str): The hostname for the local redirect server. This will - be served over http, not https. - port (int): The port for the local redirect server. - authorization_prompt_message (str): The message to display to tell - the user to navigate to the authorization URL. - success_message (str): The message to display in the web browser - the authorization flow is complete. - open_browser (bool): Whether or not to open the authorization URL - in the user's browser. - kwargs: Additional keyword arguments passed through to - :meth:`authorization_url`. - - Returns: - google.oauth2.credentials.Credentials: The OAuth 2.0 credentials - for the user. - """ - self.redirect_uri = 'http://{}:{}/'.format(host, port) - - auth_url, _ = self.authorization_url(**kwargs) - - wsgi_app = _RedirectWSGIApp(success_message) - local_server = wsgiref.simple_server.make_server( - host, port, wsgi_app, handler_class=_WSGIRequestHandler) - - if open_browser: - webbrowser.open(auth_url, new=1, autoraise=True) - - print(authorization_prompt_message.format(url=auth_url)) - - local_server.handle_request() - - # Note: using https here because oauthlib is very picky that - # OAuth 2.0 should only occur over https. - authorization_response = wsgi_app.last_request_uri.replace( - 'http', 'https') - self.fetch_token(authorization_response=authorization_response) - - return self.credentials - - -class _WSGIRequestHandler(wsgiref.simple_server.WSGIRequestHandler): - """Custom WSGIRequestHandler. - - Uses a named logger instead of printing to stderr. - """ - def log_message(self, format, *args, **kwargs): - # pylint: disable=redefined-builtin - # (format is the argument name defined in the superclass.) - _LOGGER.info(format, *args, **kwargs) - - -class _RedirectWSGIApp(object): - """WSGI app to handle the authorization redirect. - - Stores the request URI and displays the given success message. - """ - - def __init__(self, success_message): - """ - Args: - success_message (str): The message to display in the web browser - the authorization flow is complete. - """ - self.last_request_uri = None - self._success_message = success_message - - def __call__(self, environ, start_response): - """WSGI Callable. - - Args: - environ (Mapping[str, Any]): The WSGI environment. - start_response (Callable[str, list]): The WSGI start_response - callable. - - Returns: - Iterable[bytes]: The response body. - """ - start_response('200 OK', [('Content-type', 'text/plain')]) - self.last_request_uri = wsgiref.util.request_uri(environ) - return [self._success_message.encode('utf-8')] diff --git a/packages/google-auth/additional_packages/google_auth_oauthlib/google_auth_oauthlib/helpers.py b/packages/google-auth/additional_packages/google_auth_oauthlib/google_auth_oauthlib/helpers.py deleted file mode 100644 index 41866d678266..000000000000 --- a/packages/google-auth/additional_packages/google_auth_oauthlib/google_auth_oauthlib/helpers.py +++ /dev/null @@ -1,137 +0,0 @@ -# Copyright 2017 Google Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Integration helpers. - -This module provides helpers for integrating with `requests-oauthlib`_. -Typically, you'll want to use the higher-level helpers in -:mod:`google_auth_oauthlib.flow`. - -.. _requests-oauthlib: http://requests-oauthlib.readthedocs.io/en/stable/ -""" - -import json - -import google.oauth2.credentials -import requests_oauthlib - -_REQUIRED_CONFIG_KEYS = frozenset(('auth_uri', 'token_uri', 'client_id')) - - -def session_from_client_config(client_config, scopes, **kwargs): - """Creates a :class:`requests_oauthlib.OAuth2Session` from client - configuration loaded from a Google-format client secrets file. - - Args: - client_config (Mapping[str, Any]): The client - configuration in the Google `client secrets`_ format. - scopes (Sequence[str]): The list of scopes to request during the - flow. - kwargs: Any additional parameters passed to - :class:`requests_oauthlib.OAuth2Session` - - Raises: - ValueError: If the client configuration is not in the correct - format. - - Returns: - Tuple[requests_oauthlib.OAuth2Session, Mapping[str, Any]]: The new - oauthlib session and the validated client configuration. - - .. _client secrets: - https://developers.google.com/api-client-library/python/guide - /aaa_client_secrets - """ - - if 'web' in client_config: - config = client_config['web'] - elif 'installed' in client_config: - config = client_config['installed'] - else: - raise ValueError( - 'Client secrets must be for a web or installed app.') - - if not _REQUIRED_CONFIG_KEYS.issubset(config.keys()): - raise ValueError('Client secrets is not in the correct format.') - - session = requests_oauthlib.OAuth2Session( - client_id=config['client_id'], - scope=scopes, - **kwargs) - - return session, client_config - - -def session_from_client_secrets_file(client_secrets_file, scopes, **kwargs): - """Creates a :class:`requests_oauthlib.OAuth2Session` instance from a - Google-format client secrets file. - - Args: - client_secrets_file (str): The path to the `client secrets`_ .json - file. - scopes (Sequence[str]): The list of scopes to request during the - flow. - kwargs: Any additional parameters passed to - :class:`requests_oauthlib.OAuth2Session` - - Returns: - Tuple[requests_oauthlib.OAuth2Session, Mapping[str, Any]]: The new - oauthlib session and the validated client configuration. - - .. _client secrets: - https://developers.google.com/api-client-library/python/guide - /aaa_client_secrets - """ - with open(client_secrets_file, 'r') as json_file: - client_config = json.load(json_file) - - return session_from_client_config(client_config, scopes, **kwargs) - - -def credentials_from_session(session, client_config=None): - """Creates :class:`google.oauth2.credentials.Credentials` from a - :class:`requests_oauthlib.OAuth2Session`. - - :meth:`fetch_token` must be called on the session before before calling - this. This uses the session's auth token and the provided client - configuration to create :class:`google.oauth2.credentials.Credentials`. - This allows you to use the credentials from the session with Google - API client libraries. - - Args: - session (requests_oauthlib.OAuth2Session): The OAuth 2.0 session. - client_config (Mapping[str, Any]): The subset of the client - configuration to use. For example, if you have a web client - you would pass in `client_config['web']`. - - Returns: - google.oauth2.credentials.Credentials: The constructed credentials. - - Raises: - ValueError: If there is no access token in the session. - """ - client_config = client_config if client_config is not None else {} - - if not session.token: - raise ValueError( - 'There is no access token for this session, did you call ' - 'fetch_token?') - - return google.oauth2.credentials.Credentials( - session.token['access_token'], - refresh_token=session.token.get('refresh_token'), - token_uri=client_config.get('token_uri'), - client_id=client_config.get('client_id'), - client_secret=client_config.get('client_secret'), - scopes=session.scope) diff --git a/packages/google-auth/additional_packages/google_auth_oauthlib/setup.cfg b/packages/google-auth/additional_packages/google_auth_oauthlib/setup.cfg deleted file mode 100644 index 2a9acf13daa9..000000000000 --- a/packages/google-auth/additional_packages/google_auth_oauthlib/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[bdist_wheel] -universal = 1 diff --git a/packages/google-auth/additional_packages/google_auth_oauthlib/setup.py b/packages/google-auth/additional_packages/google_auth_oauthlib/setup.py deleted file mode 100644 index 36524706fab7..000000000000 --- a/packages/google-auth/additional_packages/google_auth_oauthlib/setup.py +++ /dev/null @@ -1,59 +0,0 @@ -# Copyright 2014 Google Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import io - -from setuptools import find_packages -from setuptools import setup - - -DEPENDENCIES = ( - 'google-auth', - 'requests-oauthlib>=0.7.0', -) - - -with io.open('README.rst', 'r') as fh: - long_description = fh.read() - - -setup( - name='google-auth-oauthlib', - version='0.0.1', - author='Google Cloud Platform', - author_email='jonwayne+google-auth@google.com', - description='Google Authentication Library', - long_description=long_description, - url='https://github.com/GoogleCloudPlatform/google-auth-library-python', - packages=find_packages(exclude=('tests*',)), - install_requires=DEPENDENCIES, - license='Apache 2.0', - keywords='google auth oauth client oauthlib', - classifiers=( - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Development Status :: 3 - Alpha', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: Apache Software License', - 'Operating System :: POSIX', - 'Operating System :: Microsoft :: Windows', - 'Operating System :: MacOS :: MacOS X', - 'Operating System :: OS Independent', - 'Topic :: Internet :: WWW/HTTP', - ), -) diff --git a/packages/google-auth/additional_packages/google_auth_oauthlib/tests/__init__.py b/packages/google-auth/additional_packages/google_auth_oauthlib/tests/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/packages/google-auth/additional_packages/google_auth_oauthlib/tests/data/client_secrets.json b/packages/google-auth/additional_packages/google_auth_oauthlib/tests/data/client_secrets.json deleted file mode 100644 index 1baa4995aff5..000000000000 --- a/packages/google-auth/additional_packages/google_auth_oauthlib/tests/data/client_secrets.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "web": { - "client_id": "example.apps.googleusercontent.com", - "project_id": "example", - "auth_uri": "https://accounts.google.com/o/oauth2/auth", - "token_uri": "https://accounts.google.com/o/oauth2/token", - "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", - "client_secret": "itsasecrettoeveryone", - "redirect_uris": [ - "urn:ietf:wg:oauth:2.0:oob", - "http://localhost" - ] - } -} diff --git a/packages/google-auth/additional_packages/google_auth_oauthlib/tests/test_flow.py b/packages/google-auth/additional_packages/google_auth_oauthlib/tests/test_flow.py deleted file mode 100644 index 88d283dfd9a5..000000000000 --- a/packages/google-auth/additional_packages/google_auth_oauthlib/tests/test_flow.py +++ /dev/null @@ -1,205 +0,0 @@ -# Copyright 2017 Google Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import concurrent.futures -import json -import os - -import mock -import pytest -import requests -from six.moves import urllib - -from google_auth_oauthlib import flow - -DATA_DIR = os.path.join(os.path.dirname(__file__), 'data') -CLIENT_SECRETS_FILE = os.path.join(DATA_DIR, 'client_secrets.json') - -with open(CLIENT_SECRETS_FILE, 'r') as fh: - CLIENT_SECRETS_INFO = json.load(fh) - - -class TestFlow(object): - def test_from_client_secrets_file(self): - instance = flow.Flow.from_client_secrets_file( - CLIENT_SECRETS_FILE, scopes=mock.sentinel.scopes) - assert instance.client_config == CLIENT_SECRETS_INFO['web'] - assert (instance.oauth2session.client_id == - CLIENT_SECRETS_INFO['web']['client_id']) - assert instance.oauth2session.scope == mock.sentinel.scopes - - def test_from_client_config_installed(self): - client_config = {'installed': CLIENT_SECRETS_INFO['web']} - instance = flow.Flow.from_client_config( - client_config, scopes=mock.sentinel.scopes) - assert instance.client_config == client_config['installed'] - assert (instance.oauth2session.client_id == - client_config['installed']['client_id']) - assert instance.oauth2session.scope == mock.sentinel.scopes - - def test_from_client_config_bad_format(self): - with pytest.raises(ValueError): - flow.Flow.from_client_config({}, scopes=mock.sentinel.scopes) - - @pytest.fixture - def instance(self): - yield flow.Flow.from_client_config( - CLIENT_SECRETS_INFO, scopes=mock.sentinel.scopes) - - def test_redirect_uri(self, instance): - instance.redirect_uri = mock.sentinel.redirect_uri - assert (instance.redirect_uri == - instance.oauth2session.redirect_uri == - mock.sentinel.redirect_uri) - - def test_authorization_url(self, instance): - scope = 'scope_one' - instance.oauth2session.scope = [scope] - authorization_url_patch = mock.patch.object( - instance.oauth2session, 'authorization_url', - wraps=instance.oauth2session.authorization_url) - - with authorization_url_patch as authorization_url_spy: - url, _ = instance.authorization_url(prompt='consent') - - assert CLIENT_SECRETS_INFO['web']['auth_uri'] in url - assert scope in url - authorization_url_spy.assert_called_with( - CLIENT_SECRETS_INFO['web']['auth_uri'], - access_type='offline', - prompt='consent') - - def test_fetch_token(self, instance): - fetch_token_patch = mock.patch.object( - instance.oauth2session, 'fetch_token', autospec=True, - return_value=mock.sentinel.token) - - with fetch_token_patch as fetch_token_mock: - token = instance.fetch_token(code=mock.sentinel.code) - - assert token == mock.sentinel.token - fetch_token_mock.assert_called_with( - CLIENT_SECRETS_INFO['web']['token_uri'], - client_secret=CLIENT_SECRETS_INFO['web']['client_secret'], - code=mock.sentinel.code) - - def test_credentials(self, instance): - instance.oauth2session.token = { - 'access_token': mock.sentinel.access_token, - 'refresh_token': mock.sentinel.refresh_token - } - - credentials = instance.credentials - - assert credentials.token == mock.sentinel.access_token - assert credentials._refresh_token == mock.sentinel.refresh_token - assert (credentials._client_id == - CLIENT_SECRETS_INFO['web']['client_id']) - assert (credentials._client_secret == - CLIENT_SECRETS_INFO['web']['client_secret']) - assert (credentials._token_uri == - CLIENT_SECRETS_INFO['web']['token_uri']) - - def test_authorized_session(self, instance): - instance.oauth2session.token = { - 'access_token': mock.sentinel.access_token, - 'refresh_token': mock.sentinel.refresh_token - } - - session = instance.authorized_session() - - assert session.credentials.token == mock.sentinel.access_token - - -class TestInstalledAppFlow(object): - SCOPES = ['email', 'profile'] - REDIRECT_REQUEST_PATH = '/?code=code&state=state' - - @pytest.fixture - def instance(self): - yield flow.InstalledAppFlow.from_client_config( - CLIENT_SECRETS_INFO, scopes=self.SCOPES) - - @pytest.fixture - def mock_fetch_token(self, instance): - def set_token(*args, **kwargs): - instance.oauth2session.token = { - 'access_token': mock.sentinel.access_token, - 'refresh_token': mock.sentinel.refresh_token - } - - fetch_token_patch = mock.patch.object( - instance.oauth2session, 'fetch_token', autospec=True, - side_effect=set_token) - - with fetch_token_patch as fetch_token_mock: - yield fetch_token_mock - - @mock.patch('google_auth_oauthlib.flow.input', autospec=True) - def test_run_console(self, input_mock, instance, mock_fetch_token): - input_mock.return_value = mock.sentinel.code - - credentials = instance.run_console() - - assert credentials.token == mock.sentinel.access_token - assert credentials._refresh_token == mock.sentinel.refresh_token - - mock_fetch_token.assert_called_with( - CLIENT_SECRETS_INFO['web']['token_uri'], - client_secret=CLIENT_SECRETS_INFO['web']['client_secret'], - code=mock.sentinel.code) - - @mock.patch('google_auth_oauthlib.flow.webbrowser', autospec=True) - def test_run_local_server( - self, webbrowser_mock, instance, mock_fetch_token): - auth_redirect_url = urllib.parse.urljoin( - 'http://localhost:8080', - self.REDIRECT_REQUEST_PATH) - - with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool: - future = pool.submit(instance.run_local_server) - - while not future.done(): - try: - requests.get(auth_redirect_url) - except requests.ConnectionError: # pragma: NO COVER - pass - - credentials = future.result() - - assert credentials.token == mock.sentinel.access_token - assert credentials._refresh_token == mock.sentinel.refresh_token - assert webbrowser_mock.open.called - - expected_auth_response = auth_redirect_url.replace('http', 'https') - mock_fetch_token.assert_called_with( - CLIENT_SECRETS_INFO['web']['token_uri'], - client_secret=CLIENT_SECRETS_INFO['web']['client_secret'], - authorization_response=expected_auth_response) - - @mock.patch('google_auth_oauthlib.flow.webbrowser', autospec=True) - @mock.patch('wsgiref.simple_server.make_server', autospec=True) - def test_run_local_server_no_browser( - self, make_server_mock, webbrowser_mock, instance, - mock_fetch_token): - - def assign_last_request_uri(host, port, wsgi_app, **kwargs): - wsgi_app.last_request_uri = self.REDIRECT_REQUEST_PATH - return mock.Mock() - - make_server_mock.side_effect = assign_last_request_uri - - instance.run_local_server(open_browser=False) - - assert not webbrowser_mock.open.called diff --git a/packages/google-auth/additional_packages/google_auth_oauthlib/tests/test_helpers.py b/packages/google-auth/additional_packages/google_auth_oauthlib/tests/test_helpers.py deleted file mode 100644 index faae76bc4e80..000000000000 --- a/packages/google-auth/additional_packages/google_auth_oauthlib/tests/test_helpers.py +++ /dev/null @@ -1,92 +0,0 @@ -# Copyright 2017 Google Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import json -import os - -import mock -import pytest - -from google_auth_oauthlib import helpers - -DATA_DIR = os.path.join(os.path.dirname(__file__), 'data') -CLIENT_SECRETS_FILE = os.path.join(DATA_DIR, 'client_secrets.json') - -with open(CLIENT_SECRETS_FILE, 'r') as fh: - CLIENT_SECRETS_INFO = json.load(fh) - - -def test_session_from_client_config_web(): - session, config = helpers.session_from_client_config( - CLIENT_SECRETS_INFO, scopes=mock.sentinel.scopes) - - assert config == CLIENT_SECRETS_INFO - assert session.client_id == CLIENT_SECRETS_INFO['web']['client_id'] - assert session.scope == mock.sentinel.scopes - - -def test_session_from_client_config_installed(): - info = {'installed': CLIENT_SECRETS_INFO['web']} - session, config = helpers.session_from_client_config( - info, scopes=mock.sentinel.scopes) - assert config == info - assert session.client_id == info['installed']['client_id'] - assert session.scope == mock.sentinel.scopes - - -def test_session_from_client_config_bad_format(): - with pytest.raises(ValueError): - helpers.session_from_client_config({}, scopes=[]) - - -def test_session_from_client_config_missing_keys(): - with pytest.raises(ValueError): - helpers.session_from_client_config({'web': {}}, scopes=[]) - - -def test_session_from_client_secrets_file(): - session, config = helpers.session_from_client_secrets_file( - CLIENT_SECRETS_FILE, scopes=mock.sentinel.scopes) - assert config == CLIENT_SECRETS_INFO - assert session.client_id == CLIENT_SECRETS_INFO['web']['client_id'] - assert session.scope == mock.sentinel.scopes - - -@pytest.fixture -def session(): - session, _ = helpers.session_from_client_config( - CLIENT_SECRETS_INFO, scopes=mock.sentinel.scopes) - yield session - - -def test_credentials_from_session(session): - session.token = { - 'access_token': mock.sentinel.access_token, - 'refresh_token': mock.sentinel.refresh_token - } - - credentials = helpers.credentials_from_session( - session, CLIENT_SECRETS_INFO['web']) - - assert credentials.token == mock.sentinel.access_token - assert credentials._refresh_token == mock.sentinel.refresh_token - assert credentials._client_id == CLIENT_SECRETS_INFO['web']['client_id'] - assert (credentials._client_secret == - CLIENT_SECRETS_INFO['web']['client_secret']) - assert credentials._token_uri == CLIENT_SECRETS_INFO['web']['token_uri'] - - -def test_bad_credentials(session): - with pytest.raises(ValueError): - helpers.credentials_from_session(session) diff --git a/packages/google-auth/additional_packages/google_auth_oauthlib/tox.ini b/packages/google-auth/additional_packages/google_auth_oauthlib/tox.ini deleted file mode 100644 index c0983f601207..000000000000 --- a/packages/google-auth/additional_packages/google_auth_oauthlib/tox.ini +++ /dev/null @@ -1,36 +0,0 @@ -[tox] -envlist = lint,py27,py34,py35,py36,pypy,cover - -[testenv] -deps = - mock - pytest - pytest-cov - futures -commands = - py.test --cov=google_auth_oauthlib --cov=tests {posargs:tests} - -[testenv:cover] -basepython = python3.6 -commands = - py.test --cov=google_auth_oauthlib --cov=tests --cov-report= tests - coverage report --show-missing --fail-under=100 -deps = - {[testenv]deps} - -[testenv:lint] -basepython = python3.5 -commands = - python setup.py check --metadata --restructuredtext --strict - flake8 \ - --import-order-style=google \ - --application-import-names="google_auth_oauthlib,tests" \ - google_auth_oauthlib tests - python {toxinidir}/../../scripts/run_pylint.py \ - --library-filesets google_auth_oauthlib \ - --test-filesets tests -deps = - flake8 - flake8-import-order - pylint - docutils From a10a36e68f8391778bbc11c6b5f0ae04cde48142 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Mon, 17 Apr 2017 13:13:13 -0700 Subject: [PATCH 131/966] Release v1.0.0 (#156) --- packages/google-auth/CHANGELOG.rst | 6 ++++++ packages/google-auth/README.rst | 2 +- packages/google-auth/setup.py | 4 ++-- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/google-auth/CHANGELOG.rst b/packages/google-auth/CHANGELOG.rst index 43e282eede51..683896db79d2 100644 --- a/packages/google-auth/CHANGELOG.rst +++ b/packages/google-auth/CHANGELOG.rst @@ -1,6 +1,12 @@ Changelog ========= +v1.0.0 +------ + +Milestone release for v1.0.0. +No significant changes since v0.10.0 + v0.10.0 ------- diff --git a/packages/google-auth/README.rst b/packages/google-auth/README.rst index 5a6b188abde4..4be0c5d21c27 100644 --- a/packages/google-auth/README.rst +++ b/packages/google-auth/README.rst @@ -32,7 +32,7 @@ Maintainers - `@jonparrott `_ (Jon Wayne Parrott) - `@dhermes `_ (Danny Hermes) -- `@bjwatson `_ (Brian Watson) +- `@lukesneeringer `_ (Luke Sneeringer) Contributing ------------ diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 9ec7bfed4135..c532378db6c4 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -32,7 +32,7 @@ setup( name='google-auth', - version='0.10.0', + version='1.0.0', author='Google Cloud Platform', author_email='jonwayne+google-auth@google.com', description='Google Authentication Library', @@ -50,7 +50,7 @@ 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', - 'Development Status :: 4 - Beta', + 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: Apache Software License', 'Operating System :: POSIX', From 85361735b0b823a28cbec6f1b2d6541b5642342a Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Mon, 8 May 2017 09:40:56 -0700 Subject: [PATCH 132/966] Fix clock skew calculations (#158) Previously, the clock skew adjusted in the wrong direction. It would cause us to consider credentials whose expiration time had already pass according to the system clock to still be sent to the server. This is the opposite of what we wanted to happen. This fixes it so that we report that credentials are expired slightly before the system clock thinks they've expired. --- packages/google-auth/google/auth/credentials.py | 11 +++++++---- packages/google-auth/tests/test_app_engine.py | 4 ++-- packages/google-auth/tests/test_credentials.py | 16 +++++++--------- packages/google-auth/tests/test_iam.py | 3 ++- .../google-auth/tests/transport/test_grpc.py | 3 ++- 5 files changed, 20 insertions(+), 17 deletions(-) diff --git a/packages/google-auth/google/auth/credentials.py b/packages/google-auth/google/auth/credentials.py index 6fb89d8302e1..74d6788217f9 100644 --- a/packages/google-auth/google/auth/credentials.py +++ b/packages/google-auth/google/auth/credentials.py @@ -56,10 +56,13 @@ def expired(self): Note that credentials can be invalid but not expired becaue Credentials with :attr:`expiry` set to None is considered to never expire. """ - # Err on the side of reporting expiration early so that we avoid - # the 403-refresh-retry loop. - adjusted_now = _helpers.utcnow() - _helpers.CLOCK_SKEW - return self.expiry is not None and self.expiry <= adjusted_now + if not self.expiry: + return False + + # Remove 5 minutes from expiry to err on the side of reporting + # expiration early so that we avoid the 401-refresh-retry loop. + skewed_expiry = self.expiry - _helpers.CLOCK_SKEW + return _helpers.utcnow() >= skewed_expiry @property def valid(self): diff --git a/packages/google-auth/tests/test_app_engine.py b/packages/google-auth/tests/test_app_engine.py index 136a914c9933..2b957f0dadb3 100644 --- a/packages/google-auth/tests/test_app_engine.py +++ b/packages/google-auth/tests/test_app_engine.py @@ -112,10 +112,10 @@ def test_service_account_email_explicit(self, app_identity_mock): @mock.patch( 'google.auth._helpers.utcnow', - return_value=datetime.datetime.min + _helpers.CLOCK_SKEW) + return_value=datetime.datetime.min) def test_refresh(self, now_mock, app_identity_mock): token = 'token' - ttl = 100 + ttl = _helpers.CLOCK_SKEW_SECS + 100 app_identity_mock.get_access_token.return_value = token, ttl credentials = app_engine.Credentials(scopes=['email']) diff --git a/packages/google-auth/tests/test_credentials.py b/packages/google-auth/tests/test_credentials.py index c2ff844a4441..b5a540dd8304 100644 --- a/packages/google-auth/tests/test_credentials.py +++ b/packages/google-auth/tests/test_credentials.py @@ -38,21 +38,19 @@ def test_expired_and_valid(): assert credentials.valid assert not credentials.expired - # Set the expiration in the past, but because of clock skew accomodation - # the credentials should still be valid. + # Set the expiration to one second more than now plus the clock skew + # accomodation. These credentials should be valid. credentials.expiry = ( - datetime.datetime.utcnow() - + datetime.datetime.utcnow() + + _helpers.CLOCK_SKEW + datetime.timedelta(seconds=1)) assert credentials.valid assert not credentials.expired - # Set the credentials far enough in the past to exceed the clock skew - # accomodation. They should now be expired. - credentials.expiry = ( - datetime.datetime.utcnow() - - _helpers.CLOCK_SKEW - - datetime.timedelta(seconds=1)) + # Set the credentials expiration to now. Because of the clock skew + # accomodation, these credentials should report as expired. + credentials.expiry = datetime.datetime.utcnow() assert not credentials.valid assert credentials.expired diff --git a/packages/google-auth/tests/test_iam.py b/packages/google-auth/tests/test_iam.py index f7767887bfa7..97fa90755069 100644 --- a/packages/google-auth/tests/test_iam.py +++ b/packages/google-auth/tests/test_iam.py @@ -20,6 +20,7 @@ import pytest from six.moves import http_client +from google.auth import _helpers from google.auth import exceptions from google.auth import iam from google.auth import transport @@ -42,7 +43,7 @@ def __init__(self): super(CredentialsImpl, self).__init__() self.token = 'token' # Force refresh - self.expiry = datetime.datetime.min + self.expiry = datetime.datetime.min + _helpers.CLOCK_SKEW def refresh(self, request): pass diff --git a/packages/google-auth/tests/transport/test_grpc.py b/packages/google-auth/tests/transport/test_grpc.py index 7a3cc0a7f7b7..a494ee518de0 100644 --- a/packages/google-auth/tests/transport/test_grpc.py +++ b/packages/google-auth/tests/transport/test_grpc.py @@ -17,6 +17,7 @@ import mock import pytest +from google.auth import _helpers from google.auth import credentials try: import google.auth.transport.grpc @@ -56,7 +57,7 @@ def test_call_no_refresh(self): def test_call_refresh(self): credentials = MockCredentials() - credentials.expiry = datetime.datetime.min + credentials.expiry = datetime.datetime.min + _helpers.CLOCK_SKEW request = mock.Mock() plugin = google.auth.transport.grpc.AuthMetadataPlugin( From ad3af6d73550fcd2f69cd2a82bec1a89b02ea044 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Mon, 8 May 2017 10:05:11 -0700 Subject: [PATCH 133/966] Release v1.0.1 (#159) --- packages/google-auth/CHANGELOG.rst | 5 +++++ packages/google-auth/setup.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.rst b/packages/google-auth/CHANGELOG.rst index 683896db79d2..c1c019fb2e7b 100644 --- a/packages/google-auth/CHANGELOG.rst +++ b/packages/google-auth/CHANGELOG.rst @@ -1,6 +1,11 @@ Changelog ========= +v1.0.1 +------ + +- Fixed a bug in the clock skew accommodation logic where expired credentials could be used for up to 5 minutes. (#158) + v1.0.0 ------ diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index c532378db6c4..59ba3de24d5b 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -32,7 +32,7 @@ setup( name='google-auth', - version='1.0.0', + version='1.0.1', author='Google Cloud Platform', author_email='jonwayne+google-auth@google.com', description='Google Authentication Library', From d29b9e26157cd79bd0b36eb79e524af85adad25c Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Wed, 7 Jun 2017 09:41:06 -0700 Subject: [PATCH 134/966] Fix lint issues caught by new pylint (#166) --- packages/google-auth/google/auth/transport/urllib3.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/transport/urllib3.py b/packages/google-auth/google/auth/transport/urllib3.py index a08f4275c571..0dfe9130996c 100644 --- a/packages/google-auth/google/auth/transport/urllib3.py +++ b/packages/google-auth/google/auth/transport/urllib3.py @@ -178,16 +178,22 @@ def __init__(self, credentials, http=None, if http is None: http = _make_default_http() - self.http = http self.credentials = credentials + self.http = http self._refresh_status_codes = refresh_status_codes self._max_refresh_attempts = max_refresh_attempts # Request instance used by internal methods (for example, # credentials.refresh). self._request = Request(self.http) + super(AuthorizedHttp, self).__init__() + def urlopen(self, method, url, body=None, headers=None, **kwargs): """Implementation of urllib3's urlopen.""" + # pylint: disable=arguments-differ + # We use kwargs to collect additional args that we don't need to + # introspect here. However, we do explicitly collect the two + # positional arguments. # Use a kwarg for this instead of an attribute to maintain # thread-safety. From 45497899ec593b4624dc6444ec1f750dc46fa016 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Wed, 7 Jun 2017 09:47:18 -0700 Subject: [PATCH 135/966] Add documentation around oauth2client deprecation (#165) --- packages/google-auth/docs/index.rst | 2 + .../docs/oauth2client-deprecation.rst | 117 ++++++++++++++++++ 2 files changed, 119 insertions(+) create mode 100644 packages/google-auth/docs/oauth2client-deprecation.rst diff --git a/packages/google-auth/docs/index.rst b/packages/google-auth/docs/index.rst index c0cbdbfab4c4..b6dc92c7bfc1 100644 --- a/packages/google-auth/docs/index.rst +++ b/packages/google-auth/docs/index.rst @@ -22,6 +22,8 @@ also provides integration with several HTTP libraries. :mod:`urllib3 `, and :mod:`gRPC `. +.. note:: ``oauth2client`` was recently deprecated in favor of this library. For more details on the deprecation, see :doc:`oauth2client-deprecation`. + Installing ---------- diff --git a/packages/google-auth/docs/oauth2client-deprecation.rst b/packages/google-auth/docs/oauth2client-deprecation.rst new file mode 100644 index 000000000000..2802c3e31e5c --- /dev/null +++ b/packages/google-auth/docs/oauth2client-deprecation.rst @@ -0,0 +1,117 @@ +:orphan: + +oauth2client deprecation +======================== + +This page is intended for existing users of the `oauth2client`_ who want to +understand the reasons for its deprecation and how this library relates to +``oauth2client``. + +.. _oauth2client: https://github.com/google/oauth2client + +Reasons for deprecation +----------------------- + +#. Lack of ownership: ``oauth2client`` has lacked a definitive owner since + around 2013. +#. Fragile and ad-hoc design: ``oauth2client`` is the result of several years + of ad-hoc additions and organic, uncontrolled growth. This has led to a + library that lacks overall design and cohesion. The convoluted class + hierarchy is a symptom of this. +#. Lack of a secure, thread-safe, and modern transport: ``oauth2client`` is + inextricably dependent on `httplib2`_. ``httplib2`` is largely unmaintained + (although recently there are a small group of volunteers attempting to + maintain it). +#. Lack of clear purpose and goals: The library is named "oauth2client" but is + actually a pretty poor OAuth 2.0 client and does a lot of things that have + nothing to do with OAuth and its related RFCs. + +.. _httplib2: https://github.com/httplib2/httplib2 + +We originally planned to address these issues within ``oauth2client``, however, +we determined that the number of breaking changes needed would be absolutely +untenable for downstream users. It would essentially involve our users having +to rewrite significant portions of their code if they needed to upgrade (either +directly or indirectly through a dependency). Instead, we've chosen to create a +new replacement library that can live side-by-side with ``oauth2client`` and +allow users to migrate gradually. We believe that this was the least painful +option. + +Replacement +----------- + +The long-term replacement for ``oauth2client`` is this library, +``google-auth``. This library addresses the major issues with oauthclient: + +#. Clear ownership: google-auth is owned by the teams that maintain the + `Cloud Client Libraries`_, `gRPC`_, and the + `Code Samples for Google Cloud Platform`_. +#. Thought-out design: using the lessons learned from ``oauth2client``, we have + designed a better module and class hierarchy. The ``v1.0.0`` release of this + library should provide long-term API stability. +#. Modern, secure, and extensible transports: ``google-auth`` supports + `urllib3`_, `requests`_, `gRPC`_, and has `legacy support for httplib2`_ to + help clients migration. It is transport agnostic and has explicit support + for adding new transports. +#. Clear purpose and goals: ``google-auth`` is explicitly focused on + Google-specific authentication, especially the server-to-server (service + account) use case. + +Because we reduced the scope of the library, there are several features in +``oauth2client`` we intentionally are not supporting in the ``v1.0.0`` release +of ``google-auth``. This does not mean we are not interested in supporting +these features, we just didn't feel they should be part of the initial API. +As downstream users ask for these features we will determine the best way to +serve those use cases without allowing the library to become a dumping ground. + +The unsupported features are: + +#. Support for obtaining user credentials. While this library has support for + using user credentials, there are no provisions in the core library for + doing the three-party OAuth 2.0 flow to obtain authorization from a user. + Instead, we are opting to provide a separate package that does integration + with `oauthlib`_, `google-auth-oauthlib`_. When that library has a stable + API, we will consider its inclusion into the core library. +#. Support for storing credentials. The only credentials type that needs to + be stored are user credentials. We have a `discussion thread`_ on what level + of support we should do. It's very likely we'll choose to provide an + abstract interface for this and leave it up to application to provide + storage implementation specific to their use case. It's also very likely + that we will also incubate this functionality in the + `google-auth-oauthlib`_ library before including it in the core library. + +.. _Cloud Client Libraries: https://github.com/googlecloudplatform/google-cloud-python +.. _gRPC: http://www.grpc.io/ +.. _Code Samples for Google Cloud Platform: https://github.com/googlecloudplatform/python-docs-samples +.. _urllib3: https://urllib3.readthedocs.io +.. _requests: http://python-requests.org +.. _legacy support for httplib2: https://pypi.python.org/pypi/google-auth-httplib2 +.. _oauthlib: https://oauthlib.readthedocs.io +.. _google-auth-oauthlib: http://google-auth-oauthlib.readthedocs.io/ +.. _discussion thread: https://github.com/GoogleCloudPlatform/google-auth-library-python/issues/33 + + +Post-deprecation support +------------------------ + +While ``oauth2client`` will not be implementing or accepting any new features, +the ``google-auth`` team will continue to accept bug reports and fix critical +bugs. We will make patch releases as necessary. We have no plans to remove the +library from GitHub or PyPI. Also, we have made sure that the +`google-api-python-client`_ library supports oauth2client and google-auth and +will continue to do so indefinitely. + +It is important to note that we will not be adding any features, even if an +external user goes through the trouble of sending a pull request. This policy +is in place because without it we will perpetuate the circumstances that led +to ``oauth2client`` being in the semi-unmaintained state it was in previously. + +Some old documentation and examples may use ``oauth2client`` instead of +``google-auth``. We are working to update all of these but it does take a +significant amount of time. Since we are still iterating on user auth, some +samples that use user auth will not be updated until we have settled on a final +interface. If you find any samples you feel should be updated, please +`file a bug`_. + +.. _google-api-python-client: https://github.com/google/google-api-python-client +.. _file a bug: https://github.com/GoogleCloudPlatform/google-auth-library-python/issues From 2d4a8b6f8bafe0e74478a4fffd40ceceeb048a59 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Fri, 30 Jun 2017 10:25:08 -0700 Subject: [PATCH 136/966] Make testing style more consistent (#168) Several overall style changes: 1. Avoid plain `Mock()` and `Mock(spec=thing)`., prefer `mock.create_autospec()`. 1. Don't `mock.patch` without `autospec`. 1. Don't give mock instances special names. Prefer `thing` over `thing_mock` and `mock_thing`. 1. When using `mock.patch`, use the same name as the item being patched to refer to the mock. ```python with mock.patch('module.thing') as thing: ... ``` and ``` @mock.patch('module.thing') def test(thing): ... ``` 1. Test helper factories should follow the naming convention `make_thing()`. 1. Use `ThingStub` when creating semi-functioning subclasses for testing purposes. --- .../tests/compute_engine/test__metadata.py | 114 +++++++++--------- .../tests/compute_engine/test_credentials.py | 22 ++-- .../google-auth/tests/oauth2/test__client.py | 40 +++--- .../tests/oauth2/test_credentials.py | 54 +++++---- .../google-auth/tests/oauth2/test_id_token.py | 8 +- .../tests/oauth2/test_service_account.py | 86 +++++++------ packages/google-auth/tests/test__cloud_sdk.py | 45 +++---- packages/google-auth/tests/test__default.py | 77 ++++++------ .../google-auth/tests/test__oauth2client.py | 8 +- packages/google-auth/tests/test_app_engine.py | 73 ++++++----- packages/google-auth/tests/test_iam.py | 9 +- .../google-auth/tests/transport/test_grpc.py | 40 +++--- .../tests/transport/test_requests.py | 66 +++++----- .../tests/transport/test_urllib3.py | 63 +++++----- 14 files changed, 377 insertions(+), 328 deletions(-) diff --git a/packages/google-auth/tests/compute_engine/test__metadata.py b/packages/google-auth/tests/compute_engine/test__metadata.py index 0adf882525bf..bf48882cad5d 100644 --- a/packages/google-auth/tests/compute_engine/test__metadata.py +++ b/packages/google-auth/tests/compute_engine/test__metadata.py @@ -24,103 +24,101 @@ from google.auth import _helpers from google.auth import environment_vars from google.auth import exceptions +from google.auth import transport from google.auth.compute_engine import _metadata PATH = 'instance/service-accounts/default' -@pytest.fixture -def mock_request(): - request_mock = mock.Mock() +def make_request(data, status=http_client.OK, headers=None): + response = mock.create_autospec(transport.Response, instance=True) + response.status = status + response.data = _helpers.to_bytes(data) + response.headers = headers or {} - def set_response(data, status=http_client.OK, headers=None): - response = mock.Mock() - response.status = status - response.data = _helpers.to_bytes(data) - response.headers = headers or {} - request_mock.return_value = response - return request_mock + request = mock.create_autospec(transport.Request) + request.return_value = response - yield set_response + return request -def test_ping_success(mock_request): - request_mock = mock_request('', headers=_metadata._METADATA_HEADERS) +def test_ping_success(): + request = make_request('', headers=_metadata._METADATA_HEADERS) - assert _metadata.ping(request_mock) + assert _metadata.ping(request) - request_mock.assert_called_once_with( + request.assert_called_once_with( method='GET', url=_metadata._METADATA_IP_ROOT, headers=_metadata._METADATA_HEADERS, timeout=_metadata._METADATA_DEFAULT_TIMEOUT) -def test_ping_failure_bad_flavor(mock_request): - request_mock = mock_request( +def test_ping_failure_bad_flavor(): + request = make_request( '', headers={_metadata._METADATA_FLAVOR_HEADER: 'meep'}) - assert not _metadata.ping(request_mock) + assert not _metadata.ping(request) -def test_ping_failure_connection_failed(mock_request): - request_mock = mock_request('') - request_mock.side_effect = exceptions.TransportError() +def test_ping_failure_connection_failed(): + request = make_request('') + request.side_effect = exceptions.TransportError() - assert not _metadata.ping(request_mock) + assert not _metadata.ping(request) -def test_ping_success_custom_root(mock_request): - request_mock = mock_request('', headers=_metadata._METADATA_HEADERS) +def test_ping_success_custom_root(): + request = make_request('', headers=_metadata._METADATA_HEADERS) fake_ip = '1.2.3.4' os.environ[environment_vars.GCE_METADATA_IP] = fake_ip reload_module(_metadata) try: - assert _metadata.ping(request_mock) + assert _metadata.ping(request) finally: del os.environ[environment_vars.GCE_METADATA_IP] reload_module(_metadata) - request_mock.assert_called_once_with( + request.assert_called_once_with( method='GET', url='http://' + fake_ip, headers=_metadata._METADATA_HEADERS, timeout=_metadata._METADATA_DEFAULT_TIMEOUT) -def test_get_success_json(mock_request): +def test_get_success_json(): key, value = 'foo', 'bar' data = json.dumps({key: value}) - request_mock = mock_request( + request = make_request( data, headers={'content-type': 'application/json'}) - result = _metadata.get(request_mock, PATH) + result = _metadata.get(request, PATH) - request_mock.assert_called_once_with( + request.assert_called_once_with( method='GET', url=_metadata._METADATA_ROOT + PATH, headers=_metadata._METADATA_HEADERS) assert result[key] == value -def test_get_success_text(mock_request): +def test_get_success_text(): data = 'foobar' - request_mock = mock_request(data, headers={'content-type': 'text/plain'}) + request = make_request(data, headers={'content-type': 'text/plain'}) - result = _metadata.get(request_mock, PATH) + result = _metadata.get(request, PATH) - request_mock.assert_called_once_with( + request.assert_called_once_with( method='GET', url=_metadata._METADATA_ROOT + PATH, headers=_metadata._METADATA_HEADERS) assert result == data -def test_get_success_custom_root(mock_request): - request_mock = mock_request( +def test_get_success_custom_root(): + request = make_request( '{}', headers={'content-type': 'application/json'}) fake_root = 'another.metadata.service' @@ -128,55 +126,55 @@ def test_get_success_custom_root(mock_request): reload_module(_metadata) try: - _metadata.get(request_mock, PATH) + _metadata.get(request, PATH) finally: del os.environ[environment_vars.GCE_METADATA_ROOT] reload_module(_metadata) - request_mock.assert_called_once_with( + request.assert_called_once_with( method='GET', url='http://{}/computeMetadata/v1/{}'.format(fake_root, PATH), headers=_metadata._METADATA_HEADERS) -def test_get_failure(mock_request): - request_mock = mock_request( +def test_get_failure(): + request = make_request( 'Metadata error', status=http_client.NOT_FOUND) with pytest.raises(exceptions.TransportError) as excinfo: - _metadata.get(request_mock, PATH) + _metadata.get(request, PATH) assert excinfo.match(r'Metadata error') - request_mock.assert_called_once_with( + request.assert_called_once_with( method='GET', url=_metadata._METADATA_ROOT + PATH, headers=_metadata._METADATA_HEADERS) -def test_get_failure_bad_json(mock_request): - request_mock = mock_request( +def test_get_failure_bad_json(): + request = make_request( '{', headers={'content-type': 'application/json'}) with pytest.raises(exceptions.TransportError) as excinfo: - _metadata.get(request_mock, PATH) + _metadata.get(request, PATH) assert excinfo.match(r'invalid JSON') - request_mock.assert_called_once_with( + request.assert_called_once_with( method='GET', url=_metadata._METADATA_ROOT + PATH, headers=_metadata._METADATA_HEADERS) -def test_get_project_id(mock_request): +def test_get_project_id(): project = 'example-project' - request_mock = mock_request( + request = make_request( project, headers={'content-type': 'text/plain'}) - project_id = _metadata.get_project_id(request_mock) + project_id = _metadata.get_project_id(request) - request_mock.assert_called_once_with( + request.assert_called_once_with( method='GET', url=_metadata._METADATA_ROOT + 'project/project-id', headers=_metadata._METADATA_HEADERS) @@ -184,15 +182,15 @@ def test_get_project_id(mock_request): @mock.patch('google.auth._helpers.utcnow', return_value=datetime.datetime.min) -def test_get_service_account_token(utcnow, mock_request): +def test_get_service_account_token(utcnow): ttl = 500 - request_mock = mock_request( + request = make_request( json.dumps({'access_token': 'token', 'expires_in': ttl}), headers={'content-type': 'application/json'}) - token, expiry = _metadata.get_service_account_token(request_mock) + token, expiry = _metadata.get_service_account_token(request) - request_mock.assert_called_once_with( + request.assert_called_once_with( method='GET', url=_metadata._METADATA_ROOT + PATH + '/token', headers=_metadata._METADATA_HEADERS) @@ -200,15 +198,15 @@ def test_get_service_account_token(utcnow, mock_request): assert expiry == utcnow() + datetime.timedelta(seconds=ttl) -def test_get_service_account_info(mock_request): +def test_get_service_account_info(): key, value = 'foo', 'bar' - request_mock = mock_request( + request = make_request( json.dumps({key: value}), headers={'content-type': 'application/json'}) - info = _metadata.get_service_account_info(request_mock) + info = _metadata.get_service_account_info(request) - request_mock.assert_called_once_with( + request.assert_called_once_with( method='GET', url=_metadata._METADATA_ROOT + PATH + '/?recursive=true', headers=_metadata._METADATA_HEADERS) diff --git a/packages/google-auth/tests/compute_engine/test_credentials.py b/packages/google-auth/tests/compute_engine/test_credentials.py index 2564da961352..732cb419ccd7 100644 --- a/packages/google-auth/tests/compute_engine/test_credentials.py +++ b/packages/google-auth/tests/compute_engine/test_credentials.py @@ -19,6 +19,7 @@ from google.auth import _helpers from google.auth import exceptions +from google.auth import transport from google.auth.compute_engine import credentials @@ -41,9 +42,9 @@ def test_default_state(self): @mock.patch( 'google.auth._helpers.utcnow', return_value=datetime.datetime.min + _helpers.CLOCK_SKEW) - @mock.patch('google.auth.compute_engine._metadata.get') - def test_refresh_success(self, get_mock, now_mock): - get_mock.side_effect = [{ + @mock.patch('google.auth.compute_engine._metadata.get', autospec=True) + def test_refresh_success(self, get, utcnow): + get.side_effect = [{ # First request is for sevice account info. 'email': 'service-account@example.com', 'scopes': ['one', 'two'] @@ -59,7 +60,7 @@ def test_refresh_success(self, get_mock, now_mock): # Check that the credentials have the token and proper expiration assert self.credentials.token == 'token' assert self.credentials.expiry == ( - now_mock() + datetime.timedelta(seconds=500)) + utcnow() + datetime.timedelta(seconds=500)) # Check the credential info assert (self.credentials.service_account_email == @@ -71,8 +72,8 @@ def test_refresh_success(self, get_mock, now_mock): assert self.credentials.valid @mock.patch('google.auth.compute_engine._metadata.get', autospec=True) - def test_refresh_error(self, get_mock): - get_mock.side_effect = exceptions.TransportError('http error') + def test_refresh_error(self, get): + get.side_effect = exceptions.TransportError('http error') with pytest.raises(exceptions.RefreshError) as excinfo: self.credentials.refresh(None) @@ -80,8 +81,8 @@ def test_refresh_error(self, get_mock): assert excinfo.match(r'http error') @mock.patch('google.auth.compute_engine._metadata.get', autospec=True) - def test_before_request_refreshes(self, get_mock): - get_mock.side_effect = [{ + def test_before_request_refreshes(self, get): + get.side_effect = [{ # First request is for sevice account info. 'email': 'service-account@example.com', 'scopes': 'one two' @@ -95,11 +96,12 @@ def test_before_request_refreshes(self, get_mock): assert not self.credentials.valid # before_request should cause a refresh + request = mock.create_autospec(transport.Request, instance=True) self.credentials.before_request( - mock.Mock(), 'GET', 'http://example.com?a=1#3', {}) + request, 'GET', 'http://example.com?a=1#3', {}) # The refresh endpoint should've been called. - assert get_mock.called + assert get.called # Credentials should now be valid. assert self.credentials.valid diff --git a/packages/google-auth/tests/oauth2/test__client.py b/packages/google-auth/tests/oauth2/test__client.py index 8c19c3ee98c1..6aeb3d13ba33 100644 --- a/packages/google-auth/tests/oauth2/test__client.py +++ b/packages/google-auth/tests/oauth2/test__client.py @@ -22,6 +22,7 @@ from six.moves import urllib from google.auth import exceptions +from google.auth import transport from google.oauth2 import _client @@ -46,7 +47,7 @@ def test__handle_error_response_non_json(): @mock.patch('google.auth._helpers.utcnow', return_value=datetime.datetime.min) -def test__parse_expiry(now_mock): +def test__parse_expiry(unused_utcnow): result = _client._parse_expiry({'expires_in': 500}) assert result == datetime.datetime.min + datetime.timedelta(seconds=500) @@ -55,15 +56,17 @@ def test__parse_expiry_none(): assert _client._parse_expiry({}) is None -def _make_request(response_data): - response = mock.Mock() - response.status = http_client.OK +def make_request(response_data, status=http_client.OK): + response = mock.create_autospec(transport.Response, instance=True) + response.status = status response.data = json.dumps(response_data).encode('utf-8') - return mock.Mock(return_value=response) + request = mock.create_autospec(transport.Request) + request.return_value = response + return request def test__token_endpoint_request(): - request = _make_request({'test': 'response'}) + request = make_request({'test': 'response'}) result = _client._token_endpoint_request( request, 'http://example.com', {'test': 'params'}) @@ -80,16 +83,13 @@ def test__token_endpoint_request(): def test__token_endpoint_request_error(): - response = mock.Mock() - response.status = http_client.BAD_REQUEST - response.data = b'Error' - request = mock.Mock(return_value=response) + request = make_request({}, status=http_client.BAD_REQUEST) with pytest.raises(exceptions.RefreshError): _client._token_endpoint_request(request, 'http://example.com', {}) -def _verify_request_params(request, params): +def verify_request_params(request, params): request_body = request.call_args[1]['body'] request_params = urllib.parse.parse_qs(request_body) @@ -98,8 +98,8 @@ def _verify_request_params(request, params): @mock.patch('google.auth._helpers.utcnow', return_value=datetime.datetime.min) -def test_jwt_grant(now_mock): - request = _make_request({ +def test_jwt_grant(utcnow): + request = make_request({ 'access_token': 'token', 'expires_in': 500, 'extra': 'data'}) @@ -108,19 +108,19 @@ def test_jwt_grant(now_mock): request, 'http://example.com', 'assertion_value') # Check request call - _verify_request_params(request, { + verify_request_params(request, { 'grant_type': _client._JWT_GRANT_TYPE, 'assertion': 'assertion_value' }) # Check result assert token == 'token' - assert expiry == datetime.datetime.min + datetime.timedelta(seconds=500) + assert expiry == utcnow() + datetime.timedelta(seconds=500) assert extra_data['extra'] == 'data' def test_jwt_grant_no_access_token(): - request = _make_request({ + request = make_request({ # No access token. 'expires_in': 500, 'extra': 'data'}) @@ -130,8 +130,8 @@ def test_jwt_grant_no_access_token(): @mock.patch('google.auth._helpers.utcnow', return_value=datetime.datetime.min) -def test_refresh_grant(now_mock): - request = _make_request({ +def test_refresh_grant(unused_utcnow): + request = make_request({ 'access_token': 'token', 'refresh_token': 'new_refresh_token', 'expires_in': 500, @@ -142,7 +142,7 @@ def test_refresh_grant(now_mock): 'client_secret') # Check request call - _verify_request_params(request, { + verify_request_params(request, { 'grant_type': _client._REFRESH_GRANT_TYPE, 'refresh_token': 'refresh_token', 'client_id': 'client_id', @@ -157,7 +157,7 @@ def test_refresh_grant(now_mock): def test_refresh_grant_no_access_token(): - request = _make_request({ + request = make_request({ # No access token. 'refresh_token': 'new_refresh_token', 'expires_in': 500, diff --git a/packages/google-auth/tests/oauth2/test_credentials.py b/packages/google-auth/tests/oauth2/test_credentials.py index e15766a89e9b..14984a1dbaa3 100644 --- a/packages/google-auth/tests/oauth2/test_credentials.py +++ b/packages/google-auth/tests/oauth2/test_credentials.py @@ -18,6 +18,7 @@ import pytest from google.auth import _helpers +from google.auth import transport from google.oauth2 import credentials @@ -26,40 +27,41 @@ class TestCredentials(object): REFRESH_TOKEN = 'refresh_token' CLIENT_ID = 'client_id' CLIENT_SECRET = 'client_secret' - credentials = None - @pytest.fixture(autouse=True) - def credentials_fixture(self): - self.credentials = credentials.Credentials( - token=None, refresh_token=self.REFRESH_TOKEN, - token_uri=self.TOKEN_URI, client_id=self.CLIENT_ID, - client_secret=self.CLIENT_SECRET) + @classmethod + def make_credentials(cls): + return credentials.Credentials( + token=None, refresh_token=cls.REFRESH_TOKEN, + token_uri=cls.TOKEN_URI, client_id=cls.CLIENT_ID, + client_secret=cls.CLIENT_SECRET) def test_default_state(self): - assert not self.credentials.valid + credentials = self.make_credentials() + assert not credentials.valid # Expiration hasn't been set yet - assert not self.credentials.expired + assert not credentials.expired # Scopes aren't required for these credentials - assert not self.credentials.requires_scopes + assert not credentials.requires_scopes # Test properties - assert self.credentials.refresh_token == self.REFRESH_TOKEN - assert self.credentials.token_uri == self.TOKEN_URI - assert self.credentials.client_id == self.CLIENT_ID - assert self.credentials.client_secret == self.CLIENT_SECRET + assert credentials.refresh_token == self.REFRESH_TOKEN + assert credentials.token_uri == self.TOKEN_URI + assert credentials.client_id == self.CLIENT_ID + assert credentials.client_secret == self.CLIENT_SECRET def test_create_scoped(self): + credentials = self.make_credentials() with pytest.raises(NotImplementedError): - self.credentials.with_scopes(['email']) + credentials.with_scopes(['email']) @mock.patch('google.oauth2._client.refresh_grant', autospec=True) @mock.patch( 'google.auth._helpers.utcnow', return_value=datetime.datetime.min + _helpers.CLOCK_SKEW) - def test_refresh_success(self, now_mock, refresh_grant_mock): + def test_refresh_success(self, unused_utcnow, refresh_grant): token = 'token' expiry = _helpers.utcnow() + datetime.timedelta(seconds=500) grant_response = {'id_token': mock.sentinel.id_token} - refresh_grant_mock.return_value = ( + refresh_grant.return_value = ( # Access token token, # New refresh token @@ -68,21 +70,23 @@ def test_refresh_success(self, now_mock, refresh_grant_mock): expiry, # Extra data grant_response) - request_mock = mock.Mock() + + request = mock.create_autospec(transport.Request) + credentials = self.make_credentials() # Refresh credentials - self.credentials.refresh(request_mock) + credentials.refresh(request) # Check jwt grant call. - refresh_grant_mock.assert_called_with( - request_mock, self.TOKEN_URI, self.REFRESH_TOKEN, self.CLIENT_ID, + refresh_grant.assert_called_with( + request, self.TOKEN_URI, self.REFRESH_TOKEN, self.CLIENT_ID, self.CLIENT_SECRET) # Check that the credentials have the token and expiry - assert self.credentials.token == token - assert self.credentials.expiry == expiry - assert self.credentials.id_token == mock.sentinel.id_token + assert credentials.token == token + assert credentials.expiry == expiry + assert credentials.id_token == mock.sentinel.id_token # Check that the credentials are valid (have a token and are not # expired) - assert self.credentials.valid + assert credentials.valid diff --git a/packages/google-auth/tests/oauth2/test_id_token.py b/packages/google-auth/tests/oauth2/test_id_token.py index 7f89547fb6e3..360f92f53561 100644 --- a/packages/google-auth/tests/oauth2/test_id_token.py +++ b/packages/google-auth/tests/oauth2/test_id_token.py @@ -18,18 +18,20 @@ import pytest from google.auth import exceptions -import google.auth.transport +from google.auth import transport from google.oauth2 import id_token def make_request(status, data=None): - response = mock.Mock() + response = mock.create_autospec(transport.Response, instance=True) response.status = status if data is not None: response.data = json.dumps(data).encode('utf-8') - return mock.Mock(return_value=response, spec=google.auth.transport.Request) + request = mock.create_autospec(transport.Request) + request.return_value = response + return request def test__fetch_certs_success(): diff --git a/packages/google-auth/tests/oauth2/test_service_account.py b/packages/google-auth/tests/oauth2/test_service_account.py index e80b7d497678..94bd7f4c76cd 100644 --- a/packages/google-auth/tests/oauth2/test_service_account.py +++ b/packages/google-auth/tests/oauth2/test_service_account.py @@ -17,11 +17,11 @@ import os import mock -import pytest from google.auth import _helpers from google.auth import crypt from google.auth import jwt +from google.auth import transport from google.oauth2 import service_account @@ -41,21 +41,17 @@ with open(SERVICE_ACCOUNT_JSON_FILE, 'r') as fh: SERVICE_ACCOUNT_INFO = json.load(fh) - -@pytest.fixture(scope='module') -def signer(): - return crypt.RSASigner.from_string(PRIVATE_KEY_BYTES, '1') +SIGNER = crypt.RSASigner.from_string(PRIVATE_KEY_BYTES, '1') class TestCredentials(object): SERVICE_ACCOUNT_EMAIL = 'service-account@example.com' TOKEN_URI = 'https://example.com/oauth2/token' - credentials = None - @pytest.fixture(autouse=True) - def credentials_fixture(self, signer): - self.credentials = service_account.Credentials( - signer, self.SERVICE_ACCOUNT_EMAIL, self.TOKEN_URI) + @classmethod + def make_credentials(cls): + return service_account.Credentials( + SIGNER, cls.SERVICE_ACCOUNT_EMAIL, cls.TOKEN_URI) def test_from_service_account_info(self): credentials = service_account.Credentials.from_service_account_info( @@ -112,96 +108,108 @@ def test_from_service_account_file_args(self): assert credentials._additional_claims == additional_claims def test_default_state(self): - assert not self.credentials.valid + credentials = self.make_credentials() + assert not credentials.valid # Expiration hasn't been set yet - assert not self.credentials.expired + assert not credentials.expired # Scopes haven't been specified yet - assert self.credentials.requires_scopes + assert credentials.requires_scopes def test_sign_bytes(self): + credentials = self.make_credentials() to_sign = b'123' - signature = self.credentials.sign_bytes(to_sign) + signature = credentials.sign_bytes(to_sign) assert crypt.verify_signature(to_sign, signature, PUBLIC_CERT_BYTES) def test_signer(self): - assert isinstance(self.credentials.signer, crypt.Signer) + credentials = self.make_credentials() + assert isinstance(credentials.signer, crypt.Signer) def test_signer_email(self): - assert self.credentials.signer_email == self.SERVICE_ACCOUNT_EMAIL + credentials = self.make_credentials() + assert credentials.signer_email == self.SERVICE_ACCOUNT_EMAIL def test_create_scoped(self): + credentials = self.make_credentials() scopes = ['email', 'profile'] - credentials = self.credentials.with_scopes(scopes) + credentials = credentials.with_scopes(scopes) assert credentials._scopes == scopes def test_with_claims(self): - new_credentials = self.credentials.with_claims({'meep': 'moop'}) + credentials = self.make_credentials() + new_credentials = credentials.with_claims({'meep': 'moop'}) assert new_credentials._additional_claims == {'meep': 'moop'} def test__make_authorization_grant_assertion(self): - token = self.credentials._make_authorization_grant_assertion() + credentials = self.make_credentials() + token = credentials._make_authorization_grant_assertion() payload = jwt.decode(token, PUBLIC_CERT_BYTES) assert payload['iss'] == self.SERVICE_ACCOUNT_EMAIL assert payload['aud'] == self.TOKEN_URI def test__make_authorization_grant_assertion_scoped(self): + credentials = self.make_credentials() scopes = ['email', 'profile'] - credentials = self.credentials.with_scopes(scopes) + credentials = credentials.with_scopes(scopes) token = credentials._make_authorization_grant_assertion() payload = jwt.decode(token, PUBLIC_CERT_BYTES) assert payload['scope'] == 'email profile' def test__make_authorization_grant_assertion_subject(self): + credentials = self.make_credentials() subject = 'user@example.com' - credentials = self.credentials.with_subject(subject) + credentials = credentials.with_subject(subject) token = credentials._make_authorization_grant_assertion() payload = jwt.decode(token, PUBLIC_CERT_BYTES) assert payload['sub'] == subject @mock.patch('google.oauth2._client.jwt_grant', autospec=True) - def test_refresh_success(self, jwt_grant_mock): + def test_refresh_success(self, jwt_grant): + credentials = self.make_credentials() token = 'token' - jwt_grant_mock.return_value = ( + jwt_grant.return_value = ( token, _helpers.utcnow() + datetime.timedelta(seconds=500), {}) - request_mock = mock.Mock() + request = mock.create_autospec(transport.Request, instance=True) # Refresh credentials - self.credentials.refresh(request_mock) + credentials.refresh(request) # Check jwt grant call. - assert jwt_grant_mock.called - request, token_uri, assertion = jwt_grant_mock.call_args[0] - assert request == request_mock - assert token_uri == self.credentials._token_uri + assert jwt_grant.called + + called_request, token_uri, assertion = jwt_grant.call_args[0] + assert called_request == request + assert token_uri == credentials._token_uri assert jwt.decode(assertion, PUBLIC_CERT_BYTES) # No further assertion done on the token, as there are separate tests # for checking the authorization grant assertion. # Check that the credentials have the token. - assert self.credentials.token == token + assert credentials.token == token # Check that the credentials are valid (have a token and are not # expired) - assert self.credentials.valid + assert credentials.valid @mock.patch('google.oauth2._client.jwt_grant', autospec=True) - def test_before_request_refreshes(self, jwt_grant_mock): + def test_before_request_refreshes(self, jwt_grant): + credentials = self.make_credentials() token = 'token' - jwt_grant_mock.return_value = ( + jwt_grant.return_value = ( token, _helpers.utcnow() + datetime.timedelta(seconds=500), None) - request_mock = mock.Mock() + request = mock.create_autospec(transport.Request, instance=True) # Credentials should start as invalid - assert not self.credentials.valid + assert not credentials.valid # before_request should cause a refresh - self.credentials.before_request( - request_mock, 'GET', 'http://example.com?a=1#3', {}) + credentials.before_request( + request, 'GET', 'http://example.com?a=1#3', {}) # The refresh endpoint should've been called. - assert jwt_grant_mock.called + assert jwt_grant.called # Credentials should now be valid. - assert self.credentials.valid + assert credentials.valid diff --git a/packages/google-auth/tests/test__cloud_sdk.py b/packages/google-auth/tests/test__cloud_sdk.py index 482a7ed51a81..6e92ca6455db 100644 --- a/packages/google-auth/tests/test__cloud_sdk.py +++ b/packages/google-auth/tests/test__cloud_sdk.py @@ -40,43 +40,36 @@ CLOUD_SDK_CONFIG_FILE_DATA = fh.read() -@mock.patch( - 'subprocess.check_output', autospec=True, - return_value=CLOUD_SDK_CONFIG_FILE_DATA) -def test_get_project_id(check_output_mock): - project_id = _cloud_sdk.get_project_id() - assert project_id == 'example-project' +@pytest.mark.parametrize('data, expected_project_id', [ + (CLOUD_SDK_CONFIG_FILE_DATA, 'example-project'), + (b'I am some bad json', None), + (b'{}', None) +]) +def test_get_project_id(data, expected_project_id): + check_output_patch = mock.patch( + 'subprocess.check_output', autospec=True, return_value=data) + with check_output_patch as check_output: + project_id = _cloud_sdk.get_project_id() -@mock.patch( - 'subprocess.check_output', autospec=True, - side_effect=subprocess.CalledProcessError(-1, None)) -def test_get_project_id_call_error(check_output_mock): - project_id = _cloud_sdk.get_project_id() - assert project_id is None + assert project_id == expected_project_id + assert check_output.called @mock.patch( 'subprocess.check_output', autospec=True, - return_value=b'I am some bad json') -def test_get_project_id_bad_json(check_output_mock): - project_id = _cloud_sdk.get_project_id() - assert project_id is None - - -@mock.patch( - 'subprocess.check_output', autospec=True, - return_value=b'{}') -def test_get_project_id_missing_value(check_output_mock): + side_effect=subprocess.CalledProcessError(-1, None)) +def test_get_project_id_call_error(check_output): project_id = _cloud_sdk.get_project_id() assert project_id is None + assert check_output.called @mock.patch( 'google.auth._cloud_sdk.get_config_path', autospec=True) -def test_get_application_default_credentials_path(mock_get_config_dir): +def test_get_application_default_credentials_path(get_config_dir): config_path = 'config_path' - mock_get_config_dir.return_value = config_path + get_config_dir.return_value = config_path credentials_path = _cloud_sdk.get_application_default_credentials_path() assert credentials_path == os.path.join( config_path, _cloud_sdk._CREDENTIALS_FILENAME) @@ -91,8 +84,8 @@ def test_get_config_path_env_var(monkeypatch): @mock.patch('os.path.expanduser') -def test_get_config_path_unix(mock_expanduser): - mock_expanduser.side_effect = lambda path: path +def test_get_config_path_unix(expanduser): + expanduser.side_effect = lambda path: path config_path = _cloud_sdk.get_config_path() diff --git a/packages/google-auth/tests/test__default.py b/packages/google-auth/tests/test__default.py index 001a19cd53ba..8054ac522cfd 100644 --- a/packages/google-auth/tests/test__default.py +++ b/packages/google-auth/tests/test__default.py @@ -105,20 +105,19 @@ def test__get_explicit_environ_credentials_no_env(): @LOAD_FILE_PATCH -def test__get_explicit_environ_credentials(mock_load, monkeypatch): +def test__get_explicit_environ_credentials(load, monkeypatch): monkeypatch.setenv(environment_vars.CREDENTIALS, 'filename') credentials, project_id = _default._get_explicit_environ_credentials() assert credentials is mock.sentinel.credentials assert project_id is mock.sentinel.project_id - mock_load.assert_called_with('filename') + load.assert_called_with('filename') @LOAD_FILE_PATCH -def test__get_explicit_environ_credentials_no_project_id( - mock_load, monkeypatch): - mock_load.return_value = (mock.sentinel.credentials, None) +def test__get_explicit_environ_credentials_no_project_id(load, monkeypatch): + load.return_value = mock.sentinel.credentials, None monkeypatch.setenv(environment_vars.CREDENTIALS, 'filename') credentials, project_id = _default._get_explicit_environ_credentials() @@ -131,23 +130,22 @@ def test__get_explicit_environ_credentials_no_project_id( @mock.patch( 'google.auth._cloud_sdk.get_application_default_credentials_path', autospec=True) -def test__get_gcloud_sdk_credentials( - mock_get_adc_path, mock_load): - mock_get_adc_path.return_value = SERVICE_ACCOUNT_FILE +def test__get_gcloud_sdk_credentials(get_adc_path, load): + get_adc_path.return_value = SERVICE_ACCOUNT_FILE credentials, project_id = _default._get_gcloud_sdk_credentials() assert credentials is mock.sentinel.credentials assert project_id is mock.sentinel.project_id - mock_load.assert_called_with(SERVICE_ACCOUNT_FILE) + load.assert_called_with(SERVICE_ACCOUNT_FILE) @mock.patch( 'google.auth._cloud_sdk.get_application_default_credentials_path', autospec=True) -def test__get_gcloud_sdk_credentials_non_existent(mock_get_adc_path, tmpdir): +def test__get_gcloud_sdk_credentials_non_existent(get_adc_path, tmpdir): non_existent = tmpdir.join('non-existent') - mock_get_adc_path.return_value = str(non_existent) + get_adc_path.return_value = str(non_existent) credentials, project_id = _default._get_gcloud_sdk_credentials() @@ -158,19 +156,19 @@ def test__get_gcloud_sdk_credentials_non_existent(mock_get_adc_path, tmpdir): @mock.patch( 'google.auth._cloud_sdk.get_project_id', return_value=mock.sentinel.project_id, autospec=True) -@mock.patch('os.path.isfile', return_value=True) +@mock.patch('os.path.isfile', return_value=True, autospec=True) @LOAD_FILE_PATCH def test__get_gcloud_sdk_credentials_project_id( - mock_load, unused_mock_isfile, mock_get_project_id): + load, unused_isfile, get_project_id): # Don't return a project ID from load file, make the function check # the Cloud SDK project. - mock_load.return_value = (mock.sentinel.credentials, None) + load.return_value = mock.sentinel.credentials, None credentials, project_id = _default._get_gcloud_sdk_credentials() assert credentials == mock.sentinel.credentials assert project_id == mock.sentinel.project_id - assert mock_get_project_id.called + assert get_project_id.called @mock.patch( @@ -179,28 +177,39 @@ def test__get_gcloud_sdk_credentials_project_id( @mock.patch('os.path.isfile', return_value=True) @LOAD_FILE_PATCH def test__get_gcloud_sdk_credentials_no_project_id( - mock_load, unused_mock_isfile, mock_get_project_id): + load, unused_isfile, get_project_id): # Don't return a project ID from load file, make the function check # the Cloud SDK project. - mock_load.return_value = (mock.sentinel.credentials, None) + load.return_value = mock.sentinel.credentials, None credentials, project_id = _default._get_gcloud_sdk_credentials() assert credentials == mock.sentinel.credentials assert project_id is None + assert get_project_id.called + + +class _AppIdentityModule(object): + """The interface of the App Idenity app engine module. + See https://cloud.google.com/appengine/docs/standard/python/refdocs\ + /google.appengine.api.app_identity.app_identity + """ + def get_application_id(self): + raise NotImplementedError() @pytest.fixture -def app_identity_mock(monkeypatch): +def app_identity(monkeypatch): """Mocks the app_identity module for google.auth.app_engine.""" - app_identity_mock = mock.Mock() + app_identity_module = mock.create_autospec( + _AppIdentityModule, instance=True) monkeypatch.setattr( - app_engine, 'app_identity', app_identity_mock) - yield app_identity_mock + app_engine, 'app_identity', app_identity_module) + yield app_identity_module -def test__get_gae_credentials(app_identity_mock): - app_identity_mock.get_application_id.return_value = mock.sentinel.project +def test__get_gae_credentials(app_identity): + app_identity.get_application_id.return_value = mock.sentinel.project credentials, project_id = _default._get_gae_credentials() @@ -218,7 +227,7 @@ def test__get_gae_credentials_no_apis(): @mock.patch( 'google.auth.compute_engine._metadata.get_project_id', return_value='example-project', autospec=True) -def test__get_gce_credentials(get_mock, ping_mock): +def test__get_gce_credentials(unused_get, unused_ping): credentials, project_id = _default._get_gce_credentials() assert isinstance(credentials, compute_engine.Credentials) @@ -228,7 +237,7 @@ def test__get_gce_credentials(get_mock, ping_mock): @mock.patch( 'google.auth.compute_engine._metadata.ping', return_value=False, autospec=True) -def test__get_gce_credentials_no_ping(ping_mock): +def test__get_gce_credentials_no_ping(unused_ping): credentials, project_id = _default._get_gce_credentials() assert credentials is None @@ -241,7 +250,7 @@ def test__get_gce_credentials_no_ping(ping_mock): @mock.patch( 'google.auth.compute_engine._metadata.get_project_id', side_effect=exceptions.TransportError(), autospec=True) -def test__get_gce_credentials_no_project_id(get_mock, ping_mock): +def test__get_gce_credentials_no_project_id(unused_get, unused_ping): credentials, project_id = _default._get_gce_credentials() assert isinstance(credentials, compute_engine.Credentials) @@ -251,16 +260,16 @@ def test__get_gce_credentials_no_project_id(get_mock, ping_mock): @mock.patch( 'google.auth.compute_engine._metadata.ping', return_value=False, autospec=True) -def test__get_gce_credentials_explicit_request(ping_mock): +def test__get_gce_credentials_explicit_request(ping): _default._get_gce_credentials(mock.sentinel.request) - ping_mock.assert_called_with(request=mock.sentinel.request) + ping.assert_called_with(request=mock.sentinel.request) @mock.patch( 'google.auth._default._get_explicit_environ_credentials', return_value=(mock.sentinel.credentials, mock.sentinel.project_id), autospec=True) -def test_default_early_out(get_mock): +def test_default_early_out(unused_get): assert _default.default() == ( mock.sentinel.credentials, mock.sentinel.project_id) @@ -269,7 +278,7 @@ def test_default_early_out(get_mock): 'google.auth._default._get_explicit_environ_credentials', return_value=(mock.sentinel.credentials, mock.sentinel.project_id), autospec=True) -def test_default_explict_project_id(get_mock, monkeypatch): +def test_default_explict_project_id(unused_get, monkeypatch): monkeypatch.setenv(environment_vars.PROJECT, 'explicit-env') assert _default.default() == ( mock.sentinel.credentials, 'explicit-env') @@ -279,7 +288,7 @@ def test_default_explict_project_id(get_mock, monkeypatch): 'google.auth._default._get_explicit_environ_credentials', return_value=(mock.sentinel.credentials, mock.sentinel.project_id), autospec=True) -def test_default_explict_legacy_project_id(get_mock, monkeypatch): +def test_default_explict_legacy_project_id(unused_get, monkeypatch): monkeypatch.setenv(environment_vars.LEGACY_PROJECT, 'explicit-env') assert _default.default() == ( mock.sentinel.credentials, 'explicit-env') @@ -308,12 +317,12 @@ def test_default_fail(unused_gce, unused_gae, unused_sdk, unused_explicit): autospec=True) @mock.patch( 'google.auth.credentials.with_scopes_if_required', autospec=True) -def test_default_scoped(with_scopes_mock, get_mock): +def test_default_scoped(with_scopes, get): scopes = ['one', 'two'] credentials, project_id = _default.default(scopes=scopes) - assert credentials == with_scopes_mock.return_value + assert credentials == with_scopes.return_value assert project_id == mock.sentinel.project_id - with_scopes_mock.assert_called_once_with( + with_scopes.assert_called_once_with( mock.sentinel.credentials, scopes) diff --git a/packages/google-auth/tests/test__oauth2client.py b/packages/google-auth/tests/test__oauth2client.py index 1d7a4a805f84..832b24f100e6 100644 --- a/packages/google-auth/tests/test__oauth2client.py +++ b/packages/google-auth/tests/test__oauth2client.py @@ -112,16 +112,16 @@ def test__convert_appengine_app_assertion_credentials( old_credentials.service_account_id) -class MockCredentials(object): +class FakeCredentials(object): pass def test_convert_success(): - convert_function = mock.Mock() + convert_function = mock.Mock(spec=['__call__']) conversion_map_patch = mock.patch.object( _oauth2client, '_CLASS_CONVERSION_MAP', - {MockCredentials: convert_function}) - credentials = MockCredentials() + {FakeCredentials: convert_function}) + credentials = FakeCredentials() with conversion_map_patch: result = _oauth2client.convert(credentials) diff --git a/packages/google-auth/tests/test_app_engine.py b/packages/google-auth/tests/test_app_engine.py index 2b957f0dadb3..9a6235905aa5 100644 --- a/packages/google-auth/tests/test_app_engine.py +++ b/packages/google-auth/tests/test_app_engine.py @@ -21,17 +21,36 @@ from google.auth import app_engine +class _AppIdentityModule(object): + """The interface of the App Idenity app engine module. + See https://cloud.google.com/appengine/docs/standard/python/refdocs + /google.appengine.api.app_identity.app_identity + """ + def get_application_id(self): + raise NotImplementedError() + + def sign_blob(self, bytes_to_sign, deadline=None): + raise NotImplementedError() + + def get_service_account_name(self, deadline=None): + raise NotImplementedError() + + def get_access_token(self, scopes, service_account_id=None): + raise NotImplementedError() + + @pytest.fixture -def app_identity_mock(monkeypatch): +def app_identity(monkeypatch): """Mocks the app_identity module for google.auth.app_engine.""" - app_identity_mock = mock.Mock() + app_identity_module = mock.create_autospec( + _AppIdentityModule, instance=True) monkeypatch.setattr( - app_engine, 'app_identity', app_identity_mock) - yield app_identity_mock + app_engine, 'app_identity', app_identity_module) + yield app_identity_module -def test_get_project_id(app_identity_mock): - app_identity_mock.get_application_id.return_value = mock.sentinel.project +def test_get_project_id(app_identity): + app_identity.get_application_id.return_value = mock.sentinel.project assert app_engine.get_project_id() == mock.sentinel.project @@ -43,16 +62,16 @@ def test_get_project_id_missing_apis(): class TestSigner(object): - def test_key_id(self, app_identity_mock): - app_identity_mock.sign_blob.return_value = ( + def test_key_id(self, app_identity): + app_identity.sign_blob.return_value = ( mock.sentinel.key_id, mock.sentinel.signature) signer = app_engine.Signer() assert signer.key_id is None - def test_sign(self, app_identity_mock): - app_identity_mock.sign_blob.return_value = ( + def test_sign(self, app_identity): + app_identity.sign_blob.return_value = ( mock.sentinel.key_id, mock.sentinel.signature) signer = app_engine.Signer() @@ -61,7 +80,7 @@ def test_sign(self, app_identity_mock): signature = signer.sign(to_sign) assert signature == mock.sentinel.signature - app_identity_mock.sign_blob.assert_called_with(to_sign) + app_identity.sign_blob.assert_called_with(to_sign) class TestCredentials(object): @@ -71,7 +90,7 @@ def test_missing_apis(self): assert excinfo.match(r'App Engine APIs are not available') - def test_default_state(self, app_identity_mock): + def test_default_state(self, app_identity): credentials = app_engine.Credentials() # Not token acquired yet @@ -82,7 +101,7 @@ def test_default_state(self, app_identity_mock): assert not credentials.scopes assert credentials.requires_scopes - def test_with_scopes(self, app_identity_mock): + def test_with_scopes(self, app_identity): credentials = app_engine.Credentials() assert not credentials.scopes @@ -93,44 +112,44 @@ def test_with_scopes(self, app_identity_mock): assert scoped_credentials.has_scopes(['email']) assert not scoped_credentials.requires_scopes - def test_service_account_email_implicit(self, app_identity_mock): - app_identity_mock.get_service_account_name.return_value = ( + def test_service_account_email_implicit(self, app_identity): + app_identity.get_service_account_name.return_value = ( mock.sentinel.service_account_email) credentials = app_engine.Credentials() assert (credentials.service_account_email == mock.sentinel.service_account_email) - assert app_identity_mock.get_service_account_name.called + assert app_identity.get_service_account_name.called - def test_service_account_email_explicit(self, app_identity_mock): + def test_service_account_email_explicit(self, app_identity): credentials = app_engine.Credentials( service_account_id=mock.sentinel.service_account_email) assert (credentials.service_account_email == mock.sentinel.service_account_email) - assert not app_identity_mock.get_service_account_name.called + assert not app_identity.get_service_account_name.called @mock.patch( 'google.auth._helpers.utcnow', return_value=datetime.datetime.min) - def test_refresh(self, now_mock, app_identity_mock): + def test_refresh(self, utcnow, app_identity): token = 'token' ttl = _helpers.CLOCK_SKEW_SECS + 100 - app_identity_mock.get_access_token.return_value = token, ttl + app_identity.get_access_token.return_value = token, ttl credentials = app_engine.Credentials(scopes=['email']) credentials.refresh(None) - app_identity_mock.get_access_token.assert_called_with( + app_identity.get_access_token.assert_called_with( credentials.scopes, credentials._service_account_id) assert credentials.token == token assert credentials.expiry == ( - now_mock() + datetime.timedelta(seconds=ttl)) + utcnow() + datetime.timedelta(seconds=ttl)) assert credentials.valid assert not credentials.expired - def test_sign_bytes(self, app_identity_mock): - app_identity_mock.sign_blob.return_value = ( + def test_sign_bytes(self, app_identity): + app_identity.sign_blob.return_value = ( mock.sentinel.key_id, mock.sentinel.signature) credentials = app_engine.Credentials() to_sign = b'123' @@ -138,12 +157,12 @@ def test_sign_bytes(self, app_identity_mock): signature = credentials.sign_bytes(to_sign) assert signature == mock.sentinel.signature - app_identity_mock.sign_blob.assert_called_with(to_sign) + app_identity.sign_blob.assert_called_with(to_sign) - def test_signer(self, app_identity_mock): + def test_signer(self, app_identity): credentials = app_engine.Credentials() assert isinstance(credentials.signer, app_engine.Signer) - def test_signer_email(self, app_identity_mock): + def test_signer_email(self, app_identity): credentials = app_engine.Credentials() assert credentials.signer_email == credentials.service_account_email diff --git a/packages/google-auth/tests/test_iam.py b/packages/google-auth/tests/test_iam.py index 97fa90755069..cc090850f13f 100644 --- a/packages/google-auth/tests/test_iam.py +++ b/packages/google-auth/tests/test_iam.py @@ -28,13 +28,15 @@ def make_request(status, data=None): - response = mock.Mock(spec=transport.Response) + response = mock.create_autospec(transport.Response, instance=True) response.status = status if data is not None: response.data = json.dumps(data).encode('utf-8') - return mock.Mock(return_value=response, spec=transport.Request) + request = mock.create_autospec(transport.Request) + request.return_value = response + return request def make_credentials(): @@ -54,7 +56,8 @@ def refresh(self, request): class TestSigner(object): def test_constructor(self): request = mock.sentinel.request - credentials = mock.Mock(spec=google.auth.credentials.Credentials) + credentials = mock.create_autospec( + google.auth.credentials.Credentials, instance=True) signer = iam.Signer( request, credentials, mock.sentinel.service_account_email) diff --git a/packages/google-auth/tests/transport/test_grpc.py b/packages/google-auth/tests/transport/test_grpc.py index a494ee518de0..c98dcff675ee 100644 --- a/packages/google-auth/tests/transport/test_grpc.py +++ b/packages/google-auth/tests/transport/test_grpc.py @@ -19,7 +19,11 @@ from google.auth import _helpers from google.auth import credentials +from google.auth import transport + try: + # pylint: disable=ungrouped-imports + import grpc import google.auth.transport.grpc HAS_GRPC = True except ImportError: # pragma: NO COVER @@ -29,9 +33,9 @@ pytestmark = pytest.mark.skipif(not HAS_GRPC, reason='gRPC is unavailable.') -class MockCredentials(credentials.Credentials): +class CredentialsStub(credentials.Credentials): def __init__(self, token='token'): - super(MockCredentials, self).__init__() + super(CredentialsStub, self).__init__() self.token = token self.expiry = None @@ -41,36 +45,40 @@ def refresh(self, request): class TestAuthMetadataPlugin(object): def test_call_no_refresh(self): - credentials = MockCredentials() - request = mock.Mock() + credentials = CredentialsStub() + request = mock.create_autospec(transport.Request) plugin = google.auth.transport.grpc.AuthMetadataPlugin( credentials, request) - context = mock.Mock() - callback = mock.Mock() + context = mock.create_autospec(grpc.AuthMetadataContext, instance=True) + context.method_name = mock.sentinel.method_name + context.service_url = mock.sentinel.service_url + callback = mock.create_autospec(grpc.AuthMetadataPluginCallback) plugin(context, callback) - assert callback.called_once_with( - [('authorization', 'Bearer {}'.format(credentials.token))], None) + callback.assert_called_once_with( + [(u'authorization', u'Bearer {}'.format(credentials.token))], None) def test_call_refresh(self): - credentials = MockCredentials() + credentials = CredentialsStub() credentials.expiry = datetime.datetime.min + _helpers.CLOCK_SKEW - request = mock.Mock() + request = mock.create_autospec(transport.Request) plugin = google.auth.transport.grpc.AuthMetadataPlugin( credentials, request) - context = mock.Mock() - callback = mock.Mock() + context = mock.create_autospec(grpc.AuthMetadataContext, instance=True) + context.method_name = mock.sentinel.method_name + context.service_url = mock.sentinel.service_url + callback = mock.create_autospec(grpc.AuthMetadataPluginCallback) plugin(context, callback) assert credentials.token == 'token1' - assert callback.called_once_with( - [('authorization', 'Bearer {}'.format(credentials.token))], None) + callback.assert_called_once_with( + [(u'authorization', u'Bearer {}'.format(credentials.token))], None) @mock.patch('grpc.composite_channel_credentials', autospec=True) @@ -80,8 +88,8 @@ def test_call_refresh(self): def test_secure_authorized_channel( secure_channel, ssl_channel_credentials, metadata_call_credentials, composite_channel_credentials): - credentials = mock.Mock() - request = mock.Mock() + credentials = CredentialsStub() + request = mock.create_autospec(transport.Request) target = 'example.com:80' channel = google.auth.transport.grpc.secure_authorized_channel( diff --git a/packages/google-auth/tests/transport/test_requests.py b/packages/google-auth/tests/transport/test_requests.py index 8c3a4239c7ef..41dc237eced3 100644 --- a/packages/google-auth/tests/transport/test_requests.py +++ b/packages/google-auth/tests/transport/test_requests.py @@ -17,6 +17,7 @@ import requests.adapters from six.moves import http_client +import google.auth.credentials import google.auth.transport.requests from tests.transport import compliance @@ -26,18 +27,19 @@ def make_request(self): return google.auth.transport.requests.Request() def test_timeout(self): - http = mock.Mock() + http = mock.create_autospec(requests.Session, instance=True) request = google.auth.transport.requests.Request(http) request(url='http://example.com', method='GET', timeout=5) assert http.request.call_args[1]['timeout'] == 5 -class MockCredentials(object): +class CredentialsStub(google.auth.credentials.Credentials): def __init__(self, token='token'): + super(CredentialsStub, self).__init__() self.token = token - def apply(self, headers): + def apply(self, headers, token=None): headers['authorization'] = self.token def before_request(self, request, method, url, headers): @@ -47,9 +49,9 @@ def refresh(self, request): self.token += '1' -class MockAdapter(requests.adapters.BaseAdapter): +class AdapterStub(requests.adapters.BaseAdapter): def __init__(self, responses, headers=None): - super(MockAdapter, self).__init__() + super(AdapterStub, self).__init__() self.responses = responses self.requests = [] self.headers = headers or {} @@ -84,44 +86,44 @@ def test_constructor(self): assert authed_session.credentials == mock.sentinel.credentials def test_request_no_refresh(self): - mock_credentials = mock.Mock(wraps=MockCredentials()) - mock_response = make_response() - mock_adapter = MockAdapter([mock_response]) + credentials = mock.Mock(wraps=CredentialsStub()) + response = make_response() + adapter = AdapterStub([response]) authed_session = google.auth.transport.requests.AuthorizedSession( - mock_credentials) - authed_session.mount(self.TEST_URL, mock_adapter) + credentials) + authed_session.mount(self.TEST_URL, adapter) - response = authed_session.request('GET', self.TEST_URL) + result = authed_session.request('GET', self.TEST_URL) - assert response == mock_response - assert mock_credentials.before_request.called - assert not mock_credentials.refresh.called - assert len(mock_adapter.requests) == 1 - assert mock_adapter.requests[0].url == self.TEST_URL - assert mock_adapter.requests[0].headers['authorization'] == 'token' + assert response == result + assert credentials.before_request.called + assert not credentials.refresh.called + assert len(adapter.requests) == 1 + assert adapter.requests[0].url == self.TEST_URL + assert adapter.requests[0].headers['authorization'] == 'token' def test_request_refresh(self): - mock_credentials = mock.Mock(wraps=MockCredentials()) - mock_final_response = make_response(status=http_client.OK) + credentials = mock.Mock(wraps=CredentialsStub()) + final_response = make_response(status=http_client.OK) # First request will 401, second request will succeed. - mock_adapter = MockAdapter([ + adapter = AdapterStub([ make_response(status=http_client.UNAUTHORIZED), - mock_final_response]) + final_response]) authed_session = google.auth.transport.requests.AuthorizedSession( - mock_credentials) - authed_session.mount(self.TEST_URL, mock_adapter) + credentials) + authed_session.mount(self.TEST_URL, adapter) - response = authed_session.request('GET', self.TEST_URL) + result = authed_session.request('GET', self.TEST_URL) - assert response == mock_final_response - assert mock_credentials.before_request.call_count == 2 - assert mock_credentials.refresh.called - assert len(mock_adapter.requests) == 2 + assert result == final_response + assert credentials.before_request.call_count == 2 + assert credentials.refresh.called + assert len(adapter.requests) == 2 - assert mock_adapter.requests[0].url == self.TEST_URL - assert mock_adapter.requests[0].headers['authorization'] == 'token' + assert adapter.requests[0].url == self.TEST_URL + assert adapter.requests[0].headers['authorization'] == 'token' - assert mock_adapter.requests[1].url == self.TEST_URL - assert mock_adapter.requests[1].headers['authorization'] == 'token1' + assert adapter.requests[1].url == self.TEST_URL + assert adapter.requests[1].headers['authorization'] == 'token1' diff --git a/packages/google-auth/tests/transport/test_urllib3.py b/packages/google-auth/tests/transport/test_urllib3.py index d35a759cb208..4d1a5a6b066f 100644 --- a/packages/google-auth/tests/transport/test_urllib3.py +++ b/packages/google-auth/tests/transport/test_urllib3.py @@ -16,6 +16,7 @@ from six.moves import http_client import urllib3 +import google.auth.credentials import google.auth.transport.urllib3 from tests.transport import compliance @@ -26,7 +27,7 @@ def make_request(self): return google.auth.transport.urllib3.Request(http) def test_timeout(self): - http = mock.Mock() + http = mock.create_autospec(urllib3.PoolManager) request = google.auth.transport.urllib3.Request(http) request(url='http://example.com', method='GET', timeout=5) @@ -44,11 +45,12 @@ def test__make_default_http_without_certfi(): assert 'cert_reqs' not in http.connection_pool_kw -class MockCredentials(object): +class CredentialsStub(google.auth.credentials.Credentials): def __init__(self, token='token'): + super(CredentialsStub, self).__init__() self.token = token - def apply(self, headers): + def apply(self, headers, token=None): headers['authorization'] = self.token def before_request(self, request, method, url, headers): @@ -58,7 +60,7 @@ def refresh(self, request): self.token += '1' -class MockHttp(object): +class HttpStub(object): def __init__(self, responses, headers=None): self.responses = responses self.requests = [] @@ -69,7 +71,7 @@ def urlopen(self, method, url, body=None, headers=None, **kwargs): return self.responses.pop(0) -class MockResponse(object): +class ResponseStub(object): def __init__(self, status=http_client.OK, data=None): self.status = status self.data = data @@ -86,52 +88,51 @@ def test_authed_http_defaults(self): assert isinstance(authed_http.http, urllib3.PoolManager) def test_urlopen_no_refresh(self): - mock_credentials = mock.Mock(wraps=MockCredentials()) - mock_response = MockResponse() - mock_http = MockHttp([mock_response]) + credentials = mock.Mock(wraps=CredentialsStub()) + response = ResponseStub() + http = HttpStub([response]) authed_http = google.auth.transport.urllib3.AuthorizedHttp( - mock_credentials, http=mock_http) + credentials, http=http) - response = authed_http.urlopen('GET', self.TEST_URL) + result = authed_http.urlopen('GET', self.TEST_URL) - assert response == mock_response - assert mock_credentials.before_request.called - assert not mock_credentials.refresh.called - assert mock_http.requests == [ + assert result == response + assert credentials.before_request.called + assert not credentials.refresh.called + assert http.requests == [ ('GET', self.TEST_URL, None, {'authorization': 'token'}, {})] def test_urlopen_refresh(self): - mock_credentials = mock.Mock(wraps=MockCredentials()) - mock_final_response = MockResponse(status=http_client.OK) + credentials = mock.Mock(wraps=CredentialsStub()) + final_response = ResponseStub(status=http_client.OK) # First request will 401, second request will succeed. - mock_http = MockHttp([ - MockResponse(status=http_client.UNAUTHORIZED), - mock_final_response]) + http = HttpStub([ + ResponseStub(status=http_client.UNAUTHORIZED), + final_response]) authed_http = google.auth.transport.urllib3.AuthorizedHttp( - mock_credentials, http=mock_http) + credentials, http=http) - response = authed_http.urlopen('GET', 'http://example.com') + authed_http = authed_http.urlopen('GET', 'http://example.com') - assert response == mock_final_response - assert mock_credentials.before_request.call_count == 2 - assert mock_credentials.refresh.called - assert mock_http.requests == [ + assert authed_http == final_response + assert credentials.before_request.call_count == 2 + assert credentials.refresh.called + assert http.requests == [ ('GET', self.TEST_URL, None, {'authorization': 'token'}, {}), ('GET', self.TEST_URL, None, {'authorization': 'token1'}, {})] def test_proxies(self): - mock_http = mock.MagicMock() - + http = mock.create_autospec(urllib3.PoolManager) authed_http = google.auth.transport.urllib3.AuthorizedHttp( - None, http=mock_http) + None, http=http) with authed_http: pass - assert mock_http.__enter__.called - assert mock_http.__exit__.called + assert http.__enter__.called + assert http.__exit__.called authed_http.headers = mock.sentinel.headers - assert authed_http.headers == mock_http.headers + assert authed_http.headers == http.headers From ac43b6cf0f4a20ae8c8585c12faf85472c7a0e36 Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Mon, 17 Jul 2017 09:08:49 -0700 Subject: [PATCH 137/966] Documentation Update: Added instructions for using the library in App Engine (#172) * Added instructions on setting up the google.auth library in App Engine * Fixed some formatting issues * Adding a link to requests-toolbelt * Fixed reference * Fixed the link formatting --- packages/google-auth/docs/user-guide.rst | 25 ++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/packages/google-auth/docs/user-guide.rst b/packages/google-auth/docs/user-guide.rst index 76740c7d56a6..291701ce2c87 100644 --- a/packages/google-auth/docs/user-guide.rst +++ b/packages/google-auth/docs/user-guide.rst @@ -127,10 +127,35 @@ credentials one of two ways: credentials = app_engine.Credentials() +In order to make authenticated requests in the App Engine environment using the +credentials and transports provided by this library, you need to follow a few +additional steps: + +#. If you are using the :mod:`google.auth.transport.requests` transport, vendor + in the `requests-toolbelt`_ library into you app, and enable the App Engine + monkeypatch. Refer `App Engine documentation`_ for more details on this. +#. To make HTTPS calls, enable the ``ssl`` library for you app by adding the + following configuration to the ``app.yaml`` file:: + + libraries: + - name: ssl + version: latest + +#. Enable billing for you App Engine project. Then enable socket support for + your app. This can be achieved by setting an environment variable in the + ``app.yaml`` file:: + + env_variables: + GAE_USE_SOCKETS_HTTPLIB : 'true' + .. _App Engine standard environment: https://cloud.google.com/appengine/docs/python .. _App Engine App Identity API: https://cloud.google.com/appengine/docs/python/appidentity/ +.. _requests-toolbelt: + https://toolbelt.readthedocs.io/en/latest/ +.. _App Engine documentation: + https://cloud.google.com/appengine/docs/standard/python/issue-requests User credentials ++++++++++++++++ From d9918b289bb27b9e2b26cefc872cf960f0abd7c4 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Mon, 17 Jul 2017 09:44:47 -0700 Subject: [PATCH 138/966] Fix missing timeout argument for http.client transport (#175) --- .../google/auth/transport/_http_client.py | 2 +- .../google-auth/tests/transport/compliance.py | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/google/auth/transport/_http_client.py b/packages/google-auth/google/auth/transport/_http_client.py index 3cf5583eeb32..4a1009641df9 100644 --- a/packages/google-auth/google/auth/transport/_http_client.py +++ b/packages/google-auth/google/auth/transport/_http_client.py @@ -94,7 +94,7 @@ def __call__(self, url, method='GET', body=None, headers=None, 'http.client transport only supports the http scheme, {}' 'was specified'.format(parts.scheme)) - connection = http_client.HTTPConnection(parts.netloc) + connection = http_client.HTTPConnection(parts.netloc, timeout=timeout) try: _LOGGER.debug('Making request: %s %s', method, url) diff --git a/packages/google-auth/tests/transport/compliance.py b/packages/google-auth/tests/transport/compliance.py index 6d2a30452157..50c6d7ccfe7f 100644 --- a/packages/google-auth/tests/transport/compliance.py +++ b/packages/google-auth/tests/transport/compliance.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +import time + import flask import pytest from pytest_localserver.http import WSGIServer @@ -47,6 +49,11 @@ def index(): @app.route('/server_error') def server_error(): return 'Error', http_client.INTERNAL_SERVER_ERROR + + @app.route('/wait') + def wait(): + time.sleep(3) + return 'Waited' # pylint: enable=unused-variable server = WSGIServer(application=app.wsgi_app) @@ -62,7 +69,7 @@ def test_request_basic(self, server): assert response.headers['x-test-header'] == 'value' assert response.data == b'Basic Content' - def test_request_timeout(self, server): + def test_request_with_timeout_success(self, server): request = self.make_request() response = request(url=server.url + '/basic', method='GET', timeout=2) @@ -70,6 +77,12 @@ def test_request_timeout(self, server): assert response.headers['x-test-header'] == 'value' assert response.data == b'Basic Content' + def test_request_with_timeout_failure(self, server): + request = self.make_request() + + with pytest.raises(exceptions.TransportError): + request(url=server.url + '/wait', method='GET', timeout=1) + def test_request_headers(self, server): request = self.make_request() response = request( From 56277f9130f87bdb2bc00f8410143176a0f3db99 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Wed, 19 Jul 2017 11:26:28 -0700 Subject: [PATCH 139/966] Adding kokoro config (#176) --- packages/google-auth/.kokoro/common.cfg | 13 ++++++++ packages/google-auth/.kokoro/system_tests.cfg | 16 +++++++++ packages/google-auth/.kokoro/system_tests.sh | 33 +++++++++++++++++++ packages/google-auth/.kokoro/trampoline.sh | 17 ++++++++++ .../system_tests/test_compute_engine.py | 5 ++- 5 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 packages/google-auth/.kokoro/common.cfg create mode 100644 packages/google-auth/.kokoro/system_tests.cfg create mode 100755 packages/google-auth/.kokoro/system_tests.sh create mode 100755 packages/google-auth/.kokoro/trampoline.sh diff --git a/packages/google-auth/.kokoro/common.cfg b/packages/google-auth/.kokoro/common.cfg new file mode 100644 index 000000000000..7dbafc200a81 --- /dev/null +++ b/packages/google-auth/.kokoro/common.cfg @@ -0,0 +1,13 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Download trampoline resources. These will be in ${KOKORO_GFILE_DIR} +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" + +# All builds use the trampoline script to run in docker. +build_file: "google-auth-library-python/.kokoro/trampoline.sh" + +# Use the Python worker docker iamge. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/silver-python2/python-worker" +} diff --git a/packages/google-auth/.kokoro/system_tests.cfg b/packages/google-auth/.kokoro/system_tests.cfg new file mode 100644 index 000000000000..aa3e6c6be3f9 --- /dev/null +++ b/packages/google-auth/.kokoro/system_tests.cfg @@ -0,0 +1,16 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Download secrets from Cloud Storage. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-auth-library-python" + +# Tell the trampoline which build file to use. +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/google-auth-library-python/.kokoro/system_tests.sh" +} + +# Tell the system tests which Google Cloud project to use. +env_vars: { + key: "TEST_PROJECT" + value: "python-docs-samples-tests" +} diff --git a/packages/google-auth/.kokoro/system_tests.sh b/packages/google-auth/.kokoro/system_tests.sh new file mode 100755 index 000000000000..29d509708254 --- /dev/null +++ b/packages/google-auth/.kokoro/system_tests.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +# Copyright 2017 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -eo pipefail + +export PATH=${PATH}:${HOME}/gcloud/google-cloud-sdk/bin + +cd github/google-auth-library-python + +# Unencrypt and extract secrets +SECRETS_PASSWORD=$(cat "${KOKORO_GFILE_DIR}/secrets-password.txt") +./scripts/decrypt-secrets.sh "${SECRETS_PASSWORD}" + +# Setup gcloud, this is needed for the App Engine system test. +gcloud auth activate-service-account --key-file system_tests/data/service_account.json +gcloud config set project "${TEST_PROJECT}" + +# Run tests +tox -e py27-system +tox -e py36-system diff --git a/packages/google-auth/.kokoro/trampoline.sh b/packages/google-auth/.kokoro/trampoline.sh new file mode 100755 index 000000000000..6e293e638bb3 --- /dev/null +++ b/packages/google-auth/.kokoro/trampoline.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +# Copyright 2017 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +python3 "${KOKORO_GFILE_DIR}/trampoline_v1.py" diff --git a/packages/google-auth/system_tests/test_compute_engine.py b/packages/google-auth/system_tests/test_compute_engine.py index e828cff17c0c..38733276e22d 100644 --- a/packages/google-auth/system_tests/test_compute_engine.py +++ b/packages/google-auth/system_tests/test_compute_engine.py @@ -17,12 +17,15 @@ import google.auth from google.auth import _helpers from google.auth import compute_engine +from google.auth import exceptions from google.auth.compute_engine import _metadata @pytest.fixture(autouse=True) def check_gce_environment(http_request): - if not _metadata.ping(http_request, timeout=1): + try: + _metadata.get_service_account_info(http_request) + except exceptions.TransportError: pytest.skip('Compute Engine metadata service is not available.') From 3b071c97035ae435657d21085a91b7f02b769a28 Mon Sep 17 00:00:00 2001 From: PicardParis Date: Tue, 1 Aug 2017 19:05:41 +0200 Subject: [PATCH 140/966] Fix retrieval of default project ID on Windows (#179) --- packages/google-auth/google/auth/_cloud_sdk.py | 12 +++++++++--- packages/google-auth/tests/test__cloud_sdk.py | 18 ++++++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/packages/google-auth/google/auth/_cloud_sdk.py b/packages/google-auth/google/auth/_cloud_sdk.py index 428b612c664d..898c6ec8de71 100644 --- a/packages/google-auth/google/auth/_cloud_sdk.py +++ b/packages/google-auth/google/auth/_cloud_sdk.py @@ -33,9 +33,11 @@ # The name of the file in the Cloud SDK config that contains default # credentials. _CREDENTIALS_FILENAME = 'application_default_credentials.json' +# The name of the Cloud SDK shell script +_CLOUD_SDK_POSIX_COMMAND = 'gcloud' +_CLOUD_SDK_WINDOWS_COMMAND = 'gcloud.cmd' # The command to get the Cloud SDK configuration -_CLOUD_SDK_CONFIG_COMMAND = ( - 'gcloud', 'config', 'config-helper', '--format', 'json') +_CLOUD_SDK_CONFIG_COMMAND = ('config', 'config-helper', '--format', 'json') def get_config_path(): @@ -114,10 +116,14 @@ def get_project_id(): Returns: Optional[str]: The project ID. """ + if os.name == 'nt': + command = _CLOUD_SDK_WINDOWS_COMMAND + else: + command = _CLOUD_SDK_POSIX_COMMAND try: output = subprocess.check_output( - _CLOUD_SDK_CONFIG_COMMAND, + (command,) + _CLOUD_SDK_CONFIG_COMMAND, stderr=subprocess.STDOUT) except (subprocess.CalledProcessError, OSError, IOError): return None diff --git a/packages/google-auth/tests/test__cloud_sdk.py b/packages/google-auth/tests/test__cloud_sdk.py index 6e92ca6455db..c14fc20962aa 100644 --- a/packages/google-auth/tests/test__cloud_sdk.py +++ b/packages/google-auth/tests/test__cloud_sdk.py @@ -65,6 +65,24 @@ def test_get_project_id_call_error(check_output): assert check_output.called +@mock.patch('os.name', new='nt') +def test_get_project_id_windows(): + check_output_patch = mock.patch( + 'subprocess.check_output', autospec=True, + return_value=CLOUD_SDK_CONFIG_FILE_DATA) + + with check_output_patch as check_output: + project_id = _cloud_sdk.get_project_id() + + assert project_id == 'example-project' + assert check_output.called + # Make sure the executable is `gcloud.cmd`. + args = check_output.call_args[0] + command = args[0] + executable = command[0] + assert executable == 'gcloud.cmd' + + @mock.patch( 'google.auth._cloud_sdk.get_config_path', autospec=True) def test_get_application_default_credentials_path(get_config_dir): From 88c53ea67a8dd05946e64e0ad82b11283cd95fa0 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Tue, 1 Aug 2017 10:35:01 -0700 Subject: [PATCH 141/966] Release v1.0.2 (#180) --- packages/google-auth/CHANGELOG.rst | 10 ++++++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.rst b/packages/google-auth/CHANGELOG.rst index c1c019fb2e7b..9cf2109d60af 100644 --- a/packages/google-auth/CHANGELOG.rst +++ b/packages/google-auth/CHANGELOG.rst @@ -1,6 +1,16 @@ Changelog ========= +v1.0.2 +------ + +- Fixed a bug where the Cloud SDK executable could not be found on Windows, leading to project ID detection failing. (#179) +- Fixed a bug where the timeout argument wasn't being passed through the httplib transport correctly. (#175) +- Added documentation for using the library on Google App Engine standard. (#172) +- Testing style updates. (#168) +- Added documentation around the oauth2client deprecation. (#165) +- Fixed a few lint issues caught by newer versions of pylint. (#166) + v1.0.1 ------ diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 59ba3de24d5b..e04158c4a95d 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -32,7 +32,7 @@ setup( name='google-auth', - version='1.0.1', + version='1.0.2', author='Google Cloud Platform', author_email='jonwayne+google-auth@google.com', description='Google Authentication Library', From 5744fb537d496d99c021cfcf61b954e9e4554299 Mon Sep 17 00:00:00 2001 From: Brent Shaffer Date: Fri, 4 Aug 2017 17:42:05 -0700 Subject: [PATCH 142/966] low effort typo fix (#184) defualt => default --- packages/google-auth/google/auth/compute_engine/_metadata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/compute_engine/_metadata.py b/packages/google-auth/google/auth/compute_engine/_metadata.py index b35775a23c74..9a22d7a86dbb 100644 --- a/packages/google-auth/google/auth/compute_engine/_metadata.py +++ b/packages/google-auth/google/auth/compute_engine/_metadata.py @@ -88,7 +88,7 @@ def get(request, path, root=_METADATA_ROOT, recursive=False): request (google.auth.transport.Request): A callable used to make HTTP requests. path (str): The resource to retrieve. For example, - ``'instance/service-accounts/defualt'``. + ``'instance/service-accounts/default'``. root (str): The full path to the metadata server root. recursive (bool): Whether to do a recursive query of metadata. See https://cloud.google.com/compute/docs/metadata#aggcontents for more From 47376a05bdbe4fe84e3b0eb6b4b215915e737ae8 Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Thu, 10 Aug 2017 09:11:02 -0700 Subject: [PATCH 143/966] Expose Project ID from service account credentials (#187) --- .../google-auth/google/oauth2/service_account.py | 16 ++++++++++++++-- .../tests/oauth2/test_service_account.py | 3 +++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/google/oauth2/service_account.py b/packages/google-auth/google/oauth2/service_account.py index f8a27bfa7ba7..81b5dabe07b2 100644 --- a/packages/google-auth/google/oauth2/service_account.py +++ b/packages/google-auth/google/oauth2/service_account.py @@ -117,7 +117,7 @@ class Credentials(credentials.Signing, """ def __init__(self, signer, service_account_email, token_uri, scopes=None, - subject=None, additional_claims=None): + subject=None, project_id=None, additional_claims=None): """ Args: signer (google.auth.crypt.Signer): The signer used to sign JWTs. @@ -127,6 +127,8 @@ def __init__(self, signer, service_account_email, token_uri, scopes=None, token_uri (str): The OAuth 2.0 Token URI. subject (str): For domain-wide delegation, the email address of the user to for which to request delegated access. + project_id (str): Project ID associated with the service account + credential. additional_claims (Mapping[str, str]): Any additional claims for the JWT assertion used in the authorization grant. @@ -141,6 +143,7 @@ def __init__(self, signer, service_account_email, token_uri, scopes=None, self._signer = signer self._service_account_email = service_account_email self._subject = subject + self._project_id = project_id self._token_uri = token_uri if additional_claims is not None: @@ -167,7 +170,8 @@ def _from_signer_and_info(cls, signer, info, **kwargs): return cls( signer, service_account_email=info['client_email'], - token_uri=info['token_uri'], **kwargs) + token_uri=info['token_uri'], + project_id=info.get('project_id'), **kwargs) @classmethod def from_service_account_info(cls, info, **kwargs): @@ -210,6 +214,11 @@ def service_account_email(self): """The service account email.""" return self._service_account_email + @property + def project_id(self): + """Project ID associated with this credential.""" + return self._project_id + @property def requires_scopes(self): """Checks if the credentials requires scopes. @@ -227,6 +236,7 @@ def with_scopes(self, scopes): scopes=scopes, token_uri=self._token_uri, subject=self._subject, + project_id=self._project_id, additional_claims=self._additional_claims.copy()) def with_subject(self, subject): @@ -245,6 +255,7 @@ def with_subject(self, subject): scopes=self._scopes, token_uri=self._token_uri, subject=subject, + project_id=self._project_id, additional_claims=self._additional_claims.copy()) def with_claims(self, additional_claims): @@ -268,6 +279,7 @@ def with_claims(self, additional_claims): scopes=self._scopes, token_uri=self._token_uri, subject=self._subject, + project_id=self._project_id, additional_claims=new_additional_claims) def _make_authorization_grant_assertion(self): diff --git a/packages/google-auth/tests/oauth2/test_service_account.py b/packages/google-auth/tests/oauth2/test_service_account.py index 94bd7f4c76cd..9c235db94d06 100644 --- a/packages/google-auth/tests/oauth2/test_service_account.py +++ b/packages/google-auth/tests/oauth2/test_service_account.py @@ -74,6 +74,7 @@ def test_from_service_account_info_args(self): additional_claims=additional_claims) assert credentials.service_account_email == info['client_email'] + assert credentials.project_id == info['project_id'] assert credentials._signer.key_id == info['private_key_id'] assert credentials._token_uri == info['token_uri'] assert credentials._scopes == scopes @@ -87,6 +88,7 @@ def test_from_service_account_file(self): SERVICE_ACCOUNT_JSON_FILE) assert credentials.service_account_email == info['client_email'] + assert credentials.project_id == info['project_id'] assert credentials._signer.key_id == info['private_key_id'] assert credentials._token_uri == info['token_uri'] @@ -101,6 +103,7 @@ def test_from_service_account_file_args(self): scopes=scopes, additional_claims=additional_claims) assert credentials.service_account_email == info['client_email'] + assert credentials.project_id == info['project_id'] assert credentials._signer.key_id == info['private_key_id'] assert credentials._token_uri == info['token_uri'] assert credentials._scopes == scopes From 052da41dd16251abd3523f4e3fc81b9576fbccb6 Mon Sep 17 00:00:00 2001 From: weitaiting Date: Sat, 12 Aug 2017 03:26:59 +0800 Subject: [PATCH 144/966] Add error message to handle case of empty string or missing file for GOOGLE_APPLICATION_CREDENTIALS (#188) Fixes #161 --- packages/google-auth/google/auth/_default.py | 6 +++++- packages/google-auth/tests/test__default.py | 7 +++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index 1e91368f5fa1..7dac642e20ab 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -58,8 +58,12 @@ def _load_credentials_from_file(filename): Raises: google.auth.exceptions.DefaultCredentialsError: if the file is in the - wrong format. + wrong format or is missing. """ + if not os.path.exists(filename): + raise exceptions.DefaultCredentialsError( + 'File {} was not found.'.format(filename)) + with io.open(filename, 'r') as file_obj: try: info = json.load(file_obj) diff --git a/packages/google-auth/tests/test__default.py b/packages/google-auth/tests/test__default.py index 8054ac522cfd..2df8a44f839c 100644 --- a/packages/google-auth/tests/test__default.py +++ b/packages/google-auth/tests/test__default.py @@ -43,6 +43,13 @@ mock.sentinel.credentials, mock.sentinel.project_id), autospec=True) +def test__load_credentials_from_missing_file(): + with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: + _default._load_credentials_from_file('') + + assert excinfo.match(r'not found') + + def test__load_credentials_from_file_invalid_json(tmpdir): jsonfile = tmpdir.join('invalid.json') jsonfile.write('{') From 171b790118175ad2b6d5090594e467f456d21ab8 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Fri, 11 Aug 2017 14:36:42 -0700 Subject: [PATCH 145/966] Split crypt into a package to allow alternative implementations (#189) --- .../google-auth/google/auth/crypt/__init__.py | 79 +++++++++++++ .../auth/{crypt.py => crypt/_python_rsa.py} | 105 ++---------------- .../google-auth/google/auth/crypt/base.py | 64 +++++++++++ packages/google-auth/google/auth/crypt/rsa.py | 20 ++++ packages/google-auth/tests/crypt/__init__.py | 0 .../test__python_rsa.py} | 98 ++++++---------- .../google-auth/tests/crypt/test_crypt.py | 59 ++++++++++ 7 files changed, 266 insertions(+), 159 deletions(-) create mode 100644 packages/google-auth/google/auth/crypt/__init__.py rename packages/google-auth/google/auth/{crypt.py => crypt/_python_rsa.py} (68%) create mode 100644 packages/google-auth/google/auth/crypt/base.py create mode 100644 packages/google-auth/google/auth/crypt/rsa.py create mode 100644 packages/google-auth/tests/crypt/__init__.py rename packages/google-auth/tests/{test_crypt.py => crypt/test__python_rsa.py} (66%) create mode 100644 packages/google-auth/tests/crypt/test_crypt.py diff --git a/packages/google-auth/google/auth/crypt/__init__.py b/packages/google-auth/google/auth/crypt/__init__.py new file mode 100644 index 000000000000..fa59662e0c3b --- /dev/null +++ b/packages/google-auth/google/auth/crypt/__init__.py @@ -0,0 +1,79 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Cryptography helpers for verifying and signing messages. + +The simplest way to verify signatures is using :func:`verify_signature`:: + + cert = open('certs.pem').read() + valid = crypt.verify_signature(message, signature, cert) + +If you're going to verify many messages with the same certificate, you can use +:class:`RSAVerifier`:: + + cert = open('certs.pem').read() + verifier = crypt.RSAVerifier.from_string(cert) + valid = verifier.verify(message, signature) + +To sign messages use :class:`RSASigner` with a private key:: + + private_key = open('private_key.pem').read() + signer = crypt.RSASigner(private_key) + signature = signer.sign(message) +""" + +import six + +from google.auth.crypt import base +from google.auth.crypt import rsa + + +__all__ = [ + 'RSASigner', + 'RSAVerifier', + 'Signer', + 'Verifier', +] + +# Aliases to maintain the v1.0.0 interface, as the crypt module was split +# into submodules. +Signer = base.Signer +Verifier = base.Verifier +RSASigner = rsa.RSASigner +RSAVerifier = rsa.RSAVerifier + + +def verify_signature(message, signature, certs): + """Verify an RSA cryptographic signature. + + Checks that the provided ``signature`` was generated from ``bytes`` using + the private key associated with the ``cert``. + + Args: + message (Union[str, bytes]): The plaintext message. + signature (Union[str, bytes]): The cryptographic signature to check. + certs (Union[Sequence, str, bytes]): The certificate or certificates + to use to check the signature. + + Returns: + bool: True if the signature is valid, otherwise False. + """ + if isinstance(certs, (six.text_type, six.binary_type)): + certs = [certs] + + for cert in certs: + verifier = rsa.RSAVerifier.from_string(cert) + if verifier.verify(message, signature): + return True + return False diff --git a/packages/google-auth/google/auth/crypt.py b/packages/google-auth/google/auth/crypt/_python_rsa.py similarity index 68% rename from packages/google-auth/google/auth/crypt.py rename to packages/google-auth/google/auth/crypt/_python_rsa.py index 65bf37f22600..1f6384d2ce0f 100644 --- a/packages/google-auth/google/auth/crypt.py +++ b/packages/google-auth/google/auth/crypt/_python_rsa.py @@ -12,33 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Cryptography helpers for verifying and signing messages. +"""Pure-Python RSA cryptography implementation. Uses the ``rsa``, ``pyasn1`` and ``pyasn1_modules`` packages to parse PEM files storing PKCS#1 or PKCS#8 keys as well as certificates. There is no support for p12 files. +""" -The simplest way to verify signatures is using :func:`verify_signature`:: - - cert = open('certs.pem').read() - valid = crypt.verify_signature(message, signature, cert) - -If you're going to verify many messages with the same certificate, you can use -:class:`RSAVerifier`:: - - cert = open('certs.pem').read() - verifier = crypt.RSAVerifier.from_string(cert) - valid = verifier.verify(message, signature) - - -To sign messages use :class:`RSASigner` with a private key:: - - private_key = open('private_key.pem').read() - signer = crypt.RSASigner(private_key) - signature = signer.sign(message) +from __future__ import absolute_import -""" -import abc import io import json @@ -50,6 +32,7 @@ import six from google.auth import _helpers +from google.auth.crypt import base _POW2 = (128, 64, 32, 16, 8, 4, 2, 1) _CERTIFICATE_MARKER = b'-----BEGIN CERTIFICATE-----' @@ -84,28 +67,7 @@ def _bit_list_to_bytes(bit_list): return bytes(byte_vals) -@six.add_metaclass(abc.ABCMeta) -class Verifier(object): - """Abstract base class for crytographic signature verifiers.""" - - @abc.abstractmethod - def verify(self, message, signature): - """Verifies a message against a cryptographic signature. - - Args: - message (Union[str, bytes]): The message to verify. - signature (Union[str, bytes]): The cryptography signature to check. - - Returns: - bool: True if message was signed by the private key associated - with the public key that this object was constructed with. - """ - # pylint: disable=missing-raises-doc,redundant-returns-doc - # (pylint doesn't recognize that this is abstract) - raise NotImplementedError('Verify must be implemented') - - -class RSAVerifier(Verifier): +class RSAVerifier(base.Verifier): """Verifies RSA cryptographic signatures using public keys. Args: @@ -116,7 +78,7 @@ class RSAVerifier(Verifier): def __init__(self, public_key): self._pubkey = public_key - @_helpers.copy_docstring(Verifier) + @_helpers.copy_docstring(base.Verifier) def verify(self, message, signature): message = _helpers.to_bytes(message) try: @@ -157,56 +119,7 @@ def from_string(cls, public_key): return cls(pubkey) -def verify_signature(message, signature, certs): - """Verify an RSA cryptographic signature. - - Checks that the provided ``signature`` was generated from ``bytes`` using - the private key associated with the ``cert``. - - Args: - message (Union[str, bytes]): The plaintext message. - signature (Union[str, bytes]): The cryptographic signature to check. - certs (Union[Sequence, str, bytes]): The certificate or certificates - to use to check the signature. - - Returns: - bool: True if the signature is valid, otherwise False. - """ - if isinstance(certs, (six.text_type, six.binary_type)): - certs = [certs] - - for cert in certs: - verifier = RSAVerifier.from_string(cert) - if verifier.verify(message, signature): - return True - return False - - -@six.add_metaclass(abc.ABCMeta) -class Signer(object): - """Abstract base class for cryptographic signers.""" - - @abc.abstractproperty - def key_id(self): - """Optional[str]: The key ID used to identify this private key.""" - raise NotImplementedError('Key id must be implemented') - - @abc.abstractmethod - def sign(self, message): - """Signs a message. - - Args: - message (Union[str, bytes]): The message to be signed. - - Returns: - bytes: The signature of the message. - """ - # pylint: disable=missing-raises-doc,redundant-returns-doc - # (pylint doesn't recognize that this is abstract) - raise NotImplementedError('Sign must be implemented') - - -class RSASigner(Signer): +class RSASigner(base.Signer): """Signs messages with an RSA private key. Args: @@ -221,11 +134,11 @@ def __init__(self, private_key, key_id=None): self._key_id = key_id @property - @_helpers.copy_docstring(Signer) + @_helpers.copy_docstring(base.Signer) def key_id(self): return self._key_id - @_helpers.copy_docstring(Signer) + @_helpers.copy_docstring(base.Signer) def sign(self, message): message = _helpers.to_bytes(message) return rsa.pkcs1.sign(message, self._key, 'SHA-256') diff --git a/packages/google-auth/google/auth/crypt/base.py b/packages/google-auth/google/auth/crypt/base.py new file mode 100644 index 000000000000..05c5a2bf50b8 --- /dev/null +++ b/packages/google-auth/google/auth/crypt/base.py @@ -0,0 +1,64 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Base classes for cryptographic signers and verifiers.""" + +import abc + +import six + + +@six.add_metaclass(abc.ABCMeta) +class Verifier(object): + """Abstract base class for crytographic signature verifiers.""" + + @abc.abstractmethod + def verify(self, message, signature): + """Verifies a message against a cryptographic signature. + + Args: + message (Union[str, bytes]): The message to verify. + signature (Union[str, bytes]): The cryptography signature to check. + + Returns: + bool: True if message was signed by the private key associated + with the public key that this object was constructed with. + """ + # pylint: disable=missing-raises-doc,redundant-returns-doc + # (pylint doesn't recognize that this is abstract) + raise NotImplementedError('Verify must be implemented') + + +@six.add_metaclass(abc.ABCMeta) +class Signer(object): + """Abstract base class for cryptographic signers.""" + + @abc.abstractproperty + def key_id(self): + """Optional[str]: The key ID used to identify this private key.""" + raise NotImplementedError('Key id must be implemented') + + @abc.abstractmethod + def sign(self, message): + """Signs a message. + + Args: + message (Union[str, bytes]): The message to be signed. + + Returns: + bytes: The signature of the message. + """ + # pylint: disable=missing-raises-doc,redundant-returns-doc + # (pylint doesn't recognize that this is abstract) + raise NotImplementedError('Sign must be implemented') diff --git a/packages/google-auth/google/auth/crypt/rsa.py b/packages/google-auth/google/auth/crypt/rsa.py new file mode 100644 index 000000000000..d0bf2a0b9adb --- /dev/null +++ b/packages/google-auth/google/auth/crypt/rsa.py @@ -0,0 +1,20 @@ +# Copyright 2017 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""RSA cryptography signer and verifier.""" + +from google.auth.crypt import _python_rsa + +RSASigner = _python_rsa.RSASigner +RSAVerifier = _python_rsa.RSAVerifier diff --git a/packages/google-auth/tests/crypt/__init__.py b/packages/google-auth/tests/crypt/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/google-auth/tests/test_crypt.py b/packages/google-auth/tests/crypt/test__python_rsa.py similarity index 66% rename from packages/google-auth/tests/test_crypt.py rename to packages/google-auth/tests/crypt/test__python_rsa.py index 56612dae31e0..cff1034bc180 100644 --- a/packages/google-auth/tests/test_crypt.py +++ b/packages/google-auth/tests/crypt/test__python_rsa.py @@ -22,10 +22,10 @@ import six from google.auth import _helpers -from google.auth import crypt +from google.auth.crypt import _python_rsa -DATA_DIR = os.path.join(os.path.dirname(__file__), 'data') +DATA_DIR = os.path.join(os.path.dirname(__file__), '..', 'data') # To generate privatekey.pem, privatekey.pub, and public_cert.pem: # $ openssl req -new -newkey rsa:1024 -x509 -nodes -out public_cert.pem \ @@ -42,12 +42,6 @@ with open(os.path.join(DATA_DIR, 'public_cert.pem'), 'rb') as fh: PUBLIC_CERT_BYTES = fh.read() -# To generate other_cert.pem: -# $ openssl req -new -newkey rsa:1024 -x509 -nodes -out other_cert.pem - -with open(os.path.join(DATA_DIR, 'other_cert.pem'), 'rb') as fh: - OTHER_CERT_BYTES = fh.read() - # To generate pem_from_pkcs12.pem and privatekey.p12: # $ openssl pkcs12 -export -out privatekey.p12 -inkey privatekey.pem \ # > -in public_cert.pem @@ -67,72 +61,50 @@ SERVICE_ACCOUNT_INFO = json.load(fh) -def test_verify_signature(): - to_sign = b'foo' - signer = crypt.RSASigner.from_string(PRIVATE_KEY_BYTES) - signature = signer.sign(to_sign) - - assert crypt.verify_signature( - to_sign, signature, PUBLIC_CERT_BYTES) - - # List of certs - assert crypt.verify_signature( - to_sign, signature, [OTHER_CERT_BYTES, PUBLIC_CERT_BYTES]) - - -def test_verify_signature_failure(): - to_sign = b'foo' - signer = crypt.RSASigner.from_string(PRIVATE_KEY_BYTES) - signature = signer.sign(to_sign) - - assert not crypt.verify_signature( - to_sign, signature, OTHER_CERT_BYTES) - - class TestRSAVerifier(object): def test_verify_success(self): to_sign = b'foo' - signer = crypt.RSASigner.from_string(PRIVATE_KEY_BYTES) + signer = _python_rsa.RSASigner.from_string(PRIVATE_KEY_BYTES) actual_signature = signer.sign(to_sign) - verifier = crypt.RSAVerifier.from_string(PUBLIC_KEY_BYTES) + verifier = _python_rsa.RSAVerifier.from_string(PUBLIC_KEY_BYTES) assert verifier.verify(to_sign, actual_signature) def test_verify_unicode_success(self): to_sign = u'foo' - signer = crypt.RSASigner.from_string(PRIVATE_KEY_BYTES) + signer = _python_rsa.RSASigner.from_string(PRIVATE_KEY_BYTES) actual_signature = signer.sign(to_sign) - verifier = crypt.RSAVerifier.from_string(PUBLIC_KEY_BYTES) + verifier = _python_rsa.RSAVerifier.from_string(PUBLIC_KEY_BYTES) assert verifier.verify(to_sign, actual_signature) def test_verify_failure(self): - verifier = crypt.RSAVerifier.from_string(PUBLIC_KEY_BYTES) + verifier = _python_rsa.RSAVerifier.from_string(PUBLIC_KEY_BYTES) bad_signature1 = b'' assert not verifier.verify(b'foo', bad_signature1) bad_signature2 = b'a' assert not verifier.verify(b'foo', bad_signature2) def test_from_string_pub_key(self): - verifier = crypt.RSAVerifier.from_string(PUBLIC_KEY_BYTES) - assert isinstance(verifier, crypt.RSAVerifier) + verifier = _python_rsa.RSAVerifier.from_string(PUBLIC_KEY_BYTES) + assert isinstance(verifier, _python_rsa.RSAVerifier) assert isinstance(verifier._pubkey, rsa.key.PublicKey) def test_from_string_pub_key_unicode(self): public_key = _helpers.from_bytes(PUBLIC_KEY_BYTES) - verifier = crypt.RSAVerifier.from_string(public_key) - assert isinstance(verifier, crypt.RSAVerifier) + verifier = _python_rsa.RSAVerifier.from_string(public_key) + assert isinstance(verifier, _python_rsa.RSAVerifier) assert isinstance(verifier._pubkey, rsa.key.PublicKey) def test_from_string_pub_cert(self): - verifier = crypt.RSAVerifier.from_string(PUBLIC_CERT_BYTES) - assert isinstance(verifier, crypt.RSAVerifier) + verifier = _python_rsa.RSAVerifier.from_string(PUBLIC_CERT_BYTES) + assert isinstance(verifier, _python_rsa.RSAVerifier) assert isinstance(verifier._pubkey, rsa.key.PublicKey) def test_from_string_pub_cert_unicode(self): public_cert = _helpers.from_bytes(PUBLIC_CERT_BYTES) - verifier = crypt.RSAVerifier.from_string(public_cert) - assert isinstance(verifier, crypt.RSAVerifier) + verifier = _python_rsa.RSAVerifier.from_string(public_cert) + assert isinstance(verifier, _python_rsa.RSAVerifier) assert isinstance(verifier._pubkey, rsa.key.PublicKey) def test_from_string_pub_cert_failure(self): @@ -144,32 +116,32 @@ def test_from_string_pub_cert_failure(self): with load_pem_patch as load_pem: with pytest.raises(ValueError): - crypt.RSAVerifier.from_string(cert_bytes) + _python_rsa.RSAVerifier.from_string(cert_bytes) load_pem.assert_called_once_with(cert_bytes, 'CERTIFICATE') class TestRSASigner(object): def test_from_string_pkcs1(self): - signer = crypt.RSASigner.from_string(PKCS1_KEY_BYTES) - assert isinstance(signer, crypt.RSASigner) + signer = _python_rsa.RSASigner.from_string(PKCS1_KEY_BYTES) + assert isinstance(signer, _python_rsa.RSASigner) assert isinstance(signer._key, rsa.key.PrivateKey) def test_from_string_pkcs1_unicode(self): key_bytes = _helpers.from_bytes(PKCS1_KEY_BYTES) - signer = crypt.RSASigner.from_string(key_bytes) - assert isinstance(signer, crypt.RSASigner) + signer = _python_rsa.RSASigner.from_string(key_bytes) + assert isinstance(signer, _python_rsa.RSASigner) assert isinstance(signer._key, rsa.key.PrivateKey) def test_from_string_pkcs8(self): - signer = crypt.RSASigner.from_string(PKCS8_KEY_BYTES) - assert isinstance(signer, crypt.RSASigner) + signer = _python_rsa.RSASigner.from_string(PKCS8_KEY_BYTES) + assert isinstance(signer, _python_rsa.RSASigner) assert isinstance(signer._key, rsa.key.PrivateKey) def test_from_string_pkcs8_extra_bytes(self): key_bytes = PKCS8_KEY_BYTES _, pem_bytes = pem.readPemBlocksFromFile( six.StringIO(_helpers.from_bytes(key_bytes)), - crypt._PKCS8_MARKER) + _python_rsa._PKCS8_MARKER) key_info, remaining = None, 'extra' decode_patch = mock.patch( @@ -179,44 +151,44 @@ def test_from_string_pkcs8_extra_bytes(self): with decode_patch as decode: with pytest.raises(ValueError): - crypt.RSASigner.from_string(key_bytes) + _python_rsa.RSASigner.from_string(key_bytes) # Verify mock was called. decode.assert_called_once_with( - pem_bytes, asn1Spec=crypt._PKCS8_SPEC) + pem_bytes, asn1Spec=_python_rsa._PKCS8_SPEC) def test_from_string_pkcs8_unicode(self): key_bytes = _helpers.from_bytes(PKCS8_KEY_BYTES) - signer = crypt.RSASigner.from_string(key_bytes) - assert isinstance(signer, crypt.RSASigner) + signer = _python_rsa.RSASigner.from_string(key_bytes) + assert isinstance(signer, _python_rsa.RSASigner) assert isinstance(signer._key, rsa.key.PrivateKey) def test_from_string_pkcs12(self): with pytest.raises(ValueError): - crypt.RSASigner.from_string(PKCS12_KEY_BYTES) + _python_rsa.RSASigner.from_string(PKCS12_KEY_BYTES) def test_from_string_bogus_key(self): key_bytes = 'bogus-key' with pytest.raises(ValueError): - crypt.RSASigner.from_string(key_bytes) + _python_rsa.RSASigner.from_string(key_bytes) def test_from_service_account_info(self): - signer = crypt.RSASigner.from_service_account_info( + signer = _python_rsa.RSASigner.from_service_account_info( SERVICE_ACCOUNT_INFO) assert signer.key_id == SERVICE_ACCOUNT_INFO[ - crypt._JSON_FILE_PRIVATE_KEY_ID] + _python_rsa._JSON_FILE_PRIVATE_KEY_ID] assert isinstance(signer._key, rsa.key.PrivateKey) def test_from_service_account_info_missing_key(self): with pytest.raises(ValueError) as excinfo: - crypt.RSASigner.from_service_account_info({}) + _python_rsa.RSASigner.from_service_account_info({}) - assert excinfo.match(crypt._JSON_FILE_PRIVATE_KEY) + assert excinfo.match(_python_rsa._JSON_FILE_PRIVATE_KEY) def test_from_service_account_file(self): - signer = crypt.RSASigner.from_service_account_file( + signer = _python_rsa.RSASigner.from_service_account_file( SERVICE_ACCOUNT_JSON_FILE) assert signer.key_id == SERVICE_ACCOUNT_INFO[ - crypt._JSON_FILE_PRIVATE_KEY_ID] + _python_rsa._JSON_FILE_PRIVATE_KEY_ID] assert isinstance(signer._key, rsa.key.PrivateKey) diff --git a/packages/google-auth/tests/crypt/test_crypt.py b/packages/google-auth/tests/crypt/test_crypt.py new file mode 100644 index 000000000000..d8b1d00a852c --- /dev/null +++ b/packages/google-auth/tests/crypt/test_crypt.py @@ -0,0 +1,59 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os + +from google.auth import crypt + + +DATA_DIR = os.path.join(os.path.dirname(__file__), '..', 'data') + +# To generate privatekey.pem, privatekey.pub, and public_cert.pem: +# $ openssl req -new -newkey rsa:1024 -x509 -nodes -out public_cert.pem \ +# > -keyout privatekey.pem +# $ openssl rsa -in privatekey.pem -pubout -out privatekey.pub + +with open(os.path.join(DATA_DIR, 'privatekey.pem'), 'rb') as fh: + PRIVATE_KEY_BYTES = fh.read() + +with open(os.path.join(DATA_DIR, 'public_cert.pem'), 'rb') as fh: + PUBLIC_CERT_BYTES = fh.read() + +# To generate other_cert.pem: +# $ openssl req -new -newkey rsa:1024 -x509 -nodes -out other_cert.pem + +with open(os.path.join(DATA_DIR, 'other_cert.pem'), 'rb') as fh: + OTHER_CERT_BYTES = fh.read() + + +def test_verify_signature(): + to_sign = b'foo' + signer = crypt.RSASigner.from_string(PRIVATE_KEY_BYTES) + signature = signer.sign(to_sign) + + assert crypt.verify_signature( + to_sign, signature, PUBLIC_CERT_BYTES) + + # List of certs + assert crypt.verify_signature( + to_sign, signature, [OTHER_CERT_BYTES, PUBLIC_CERT_BYTES]) + + +def test_verify_signature_failure(): + to_sign = b'foo' + signer = crypt.RSASigner.from_string(PRIVATE_KEY_BYTES) + signature = signer.sign(to_sign) + + assert not crypt.verify_signature( + to_sign, signature, OTHER_CERT_BYTES) From 9273d06b832f1ee0eff8300db980b74626d937d2 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Mon, 11 Sep 2017 15:36:53 -0400 Subject: [PATCH 146/966] Move read-only methods of 'Scoped' into new interface, 'ReadOnlyScoped'. (#195) Not all subclasses of 'Scoped' can sanely implement 'with_scopes' (e.g, on GCE the scopes are hard-wired in when creating the GCE node). Make 'Scoped' derive from 'ReadOnlyScoped', adding the 'with_scopes' method. Make GCE's 'credentials' class derive from 'ReadOnlyScoped'. Closes #194. --- .../google/auth/compute_engine/credentials.py | 16 +----- .../google-auth/google/auth/credentials.py | 57 ++++++++++++++----- .../tests/compute_engine/test_credentials.py | 4 -- .../google-auth/tests/test_credentials.py | 20 +++---- 4 files changed, 53 insertions(+), 44 deletions(-) diff --git a/packages/google-auth/google/auth/compute_engine/credentials.py b/packages/google-auth/google/auth/compute_engine/credentials.py index 572995690624..f2a465662f70 100644 --- a/packages/google-auth/google/auth/compute_engine/credentials.py +++ b/packages/google-auth/google/auth/compute_engine/credentials.py @@ -24,7 +24,7 @@ from google.auth.compute_engine import _metadata -class Credentials(credentials.Scoped, credentials.Credentials): +class Credentials(credentials.ReadOnnlyScoped, credentials.Credentials): """Compute Engine Credentials. These credentials use the Google Compute Engine metadata server to obtain @@ -105,17 +105,3 @@ def service_account_email(self): def requires_scopes(self): """False: Compute Engine credentials can not be scoped.""" return False - - def with_scopes(self, scopes): - """Unavailable, Compute Engine credentials can not be scoped. - - Scopes can only be set at Compute Engine instance creation time. - See the `Compute Engine authentication documentation`_ for details on - how to configure instance scopes. - - .. _Compute Engine authentication documentation: - https://cloud.google.com/compute/docs/authentication#using - """ - raise NotImplementedError( - 'Compute Engine credentials can not set scopes. Scopes must be ' - 'set when the Compute Engine instance is created.') diff --git a/packages/google-auth/google/auth/credentials.py b/packages/google-auth/google/auth/credentials.py index 74d6788217f9..83683eb7a80d 100644 --- a/packages/google-auth/google/auth/credentials.py +++ b/packages/google-auth/google/auth/credentials.py @@ -123,8 +123,8 @@ def before_request(self, request, method, url, headers): @six.add_metaclass(abc.ABCMeta) -class Scoped(object): - """Interface for scoped credentials. +class ReadOnnlyScoped(object): + """Interface for credentials whose scopes can be queried. OAuth 2.0-based credentials allow limiting access using scopes as described in `RFC6749 Section 3.3`_. @@ -152,7 +152,7 @@ class Scoped(object): .. _RFC6749 Section 3.3: https://tools.ietf.org/html/rfc6749#section-3.3 """ def __init__(self): - super(Scoped, self).__init__() + super(ReadOnnlyScoped, self).__init__() self._scopes = None @property @@ -166,6 +166,46 @@ def requires_scopes(self): """ return False + def has_scopes(self, scopes): + """Checks if the credentials have the given scopes. + + .. warning: This method is not guaranteed to be accurate if the + credentials are :attr:`~Credentials.invalid`. + + Returns: + bool: True if the credentials have the given scopes. + """ + return set(scopes).issubset(set(self._scopes or [])) + + +class Scoped(ReadOnnlyScoped): + """Interface for credentials whose scopes can be replaced while copying. + + OAuth 2.0-based credentials allow limiting access using scopes as described + in `RFC6749 Section 3.3`_. + If a credential class implements this interface then the credentials either + use scopes in their implementation. + + Some credentials require scopes in order to obtain a token. You can check + if scoping is necessary with :attr:`requires_scopes`:: + + if credentials.requires_scopes: + # Scoping is required. + credentials = credentials.create_scoped(['one', 'two']) + + Credentials that require scopes must either be constructed with scopes:: + + credentials = SomeScopedCredentials(scopes=['one', 'two']) + + Or must copy an existing instance using :meth:`with_scopes`:: + + scoped_credentials = credentials.with_scopes(scopes=['one', 'two']) + + Some credentials have scopes but do not allow or require scopes to be set, + these credentials can be used as-is. + + .. _RFC6749 Section 3.3: https://tools.ietf.org/html/rfc6749#section-3.3 + """ @abc.abstractmethod def with_scopes(self, scopes): """Create a copy of these credentials with the specified scopes. @@ -180,17 +220,6 @@ def with_scopes(self, scopes): """ raise NotImplementedError('This class does not require scoping.') - def has_scopes(self, scopes): - """Checks if the credentials have the given scopes. - - .. warning: This method is not guaranteed to be accurate if the - credentials are :attr:`~Credentials.invalid`. - - Returns: - bool: True if the credentials have the given scopes. - """ - return set(scopes).issubset(set(self._scopes or [])) - def with_scopes_if_required(credentials, scopes): """Creates a copy of the credentials with scopes if scoping is required. diff --git a/packages/google-auth/tests/compute_engine/test_credentials.py b/packages/google-auth/tests/compute_engine/test_credentials.py index 732cb419ccd7..ae2597d300f3 100644 --- a/packages/google-auth/tests/compute_engine/test_credentials.py +++ b/packages/google-auth/tests/compute_engine/test_credentials.py @@ -105,7 +105,3 @@ def test_before_request_refreshes(self, get): # Credentials should now be valid. assert self.credentials.valid - - def test_with_scopes(self): - with pytest.raises(NotImplementedError): - self.credentials.with_scopes(['one', 'two']) diff --git a/packages/google-auth/tests/test_credentials.py b/packages/google-auth/tests/test_credentials.py index b5a540dd8304..ae53cd970ef3 100644 --- a/packages/google-auth/tests/test_credentials.py +++ b/packages/google-auth/tests/test_credentials.py @@ -77,22 +77,20 @@ def test_before_request(): assert headers['authorization'] == 'Bearer token' -class ScopedCredentialsImpl(credentials.Scoped, CredentialsImpl): +class ReadOnnlyScopedCredentialsImpl(credentials.ReadOnnlyScoped, + CredentialsImpl): @property def requires_scopes(self): - return super(ScopedCredentialsImpl, self).requires_scopes - - def with_scopes(self, scopes): - raise NotImplementedError + return super(ReadOnnlyScopedCredentialsImpl, self).requires_scopes -def test_scoped_credentials_constructor(): - credentials = ScopedCredentialsImpl() +def test_readonly_scoped_credentials_constructor(): + credentials = ReadOnnlyScopedCredentialsImpl() assert credentials._scopes is None -def test_scoped_credentials_scopes(): - credentials = ScopedCredentialsImpl() +def test_readonly_scoped_credentials_scopes(): + credentials = ReadOnnlyScopedCredentialsImpl() credentials._scopes = ['one', 'two'] assert credentials.scopes == ['one', 'two'] assert credentials.has_scopes(['one']) @@ -101,8 +99,8 @@ def test_scoped_credentials_scopes(): assert not credentials.has_scopes(['three']) -def test_scoped_credentials_requires_scopes(): - credentials = ScopedCredentialsImpl() +def test_readonly_scoped_credentials_requires_scopes(): + credentials = ReadOnnlyScopedCredentialsImpl() assert not credentials.requires_scopes From bc4c57442163784dc6615254ec55c24bd41b0015 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Tue, 12 Sep 2017 10:01:23 -0700 Subject: [PATCH 147/966] Fix typo of 'Only' as 'Onnly' (#196) --- .../google/auth/compute_engine/credentials.py | 2 +- packages/google-auth/google/auth/credentials.py | 6 +++--- packages/google-auth/tests/test_credentials.py | 12 ++++++------ 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/google-auth/google/auth/compute_engine/credentials.py b/packages/google-auth/google/auth/compute_engine/credentials.py index f2a465662f70..b8fe6f5fadaf 100644 --- a/packages/google-auth/google/auth/compute_engine/credentials.py +++ b/packages/google-auth/google/auth/compute_engine/credentials.py @@ -24,7 +24,7 @@ from google.auth.compute_engine import _metadata -class Credentials(credentials.ReadOnnlyScoped, credentials.Credentials): +class Credentials(credentials.ReadOnlyScoped, credentials.Credentials): """Compute Engine Credentials. These credentials use the Google Compute Engine metadata server to obtain diff --git a/packages/google-auth/google/auth/credentials.py b/packages/google-auth/google/auth/credentials.py index 83683eb7a80d..28f9c9f70a7e 100644 --- a/packages/google-auth/google/auth/credentials.py +++ b/packages/google-auth/google/auth/credentials.py @@ -123,7 +123,7 @@ def before_request(self, request, method, url, headers): @six.add_metaclass(abc.ABCMeta) -class ReadOnnlyScoped(object): +class ReadOnlyScoped(object): """Interface for credentials whose scopes can be queried. OAuth 2.0-based credentials allow limiting access using scopes as described @@ -152,7 +152,7 @@ class ReadOnnlyScoped(object): .. _RFC6749 Section 3.3: https://tools.ietf.org/html/rfc6749#section-3.3 """ def __init__(self): - super(ReadOnnlyScoped, self).__init__() + super(ReadOnlyScoped, self).__init__() self._scopes = None @property @@ -178,7 +178,7 @@ def has_scopes(self, scopes): return set(scopes).issubset(set(self._scopes or [])) -class Scoped(ReadOnnlyScoped): +class Scoped(ReadOnlyScoped): """Interface for credentials whose scopes can be replaced while copying. OAuth 2.0-based credentials allow limiting access using scopes as described diff --git a/packages/google-auth/tests/test_credentials.py b/packages/google-auth/tests/test_credentials.py index ae53cd970ef3..128ae1a6e0aa 100644 --- a/packages/google-auth/tests/test_credentials.py +++ b/packages/google-auth/tests/test_credentials.py @@ -77,20 +77,20 @@ def test_before_request(): assert headers['authorization'] == 'Bearer token' -class ReadOnnlyScopedCredentialsImpl(credentials.ReadOnnlyScoped, - CredentialsImpl): +class ReadOnlyScopedCredentialsImpl( + credentials.ReadOnlyScoped, CredentialsImpl): @property def requires_scopes(self): - return super(ReadOnnlyScopedCredentialsImpl, self).requires_scopes + return super(ReadOnlyScopedCredentialsImpl, self).requires_scopes def test_readonly_scoped_credentials_constructor(): - credentials = ReadOnnlyScopedCredentialsImpl() + credentials = ReadOnlyScopedCredentialsImpl() assert credentials._scopes is None def test_readonly_scoped_credentials_scopes(): - credentials = ReadOnnlyScopedCredentialsImpl() + credentials = ReadOnlyScopedCredentialsImpl() credentials._scopes = ['one', 'two'] assert credentials.scopes == ['one', 'two'] assert credentials.has_scopes(['one']) @@ -100,7 +100,7 @@ def test_readonly_scoped_credentials_scopes(): def test_readonly_scoped_credentials_requires_scopes(): - credentials = ReadOnnlyScopedCredentialsImpl() + credentials = ReadOnlyScopedCredentialsImpl() assert not credentials.requires_scopes From 599c8010e37d777f6fcfc09670027e83399b2a26 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Tue, 12 Sep 2017 10:21:02 -0700 Subject: [PATCH 148/966] Fix App Engine's expiration calculation (#197) --- packages/google-auth/google/auth/app_engine.py | 2 +- packages/google-auth/tests/test_app_engine.py | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/google-auth/google/auth/app_engine.py b/packages/google-auth/google/auth/app_engine.py index 6dc871256ba1..fa13f8ef8076 100644 --- a/packages/google-auth/google/auth/app_engine.py +++ b/packages/google-auth/google/auth/app_engine.py @@ -114,7 +114,7 @@ def refresh(self, request): # pylint: disable=unused-argument token, ttl = app_identity.get_access_token( self._scopes, self._service_account_id) - expiry = _helpers.utcnow() + datetime.timedelta(seconds=ttl) + expiry = datetime.datetime.utcfromtimestamp(ttl) self.token, self.expiry = token, expiry diff --git a/packages/google-auth/tests/test_app_engine.py b/packages/google-auth/tests/test_app_engine.py index 9a6235905aa5..70fa5ca3352a 100644 --- a/packages/google-auth/tests/test_app_engine.py +++ b/packages/google-auth/tests/test_app_engine.py @@ -17,7 +17,6 @@ import mock import pytest -from google.auth import _helpers from google.auth import app_engine @@ -134,7 +133,7 @@ def test_service_account_email_explicit(self, app_identity): return_value=datetime.datetime.min) def test_refresh(self, utcnow, app_identity): token = 'token' - ttl = _helpers.CLOCK_SKEW_SECS + 100 + ttl = 643942923 app_identity.get_access_token.return_value = token, ttl credentials = app_engine.Credentials(scopes=['email']) @@ -143,8 +142,8 @@ def test_refresh(self, utcnow, app_identity): app_identity.get_access_token.assert_called_with( credentials.scopes, credentials._service_account_id) assert credentials.token == token - assert credentials.expiry == ( - utcnow() + datetime.timedelta(seconds=ttl)) + assert credentials.expiry == datetime.datetime( + 1990, 5, 29, 1, 2, 3) assert credentials.valid assert not credentials.expired From cddc665dbfff5f6d58a622a161210035d1adb191 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Tue, 12 Sep 2017 12:40:46 -0700 Subject: [PATCH 149/966] Release v1.1.0 (#198) --- packages/google-auth/CHANGELOG.rst | 10 ++++++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.rst b/packages/google-auth/CHANGELOG.rst index 9cf2109d60af..5e6d0eef23bf 100644 --- a/packages/google-auth/CHANGELOG.rst +++ b/packages/google-auth/CHANGELOG.rst @@ -1,6 +1,16 @@ Changelog ========= +v1.1.0 +------ + +- Added ``service_account.Credentials.project_id``. (#187) +- Move read-only methods of ``credentials.Scoped`` into new interface ``credentials.ReadOnlyScoped``. (#195, #196) +- Make ``compute_engine.Credentials`` derive from ``ReadOnlyScoped`` instead of ``Scoped``. (#195) +- Fix App Engine's expiration calculation (#197) +- Split ``crypt`` module into a package to allow alternative implementations. (#189) +- Add error message to handle case of empty string or missing file for GOOGLE_APPLICATION_CREDENTIALS (#188) + v1.0.2 ------ diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index e04158c4a95d..20a4bebb7086 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -32,7 +32,7 @@ setup( name='google-auth', - version='1.0.2', + version='1.1.0', author='Google Cloud Platform', author_email='jonwayne+google-auth@google.com', description='Google Authentication Library', From cb5cba6c79e60e5972f30daec3cfa7b96bf43488 Mon Sep 17 00:00:00 2001 From: Mahmoud Bassiouny Date: Wed, 20 Sep 2017 09:01:28 -0700 Subject: [PATCH 150/966] Fix OAuth2 credentials to inherit from ReadOnlyScoped instead of Scoped. (#200) * Fix OAuth2 credentials to inherit from ReadOnlyScoped instead of Scoped. * Remove unused pytest --- packages/google-auth/google/oauth2/credentials.py | 11 +---------- packages/google-auth/tests/oauth2/test_credentials.py | 6 ------ 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/packages/google-auth/google/oauth2/credentials.py b/packages/google-auth/google/oauth2/credentials.py index 6a635ddad99b..f1df88791645 100644 --- a/packages/google-auth/google/oauth2/credentials.py +++ b/packages/google-auth/google/oauth2/credentials.py @@ -36,7 +36,7 @@ from google.oauth2 import _client -class Credentials(credentials.Scoped, credentials.Credentials): +class Credentials(credentials.ReadOnlyScoped, credentials.Credentials): """Credentials using OAuth 2.0 access and refresh tokens.""" def __init__(self, token, refresh_token=None, id_token=None, @@ -109,15 +109,6 @@ def requires_scopes(self): the initial token is requested and can not be changed.""" return False - def with_scopes(self, scopes): - """Unavailable, OAuth 2.0 credentials can not be re-scoped. - - OAuth 2.0 credentials have their scopes set when the initial token is - requested and can not be changed. - """ - raise NotImplementedError( - 'OAuth 2.0 Credentials can not modify their scopes.') - @_helpers.copy_docstring(credentials.Credentials) def refresh(self, request): access_token, refresh_token, expiry, grant_response = ( diff --git a/packages/google-auth/tests/oauth2/test_credentials.py b/packages/google-auth/tests/oauth2/test_credentials.py index 14984a1dbaa3..5e09d6f0ad89 100644 --- a/packages/google-auth/tests/oauth2/test_credentials.py +++ b/packages/google-auth/tests/oauth2/test_credentials.py @@ -15,7 +15,6 @@ import datetime import mock -import pytest from google.auth import _helpers from google.auth import transport @@ -48,11 +47,6 @@ def test_default_state(self): assert credentials.client_id == self.CLIENT_ID assert credentials.client_secret == self.CLIENT_SECRET - def test_create_scoped(self): - credentials = self.make_credentials() - with pytest.raises(NotImplementedError): - credentials.with_scopes(['email']) - @mock.patch('google.oauth2._client.refresh_grant', autospec=True) @mock.patch( 'google.auth._helpers.utcnow', From a575b9c8cd75b3ef70d5a1cadc054173e5d0a415 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Wed, 20 Sep 2017 14:32:25 -0700 Subject: [PATCH 151/966] Release v1.1.1 (#201) --- packages/google-auth/CHANGELOG.rst | 5 +++++ packages/google-auth/setup.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.rst b/packages/google-auth/CHANGELOG.rst index 5e6d0eef23bf..5483dc804159 100644 --- a/packages/google-auth/CHANGELOG.rst +++ b/packages/google-auth/CHANGELOG.rst @@ -1,6 +1,11 @@ Changelog ========= +v1.1.1 +------ + +- ``google.oauth.credentials.Credentials`` now correctly inherits from ``ReadOnlyScoped`` instead of ``Scoped``. (#200) + v1.1.0 ------ diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 20a4bebb7086..ca860ec61852 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -32,7 +32,7 @@ setup( name='google-auth', - version='1.1.0', + version='1.1.1', author='Google Cloud Platform', author_email='jonwayne+google-auth@google.com', description='Google Authentication Library', From be22c739ef72e9e6e991a6634a3ede2c45abac9f Mon Sep 17 00:00:00 2001 From: michaelawyu Date: Fri, 13 Oct 2017 10:20:44 -0700 Subject: [PATCH 152/966] Updated the doc with python setup guide (#204) * Update README.rst * Update index.rst --- packages/google-auth/README.rst | 4 ++++ packages/google-auth/docs/index.rst | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/packages/google-auth/README.rst b/packages/google-auth/README.rst index 4be0c5d21c27..72237a7868e2 100644 --- a/packages/google-auth/README.rst +++ b/packages/google-auth/README.rst @@ -22,6 +22,10 @@ You can install using `pip`_:: .. _pip: https://pip.pypa.io/en/stable/ +For more information on setting up your Python development environment, please refer to `Python Development Environment Setup Guide`_ for Google Cloud Platform. + +.. _`Python Development Environment Setup Guide`: https://cloud.google.com/python/setup + Documentation ------------- diff --git a/packages/google-auth/docs/index.rst b/packages/google-auth/docs/index.rst index b6dc92c7bfc1..1b5f5d6f0afd 100644 --- a/packages/google-auth/docs/index.rst +++ b/packages/google-auth/docs/index.rst @@ -34,6 +34,10 @@ google-auth can be installed with `pip`_:: google-auth is open-source, so you can alternatively grab the source code from `GitHub`_ and install from source. + +For more information on setting up your Python development environment, please refer to `Python Development Environment Setup Guide`_ for Google Cloud Platform. + +.. _`Python Development Environment Setup Guide`: https://cloud.google.com/python/setup .. _pip: https://pip.pypa.io .. _GitHub: https://github.com/GoogleCloudPlatform/google-auth-library-python From 9756b326e75fef119226d791a6520684c9d757f2 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Mon, 30 Oct 2017 16:12:37 -0400 Subject: [PATCH 153/966] Add 'AnonymousCredentials' class. (#206) * Add 'AnonymousCredentials' class. See: https://github.com/GoogleCloudPlatform/google-cloud-python/issues/4279. * Nits * Fix whitespace --- .../google-auth/google/auth/credentials.py | 37 +++++++++++++++++++ .../google-auth/tests/test_credentials.py | 36 ++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/packages/google-auth/google/auth/credentials.py b/packages/google-auth/google/auth/credentials.py index 28f9c9f70a7e..1fb5dece27af 100644 --- a/packages/google-auth/google/auth/credentials.py +++ b/packages/google-auth/google/auth/credentials.py @@ -122,6 +122,43 @@ def before_request(self, request, method, url, headers): self.apply(headers) +class AnonymousCredentials(Credentials): + """Credentials that do not provide any authentication information. + + These are useful in the case of services that support anonymous access or + local service emulators that do not use credentials. + """ + + @property + def expired(self): + """Returns `False`, anonymous credentials never expire.""" + return False + + @property + def valid(self): + """Returns `True`, anonymous credentials are always valid.""" + return True + + def refresh(self, request): + """Raises :class:`ValueError``, anonymous credentials cannot be + refreshed.""" + raise ValueError("Anonymous credentials cannot be refreshed.") + + def apply(self, headers, token=None): + """Anonymous credentials do nothing to the request. + + The optional ``token`` argument is not supported. + + Raises: + ValueError: If a token was specified. + """ + if token is not None: + raise ValueError("Anonymous credentials don't support tokens.") + + def before_request(self, request, method, url, headers): + """Anonymous credentials do nothing to the request.""" + + @six.add_metaclass(abc.ABCMeta) class ReadOnlyScoped(object): """Interface for credentials whose scopes can be queried. diff --git a/packages/google-auth/tests/test_credentials.py b/packages/google-auth/tests/test_credentials.py index 128ae1a6e0aa..b302989fbf19 100644 --- a/packages/google-auth/tests/test_credentials.py +++ b/packages/google-auth/tests/test_credentials.py @@ -14,6 +14,8 @@ import datetime +import pytest + from google.auth import _helpers from google.auth import credentials @@ -77,6 +79,40 @@ def test_before_request(): assert headers['authorization'] == 'Bearer token' +def test_anonymous_credentials_ctor(): + anon = credentials.AnonymousCredentials() + assert anon.token is None + assert anon.expiry is None + assert not anon.expired + assert anon.valid + + +def test_anonymous_credentials_refresh(): + anon = credentials.AnonymousCredentials() + request = object() + with pytest.raises(ValueError): + anon.refresh(request) + + +def test_anonymous_credentials_apply_default(): + anon = credentials.AnonymousCredentials() + headers = {} + anon.apply(headers) + assert headers == {} + with pytest.raises(ValueError): + anon.apply(headers, token='TOKEN') + + +def test_anonymous_credentials_before_request(): + anon = credentials.AnonymousCredentials() + request = object() + method = 'GET' + url = 'https://example.com/api/endpoint' + headers = {} + anon.before_request(request, method, url, headers) + assert headers == {} + + class ReadOnlyScopedCredentialsImpl( credentials.ReadOnlyScoped, CredentialsImpl): @property From 2c9e38d6d32a62e79034952cbc5747619f736065 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Tue, 31 Oct 2017 09:41:06 -0700 Subject: [PATCH 154/966] Release v1.2.0 (#207) --- packages/google-auth/CHANGELOG.rst | 6 ++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.rst b/packages/google-auth/CHANGELOG.rst index 5483dc804159..e281f4a7d623 100644 --- a/packages/google-auth/CHANGELOG.rst +++ b/packages/google-auth/CHANGELOG.rst @@ -1,6 +1,12 @@ Changelog ========= +v1.2.0 +------ + +- Added ``google.auth.credentials.AnonymousCredentials``. (#206) +- Updated the documentation to link to the Google Cloud Platform Python setup guide (#204) + v1.1.1 ------ diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index ca860ec61852..d76570fc0dba 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -32,7 +32,7 @@ setup( name='google-auth', - version='1.1.1', + version='1.2.0', author='Google Cloud Platform', author_email='jonwayne+google-auth@google.com', description='Google Authentication Library', From ca69cf31008c897eee354952557491fd4d57f492 Mon Sep 17 00:00:00 2001 From: Albert-Jan Nijburg Date: Wed, 8 Nov 2017 20:15:18 +0000 Subject: [PATCH 155/966] fix comment, seconds not sections (#210) * fix comment, seconds not sections * make the build go green --- packages/google-auth/google/oauth2/service_account.py | 2 +- packages/google-auth/tests/transport/test__http_client.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/google-auth/google/oauth2/service_account.py b/packages/google-auth/google/oauth2/service_account.py index 81b5dabe07b2..54bd8d671afb 100644 --- a/packages/google-auth/google/oauth2/service_account.py +++ b/packages/google-auth/google/oauth2/service_account.py @@ -79,7 +79,7 @@ from google.auth import jwt from google.oauth2 import _client -_DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in sections +_DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds class Credentials(credentials.Signing, diff --git a/packages/google-auth/tests/transport/test__http_client.py b/packages/google-auth/tests/transport/test__http_client.py index b66e8a4a00ac..6b69088b824f 100644 --- a/packages/google-auth/tests/transport/test__http_client.py +++ b/packages/google-auth/tests/transport/test__http_client.py @@ -16,7 +16,6 @@ from google.auth import exceptions import google.auth.transport._http_client - from tests.transport import compliance From 2a871f404975fefd3f3ed1061b6add26b4e4d9ed Mon Sep 17 00:00:00 2001 From: Danny Hermes Date: Thu, 9 Nov 2017 11:35:57 -0800 Subject: [PATCH 156/966] Using `six.raise_from` wherever possible. --- packages/google-auth/google/auth/_default.py | 23 +++++++++++-------- .../google-auth/google/auth/_oauth2client.py | 11 +++++---- .../google/auth/compute_engine/_metadata.py | 6 +++-- .../google/auth/compute_engine/credentials.py | 7 ++++-- packages/google-auth/google/auth/jwt.py | 6 +++-- .../google/auth/transport/_http_client.py | 6 +++-- .../google-auth/google/auth/transport/grpc.py | 7 +++--- .../google/auth/transport/requests.py | 11 +++++---- .../google/auth/transport/urllib3.py | 11 +++++---- packages/google-auth/google/oauth2/_client.py | 11 +++++---- .../tests/compute_engine/test_credentials.py | 2 -- packages/google-auth/tests/test__default.py | 2 -- 12 files changed, 63 insertions(+), 40 deletions(-) diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index 7dac642e20ab..06f52bbd83be 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -22,6 +22,8 @@ import logging import os +import six + from google.auth import environment_vars from google.auth import exceptions import google.auth.transport._http_client @@ -67,9 +69,10 @@ def _load_credentials_from_file(filename): with io.open(filename, 'r') as file_obj: try: info = json.load(file_obj) - except ValueError as exc: - raise exceptions.DefaultCredentialsError( - 'File {} is not a valid json file.'.format(filename), exc) + except ValueError as caught_exc: + new_exc = exceptions.DefaultCredentialsError( + 'File {} is not a valid json file.'.format(filename)) + six.raise_from(new_exc, caught_exc) # The type key should indicate that the file is either a service account # credentials file or an authorized user credentials file. @@ -80,10 +83,11 @@ def _load_credentials_from_file(filename): try: credentials = _cloud_sdk.load_authorized_user_credentials(info) - except ValueError as exc: - raise exceptions.DefaultCredentialsError( + except ValueError as caught_exc: + new_exc = exceptions.DefaultCredentialsError( 'Failed to load authorized user credentials from {}'.format( - filename), exc) + filename)) + six.raise_from(new_exc, caught_exc) # Authorized user credentials do not contain the project ID. return credentials, None @@ -93,10 +97,11 @@ def _load_credentials_from_file(filename): try: credentials = ( service_account.Credentials.from_service_account_info(info)) - except ValueError as exc: - raise exceptions.DefaultCredentialsError( + except ValueError as caught_exc: + new_exc = exceptions.DefaultCredentialsError( 'Failed to load service account credentials from {}'.format( - filename), exc) + filename)) + six.raise_from(new_exc, caught_exc) return credentials, info.get('project_id') else: diff --git a/packages/google-auth/google/auth/_oauth2client.py b/packages/google-auth/google/auth/_oauth2client.py index 312326e18190..e298fb15547b 100644 --- a/packages/google-auth/google/auth/_oauth2client.py +++ b/packages/google-auth/google/auth/_oauth2client.py @@ -26,12 +26,14 @@ import google.oauth2.credentials import google.oauth2.service_account +import six try: import oauth2client.client import oauth2client.contrib.gce import oauth2client.service_account -except ImportError: - raise ImportError('oauth2client is not installed.') +except ImportError as caught_exc: + new_exc = ImportError('oauth2client is not installed.') + six.raise_from(new_exc, caught_exc) try: import oauth2client.contrib.appengine @@ -162,5 +164,6 @@ def convert(credentials): try: return _CLASS_CONVERSION_MAP[credentials_class](credentials) - except KeyError: - raise ValueError(_CONVERT_ERROR_TMPL.format(credentials_class)) + except KeyError as caught_exc: + new_exc = ValueError(_CONVERT_ERROR_TMPL.format(credentials_class)) + six.raise_from(new_exc, caught_exc) diff --git a/packages/google-auth/google/auth/compute_engine/_metadata.py b/packages/google-auth/google/auth/compute_engine/_metadata.py index 9a22d7a86dbb..c47be3fae2b7 100644 --- a/packages/google-auth/google/auth/compute_engine/_metadata.py +++ b/packages/google-auth/google/auth/compute_engine/_metadata.py @@ -22,6 +22,7 @@ import logging import os +import six from six.moves import http_client from six.moves.urllib import parse as urlparse @@ -118,10 +119,11 @@ def get(request, path, root=_METADATA_ROOT, recursive=False): if response.headers['content-type'] == 'application/json': try: return json.loads(content) - except ValueError: - raise exceptions.TransportError( + except ValueError as caught_exc: + new_exc = exceptions.TransportError( 'Received invalid JSON from the Google Compute Engine' 'metadata service: {:.20}'.format(content)) + six.raise_from(new_exc, caught_exc) else: return content else: diff --git a/packages/google-auth/google/auth/compute_engine/credentials.py b/packages/google-auth/google/auth/compute_engine/credentials.py index b8fe6f5fadaf..cbc7cf4f328a 100644 --- a/packages/google-auth/google/auth/compute_engine/credentials.py +++ b/packages/google-auth/google/auth/compute_engine/credentials.py @@ -19,6 +19,8 @@ """ +import six + from google.auth import credentials from google.auth import exceptions from google.auth.compute_engine import _metadata @@ -89,8 +91,9 @@ def refresh(self, request): self.token, self.expiry = _metadata.get_service_account_token( request, service_account=self._service_account_email) - except exceptions.TransportError as exc: - raise exceptions.RefreshError(exc) + except exceptions.TransportError as caught_exc: + new_exc = exceptions.RefreshError() + six.raise_from(new_exc, caught_exc) @property def service_account_email(self): diff --git a/packages/google-auth/google/auth/jwt.py b/packages/google-auth/google/auth/jwt.py index b1eb5fb91e95..02533762fac8 100644 --- a/packages/google-auth/google/auth/jwt.py +++ b/packages/google-auth/google/auth/jwt.py @@ -47,6 +47,7 @@ import json import cachetools +import six from six.moves import urllib from google.auth import _helpers @@ -101,8 +102,9 @@ def _decode_jwt_segment(encoded_section): section_bytes = _helpers.padded_urlsafe_b64decode(encoded_section) try: return json.loads(section_bytes.decode('utf-8')) - except ValueError: - raise ValueError('Can\'t parse segment: {0}'.format(section_bytes)) + except ValueError as caught_exc: + new_exc = ValueError('Can\'t parse segment: {0}'.format(section_bytes)) + six.raise_from(new_exc, caught_exc) def _unverified_decode(token): diff --git a/packages/google-auth/google/auth/transport/_http_client.py b/packages/google-auth/google/auth/transport/_http_client.py index 4a1009641df9..35b100568c27 100644 --- a/packages/google-auth/google/auth/transport/_http_client.py +++ b/packages/google-auth/google/auth/transport/_http_client.py @@ -17,6 +17,7 @@ import logging import socket +import six from six.moves import http_client from six.moves import urllib @@ -104,8 +105,9 @@ def __call__(self, url, method='GET', body=None, headers=None, response = connection.getresponse() return Response(response) - except (http_client.HTTPException, socket.error) as exc: - raise exceptions.TransportError(exc) + except (http_client.HTTPException, socket.error) as caught_exc: + new_exc = exceptions.TransportError() + six.raise_from(new_exc, caught_exc) finally: connection.close() diff --git a/packages/google-auth/google/auth/transport/grpc.py b/packages/google-auth/google/auth/transport/grpc.py index 8554ffa36a98..08b2f575815a 100644 --- a/packages/google-auth/google/auth/transport/grpc.py +++ b/packages/google-auth/google/auth/transport/grpc.py @@ -16,13 +16,14 @@ from __future__ import absolute_import +import six try: import grpc -except ImportError: # pragma: NO COVER - raise ImportError( +except ImportError as caught_exc: # pragma: NO COVER + new_exc = ImportError( 'gRPC is not installed, please install the grpcio package to use the ' 'gRPC transport.') -import six + six.raise_from(new_exc, caught_exc) class AuthMetadataPlugin(grpc.AuthMetadataPlugin): diff --git a/packages/google-auth/google/auth/transport/requests.py b/packages/google-auth/google/auth/transport/requests.py index 6fc395e27066..31ce22ddd63d 100644 --- a/packages/google-auth/google/auth/transport/requests.py +++ b/packages/google-auth/google/auth/transport/requests.py @@ -18,12 +18,14 @@ import logging +import six try: import requests -except ImportError: # pragma: NO COVER - raise ImportError( +except ImportError as caught_exc: # pragma: NO COVER + new_exc = ImportError( 'The requests library is not installed, please install the requests ' 'package to use the requests transport.') + six.raise_from(new_exc, caught_exc) import requests.exceptions from google.auth import exceptions @@ -111,8 +113,9 @@ def __call__(self, url, method='GET', body=None, headers=None, method, url, data=body, headers=headers, timeout=timeout, **kwargs) return _Response(response) - except requests.exceptions.RequestException as exc: - raise exceptions.TransportError(exc) + except requests.exceptions.RequestException as caught_exc: + new_exc = exceptions.TransportError() + six.raise_from(new_exc, caught_exc) class AuthorizedSession(requests.Session): diff --git a/packages/google-auth/google/auth/transport/urllib3.py b/packages/google-auth/google/auth/transport/urllib3.py index 0dfe9130996c..872bf9fa759d 100644 --- a/packages/google-auth/google/auth/transport/urllib3.py +++ b/packages/google-auth/google/auth/transport/urllib3.py @@ -30,12 +30,14 @@ except ImportError: # pragma: NO COVER certifi = None +import six try: import urllib3 -except ImportError: # pragma: NO COVER - raise ImportError( +except ImportError as caught_exc: # pragma: NO COVER + new_exc = ImportError( 'The urllib3 library is not installed, please install the urllib3 ' 'package to use the urllib3 transport.') + six.raise_from(new_exc, caught_exc) import urllib3.exceptions from google.auth import exceptions @@ -126,8 +128,9 @@ def __call__(self, url, method='GET', body=None, headers=None, response = self.http.request( method, url, body=body, headers=headers, **kwargs) return _Response(response) - except urllib3.exceptions.HTTPError as exc: - raise exceptions.TransportError(exc) + except urllib3.exceptions.HTTPError as caught_exc: + new_exc = exceptions.TransportError() + six.raise_from(new_exc, caught_exc) def _make_default_http(): diff --git a/packages/google-auth/google/oauth2/_client.py b/packages/google-auth/google/oauth2/_client.py index 468cb7e889c4..66251df4135d 100644 --- a/packages/google-auth/google/oauth2/_client.py +++ b/packages/google-auth/google/oauth2/_client.py @@ -26,6 +26,7 @@ import datetime import json +import six from six.moves import http_client from six.moves import urllib @@ -144,9 +145,10 @@ def jwt_grant(request, token_uri, assertion): try: access_token = response_data['access_token'] - except KeyError: - raise exceptions.RefreshError( + except KeyError as caught_exc: + new_exc = exceptions.RefreshError( 'No access token in response.', response_data) + six.raise_from(new_exc, caught_exc) expiry = _parse_expiry(response_data) @@ -190,9 +192,10 @@ def refresh_grant(request, token_uri, refresh_token, client_id, client_secret): try: access_token = response_data['access_token'] - except KeyError: - raise exceptions.RefreshError( + except KeyError as caught_exc: + new_exc = exceptions.RefreshError( 'No access token in response.', response_data) + six.raise_from(new_exc, caught_exc) refresh_token = response_data.get('refresh_token', refresh_token) expiry = _parse_expiry(response_data) diff --git a/packages/google-auth/tests/compute_engine/test_credentials.py b/packages/google-auth/tests/compute_engine/test_credentials.py index ae2597d300f3..ee343fe4e7b8 100644 --- a/packages/google-auth/tests/compute_engine/test_credentials.py +++ b/packages/google-auth/tests/compute_engine/test_credentials.py @@ -78,8 +78,6 @@ def test_refresh_error(self, get): with pytest.raises(exceptions.RefreshError) as excinfo: self.credentials.refresh(None) - assert excinfo.match(r'http error') - @mock.patch('google.auth.compute_engine._metadata.get', autospec=True) def test_before_request_refreshes(self, get): get.side_effect = [{ diff --git a/packages/google-auth/tests/test__default.py b/packages/google-auth/tests/test__default.py index 2df8a44f839c..090cea688929 100644 --- a/packages/google-auth/tests/test__default.py +++ b/packages/google-auth/tests/test__default.py @@ -85,7 +85,6 @@ def test__load_credentials_from_file_authorized_user_bad_format(tmpdir): _default._load_credentials_from_file(str(filename)) assert excinfo.match(r'Failed to load authorized user') - assert excinfo.match(r'missing fields') def test__load_credentials_from_file_service_account(): @@ -103,7 +102,6 @@ def test__load_credentials_from_file_service_account_bad_format(tmpdir): _default._load_credentials_from_file(str(filename)) assert excinfo.match(r'Failed to load service account') - assert excinfo.match(r'missing fields') @mock.patch.dict(os.environ, {}, clear=True) From fab9ae622fa1586954c5f9a3834c2bfd24bce670 Mon Sep 17 00:00:00 2001 From: Danny Hermes Date: Thu, 9 Nov 2017 12:04:14 -0800 Subject: [PATCH 157/966] Lint fix. --- .../google-auth/google/auth/_oauth2client.py | 7 ++++--- .../google-auth/google/auth/transport/grpc.py | 11 +++++++---- .../google/auth/transport/requests.py | 16 ++++++++++------ .../google-auth/google/auth/transport/urllib3.py | 16 ++++++++++------ .../tests/compute_engine/test_credentials.py | 2 +- 5 files changed, 32 insertions(+), 20 deletions(-) diff --git a/packages/google-auth/google/auth/_oauth2client.py b/packages/google-auth/google/auth/_oauth2client.py index e298fb15547b..71fd7bf41d12 100644 --- a/packages/google-auth/google/auth/_oauth2client.py +++ b/packages/google-auth/google/auth/_oauth2client.py @@ -21,19 +21,20 @@ from __future__ import absolute_import +import six + from google.auth import _helpers import google.auth.app_engine import google.oauth2.credentials import google.oauth2.service_account -import six try: import oauth2client.client import oauth2client.contrib.gce import oauth2client.service_account except ImportError as caught_exc: - new_exc = ImportError('oauth2client is not installed.') - six.raise_from(new_exc, caught_exc) + six.raise_from( + ImportError('oauth2client is not installed.'), caught_exc) try: import oauth2client.contrib.appengine diff --git a/packages/google-auth/google/auth/transport/grpc.py b/packages/google-auth/google/auth/transport/grpc.py index 08b2f575815a..0d44f6458eac 100644 --- a/packages/google-auth/google/auth/transport/grpc.py +++ b/packages/google-auth/google/auth/transport/grpc.py @@ -20,10 +20,13 @@ try: import grpc except ImportError as caught_exc: # pragma: NO COVER - new_exc = ImportError( - 'gRPC is not installed, please install the grpcio package to use the ' - 'gRPC transport.') - six.raise_from(new_exc, caught_exc) + six.raise_from( + ImportError( + 'gRPC is not installed, please install the grpcio package ' + 'to use the gRPC transport.' + ), + caught_exc, + ) class AuthMetadataPlugin(grpc.AuthMetadataPlugin): diff --git a/packages/google-auth/google/auth/transport/requests.py b/packages/google-auth/google/auth/transport/requests.py index 31ce22ddd63d..66ff0d3e20c4 100644 --- a/packages/google-auth/google/auth/transport/requests.py +++ b/packages/google-auth/google/auth/transport/requests.py @@ -18,15 +18,19 @@ import logging -import six try: import requests except ImportError as caught_exc: # pragma: NO COVER - new_exc = ImportError( - 'The requests library is not installed, please install the requests ' - 'package to use the requests transport.') - six.raise_from(new_exc, caught_exc) -import requests.exceptions + import six + six.raise_from( + ImportError( + 'The requests library is not installed, please install the ' + 'requests package to use the requests transport.' + ), + caught_exc, + ) +import requests.exceptions # pylint: disable=ungrouped-imports +import six # pylint: disable=ungrouped-imports from google.auth import exceptions from google.auth import transport diff --git a/packages/google-auth/google/auth/transport/urllib3.py b/packages/google-auth/google/auth/transport/urllib3.py index 872bf9fa759d..ec20fb64f566 100644 --- a/packages/google-auth/google/auth/transport/urllib3.py +++ b/packages/google-auth/google/auth/transport/urllib3.py @@ -30,15 +30,19 @@ except ImportError: # pragma: NO COVER certifi = None -import six try: import urllib3 except ImportError as caught_exc: # pragma: NO COVER - new_exc = ImportError( - 'The urllib3 library is not installed, please install the urllib3 ' - 'package to use the urllib3 transport.') - six.raise_from(new_exc, caught_exc) -import urllib3.exceptions + import six + six.raise_from( + ImportError( + 'The urllib3 library is not installed, please install the ' + 'urllib3 package to use the urllib3 transport.' + ), + caught_exc, + ) +import six +import urllib3.exceptions # pylint: disable=ungrouped-imports from google.auth import exceptions from google.auth import transport diff --git a/packages/google-auth/tests/compute_engine/test_credentials.py b/packages/google-auth/tests/compute_engine/test_credentials.py index ee343fe4e7b8..c78511b568cc 100644 --- a/packages/google-auth/tests/compute_engine/test_credentials.py +++ b/packages/google-auth/tests/compute_engine/test_credentials.py @@ -75,7 +75,7 @@ def test_refresh_success(self, get, utcnow): def test_refresh_error(self, get): get.side_effect = exceptions.TransportError('http error') - with pytest.raises(exceptions.RefreshError) as excinfo: + with pytest.raises(exceptions.RefreshError): self.credentials.refresh(None) @mock.patch('google.auth.compute_engine._metadata.get', autospec=True) From 148e6de125295ac21c4da0f0863c877eb1d0f76a Mon Sep 17 00:00:00 2001 From: Danny Hermes Date: Thu, 9 Nov 2017 12:18:58 -0800 Subject: [PATCH 158/966] Adding back "wrapped" exceptions in Refresh/Transport errors. --- packages/google-auth/google/auth/_default.py | 15 ++++++++------- .../google/auth/compute_engine/credentials.py | 2 +- .../google/auth/transport/_http_client.py | 2 +- .../google-auth/google/auth/transport/requests.py | 2 +- .../google-auth/google/auth/transport/urllib3.py | 2 +- .../tests/compute_engine/test_credentials.py | 4 +++- packages/google-auth/tests/test__default.py | 2 ++ 7 files changed, 17 insertions(+), 12 deletions(-) diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index 06f52bbd83be..5410a4c2d2d2 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -71,7 +71,8 @@ def _load_credentials_from_file(filename): info = json.load(file_obj) except ValueError as caught_exc: new_exc = exceptions.DefaultCredentialsError( - 'File {} is not a valid json file.'.format(filename)) + 'File {} is not a valid json file.'.format(filename), + caught_exc) six.raise_from(new_exc, caught_exc) # The type key should indicate that the file is either a service account @@ -84,9 +85,9 @@ def _load_credentials_from_file(filename): try: credentials = _cloud_sdk.load_authorized_user_credentials(info) except ValueError as caught_exc: - new_exc = exceptions.DefaultCredentialsError( - 'Failed to load authorized user credentials from {}'.format( - filename)) + msg = 'Failed to load authorized user credentials from {}'.format( + filename) + new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) six.raise_from(new_exc, caught_exc) # Authorized user credentials do not contain the project ID. return credentials, None @@ -98,9 +99,9 @@ def _load_credentials_from_file(filename): credentials = ( service_account.Credentials.from_service_account_info(info)) except ValueError as caught_exc: - new_exc = exceptions.DefaultCredentialsError( - 'Failed to load service account credentials from {}'.format( - filename)) + msg = 'Failed to load service account credentials from {}'.format( + filename) + new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) six.raise_from(new_exc, caught_exc) return credentials, info.get('project_id') diff --git a/packages/google-auth/google/auth/compute_engine/credentials.py b/packages/google-auth/google/auth/compute_engine/credentials.py index cbc7cf4f328a..3841df2a4aa6 100644 --- a/packages/google-auth/google/auth/compute_engine/credentials.py +++ b/packages/google-auth/google/auth/compute_engine/credentials.py @@ -92,7 +92,7 @@ def refresh(self, request): request, service_account=self._service_account_email) except exceptions.TransportError as caught_exc: - new_exc = exceptions.RefreshError() + new_exc = exceptions.RefreshError(caught_exc) six.raise_from(new_exc, caught_exc) @property diff --git a/packages/google-auth/google/auth/transport/_http_client.py b/packages/google-auth/google/auth/transport/_http_client.py index 35b100568c27..08b1ab6c7cae 100644 --- a/packages/google-auth/google/auth/transport/_http_client.py +++ b/packages/google-auth/google/auth/transport/_http_client.py @@ -106,7 +106,7 @@ def __call__(self, url, method='GET', body=None, headers=None, return Response(response) except (http_client.HTTPException, socket.error) as caught_exc: - new_exc = exceptions.TransportError() + new_exc = exceptions.TransportError(caught_exc) six.raise_from(new_exc, caught_exc) finally: diff --git a/packages/google-auth/google/auth/transport/requests.py b/packages/google-auth/google/auth/transport/requests.py index 66ff0d3e20c4..a49d3de0ea29 100644 --- a/packages/google-auth/google/auth/transport/requests.py +++ b/packages/google-auth/google/auth/transport/requests.py @@ -118,7 +118,7 @@ def __call__(self, url, method='GET', body=None, headers=None, **kwargs) return _Response(response) except requests.exceptions.RequestException as caught_exc: - new_exc = exceptions.TransportError() + new_exc = exceptions.TransportError(caught_exc) six.raise_from(new_exc, caught_exc) diff --git a/packages/google-auth/google/auth/transport/urllib3.py b/packages/google-auth/google/auth/transport/urllib3.py index ec20fb64f566..37eb31757bd0 100644 --- a/packages/google-auth/google/auth/transport/urllib3.py +++ b/packages/google-auth/google/auth/transport/urllib3.py @@ -133,7 +133,7 @@ def __call__(self, url, method='GET', body=None, headers=None, method, url, body=body, headers=headers, **kwargs) return _Response(response) except urllib3.exceptions.HTTPError as caught_exc: - new_exc = exceptions.TransportError() + new_exc = exceptions.TransportError(caught_exc) six.raise_from(new_exc, caught_exc) diff --git a/packages/google-auth/tests/compute_engine/test_credentials.py b/packages/google-auth/tests/compute_engine/test_credentials.py index c78511b568cc..ae2597d300f3 100644 --- a/packages/google-auth/tests/compute_engine/test_credentials.py +++ b/packages/google-auth/tests/compute_engine/test_credentials.py @@ -75,9 +75,11 @@ def test_refresh_success(self, get, utcnow): def test_refresh_error(self, get): get.side_effect = exceptions.TransportError('http error') - with pytest.raises(exceptions.RefreshError): + with pytest.raises(exceptions.RefreshError) as excinfo: self.credentials.refresh(None) + assert excinfo.match(r'http error') + @mock.patch('google.auth.compute_engine._metadata.get', autospec=True) def test_before_request_refreshes(self, get): get.side_effect = [{ diff --git a/packages/google-auth/tests/test__default.py b/packages/google-auth/tests/test__default.py index 090cea688929..2df8a44f839c 100644 --- a/packages/google-auth/tests/test__default.py +++ b/packages/google-auth/tests/test__default.py @@ -85,6 +85,7 @@ def test__load_credentials_from_file_authorized_user_bad_format(tmpdir): _default._load_credentials_from_file(str(filename)) assert excinfo.match(r'Failed to load authorized user') + assert excinfo.match(r'missing fields') def test__load_credentials_from_file_service_account(): @@ -102,6 +103,7 @@ def test__load_credentials_from_file_service_account_bad_format(tmpdir): _default._load_credentials_from_file(str(filename)) assert excinfo.match(r'Failed to load service account') + assert excinfo.match(r'missing fields') @mock.patch.dict(os.environ, {}, clear=True) From 0036df80b9eb28bd815ea6f9b0754ec929adce95 Mon Sep 17 00:00:00 2001 From: bene Date: Fri, 10 Nov 2017 21:48:59 +0000 Subject: [PATCH 159/966] Updating docs for creating RSASigner from string (#213) --- packages/google-auth/google/auth/crypt/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/crypt/__init__.py b/packages/google-auth/google/auth/crypt/__init__.py index fa59662e0c3b..7baa206ecf45 100644 --- a/packages/google-auth/google/auth/crypt/__init__.py +++ b/packages/google-auth/google/auth/crypt/__init__.py @@ -29,7 +29,7 @@ To sign messages use :class:`RSASigner` with a private key:: private_key = open('private_key.pem').read() - signer = crypt.RSASigner(private_key) + signer = crypt.RSASigner.from_string(private_key) signature = signer.sign(message) """ From 5a34868ecb4a2cd1485033ca054c249c33080bd5 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Mon, 13 Nov 2017 10:06:55 -0800 Subject: [PATCH 160/966] Exclude compiled Python files in source distributions (#215) --- packages/google-auth/MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/google-auth/MANIFEST.in b/packages/google-auth/MANIFEST.in index 401787fe7bf3..2c28207f18c0 100644 --- a/packages/google-auth/MANIFEST.in +++ b/packages/google-auth/MANIFEST.in @@ -1,2 +1,3 @@ include README.rst LICENSE CHANGELOG.rst recursive-include tests * +global-exclude *.pyc __pycache__ From fa4de54d51bcbaecaa33766a8b0b7bf55e76fdad Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Mon, 13 Nov 2017 10:17:29 -0800 Subject: [PATCH 161/966] Release v1.2.1 (#216) --- packages/google-auth/CHANGELOG.rst | 8 ++++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.rst b/packages/google-auth/CHANGELOG.rst index e281f4a7d623..d359cf6dc4a5 100644 --- a/packages/google-auth/CHANGELOG.rst +++ b/packages/google-auth/CHANGELOG.rst @@ -1,6 +1,14 @@ Changelog ========= +v1.2.1 +------ + +- Excluded compiled Python files in source distributions. (#215) +- Updated docs for creating RSASigner from string. (#213) +- Use ``six.raise_from`` wherever possible. (#212) +- Fixed a typo in a comment ``seconds`` not ``sections``. (#210) + v1.2.0 ------ diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index d76570fc0dba..9308707e9200 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -32,7 +32,7 @@ setup( name='google-auth', - version='1.2.0', + version='1.2.1', author='Google Cloud Platform', author_email='jonwayne+google-auth@google.com', description='Google Authentication Library', From d1ec06515ac4dc4ea71be616fcc97551c6a02086 Mon Sep 17 00:00:00 2001 From: Yuki Soma Date: Tue, 28 Nov 2017 02:33:12 +0900 Subject: [PATCH 162/966] transport.requests: make credential refresh robust (#220) add refresh_timeout parameter to AuthorizedSession constructor set the retry adapter --- .../google/auth/transport/requests.py | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/google/auth/transport/requests.py b/packages/google-auth/google/auth/transport/requests.py index a49d3de0ea29..2268243a6057 100644 --- a/packages/google-auth/google/auth/transport/requests.py +++ b/packages/google-auth/google/auth/transport/requests.py @@ -16,6 +16,7 @@ from __future__ import absolute_import +import functools import logging try: @@ -29,6 +30,7 @@ ), caught_exc, ) +import requests.adapters # pylint: disable=ungrouped-imports import requests.exceptions # pylint: disable=ungrouped-imports import six # pylint: disable=ungrouped-imports @@ -146,22 +148,35 @@ class AuthorizedSession(requests.Session): retried. max_refresh_attempts (int): The maximum number of times to attempt to refresh the credentials and retry the request. + refresh_timeout (Optional[int]): The timeout value in seconds for + credential refresh HTTP requests. kwargs: Additional arguments passed to the :class:`requests.Session` constructor. """ def __init__(self, credentials, refresh_status_codes=transport.DEFAULT_REFRESH_STATUS_CODES, max_refresh_attempts=transport.DEFAULT_MAX_REFRESH_ATTEMPTS, + refresh_timeout=None, **kwargs): super(AuthorizedSession, self).__init__(**kwargs) self.credentials = credentials self._refresh_status_codes = refresh_status_codes self._max_refresh_attempts = max_refresh_attempts + self._refresh_timeout = refresh_timeout + + auth_request_session = requests.Session() + + # Using an adapter to make HTTP requests robust to network errors. + # This adapter retrys HTTP requests when network errors occur + # and the requests seems safely retryable. + retry_adapter = requests.adapters.HTTPAdapter(max_retries=3) + auth_request_session.mount("https://", retry_adapter) + # Request instance used by internal methods (for example, # credentials.refresh). # Do not pass `self` as the session here, as it can lead to infinite # recursion. - self._auth_request = Request() + self._auth_request = Request(auth_request_session) def request(self, method, url, data=None, headers=None, **kwargs): """Implementation of Requests' request.""" @@ -198,7 +213,9 @@ def request(self, method, url, data=None, headers=None, **kwargs): response.status_code, _credential_refresh_attempt + 1, self._max_refresh_attempts) - self.credentials.refresh(self._auth_request) + auth_request_with_timeout = functools.partial( + self._auth_request, timeout=self._refresh_timeout) + self.credentials.refresh(auth_request_with_timeout) # Recurse. Pass in the original headers, not our modified set. return self.request( From 48564b34133f6b371decb2f61f5a30f295b2ea59 Mon Sep 17 00:00:00 2001 From: Ondrej Medek Date: Mon, 27 Nov 2017 18:33:46 +0100 Subject: [PATCH 163/966] Fix ReadOnlyScoped doc with_scopes (#219) --- packages/google-auth/google/auth/credentials.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/credentials.py b/packages/google-auth/google/auth/credentials.py index 1fb5dece27af..db2869c2586d 100644 --- a/packages/google-auth/google/auth/credentials.py +++ b/packages/google-auth/google/auth/credentials.py @@ -173,7 +173,7 @@ class ReadOnlyScoped(object): if credentials.requires_scopes: # Scoping is required. - credentials = credentials.create_scoped(['one', 'two']) + credentials = credentials.with_scopes(scopes=['one', 'two']) Credentials that require scopes must either be constructed with scopes:: From 5f37bf9c257bc7f926f6fe7c770982b8edede133 Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Tue, 5 Dec 2017 09:29:59 -0800 Subject: [PATCH 164/966] Add google.oauth2.credentials.Credentials.from_authorized_user_file (#226) --- .../google-auth/google/auth/_cloud_sdk.py | 20 +----- .../google-auth/google/oauth2/credentials.py | 62 +++++++++++++++++++ .../tests/oauth2/test_credentials.py | 49 +++++++++++++++ packages/google-auth/tests/test__cloud_sdk.py | 3 +- 4 files changed, 115 insertions(+), 19 deletions(-) diff --git a/packages/google-auth/google/auth/_cloud_sdk.py b/packages/google-auth/google/auth/_cloud_sdk.py index 898c6ec8de71..31be5e7cc9dd 100644 --- a/packages/google-auth/google/auth/_cloud_sdk.py +++ b/packages/google-auth/google/auth/_cloud_sdk.py @@ -18,13 +18,9 @@ import os import subprocess -import six - from google.auth import environment_vars import google.oauth2.credentials -# The Google OAuth 2.0 token endpoint. Used for authorized user credentials. -_GOOGLE_OAUTH2_TOKEN_ENDPOINT = 'https://accounts.google.com/o/oauth2/token' # The ~/.config subdirectory containing gcloud credentials. _CONFIG_DIRECTORY = 'gcloud' @@ -94,20 +90,8 @@ def load_authorized_user_credentials(info): Raises: ValueError: if the info is in the wrong format or missing data. """ - keys_needed = set(('refresh_token', 'client_id', 'client_secret')) - missing = keys_needed.difference(six.iterkeys(info)) - - if missing: - raise ValueError( - 'Authorized user info was not in the expected format, missing ' - 'fields {}.'.format(', '.join(missing))) - - return google.oauth2.credentials.Credentials( - None, # No access token, must be refreshed. - refresh_token=info['refresh_token'], - token_uri=_GOOGLE_OAUTH2_TOKEN_ENDPOINT, - client_id=info['client_id'], - client_secret=info['client_secret']) + return google.oauth2.credentials.Credentials.from_authorized_user_info( + info) def get_project_id(): diff --git a/packages/google-auth/google/oauth2/credentials.py b/packages/google-auth/google/oauth2/credentials.py index f1df88791645..24b3a3eedf43 100644 --- a/packages/google-auth/google/oauth2/credentials.py +++ b/packages/google-auth/google/oauth2/credentials.py @@ -31,11 +31,20 @@ .. _rfc6749 section 4.1: https://tools.ietf.org/html/rfc6749#section-4.1 """ +import io +import json + +import six + from google.auth import _helpers from google.auth import credentials from google.oauth2 import _client +# The Google OAuth 2.0 token endpoint. Used for authorized user credentials. +_GOOGLE_OAUTH2_TOKEN_ENDPOINT = 'https://accounts.google.com/o/oauth2/token' + + class Credentials(credentials.ReadOnlyScoped, credentials.Credentials): """Credentials using OAuth 2.0 access and refresh tokens.""" @@ -120,3 +129,56 @@ def refresh(self, request): self.expiry = expiry self._refresh_token = refresh_token self._id_token = grant_response.get('id_token') + + @classmethod + def from_authorized_user_info(cls, info, scopes=None): + """Creates a Credentials instance from parsed authorized user info. + + Args: + info (Mapping[str, str]): The authorized user info in Google + format. + scopes (Sequence[str]): Optional list of scopes to include in the + credentials. + + Returns: + google.oauth2.credentials.Credentials: The constructed + credentials. + + Raises: + ValueError: If the info is not in the expected format. + """ + keys_needed = set(('refresh_token', 'client_id', 'client_secret')) + missing = keys_needed.difference(six.iterkeys(info)) + + if missing: + raise ValueError( + 'Authorized user info was not in the expected format, missing ' + 'fields {}.'.format(', '.join(missing))) + + return Credentials( + None, # No access token, must be refreshed. + refresh_token=info['refresh_token'], + token_uri=_GOOGLE_OAUTH2_TOKEN_ENDPOINT, + scopes=scopes, + client_id=info['client_id'], + client_secret=info['client_secret']) + + @classmethod + def from_authorized_user_file(cls, filename, scopes=None): + """Creates a Credentials instance from an authorized user json file. + + Args: + filename (str): The path to the authorized user json file. + scopes (Sequence[str]): Optional list of scopes to include in the + credentials. + + Returns: + google.oauth2.credentials.Credentials: The constructed + credentials. + + Raises: + ValueError: If the file is not in the expected format. + """ + with io.open(filename, 'r', encoding='utf-8') as json_file: + data = json.load(json_file) + return cls.from_authorized_user_info(data, scopes) diff --git a/packages/google-auth/tests/oauth2/test_credentials.py b/packages/google-auth/tests/oauth2/test_credentials.py index 5e09d6f0ad89..906436353e64 100644 --- a/packages/google-auth/tests/oauth2/test_credentials.py +++ b/packages/google-auth/tests/oauth2/test_credentials.py @@ -13,6 +13,8 @@ # limitations under the License. import datetime +import json +import os import mock @@ -21,6 +23,14 @@ from google.oauth2 import credentials +DATA_DIR = os.path.join(os.path.dirname(__file__), '..', 'data') + +AUTH_USER_JSON_FILE = os.path.join(DATA_DIR, 'authorized_user.json') + +with open(AUTH_USER_JSON_FILE, 'r') as fh: + AUTH_USER_INFO = json.load(fh) + + class TestCredentials(object): TOKEN_URI = 'https://example.com/oauth2/token' REFRESH_TOKEN = 'refresh_token' @@ -84,3 +94,42 @@ def test_refresh_success(self, unused_utcnow, refresh_grant): # Check that the credentials are valid (have a token and are not # expired) assert credentials.valid + + def test_from_authorized_user_info(self): + info = AUTH_USER_INFO.copy() + + creds = credentials.Credentials.from_authorized_user_info(info) + assert creds.client_secret == info['client_secret'] + assert creds.client_id == info['client_id'] + assert creds.refresh_token == info['refresh_token'] + assert creds.token_uri == credentials._GOOGLE_OAUTH2_TOKEN_ENDPOINT + assert creds.scopes is None + + scopes = ['email', 'profile'] + creds = credentials.Credentials.from_authorized_user_info( + info, scopes) + assert creds.client_secret == info['client_secret'] + assert creds.client_id == info['client_id'] + assert creds.refresh_token == info['refresh_token'] + assert creds.token_uri == credentials._GOOGLE_OAUTH2_TOKEN_ENDPOINT + assert creds.scopes == scopes + + def test_from_authorized_user_file(self): + info = AUTH_USER_INFO.copy() + + creds = credentials.Credentials.from_authorized_user_file( + AUTH_USER_JSON_FILE) + assert creds.client_secret == info['client_secret'] + assert creds.client_id == info['client_id'] + assert creds.refresh_token == info['refresh_token'] + assert creds.token_uri == credentials._GOOGLE_OAUTH2_TOKEN_ENDPOINT + assert creds.scopes is None + + scopes = ['email', 'profile'] + creds = credentials.Credentials.from_authorized_user_file( + AUTH_USER_JSON_FILE, scopes) + assert creds.client_secret == info['client_secret'] + assert creds.client_id == info['client_id'] + assert creds.refresh_token == info['refresh_token'] + assert creds.token_uri == credentials._GOOGLE_OAUTH2_TOKEN_ENDPOINT + assert creds.scopes == scopes diff --git a/packages/google-auth/tests/test__cloud_sdk.py b/packages/google-auth/tests/test__cloud_sdk.py index c14fc20962aa..58c7270b6b9d 100644 --- a/packages/google-auth/tests/test__cloud_sdk.py +++ b/packages/google-auth/tests/test__cloud_sdk.py @@ -145,7 +145,8 @@ def test_load_authorized_user_credentials(): assert credentials._client_id == AUTHORIZED_USER_FILE_DATA['client_id'] assert (credentials._client_secret == AUTHORIZED_USER_FILE_DATA['client_secret']) - assert credentials._token_uri == _cloud_sdk._GOOGLE_OAUTH2_TOKEN_ENDPOINT + assert (credentials._token_uri == + google.oauth2.credentials._GOOGLE_OAUTH2_TOKEN_ENDPOINT) def test_load_authorized_user_credentials_bad_format(): From 5c03b7bc52b35427a976269892cec25629b4766f Mon Sep 17 00:00:00 2001 From: Danny Hermes Date: Wed, 13 Dec 2017 12:08:15 -0800 Subject: [PATCH 165/966] Docstring fix for has_scopes() and with_scopes(). (#228) --- packages/google-auth/google/auth/credentials.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/credentials.py b/packages/google-auth/google/auth/credentials.py index db2869c2586d..64b333823fdc 100644 --- a/packages/google-auth/google/auth/credentials.py +++ b/packages/google-auth/google/auth/credentials.py @@ -209,6 +209,9 @@ def has_scopes(self, scopes): .. warning: This method is not guaranteed to be accurate if the credentials are :attr:`~Credentials.invalid`. + Args: + scopes (Sequence[str]): The list of scopes to check. + Returns: bool: True if the credentials have the given scopes. """ @@ -248,7 +251,8 @@ def with_scopes(self, scopes): """Create a copy of these credentials with the specified scopes. Args: - scopes (Sequence[str]): The list of scopes to request. + scopes (Sequence[str]): The list of scopes to attach to the + current credentials. Raises: NotImplementedError: If the credentials' scopes can not be changed. From 45572c0038dd2fe5027594d3c449e43d9d8272b0 Mon Sep 17 00:00:00 2001 From: Jacob Hayes Date: Wed, 13 Dec 2017 14:09:47 -0600 Subject: [PATCH 166/966] Check for the project ID env var before warning about missing project ID (#227) * Check for project_id envvar before warning Resolves GoogleCloudPlatform/google-auth-library-python#221 * Add missed env check and fix line length * Remove redundant missing-project log statements * Add _default.default() test without project_id * Fix line length --- packages/google-auth/google/auth/_default.py | 25 +++++++------------- packages/google-auth/tests/test__default.py | 24 ++++++++++++++++++- 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index 5410a4c2d2d2..d63dcee3bcb6 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -129,12 +129,6 @@ def _get_gcloud_sdk_credentials(): if not project_id: project_id = _cloud_sdk.get_project_id() - if not project_id: - _LOGGER.warning( - 'No project ID could be determined from the Cloud SDK ' - 'configuration. Consider running `gcloud config set project` or ' - 'setting the %s environment variable', environment_vars.PROJECT) - return credentials, project_id @@ -147,12 +141,6 @@ def _get_explicit_environ_credentials(): credentials, project_id = _load_credentials_from_file( os.environ[environment_vars.CREDENTIALS]) - if not project_id: - _LOGGER.warning( - 'No project ID could be determined from the credentials at %s ' - 'Consider setting the %s environment variable', - environment_vars.CREDENTIALS, environment_vars.PROJECT) - return credentials, project_id else: @@ -188,10 +176,6 @@ def _get_gce_credentials(request=None): try: project_id = _metadata.get_project_id(request=request) except exceptions.TransportError: - _LOGGER.warning( - 'No project ID could be determined from the Compute Engine ' - 'metadata service. Consider setting the %s environment ' - 'variable.', environment_vars.PROJECT) project_id = None return compute_engine.Credentials(), project_id @@ -287,6 +271,13 @@ def default(scopes=None, request=None): credentials, project_id = checker() if credentials is not None: credentials = with_scopes_if_required(credentials, scopes) - return credentials, explicit_project_id or project_id + effective_project_id = explicit_project_id or project_id + if not effective_project_id: + _LOGGER.warning( + 'No project ID could be determined. Consider running ' + '`gcloud config set project` or setting the %s ' + 'environment variable', + environment_vars.PROJECT) + return credentials, effective_project_id raise exceptions.DefaultCredentialsError(_HELP_MESSAGE) diff --git a/packages/google-auth/tests/test__default.py b/packages/google-auth/tests/test__default.py index 2df8a44f839c..68c4fb035144 100644 --- a/packages/google-auth/tests/test__default.py +++ b/packages/google-auth/tests/test__default.py @@ -301,6 +301,28 @@ def test_default_explict_legacy_project_id(unused_get, monkeypatch): mock.sentinel.credentials, 'explicit-env') +@mock.patch( + 'logging.Logger.warning', + autospec=True) +@mock.patch( + 'google.auth._default._get_explicit_environ_credentials', + return_value=(mock.sentinel.credentials, None), autospec=True) +@mock.patch( + 'google.auth._default._get_gcloud_sdk_credentials', + return_value=(mock.sentinel.credentials, None), autospec=True) +@mock.patch( + 'google.auth._default._get_gae_credentials', + return_value=(mock.sentinel.credentials, None), autospec=True) +@mock.patch( + 'google.auth._default._get_gce_credentials', + return_value=(mock.sentinel.credentials, None), autospec=True) +def test_default_without_project_id( + unused_gce, unused_gae, unused_sdk, unused_explicit, logger_warning): + assert _default.default() == ( + mock.sentinel.credentials, None) + logger_warning.assert_called_with(mock.ANY, mock.ANY, mock.ANY) + + @mock.patch( 'google.auth._default._get_explicit_environ_credentials', return_value=(None, None), autospec=True) @@ -324,7 +346,7 @@ def test_default_fail(unused_gce, unused_gae, unused_sdk, unused_explicit): autospec=True) @mock.patch( 'google.auth.credentials.with_scopes_if_required', autospec=True) -def test_default_scoped(with_scopes, get): +def test_default_scoped(with_scopes, unused_get): scopes = ['one', 'two'] credentials, project_id = _default.default(scopes=scopes) From 673a56e25e31981c55e8359a7b811f26e966ccd3 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Fri, 15 Dec 2017 10:00:22 -0800 Subject: [PATCH 167/966] Drop direct pyasn1 dependency (#230) Instead, rely on pyans1-modules to pull the appropriate version. --- packages/google-auth/setup.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 9308707e9200..42da7a36775d 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -19,8 +19,7 @@ DEPENDENCIES = ( - 'pyasn1>=0.1.7', - 'pyasn1-modules>=0.0.5', + 'pyasn1-modules>=0.2.1', 'rsa>=3.1.4', 'six>=1.9.0', 'cachetools>=2.0.0', From 982ceaaf681dbd0ef09c6e8b3298bf83deef0178 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Tue, 9 Jan 2018 10:41:11 -0800 Subject: [PATCH 168/966] Drop pylint for the time being. It served us well (#231) --- packages/google-auth/.travis.yml | 2 +- packages/google-auth/pylint.config.py | 71 --------------------------- packages/google-auth/tox.ini | 10 +--- 3 files changed, 3 insertions(+), 80 deletions(-) delete mode 100644 packages/google-auth/pylint.config.py diff --git a/packages/google-auth/.travis.yml b/packages/google-auth/.travis.yml index 8fc00d538b2a..8c6728826ffa 100644 --- a/packages/google-auth/.travis.yml +++ b/packages/google-auth/.travis.yml @@ -2,7 +2,7 @@ language: python sudo: false matrix: include: - - python: 3.5 + - python: 3.6 env: TOXENV=lint - python: 3.6 env: TOXENV=docs diff --git a/packages/google-auth/pylint.config.py b/packages/google-auth/pylint.config.py deleted file mode 100644 index d63f3376e591..000000000000 --- a/packages/google-auth/pylint.config.py +++ /dev/null @@ -1,71 +0,0 @@ -# Copyright 2016 Google Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""This module is used to config gcp-devrel-py-tools run-pylint.""" - -import copy - -library_additions = { - 'MESSAGES CONTROL': { - 'disable': [ - 'I', - 'import-error', - 'no-member', - 'protected-access', - 'redefined-variable-type', - 'similarities', - 'no-else-return', - ], - }, -} - -library_replacements = { - 'MASTER': { - 'ignore': ['CVS', '.git', '.cache', '.tox', '.nox'], - 'load-plugins': 'pylint.extensions.check_docs', - }, - 'REPORTS': { - 'reports': 'no', - }, - 'BASIC': { - 'method-rgx': '[a-z_][a-z0-9_]{2,40}$', - 'function-rgx': '[a-z_][a-z0-9_]{2,40}$', - }, - 'TYPECHECK': { - 'ignored-modules': ['six', 'google.protobuf'], - }, - 'DESIGN': { - 'min-public-methods': '0', - 'max-args': '10', - 'max-attributes': '15', - }, -} - -test_additions = copy.deepcopy(library_additions) -test_additions['MESSAGES CONTROL']['disable'].extend([ - 'missing-docstring', - 'no-self-use', - 'redefined-outer-name', - 'unused-argument', - 'no-name-in-module', -]) -test_replacements = copy.deepcopy(library_replacements) -test_replacements.setdefault('BASIC', {}) -test_replacements['BASIC'].update({ - 'good-names': ['i', 'j', 'k', 'ex', 'Run', '_', 'fh', 'pytestmark'], - 'method-rgx': '[a-z_][a-z0-9_]{2,80}$', - 'function-rgx': '[a-z_][a-z0-9_]{2,80}$', -}) - -ignored_files = () diff --git a/packages/google-auth/tox.ini b/packages/google-auth/tox.ini index ddc6430692fd..b8031901dc49 100644 --- a/packages/google-auth/tox.ini +++ b/packages/google-auth/tox.ini @@ -70,20 +70,14 @@ deps = commands = make -C docs html [testenv:lint] -basepython = python3.5 +basepython = python3.6 commands = - python setup.py check --metadata --restructuredtext --strict flake8 \ --import-order-style=google \ --application-import-names="google,tests,system_tests" \ google tests - gcp-devrel-py-tools run-pylint \ - --config pylint.config.py \ - --library-filesets google \ - --test-filesets tests system_tests + python setup.py check --metadata --restructuredtext --strict deps = flake8 flake8-import-order - pylint docutils - gcp-devrel-py-tools From 222b2f30d2696dc2e44d8338807495c8d397b3a3 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Tue, 9 Jan 2018 10:42:48 -0800 Subject: [PATCH 169/966] Release v1.3.0 (#233) --- packages/google-auth/CHANGELOG.rst | 10 ++++++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.rst b/packages/google-auth/CHANGELOG.rst index d359cf6dc4a5..58052f5868c5 100644 --- a/packages/google-auth/CHANGELOG.rst +++ b/packages/google-auth/CHANGELOG.rst @@ -1,6 +1,16 @@ Changelog ========= +v1.3.0 +------ + +- Added ``google.oauth2.credentials.Credentials.from_authorized_user_file`` (#226) +- Dropped direct pyasn1 dependency in favor of letting ``pyasn1-modules`` specify the right version. (#230) +- ``default()`` now checks for the project ID environment var before warning about missing project ID. (#227) +- Fixed the docstrings for ``has_scopes()`` and ``with_scopes()``. (#228) +- Fixed example in docstring for ``ReadOnlyScoped``. (#219) +- Made ``transport.requests`` use timeouts and retries to improve reliability. (#220) + v1.2.1 ------ diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 42da7a36775d..0a21f255d747 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -31,7 +31,7 @@ setup( name='google-auth', - version='1.2.1', + version='1.3.0', author='Google Cloud Platform', author_email='jonwayne+google-auth@google.com', description='Google Authentication Library', From 1c24ddd790e6cb27511c5bf9df2feb09334218bb Mon Sep 17 00:00:00 2001 From: Christophe Taton Date: Thu, 8 Feb 2018 14:12:23 -0800 Subject: [PATCH 170/966] Add `google.oauth2.service_account.IDTokenCredentials`. (#234) --- .../google-auth/google/auth/app_engine.py | 2 +- packages/google-auth/google/auth/jwt.py | 4 +- packages/google-auth/google/oauth2/_client.py | 46 ++++ .../google/oauth2/service_account.py | 210 +++++++++++++++++- .../google-auth/tests/oauth2/test__client.py | 48 ++++ .../tests/oauth2/test_service_account.py | 123 ++++++++++ 6 files changed, 427 insertions(+), 6 deletions(-) diff --git a/packages/google-auth/google/auth/app_engine.py b/packages/google-auth/google/auth/app_engine.py index fa13f8ef8076..f47dae12682c 100644 --- a/packages/google-auth/google/auth/app_engine.py +++ b/packages/google-auth/google/auth/app_engine.py @@ -136,7 +136,7 @@ def requires_scopes(self): @_helpers.copy_docstring(credentials.Scoped) def with_scopes(self, scopes): - return Credentials( + return self.__class__( scopes=scopes, service_account_id=self._service_account_id) @_helpers.copy_docstring(credentials.Signing) diff --git a/packages/google-auth/google/auth/jwt.py b/packages/google-auth/google/auth/jwt.py index 02533762fac8..695737496797 100644 --- a/packages/google-auth/google/auth/jwt.py +++ b/packages/google-auth/google/auth/jwt.py @@ -438,7 +438,7 @@ def with_claims(self, issuer=None, subject=None, audience=None, new_additional_claims = copy.deepcopy(self._additional_claims) new_additional_claims.update(additional_claims or {}) - return Credentials( + return self.__class__( self._signer, issuer=issuer if issuer is not None else self._issuer, subject=subject if subject is not None else self._subject, @@ -643,7 +643,7 @@ def with_claims(self, issuer=None, subject=None, additional_claims=None): new_additional_claims = copy.deepcopy(self._additional_claims) new_additional_claims.update(additional_claims or {}) - return OnDemandCredentials( + return self.__class__( self._signer, issuer=issuer if issuer is not None else self._issuer, subject=subject if subject is not None else self._subject, diff --git a/packages/google-auth/google/oauth2/_client.py b/packages/google-auth/google/oauth2/_client.py index 66251df4135d..dc35be271f48 100644 --- a/packages/google-auth/google/oauth2/_client.py +++ b/packages/google-auth/google/oauth2/_client.py @@ -32,6 +32,7 @@ from google.auth import _helpers from google.auth import exceptions +from google.auth import jwt _URLENCODED_CONTENT_TYPE = 'application/x-www-form-urlencoded' _JWT_GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:jwt-bearer' @@ -155,6 +156,51 @@ def jwt_grant(request, token_uri, assertion): return access_token, expiry, response_data +def id_token_jwt_grant(request, token_uri, assertion): + """Implements the JWT Profile for OAuth 2.0 Authorization Grants, but + requests an OpenID Connect ID Token instead of an access token. + + This is a variant on the standard JWT Profile that is currently unique + to Google. This was added for the benefit of authenticating to services + that require ID Tokens instead of access tokens or JWT bearer tokens. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + token_uri (str): The OAuth 2.0 authorization server's token endpoint + URI. + assertion (str): JWT token signed by a service account. The token's + payload must include a ``target_audience`` claim. + + Returns: + Tuple[str, Optional[datetime], Mapping[str, str]]: + The (encoded) Open ID Connect ID Token, expiration, and additional + data returned by the endpoint. + + Raises: + google.auth.exceptions.RefreshError: If the token endpoint returned + an error. + """ + body = { + 'assertion': assertion, + 'grant_type': _JWT_GRANT_TYPE, + } + + response_data = _token_endpoint_request(request, token_uri, body) + + try: + id_token = response_data['id_token'] + except KeyError as caught_exc: + new_exc = exceptions.RefreshError( + 'No ID token in response.', response_data) + six.raise_from(new_exc, caught_exc) + + payload = jwt.decode(id_token, verify=False) + expiry = datetime.datetime.utcfromtimestamp(payload['exp']) + + return id_token, expiry, response_data + + def refresh_grant(request, token_uri, refresh_token, client_id, client_secret): """Implements the OAuth 2.0 refresh token grant. diff --git a/packages/google-auth/google/oauth2/service_account.py b/packages/google-auth/google/oauth2/service_account.py index 54bd8d671afb..c60c56546443 100644 --- a/packages/google-auth/google/oauth2/service_account.py +++ b/packages/google-auth/google/oauth2/service_account.py @@ -230,7 +230,7 @@ def requires_scopes(self): @_helpers.copy_docstring(credentials.Scoped) def with_scopes(self, scopes): - return Credentials( + return self.__class__( self._signer, service_account_email=self._service_account_email, scopes=scopes, @@ -249,7 +249,7 @@ def with_subject(self, subject): google.auth.service_account.Credentials: A new credentials instance. """ - return Credentials( + return self.__class__( self._signer, service_account_email=self._service_account_email, scopes=self._scopes, @@ -273,7 +273,7 @@ def with_claims(self, additional_claims): new_additional_claims = copy.deepcopy(self._additional_claims) new_additional_claims.update(additional_claims or {}) - return Credentials( + return self.__class__( self._signer, service_account_email=self._service_account_email, scopes=self._scopes, @@ -336,3 +336,207 @@ def signer(self): @_helpers.copy_docstring(credentials.Signing) def signer_email(self): return self._service_account_email + + +class IDTokenCredentials(credentials.Signing, credentials.Credentials): + """Open ID Connect ID Token-based service account credentials. + + These credentials are largely similar to :class:`.Credentials`, but instead + of using an OAuth 2.0 Access Token as the bearer token, they use an Open + ID Connect ID Token as the bearer token. These credentials are useful when + communicating to services that require ID Tokens and can not accept access + tokens. + + Usually, you'll create these credentials with one of the helper + constructors. To create credentials using a Google service account + private key JSON file:: + + credentials = ( + service_account.IDTokenCredentials.from_service_account_file( + 'service-account.json')) + + Or if you already have the service account file loaded:: + + service_account_info = json.load(open('service_account.json')) + credentials = ( + service_account.IDTokenCredentials.from_service_account_info( + service_account_info)) + + Both helper methods pass on arguments to the constructor, so you can + specify additional scopes and a subject if necessary:: + + credentials = ( + service_account.IDTokenCredentials.from_service_account_file( + 'service-account.json', + scopes=['email'], + subject='user@example.com')) +` + The credentials are considered immutable. If you want to modify the scopes + or the subject used for delegation, use :meth:`with_scopes` or + :meth:`with_subject`:: + + scoped_credentials = credentials.with_scopes(['email']) + delegated_credentials = credentials.with_subject(subject) + + """ + def __init__(self, signer, service_account_email, token_uri, + target_audience, additional_claims=None): + """ + Args: + signer (google.auth.crypt.Signer): The signer used to sign JWTs. + service_account_email (str): The service account's email. + token_uri (str): The OAuth 2.0 Token URI. + target_audience (str): The intended audience for these credentials, + used when requesting the ID Token. The ID Token's ``aud`` claim + will be set to this string. + additional_claims (Mapping[str, str]): Any additional claims for + the JWT assertion used in the authorization grant. + + .. note:: Typically one of the helper constructors + :meth:`from_service_account_file` or + :meth:`from_service_account_info` are used instead of calling the + constructor directly. + """ + super(IDTokenCredentials, self).__init__() + self._signer = signer + self._service_account_email = service_account_email + self._token_uri = token_uri + self._target_audience = target_audience + + if additional_claims is not None: + self._additional_claims = additional_claims + else: + self._additional_claims = {} + + @classmethod + def _from_signer_and_info(cls, signer, info, **kwargs): + """Creates a credentials instance from a signer and service account + info. + + Args: + signer (google.auth.crypt.Signer): The signer used to sign JWTs. + info (Mapping[str, str]): The service account info. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.jwt.IDTokenCredentials: The constructed credentials. + + Raises: + ValueError: If the info is not in the expected format. + """ + kwargs.setdefault('service_account_email', info['client_email']) + kwargs.setdefault('token_uri', info['token_uri']) + return cls(signer, **kwargs) + + @classmethod + def from_service_account_info(cls, info, **kwargs): + """Creates a credentials instance from parsed service account info. + + Args: + info (Mapping[str, str]): The service account info in Google + format. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.service_account.IDTokenCredentials: The constructed + credentials. + + Raises: + ValueError: If the info is not in the expected format. + """ + signer = _service_account_info.from_dict( + info, require=['client_email', 'token_uri']) + return cls._from_signer_and_info(signer, info, **kwargs) + + @classmethod + def from_service_account_file(cls, filename, **kwargs): + """Creates a credentials instance from a service account json file. + + Args: + filename (str): The path to the service account json file. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.service_account.IDTokenCredentials: The constructed + credentials. + """ + info, signer = _service_account_info.from_filename( + filename, require=['client_email', 'token_uri']) + return cls._from_signer_and_info(signer, info, **kwargs) + + def with_target_audience(self, target_audience): + """Create a copy of these credentials with the specified target + audience. + + Args: + target_audience (str): The intended audience for these credentials, + used when requesting the ID Token. + + Returns: + google.auth.service_account.IDTokenCredentials: A new credentials + instance. + """ + return self.__class__( + self._signer, + service_account_email=self._service_account_email, + token_uri=self._token_uri, + target_audience=target_audience, + additional_claims=self._additional_claims.copy()) + + def _make_authorization_grant_assertion(self): + """Create the OAuth 2.0 assertion. + + This assertion is used during the OAuth 2.0 grant to acquire an + ID token. + + Returns: + bytes: The authorization grant assertion. + """ + now = _helpers.utcnow() + lifetime = datetime.timedelta(seconds=_DEFAULT_TOKEN_LIFETIME_SECS) + expiry = now + lifetime + + payload = { + 'iat': _helpers.datetime_to_secs(now), + 'exp': _helpers.datetime_to_secs(expiry), + # The issuer must be the service account email. + 'iss': self.service_account_email, + # The audience must be the auth token endpoint's URI + 'aud': self._token_uri, + # The target audience specifies which service the ID token is + # intended for. + 'target_audience': self._target_audience + } + + payload.update(self._additional_claims) + + token = jwt.encode(self._signer, payload) + + return token + + @_helpers.copy_docstring(credentials.Credentials) + def refresh(self, request): + assertion = self._make_authorization_grant_assertion() + access_token, expiry, _ = _client.id_token_jwt_grant( + request, self._token_uri, assertion) + self.token = access_token + self.expiry = expiry + + @property + def service_account_email(self): + """The service account email.""" + return self._service_account_email + + @_helpers.copy_docstring(credentials.Signing) + def sign_bytes(self, message): + return self._signer.sign(message) + + @property + @_helpers.copy_docstring(credentials.Signing) + def signer(self): + return self._signer + + @property + @_helpers.copy_docstring(credentials.Signing) + def signer_email(self): + return self._service_account_email diff --git a/packages/google-auth/tests/oauth2/test__client.py b/packages/google-auth/tests/oauth2/test__client.py index 6aeb3d13ba33..3ec7fc62a822 100644 --- a/packages/google-auth/tests/oauth2/test__client.py +++ b/packages/google-auth/tests/oauth2/test__client.py @@ -14,6 +14,7 @@ import datetime import json +import os import mock import pytest @@ -21,11 +22,22 @@ from six.moves import http_client from six.moves import urllib +from google.auth import _helpers +from google.auth import crypt from google.auth import exceptions +from google.auth import jwt from google.auth import transport from google.oauth2 import _client +DATA_DIR = os.path.join(os.path.dirname(__file__), '..', 'data') + +with open(os.path.join(DATA_DIR, 'privatekey.pem'), 'rb') as fh: + PRIVATE_KEY_BYTES = fh.read() + +SIGNER = crypt.RSASigner.from_string(PRIVATE_KEY_BYTES, '1') + + def test__handle_error_response(): response_data = json.dumps({ 'error': 'help', @@ -129,6 +141,42 @@ def test_jwt_grant_no_access_token(): _client.jwt_grant(request, 'http://example.com', 'assertion_value') +def test_id_token_jwt_grant(): + now = _helpers.utcnow() + id_token_expiry = _helpers.datetime_to_secs(now) + id_token = jwt.encode(SIGNER, {'exp': id_token_expiry}).decode('utf-8') + request = make_request({ + 'id_token': id_token, + 'extra': 'data'}) + + token, expiry, extra_data = _client.id_token_jwt_grant( + request, 'http://example.com', 'assertion_value') + + # Check request call + verify_request_params(request, { + 'grant_type': _client._JWT_GRANT_TYPE, + 'assertion': 'assertion_value' + }) + + # Check result + assert token == id_token + # JWT does not store microseconds + now = now.replace(microsecond=0) + assert expiry == now + assert extra_data['extra'] == 'data' + + +def test_id_token_jwt_grant_no_access_token(): + request = make_request({ + # No access token. + 'expires_in': 500, + 'extra': 'data'}) + + with pytest.raises(exceptions.RefreshError): + _client.id_token_jwt_grant( + request, 'http://example.com', 'assertion_value') + + @mock.patch('google.auth._helpers.utcnow', return_value=datetime.datetime.min) def test_refresh_grant(unused_utcnow): request = make_request({ diff --git a/packages/google-auth/tests/oauth2/test_service_account.py b/packages/google-auth/tests/oauth2/test_service_account.py index 9c235db94d06..54ac0f5e9c98 100644 --- a/packages/google-auth/tests/oauth2/test_service_account.py +++ b/packages/google-auth/tests/oauth2/test_service_account.py @@ -216,3 +216,126 @@ def test_before_request_refreshes(self, jwt_grant): # Credentials should now be valid. assert credentials.valid + + +class TestIDTokenCredentials(object): + SERVICE_ACCOUNT_EMAIL = 'service-account@example.com' + TOKEN_URI = 'https://example.com/oauth2/token' + TARGET_AUDIENCE = 'https://example.com' + + @classmethod + def make_credentials(cls): + return service_account.IDTokenCredentials( + SIGNER, cls.SERVICE_ACCOUNT_EMAIL, cls.TOKEN_URI, + cls.TARGET_AUDIENCE) + + def test_from_service_account_info(self): + credentials = ( + service_account.IDTokenCredentials.from_service_account_info( + SERVICE_ACCOUNT_INFO, + target_audience=self.TARGET_AUDIENCE)) + + assert (credentials._signer.key_id == + SERVICE_ACCOUNT_INFO['private_key_id']) + assert (credentials.service_account_email == + SERVICE_ACCOUNT_INFO['client_email']) + assert credentials._token_uri == SERVICE_ACCOUNT_INFO['token_uri'] + assert credentials._target_audience == self.TARGET_AUDIENCE + + def test_from_service_account_file(self): + info = SERVICE_ACCOUNT_INFO.copy() + + credentials = ( + service_account.IDTokenCredentials.from_service_account_file( + SERVICE_ACCOUNT_JSON_FILE, + target_audience=self.TARGET_AUDIENCE)) + + assert credentials.service_account_email == info['client_email'] + assert credentials._signer.key_id == info['private_key_id'] + assert credentials._token_uri == info['token_uri'] + assert credentials._target_audience == self.TARGET_AUDIENCE + + def test_default_state(self): + credentials = self.make_credentials() + assert not credentials.valid + # Expiration hasn't been set yet + assert not credentials.expired + + def test_sign_bytes(self): + credentials = self.make_credentials() + to_sign = b'123' + signature = credentials.sign_bytes(to_sign) + assert crypt.verify_signature(to_sign, signature, PUBLIC_CERT_BYTES) + + def test_signer(self): + credentials = self.make_credentials() + assert isinstance(credentials.signer, crypt.Signer) + + def test_signer_email(self): + credentials = self.make_credentials() + assert credentials.signer_email == self.SERVICE_ACCOUNT_EMAIL + + def test_with_target_audience(self): + credentials = self.make_credentials() + new_credentials = credentials.with_target_audience( + 'https://new.example.com') + assert new_credentials._target_audience == 'https://new.example.com' + + def test__make_authorization_grant_assertion(self): + credentials = self.make_credentials() + token = credentials._make_authorization_grant_assertion() + payload = jwt.decode(token, PUBLIC_CERT_BYTES) + assert payload['iss'] == self.SERVICE_ACCOUNT_EMAIL + assert payload['aud'] == self.TOKEN_URI + assert payload['target_audience'] == self.TARGET_AUDIENCE + + @mock.patch('google.oauth2._client.id_token_jwt_grant', autospec=True) + def test_refresh_success(self, id_token_jwt_grant): + credentials = self.make_credentials() + token = 'token' + id_token_jwt_grant.return_value = ( + token, + _helpers.utcnow() + datetime.timedelta(seconds=500), + {}) + request = mock.create_autospec(transport.Request, instance=True) + + # Refresh credentials + credentials.refresh(request) + + # Check jwt grant call. + assert id_token_jwt_grant.called + + called_request, token_uri, assertion = id_token_jwt_grant.call_args[0] + assert called_request == request + assert token_uri == credentials._token_uri + assert jwt.decode(assertion, PUBLIC_CERT_BYTES) + # No further assertion done on the token, as there are separate tests + # for checking the authorization grant assertion. + + # Check that the credentials have the token. + assert credentials.token == token + + # Check that the credentials are valid (have a token and are not + # expired) + assert credentials.valid + + @mock.patch('google.oauth2._client.id_token_jwt_grant', autospec=True) + def test_before_request_refreshes(self, id_token_jwt_grant): + credentials = self.make_credentials() + token = 'token' + id_token_jwt_grant.return_value = ( + token, _helpers.utcnow() + datetime.timedelta(seconds=500), None) + request = mock.create_autospec(transport.Request, instance=True) + + # Credentials should start as invalid + assert not credentials.valid + + # before_request should cause a refresh + credentials.before_request( + request, 'GET', 'http://example.com?a=1#3', {}) + + # The refresh endpoint should've been called. + assert id_token_jwt_grant.called + + # Credentials should now be valid. + assert credentials.valid From bf8ee7fc4528885cdfc34f81cb54bbaea641f012 Mon Sep 17 00:00:00 2001 From: Danny Hermes Date: Thu, 8 Feb 2018 15:41:51 -0800 Subject: [PATCH 171/966] Add `cryptography`-based RSA signer and verifier. (#185) Fixes #183. --- packages/google-auth/.gitignore | 1 + .../google/auth/crypt/_cryptography_rsa.py | 137 +++++++++++++++ .../google-auth/google/auth/crypt/_helpers.py | 0 .../google/auth/crypt/_python_rsa.py | 47 +---- .../google-auth/google/auth/crypt/base.py | 67 ++++++++ packages/google-auth/google/auth/crypt/rsa.py | 16 +- .../tests/crypt/test__cryptography_rsa.py | 161 ++++++++++++++++++ .../tests/crypt/test__python_rsa.py | 7 +- .../tests/test__service_account_info.py | 2 +- packages/google-auth/tox.ini | 1 + 10 files changed, 386 insertions(+), 53 deletions(-) create mode 100644 packages/google-auth/google/auth/crypt/_cryptography_rsa.py create mode 100644 packages/google-auth/google/auth/crypt/_helpers.py create mode 100644 packages/google-auth/tests/crypt/test__cryptography_rsa.py diff --git a/packages/google-auth/.gitignore b/packages/google-auth/.gitignore index 1f65cf324447..c6ab9e7e678d 100644 --- a/packages/google-auth/.gitignore +++ b/packages/google-auth/.gitignore @@ -12,6 +12,7 @@ docs/_build .nox/ .tox/ .cache/ +.pytest_cache/ # Django test database db.sqlite3 diff --git a/packages/google-auth/google/auth/crypt/_cryptography_rsa.py b/packages/google-auth/google/auth/crypt/_cryptography_rsa.py new file mode 100644 index 000000000000..153e68885702 --- /dev/null +++ b/packages/google-auth/google/auth/crypt/_cryptography_rsa.py @@ -0,0 +1,137 @@ +# Copyright 2017 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""RSA verifier and signer that use the ``cryptography`` library. + +This is a much faster implementation than the default (in +``google.auth.crypt._python_rsa``), which depends on the pure-Python +``rsa`` library. +""" + +import cryptography.exceptions +from cryptography.hazmat import backends +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import padding +import cryptography.x509 + +from google.auth import _helpers +from google.auth.crypt import base + + +_CERTIFICATE_MARKER = b'-----BEGIN CERTIFICATE-----' +_BACKEND = backends.default_backend() +_PADDING = padding.PKCS1v15() +_SHA256 = hashes.SHA256() + + +class RSAVerifier(base.Verifier): + """Verifies RSA cryptographic signatures using public keys. + + Args: + public_key ( + cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey): + The public key used to verify signatures. + """ + + def __init__(self, public_key): + self._pubkey = public_key + + @_helpers.copy_docstring(base.Verifier) + def verify(self, message, signature): + message = _helpers.to_bytes(message) + try: + self._pubkey.verify(signature, message, _PADDING, _SHA256) + return True + except (ValueError, cryptography.exceptions.InvalidSignature): + return False + + @classmethod + def from_string(cls, public_key): + """Construct an Verifier instance from a public key or public + certificate string. + + Args: + public_key (Union[str, bytes]): The public key in PEM format or the + x509 public key certificate. + + Returns: + Verifier: The constructed verifier. + + Raises: + ValueError: If the public key can't be parsed. + """ + public_key_data = _helpers.to_bytes(public_key) + + if _CERTIFICATE_MARKER in public_key_data: + cert = cryptography.x509.load_pem_x509_certificate( + public_key_data, _BACKEND) + pubkey = cert.public_key() + + else: + pubkey = serialization.load_pem_public_key( + public_key_data, _BACKEND) + + return cls(pubkey) + + +class RSASigner(base.Signer, base.FromServiceAccountMixin): + """Signs messages with an RSA private key. + + Args: + private_key ( + cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey): + The private key to sign with. + key_id (str): Optional key ID used to identify this private key. This + can be useful to associate the private key with its associated + public key or certificate. + """ + + def __init__(self, private_key, key_id=None): + self._key = private_key + self._key_id = key_id + + @property + @_helpers.copy_docstring(base.Signer) + def key_id(self): + return self._key_id + + @_helpers.copy_docstring(base.Signer) + def sign(self, message): + message = _helpers.to_bytes(message) + return self._key.sign( + message, _PADDING, _SHA256) + + @classmethod + def from_string(cls, key, key_id=None): + """Construct a RSASigner from a private key in PEM format. + + Args: + key (Union[bytes, str]): Private key in PEM format. + key_id (str): An optional key id used to identify the private key. + + Returns: + google.auth.crypt._cryptography_rsa.RSASigner: The + constructed signer. + + Raises: + ValueError: If ``key`` is not ``bytes`` or ``str`` (unicode). + UnicodeDecodeError: If ``key`` is ``bytes`` but cannot be decoded + into a UTF-8 ``str``. + ValueError: If ``cryptography`` "Could not deserialize key data." + """ + key = _helpers.to_bytes(key) + private_key = serialization.load_pem_private_key( + key, password=None, backend=_BACKEND) + return cls(private_key, key_id=key_id) diff --git a/packages/google-auth/google/auth/crypt/_helpers.py b/packages/google-auth/google/auth/crypt/_helpers.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/google-auth/google/auth/crypt/_python_rsa.py b/packages/google-auth/google/auth/crypt/_python_rsa.py index 1f6384d2ce0f..44aa79191317 100644 --- a/packages/google-auth/google/auth/crypt/_python_rsa.py +++ b/packages/google-auth/google/auth/crypt/_python_rsa.py @@ -21,9 +21,6 @@ from __future__ import absolute_import -import io -import json - from pyasn1.codec.der import decoder from pyasn1_modules import pem from pyasn1_modules.rfc2459 import Certificate @@ -41,8 +38,6 @@ _PKCS8_MARKER = ('-----BEGIN PRIVATE KEY-----', '-----END PRIVATE KEY-----') _PKCS8_SPEC = PrivateKeyInfo() -_JSON_FILE_PRIVATE_KEY = 'private_key' -_JSON_FILE_PRIVATE_KEY_ID = 'private_key_id' def _bit_list_to_bytes(bit_list): @@ -119,7 +114,7 @@ def from_string(cls, public_key): return cls(pubkey) -class RSASigner(base.Signer): +class RSASigner(base.Signer, base.FromServiceAccountMixin): """Signs messages with an RSA private key. Args: @@ -179,43 +174,3 @@ def from_string(cls, key, key_id=None): raise ValueError('No key could be detected.') return cls(private_key, key_id=key_id) - - @classmethod - def from_service_account_info(cls, info): - """Creates a Signer instance instance from a dictionary containing - service account info in Google format. - - Args: - info (Mapping[str, str]): The service account info in Google - format. - - Returns: - google.auth.crypt.Signer: The constructed signer. - - Raises: - ValueError: If the info is not in the expected format. - """ - if _JSON_FILE_PRIVATE_KEY not in info: - raise ValueError( - 'The private_key field was not found in the service account ' - 'info.') - - return cls.from_string( - info[_JSON_FILE_PRIVATE_KEY], - info.get(_JSON_FILE_PRIVATE_KEY_ID)) - - @classmethod - def from_service_account_file(cls, filename): - """Creates a Signer instance from a service account .json file - in Google format. - - Args: - filename (str): The path to the service account .json file. - - Returns: - google.auth.crypt.Signer: The constructed signer. - """ - with io.open(filename, 'r', encoding='utf-8') as json_file: - data = json.load(json_file) - - return cls.from_service_account_info(data) diff --git a/packages/google-auth/google/auth/crypt/base.py b/packages/google-auth/google/auth/crypt/base.py index 05c5a2bf50b8..c6c042721df7 100644 --- a/packages/google-auth/google/auth/crypt/base.py +++ b/packages/google-auth/google/auth/crypt/base.py @@ -15,10 +15,16 @@ """Base classes for cryptographic signers and verifiers.""" import abc +import io +import json import six +_JSON_FILE_PRIVATE_KEY = 'private_key' +_JSON_FILE_PRIVATE_KEY_ID = 'private_key_id' + + @six.add_metaclass(abc.ABCMeta) class Verifier(object): """Abstract base class for crytographic signature verifiers.""" @@ -62,3 +68,64 @@ def sign(self, message): # pylint: disable=missing-raises-doc,redundant-returns-doc # (pylint doesn't recognize that this is abstract) raise NotImplementedError('Sign must be implemented') + + +@six.add_metaclass(abc.ABCMeta) +class FromServiceAccountMixin(object): + """Mix-in to enable factory constructors for a Signer.""" + + @abc.abstractmethod + def from_string(cls, key, key_id=None): + """Construct an Signer instance from a private key string. + + Args: + key (str): Private key as a string. + key_id (str): An optional key id used to identify the private key. + + Returns: + google.auth.crypt.Signer: The constructed signer. + + Raises: + ValueError: If the key cannot be parsed. + """ + raise NotImplementedError('from_string must be implemented') + + @classmethod + def from_service_account_info(cls, info): + """Creates a Signer instance instance from a dictionary containing + service account info in Google format. + + Args: + info (Mapping[str, str]): The service account info in Google + format. + + Returns: + google.auth.crypt.Signer: The constructed signer. + + Raises: + ValueError: If the info is not in the expected format. + """ + if _JSON_FILE_PRIVATE_KEY not in info: + raise ValueError( + 'The private_key field was not found in the service account ' + 'info.') + + return cls.from_string( + info[_JSON_FILE_PRIVATE_KEY], + info.get(_JSON_FILE_PRIVATE_KEY_ID)) + + @classmethod + def from_service_account_file(cls, filename): + """Creates a Signer instance from a service account .json file + in Google format. + + Args: + filename (str): The path to the service account .json file. + + Returns: + google.auth.crypt.Signer: The constructed signer. + """ + with io.open(filename, 'r', encoding='utf-8') as json_file: + data = json.load(json_file) + + return cls.from_service_account_info(data) diff --git a/packages/google-auth/google/auth/crypt/rsa.py b/packages/google-auth/google/auth/crypt/rsa.py index d0bf2a0b9adb..5da1ba6087db 100644 --- a/packages/google-auth/google/auth/crypt/rsa.py +++ b/packages/google-auth/google/auth/crypt/rsa.py @@ -14,7 +14,17 @@ """RSA cryptography signer and verifier.""" -from google.auth.crypt import _python_rsa -RSASigner = _python_rsa.RSASigner -RSAVerifier = _python_rsa.RSAVerifier +try: + # Prefer cryptograph-based RSA implementation. + from google.auth.crypt import _cryptography_rsa + + RSASigner = _cryptography_rsa.RSASigner + RSAVerifier = _cryptography_rsa.RSAVerifier +except ImportError: # pragma: NO COVER + # Fallback to pure-python RSA implementation if cryptography is + # unavailable. + from google.auth.crypt import _python_rsa + + RSASigner = _python_rsa.RSASigner + RSAVerifier = _python_rsa.RSAVerifier diff --git a/packages/google-auth/tests/crypt/test__cryptography_rsa.py b/packages/google-auth/tests/crypt/test__cryptography_rsa.py new file mode 100644 index 000000000000..a7ebb64a7810 --- /dev/null +++ b/packages/google-auth/tests/crypt/test__cryptography_rsa.py @@ -0,0 +1,161 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import os + +from cryptography.hazmat.primitives.asymmetric import rsa +import pytest + +from google.auth import _helpers +from google.auth.crypt import _cryptography_rsa +from google.auth.crypt import base + + +DATA_DIR = os.path.join(os.path.dirname(__file__), '..', 'data') + +# To generate privatekey.pem, privatekey.pub, and public_cert.pem: +# $ openssl req -new -newkey rsa:1024 -x509 -nodes -out public_cert.pem \ +# > -keyout privatekey.pem +# $ openssl rsa -in privatekey.pem -pubout -out privatekey.pub + +with open(os.path.join(DATA_DIR, 'privatekey.pem'), 'rb') as fh: + PRIVATE_KEY_BYTES = fh.read() + PKCS1_KEY_BYTES = PRIVATE_KEY_BYTES + +with open(os.path.join(DATA_DIR, 'privatekey.pub'), 'rb') as fh: + PUBLIC_KEY_BYTES = fh.read() + +with open(os.path.join(DATA_DIR, 'public_cert.pem'), 'rb') as fh: + PUBLIC_CERT_BYTES = fh.read() + +# To generate pem_from_pkcs12.pem and privatekey.p12: +# $ openssl pkcs12 -export -out privatekey.p12 -inkey privatekey.pem \ +# > -in public_cert.pem +# $ openssl pkcs12 -in privatekey.p12 -nocerts -nodes \ +# > -out pem_from_pkcs12.pem + +with open(os.path.join(DATA_DIR, 'pem_from_pkcs12.pem'), 'rb') as fh: + PKCS8_KEY_BYTES = fh.read() + +with open(os.path.join(DATA_DIR, 'privatekey.p12'), 'rb') as fh: + PKCS12_KEY_BYTES = fh.read() + +# The service account JSON file can be generated from the Google Cloud Console. +SERVICE_ACCOUNT_JSON_FILE = os.path.join(DATA_DIR, 'service_account.json') + +with open(SERVICE_ACCOUNT_JSON_FILE, 'r') as fh: + SERVICE_ACCOUNT_INFO = json.load(fh) + + +class TestRSAVerifier(object): + def test_verify_success(self): + to_sign = b'foo' + signer = _cryptography_rsa.RSASigner.from_string(PRIVATE_KEY_BYTES) + actual_signature = signer.sign(to_sign) + + verifier = _cryptography_rsa.RSAVerifier.from_string(PUBLIC_KEY_BYTES) + assert verifier.verify(to_sign, actual_signature) + + def test_verify_unicode_success(self): + to_sign = u'foo' + signer = _cryptography_rsa.RSASigner.from_string(PRIVATE_KEY_BYTES) + actual_signature = signer.sign(to_sign) + + verifier = _cryptography_rsa.RSAVerifier.from_string(PUBLIC_KEY_BYTES) + assert verifier.verify(to_sign, actual_signature) + + def test_verify_failure(self): + verifier = _cryptography_rsa.RSAVerifier.from_string(PUBLIC_KEY_BYTES) + bad_signature1 = b'' + assert not verifier.verify(b'foo', bad_signature1) + bad_signature2 = b'a' + assert not verifier.verify(b'foo', bad_signature2) + + def test_from_string_pub_key(self): + verifier = _cryptography_rsa.RSAVerifier.from_string(PUBLIC_KEY_BYTES) + assert isinstance(verifier, _cryptography_rsa.RSAVerifier) + assert isinstance(verifier._pubkey, rsa.RSAPublicKey) + + def test_from_string_pub_key_unicode(self): + public_key = _helpers.from_bytes(PUBLIC_KEY_BYTES) + verifier = _cryptography_rsa.RSAVerifier.from_string(public_key) + assert isinstance(verifier, _cryptography_rsa.RSAVerifier) + assert isinstance(verifier._pubkey, rsa.RSAPublicKey) + + def test_from_string_pub_cert(self): + verifier = _cryptography_rsa.RSAVerifier.from_string(PUBLIC_CERT_BYTES) + assert isinstance(verifier, _cryptography_rsa.RSAVerifier) + assert isinstance(verifier._pubkey, rsa.RSAPublicKey) + + def test_from_string_pub_cert_unicode(self): + public_cert = _helpers.from_bytes(PUBLIC_CERT_BYTES) + verifier = _cryptography_rsa.RSAVerifier.from_string(public_cert) + assert isinstance(verifier, _cryptography_rsa.RSAVerifier) + assert isinstance(verifier._pubkey, rsa.RSAPublicKey) + + +class TestRSASigner(object): + def test_from_string_pkcs1(self): + signer = _cryptography_rsa.RSASigner.from_string(PKCS1_KEY_BYTES) + assert isinstance(signer, _cryptography_rsa.RSASigner) + assert isinstance(signer._key, rsa.RSAPrivateKey) + + def test_from_string_pkcs1_unicode(self): + key_bytes = _helpers.from_bytes(PKCS1_KEY_BYTES) + signer = _cryptography_rsa.RSASigner.from_string(key_bytes) + assert isinstance(signer, _cryptography_rsa.RSASigner) + assert isinstance(signer._key, rsa.RSAPrivateKey) + + def test_from_string_pkcs8(self): + signer = _cryptography_rsa.RSASigner.from_string(PKCS8_KEY_BYTES) + assert isinstance(signer, _cryptography_rsa.RSASigner) + assert isinstance(signer._key, rsa.RSAPrivateKey) + + def test_from_string_pkcs8_unicode(self): + key_bytes = _helpers.from_bytes(PKCS8_KEY_BYTES) + signer = _cryptography_rsa.RSASigner.from_string(key_bytes) + assert isinstance(signer, _cryptography_rsa.RSASigner) + assert isinstance(signer._key, rsa.RSAPrivateKey) + + def test_from_string_pkcs12(self): + with pytest.raises(ValueError): + _cryptography_rsa.RSASigner.from_string(PKCS12_KEY_BYTES) + + def test_from_string_bogus_key(self): + key_bytes = 'bogus-key' + with pytest.raises(ValueError): + _cryptography_rsa.RSASigner.from_string(key_bytes) + + def test_from_service_account_info(self): + signer = _cryptography_rsa.RSASigner.from_service_account_info( + SERVICE_ACCOUNT_INFO) + + assert signer.key_id == SERVICE_ACCOUNT_INFO[ + base._JSON_FILE_PRIVATE_KEY_ID] + assert isinstance(signer._key, rsa.RSAPrivateKey) + + def test_from_service_account_info_missing_key(self): + with pytest.raises(ValueError) as excinfo: + _cryptography_rsa.RSASigner.from_service_account_info({}) + + assert excinfo.match(base._JSON_FILE_PRIVATE_KEY) + + def test_from_service_account_file(self): + signer = _cryptography_rsa.RSASigner.from_service_account_file( + SERVICE_ACCOUNT_JSON_FILE) + + assert signer.key_id == SERVICE_ACCOUNT_INFO[ + base._JSON_FILE_PRIVATE_KEY_ID] + assert isinstance(signer._key, rsa.RSAPrivateKey) diff --git a/packages/google-auth/tests/crypt/test__python_rsa.py b/packages/google-auth/tests/crypt/test__python_rsa.py index cff1034bc180..d13105f47f92 100644 --- a/packages/google-auth/tests/crypt/test__python_rsa.py +++ b/packages/google-auth/tests/crypt/test__python_rsa.py @@ -23,6 +23,7 @@ from google.auth import _helpers from google.auth.crypt import _python_rsa +from google.auth.crypt import base DATA_DIR = os.path.join(os.path.dirname(__file__), '..', 'data') @@ -176,19 +177,19 @@ def test_from_service_account_info(self): SERVICE_ACCOUNT_INFO) assert signer.key_id == SERVICE_ACCOUNT_INFO[ - _python_rsa._JSON_FILE_PRIVATE_KEY_ID] + base._JSON_FILE_PRIVATE_KEY_ID] assert isinstance(signer._key, rsa.key.PrivateKey) def test_from_service_account_info_missing_key(self): with pytest.raises(ValueError) as excinfo: _python_rsa.RSASigner.from_service_account_info({}) - assert excinfo.match(_python_rsa._JSON_FILE_PRIVATE_KEY) + assert excinfo.match(base._JSON_FILE_PRIVATE_KEY) def test_from_service_account_file(self): signer = _python_rsa.RSASigner.from_service_account_file( SERVICE_ACCOUNT_JSON_FILE) assert signer.key_id == SERVICE_ACCOUNT_INFO[ - _python_rsa._JSON_FILE_PRIVATE_KEY_ID] + base._JSON_FILE_PRIVATE_KEY_ID] assert isinstance(signer._key, rsa.key.PrivateKey) diff --git a/packages/google-auth/tests/test__service_account_info.py b/packages/google-auth/tests/test__service_account_info.py index 546686530458..ef41e27571b0 100644 --- a/packages/google-auth/tests/test__service_account_info.py +++ b/packages/google-auth/tests/test__service_account_info.py @@ -42,7 +42,7 @@ def test_from_dict_bad_private_key(): with pytest.raises(ValueError) as excinfo: _service_account_info.from_dict(info) - assert excinfo.match(r'No key could be detected') + assert excinfo.match(r'key') def test_from_dict_bad_format(): diff --git a/packages/google-auth/tox.ini b/packages/google-auth/tox.ini index b8031901dc49..be124e650d8e 100644 --- a/packages/google-auth/tox.ini +++ b/packages/google-auth/tox.ini @@ -13,6 +13,7 @@ deps = requests requests-oauthlib urllib3 + cryptography grpcio; platform_python_implementation != 'PyPy' commands = py.test --cov=google.auth --cov=google.oauth2 --cov=tests {posargs:tests} From e40c3014894a51d54ed71576ea30578dc5369da7 Mon Sep 17 00:00:00 2001 From: Arlan Jaska Date: Thu, 8 Feb 2018 15:51:26 -0800 Subject: [PATCH 172/966] Improve documentation around ID Tokens (#224) --- packages/google-auth/docs/index.rst | 1 + .../google-auth/google/oauth2/id_token.py | 46 ++++++++++++++++++- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/docs/index.rst b/packages/google-auth/docs/index.rst index 1b5f5d6f0afd..af2c8045912f 100644 --- a/packages/google-auth/docs/index.rst +++ b/packages/google-auth/docs/index.rst @@ -14,6 +14,7 @@ also provides integration with several HTTP libraries. - Support for Google :func:`Application Default Credentials `. - Support for signing and verifying :mod:`JWTs `. +- Support for verifying and decoding :mod:`ID Tokens `. - Support for Google :mod:`Service Account credentials `. - Support for :mod:`Google Compute Engine credentials `. - Support for :mod:`Google App Engine standard credentials `. diff --git a/packages/google-auth/google/oauth2/id_token.py b/packages/google-auth/google/oauth2/id_token.py index fa96fc0321b6..208ab62240d6 100644 --- a/packages/google-auth/google/oauth2/id_token.py +++ b/packages/google-auth/google/oauth2/id_token.py @@ -12,7 +12,51 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Google ID Token helpers.""" +"""Google ID Token helpers. + +Provides support for verifying `OpenID Connect ID Tokens`_, especially ones +generated by Google infrastructure. + +To parse and verify an ID Token issued by Google's OAuth 2.0 authorization +server use :func:`verify_oauth2_token`. To verify an ID Token issued by +Firebase, use :func:`verify_firebase_token`. + +A general purpose ID Token verifier is available as :func:`verify_token`. + +Example:: + + from google.oauth2 import id_token + from google.auth.transport import requests + + request = requests.Request() + + id_info = id_token.verify_oauth2_token( + token, request, 'my-client-id.example.com') + + if id_info['iss'] != 'https://accounts.google.com': + raise ValueError('Wrong issuer.') + + userid = id_info['sub'] + +By default, this will re-fetch certificates for each verification. Because +Google's public keys are only changed infrequently (on the order of once per +day), you may wish to take advantage of caching to reduce latency and the +potential for network errors. This can be accomplished using an external +library like `CacheControl`_ to create a cache-aware +:class:`google.auth.transport.Request`:: + + import cachecontrol + import google.auth.transport.requests + import requests + + session = requests.session() + cached_session = cachecontrol.CacheControl(session) + request = google.auth.transport.requests.Request(session=cached_session) + +.. _OpenID Connect ID Token: + http://openid.net/specs/openid-connect-core-1_0.html#IDToken +.. _CacheControl: https://cachecontrol.readthedocs.io +""" import json From 78e844584379d60f9280c686920258a3d6b867fa Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Thu, 8 Feb 2018 16:03:53 -0800 Subject: [PATCH 173/966] Release v1.4.0 (#240) --- packages/google-auth/CHANGELOG.rst | 7 +++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.rst b/packages/google-auth/CHANGELOG.rst index 58052f5868c5..192a30eee6ec 100644 --- a/packages/google-auth/CHANGELOG.rst +++ b/packages/google-auth/CHANGELOG.rst @@ -1,6 +1,13 @@ Changelog ========= +v1.4.0 +------ + +- Added `cryptography`-based RSA signer and verifier. (#185) +- Added `google.oauth2.service_account.IDTokenCredentials`. (#234) +- Improved documentation around ID Tokens (#224) + v1.3.0 ------ diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 0a21f255d747..6fec08bd306b 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -31,7 +31,7 @@ setup( name='google-auth', - version='1.3.0', + version='1.4.0', author='Google Cloud Platform', author_email='jonwayne+google-auth@google.com', description='Google Authentication Library', From f1008aa099b378be7e34b87fc449ef3529367f35 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Mon, 12 Feb 2018 10:53:58 -0800 Subject: [PATCH 174/966] Add a check for the cryptography version before attempting to use it. (#243) --- .../google/auth/crypt/_cryptography_rsa.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/google-auth/google/auth/crypt/_cryptography_rsa.py b/packages/google-auth/google/auth/crypt/_cryptography_rsa.py index 153e68885702..87076b0ab9cf 100644 --- a/packages/google-auth/google/auth/crypt/_cryptography_rsa.py +++ b/packages/google-auth/google/auth/crypt/_cryptography_rsa.py @@ -25,10 +25,22 @@ from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import padding import cryptography.x509 +import pkg_resources from google.auth import _helpers from google.auth.crypt import base +_IMPORT_ERROR_MSG = ( + 'cryptography>=1.4.0 is required to use cryptography-based RSA ' + 'implementation.') + +try: # pragma: NO COVER + release = pkg_resources.get_distribution('cryptography').parsed_version + if release < pkg_resources.parse_version('1.4.0'): + raise ImportError(_IMPORT_ERROR_MSG) +except pkg_resources.DistributionNotFound: # pragma: NO COVER + raise ImportError(_IMPORT_ERROR_MSG) + _CERTIFICATE_MARKER = b'-----BEGIN CERTIFICATE-----' _BACKEND = backends.default_backend() From 67ac99655476a6725408006b48305125c5d3962a Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Mon, 12 Feb 2018 11:01:40 -0800 Subject: [PATCH 175/966] Release v1.4.1 (#244) --- packages/google-auth/CHANGELOG.rst | 5 +++++ packages/google-auth/setup.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.rst b/packages/google-auth/CHANGELOG.rst index 192a30eee6ec..45d8c4c17201 100644 --- a/packages/google-auth/CHANGELOG.rst +++ b/packages/google-auth/CHANGELOG.rst @@ -1,6 +1,11 @@ Changelog ========= +v1.4.1 +------ + +- Added a check for the cryptography version before attempting to use it. (#243) + v1.4.0 ------ diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 6fec08bd306b..aa0eaa627706 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -31,7 +31,7 @@ setup( name='google-auth', - version='1.4.0', + version='1.4.1', author='Google Cloud Platform', author_email='jonwayne+google-auth@google.com', description='Google Authentication Library', From 7881c2a1df99ec4ac82f3e54ca00c9cc9d93422f Mon Sep 17 00:00:00 2001 From: Marco Rougeth Date: Wed, 28 Feb 2018 20:13:56 -0300 Subject: [PATCH 176/966] Fix typo on exemple of jwt usage (#245) --- packages/google-auth/google/auth/jwt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/jwt.py b/packages/google-auth/google/auth/jwt.py index 695737496797..ef23db233c0b 100644 --- a/packages/google-auth/google/auth/jwt.py +++ b/packages/google-auth/google/auth/jwt.py @@ -21,7 +21,7 @@ To encode a JWT use :func:`encode`:: - from google.auth import crypto + from google.auth import crypt from google.auth import jwt signer = crypt.Signer(private_key) From 96db48c2e00d320711a0fc3f81a2182545c787f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Mon, 14 May 2018 04:39:10 +0200 Subject: [PATCH 177/966] Use pytest instead of py.test per upstream recommendation, #dropthedot (#255) http://blog.pytest.org/2016/whats-new-in-pytest-30/ https://twitter.com/hashtag/dropthedot --- packages/google-auth/system_tests/app_engine_test_app/main.py | 4 ++-- packages/google-auth/tox.ini | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/google-auth/system_tests/app_engine_test_app/main.py b/packages/google-auth/system_tests/app_engine_test_app/main.py index c7b89ab8cb93..122b505fb9e0 100644 --- a/packages/google-auth/system_tests/app_engine_test_app/main.py +++ b/packages/google-auth/system_tests/app_engine_test_app/main.py @@ -15,8 +15,8 @@ """App Engine standard application that runs basic system tests for google.auth.app_engine. -This application has to run tests manually instead of using py.test because -py.test currently doesn't work on App Engine standard. +This application has to run tests manually instead of using pytest because +pytest currently doesn't work on App Engine standard. """ import contextlib diff --git a/packages/google-auth/tox.ini b/packages/google-auth/tox.ini index be124e650d8e..1c52c4bb0241 100644 --- a/packages/google-auth/tox.ini +++ b/packages/google-auth/tox.ini @@ -16,12 +16,12 @@ deps = cryptography grpcio; platform_python_implementation != 'PyPy' commands = - py.test --cov=google.auth --cov=google.oauth2 --cov=tests {posargs:tests} + pytest --cov=google.auth --cov=google.oauth2 --cov=tests {posargs:tests} [testenv:cover] basepython = python3.6 commands = - py.test --cov=google.auth --cov=google.oauth2 --cov=tests --cov-report= tests + pytest --cov=google.auth --cov=google.oauth2 --cov=tests --cov-report= tests coverage report --show-missing --fail-under=100 deps = {[testenv]deps} From 9ccac6373f6e34b33fed3f807102af6560f6373c Mon Sep 17 00:00:00 2001 From: Craig Citro Date: Mon, 14 May 2018 23:29:46 -0700 Subject: [PATCH 178/966] Fix a typo in credentials.py. (#256) --- packages/google-auth/google/auth/credentials.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/google/auth/credentials.py b/packages/google-auth/google/auth/credentials.py index 64b333823fdc..8ff1f0251b16 100644 --- a/packages/google-auth/google/auth/credentials.py +++ b/packages/google-auth/google/auth/credentials.py @@ -53,8 +53,9 @@ def __init__(self): def expired(self): """Checks if the credentials are expired. - Note that credentials can be invalid but not expired becaue Credentials - with :attr:`expiry` set to None is considered to never expire. + Note that credentials can be invalid but not expired because + Credentials with :attr:`expiry` set to None is considered to never + expire. """ if not self.expiry: return False From 291eacf9674023e6ba760e09e1fe33b2899f0cac Mon Sep 17 00:00:00 2001 From: Thea Flowers Date: Thu, 24 May 2018 13:09:47 -0700 Subject: [PATCH 179/966] Fix links to README and CONTRIBUTING in docs/index.rst (#260) --- packages/google-auth/docs/index.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/docs/index.rst b/packages/google-auth/docs/index.rst index af2c8045912f..56e3ecaf6849 100644 --- a/packages/google-auth/docs/index.rst +++ b/packages/google-auth/docs/index.rst @@ -57,7 +57,7 @@ google-auth is made available under the Apache License, Version 2.0. For more details, see `LICENSE`_ .. _LICENSE: - https://github.com/GoogleCloudPlatform/google-auth-library-python/LICENSE + https://github.com/GoogleCloudPlatform/google-auth-library-python/blob/master/LICENSE Contributing ------------ @@ -66,4 +66,4 @@ We happily welcome contributions, please see our `contributing`_ documentation for details. .. _contributing: - https://github.com/GoogleCloudPlatform/google-auth-library-python/CONTRIBUTING.rst + https://github.com/GoogleCloudPlatform/google-auth-library-python/blob/master/CONTRIBUTING.rst From 109eccd3c4cd9cf82a16f13228833ab7cdde4e7f Mon Sep 17 00:00:00 2001 From: Thea Flowers Date: Thu, 24 May 2018 13:34:07 -0700 Subject: [PATCH 180/966] Raise a helpful exception when trying to refresh credentials without a refresh token (#262) --- packages/google-auth/google/oauth2/credentials.py | 10 ++++++++++ .../google-auth/tests/oauth2/test_credentials.py | 12 ++++++++++++ 2 files changed, 22 insertions(+) diff --git a/packages/google-auth/google/oauth2/credentials.py b/packages/google-auth/google/oauth2/credentials.py index 24b3a3eedf43..8e2a7f80593f 100644 --- a/packages/google-auth/google/oauth2/credentials.py +++ b/packages/google-auth/google/oauth2/credentials.py @@ -38,6 +38,7 @@ from google.auth import _helpers from google.auth import credentials +from google.auth import exceptions from google.oauth2 import _client @@ -120,6 +121,15 @@ def requires_scopes(self): @_helpers.copy_docstring(credentials.Credentials) def refresh(self, request): + if (self._refresh_token is None or + self._token_uri is None or + self._client_id is None or + self._client_secret is None): + raise exceptions.RefreshError( + 'The credentials do not contain the necessary fields need to ' + 'refresh the access token. You must specify refresh_token, ' + 'token_uri, client_id, and client_secret.') + access_token, refresh_token, expiry, grant_response = ( _client.refresh_grant( request, self._token_uri, self._refresh_token, self._client_id, diff --git a/packages/google-auth/tests/oauth2/test_credentials.py b/packages/google-auth/tests/oauth2/test_credentials.py index 906436353e64..922c3bbf7205 100644 --- a/packages/google-auth/tests/oauth2/test_credentials.py +++ b/packages/google-auth/tests/oauth2/test_credentials.py @@ -17,8 +17,10 @@ import os import mock +import pytest from google.auth import _helpers +from google.auth import exceptions from google.auth import transport from google.oauth2 import credentials @@ -95,6 +97,16 @@ def test_refresh_success(self, unused_utcnow, refresh_grant): # expired) assert credentials.valid + def test_refresh_no_refresh_token(self): + request = mock.create_autospec(transport.Request) + credentials_ = credentials.Credentials( + token=None, refresh_token=None) + + with pytest.raises(exceptions.RefreshError, match='necessary fields'): + credentials_.refresh(request) + + request.assert_not_called() + def test_from_authorized_user_info(self): info = AUTH_USER_INFO.copy() From fe610be339d9cc7f7b864c99b34cb85d405959e1 Mon Sep 17 00:00:00 2001 From: Thea Flowers Date: Thu, 24 May 2018 14:07:55 -0700 Subject: [PATCH 181/966] Release 1.4.2 (#263) --- packages/google-auth/CHANGELOG.rst | 9 +++++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.rst b/packages/google-auth/CHANGELOG.rst index 45d8c4c17201..9d22c60f1608 100644 --- a/packages/google-auth/CHANGELOG.rst +++ b/packages/google-auth/CHANGELOG.rst @@ -1,6 +1,15 @@ Changelog ========= +v1.4.2 +------ + +- Raise a helpful exception when trying to refresh credentials without a refresh token. (#262) +- Fix links to README and CONTRIBUTING in docs/index.rst. (#260) +- Fix a typo in credentials.py. (#256) +- Use pytest instead of py.test per upstream recommendation, #dropthedot. (#255) +- Fix typo on exemple of jwt usage (#245) + v1.4.1 ------ diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index aa0eaa627706..a1af80f1fb4d 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -31,7 +31,7 @@ setup( name='google-auth', - version='1.4.1', + version='1.4.2', author='Google Cloud Platform', author_email='jonwayne+google-auth@google.com', description='Google Authentication Library', From 9dd991a3aa4861896d2c01dfd6129f9a497f4fcd Mon Sep 17 00:00:00 2001 From: Rohan Talip Date: Tue, 29 May 2018 08:48:25 -0700 Subject: [PATCH 182/966] Corrected some typos (#265) --- packages/google-auth/docs/user-guide.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/google-auth/docs/user-guide.rst b/packages/google-auth/docs/user-guide.rst index 291701ce2c87..060d9b84a01a 100644 --- a/packages/google-auth/docs/user-guide.rst +++ b/packages/google-auth/docs/user-guide.rst @@ -132,16 +132,16 @@ credentials and transports provided by this library, you need to follow a few additional steps: #. If you are using the :mod:`google.auth.transport.requests` transport, vendor - in the `requests-toolbelt`_ library into you app, and enable the App Engine + in the `requests-toolbelt`_ library into your app, and enable the App Engine monkeypatch. Refer `App Engine documentation`_ for more details on this. -#. To make HTTPS calls, enable the ``ssl`` library for you app by adding the +#. To make HTTPS calls, enable the ``ssl`` library for your app by adding the following configuration to the ``app.yaml`` file:: libraries: - name: ssl version: latest -#. Enable billing for you App Engine project. Then enable socket support for +#. Enable billing for your App Engine project. Then enable socket support for your app. This can be achieved by setting an environment variable in the ``app.yaml`` file:: From 20dbb3aeafc03277bb36f478cb8c13a35dcede0b Mon Sep 17 00:00:00 2001 From: Christophe Taton Date: Thu, 31 May 2018 11:15:41 -0700 Subject: [PATCH 183/966] Add compute engine-based IDTokenCredentials (#236) --- .../google/auth/compute_engine/__init__.py | 4 +- .../google/auth/compute_engine/credentials.py | 129 ++++++++ .../tests/compute_engine/test_credentials.py | 276 ++++++++++++++++++ 3 files changed, 408 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/compute_engine/__init__.py b/packages/google-auth/google/auth/compute_engine/__init__.py index 3794be2f7d1f..ca31b4643b43 100644 --- a/packages/google-auth/google/auth/compute_engine/__init__.py +++ b/packages/google-auth/google/auth/compute_engine/__init__.py @@ -15,8 +15,10 @@ """Google Compute Engine authentication.""" from google.auth.compute_engine.credentials import Credentials +from google.auth.compute_engine.credentials import IDTokenCredentials __all__ = [ - 'Credentials' + 'Credentials', + 'IDTokenCredentials', ] diff --git a/packages/google-auth/google/auth/compute_engine/credentials.py b/packages/google-auth/google/auth/compute_engine/credentials.py index 3841df2a4aa6..d9c6e26d6d8f 100644 --- a/packages/google-auth/google/auth/compute_engine/credentials.py +++ b/packages/google-auth/google/auth/compute_engine/credentials.py @@ -19,11 +19,17 @@ """ +import datetime + import six +from google.auth import _helpers from google.auth import credentials from google.auth import exceptions +from google.auth import iam +from google.auth import jwt from google.auth.compute_engine import _metadata +from google.oauth2 import _client class Credentials(credentials.ReadOnlyScoped, credentials.Credentials): @@ -108,3 +114,126 @@ def service_account_email(self): def requires_scopes(self): """False: Compute Engine credentials can not be scoped.""" return False + + +_DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds +_DEFAULT_TOKEN_URI = 'https://www.googleapis.com/oauth2/v4/token' + + +class IDTokenCredentials(credentials.Credentials, credentials.Signing): + """Open ID Connect ID Token-based service account credentials. + + These credentials relies on the default service account of a GCE instance. + + In order for this to work, the GCE instance must have been started with + a service account that has access to the IAM Cloud API. + """ + def __init__(self, request, target_audience, + token_uri=_DEFAULT_TOKEN_URI, + additional_claims=None, + service_account_email=None): + """ + Args: + request (google.auth.transport.Request): The object used to make + HTTP requests. + target_audience (str): The intended audience for these credentials, + used when requesting the ID Token. The ID Token's ``aud`` claim + will be set to this string. + token_uri (str): The OAuth 2.0 Token URI. + additional_claims (Mapping[str, str]): Any additional claims for + the JWT assertion used in the authorization grant. + service_account_email (str): Optional explicit service account to + use to sign JWT tokens. + By default, this is the default GCE service account. + """ + super(IDTokenCredentials, self).__init__() + + if service_account_email is None: + sa_info = _metadata.get_service_account_info(request) + service_account_email = sa_info['email'] + self._service_account_email = service_account_email + + self._signer = iam.Signer( + request=request, + credentials=Credentials(), + service_account_email=service_account_email) + + self._token_uri = token_uri + self._target_audience = target_audience + + if additional_claims is not None: + self._additional_claims = additional_claims + else: + self._additional_claims = {} + + def with_target_audience(self, target_audience): + """Create a copy of these credentials with the specified target + audience. + Args: + target_audience (str): The intended audience for these credentials, + used when requesting the ID Token. + Returns: + google.auth.service_account.IDTokenCredentials: A new credentials + instance. + """ + return self.__class__( + self._signer, + service_account_email=self._service_account_email, + token_uri=self._token_uri, + target_audience=target_audience, + additional_claims=self._additional_claims.copy()) + + def _make_authorization_grant_assertion(self): + """Create the OAuth 2.0 assertion. + This assertion is used during the OAuth 2.0 grant to acquire an + ID token. + Returns: + bytes: The authorization grant assertion. + """ + now = _helpers.utcnow() + lifetime = datetime.timedelta(seconds=_DEFAULT_TOKEN_LIFETIME_SECS) + expiry = now + lifetime + + payload = { + 'iat': _helpers.datetime_to_secs(now), + 'exp': _helpers.datetime_to_secs(expiry), + # The issuer must be the service account email. + 'iss': self.service_account_email, + # The audience must be the auth token endpoint's URI + 'aud': self._token_uri, + # The target audience specifies which service the ID token is + # intended for. + 'target_audience': self._target_audience + } + + payload.update(self._additional_claims) + + token = jwt.encode(self._signer, payload) + + return token + + @_helpers.copy_docstring(credentials.Credentials) + def refresh(self, request): + assertion = self._make_authorization_grant_assertion() + access_token, expiry, _ = _client.id_token_jwt_grant( + request, self._token_uri, assertion) + self.token = access_token + self.expiry = expiry + + @property + @_helpers.copy_docstring(credentials.Signing) + def signer(self): + return self._signer + + @_helpers.copy_docstring(credentials.Signing) + def sign_bytes(self, message): + return self._signer.sign(message) + + @property + def service_account_email(self): + """The service account email.""" + return self._service_account_email + + @property + def signer_email(self): + return self._service_account_email diff --git a/packages/google-auth/tests/compute_engine/test_credentials.py b/packages/google-auth/tests/compute_engine/test_credentials.py index ae2597d300f3..ee415db98780 100644 --- a/packages/google-auth/tests/compute_engine/test_credentials.py +++ b/packages/google-auth/tests/compute_engine/test_credentials.py @@ -19,6 +19,7 @@ from google.auth import _helpers from google.auth import exceptions +from google.auth import jwt from google.auth import transport from google.auth.compute_engine import credentials @@ -105,3 +106,278 @@ def test_before_request_refreshes(self, get): # Credentials should now be valid. assert self.credentials.valid + + +class TestIDTokenCredentials(object): + credentials = None + + @mock.patch('google.auth.compute_engine._metadata.get', autospec=True) + def test_default_state(self, get): + get.side_effect = [{ + 'email': 'service-account@example.com', + 'scope': ['one', 'two'], + }] + + request = mock.create_autospec(transport.Request, instance=True) + self.credentials = credentials.IDTokenCredentials( + request=request, target_audience="https://example.com") + + assert not self.credentials.valid + # Expiration hasn't been set yet + assert not self.credentials.expired + # Service account email hasn't been populated + assert (self.credentials.service_account_email + == 'service-account@example.com') + # Signer is initialized + assert self.credentials.signer + assert self.credentials.signer_email == 'service-account@example.com' + + @mock.patch( + 'google.auth._helpers.utcnow', + return_value=datetime.datetime.utcfromtimestamp(0)) + @mock.patch('google.auth.compute_engine._metadata.get', autospec=True) + @mock.patch('google.auth.iam.Signer.sign', autospec=True) + def test_make_authorization_grant_assertion(self, sign, get, utcnow): + get.side_effect = [{ + 'email': 'service-account@example.com', + 'scopes': ['one', 'two'] + }] + sign.side_effect = [b'signature'] + + request = mock.create_autospec(transport.Request, instance=True) + self.credentials = credentials.IDTokenCredentials( + request=request, target_audience="https://audience.com") + + # Generate authorization grant: + token = self.credentials._make_authorization_grant_assertion() + payload = jwt.decode(token, verify=False) + + # The JWT token signature is 'signature' encoded in base 64: + assert token.endswith(b'.c2lnbmF0dXJl') + + # Check that the credentials have the token and proper expiration + assert payload == { + 'aud': 'https://www.googleapis.com/oauth2/v4/token', + 'exp': 3600, + 'iat': 0, + 'iss': 'service-account@example.com', + 'target_audience': 'https://audience.com'} + + @mock.patch( + 'google.auth._helpers.utcnow', + return_value=datetime.datetime.utcfromtimestamp(0)) + @mock.patch('google.auth.compute_engine._metadata.get', autospec=True) + @mock.patch('google.auth.iam.Signer.sign', autospec=True) + def test_with_service_account(self, sign, get, utcnow): + sign.side_effect = [b'signature'] + + request = mock.create_autospec(transport.Request, instance=True) + self.credentials = credentials.IDTokenCredentials( + request=request, target_audience="https://audience.com", + service_account_email="service-account@other.com") + + # Generate authorization grant: + token = self.credentials._make_authorization_grant_assertion() + payload = jwt.decode(token, verify=False) + + # The JWT token signature is 'signature' encoded in base 64: + assert token.endswith(b'.c2lnbmF0dXJl') + + # Check that the credentials have the token and proper expiration + assert payload == { + 'aud': 'https://www.googleapis.com/oauth2/v4/token', + 'exp': 3600, + 'iat': 0, + 'iss': 'service-account@other.com', + 'target_audience': 'https://audience.com'} + + @mock.patch( + 'google.auth._helpers.utcnow', + return_value=datetime.datetime.utcfromtimestamp(0)) + @mock.patch('google.auth.compute_engine._metadata.get', autospec=True) + @mock.patch('google.auth.iam.Signer.sign', autospec=True) + def test_additional_claims(self, sign, get, utcnow): + get.side_effect = [{ + 'email': 'service-account@example.com', + 'scopes': ['one', 'two'] + }] + sign.side_effect = [b'signature'] + + request = mock.create_autospec(transport.Request, instance=True) + self.credentials = credentials.IDTokenCredentials( + request=request, target_audience="https://audience.com", + additional_claims={'foo': 'bar'}) + + # Generate authorization grant: + token = self.credentials._make_authorization_grant_assertion() + payload = jwt.decode(token, verify=False) + + # The JWT token signature is 'signature' encoded in base 64: + assert token.endswith(b'.c2lnbmF0dXJl') + + # Check that the credentials have the token and proper expiration + assert payload == { + 'aud': 'https://www.googleapis.com/oauth2/v4/token', + 'exp': 3600, + 'iat': 0, + 'iss': 'service-account@example.com', + 'target_audience': 'https://audience.com', + 'foo': 'bar'} + + @mock.patch( + 'google.auth._helpers.utcnow', + return_value=datetime.datetime.utcfromtimestamp(0)) + @mock.patch('google.auth.compute_engine._metadata.get', autospec=True) + @mock.patch('google.auth.iam.Signer.sign', autospec=True) + def test_with_target_audience(self, sign, get, utcnow): + get.side_effect = [{ + 'email': 'service-account@example.com', + 'scopes': ['one', 'two'] + }] + sign.side_effect = [b'signature'] + + request = mock.create_autospec(transport.Request, instance=True) + self.credentials = credentials.IDTokenCredentials( + request=request, target_audience="https://audience.com") + self.credentials = ( + self.credentials.with_target_audience("https://actually.not")) + + # Generate authorization grant: + token = self.credentials._make_authorization_grant_assertion() + payload = jwt.decode(token, verify=False) + + # The JWT token signature is 'signature' encoded in base 64: + assert token.endswith(b'.c2lnbmF0dXJl') + + # Check that the credentials have the token and proper expiration + assert payload == { + 'aud': 'https://www.googleapis.com/oauth2/v4/token', + 'exp': 3600, + 'iat': 0, + 'iss': 'service-account@example.com', + 'target_audience': 'https://actually.not'} + + @mock.patch( + 'google.auth._helpers.utcnow', + return_value=datetime.datetime.utcfromtimestamp(0)) + @mock.patch('google.auth.compute_engine._metadata.get', autospec=True) + @mock.patch('google.auth.iam.Signer.sign', autospec=True) + @mock.patch('google.oauth2._client.id_token_jwt_grant', autospec=True) + def test_refresh_success(self, id_token_jwt_grant, sign, get, utcnow): + get.side_effect = [{ + 'email': 'service-account@example.com', + 'scopes': ['one', 'two'] + }] + sign.side_effect = [b'signature'] + id_token_jwt_grant.side_effect = [( + 'idtoken', + datetime.datetime.utcfromtimestamp(3600), + {}, + )] + + request = mock.create_autospec(transport.Request, instance=True) + self.credentials = credentials.IDTokenCredentials( + request=request, target_audience="https://audience.com") + + # Refresh credentials + self.credentials.refresh(None) + + # Check that the credentials have the token and proper expiration + assert self.credentials.token == 'idtoken' + assert self.credentials.expiry == ( + datetime.datetime.utcfromtimestamp(3600)) + + # Check the credential info + assert (self.credentials.service_account_email == + 'service-account@example.com') + + # Check that the credentials are valid (have a token and are not + # expired) + assert self.credentials.valid + + @mock.patch( + 'google.auth._helpers.utcnow', + return_value=datetime.datetime.utcfromtimestamp(0)) + @mock.patch('google.auth.compute_engine._metadata.get', autospec=True) + @mock.patch('google.auth.iam.Signer.sign', autospec=True) + def test_refresh_error(self, sign, get, utcnow): + get.side_effect = [{ + 'email': 'service-account@example.com', + 'scopes': ['one', 'two'], + }] + sign.side_effect = [b'signature'] + + request = mock.create_autospec(transport.Request, instance=True) + response = mock.Mock() + response.data = b'{"error": "http error"}' + response.status = 500 + request.side_effect = [response] + + self.credentials = credentials.IDTokenCredentials( + request=request, target_audience="https://audience.com") + + with pytest.raises(exceptions.RefreshError) as excinfo: + self.credentials.refresh(request) + + assert excinfo.match(r'http error') + + @mock.patch( + 'google.auth._helpers.utcnow', + return_value=datetime.datetime.utcfromtimestamp(0)) + @mock.patch('google.auth.compute_engine._metadata.get', autospec=True) + @mock.patch('google.auth.iam.Signer.sign', autospec=True) + @mock.patch('google.oauth2._client.id_token_jwt_grant', autospec=True) + def test_before_request_refreshes( + self, id_token_jwt_grant, sign, get, utcnow): + get.side_effect = [{ + 'email': 'service-account@example.com', + 'scopes': 'one two' + }] + sign.side_effect = [b'signature'] + id_token_jwt_grant.side_effect = [( + 'idtoken', + datetime.datetime.utcfromtimestamp(3600), + {}, + )] + + request = mock.create_autospec(transport.Request, instance=True) + self.credentials = credentials.IDTokenCredentials( + request=request, target_audience="https://audience.com") + + # Credentials should start as invalid + assert not self.credentials.valid + + # before_request should cause a refresh + request = mock.create_autospec(transport.Request, instance=True) + self.credentials.before_request( + request, 'GET', 'http://example.com?a=1#3', {}) + + # The refresh endpoint should've been called. + assert get.called + + # Credentials should now be valid. + assert self.credentials.valid + + @mock.patch('google.auth.compute_engine._metadata.get', autospec=True) + @mock.patch('google.auth.iam.Signer.sign', autospec=True) + def test_sign_bytes(self, sign, get): + get.side_effect = [{ + 'email': 'service-account@example.com', + 'scopes': ['one', 'two'] + }] + sign.side_effect = [b'signature'] + + request = mock.create_autospec(transport.Request, instance=True) + response = mock.Mock() + response.data = b'{"signature": "c2lnbmF0dXJl"}' + response.status = 200 + request.side_effect = [response] + + self.credentials = credentials.IDTokenCredentials( + request=request, target_audience="https://audience.com") + + # Generate authorization grant: + signature = self.credentials.sign_bytes(b"some bytes") + + # The JWT token signature is 'signature' encoded in base 64: + assert signature == b'signature' From 0d99a724a3a1c722f82ad5f2ff850876d8e4e02b Mon Sep 17 00:00:00 2001 From: Thea Flowers Date: Thu, 31 May 2018 14:52:06 -0700 Subject: [PATCH 184/966] Warn when using user credentials from the Cloud SDK (#266) --- .../google-auth/google/auth/_cloud_sdk.py | 3 ++ packages/google-auth/google/auth/_default.py | 31 ++++++++++++++++--- .../tests/data/authorized_user_cloud_sdk.json | 6 ++++ packages/google-auth/tests/test__default.py | 11 +++++++ 4 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 packages/google-auth/tests/data/authorized_user_cloud_sdk.json diff --git a/packages/google-auth/google/auth/_cloud_sdk.py b/packages/google-auth/google/auth/_cloud_sdk.py index 31be5e7cc9dd..0d4b222f216e 100644 --- a/packages/google-auth/google/auth/_cloud_sdk.py +++ b/packages/google-auth/google/auth/_cloud_sdk.py @@ -34,6 +34,9 @@ _CLOUD_SDK_WINDOWS_COMMAND = 'gcloud.cmd' # The command to get the Cloud SDK configuration _CLOUD_SDK_CONFIG_COMMAND = ('config', 'config-helper', '--format', 'json') +# Cloud SDK's application-default client ID +CLOUD_SDK_CLIENT_ID = ( + '764086051850-6qr4p6gpi6hn506pt8ejuq83di341hur.apps.googleusercontent.com') def get_config_path(): diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index d63dcee3bcb6..1f75be059a3d 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -21,6 +21,7 @@ import json import logging import os +import warnings import six @@ -36,13 +37,34 @@ _VALID_TYPES = (_AUTHORIZED_USER_TYPE, _SERVICE_ACCOUNT_TYPE) # Help message when no credentials can be found. -_HELP_MESSAGE = """ -Could not automatically determine credentials. Please set {env} or -explicitly create credential and re-run the application. For more -information, please see +_HELP_MESSAGE = """\ +Could not automatically determine credentials. Please set {env} or \ +explicitly create credentials and re-run the application. For more \ +information, please see \ https://developers.google.com/accounts/docs/application-default-credentials. """.format(env=environment_vars.CREDENTIALS).strip() +# Warning when using Cloud SDK user credentials +_CLOUD_SDK_CREDENTIALS_WARNING = """\ +Your application has authenticated using end user credentials from Google \ +Cloud SDK. We recommend that most server applications use service accounts \ +instead. If your application continues to use end user credentials from Cloud \ +SDK, you might receive a "quota exceeded" or "API not enabled" error. For \ +more information about service accounts, see \ +https://cloud.google.com/docs/authentication/.""" + + +def _warn_about_problematic_credentials(credentials): + """Determines if the credentials are problematic. + + Credentials from the Cloud SDK that are associated with Cloud SDK's project + are problematic because they may not have APIs enabled and have limited + quota. If this is the case, warn about it. + """ + from google.auth import _cloud_sdk + if credentials.client_id == _cloud_sdk.CLOUD_SDK_CLIENT_ID: + warnings.warn(_CLOUD_SDK_CREDENTIALS_WARNING) + def _load_credentials_from_file(filename): """Loads credentials from a file. @@ -90,6 +112,7 @@ def _load_credentials_from_file(filename): new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) six.raise_from(new_exc, caught_exc) # Authorized user credentials do not contain the project ID. + _warn_about_problematic_credentials(credentials) return credentials, None elif credential_type == _SERVICE_ACCOUNT_TYPE: diff --git a/packages/google-auth/tests/data/authorized_user_cloud_sdk.json b/packages/google-auth/tests/data/authorized_user_cloud_sdk.json new file mode 100644 index 000000000000..c9e19a66e01c --- /dev/null +++ b/packages/google-auth/tests/data/authorized_user_cloud_sdk.json @@ -0,0 +1,6 @@ +{ + "client_id": "764086051850-6qr4p6gpi6hn506pt8ejuq83di341hur.apps.googleusercontent.com", + "client_secret": "secret", + "refresh_token": "alabalaportocala", + "type": "authorized_user" +} diff --git a/packages/google-auth/tests/test__default.py b/packages/google-auth/tests/test__default.py index 68c4fb035144..d7d537c82b3e 100644 --- a/packages/google-auth/tests/test__default.py +++ b/packages/google-auth/tests/test__default.py @@ -33,6 +33,9 @@ with open(AUTHORIZED_USER_FILE) as fh: AUTHORIZED_USER_FILE_DATA = json.load(fh) +AUTHORIZED_USER_CLOUD_SDK_FILE = os.path.join( + DATA_DIR, 'authorized_user_cloud_sdk.json') + SERVICE_ACCOUNT_FILE = os.path.join(DATA_DIR, 'service_account.json') with open(SERVICE_ACCOUNT_FILE) as fh: @@ -88,6 +91,14 @@ def test__load_credentials_from_file_authorized_user_bad_format(tmpdir): assert excinfo.match(r'missing fields') +def test__load_credentials_from_file_authorized_user_cloud_sdk(): + with pytest.warns(UserWarning, matches='Cloud SDK'): + credentials, project_id = _default._load_credentials_from_file( + AUTHORIZED_USER_CLOUD_SDK_FILE) + assert isinstance(credentials, google.oauth2.credentials.Credentials) + assert project_id is None + + def test__load_credentials_from_file_service_account(): credentials, project_id = _default._load_credentials_from_file( SERVICE_ACCOUNT_FILE) From f857d416fa447630a435797401c753fdc8f41525 Mon Sep 17 00:00:00 2001 From: Thea Flowers Date: Thu, 31 May 2018 14:59:17 -0700 Subject: [PATCH 185/966] Release v1.5.0 (#267) --- packages/google-auth/CHANGELOG.rst | 7 +++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.rst b/packages/google-auth/CHANGELOG.rst index 9d22c60f1608..6506f6789aa6 100644 --- a/packages/google-auth/CHANGELOG.rst +++ b/packages/google-auth/CHANGELOG.rst @@ -1,6 +1,13 @@ Changelog ========= +v1.5.0 +------ + +- Warn when using user credentials from the Cloud SDK (#266) +- Add compute engine-based IDTokenCredentials (#236) +- Corrected some typos (#265) + v1.4.2 ------ diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index a1af80f1fb4d..6bda014b136c 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -31,7 +31,7 @@ setup( name='google-auth', - version='1.4.2', + version='1.5.0', author='Google Cloud Platform', author_email='jonwayne+google-auth@google.com', description='Google Authentication Library', From 0c61531c3be67dac50ab59179b881ecc565fd691 Mon Sep 17 00:00:00 2001 From: Felix Yan Date: Thu, 7 Jun 2018 23:40:45 +0800 Subject: [PATCH 186/966] Fix some typos in test_urllib3.py (#268) --- packages/google-auth/tests/transport/test_urllib3.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/tests/transport/test_urllib3.py b/packages/google-auth/tests/transport/test_urllib3.py index 4d1a5a6b066f..a119b74c9558 100644 --- a/packages/google-auth/tests/transport/test_urllib3.py +++ b/packages/google-auth/tests/transport/test_urllib3.py @@ -34,13 +34,13 @@ def test_timeout(self): assert http.request.call_args[1]['timeout'] == 5 -def test__make_default_http_with_certfi(): +def test__make_default_http_with_certifi(): http = google.auth.transport.urllib3._make_default_http() assert 'cert_reqs' in http.connection_pool_kw @mock.patch.object(google.auth.transport.urllib3, 'certifi', new=None) -def test__make_default_http_without_certfi(): +def test__make_default_http_without_certifi(): http = google.auth.transport.urllib3._make_default_http() assert 'cert_reqs' not in http.connection_pool_kw From 6966e8dd69942f476d6d0acf4e3c6aad2b810e06 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Thu, 7 Jun 2018 13:15:23 -0400 Subject: [PATCH 187/966] Add code-of-conduct codument. (#270) --- packages/google-auth/CODE_OF_CONDUCT.md | 43 +++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 packages/google-auth/CODE_OF_CONDUCT.md diff --git a/packages/google-auth/CODE_OF_CONDUCT.md b/packages/google-auth/CODE_OF_CONDUCT.md new file mode 100644 index 000000000000..46b2a08ea6d1 --- /dev/null +++ b/packages/google-auth/CODE_OF_CONDUCT.md @@ -0,0 +1,43 @@ +# Contributor Code of Conduct + +As contributors and maintainers of this project, +and in the interest of fostering an open and welcoming community, +we pledge to respect all people who contribute through reporting issues, +posting feature requests, updating documentation, +submitting pull requests or patches, and other activities. + +We are committed to making participation in this project +a harassment-free experience for everyone, +regardless of level of experience, gender, gender identity and expression, +sexual orientation, disability, personal appearance, +body size, race, ethnicity, age, religion, or nationality. + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery +* Personal attacks +* Trolling or insulting/derogatory comments +* Public or private harassment +* Publishing other's private information, +such as physical or electronic +addresses, without explicit permission +* Other unethical or unprofessional conduct. + +Project maintainers have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct. +By adopting this Code of Conduct, +project maintainers commit themselves to fairly and consistently +applying these principles to every aspect of managing this project. +Project maintainers who do not follow or enforce the Code of Conduct +may be permanently removed from the project team. + +This code of conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. + +Instances of abusive, harassing, or otherwise unacceptable behavior +may be reported by opening an issue +or contacting one or more of the project maintainers. + +This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0, +available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/) From 416cada2f652ea4d7437bfe27cdd686fafc84760 Mon Sep 17 00:00:00 2001 From: Thea Flowers Date: Tue, 31 Jul 2018 14:25:20 -0700 Subject: [PATCH 188/966] Use new Auth URIs (#281) --- packages/google-auth/google/oauth2/credentials.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/google/oauth2/credentials.py b/packages/google-auth/google/oauth2/credentials.py index 8e2a7f80593f..4cb909cb6fa9 100644 --- a/packages/google-auth/google/oauth2/credentials.py +++ b/packages/google-auth/google/oauth2/credentials.py @@ -43,7 +43,7 @@ # The Google OAuth 2.0 token endpoint. Used for authorized user credentials. -_GOOGLE_OAUTH2_TOKEN_ENDPOINT = 'https://accounts.google.com/o/oauth2/token' +_GOOGLE_OAUTH2_TOKEN_ENDPOINT = 'https://oauth2.googleapis.com/token' class Credentials(credentials.ReadOnlyScoped, credentials.Credentials): From bca3e28cc5ee172f0e716b3fa8e01e4d66d87212 Mon Sep 17 00:00:00 2001 From: Felix Yan Date: Wed, 1 Aug 2018 06:37:20 +0800 Subject: [PATCH 189/966] Fix check for error text on Python 3.7 (#278) --- packages/google-auth/tests/test_jwt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/tests/test_jwt.py b/packages/google-auth/tests/test_jwt.py index 22c5bc538649..4a64717ff0fb 100644 --- a/packages/google-auth/tests/test_jwt.py +++ b/packages/google-auth/tests/test_jwt.py @@ -118,7 +118,7 @@ def test_decode_bad_token_wrong_number_of_segments(): def test_decode_bad_token_not_base64(): with pytest.raises((ValueError, TypeError)) as excinfo: jwt.decode('1.2.3', PUBLIC_CERT_BYTES) - assert excinfo.match(r'Incorrect padding') + assert excinfo.match(r'Incorrect padding|more than a multiple of 4') def test_decode_bad_token_not_json(): From 96c0803754f092bfa94adb85e5cba966512ee791 Mon Sep 17 00:00:00 2001 From: Thea Flowers Date: Tue, 31 Jul 2018 16:13:14 -0700 Subject: [PATCH 190/966] Release v1.5.1 (#282) --- packages/google-auth/CHANGELOG.rst | 8 ++++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.rst b/packages/google-auth/CHANGELOG.rst index 6506f6789aa6..54fbbb184dd7 100644 --- a/packages/google-auth/CHANGELOG.rst +++ b/packages/google-auth/CHANGELOG.rst @@ -1,6 +1,14 @@ Changelog ========= +v1.5.1 +------ + +- Fix check for error text on Python 3.7. (#278) +- Use new Auth URIs. (#281) +- Add code-of-conduct document. (#270) +- Fix some typos in test_urllib3.py (#268) + v1.5.0 ------ diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 6bda014b136c..582d08f40671 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -31,7 +31,7 @@ setup( name='google-auth', - version='1.5.0', + version='1.5.1', author='Google Cloud Platform', author_email='jonwayne+google-auth@google.com', description='Google Authentication Library', From 42f773dc51337949fbc174ee5bdbd723f6e2080f Mon Sep 17 00:00:00 2001 From: Thilo Maurer Date: Wed, 1 Aug 2018 23:33:08 +0200 Subject: [PATCH 191/966] Make classifiers in setup.py an array. (#280) --- packages/google-auth/setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 582d08f40671..1d603c780704 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -42,7 +42,7 @@ install_requires=DEPENDENCIES, license='Apache 2.0', keywords='google auth oauth client', - classifiers=( + classifiers=[ 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', @@ -57,5 +57,5 @@ 'Operating System :: MacOS :: MacOS X', 'Operating System :: OS Independent', 'Topic :: Internet :: WWW/HTTP', - ), + ], ) From e6e675766b1f142f636d8d04e50e68ea2f6deddb Mon Sep 17 00:00:00 2001 From: Jeffrey Sorensen Date: Mon, 13 Aug 2018 16:39:06 -0400 Subject: [PATCH 192/966] Remove punctuation which becomes part of the url (#284) In colab and likely other applications, the period is incorrectly bound into the url, which is then invalid and produces a 404 DefaultCredentialsError: Could not automatically determine credentials. Please set GOOGLE_APPLICATION_CREDENTIALS or explicitly create credential and re-run the application. For more information, please see https://developers.google.com/accounts/docs/application-default-credentials. --- packages/google-auth/google/auth/_default.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index 1f75be059a3d..657aecf45817 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -41,7 +41,7 @@ Could not automatically determine credentials. Please set {env} or \ explicitly create credentials and re-run the application. For more \ information, please see \ -https://developers.google.com/accounts/docs/application-default-credentials. +https://developers.google.com/accounts/docs/application-default-credentials """.format(env=environment_vars.CREDENTIALS).strip() # Warning when using Cloud SDK user credentials @@ -51,7 +51,7 @@ instead. If your application continues to use end user credentials from Cloud \ SDK, you might receive a "quota exceeded" or "API not enabled" error. For \ more information about service accounts, see \ -https://cloud.google.com/docs/authentication/.""" +https://cloud.google.com/docs/authentication/""" def _warn_about_problematic_credentials(credentials): From 236784ec6ab12afa9615fe132d518edc17273c5d Mon Sep 17 00:00:00 2001 From: Christopher Wilcox Date: Wed, 19 Sep 2018 10:35:40 -0700 Subject: [PATCH 193/966] Update README --- packages/google-auth/README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/README.rst b/packages/google-auth/README.rst index 72237a7868e2..512e550ed0e4 100644 --- a/packages/google-auth/README.rst +++ b/packages/google-auth/README.rst @@ -34,7 +34,7 @@ Google Auth Python Library has usage and reference documentation at `google-auth Maintainers ----------- -- `@jonparrott `_ (Jon Wayne Parrott) +- `@theacodes `_ (Thea Flowers) - `@dhermes `_ (Danny Hermes) - `@lukesneeringer `_ (Luke Sneeringer) From b027663e7f529a8f8bd33a74abdd37241abe23c6 Mon Sep 17 00:00:00 2001 From: Christopher Wilcox Date: Fri, 21 Sep 2018 10:03:04 -0700 Subject: [PATCH 194/966] Update link to documentation for default credentials (#296) --- packages/google-auth/google/auth/_default.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index 657aecf45817..c93b48963263 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -41,7 +41,7 @@ Could not automatically determine credentials. Please set {env} or \ explicitly create credentials and re-run the application. For more \ information, please see \ -https://developers.google.com/accounts/docs/application-default-credentials +https://cloud.google.com/docs/authentication/getting-started """.format(env=environment_vars.CREDENTIALS).strip() # Warning when using Cloud SDK user credentials From f9737c734a6d4da3d50ebc9ba1d1b2922f2b145a Mon Sep 17 00:00:00 2001 From: Teddy Sudol Date: Fri, 5 Oct 2018 10:20:33 -0700 Subject: [PATCH 195/966] Enable static type checking with pytype (#298) * Ignore pytype import errors in google/auth. These errors are raised because the packages are not required, i.e. not listed in setup.py. We can't guarantee they'll be installed or that their pyi files exist in github.com/python/typeshed, so silence potential errors instead. This does impact the accuracy of type checking. * Ignore pytype import errors in transport/. These imports are not listed in setup.py, because they are optional -- it is assumed the user has them installed if needed. Disabling import-error for these imports prevents useless errors from pytype. * Ignore various type errors raised by pytype. - `__init__.py`: pytype is not aware of `__path__`. - jwt.py: the pyi file for urllib.unparse is not aware of None. Empty strings are clearer. * Add pytype disable comments for scripts/ oauth2client isn't listed as a requirement, so users may not have it installed. * Fix lint errors from pytype directives. * Enable pytype -V3.6. A few notes: - Previous commits fixed type errors detected by pytype. - setup.cfg disables pytype's `pyi-error`. This is necessary due to incomplete type stubs in https://github.com/python/typeshed. - This only enables pytype's Python3.6 checks. Python2.7 is supported by pytype but incomplete type stubs cause spurious type errors. * Remove pytype directives. Updates to pytype made these directives unnecessary. * Move pytype install command. * Add pytype to tox. * Remove pytype directives for tox-installed imports. These imports are handled by setup.py and tox.ini, so they'll be available when pytype is run under tox. * Fix lint error. --- packages/google-auth/.gitignore | 1 + packages/google-auth/.travis.yml | 2 ++ packages/google-auth/google/auth/_oauth2client.py | 3 ++- packages/google-auth/google/auth/app_engine.py | 2 ++ packages/google-auth/google/auth/jwt.py | 2 +- packages/google-auth/setup.cfg | 15 +++++++++++++++ packages/google-auth/tox.ini | 10 +++++++++- 7 files changed, 32 insertions(+), 3 deletions(-) diff --git a/packages/google-auth/.gitignore b/packages/google-auth/.gitignore index c6ab9e7e678d..88a8b8bc4f5d 100644 --- a/packages/google-auth/.gitignore +++ b/packages/google-auth/.gitignore @@ -35,3 +35,4 @@ tests/data/user-key.json # Generated files pylintrc pylintrc.test +pytype_output/ diff --git a/packages/google-auth/.travis.yml b/packages/google-auth/.travis.yml index 8c6728826ffa..43f8e31e6c0a 100644 --- a/packages/google-auth/.travis.yml +++ b/packages/google-auth/.travis.yml @@ -22,6 +22,8 @@ matrix: env: TOXENV=py36-system SYSTEM_TEST=1 SKIP_APP_ENGINE_SYSTEM_TEST=1 - python: 2.7 env: TOXENV=py27-system SYSTEM_TEST=1 SKIP_APP_ENGINE_SYSTEM_TEST=1 + - python: 3.6 + env: TOXENV=pytype cache: directories: - ${HOME}/.cache diff --git a/packages/google-auth/google/auth/_oauth2client.py b/packages/google-auth/google/auth/_oauth2client.py index 71fd7bf41d12..afe7dc45dc39 100644 --- a/packages/google-auth/google/auth/_oauth2client.py +++ b/packages/google-auth/google/auth/_oauth2client.py @@ -25,6 +25,7 @@ from google.auth import _helpers import google.auth.app_engine +import google.auth.compute_engine import google.oauth2.credentials import google.oauth2.service_account @@ -37,7 +38,7 @@ ImportError('oauth2client is not installed.'), caught_exc) try: - import oauth2client.contrib.appengine + import oauth2client.contrib.appengine # pytype: disable=import-error _HAS_APPENGINE = True except ImportError: _HAS_APPENGINE = False diff --git a/packages/google-auth/google/auth/app_engine.py b/packages/google-auth/google/auth/app_engine.py index f47dae12682c..91ba8427d106 100644 --- a/packages/google-auth/google/auth/app_engine.py +++ b/packages/google-auth/google/auth/app_engine.py @@ -28,10 +28,12 @@ from google.auth import credentials from google.auth import crypt +# pytype: disable=import-error try: from google.appengine.api import app_identity except ImportError: app_identity = None +# pytype: enable=import-error class Signer(crypt.Signer): diff --git a/packages/google-auth/google/auth/jwt.py b/packages/google-auth/google/auth/jwt.py index ef23db233c0b..3805f3716287 100644 --- a/packages/google-auth/google/auth/jwt.py +++ b/packages/google-auth/google/auth/jwt.py @@ -738,7 +738,7 @@ def before_request(self, request, method, url, headers): parts = urllib.parse.urlsplit(url) # Strip query string and fragment audience = urllib.parse.urlunsplit( - (parts.scheme, parts.netloc, parts.path, None, None)) + (parts.scheme, parts.netloc, parts.path, "", "")) token = self._get_jwt_for_audience(audience) self.apply(headers, token=token) diff --git a/packages/google-auth/setup.cfg b/packages/google-auth/setup.cfg index 2a9acf13daa9..0f15a3826863 100644 --- a/packages/google-auth/setup.cfg +++ b/packages/google-auth/setup.cfg @@ -1,2 +1,17 @@ [bdist_wheel] universal = 1 + +[pytype] +# Where to start analysis. +inputs = . +# Some files aren't worth analyzing, namely tests. Hidden directories (e.g. +# .tox) are automatically filtered out. +exclude = tests system_tests +# All pytype output goes here. +output = pytype_output +# Python version (major.minor) of the target code. +python_version = 3.6 +# Paths to source code directories, separated by ':'. +pythonpath = . +# Errors to disable. +disable = pyi-error diff --git a/packages/google-auth/tox.ini b/packages/google-auth/tox.ini index 1c52c4bb0241..59fd6aba1706 100644 --- a/packages/google-auth/tox.ini +++ b/packages/google-auth/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = lint,py27,py34,py35,py36,pypy,cover +envlist = lint,py27,py34,py35,py36,pypy,cover,pytype [testenv] deps = @@ -82,3 +82,11 @@ deps = flake8 flake8-import-order docutils + +[testenv:pytype] +basepython = python3.6 +commands = + pytype +deps = + {[testenv]deps} + pytype From 1548befe32b2ea58f4c2068be6427a39702e5c3d Mon Sep 17 00:00:00 2001 From: Charles Engelke Date: Wed, 7 Nov 2018 11:21:50 -0800 Subject: [PATCH 196/966] Update trampoline.sh (#302) Clean up temp files at exit. --- packages/google-auth/.kokoro/trampoline.sh | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/.kokoro/trampoline.sh b/packages/google-auth/.kokoro/trampoline.sh index 6e293e638bb3..fef7c24de02d 100755 --- a/packages/google-auth/.kokoro/trampoline.sh +++ b/packages/google-auth/.kokoro/trampoline.sh @@ -1,5 +1,4 @@ #!/bin/bash - # Copyright 2017 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,4 +13,16 @@ # See the License for the specific language governing permissions and # limitations under the License. +set -eo pipefail + +# Always run the cleanup script, regardless of the success of bouncing into +# the container. + +function cleanup() { + chmod +x ${KOKORO_GFILE_DIR}/trampoline_cleanup.sh + ${KOKORO_GFILE_DIR}/trampoline_cleanup.sh + echo "cleanup"; +} +trap cleanup EXIT + python3 "${KOKORO_GFILE_DIR}/trampoline_v1.py" From 5a7b2107f306c9daa14b21eaae78848485885ecc Mon Sep 17 00:00:00 2001 From: Justin Beckwith Date: Wed, 7 Nov 2018 14:19:27 -0800 Subject: [PATCH 197/966] Update github issue templates (#300) --- packages/google-auth/.github/CONTRIBUTING.md | 28 +++++++++++++++++ .../.github/ISSUE_TEMPLATE/bug_report.md | 31 +++++++++++++++++++ .../.github/ISSUE_TEMPLATE/feature_request.md | 18 +++++++++++ .../.github/ISSUE_TEMPLATE/support_request.md | 7 +++++ 4 files changed, 84 insertions(+) create mode 100644 packages/google-auth/.github/CONTRIBUTING.md create mode 100644 packages/google-auth/.github/ISSUE_TEMPLATE/bug_report.md create mode 100644 packages/google-auth/.github/ISSUE_TEMPLATE/feature_request.md create mode 100644 packages/google-auth/.github/ISSUE_TEMPLATE/support_request.md diff --git a/packages/google-auth/.github/CONTRIBUTING.md b/packages/google-auth/.github/CONTRIBUTING.md new file mode 100644 index 000000000000..939e5341e74d --- /dev/null +++ b/packages/google-auth/.github/CONTRIBUTING.md @@ -0,0 +1,28 @@ +# How to Contribute + +We'd love to accept your patches and contributions to this project. There are +just a few small guidelines you need to follow. + +## Contributor License Agreement + +Contributions to this project must be accompanied by a Contributor License +Agreement. You (or your employer) retain the copyright to your contribution; +this simply gives us permission to use and redistribute your contributions as +part of the project. Head over to to see +your current agreements on file or to sign a new one. + +You generally only need to submit a CLA once, so if you've already submitted one +(even if it was for a different project), you probably don't need to do it +again. + +## Code reviews + +All submissions, including submissions by project members, require review. We +use GitHub pull requests for this purpose. Consult +[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more +information on using pull requests. + +## Community Guidelines + +This project follows [Google's Open Source Community +Guidelines](https://opensource.google.com/conduct/). diff --git a/packages/google-auth/.github/ISSUE_TEMPLATE/bug_report.md b/packages/google-auth/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000000..e43ad68cb49c --- /dev/null +++ b/packages/google-auth/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,31 @@ +--- +name: Bug report +about: Create a report to help us improve + +--- + +Thanks for stopping by to let us know something could be better! + +**PLEASE READ**: If you have a support contract with Google, please create an issue in the [support console](https://cloud.google.com/support/) instead of filing on GitHub. This will ensure a timely response. + +Please run down the following list and make sure you've tried the usual "quick fixes": + + - Search the issues already opened: https://github.com/googleapis/google-auth-library-python/issues + +If you are still having issues, please be sure to include as much information as possible: + +#### Environment details + + - OS: + - Python version: + - pip version: + - `google-auth` version: + +#### Steps to reproduce + + 1. ? + 2. ? + +Making sure to follow these steps will guarantee the quickest resolution possible. + +Thanks! diff --git a/packages/google-auth/.github/ISSUE_TEMPLATE/feature_request.md b/packages/google-auth/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 000000000000..6365857f33c6 --- /dev/null +++ b/packages/google-auth/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,18 @@ +--- +name: Feature request +about: Suggest an idea for this library + +--- + +Thanks for stopping by to let us know something could be better! + +**PLEASE READ**: If you have a support contract with Google, please create an issue in the [support console](https://cloud.google.com/support/) instead of filing on GitHub. This will ensure a timely response. + + **Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + **Describe the solution you'd like** +A clear and concise description of what you want to happen. + **Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + **Additional context** +Add any other context or screenshots about the feature request here. diff --git a/packages/google-auth/.github/ISSUE_TEMPLATE/support_request.md b/packages/google-auth/.github/ISSUE_TEMPLATE/support_request.md new file mode 100644 index 000000000000..995869032125 --- /dev/null +++ b/packages/google-auth/.github/ISSUE_TEMPLATE/support_request.md @@ -0,0 +1,7 @@ +--- +name: Support request +about: If you have a support contract with Google, please create an issue in the Google Cloud Support console. + +--- + +**PLEASE READ**: If you have a support contract with Google, please create an issue in the [support console](https://cloud.google.com/support/) instead of filing on GitHub. This will ensure a timely response. From 204e16d76ed3dee4311e78c2bab43bc084c37d56 Mon Sep 17 00:00:00 2001 From: salrashid123 Date: Fri, 9 Nov 2018 11:05:34 -0800 Subject: [PATCH 198/966] Add google.auth.impersonated_credentials (#299) --- packages/google-auth/docs/index.rst | 1 + .../google.auth.impersonated_credentials.rst | 7 + .../docs/reference/google.auth.rst | 1 + packages/google-auth/docs/user-guide.rst | 29 +++ .../google/auth/impersonated_credentials.py | 239 ++++++++++++++++++ .../tests/test_impersonated_credentials.py | 204 +++++++++++++++ 6 files changed, 481 insertions(+) create mode 100644 packages/google-auth/docs/reference/google.auth.impersonated_credentials.rst create mode 100644 packages/google-auth/google/auth/impersonated_credentials.py create mode 100644 packages/google-auth/tests/test_impersonated_credentials.py diff --git a/packages/google-auth/docs/index.rst b/packages/google-auth/docs/index.rst index 56e3ecaf6849..1eb3d861ad0f 100644 --- a/packages/google-auth/docs/index.rst +++ b/packages/google-auth/docs/index.rst @@ -16,6 +16,7 @@ also provides integration with several HTTP libraries. - Support for signing and verifying :mod:`JWTs `. - Support for verifying and decoding :mod:`ID Tokens `. - Support for Google :mod:`Service Account credentials `. +- Support for Google :mod:`Impersonated Credentials `. - Support for :mod:`Google Compute Engine credentials `. - Support for :mod:`Google App Engine standard credentials `. - Support for various transports, including diff --git a/packages/google-auth/docs/reference/google.auth.impersonated_credentials.rst b/packages/google-auth/docs/reference/google.auth.impersonated_credentials.rst new file mode 100644 index 000000000000..653708ef778c --- /dev/null +++ b/packages/google-auth/docs/reference/google.auth.impersonated_credentials.rst @@ -0,0 +1,7 @@ +google.auth.impersonated\_credentials module +============================================ + +.. automodule:: google.auth.impersonated_credentials + :members: + :inherited-members: + :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.auth.rst b/packages/google-auth/docs/reference/google.auth.rst index 244d0bbd3b39..bc6740b09bcd 100644 --- a/packages/google-auth/docs/reference/google.auth.rst +++ b/packages/google-auth/docs/reference/google.auth.rst @@ -25,5 +25,6 @@ Submodules google.auth.environment_vars google.auth.exceptions google.auth.iam + google.auth.impersonated_credentials google.auth.jwt diff --git a/packages/google-auth/docs/user-guide.rst b/packages/google-auth/docs/user-guide.rst index 060d9b84a01a..75879179522c 100644 --- a/packages/google-auth/docs/user-guide.rst +++ b/packages/google-auth/docs/user-guide.rst @@ -205,6 +205,35 @@ You can also use :class:`google_auth_oauthlib.flow.Flow` to perform the OAuth .. _requests-oauthlib: https://requests-oauthlib.readthedocs.io/en/latest/ +Impersonated credentials +++++++++++++++++++++++++ + +Impersonated Credentials allows one set of credentials issued to a user or service account +to impersonate another. The target service account must grant the source credential +the "Service Account Token Creator" IAM role:: + + from google.auth import impersonated_credentials + + target_scopes = ['https://www.googleapis.com/auth/devstorage.read_only'] + source_credentials = service_account.Credentials.from_service_account_file( + '/path/to/svc_account.json', + scopes=target_scopes) + + target_credentials = impersonated_credentials.Credentials( + source_credentials=source_credentials, + target_principal='impersonated-account@_project_.iam.gserviceaccount.com', + target_scopes=target_scopes, + lifetime=500) + client = storage.Client(credentials=target_credentials) + buckets = client.list_buckets(project='your_project') + for bucket in buckets: + print bucket.name + + +In the example above `source_credentials` does not have direct access to list buckets +in the target project. Using `ImpersonatedCredentials` will allow the source_credentials +to assume the identity of a target_principal that does have access + Making authenticated requests ----------------------------- diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py new file mode 100644 index 000000000000..ca625b8d488f --- /dev/null +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -0,0 +1,239 @@ +# Copyright 2018 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google Cloud Impersonated credentials. + +This module provides authentication for applications where local credentials +impersonates a remote service account using `IAM Credentials API`_. + +This class can be used to impersonate a service account as long as the original +Credential object has the "Service Account Token Creator" role on the target +service account. + + .. _IAM Credentials API: + https://cloud.google.com/iam/credentials/reference/rest/ +""" + +import copy +from datetime import datetime +import json + +import six +from six.moves import http_client + +from google.auth import _helpers +from google.auth import credentials +from google.auth import exceptions + +_DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds + +_IAM_SCOPE = ['https://www.googleapis.com/auth/iam'] + +_IAM_ENDPOINT = ('https://iamcredentials.googleapis.com/v1/projects/-' + + '/serviceAccounts/{}:generateAccessToken') + +_REFRESH_ERROR = 'Unable to acquire impersonated credentials' +_LIFETIME_ERROR = 'Credentials with lifetime set cannot be renewed' + + +def _make_iam_token_request(request, principal, headers, body): + """Makes a request to the Google Cloud IAM service for an access token. + Args: + request (Request): The Request object to use. + principal (str): The principal to request an access token for. + headers (Mapping[str, str]): Map of headers to transmit. + body (Mapping[str, str]): JSON Payload body for the iamcredentials + API call. + + Raises: + TransportError: Raised if there is an underlying HTTP connection + Error + DefaultCredentialsError: Raised if the impersonated credentials + are not available. Common reasons are + `iamcredentials.googleapis.com` is not enabled or the + `Service Account Token Creator` is not assigned + """ + iam_endpoint = _IAM_ENDPOINT.format(principal) + + body = json.dumps(body) + + response = request( + url=iam_endpoint, + method='POST', + headers=headers, + body=body) + + response_body = response.data.decode('utf-8') + + if response.status != http_client.OK: + exceptions.RefreshError(_REFRESH_ERROR, response_body) + + try: + token_response = json.loads(response.data.decode('utf-8')) + token = token_response['accessToken'] + expiry = datetime.strptime( + token_response['expireTime'], '%Y-%m-%dT%H:%M:%SZ') + + return token, expiry + + except (KeyError, ValueError) as caught_exc: + new_exc = exceptions.RefreshError( + '{}: No access token or invalid expiration in response.'.format( + _REFRESH_ERROR), + response_body) + six.raise_from(new_exc, caught_exc) + + +class Credentials(credentials.Credentials): + """This module defines impersonated credentials which are essentially + impersonated identities. + + Impersonated Credentials allows credentials issued to a user or + service account to impersonate another. The target service account must + grant the originating credential principal the + `Service Account Token Creator`_ IAM role: + + For more information about Token Creator IAM role and + IAMCredentials API, see + `Creating Short-Lived Service Account Credentials`_. + + .. _Service Account Token Creator: + https://cloud.google.com/iam/docs/service-accounts#the_service_account_token_creator_role + + .. _Creating Short-Lived Service Account Credentials: + https://cloud.google.com/iam/docs/creating-short-lived-service-account-credentials + + Usage: + + First grant source_credentials the `Service Account Token Creator` + role on the target account to impersonate. In this example, the + service account represented by svc_account.json has the + token creator role on + `impersonated-account@_project_.iam.gserviceaccount.com`. + + Initialize a source credential which does not have access to + list bucket:: + + from google.oauth2 import service_acccount + + target_scopes = [ + 'https://www.googleapis.com/auth/devstorage.read_only'] + + source_credentials = ( + service_account.Credentials.from_service_account_file( + '/path/to/svc_account.json', + scopes=target_scopes)) + + Now use the source credentials to acquire credentials to impersonate + another service account:: + + from google.auth import impersonated_credentials + + target_credentials = impersonated_credentials.Credentials( + source_credentials=source_credentials, + target_principal='impersonated-account@_project_.iam.gserviceaccount.com', + target_scopes = target_scopes, + lifetime=500) + + Resource access is granted:: + + client = storage.Client(credentials=target_credentials) + buckets = client.list_buckets(project='your_project') + for bucket in buckets: + print bucket.name + """ + + def __init__(self, source_credentials, target_principal, + target_scopes, delegates=None, + lifetime=None): + """ + Args: + source_credentials (google.auth.Credentials): The source credential + used as to acquire the impersonated credentials. + target_principal (str): The service account to impersonate. + target_scopes (Sequence[str]): Scopes to request during the + authorization grant. + delegates (Sequence[str]): The chained list of delegates required + to grant the final access_token. If set, the sequence of + identities must have "Service Account Token Creator" capability + granted to the prceeding identity. For example, if set to + [serviceAccountB, serviceAccountC], the source_credential + must have the Token Creator role on serviceAccountB. + serviceAccountB must have the Token Creator on serviceAccountC. + Finally, C must have Token Creator on target_principal. + If left unset, source_credential must have that role on + target_principal. + lifetime (int): Number of seconds the delegated credential should + be valid for (upto 3600). If set, the credentials will + **not** get refreshed after expiration. If not set, the + credentials will be refreshed every 3600s. + """ + + super(Credentials, self).__init__() + + self._source_credentials = copy.copy(source_credentials) + self._source_credentials._scopes = _IAM_SCOPE + self._target_principal = target_principal + self._target_scopes = target_scopes + self._delegates = delegates + self._lifetime = lifetime + self.token = None + self.expiry = _helpers.utcnow() + + @_helpers.copy_docstring(credentials.Credentials) + def refresh(self, request): + if (self.token is not None and self._lifetime is not None): + self.expiry = _helpers.utcnow() + raise exceptions.RefreshError(_LIFETIME_ERROR) + self._source_credentials.refresh(request) + self._update_token(request) + + @property + def expired(self): + return _helpers.utcnow() >= self.expiry + + def _update_token(self, request): + """Updates credentials with a new access_token representing + the impersonated account. + + Args: + request (google.auth.transport.requests.Request): Request object + to use for refreshing credentials. + """ + + # Refresh our source credentials. + self._source_credentials.refresh(request) + + lifetime = self._lifetime + if (self._lifetime is None): + lifetime = _DEFAULT_TOKEN_LIFETIME_SECS + + body = { + "delegates": self._delegates, + "scope": self._target_scopes, + "lifetime": str(lifetime) + "s" + } + + headers = { + 'Content-Type': 'application/json', + } + + # Apply the source credentials authentication info. + self._source_credentials.apply(headers) + + self.token, self.expiry = _make_iam_token_request( + request=request, + principal=self._target_principal, + headers=headers, + body=body) diff --git a/packages/google-auth/tests/test_impersonated_credentials.py b/packages/google-auth/tests/test_impersonated_credentials.py new file mode 100644 index 000000000000..74342ce03f68 --- /dev/null +++ b/packages/google-auth/tests/test_impersonated_credentials.py @@ -0,0 +1,204 @@ +# Copyright 2018 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import datetime +import json +import os + +import mock +import pytest +from six.moves import http_client + +from google.auth import _helpers +from google.auth import crypt +from google.auth import exceptions +from google.auth import impersonated_credentials +from google.auth import transport +from google.auth.impersonated_credentials import Credentials +from google.oauth2 import service_account + +DATA_DIR = os.path.join(os.path.dirname(__file__), '', 'data') + +with open(os.path.join(DATA_DIR, 'privatekey.pem'), 'rb') as fh: + PRIVATE_KEY_BYTES = fh.read() + +SERVICE_ACCOUNT_JSON_FILE = os.path.join(DATA_DIR, 'service_account.json') + +with open(SERVICE_ACCOUNT_JSON_FILE, 'r') as fh: + SERVICE_ACCOUNT_INFO = json.load(fh) + +SIGNER = crypt.RSASigner.from_string(PRIVATE_KEY_BYTES, '1') +TOKEN_URI = 'https://example.com/oauth2/token' + + +@pytest.fixture +def mock_donor_credentials(): + with mock.patch('google.oauth2._client.jwt_grant', autospec=True) as grant: + grant.return_value = ( + "source token", + _helpers.utcnow() + datetime.timedelta(seconds=500), + {}) + yield grant + + +class TestImpersonatedCredentials(object): + + SERVICE_ACCOUNT_EMAIL = 'service-account@example.com' + TARGET_PRINCIPAL = 'impersonated@project.iam.gserviceaccount.com' + TARGET_SCOPES = ['https://www.googleapis.com/auth/devstorage.read_only'] + DELEGATES = [] + LIFETIME = 3600 + SOURCE_CREDENTIALS = service_account.Credentials( + SIGNER, SERVICE_ACCOUNT_EMAIL, TOKEN_URI) + + def make_credentials(self, lifetime=LIFETIME): + return Credentials( + source_credentials=self.SOURCE_CREDENTIALS, + target_principal=self.TARGET_PRINCIPAL, + target_scopes=self.TARGET_SCOPES, + delegates=self.DELEGATES, + lifetime=lifetime) + + def test_default_state(self): + credentials = self.make_credentials() + assert not credentials.valid + assert credentials.expired + + def make_request(self, data, status=http_client.OK, + headers=None, side_effect=None): + response = mock.create_autospec(transport.Response, instance=False) + response.status = status + response.data = _helpers.to_bytes(data) + response.headers = headers or {} + + request = mock.create_autospec(transport.Request, instance=False) + request.side_effect = side_effect + request.return_value = response + + return request + + def test_refresh_success(self, mock_donor_credentials): + credentials = self.make_credentials(lifetime=None) + token = 'token' + + expire_time = ( + _helpers.utcnow().replace(microsecond=0) + + datetime.timedelta(seconds=500)).isoformat('T') + 'Z' + response_body = { + "accessToken": token, + "expireTime": expire_time + } + + request = self.make_request( + data=json.dumps(response_body), + status=http_client.OK) + + credentials.refresh(request) + + assert credentials.valid + assert not credentials.expired + + def test_refresh_failure_malformed_expire_time( + self, mock_donor_credentials): + credentials = self.make_credentials(lifetime=None) + token = 'token' + + expire_time = ( + _helpers.utcnow() + datetime.timedelta(seconds=500)).isoformat('T') + response_body = { + "accessToken": token, + "expireTime": expire_time + } + + request = self.make_request( + data=json.dumps(response_body), + status=http_client.OK) + + with pytest.raises(exceptions.RefreshError) as excinfo: + credentials.refresh(request) + + assert excinfo.match(impersonated_credentials._REFRESH_ERROR) + + assert not credentials.valid + assert credentials.expired + + def test_refresh_failure_lifetime_specified(self, mock_donor_credentials): + credentials = self.make_credentials(lifetime=500) + token = 'token' + + expire_time = ( + _helpers.utcnow().replace(microsecond=0) + + datetime.timedelta(seconds=500)).isoformat('T') + 'Z' + response_body = { + "accessToken": token, + "expireTime": expire_time + } + + request = self.make_request( + data=json.dumps(response_body), + status=http_client.OK) + + credentials.refresh(request) + + with pytest.raises(exceptions.RefreshError) as excinfo: + credentials.refresh(request) + + assert excinfo.match(impersonated_credentials._LIFETIME_ERROR) + + assert not credentials.valid + assert credentials.expired + + def test_refresh_failure_unauthorzed(self, mock_donor_credentials): + credentials = self.make_credentials(lifetime=None) + + response_body = { + "error": { + "code": 403, + "message": "The caller does not have permission", + "status": "PERMISSION_DENIED" + } + } + + request = self.make_request( + data=json.dumps(response_body), + status=http_client.UNAUTHORIZED) + + with pytest.raises(exceptions.RefreshError) as excinfo: + credentials.refresh(request) + + assert excinfo.match(impersonated_credentials._REFRESH_ERROR) + + assert not credentials.valid + assert credentials.expired + + def test_refresh_failure_http_error(self, mock_donor_credentials): + credentials = self.make_credentials(lifetime=None) + + response_body = {} + + request = self.make_request( + data=json.dumps(response_body), + status=http_client.HTTPException) + + with pytest.raises(exceptions.RefreshError) as excinfo: + credentials.refresh(request) + + assert excinfo.match(impersonated_credentials._REFRESH_ERROR) + + assert not credentials.valid + assert credentials.expired + + def test_expired(self): + credentials = self.make_credentials(lifetime=None) + assert credentials.expired From 64a1abc350b5e012cf41dffff1566637533a1e2c Mon Sep 17 00:00:00 2001 From: Thea Flowers Date: Fri, 9 Nov 2018 11:21:47 -0800 Subject: [PATCH 199/966] Release google-auth-library-python 1.6.0 (#303) --- packages/google-auth/CHANGELOG.rst | 25 +++++++++++++++++++++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.rst b/packages/google-auth/CHANGELOG.rst index 54fbbb184dd7..988ac6985a85 100644 --- a/packages/google-auth/CHANGELOG.rst +++ b/packages/google-auth/CHANGELOG.rst @@ -1,6 +1,31 @@ Changelog ========= +v1.6.0 +------ + +11-09-2018 11:07 PST + +New Features +++++++++++++ + +- Add google.auth.impersonated_credentials ([#299](https://github.com/googleapis/google-auth-library-python/pull/299)) + +Documentation ++++++++++++++ + +- Update link to documentation for default credentials ([#296](https://github.com/googleapis/google-auth-library-python/pull/296)) +- Update github issue templates ([#300](https://github.com/googleapis/google-auth-library-python/pull/300)) +- Remove punctuation which becomes part of the url ([#284](https://github.com/googleapis/google-auth-library-python/pull/284)) + +Internal / Testing Changes +++++++++++++++++++++++++++ + +- Update trampoline.sh ([#302](https://github.com/googleapis/google-auth-library-python/pull/302)) +- Enable static type checking with pytype ([#298](https://github.com/googleapis/google-auth-library-python/pull/298)) +- Make classifiers in setup.py an array. ([#280](https://github.com/googleapis/google-auth-library-python/pull/280)) + + v1.5.1 ------ diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 1d603c780704..f86748c7c2ef 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -31,7 +31,7 @@ setup( name='google-auth', - version='1.5.1', + version='1.6.0', author='Google Cloud Platform', author_email='jonwayne+google-auth@google.com', description='Google Authentication Library', From 10cd18f416cf60daa02e978119f44c2ec2acab30 Mon Sep 17 00:00:00 2001 From: salrashid123 Date: Mon, 12 Nov 2018 09:49:16 -0800 Subject: [PATCH 200/966] Automatically refresh impersonated credentials (#304) --- .../google/auth/impersonated_credentials.py | 20 +++++--------- .../tests/test_impersonated_credentials.py | 26 ------------------- 2 files changed, 6 insertions(+), 40 deletions(-) diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index ca625b8d488f..32dfe8309e88 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -44,7 +44,6 @@ '/serviceAccounts/{}:generateAccessToken') _REFRESH_ERROR = 'Unable to acquire impersonated credentials' -_LIFETIME_ERROR = 'Credentials with lifetime set cannot be renewed' def _make_iam_token_request(request, principal, headers, body): @@ -122,6 +121,9 @@ class Credentials(credentials.Credentials): token creator role on `impersonated-account@_project_.iam.gserviceaccount.com`. + Enable the IAMCredentials API on the source project: + `gcloud services enable iamcredentials.googleapis.com`. + Initialize a source credential which does not have access to list bucket:: @@ -156,7 +158,7 @@ class Credentials(credentials.Credentials): def __init__(self, source_credentials, target_principal, target_scopes, delegates=None, - lifetime=None): + lifetime=_DEFAULT_TOKEN_LIFETIME_SECS): """ Args: source_credentials (google.auth.Credentials): The source credential @@ -175,9 +177,7 @@ def __init__(self, source_credentials, target_principal, If left unset, source_credential must have that role on target_principal. lifetime (int): Number of seconds the delegated credential should - be valid for (upto 3600). If set, the credentials will - **not** get refreshed after expiration. If not set, the - credentials will be refreshed every 3600s. + be valid for (upto 3600). """ super(Credentials, self).__init__() @@ -193,10 +193,6 @@ def __init__(self, source_credentials, target_principal, @_helpers.copy_docstring(credentials.Credentials) def refresh(self, request): - if (self.token is not None and self._lifetime is not None): - self.expiry = _helpers.utcnow() - raise exceptions.RefreshError(_LIFETIME_ERROR) - self._source_credentials.refresh(request) self._update_token(request) @property @@ -215,14 +211,10 @@ def _update_token(self, request): # Refresh our source credentials. self._source_credentials.refresh(request) - lifetime = self._lifetime - if (self._lifetime is None): - lifetime = _DEFAULT_TOKEN_LIFETIME_SECS - body = { "delegates": self._delegates, "scope": self._target_scopes, - "lifetime": str(lifetime) + "s" + "lifetime": str(self._lifetime) + "s" } headers = { diff --git a/packages/google-auth/tests/test_impersonated_credentials.py b/packages/google-auth/tests/test_impersonated_credentials.py index 74342ce03f68..68a2af8f2df4 100644 --- a/packages/google-auth/tests/test_impersonated_credentials.py +++ b/packages/google-auth/tests/test_impersonated_credentials.py @@ -133,32 +133,6 @@ def test_refresh_failure_malformed_expire_time( assert not credentials.valid assert credentials.expired - def test_refresh_failure_lifetime_specified(self, mock_donor_credentials): - credentials = self.make_credentials(lifetime=500) - token = 'token' - - expire_time = ( - _helpers.utcnow().replace(microsecond=0) + - datetime.timedelta(seconds=500)).isoformat('T') + 'Z' - response_body = { - "accessToken": token, - "expireTime": expire_time - } - - request = self.make_request( - data=json.dumps(response_body), - status=http_client.OK) - - credentials.refresh(request) - - with pytest.raises(exceptions.RefreshError) as excinfo: - credentials.refresh(request) - - assert excinfo.match(impersonated_credentials._LIFETIME_ERROR) - - assert not credentials.valid - assert credentials.expired - def test_refresh_failure_unauthorzed(self, mock_donor_credentials): credentials = self.make_credentials(lifetime=None) From 87990730b736c43ef9a4e1aeaac2da6e4f3caa39 Mon Sep 17 00:00:00 2001 From: Thea Flowers Date: Mon, 12 Nov 2018 10:26:12 -0800 Subject: [PATCH 201/966] Release v1.6.1 (#305) --- packages/google-auth/.travis.yml | 2 +- packages/google-auth/CHANGELOG.rst | 10 ++++++++++ packages/google-auth/setup.py | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/.travis.yml b/packages/google-auth/.travis.yml index 43f8e31e6c0a..7335fd72f4a9 100644 --- a/packages/google-auth/.travis.yml +++ b/packages/google-auth/.travis.yml @@ -39,7 +39,7 @@ deploy: on: tags: true distributions: sdist bdist_wheel - repo: GoogleCloudPlatform/google-auth-library-python + repo: googleapis/google-auth-library-python condition: "$TOXENV = \"cover\"" env: global: diff --git a/packages/google-auth/CHANGELOG.rst b/packages/google-auth/CHANGELOG.rst index 988ac6985a85..9b89ecfb4209 100644 --- a/packages/google-auth/CHANGELOG.rst +++ b/packages/google-auth/CHANGELOG.rst @@ -1,6 +1,16 @@ Changelog ========= +v1.6.1 +------ + +11-12-2018 10:10 PST + +Implementation Changes +++++++++++++++++++++++ + +- Automatically refresh impersonated credentials (#304) + v1.6.0 ------ diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index f86748c7c2ef..2d29b95c54d9 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -31,7 +31,7 @@ setup( name='google-auth', - version='1.6.0', + version='1.6.1', author='Google Cloud Platform', author_email='jonwayne+google-auth@google.com', description='Google Authentication Library', From 34812c8c9763b44edd72fcd89508da173155c6a1 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Tue, 13 Nov 2018 18:17:47 +0000 Subject: [PATCH 202/966] Link all the PRs in CHANGELOG (#307) The 1.6.0 release included Markdown link syntax for the PR's, which doesn't work in reStructuredText. Whilst fixing that I converted all the reference numbers to be links. --- packages/google-auth/CHANGELOG.rst | 194 ++++++++++++++--------------- 1 file changed, 97 insertions(+), 97 deletions(-) diff --git a/packages/google-auth/CHANGELOG.rst b/packages/google-auth/CHANGELOG.rst index 9b89ecfb4209..508f14252529 100644 --- a/packages/google-auth/CHANGELOG.rst +++ b/packages/google-auth/CHANGELOG.rst @@ -9,7 +9,7 @@ v1.6.1 Implementation Changes ++++++++++++++++++++++ -- Automatically refresh impersonated credentials (#304) +- Automatically refresh impersonated credentials (`#304 `_) v1.6.0 ------ @@ -19,112 +19,112 @@ v1.6.0 New Features ++++++++++++ -- Add google.auth.impersonated_credentials ([#299](https://github.com/googleapis/google-auth-library-python/pull/299)) +- Add google.auth.impersonated_credentials (`#299 `_) Documentation +++++++++++++ -- Update link to documentation for default credentials ([#296](https://github.com/googleapis/google-auth-library-python/pull/296)) -- Update github issue templates ([#300](https://github.com/googleapis/google-auth-library-python/pull/300)) -- Remove punctuation which becomes part of the url ([#284](https://github.com/googleapis/google-auth-library-python/pull/284)) +- Update link to documentation for default credentials (`#296 `_) +- Update github issue templates (`#300 `_) +- Remove punctuation which becomes part of the url (`#284 `_) Internal / Testing Changes ++++++++++++++++++++++++++ -- Update trampoline.sh ([#302](https://github.com/googleapis/google-auth-library-python/pull/302)) -- Enable static type checking with pytype ([#298](https://github.com/googleapis/google-auth-library-python/pull/298)) -- Make classifiers in setup.py an array. ([#280](https://github.com/googleapis/google-auth-library-python/pull/280)) +- Update trampoline.sh (`#302 `_) +- Enable static type checking with pytype (`#298 `_) +- Make classifiers in setup.py an array. (`#280 `_) v1.5.1 ------ -- Fix check for error text on Python 3.7. (#278) -- Use new Auth URIs. (#281) -- Add code-of-conduct document. (#270) -- Fix some typos in test_urllib3.py (#268) +- Fix check for error text on Python 3.7. (`#278 `_) +- Use new Auth URIs. (`#281 `_) +- Add code-of-conduct document. (`#270 `_) +- Fix some typos in test_urllib3.py (`#268 `_) v1.5.0 ------ -- Warn when using user credentials from the Cloud SDK (#266) -- Add compute engine-based IDTokenCredentials (#236) -- Corrected some typos (#265) +- Warn when using user credentials from the Cloud SDK (`#266 `_) +- Add compute engine-based IDTokenCredentials (`#236 `_) +- Corrected some typos (`#265 `_) v1.4.2 ------ -- Raise a helpful exception when trying to refresh credentials without a refresh token. (#262) -- Fix links to README and CONTRIBUTING in docs/index.rst. (#260) -- Fix a typo in credentials.py. (#256) -- Use pytest instead of py.test per upstream recommendation, #dropthedot. (#255) -- Fix typo on exemple of jwt usage (#245) +- Raise a helpful exception when trying to refresh credentials without a refresh token. (`#262 `_) +- Fix links to README and CONTRIBUTING in docs/index.rst. (`#260 `_) +- Fix a typo in credentials.py. (`#256 `_) +- Use pytest instead of py.test per upstream recommendation, #dropthedot. (`#255 `_) +- Fix typo on exemple of jwt usage (`#245 `_) v1.4.1 ------ -- Added a check for the cryptography version before attempting to use it. (#243) +- Added a check for the cryptography version before attempting to use it. (`#243 `_) v1.4.0 ------ -- Added `cryptography`-based RSA signer and verifier. (#185) -- Added `google.oauth2.service_account.IDTokenCredentials`. (#234) -- Improved documentation around ID Tokens (#224) +- Added `cryptography`-based RSA signer and verifier. (`#185 `_) +- Added `google.oauth2.service_account.IDTokenCredentials`. (`#234 `_) +- Improved documentation around ID Tokens (`#224 `_) v1.3.0 ------ -- Added ``google.oauth2.credentials.Credentials.from_authorized_user_file`` (#226) -- Dropped direct pyasn1 dependency in favor of letting ``pyasn1-modules`` specify the right version. (#230) -- ``default()`` now checks for the project ID environment var before warning about missing project ID. (#227) -- Fixed the docstrings for ``has_scopes()`` and ``with_scopes()``. (#228) -- Fixed example in docstring for ``ReadOnlyScoped``. (#219) -- Made ``transport.requests`` use timeouts and retries to improve reliability. (#220) +- Added ``google.oauth2.credentials.Credentials.from_authorized_user_file`` (`#226 `_) +- Dropped direct pyasn1 dependency in favor of letting ``pyasn1-modules`` specify the right version. (`#230 `_) +- ``default()`` now checks for the project ID environment var before warning about missing project ID. (`#227 `_) +- Fixed the docstrings for ``has_scopes()`` and ``with_scopes()``. (`#228 `_) +- Fixed example in docstring for ``ReadOnlyScoped``. (`#219 `_) +- Made ``transport.requests`` use timeouts and retries to improve reliability. (`#220 `_) v1.2.1 ------ -- Excluded compiled Python files in source distributions. (#215) -- Updated docs for creating RSASigner from string. (#213) -- Use ``six.raise_from`` wherever possible. (#212) -- Fixed a typo in a comment ``seconds`` not ``sections``. (#210) +- Excluded compiled Python files in source distributions. (`#215 `_) +- Updated docs for creating RSASigner from string. (`#213 `_) +- Use ``six.raise_from`` wherever possible. (`#212 `_) +- Fixed a typo in a comment ``seconds`` not ``sections``. (`#210 `_) v1.2.0 ------ -- Added ``google.auth.credentials.AnonymousCredentials``. (#206) -- Updated the documentation to link to the Google Cloud Platform Python setup guide (#204) +- Added ``google.auth.credentials.AnonymousCredentials``. (`#206 `_) +- Updated the documentation to link to the Google Cloud Platform Python setup guide (`#204 `_) v1.1.1 ------ -- ``google.oauth.credentials.Credentials`` now correctly inherits from ``ReadOnlyScoped`` instead of ``Scoped``. (#200) +- ``google.oauth.credentials.Credentials`` now correctly inherits from ``ReadOnlyScoped`` instead of ``Scoped``. (`#200 `_) v1.1.0 ------ -- Added ``service_account.Credentials.project_id``. (#187) -- Move read-only methods of ``credentials.Scoped`` into new interface ``credentials.ReadOnlyScoped``. (#195, #196) -- Make ``compute_engine.Credentials`` derive from ``ReadOnlyScoped`` instead of ``Scoped``. (#195) -- Fix App Engine's expiration calculation (#197) -- Split ``crypt`` module into a package to allow alternative implementations. (#189) -- Add error message to handle case of empty string or missing file for GOOGLE_APPLICATION_CREDENTIALS (#188) +- Added ``service_account.Credentials.project_id``. (`#187 `_) +- Move read-only methods of ``credentials.Scoped`` into new interface ``credentials.ReadOnlyScoped``. (`#195 `_, `#196 `_) +- Make ``compute_engine.Credentials`` derive from ``ReadOnlyScoped`` instead of ``Scoped``. (`#195 `_) +- Fix App Engine's expiration calculation (`#197 `_) +- Split ``crypt`` module into a package to allow alternative implementations. (`#189 `_) +- Add error message to handle case of empty string or missing file for GOOGLE_APPLICATION_CREDENTIALS (`#188 `_) v1.0.2 ------ -- Fixed a bug where the Cloud SDK executable could not be found on Windows, leading to project ID detection failing. (#179) -- Fixed a bug where the timeout argument wasn't being passed through the httplib transport correctly. (#175) -- Added documentation for using the library on Google App Engine standard. (#172) -- Testing style updates. (#168) -- Added documentation around the oauth2client deprecation. (#165) -- Fixed a few lint issues caught by newer versions of pylint. (#166) +- Fixed a bug where the Cloud SDK executable could not be found on Windows, leading to project ID detection failing. (`#179 `_) +- Fixed a bug where the timeout argument wasn't being passed through the httplib transport correctly. (`#175 `_) +- Added documentation for using the library on Google App Engine standard. (`#172 `_) +- Testing style updates. (`#168 `_) +- Added documentation around the oauth2client deprecation. (`#165 `_) +- Fixed a few lint issues caught by newer versions of pylint. (`#166 `_) v1.0.1 ------ -- Fixed a bug in the clock skew accommodation logic where expired credentials could be used for up to 5 minutes. (#158) +- Fixed a bug in the clock skew accommodation logic where expired credentials could be used for up to 5 minutes. (`#158 `_) v1.0.0 ------ @@ -135,87 +135,87 @@ No significant changes since v0.10.0 v0.10.0 ------- -- Added ``jwt.OnDemandCredentials``. (#142) -- Added new public property ``id_token`` to ``oauth2.credentials.Credentials``. (#150) -- Added the ability to set the address used to communicate with the Compute Engine metadata server via the ``GCE_METADATA_ROOT`` and ``GCE_METADATA_IP`` environment variables. (#148) -- Changed the way cloud project IDs are ascertained from the Google Cloud SDK. (#147) -- Modified expiration logic to add a 5 minute clock skew accommodation. (#145) +- Added ``jwt.OnDemandCredentials``. (`#142 `_) +- Added new public property ``id_token`` to ``oauth2.credentials.Credentials``. (`#150 `_) +- Added the ability to set the address used to communicate with the Compute Engine metadata server via the ``GCE_METADATA_ROOT`` and ``GCE_METADATA_IP`` environment variables. (`#148 `_) +- Changed the way cloud project IDs are ascertained from the Google Cloud SDK. (`#147 `_) +- Modified expiration logic to add a 5 minute clock skew accommodation. (`#145 `_) v0.9.0 ------ -- Added ``service_account.Credentials.with_claims``. (#140) -- Moved ``google.auth.oauthlib`` and ``google.auth.flow`` to a new separate package ``google_auth_oauthlib``. (#137, #139, #135, #126) -- Added ``InstalledAppFlow`` to ``google_auth_oauthlib``. (#128) -- Fixed some packaging and documentation issues. (#131) -- Added a helpful error message when importing optional dependencies. (#125) -- Made all properties required to reconstruct ``google.oauth2.credentials.Credentials`` public. (#124) -- Added official Python 3.6 support. (#102) -- Added ``jwt.Credentials.from_signing_credentials`` and removed ``service_account.Credentials.to_jwt_credentials``. (#120) +- Added ``service_account.Credentials.with_claims``. (`#140 `_) +- Moved ``google.auth.oauthlib`` and ``google.auth.flow`` to a new separate package ``google_auth_oauthlib``. (`#137 `_, `#139 `_, `#135 `_, `#126 `_) +- Added ``InstalledAppFlow`` to ``google_auth_oauthlib``. (`#128 `_) +- Fixed some packaging and documentation issues. (`#131 `_) +- Added a helpful error message when importing optional dependencies. (`#125 `_) +- Made all properties required to reconstruct ``google.oauth2.credentials.Credentials`` public. (`#124 `_) +- Added official Python 3.6 support. (`#102 `_) +- Added ``jwt.Credentials.from_signing_credentials`` and removed ``service_account.Credentials.to_jwt_credentials``. (`#120 `_) v0.8.0 ------ -- Removed one-time token behavior from ``jwt.Credentials``, audience claim is now required and fixed. (#117) -- ``crypt.Signer`` and ``crypt.Verifier`` are now abstract base classes. The concrete implementations have been renamed to ``crypt.RSASigner`` and ``crypt.RSAVerifier``. ``app_engine.Signer`` and ``iam.Signer`` now inherit from ``crypt.Signer``. (#115) -- ``transport.grpc`` now correctly calls ``Credentials.before_request``. (#116) +- Removed one-time token behavior from ``jwt.Credentials``, audience claim is now required and fixed. (`#117 `_) +- ``crypt.Signer`` and ``crypt.Verifier`` are now abstract base classes. The concrete implementations have been renamed to ``crypt.RSASigner`` and ``crypt.RSAVerifier``. ``app_engine.Signer`` and ``iam.Signer`` now inherit from ``crypt.Signer``. (`#115 `_) +- ``transport.grpc`` now correctly calls ``Credentials.before_request``. (`#116 `_) v0.7.0 ------ -- Added ``google.auth.iam.Signer``. (#108) -- Fixed issue where ``google.auth.app_engine.Signer`` erroneously returns a tuple from ``sign()``. (#109) -- Added public property ``google.auth.credentials.Signing.signer``. (#110) +- Added ``google.auth.iam.Signer``. (`#108 `_) +- Fixed issue where ``google.auth.app_engine.Signer`` erroneously returns a tuple from ``sign()``. (`#109 `_) +- Added public property ``google.auth.credentials.Signing.signer``. (`#110 `_) v0.6.0 ------ -- Added experimental integration with ``requests-oauthlib`` in ``google.oauth2.oauthlib`` and ``google.oauth2.flow``. (#100, #105, #106) -- Fixed typo in ``google_auth_httplib2``'s README. (#105) +- Added experimental integration with ``requests-oauthlib`` in ``google.oauth2.oauthlib`` and ``google.oauth2.flow``. (`#100 `_, `#105 `_, `#106 `_) +- Fixed typo in ``google_auth_httplib2``'s README. (`#105 `_) v0.5.0 ------ -- Added ``app_engine.Signer``. (#97) -- Added ``crypt.Signer.from_service_account_file``. (#95) -- Fixed error handling in the oauth2 client. (#96) +- Added ``app_engine.Signer``. (`#97 `_) +- Added ``crypt.Signer.from_service_account_file``. (`#95 `_) +- Fixed error handling in the oauth2 client. (`#96 `_) - Fixed the App Engine system tests. v0.4.0 ------ -- ``transports.grpc.secure_authorized_channel`` now passes ``kwargs`` to ``grpc.secure_channel``. (#90) -- Added new property ``credentials.Singing.signer_email`` which can be used to identify the signer of a message. (#89) +- ``transports.grpc.secure_authorized_channel`` now passes ``kwargs`` to ``grpc.secure_channel``. (`#90 `_) +- Added new property ``credentials.Singing.signer_email`` which can be used to identify the signer of a message. (`#89 `_) - (google_auth_httplib2) Added a proxy to ``httplib2.Http.connections``. v0.3.2 ------ -- Fixed an issue where an ``ImportError`` would occur if ``google.oauth2`` was imported before ``google.auth``. (#88) +- Fixed an issue where an ``ImportError`` would occur if ``google.oauth2`` was imported before ``google.auth``. (`#88 `_) v0.3.1 ------ -- Fixed a bug where non-padded base64 encoded strings were not accepted. (#87) -- Fixed a bug where ID token verification did not correctly call the HTTP request function. (#87) +- Fixed a bug where non-padded base64 encoded strings were not accepted. (`#87 `_) +- Fixed a bug where ID token verification did not correctly call the HTTP request function. (`#87 `_) v0.3.0 ------ -- Added Google ID token verification helpers. (#82) -- Swapped the ``target`` and ``request`` argument order for ``grpc.secure_authorized_channel``. (#81) -- Added a user's guide. (#79) -- Made ``service_account_email`` a public property on several credential classes. (#76) -- Added a ``scope`` argument to ``google.auth.default``. (#75) -- Added support for the ``GCLOUD_PROJECT`` environment variable. (#73) +- Added Google ID token verification helpers. (`#82 `_) +- Swapped the ``target`` and ``request`` argument order for ``grpc.secure_authorized_channel``. (`#81 `_) +- Added a user's guide. (`#79 `_) +- Made ``service_account_email`` a public property on several credential classes. (`#76 `_) +- Added a ``scope`` argument to ``google.auth.default``. (`#75 `_) +- Added support for the ``GCLOUD_PROJECT`` environment variable. (`#73 `_) v0.2.0 ------ -- Added gRPC support. (#67) -- Added Requests support. (#66) -- Added ``google.auth.credentials.with_scopes_if_required`` helper. (#65) -- Added private helper for oauth2client migration. (#70) +- Added gRPC support. (`#67 `_) +- Added Requests support. (`#66 `_) +- Added ``google.auth.credentials.with_scopes_if_required`` helper. (`#65 `_) +- Added private helper for oauth2client migration. (`#70 `_) v0.1.0 ------ @@ -223,15 +223,15 @@ v0.1.0 First release with core functionality available. This version is ready for initial usage and testing. -- Added ``google.auth.credentials``, public interfaces for Credential types. (#8) -- Added ``google.oauth2.credentials``, credentials that use OAuth 2.0 access and refresh tokens (#24) -- Added ``google.oauth2.service_account``, credentials that use Service Account private keys to obtain OAuth 2.0 access tokens. (#25) -- Added ``google.auth.compute_engine``, credentials that use the Compute Engine metadata service to obtain OAuth 2.0 access tokens. (#22) +- Added ``google.auth.credentials``, public interfaces for Credential types. (`#8 `_) +- Added ``google.oauth2.credentials``, credentials that use OAuth 2.0 access and refresh tokens (`#24 `_) +- Added ``google.oauth2.service_account``, credentials that use Service Account private keys to obtain OAuth 2.0 access tokens. (`#25 `_) +- Added ``google.auth.compute_engine``, credentials that use the Compute Engine metadata service to obtain OAuth 2.0 access tokens. (`#22 `_) - Added ``google.auth.jwt.Credentials``, credentials that use a JWT as a bearer token. -- Added ``google.auth.app_engine``, credentials that use the Google App Engine App Identity service to obtain OAuth 2.0 access tokens. (#46) -- Added ``google.auth.default()``, an implementation of Google Application Default Credentials that supports automatic Project ID detection. (#32) -- Added system tests for all credential types. (#51, #54, #56, #58, #59, #60, #61, #62) -- Added ``google.auth.transports.urllib3.AuthorizedHttp``, an HTTP client that includes authentication provided by credentials. (#19) +- Added ``google.auth.app_engine``, credentials that use the Google App Engine App Identity service to obtain OAuth 2.0 access tokens. (`#46 `_) +- Added ``google.auth.default()``, an implementation of Google Application Default Credentials that supports automatic Project ID detection. (`#32 `_) +- Added system tests for all credential types. (`#51 `_, `#54 `_, `#56 `_, `#58 `_, `#59 `_, `#60 `_, `#61 `_, `#62 `_) +- Added ``google.auth.transports.urllib3.AuthorizedHttp``, an HTTP client that includes authentication provided by credentials. (`#19 `_) - Documentation style and formatting updates. v0.0.1 From c54d7299d00e3a0d08dcaba6708c6f8f579cb980 Mon Sep 17 00:00:00 2001 From: Christopher Wilcox Date: Fri, 14 Dec 2018 12:24:38 -0800 Subject: [PATCH 203/966] Announce deprecation of Python 2.7 (#311) --- packages/google-auth/README.rst | 8 ++++++++ packages/google-auth/setup.py | 2 ++ 2 files changed, 10 insertions(+) diff --git a/packages/google-auth/README.rst b/packages/google-auth/README.rst index 512e550ed0e4..00dbfef62f0d 100644 --- a/packages/google-auth/README.rst +++ b/packages/google-auth/README.rst @@ -26,6 +26,14 @@ For more information on setting up your Python development environment, please r .. _`Python Development Environment Setup Guide`: https://cloud.google.com/python/setup +Supported Python Versions +^^^^^^^^^^^^^^^^^^^^^^^^^ +Python >= 3.4 + +Deprecated Python Versions +^^^^^^^^^^^^^^^^^^^^^^^^^^ +Python == 2.7. Python 2.7 support will be removed on January 1, 2020. + Documentation ------------- diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 2d29b95c54d9..23c9c7eaaa83 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -40,6 +40,7 @@ packages=find_packages(exclude=('tests*', 'system_tests*')), namespace_packages=('google',), install_requires=DEPENDENCIES, + python_requires='>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*', license='Apache 2.0', keywords='google auth oauth client', classifiers=[ @@ -49,6 +50,7 @@ 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: Apache Software License', From 752ca1735685f44813af122de3716fc11a829a13 Mon Sep 17 00:00:00 2001 From: Christopher Wilcox Date: Mon, 17 Dec 2018 13:30:30 -0800 Subject: [PATCH 204/966] Release 1.6.1 (#314) --- packages/google-auth/CHANGELOG.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/google-auth/CHANGELOG.rst b/packages/google-auth/CHANGELOG.rst index 508f14252529..8212170b3ef1 100644 --- a/packages/google-auth/CHANGELOG.rst +++ b/packages/google-auth/CHANGELOG.rst @@ -1,6 +1,17 @@ Changelog ========= +v1.6.2 +------ + +12-17-2018 10:51 PST + +Documentation ++++++++++++++ + +- Announce deprecation of Python 2.7 (`#311 `_) +- Link all the PRs in CHANGELOG (`#307 `_) + v1.6.1 ------ From 7e892d376b75f84102ce77a99030300c07aa66fd Mon Sep 17 00:00:00 2001 From: Christopher Wilcox Date: Mon, 17 Dec 2018 13:37:43 -0800 Subject: [PATCH 205/966] Update setup.py --- packages/google-auth/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 23c9c7eaaa83..ef9bb51856e5 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -31,7 +31,7 @@ setup( name='google-auth', - version='1.6.1', + version='1.6.2', author='Google Cloud Platform', author_email='jonwayne+google-auth@google.com', description='Google Authentication Library', From 27b66561a33e1ec6addf9149889bd4b1325bb297 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Thu, 20 Dec 2018 20:38:52 -0500 Subject: [PATCH 206/966] _default.default() now handles ImportErrors for optional dependencies. (#313) * _default.default() now handles ImportErrors for optional dependencies. --- packages/google-auth/google/auth/_default.py | 17 +++++++-- packages/google-auth/tests/test__default.py | 36 ++++++++++++++++++++ 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index c93b48963263..27de58de7397 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -172,7 +172,12 @@ def _get_explicit_environ_credentials(): def _get_gae_credentials(): """Gets Google App Engine App Identity credentials and project ID.""" - from google.auth import app_engine + # While this library is normally bundled with app_engine, there are + # some cases where it's not available, so we tolerate ImportError. + try: + import google.auth.app_engine as app_engine + except ImportError: + return None, None try: credentials = app_engine.Credentials() @@ -188,8 +193,14 @@ def _get_gce_credentials(request=None): # to require no arguments. So, we'll use the _http_client transport which # uses http.client. This is only acceptable because the metadata server # doesn't do SSL and never requires proxies. - from google.auth import compute_engine - from google.auth.compute_engine import _metadata + + # While this library is normally bundled with compute_engine, there are + # some cases where it's not available, so we tolerate ImportError. + try: + from google.auth import compute_engine + from google.auth.compute_engine import _metadata + except ImportError: + return None, None if request is None: request = google.auth.transport._http_client.Request() diff --git a/packages/google-auth/tests/test__default.py b/packages/google-auth/tests/test__default.py index d7d537c82b3e..3fb0fa1b18d3 100644 --- a/packages/google-auth/tests/test__default.py +++ b/packages/google-auth/tests/test__default.py @@ -235,6 +235,15 @@ def test__get_gae_credentials(app_identity): assert project_id == mock.sentinel.project +def test__get_gae_credentials_no_app_engine(): + import sys + with mock.patch.dict('sys.modules'): + sys.modules['google.auth.app_engine'] = None + credentials, project_id = _default._get_gae_credentials() + assert credentials is None + assert project_id is None + + def test__get_gae_credentials_no_apis(): assert _default._get_gae_credentials() == (None, None) @@ -275,6 +284,15 @@ def test__get_gce_credentials_no_project_id(unused_get, unused_ping): assert project_id is None +def test__get_gce_credentials_no_compute_engine(): + import sys + with mock.patch.dict('sys.modules'): + sys.modules['google.auth.compute_engine'] = None + credentials, project_id = _default._get_gce_credentials() + assert credentials is None + assert project_id is None + + @mock.patch( 'google.auth.compute_engine._metadata.ping', return_value=False, autospec=True) @@ -366,3 +384,21 @@ def test_default_scoped(with_scopes, unused_get): assert project_id == mock.sentinel.project_id with_scopes.assert_called_once_with( mock.sentinel.credentials, scopes) + + +@mock.patch( + 'google.auth._default._get_explicit_environ_credentials', + return_value=(mock.sentinel.credentials, mock.sentinel.project_id), + autospec=True) +def test_default_no_app_engine_compute_engine_module(unused_get): + """ + google.auth.compute_engine and google.auth.app_engine are both optional + to allow not including them when using this package. This verifies + that default fails gracefully if these modules are absent + """ + import sys + with mock.patch.dict('sys.modules'): + sys.modules['google.auth.compute_engine'] = None + sys.modules['google.auth.app_engine'] = None + assert _default.default() == ( + mock.sentinel.credentials, mock.sentinel.project_id) From 8c8e3a414f6280e13a0969a28ca308afda49d935 Mon Sep 17 00:00:00 2001 From: Brian Bao Date: Mon, 7 Jan 2019 12:26:06 -0500 Subject: [PATCH 207/966] Allow user to pass custom Session object to AuthorizedSession to make requests. (#306) --- .../google/auth/transport/requests.py | 33 +++++++++++-------- .../tests/transport/test_requests.py | 9 +++++ 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/packages/google-auth/google/auth/transport/requests.py b/packages/google-auth/google/auth/transport/requests.py index 2268243a6057..8250c74c6f02 100644 --- a/packages/google-auth/google/auth/transport/requests.py +++ b/packages/google-auth/google/auth/transport/requests.py @@ -150,33 +150,40 @@ class AuthorizedSession(requests.Session): refresh the credentials and retry the request. refresh_timeout (Optional[int]): The timeout value in seconds for credential refresh HTTP requests. - kwargs: Additional arguments passed to the :class:`requests.Session` - constructor. + auth_request (google.auth.transport.requests.Request): + (Optional) An instance of + :class:`~google.auth.transport.requests.Request` used when + refreshing credentials. If not passed, + an instance of :class:`~google.auth.transport.requests.Request` + is created. """ def __init__(self, credentials, refresh_status_codes=transport.DEFAULT_REFRESH_STATUS_CODES, max_refresh_attempts=transport.DEFAULT_MAX_REFRESH_ATTEMPTS, refresh_timeout=None, - **kwargs): - super(AuthorizedSession, self).__init__(**kwargs) + auth_request=None): + super(AuthorizedSession, self).__init__() self.credentials = credentials self._refresh_status_codes = refresh_status_codes self._max_refresh_attempts = max_refresh_attempts self._refresh_timeout = refresh_timeout - auth_request_session = requests.Session() + if auth_request is None: + auth_request_session = requests.Session() - # Using an adapter to make HTTP requests robust to network errors. - # This adapter retrys HTTP requests when network errors occur - # and the requests seems safely retryable. - retry_adapter = requests.adapters.HTTPAdapter(max_retries=3) - auth_request_session.mount("https://", retry_adapter) + # Using an adapter to make HTTP requests robust to network errors. + # This adapter retrys HTTP requests when network errors occur + # and the requests seems safely retryable. + retry_adapter = requests.adapters.HTTPAdapter(max_retries=3) + auth_request_session.mount("https://", retry_adapter) + + # Do not pass `self` as the session here, as it can lead to + # infinite recursion. + auth_request = Request(auth_request_session) # Request instance used by internal methods (for example, # credentials.refresh). - # Do not pass `self` as the session here, as it can lead to infinite - # recursion. - self._auth_request = Request(auth_request_session) + self._auth_request = auth_request def request(self, method, url, data=None, headers=None, **kwargs): """Implementation of Requests' request.""" diff --git a/packages/google-auth/tests/transport/test_requests.py b/packages/google-auth/tests/transport/test_requests.py index 41dc237eced3..311992ae97eb 100644 --- a/packages/google-auth/tests/transport/test_requests.py +++ b/packages/google-auth/tests/transport/test_requests.py @@ -85,6 +85,15 @@ def test_constructor(self): assert authed_session.credentials == mock.sentinel.credentials + def test_constructor_with_auth_request(self): + http = mock.create_autospec(requests.Session) + auth_request = google.auth.transport.requests.Request(http) + + authed_session = google.auth.transport.requests.AuthorizedSession( + mock.sentinel.credentials, auth_request=auth_request) + + assert authed_session._auth_request == auth_request + def test_request_no_refresh(self): credentials = mock.Mock(wraps=CredentialsStub()) response = make_response() From 567ec8b91b45300a8beb05255bf7cc67fdddfc6a Mon Sep 17 00:00:00 2001 From: Aditya Natraj Date: Fri, 15 Feb 2019 00:02:10 -0500 Subject: [PATCH 208/966] Follow rfc 7515 and strip padding from JWS segments (#324) * strip off illegal padding * oops: remove unused import base64 --- packages/google-auth/google/auth/_helpers.py | 17 +++++++++++++++++ packages/google-auth/google/auth/jwt.py | 13 +++++++++---- packages/google-auth/tests/test__helpers.py | 12 ++++++++++++ 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/packages/google-auth/google/auth/_helpers.py b/packages/google-auth/google/auth/_helpers.py index 860b82719a2a..b32801a32039 100644 --- a/packages/google-auth/google/auth/_helpers.py +++ b/packages/google-auth/google/auth/_helpers.py @@ -215,3 +215,20 @@ def padded_urlsafe_b64decode(value): b64string = to_bytes(value) padded = b64string + b'=' * (-len(b64string) % 4) return base64.urlsafe_b64decode(padded) + + +def unpadded_urlsafe_b64encode(value): + """Encodes base64 strings removing any padding characters. + + `rfc 7515`_ defines Base64url to NOT include any padding + characters, but the stdlib doesn't do that by default. + + _rfc7515: https://tools.ietf.org/html/rfc7515#page-6 + + Args: + value (Union[str|bytes]): The bytes-like value to encode + + Returns: + Union[str|bytes]: The encoded value + """ + return base64.urlsafe_b64encode(value).rstrip(b'=') diff --git a/packages/google-auth/google/auth/jwt.py b/packages/google-auth/google/auth/jwt.py index 3805f3716287..bea70adf39e5 100644 --- a/packages/google-auth/google/auth/jwt.py +++ b/packages/google-auth/google/auth/jwt.py @@ -40,7 +40,6 @@ """ -import base64 import collections import copy import datetime @@ -86,13 +85,19 @@ def encode(signer, payload, header=None, key_id=None): header['kid'] = key_id segments = [ - base64.urlsafe_b64encode(json.dumps(header).encode('utf-8')), - base64.urlsafe_b64encode(json.dumps(payload).encode('utf-8')), + _helpers.unpadded_urlsafe_b64encode( + json.dumps(header).encode('utf-8') + ), + _helpers.unpadded_urlsafe_b64encode( + json.dumps(payload).encode('utf-8') + ), ] signing_input = b'.'.join(segments) signature = signer.sign(signing_input) - segments.append(base64.urlsafe_b64encode(signature)) + segments.append( + _helpers.unpadded_urlsafe_b64encode(signature) + ) return b'.'.join(segments) diff --git a/packages/google-auth/tests/test__helpers.py b/packages/google-auth/tests/test__helpers.py index b067faabf3ce..79bdef3d7bfd 100644 --- a/packages/google-auth/tests/test__helpers.py +++ b/packages/google-auth/tests/test__helpers.py @@ -167,3 +167,15 @@ def test_padded_urlsafe_b64decode(): for case, expected in cases: assert _helpers.padded_urlsafe_b64decode(case) == expected + + +def test_unpadded_urlsafe_b64encode(): + cases = [ + (b'', b''), + (b'a', b'YQ'), + (b'aa', b'YWE'), + (b'aaa', b'YWFh'), + ] + + for case, expected in cases: + assert _helpers.unpadded_urlsafe_b64encode(case) == expected From ade0cb6653f5b7c74c5170d9cf64a6e1db1769c1 Mon Sep 17 00:00:00 2001 From: Robert Barron Date: Thu, 14 Feb 2019 21:46:27 -0800 Subject: [PATCH 209/966] Add retry to _metadata.ping() (#323) --- .../google/auth/compute_engine/_metadata.py | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/packages/google-auth/google/auth/compute_engine/_metadata.py b/packages/google-auth/google/auth/compute_engine/_metadata.py index c47be3fae2b7..d8004bb249e5 100644 --- a/packages/google-auth/google/auth/compute_engine/_metadata.py +++ b/packages/google-auth/google/auth/compute_engine/_metadata.py @@ -51,13 +51,15 @@ _METADATA_DEFAULT_TIMEOUT = 3 -def ping(request, timeout=_METADATA_DEFAULT_TIMEOUT): +def ping(request, timeout=_METADATA_DEFAULT_TIMEOUT, retry_count=3): """Checks to see if the metadata server is available. Args: request (google.auth.transport.Request): A callable used to make HTTP requests. timeout (int): How long to wait for the metadata server to respond. + retry_count (int): How many times to attempt connecting to metadata + server using above timeout. Returns: bool: True if the metadata server is reachable, False otherwise. @@ -68,18 +70,23 @@ def ping(request, timeout=_METADATA_DEFAULT_TIMEOUT): # could lead to false negatives in the event that we are on GCE, but # the metadata resolution was particularly slow. The latter case is # "unlikely". - try: - response = request( - url=_METADATA_IP_ROOT, method='GET', headers=_METADATA_HEADERS, - timeout=timeout) - - metadata_flavor = response.headers.get(_METADATA_FLAVOR_HEADER) - return (response.status == http_client.OK and - metadata_flavor == _METADATA_FLAVOR_VALUE) - - except exceptions.TransportError: - _LOGGER.info('Compute Engine Metadata server unavailable.') - return False + retries = 0 + while retries < retry_count: + try: + response = request( + url=_METADATA_IP_ROOT, method='GET', headers=_METADATA_HEADERS, + timeout=timeout) + + metadata_flavor = response.headers.get(_METADATA_FLAVOR_HEADER) + return (response.status == http_client.OK and + metadata_flavor == _METADATA_FLAVOR_VALUE) + + except exceptions.TransportError: + _LOGGER.info('Compute Engine Metadata server unavailable on' + 'attempt %s of %s', retries+1, retry_count) + retries += 1 + + return False def get(request, path, root=_METADATA_ROOT, recursive=False): From 1ace9a174dbd8186cbc21200e94f7d632be8e2d6 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Tue, 19 Feb 2019 13:08:13 -0800 Subject: [PATCH 210/966] Release 1.6.3 (#325) --- packages/google-auth/CHANGELOG.rst | 11 +++++++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.rst b/packages/google-auth/CHANGELOG.rst index 8212170b3ef1..9a33bfcd09c0 100644 --- a/packages/google-auth/CHANGELOG.rst +++ b/packages/google-auth/CHANGELOG.rst @@ -1,6 +1,17 @@ Changelog ========= +v1.6.3 +------ + +02-15-2019 9:31 PST + +Implementation Changes ++++++++++++++ + +- follow rfc 7515 : strip padding from JWS segments #324 (`#324 `_) +- Add retry to _metadata.ping() (`#323 `_) + v1.6.2 ------ diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index ef9bb51856e5..b2eb5984df73 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -31,7 +31,7 @@ setup( name='google-auth', - version='1.6.2', + version='1.6.3', author='Google Cloud Platform', author_email='jonwayne+google-auth@google.com', description='Google Authentication Library', From 06e546958869811310998e0ea3e2e4399677e017 Mon Sep 17 00:00:00 2001 From: Jeff Widman Date: Wed, 27 Feb 2019 10:03:22 -0800 Subject: [PATCH 211/966] Require a minimum version of setuptools (#322) I was using `pex` on an application that lists `google-auth` as a dependency and got the following warning: ..../pex/environment.py:330 UserWarning: The `pkg_resources` package was loaded from a pex vendored version when declaring namespace packages defined by google-auth 1.6.2. The google-auth 1.6.2 distribution should fix its `install_requires` to include `setuptools` So adding `setuptools` as a listed dependency to fix this. Version `40.3.0` was chosen because it fixed a bug in the handling of `pkg_resource`-style namespaces (https://github.com/pypa/setuptools/issues/1321). For more details on why this version was picked, see the discussion in https://github.com/googleapis/google-auth-library-python/pull/322. Also making the listing alphabetical. --- packages/google-auth/setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index b2eb5984df73..73d0f71cec85 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -19,10 +19,11 @@ DEPENDENCIES = ( + 'cachetools>=2.0.0', 'pyasn1-modules>=0.2.1', 'rsa>=3.1.4', + 'setuptools>=40.3.0', 'six>=1.9.0', - 'cachetools>=2.0.0', ) From fdc2680d0c90af569982c062364c288695ea4efd Mon Sep 17 00:00:00 2001 From: Tianzi Cai Date: Fri, 29 Mar 2019 11:12:37 -0700 Subject: [PATCH 212/966] doc: clarify which SA has Token Creator role (#330) --- packages/google-auth/docs/user-guide.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/google-auth/docs/user-guide.rst b/packages/google-auth/docs/user-guide.rst index 75879179522c..62836623fd25 100644 --- a/packages/google-auth/docs/user-guide.rst +++ b/packages/google-auth/docs/user-guide.rst @@ -209,8 +209,8 @@ Impersonated credentials ++++++++++++++++++++++++ Impersonated Credentials allows one set of credentials issued to a user or service account -to impersonate another. The target service account must grant the source credential -the "Service Account Token Creator" IAM role:: +to impersonate another. The source credentials must be granted +the "Service Account Token Creator" IAM role. :: from google.auth import impersonated_credentials @@ -232,7 +232,7 @@ the "Service Account Token Creator" IAM role:: In the example above `source_credentials` does not have direct access to list buckets in the target project. Using `ImpersonatedCredentials` will allow the source_credentials -to assume the identity of a target_principal that does have access +to assume the identity of a target_principal that does have access. Making authenticated requests ----------------------------- From c6a34a3c6bbbb00b14d4e7261aa0648ed89d33e7 Mon Sep 17 00:00:00 2001 From: Tianzi Cai Date: Fri, 29 Mar 2019 13:49:06 -0700 Subject: [PATCH 213/966] Fix typo in jwt docs. (#332) --- packages/google-auth/google/auth/jwt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/jwt.py b/packages/google-auth/google/auth/jwt.py index bea70adf39e5..d63c50bb9f39 100644 --- a/packages/google-auth/google/auth/jwt.py +++ b/packages/google-auth/google/auth/jwt.py @@ -200,7 +200,7 @@ def decode(token, certs=None, verify=True, audience=None): Args: token (str): The encoded JWT. certs (Union[str, bytes, Mapping[str, Union[str, bytes]]]): The - certificate used to validate the JWT signatyre. If bytes or string, + certificate used to validate the JWT signature. If bytes or string, it must the the public key certificate in PEM format. If a mapping, it must be a mapping of key IDs to public key certificates in PEM format. The mapping must contain the same key ID that's specified From 7a51682c60866320190dc0aa5ccdfa1bf760f58e Mon Sep 17 00:00:00 2001 From: "Eugene W. Foley" Date: Wed, 22 May 2019 13:50:38 -0400 Subject: [PATCH 214/966] Add downscoping to ouath2 credentials (#309) --- .../docs/reference/google.auth.crypt.rst | 13 +- .../docs/reference/google.auth.rst | 2 +- packages/google-auth/google/oauth2/_client.py | 9 +- .../google-auth/google/oauth2/credentials.py | 24 ++- .../google-auth/tests/oauth2/test__client.py | 34 +++++ .../tests/oauth2/test_credentials.py | 139 +++++++++++++++++- 6 files changed, 211 insertions(+), 10 deletions(-) diff --git a/packages/google-auth/docs/reference/google.auth.crypt.rst b/packages/google-auth/docs/reference/google.auth.crypt.rst index 26b8b4ac4fcd..a3e2b1206961 100644 --- a/packages/google-auth/docs/reference/google.auth.crypt.rst +++ b/packages/google-auth/docs/reference/google.auth.crypt.rst @@ -1,7 +1,16 @@ -google.auth.crypt module -======================== +google.auth.crypt package +========================= .. automodule:: google.auth.crypt :members: :inherited-members: :show-inheritance: + +Submodules +---------- + +.. toctree:: + + google.auth.crypt.base + google.auth.crypt.rsa + diff --git a/packages/google-auth/docs/reference/google.auth.rst b/packages/google-auth/docs/reference/google.auth.rst index bc6740b09bcd..53ab699c696c 100644 --- a/packages/google-auth/docs/reference/google.auth.rst +++ b/packages/google-auth/docs/reference/google.auth.rst @@ -12,6 +12,7 @@ Subpackages .. toctree:: google.auth.compute_engine + google.auth.crypt google.auth.transport Submodules @@ -21,7 +22,6 @@ Submodules google.auth.app_engine google.auth.credentials - google.auth.crypt google.auth.environment_vars google.auth.exceptions google.auth.iam diff --git a/packages/google-auth/google/oauth2/_client.py b/packages/google-auth/google/oauth2/_client.py index dc35be271f48..5121a3274651 100644 --- a/packages/google-auth/google/oauth2/_client.py +++ b/packages/google-auth/google/oauth2/_client.py @@ -201,7 +201,8 @@ def id_token_jwt_grant(request, token_uri, assertion): return id_token, expiry, response_data -def refresh_grant(request, token_uri, refresh_token, client_id, client_secret): +def refresh_grant(request, token_uri, refresh_token, client_id, client_secret, + scopes=None): """Implements the OAuth 2.0 refresh token grant. For more details, see `rfc678 section 6`_. @@ -215,6 +216,10 @@ def refresh_grant(request, token_uri, refresh_token, client_id, client_secret): token. client_id (str): The OAuth 2.0 application's client ID. client_secret (str): The Oauth 2.0 appliaction's client secret. + scopes (Optional(Sequence[str])): Scopes to request. If present, all + scopes must be authorized for the refresh token. Useful if refresh + token has a wild card scope (e.g. + 'https://www.googleapis.com/auth/any-api'). Returns: Tuple[str, Optional[str], Optional[datetime], Mapping[str, str]]: The @@ -233,6 +238,8 @@ def refresh_grant(request, token_uri, refresh_token, client_id, client_secret): 'client_secret': client_secret, 'refresh_token': refresh_token, } + if scopes: + body['scope'] = ' '.join(scopes) response_data = _token_endpoint_request(request, token_uri, body) diff --git a/packages/google-auth/google/oauth2/credentials.py b/packages/google-auth/google/oauth2/credentials.py index 4cb909cb6fa9..b56e31426348 100644 --- a/packages/google-auth/google/oauth2/credentials.py +++ b/packages/google-auth/google/oauth2/credentials.py @@ -67,10 +67,13 @@ def __init__(self, token, refresh_token=None, id_token=None, client_secret(str): The OAuth 2.0 client secret. Must be specified for refresh, can be left as None if the token can not be refreshed. - scopes (Sequence[str]): The scopes that were originally used - to obtain authorization. This is a purely informative parameter - that can be used by :meth:`has_scopes`. OAuth 2.0 credentials - can not request additional scopes after authorization. + scopes (Sequence[str]): The scopes used to obtain authorization. + This parameter is used by :meth:`has_scopes`. OAuth 2.0 + credentials can not request additional scopes after + authorization. The scopes must be derivable from the refresh + token if refresh information is provided (e.g. The refresh + token scopes are a superset of this or contain a wild card + scope like 'https://www.googleapis.com/auth/any-api'). """ super(Credentials, self).__init__() self.token = token @@ -133,13 +136,24 @@ def refresh(self, request): access_token, refresh_token, expiry, grant_response = ( _client.refresh_grant( request, self._token_uri, self._refresh_token, self._client_id, - self._client_secret)) + self._client_secret, self._scopes)) self.token = access_token self.expiry = expiry self._refresh_token = refresh_token self._id_token = grant_response.get('id_token') + if self._scopes and 'scopes' in grant_response: + requested_scopes = frozenset(self._scopes) + granted_scopes = frozenset(grant_response['scopes'].split()) + scopes_requested_but_not_granted = ( + requested_scopes - granted_scopes) + if scopes_requested_but_not_granted: + raise exceptions.RefreshError( + 'Not all requested scopes were granted by the ' + 'authorization server, missing scopes {}.'.format( + ', '.join(scopes_requested_but_not_granted))) + @classmethod def from_authorized_user_info(cls, info, scopes=None): """Creates a Credentials instance from parsed authorized user info. diff --git a/packages/google-auth/tests/oauth2/test__client.py b/packages/google-auth/tests/oauth2/test__client.py index 3ec7fc62a822..5a4a567456c1 100644 --- a/packages/google-auth/tests/oauth2/test__client.py +++ b/packages/google-auth/tests/oauth2/test__client.py @@ -37,6 +37,11 @@ SIGNER = crypt.RSASigner.from_string(PRIVATE_KEY_BYTES, '1') +SCOPES_AS_LIST = ['https://www.googleapis.com/auth/pubsub', + 'https://www.googleapis.com/auth/logging.write'] +SCOPES_AS_STRING = ('https://www.googleapis.com/auth/pubsub' + ' https://www.googleapis.com/auth/logging.write') + def test__handle_error_response(): response_data = json.dumps({ @@ -204,6 +209,35 @@ def test_refresh_grant(unused_utcnow): assert extra_data['extra'] == 'data' +@mock.patch('google.auth._helpers.utcnow', return_value=datetime.datetime.min) +def test_refresh_grant_with_scopes(unused_utcnow): + request = make_request({ + 'access_token': 'token', + 'refresh_token': 'new_refresh_token', + 'expires_in': 500, + 'extra': 'data', + 'scope': SCOPES_AS_STRING}) + + token, refresh_token, expiry, extra_data = _client.refresh_grant( + request, 'http://example.com', 'refresh_token', 'client_id', + 'client_secret', SCOPES_AS_LIST) + + # Check request call. + verify_request_params(request, { + 'grant_type': _client._REFRESH_GRANT_TYPE, + 'refresh_token': 'refresh_token', + 'client_id': 'client_id', + 'client_secret': 'client_secret', + 'scope': SCOPES_AS_STRING + }) + + # Check result. + assert token == 'token' + assert refresh_token == 'new_refresh_token' + assert expiry == datetime.datetime.min + datetime.timedelta(seconds=500) + assert extra_data['extra'] == 'data' + + def test_refresh_grant_no_access_token(): request = make_request({ # No access token. diff --git a/packages/google-auth/tests/oauth2/test_credentials.py b/packages/google-auth/tests/oauth2/test_credentials.py index 922c3bbf7205..32315096a8a3 100644 --- a/packages/google-auth/tests/oauth2/test_credentials.py +++ b/packages/google-auth/tests/oauth2/test_credentials.py @@ -86,7 +86,7 @@ def test_refresh_success(self, unused_utcnow, refresh_grant): # Check jwt grant call. refresh_grant.assert_called_with( request, self.TOKEN_URI, self.REFRESH_TOKEN, self.CLIENT_ID, - self.CLIENT_SECRET) + self.CLIENT_SECRET, None) # Check that the credentials have the token and expiry assert credentials.token == token @@ -107,6 +107,143 @@ def test_refresh_no_refresh_token(self): request.assert_not_called() + @mock.patch('google.oauth2._client.refresh_grant', autospec=True) + @mock.patch( + 'google.auth._helpers.utcnow', + return_value=datetime.datetime.min + _helpers.CLOCK_SKEW) + def test_credentials_with_scopes_requested_refresh_success( + self, unused_utcnow, refresh_grant): + scopes = ['email', 'profile'] + token = 'token' + expiry = _helpers.utcnow() + datetime.timedelta(seconds=500) + grant_response = {'id_token': mock.sentinel.id_token} + refresh_grant.return_value = ( + # Access token + token, + # New refresh token + None, + # Expiry, + expiry, + # Extra data + grant_response) + + request = mock.create_autospec(transport.Request) + creds = credentials.Credentials( + token=None, refresh_token=self.REFRESH_TOKEN, + token_uri=self.TOKEN_URI, client_id=self.CLIENT_ID, + client_secret=self.CLIENT_SECRET, scopes=scopes) + + # Refresh credentials + creds.refresh(request) + + # Check jwt grant call. + refresh_grant.assert_called_with( + request, self.TOKEN_URI, self.REFRESH_TOKEN, self.CLIENT_ID, + self.CLIENT_SECRET, scopes) + + # Check that the credentials have the token and expiry + assert creds.token == token + assert creds.expiry == expiry + assert creds.id_token == mock.sentinel.id_token + assert creds.has_scopes(scopes) + + # Check that the credentials are valid (have a token and are not + # expired.) + assert creds.valid + + @mock.patch('google.oauth2._client.refresh_grant', autospec=True) + @mock.patch( + 'google.auth._helpers.utcnow', + return_value=datetime.datetime.min + _helpers.CLOCK_SKEW) + def test_credentials_with_scopes_returned_refresh_success( + self, unused_utcnow, refresh_grant): + scopes = ['email', 'profile'] + token = 'token' + expiry = _helpers.utcnow() + datetime.timedelta(seconds=500) + grant_response = {'id_token': mock.sentinel.id_token, + 'scopes': ' '.join(scopes)} + refresh_grant.return_value = ( + # Access token + token, + # New refresh token + None, + # Expiry, + expiry, + # Extra data + grant_response) + + request = mock.create_autospec(transport.Request) + creds = credentials.Credentials( + token=None, refresh_token=self.REFRESH_TOKEN, + token_uri=self.TOKEN_URI, client_id=self.CLIENT_ID, + client_secret=self.CLIENT_SECRET, scopes=scopes) + + # Refresh credentials + creds.refresh(request) + + # Check jwt grant call. + refresh_grant.assert_called_with( + request, self.TOKEN_URI, self.REFRESH_TOKEN, self.CLIENT_ID, + self.CLIENT_SECRET, scopes) + + # Check that the credentials have the token and expiry + assert creds.token == token + assert creds.expiry == expiry + assert creds.id_token == mock.sentinel.id_token + assert creds.has_scopes(scopes) + + # Check that the credentials are valid (have a token and are not + # expired.) + assert creds.valid + + @mock.patch('google.oauth2._client.refresh_grant', autospec=True) + @mock.patch( + 'google.auth._helpers.utcnow', + return_value=datetime.datetime.min + _helpers.CLOCK_SKEW) + def test_credentials_with_scopes_refresh_failure_raises_refresh_error( + self, unused_utcnow, refresh_grant): + scopes = ['email', 'profile'] + scopes_returned = ['email'] + token = 'token' + expiry = _helpers.utcnow() + datetime.timedelta(seconds=500) + grant_response = {'id_token': mock.sentinel.id_token, + 'scopes': ' '.join(scopes_returned)} + refresh_grant.return_value = ( + # Access token + token, + # New refresh token + None, + # Expiry, + expiry, + # Extra data + grant_response) + + request = mock.create_autospec(transport.Request) + creds = credentials.Credentials( + token=None, refresh_token=self.REFRESH_TOKEN, + token_uri=self.TOKEN_URI, client_id=self.CLIENT_ID, + client_secret=self.CLIENT_SECRET, scopes=scopes) + + # Refresh credentials + with pytest.raises(exceptions.RefreshError, + match='Not all requested scopes were granted'): + creds.refresh(request) + + # Check jwt grant call. + refresh_grant.assert_called_with( + request, self.TOKEN_URI, self.REFRESH_TOKEN, self.CLIENT_ID, + self.CLIENT_SECRET, scopes) + + # Check that the credentials have the token and expiry + assert creds.token == token + assert creds.expiry == expiry + assert creds.id_token == mock.sentinel.id_token + assert creds.has_scopes(scopes) + + # Check that the credentials are valid (have a token and are not + # expired.) + assert creds.valid + def test_from_authorized_user_info(self): info = AUTH_USER_INFO.copy() From 2ec4ee269811322baa7c63bd37f448004775f63c Mon Sep 17 00:00:00 2001 From: Jordan Pittier Date: Tue, 16 Jul 2019 16:30:12 -0700 Subject: [PATCH 215/966] setup.py: set an upper bound on dependencies version (#352) This should make this library more robust in case of breaking changes in one of its dependencies. --- packages/google-auth/setup.py | 4 ++-- packages/google-auth/tox.ini | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 73d0f71cec85..4e87a7283549 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -19,9 +19,9 @@ DEPENDENCIES = ( - 'cachetools>=2.0.0', + 'cachetools>=2.0.0,<3.0', 'pyasn1-modules>=0.2.1', - 'rsa>=3.1.4', + 'rsa>=3.1.4,<4.0', 'setuptools>=40.3.0', 'six>=1.9.0', ) diff --git a/packages/google-auth/tox.ini b/packages/google-auth/tox.ini index 59fd6aba1706..9e94666d6101 100644 --- a/packages/google-auth/tox.ini +++ b/packages/google-auth/tox.ini @@ -7,7 +7,7 @@ deps = flask mock oauth2client - pytest + pytest<5.0 pytest-cov pytest-localserver requests From 77d0a0e89d6b8d5d13b40675cc9c7e9d97312a30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Chv=C3=A1tal?= Date: Fri, 26 Jul 2019 01:20:41 +0200 Subject: [PATCH 216/966] Use new pytest api to keep building with pytest 5 (#353) --- packages/google-auth/tests/test__default.py | 2 +- packages/google-auth/tox.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/tests/test__default.py b/packages/google-auth/tests/test__default.py index 3fb0fa1b18d3..d1434796f9a0 100644 --- a/packages/google-auth/tests/test__default.py +++ b/packages/google-auth/tests/test__default.py @@ -92,7 +92,7 @@ def test__load_credentials_from_file_authorized_user_bad_format(tmpdir): def test__load_credentials_from_file_authorized_user_cloud_sdk(): - with pytest.warns(UserWarning, matches='Cloud SDK'): + with pytest.warns(UserWarning, match='Cloud SDK'): credentials, project_id = _default._load_credentials_from_file( AUTHORIZED_USER_CLOUD_SDK_FILE) assert isinstance(credentials, google.oauth2.credentials.Credentials) diff --git a/packages/google-auth/tox.ini b/packages/google-auth/tox.ini index 9e94666d6101..59fd6aba1706 100644 --- a/packages/google-auth/tox.ini +++ b/packages/google-auth/tox.ini @@ -7,7 +7,7 @@ deps = flask mock oauth2client - pytest<5.0 + pytest pytest-cov pytest-localserver requests From af8b8bd9105e71e162101bf27209959012592486 Mon Sep 17 00:00:00 2001 From: ylil93 Date: Thu, 25 Jul 2019 16:21:13 -0700 Subject: [PATCH 217/966] add compatibility check badges to README (#346) --- packages/google-auth/README.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/README.rst b/packages/google-auth/README.rst index 00dbfef62f0d..c171f95884a8 100644 --- a/packages/google-auth/README.rst +++ b/packages/google-auth/README.rst @@ -1,7 +1,7 @@ Google Auth Python Library ========================== -|build| |docs| |pypi| +|build| |docs| |pypi| |compat_check_pypi| |compat_check_github| This library simplifies using Google's various server-to-server authentication mechanisms to access Google APIs. @@ -12,6 +12,10 @@ mechanisms to access Google APIs. :target: https://google-auth.readthedocs.io/en/latest/ .. |pypi| image:: https://img.shields.io/pypi/v/google-auth.svg :target: https://pypi.python.org/pypi/google-auth +.. |compat_check_pypi| image:: https://python-compatibility-tools.appspot.com/one_badge_image?package=google-auth + :target: https://python-compatibility-tools.appspot.com/one_badge_target?package=google-auth +.. |compat_check_github| image:: https://python-compatibility-tools.appspot.com/one_badge_image?package=git%2Bgit%3A//github.com/googleapis/google-auth-library-python.git + :target: https://python-compatibility-tools.appspot.com/one_badge_target?package=git%2Bgit%3A//github.com/googleapis/google-auth-library-python.git Installing ---------- From 6af3f247ca6df1ca983c93c6dcff7928b4741e93 Mon Sep 17 00:00:00 2001 From: Alan Yee Date: Thu, 25 Jul 2019 16:22:56 -0700 Subject: [PATCH 218/966] Update user-guide.rst (#337) Add additional way to supply creds to client without relying on I/O --- packages/google-auth/docs/user-guide.rst | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/docs/user-guide.rst b/packages/google-auth/docs/user-guide.rst index 62836623fd25..d43163fe12e0 100644 --- a/packages/google-auth/docs/user-guide.rst +++ b/packages/google-auth/docs/user-guide.rst @@ -56,7 +56,7 @@ Service account private key files A service account private key file can be used to obtain credentials for a service account. You can create a private key using the `Credentials page of the Google Cloud Console`_. Once you have a private key you can either obtain -credentials one of two ways: +credentials one of three ways: 1. Set the ``GOOGLE_APPLICATION_CREDENTIALS`` environment variable to the full path to your service account private key file @@ -81,6 +81,20 @@ credentials one of two ways: scoped_credentials = credentials.with_scopes( ['https://www.googleapis.com/auth/cloud-platform']) +3. Use :meth:`service_account.Credentials.from_service_account_info + `:: + + import json + + from google.oauth2 import service_account + + json_acct_info = json.loads(function_to_get_json_creds()) + credentials = service_account.Credentials.from_service_account_info( + json_acct_info) + + scoped_credentials = credentials.with_scopes( + ['https://www.googleapis.com/auth/cloud-platform']) + .. warning:: Private keys must be kept secret. If you expose your private key it is recommended to revoke it immediately from the Google Cloud Console. From 5ce9d281f52d6d0d41334f32a62de04afebfb42f Mon Sep 17 00:00:00 2001 From: Emile Caron Date: Fri, 26 Jul 2019 01:23:25 +0200 Subject: [PATCH 219/966] Use cls parameter instead of class (#341) Use cls parameter instead of explicit `Credentials` reference to allow subclassing --- packages/google-auth/google/oauth2/credentials.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/google/oauth2/credentials.py b/packages/google-auth/google/oauth2/credentials.py index b56e31426348..9e1141646b64 100644 --- a/packages/google-auth/google/oauth2/credentials.py +++ b/packages/google-auth/google/oauth2/credentials.py @@ -179,7 +179,7 @@ def from_authorized_user_info(cls, info, scopes=None): 'Authorized user info was not in the expected format, missing ' 'fields {}.'.format(', '.join(missing))) - return Credentials( + return cls( None, # No access token, must be refreshed. refresh_token=info['refresh_token'], token_uri=_GOOGLE_OAUTH2_TOKEN_ENDPOINT, From 121c1a068a9e3141d7c7570b290ddc5b393c3e78 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Fri, 26 Jul 2019 15:28:24 -0400 Subject: [PATCH 220/966] Initial renovate config. (#356) --- packages/google-auth/renovate.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 packages/google-auth/renovate.json diff --git a/packages/google-auth/renovate.json b/packages/google-auth/renovate.json new file mode 100644 index 000000000000..4fa949311b20 --- /dev/null +++ b/packages/google-auth/renovate.json @@ -0,0 +1,5 @@ +{ + "extends": [ + "config:base", ":preserveSemverRanges" + ] +} From 301708beb0858c2a984d0175cb3f3ce6577fab12 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Mon, 29 Jul 2019 21:18:13 +0300 Subject: [PATCH 221/966] Update dependency rsa to v4 (#358) --- packages/google-auth/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 4e87a7283549..7149981e756e 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -21,7 +21,7 @@ DEPENDENCIES = ( 'cachetools>=2.0.0,<3.0', 'pyasn1-modules>=0.2.1', - 'rsa>=3.1.4,<4.0', + 'rsa>=3.1.4,<4.1', 'setuptools>=40.3.0', 'six>=1.9.0', ) From fb10defbc8bfad079fa76d623b682248493adcce Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Mon, 29 Jul 2019 21:19:06 +0300 Subject: [PATCH 222/966] Update dependency cachetools to v3 (#357) --- packages/google-auth/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 7149981e756e..60c7df43bed9 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -19,7 +19,7 @@ DEPENDENCIES = ( - 'cachetools>=2.0.0,<3.0', + 'cachetools>=2.0.0,<3.2', 'pyasn1-modules>=0.2.1', 'rsa>=3.1.4,<4.1', 'setuptools>=40.3.0', From 3c8d485168108de35596ead9a30243e652d02c3c Mon Sep 17 00:00:00 2001 From: salrashid123 Date: Wed, 7 Aug 2019 14:31:33 -0700 Subject: [PATCH 223/966] Add support for imersonated_credentials.Sign, IDToken (#348) --- packages/google-auth/docs/index.rst | 1 + packages/google-auth/docs/user-guide.rst | 80 ++++++- .../google/auth/impersonated_credentials.py | 125 +++++++++- .../tests/test_impersonated_credentials.py | 223 +++++++++++++++++- 4 files changed, 423 insertions(+), 6 deletions(-) diff --git a/packages/google-auth/docs/index.rst b/packages/google-auth/docs/index.rst index 1eb3d861ad0f..4287c3db3eb5 100644 --- a/packages/google-auth/docs/index.rst +++ b/packages/google-auth/docs/index.rst @@ -14,6 +14,7 @@ also provides integration with several HTTP libraries. - Support for Google :func:`Application Default Credentials `. - Support for signing and verifying :mod:`JWTs `. +- Support for creating `Google ID Tokens `__. - Support for verifying and decoding :mod:`ID Tokens `. - Support for Google :mod:`Service Account credentials `. - Support for Google :mod:`Impersonated Credentials `. diff --git a/packages/google-auth/docs/user-guide.rst b/packages/google-auth/docs/user-guide.rst index d43163fe12e0..0abe160a3bee 100644 --- a/packages/google-auth/docs/user-guide.rst +++ b/packages/google-auth/docs/user-guide.rst @@ -241,13 +241,91 @@ the "Service Account Token Creator" IAM role. :: client = storage.Client(credentials=target_credentials) buckets = client.list_buckets(project='your_project') for bucket in buckets: - print bucket.name + print(bucket.name) In the example above `source_credentials` does not have direct access to list buckets in the target project. Using `ImpersonatedCredentials` will allow the source_credentials to assume the identity of a target_principal that does have access. +Identity Tokens ++++++++++++++++ + +`Google OpenID Connect`_ tokens are avaiable through :mod:`Service Account `, +:mod:`Impersonated `, +and :mod:`Compute Engine `. These tokens can be used to +authenticate against `Cloud Functions`_, `Cloud Run`_, a user service behind +`Identity Aware Proxy`_ or any other service capable of verifying a `Google ID Token`_. + +ServiceAccount :: + + from google.oauth2 import service_account + + target_audience = 'https://example.com' + + creds = service_account.IDTokenCredentials.from_service_account_file( + '/path/to/svc.json', + target_audience=target_audience) + + +Compute :: + + from google.auth import compute_engine + import google.auth.transport.requests + + target_audience = 'https://example.com' + + request = google.auth.transport.requests.Request() + creds = compute_engine.IDTokenCredentials(request, + target_audience=target_audience) + +Impersonated :: + + from google.auth import impersonated_credentials + + # get target_credentials from a source_credential + + target_audience = 'https://example.com' + + creds = impersonated_credentials.IDTokenCredentials( + target_credentials, + target_audience=target_audience) + +IDToken verification can be done for various type of IDTokens using the :class:`google.oauth2.id_token` module + +A sample end-to-end flow using an ID Token against a Cloud Run endpoint maybe :: + + from google.oauth2 import id_token + from google.oauth2 import service_account + import google.auth + import google.auth.transport.requests + from google.auth.transport.requests import AuthorizedSession + + target_audience = 'https://your-cloud-run-app.a.run.app' + url = 'https://your-cloud-run-app.a.run.app' + + creds = service_account.IDTokenCredentials.from_service_account_file( + '/path/to/svc.json', target_audience=target_audience) + + authed_session = AuthorizedSession(creds) + + # make authenticated request and print the response, status_code + resp = authed_session.get(url) + print(resp.status_code) + print(resp.text) + + # to verify an ID Token + request = google.auth.transport.requests.Request() + token = creds.token + print(token) + print(id_token.verify_token(token,request)) + +.. _Cloud Functions: https://cloud.google.com/functions/ +.. _Cloud Run: https://cloud.google.com/run/ +.. _Identity Aware Proxy: https://cloud.google.com/iap/ +.. _Google OpenID Connect: https://developers.google.com/identity/protocols/OpenIDConnect +.. _Google ID Token: https://developers.google.com/identity/protocols/OpenIDConnect#validatinganidtoken + Making authenticated requests ----------------------------- diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index 32dfe8309e88..bb2bbf26a139 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -25,6 +25,7 @@ https://cloud.google.com/iam/credentials/reference/rest/ """ +import base64 import copy from datetime import datetime import json @@ -35,6 +36,8 @@ from google.auth import _helpers from google.auth import credentials from google.auth import exceptions +from google.auth import jwt +from google.auth.transport.requests import AuthorizedSession _DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds @@ -43,8 +46,18 @@ _IAM_ENDPOINT = ('https://iamcredentials.googleapis.com/v1/projects/-' + '/serviceAccounts/{}:generateAccessToken') +_IAM_SIGN_ENDPOINT = ('https://iamcredentials.googleapis.com/v1/projects/-' + + '/serviceAccounts/{}:signBlob') + +_IAM_IDTOKEN_ENDPOINT = ('https://iamcredentials.googleapis.com/v1/' + + 'projects/-/serviceAccounts/{}:generateIdToken') + _REFRESH_ERROR = 'Unable to acquire impersonated credentials' +_DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds + +_DEFAULT_TOKEN_URI = 'https://oauth2.googleapis.com/token' + def _make_iam_token_request(request, principal, headers, body): """Makes a request to the Google Cloud IAM service for an access token. @@ -94,7 +107,7 @@ def _make_iam_token_request(request, principal, headers, body): six.raise_from(new_exc, caught_exc) -class Credentials(credentials.Credentials): +class Credentials(credentials.Credentials, credentials.Signing): """This module defines impersonated credentials which are essentially impersonated identities. @@ -153,7 +166,7 @@ class Credentials(credentials.Credentials): client = storage.Client(credentials=target_credentials) buckets = client.list_buckets(project='your_project') for bucket in buckets: - print bucket.name + print(bucket.name) """ def __init__(self, source_credentials, target_principal, @@ -172,7 +185,8 @@ def __init__(self, source_credentials, target_principal, granted to the prceeding identity. For example, if set to [serviceAccountB, serviceAccountC], the source_credential must have the Token Creator role on serviceAccountB. - serviceAccountB must have the Token Creator on serviceAccountC. + serviceAccountB must have the Token Creator on + serviceAccountC. Finally, C must have Token Creator on target_principal. If left unset, source_credential must have that role on target_principal. @@ -229,3 +243,108 @@ def _update_token(self, request): principal=self._target_principal, headers=headers, body=body) + + def sign_bytes(self, message): + + iam_sign_endpoint = _IAM_SIGN_ENDPOINT.format(self._target_principal) + + body = { + "payload": base64.b64encode(message), + "delegates": self._delegates + } + + headers = { + 'Content-Type': 'application/json', + } + + authed_session = AuthorizedSession(self._source_credentials) + + response = authed_session.post( + url=iam_sign_endpoint, + headers=headers, + json=body) + + return base64.b64decode(response.json()['signedBlob']) + + @property + def signer_email(self): + return self._target_principal + + @property + def service_account_email(self): + return self._target_principal + + @property + def signer(self): + return self + + +class IDTokenCredentials(credentials.Credentials): + """Open ID Connect ID Token-based service account credentials. + + """ + def __init__(self, target_credentials, + target_audience=None, include_email=False): + """ + Args: + target_credentials (google.auth.Credentials): The target + credential used as to acquire the id tokens for. + target_audience (string): Audience to issue the token for. + include_email (bool): Include email in IdToken + """ + super(IDTokenCredentials, self).__init__() + + if not isinstance(target_credentials, + Credentials): + raise exceptions.GoogleAuthError("Provided Credential must be " + "impersonated_credentials") + self._target_credentials = target_credentials + self._target_audience = target_audience + self._include_email = include_email + + def from_credentials(self, target_credentials, + target_audience=None): + return self.__class__( + target_credentials=self._target_credentials, + target_audience=target_audience) + + def with_target_audience(self, target_audience): + return self.__class__( + target_credentials=self._target_credentials, + target_audience=target_audience) + + def with_include_email(self, include_email): + return self.__class__( + target_credentials=self._target_credentials, + target_audience=self._target_audience, + include_email=include_email) + + @_helpers.copy_docstring(credentials.Credentials) + def refresh(self, request): + + iam_sign_endpoint = _IAM_IDTOKEN_ENDPOINT.format(self. + _target_credentials. + signer_email) + + body = { + "audience": self._target_audience, + "delegates": self._target_credentials._delegates, + "includeEmail": self._include_email + } + + headers = { + 'Content-Type': 'application/json', + } + + authed_session = AuthorizedSession(self._target_credentials. + _source_credentials) + + response = authed_session.post( + url=iam_sign_endpoint, + headers=headers, + data=json.dumps(body).encode('utf-8')) + + id_token = response.json()['token'] + self.token = id_token + self.expiry = datetime.fromtimestamp(jwt.decode(id_token, + verify=False)['exp']) diff --git a/packages/google-auth/tests/test_impersonated_credentials.py b/packages/google-auth/tests/test_impersonated_credentials.py index 68a2af8f2df4..9945401d4ffc 100644 --- a/packages/google-auth/tests/test_impersonated_credentials.py +++ b/packages/google-auth/tests/test_impersonated_credentials.py @@ -35,6 +35,14 @@ SERVICE_ACCOUNT_JSON_FILE = os.path.join(DATA_DIR, 'service_account.json') +ID_TOKEN_DATA = ('eyJhbGciOiJSUzI1NiIsImtpZCI6ImRmMzc1ODkwOGI3OTIyOTNhZDk3N2Ew' + 'Yjk5MWQ5OGE3N2Y0ZWVlY2QiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJodHRwc' + 'zovL2Zvby5iYXIiLCJhenAiOiIxMDIxMDE1NTA4MzQyMDA3MDg1NjgiLCJle' + 'HAiOjE1NjQ0NzUwNTEsImlhdCI6MTU2NDQ3MTQ1MSwiaXNzIjoiaHR0cHM6L' + 'y9hY2NvdW50cy5nb29nbGUuY29tIiwic3ViIjoiMTAyMTAxNTUwODM0MjAwN' + 'zA4NTY4In0.redacted') +ID_TOKEN_EXPIRY = 1564475051 + with open(SERVICE_ACCOUNT_JSON_FILE, 'r') as fh: SERVICE_ACCOUNT_INFO = json.load(fh) @@ -52,6 +60,38 @@ def mock_donor_credentials(): yield grant +class MockResponse: + def __init__(self, json_data, status_code): + self.json_data = json_data + self.status_code = status_code + + def json(self): + return self.json_data + + +@pytest.fixture +def mock_authorizedsession_sign(): + with mock.patch('google.auth.transport.requests.AuthorizedSession.request', + autospec=True) as auth_session: + data = { + "keyId": "1", + "signedBlob": "c2lnbmF0dXJl" + } + auth_session.return_value = MockResponse(data, http_client.OK) + yield auth_session + + +@pytest.fixture +def mock_authorizedsession_idtoken(): + with mock.patch('google.auth.transport.requests.AuthorizedSession.request', + autospec=True) as auth_session: + data = { + "token": ID_TOKEN_DATA + } + auth_session.return_value = MockResponse(data, http_client.OK) + yield auth_session + + class TestImpersonatedCredentials(object): SERVICE_ACCOUNT_EMAIL = 'service-account@example.com' @@ -62,10 +102,12 @@ class TestImpersonatedCredentials(object): SOURCE_CREDENTIALS = service_account.Credentials( SIGNER, SERVICE_ACCOUNT_EMAIL, TOKEN_URI) - def make_credentials(self, lifetime=LIFETIME): + def make_credentials(self, lifetime=LIFETIME, + target_principal=TARGET_PRINCIPAL): + return Credentials( source_credentials=self.SOURCE_CREDENTIALS, - target_principal=self.TARGET_PRINCIPAL, + target_principal=target_principal, target_scopes=self.TARGET_SCOPES, delegates=self.DELEGATES, lifetime=lifetime) @@ -176,3 +218,180 @@ def test_refresh_failure_http_error(self, mock_donor_credentials): def test_expired(self): credentials = self.make_credentials(lifetime=None) assert credentials.expired + + def test_signer(self): + credentials = self.make_credentials() + assert isinstance(credentials.signer, + impersonated_credentials.Credentials) + + def test_signer_email(self): + credentials = self.make_credentials( + target_principal=self.TARGET_PRINCIPAL) + assert credentials.signer_email == self.TARGET_PRINCIPAL + + def test_service_account_email(self): + credentials = self.make_credentials( + target_principal=self.TARGET_PRINCIPAL) + assert credentials.service_account_email == self.TARGET_PRINCIPAL + + def test_sign_bytes(self, mock_donor_credentials, + mock_authorizedsession_sign): + credentials = self.make_credentials(lifetime=None) + token = 'token' + + expire_time = ( + _helpers.utcnow().replace(microsecond=0) + + datetime.timedelta(seconds=500)).isoformat('T') + 'Z' + token_response_body = { + "accessToken": token, + "expireTime": expire_time + } + + response = mock.create_autospec(transport.Response, instance=False) + response.status = http_client.OK + response.data = _helpers.to_bytes(json.dumps(token_response_body)) + + request = mock.create_autospec(transport.Request, instance=False) + request.return_value = response + + credentials.refresh(request) + + assert credentials.valid + assert not credentials.expired + + signature = credentials.sign_bytes(b'signed bytes') + assert signature == b'signature' + + def test_id_token_success(self, mock_donor_credentials, + mock_authorizedsession_idtoken): + credentials = self.make_credentials(lifetime=None) + token = 'token' + target_audience = 'https://foo.bar' + + expire_time = ( + _helpers.utcnow().replace(microsecond=0) + + datetime.timedelta(seconds=500)).isoformat('T') + 'Z' + response_body = { + "accessToken": token, + "expireTime": expire_time + } + + request = self.make_request( + data=json.dumps(response_body), + status=http_client.OK) + + credentials.refresh(request) + + assert credentials.valid + assert not credentials.expired + + id_creds = impersonated_credentials.IDTokenCredentials( + credentials, target_audience=target_audience) + id_creds.refresh(request) + + assert id_creds.token == ID_TOKEN_DATA + assert id_creds.expiry == datetime.datetime.fromtimestamp( + ID_TOKEN_EXPIRY) + + def test_id_token_from_credential(self, mock_donor_credentials, + mock_authorizedsession_idtoken): + credentials = self.make_credentials(lifetime=None) + token = 'token' + target_audience = 'https://foo.bar' + + expire_time = ( + _helpers.utcnow().replace(microsecond=0) + + datetime.timedelta(seconds=500)).isoformat('T') + 'Z' + response_body = { + "accessToken": token, + "expireTime": expire_time + } + + request = self.make_request( + data=json.dumps(response_body), + status=http_client.OK) + + credentials.refresh(request) + + assert credentials.valid + assert not credentials.expired + + id_creds = impersonated_credentials.IDTokenCredentials( + credentials, target_audience=target_audience) + id_creds = id_creds.from_credentials(target_credentials=credentials) + id_creds.refresh(request) + + assert id_creds.token == ID_TOKEN_DATA + + def test_id_token_with_target_audience(self, mock_donor_credentials, + mock_authorizedsession_idtoken): + credentials = self.make_credentials(lifetime=None) + token = 'token' + target_audience = 'https://foo.bar' + + expire_time = ( + _helpers.utcnow().replace(microsecond=0) + + datetime.timedelta(seconds=500)).isoformat('T') + 'Z' + response_body = { + "accessToken": token, + "expireTime": expire_time + } + + request = self.make_request( + data=json.dumps(response_body), + status=http_client.OK) + + credentials.refresh(request) + + assert credentials.valid + assert not credentials.expired + + id_creds = impersonated_credentials.IDTokenCredentials( + credentials) + id_creds = id_creds.with_target_audience( + target_audience=target_audience) + id_creds.refresh(request) + + assert id_creds.token == ID_TOKEN_DATA + assert id_creds.expiry == datetime.datetime.fromtimestamp( + ID_TOKEN_EXPIRY) + + def test_id_token_invalid_cred(self, mock_donor_credentials, + mock_authorizedsession_idtoken): + credentials = None + + with pytest.raises(exceptions.GoogleAuthError) as excinfo: + impersonated_credentials.IDTokenCredentials(credentials) + + assert excinfo.match('Provided Credential must be' + ' impersonated_credentials') + + def test_id_token_with_include_email(self, mock_donor_credentials, + mock_authorizedsession_idtoken): + credentials = self.make_credentials(lifetime=None) + token = 'token' + target_audience = 'https://foo.bar' + + expire_time = ( + _helpers.utcnow().replace(microsecond=0) + + datetime.timedelta(seconds=500)).isoformat('T') + 'Z' + response_body = { + "accessToken": token, + "expireTime": expire_time + } + + request = self.make_request( + data=json.dumps(response_body), + status=http_client.OK) + + credentials.refresh(request) + + assert credentials.valid + assert not credentials.expired + + id_creds = impersonated_credentials.IDTokenCredentials( + credentials, target_audience=target_audience) + id_creds = id_creds.with_include_email(True) + id_creds.refresh(request) + + assert id_creds.token == ID_TOKEN_DATA From da4a17816b6f5b06c377448cb5e5195cb695288d Mon Sep 17 00:00:00 2001 From: Anjali Doneria Date: Mon, 9 Sep 2019 16:36:10 -0700 Subject: [PATCH 224/966] Add retry loop for fetching authentication token if any 'Internal Failure' occurs (#368) * Add retry loop for fetching authentication token if any 'Internal failure' occurs This is to prevent errors while client tries to connect with server and fails with 'Internal Failure' because it was unable to fetch OAuth2 token. * Add retry loop for fetching authentication token if any 'Internal failure' occurs This is to prevent errors while client tries to connect with server and fails with 'Internal Failure' because it was unable to fetch OAuth2 token. --- .../docs/reference/google.auth.app_engine.rst | 10 ++++---- ...google.auth.compute_engine.credentials.rst | 10 ++++---- .../reference/google.auth.compute_engine.rst | 11 ++++----- .../reference/google.auth.credentials.rst | 6 ++--- .../docs/reference/google.auth.crypt.rst | 7 +++--- .../google.auth.environment_vars.rst | 10 ++++---- .../docs/reference/google.auth.exceptions.rst | 6 ++--- .../docs/reference/google.auth.iam.rst | 6 ++--- .../google.auth.impersonated_credentials.rst | 6 ++--- .../docs/reference/google.auth.jwt.rst | 6 ++--- .../docs/reference/google.auth.rst | 13 +++++----- .../reference/google.auth.transport.grpc.rst | 6 ++--- .../google.auth.transport.requests.rst | 6 ++--- .../docs/reference/google.auth.transport.rst | 7 +++--- .../google.auth.transport.urllib3.rst | 6 ++--- .../reference/google.oauth2.credentials.rst | 6 ++--- .../docs/reference/google.oauth2.id_token.rst | 10 ++++---- .../docs/reference/google.oauth2.rst | 7 +++--- .../google.oauth2.service_account.rst | 10 ++++---- .../google-auth/docs/reference/google.rst | 11 ++++----- packages/google-auth/google/oauth2/_client.py | 24 +++++++++++++------ .../google-auth/tests/oauth2/test__client.py | 12 ++++++++++ 22 files changed, 106 insertions(+), 90 deletions(-) diff --git a/packages/google-auth/docs/reference/google.auth.app_engine.rst b/packages/google-auth/docs/reference/google.auth.app_engine.rst index 4525c089e35a..2142b6f50e8f 100644 --- a/packages/google-auth/docs/reference/google.auth.app_engine.rst +++ b/packages/google-auth/docs/reference/google.auth.app_engine.rst @@ -1,7 +1,7 @@ -google.auth.app_engine module -============================= +google.auth.app\_engine module +============================== .. automodule:: google.auth.app_engine - :members: - :inherited-members: - :show-inheritance: + :members: + :inherited-members: + :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.auth.compute_engine.credentials.rst b/packages/google-auth/docs/reference/google.auth.compute_engine.credentials.rst index ebe62c07cfd7..782d95f203c6 100644 --- a/packages/google-auth/docs/reference/google.auth.compute_engine.credentials.rst +++ b/packages/google-auth/docs/reference/google.auth.compute_engine.credentials.rst @@ -1,7 +1,7 @@ -google.auth.compute_engine.credentials module -============================================= +google.auth.compute\_engine.credentials module +============================================== .. automodule:: google.auth.compute_engine.credentials - :members: - :inherited-members: - :show-inheritance: + :members: + :inherited-members: + :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.auth.compute_engine.rst b/packages/google-auth/docs/reference/google.auth.compute_engine.rst index 2e7b830db9e4..38785c803227 100644 --- a/packages/google-auth/docs/reference/google.auth.compute_engine.rst +++ b/packages/google-auth/docs/reference/google.auth.compute_engine.rst @@ -1,10 +1,10 @@ -google.auth.compute_engine package -================================== +google.auth.compute\_engine package +=================================== .. automodule:: google.auth.compute_engine - :members: - :inherited-members: - :show-inheritance: + :members: + :inherited-members: + :show-inheritance: Submodules ---------- @@ -12,4 +12,3 @@ Submodules .. toctree:: google.auth.compute_engine.credentials - diff --git a/packages/google-auth/docs/reference/google.auth.credentials.rst b/packages/google-auth/docs/reference/google.auth.credentials.rst index b710a8c8c301..18d1d8cdf2f6 100644 --- a/packages/google-auth/docs/reference/google.auth.credentials.rst +++ b/packages/google-auth/docs/reference/google.auth.credentials.rst @@ -2,6 +2,6 @@ google.auth.credentials module ============================== .. automodule:: google.auth.credentials - :members: - :inherited-members: - :show-inheritance: + :members: + :inherited-members: + :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.auth.crypt.rst b/packages/google-auth/docs/reference/google.auth.crypt.rst index a3e2b1206961..0833e7f2ff92 100644 --- a/packages/google-auth/docs/reference/google.auth.crypt.rst +++ b/packages/google-auth/docs/reference/google.auth.crypt.rst @@ -2,9 +2,9 @@ google.auth.crypt package ========================= .. automodule:: google.auth.crypt - :members: - :inherited-members: - :show-inheritance: + :members: + :inherited-members: + :show-inheritance: Submodules ---------- @@ -13,4 +13,3 @@ Submodules google.auth.crypt.base google.auth.crypt.rsa - diff --git a/packages/google-auth/docs/reference/google.auth.environment_vars.rst b/packages/google-auth/docs/reference/google.auth.environment_vars.rst index fe34849ddbb6..5996e993596c 100644 --- a/packages/google-auth/docs/reference/google.auth.environment_vars.rst +++ b/packages/google-auth/docs/reference/google.auth.environment_vars.rst @@ -1,7 +1,7 @@ -google.auth.environment_vars module -=================================== +google.auth.environment\_vars module +==================================== .. automodule:: google.auth.environment_vars - :members: - :inherited-members: - :show-inheritance: + :members: + :inherited-members: + :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.auth.exceptions.rst b/packages/google-auth/docs/reference/google.auth.exceptions.rst index d603eaf8998e..c87a7f2afef9 100644 --- a/packages/google-auth/docs/reference/google.auth.exceptions.rst +++ b/packages/google-auth/docs/reference/google.auth.exceptions.rst @@ -2,6 +2,6 @@ google.auth.exceptions module ============================= .. automodule:: google.auth.exceptions - :members: - :inherited-members: - :show-inheritance: + :members: + :inherited-members: + :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.auth.iam.rst b/packages/google-auth/docs/reference/google.auth.iam.rst index 8a5edb450ee4..8472ed731aca 100644 --- a/packages/google-auth/docs/reference/google.auth.iam.rst +++ b/packages/google-auth/docs/reference/google.auth.iam.rst @@ -2,6 +2,6 @@ google.auth.iam module ====================== .. automodule:: google.auth.iam - :members: - :inherited-members: - :show-inheritance: + :members: + :inherited-members: + :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.auth.impersonated_credentials.rst b/packages/google-auth/docs/reference/google.auth.impersonated_credentials.rst index 653708ef778c..f139ccf4f66f 100644 --- a/packages/google-auth/docs/reference/google.auth.impersonated_credentials.rst +++ b/packages/google-auth/docs/reference/google.auth.impersonated_credentials.rst @@ -2,6 +2,6 @@ google.auth.impersonated\_credentials module ============================================ .. automodule:: google.auth.impersonated_credentials - :members: - :inherited-members: - :show-inheritance: + :members: + :inherited-members: + :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.auth.jwt.rst b/packages/google-auth/docs/reference/google.auth.jwt.rst index c58be5fbd4ea..c7c2fdf62e7c 100644 --- a/packages/google-auth/docs/reference/google.auth.jwt.rst +++ b/packages/google-auth/docs/reference/google.auth.jwt.rst @@ -2,6 +2,6 @@ google.auth.jwt module ====================== .. automodule:: google.auth.jwt - :members: - :inherited-members: - :show-inheritance: + :members: + :inherited-members: + :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.auth.rst b/packages/google-auth/docs/reference/google.auth.rst index 53ab699c696c..f6ea073c55e0 100644 --- a/packages/google-auth/docs/reference/google.auth.rst +++ b/packages/google-auth/docs/reference/google.auth.rst @@ -2,18 +2,18 @@ google.auth package =================== .. automodule:: google.auth - :members: - :inherited-members: - :show-inheritance: + :members: + :inherited-members: + :show-inheritance: Subpackages ----------- .. toctree:: - google.auth.compute_engine - google.auth.crypt - google.auth.transport + google.auth.compute_engine + google.auth.crypt + google.auth.transport Submodules ---------- @@ -27,4 +27,3 @@ Submodules google.auth.iam google.auth.impersonated_credentials google.auth.jwt - diff --git a/packages/google-auth/docs/reference/google.auth.transport.grpc.rst b/packages/google-auth/docs/reference/google.auth.transport.grpc.rst index e16d1d853cbe..f9f34429558a 100644 --- a/packages/google-auth/docs/reference/google.auth.transport.grpc.rst +++ b/packages/google-auth/docs/reference/google.auth.transport.grpc.rst @@ -2,6 +2,6 @@ google.auth.transport.grpc module ================================= .. automodule:: google.auth.transport.grpc - :members: - :inherited-members: - :show-inheritance: + :members: + :inherited-members: + :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.auth.transport.requests.rst b/packages/google-auth/docs/reference/google.auth.transport.requests.rst index f31830c56ba5..5f0c23c2db24 100644 --- a/packages/google-auth/docs/reference/google.auth.transport.requests.rst +++ b/packages/google-auth/docs/reference/google.auth.transport.requests.rst @@ -2,6 +2,6 @@ google.auth.transport.requests module ===================================== .. automodule:: google.auth.transport.requests - :members: - :inherited-members: - :show-inheritance: + :members: + :inherited-members: + :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.auth.transport.rst b/packages/google-auth/docs/reference/google.auth.transport.rst index 1f802c3b5ed8..48e2e0551478 100644 --- a/packages/google-auth/docs/reference/google.auth.transport.rst +++ b/packages/google-auth/docs/reference/google.auth.transport.rst @@ -2,9 +2,9 @@ google.auth.transport package ============================= .. automodule:: google.auth.transport - :members: - :inherited-members: - :show-inheritance: + :members: + :inherited-members: + :show-inheritance: Submodules ---------- @@ -14,4 +14,3 @@ Submodules google.auth.transport.grpc google.auth.transport.requests google.auth.transport.urllib3 - diff --git a/packages/google-auth/docs/reference/google.auth.transport.urllib3.rst b/packages/google-auth/docs/reference/google.auth.transport.urllib3.rst index 339c77ead3c3..667bb0978c09 100644 --- a/packages/google-auth/docs/reference/google.auth.transport.urllib3.rst +++ b/packages/google-auth/docs/reference/google.auth.transport.urllib3.rst @@ -2,6 +2,6 @@ google.auth.transport.urllib3 module ==================================== .. automodule:: google.auth.transport.urllib3 - :members: - :inherited-members: - :show-inheritance: + :members: + :inherited-members: + :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.oauth2.credentials.rst b/packages/google-auth/docs/reference/google.oauth2.credentials.rst index ca978d14822f..d3bdc16de652 100644 --- a/packages/google-auth/docs/reference/google.oauth2.credentials.rst +++ b/packages/google-auth/docs/reference/google.oauth2.credentials.rst @@ -2,6 +2,6 @@ google.oauth2.credentials module ================================ .. automodule:: google.oauth2.credentials - :members: - :inherited-members: - :show-inheritance: + :members: + :inherited-members: + :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.oauth2.id_token.rst b/packages/google-auth/docs/reference/google.oauth2.id_token.rst index db38b6085516..fbe6eab8dade 100644 --- a/packages/google-auth/docs/reference/google.oauth2.id_token.rst +++ b/packages/google-auth/docs/reference/google.oauth2.id_token.rst @@ -1,7 +1,7 @@ -google.oauth2.id_token module -============================= +google.oauth2.id\_token module +============================== .. automodule:: google.oauth2.id_token - :members: - :inherited-members: - :show-inheritance: + :members: + :inherited-members: + :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.oauth2.rst b/packages/google-auth/docs/reference/google.oauth2.rst index adb9403efe28..4f1df071f5d5 100644 --- a/packages/google-auth/docs/reference/google.oauth2.rst +++ b/packages/google-auth/docs/reference/google.oauth2.rst @@ -2,9 +2,9 @@ google.oauth2 package ===================== .. automodule:: google.oauth2 - :members: - :inherited-members: - :show-inheritance: + :members: + :inherited-members: + :show-inheritance: Submodules ---------- @@ -14,4 +14,3 @@ Submodules google.oauth2.credentials google.oauth2.id_token google.oauth2.service_account - diff --git a/packages/google-auth/docs/reference/google.oauth2.service_account.rst b/packages/google-auth/docs/reference/google.oauth2.service_account.rst index cc4e43899d74..8d8fcd3f1af9 100644 --- a/packages/google-auth/docs/reference/google.oauth2.service_account.rst +++ b/packages/google-auth/docs/reference/google.oauth2.service_account.rst @@ -1,7 +1,7 @@ -google.oauth2.service_account module -==================================== +google.oauth2.service\_account module +===================================== .. automodule:: google.oauth2.service_account - :members: - :inherited-members: - :show-inheritance: + :members: + :inherited-members: + :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.rst b/packages/google-auth/docs/reference/google.rst index fc63d1fdbd72..4b1e0853787d 100644 --- a/packages/google-auth/docs/reference/google.rst +++ b/packages/google-auth/docs/reference/google.rst @@ -2,15 +2,14 @@ google package ============== .. automodule:: google - :members: - :inherited-members: - :show-inheritance: + :members: + :inherited-members: + :show-inheritance: Subpackages ----------- .. toctree:: - google.auth - google.oauth2 - + google.auth + google.oauth2 diff --git a/packages/google-auth/google/oauth2/_client.py b/packages/google-auth/google/oauth2/_client.py index 5121a3274651..eac01b7ecd9e 100644 --- a/packages/google-auth/google/oauth2/_client.py +++ b/packages/google-auth/google/oauth2/_client.py @@ -102,13 +102,23 @@ def _token_endpoint_request(request, token_uri, body): 'content-type': _URLENCODED_CONTENT_TYPE, } - response = request( - method='POST', url=token_uri, headers=headers, body=body) - - response_body = response.data.decode('utf-8') - - if response.status != http_client.OK: - _handle_error_response(response_body) + retry = 0 + # retry to fetch token for maximum of two times if any internal failure + # occurs. + while True: + response = request( + method='POST', url=token_uri, headers=headers, body=body) + response_body = response.data.decode('utf-8') + + if response.status == http_client.OK: + break + else: + error_desc = json.loads( + response_body).get('error_description') or '' + if error_desc == 'internal_failure' and retry < 1: + retry += 1 + continue + _handle_error_response(response_body) response_data = json.loads(response_body) diff --git a/packages/google-auth/tests/oauth2/test__client.py b/packages/google-auth/tests/oauth2/test__client.py index 5a4a567456c1..6fc4c3b12b0f 100644 --- a/packages/google-auth/tests/oauth2/test__client.py +++ b/packages/google-auth/tests/oauth2/test__client.py @@ -106,6 +106,18 @@ def test__token_endpoint_request_error(): _client._token_endpoint_request(request, 'http://example.com', {}) +def test__token_endpoint_request_internal_failure_error(): + request = make_request({'error': 'internal_failure', + 'error_description': 'internal_failure'}, + status=http_client.BAD_REQUEST) + + with pytest.raises(exceptions.RefreshError): + _client._token_endpoint_request( + request, 'http://example.com', + {'error': 'internal_failure', + 'error_description': 'internal_failure'}) + + def verify_request_params(request, params): request_body = request.call_args[1]['body'] request_params = urllib.parse.parse_qs(request_body) From 70cf098f27df440cb288c48d060df23b2c05fe20 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Thu, 12 Sep 2019 09:31:13 -0700 Subject: [PATCH 225/966] Rename nox.py -> noxfile.py (#369) --- packages/google-auth/system_tests/{nox.py => noxfile.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/google-auth/system_tests/{nox.py => noxfile.py} (100%) diff --git a/packages/google-auth/system_tests/nox.py b/packages/google-auth/system_tests/noxfile.py similarity index 100% rename from packages/google-auth/system_tests/nox.py rename to packages/google-auth/system_tests/noxfile.py From ca0df2b5185e6a5b302dff21ef5fd08ba7af8910 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Wed, 2 Oct 2019 14:05:04 -0700 Subject: [PATCH 226/966] docs: add busunkim96 as maintainer (#373) --- packages/google-auth/README.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/README.rst b/packages/google-auth/README.rst index c171f95884a8..3d7ba76afa83 100644 --- a/packages/google-auth/README.rst +++ b/packages/google-auth/README.rst @@ -43,8 +43,12 @@ Documentation Google Auth Python Library has usage and reference documentation at `google-auth.readthedocs.io `_. -Maintainers ------------ +Current Maintainers +------------------- +- `@busunkim96 `_ (Bu Sun Kim) + +Authors +------- - `@theacodes `_ (Thea Flowers) - `@dhermes `_ (Danny Hermes) From 69021c8db6394531a00f8f7fa93b64befd879d81 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Mon, 21 Oct 2019 17:04:21 -0700 Subject: [PATCH 227/966] chore: blacken (#375) --- packages/google-auth/.flake8 | 8 + packages/google-auth/docs/conf.py | 125 ++++--- packages/google-auth/google/__init__.py | 2 + packages/google-auth/google/auth/__init__.py | 4 +- .../google-auth/google/auth/_cloud_sdk.py | 42 +-- packages/google-auth/google/auth/_default.py | 64 ++-- packages/google-auth/google/auth/_helpers.py | 30 +- .../google-auth/google/auth/_oauth2client.py | 34 +- .../google/auth/_service_account_info.py | 7 +- .../google-auth/google/auth/app_engine.py | 15 +- .../google/auth/compute_engine/__init__.py | 5 +- .../google/auth/compute_engine/_metadata.py | 76 ++-- .../google/auth/compute_engine/credentials.py | 50 +-- .../google-auth/google/auth/credentials.py | 18 +- .../google-auth/google/auth/crypt/__init__.py | 7 +- .../google/auth/crypt/_cryptography_rsa.py | 22 +- .../google/auth/crypt/_python_rsa.py | 49 ++- .../google-auth/google/auth/crypt/base.py | 22 +- .../google/auth/environment_vars.py | 12 +- packages/google-auth/google/auth/iam.py | 22 +- .../google/auth/impersonated_credentials.py | 134 ++++--- packages/google-auth/google/auth/jwt.py | 163 +++++---- .../google/auth/transport/__init__.py | 13 +- .../google/auth/transport/_http_client.py | 24 +- .../google-auth/google/auth/transport/grpc.py | 18 +- .../google/auth/transport/requests.py | 66 ++-- .../google/auth/transport/urllib3.py | 65 ++-- packages/google-auth/google/oauth2/_client.py | 78 ++-- .../google-auth/google/oauth2/credentials.py | 79 ++-- .../google-auth/google/oauth2/id_token.py | 24 +- .../google/oauth2/service_account.py | 91 +++-- packages/google-auth/setup.py | 64 ++-- .../app_engine_test_app/appengine_config.py | 3 +- .../system_tests/app_engine_test_app/main.py | 30 +- packages/google-auth/system_tests/conftest.py | 33 +- packages/google-auth/system_tests/noxfile.py | 144 ++++---- .../system_tests/test_app_engine.py | 6 +- .../system_tests/test_compute_engine.py | 4 +- .../google-auth/system_tests/test_default.py | 2 +- .../google-auth/system_tests/test_grpc.py | 39 +- .../system_tests/test_oauth2_credentials.py | 22 +- .../system_tests/test_service_account.py | 18 +- .../tests/compute_engine/test__metadata.py | 117 +++--- .../tests/compute_engine/test_credentials.py | 346 +++++++++--------- packages/google-auth/tests/conftest.py | 8 +- .../tests/crypt/test__cryptography_rsa.py | 42 +-- .../tests/crypt/test__python_rsa.py | 64 ++-- .../google-auth/tests/crypt/test_crypt.py | 21 +- .../google-auth/tests/oauth2/test__client.py | 242 ++++++------ .../tests/oauth2/test_credentials.py | 217 ++++++----- .../google-auth/tests/oauth2/test_id_token.py | 53 +-- .../tests/oauth2/test_service_account.py | 195 +++++----- packages/google-auth/tests/test__cloud_sdk.py | 93 ++--- packages/google-auth/tests/test__default.py | 258 +++++++------ packages/google-auth/tests/test__helpers.py | 93 +++-- .../google-auth/tests/test__oauth2client.py | 99 ++--- .../tests/test__service_account_info.py | 21 +- packages/google-auth/tests/test_app_engine.py | 57 +-- .../google-auth/tests/test_credentials.py | 52 +-- packages/google-auth/tests/test_iam.py | 33 +- .../tests/test_impersonated_credentials.py | 260 ++++++------- packages/google-auth/tests/test_jwt.py | 311 ++++++++-------- .../google-auth/tests/transport/compliance.py | 52 +-- .../tests/transport/test__http_client.py | 4 +- .../google-auth/tests/transport/test_grpc.py | 78 ++-- .../tests/transport/test_requests.py | 40 +- .../tests/transport/test_urllib3.py | 46 +-- 67 files changed, 2384 insertions(+), 2152 deletions(-) create mode 100644 packages/google-auth/.flake8 diff --git a/packages/google-auth/.flake8 b/packages/google-auth/.flake8 new file mode 100644 index 000000000000..0574e0a3ab66 --- /dev/null +++ b/packages/google-auth/.flake8 @@ -0,0 +1,8 @@ +[flake8] +ignore = E203, E266, E501, W503 +exclude = + # Standard linting exemptions. + __pycache__, + .git, + *.pyc, + conf.py diff --git a/packages/google-auth/docs/conf.py b/packages/google-auth/docs/conf.py index 2ac5f3428ede..831c752cf2a9 100644 --- a/packages/google-auth/docs/conf.py +++ b/packages/google-auth/docs/conf.py @@ -33,40 +33,40 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.intersphinx', - 'sphinx.ext.viewcode', - 'sphinx.ext.napoleon', - 'sphinx_docstring_typing' + "sphinx.ext.autodoc", + "sphinx.ext.intersphinx", + "sphinx.ext.viewcode", + "sphinx.ext.napoleon", + "sphinx_docstring_typing", ] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. # # source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = 'google-auth' -copyright = '2016, Google, Inc.' -author = 'Google, Inc.' +project = "google-auth" +copyright = "2016, Google, Inc." +author = "Google, Inc." # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = pkg_resources.get_distribution('google-auth').version +version = pkg_resources.get_distribution("google-auth").version # The full version, including alpha/beta/rc tags. release = version @@ -89,7 +89,7 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] # The reST default role (used for this markup: `text`) to use for all # documents. @@ -111,7 +111,7 @@ # show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] @@ -128,21 +128,21 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'alabaster' +html_theme = "alabaster" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = { - 'description': 'Google Auth Library for Python', - 'github_user': 'GoogleCloudPlatform', - 'github_repo': 'google-auth-library-python', - 'github_banner': True, - 'travis_button': True, - 'font_family': "'Roboto', Georgia, sans", - 'head_font_family': "'Roboto', Georgia, serif", - 'code_font_family': "'Roboto Mono', 'Consolas', monospace", + "description": "Google Auth Library for Python", + "github_user": "GoogleCloudPlatform", + "github_repo": "google-auth-library-python", + "github_banner": True, + "travis_button": True, + "font_family": "'Roboto', Georgia, sans", + "head_font_family": "'Roboto', Georgia, serif", + "code_font_family": "'Roboto Mono', 'Consolas', monospace", } # Add any paths that contain custom themes here, relative to this directory. @@ -171,7 +171,7 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied @@ -194,12 +194,7 @@ # html_sidebars = { - '**': [ - 'about.html', - 'navigation.html', - 'relations.html', - 'searchbox.html', - ] + "**": ["about.html", "navigation.html", "relations.html", "searchbox.html"] } # Additional templates that should be rendered to pages, maps page names to @@ -259,34 +254,36 @@ # html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. -htmlhelp_basename = 'google-authdoc' +htmlhelp_basename = "google-authdoc" # -- Options for LaTeX output --------------------------------------------- latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # - # 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # - # 'preamble': '', - - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'google-auth.tex', 'google-auth Documentation', - 'Google, Inc.', 'manual'), + ( + master_doc, + "google-auth.tex", + "google-auth Documentation", + "Google, Inc.", + "manual", + ) ] # The name of an image file (relative to this directory) to place at the top of @@ -326,10 +323,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'google-auth', 'google-auth Documentation', - [author], 1) -] +man_pages = [(master_doc, "google-auth", "google-auth Documentation", [author], 1)] # If true, show URL addresses after external links. # @@ -342,9 +336,15 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'google-auth', 'google-auth Documentation', - author, 'google-auth', 'One line description of project.', - 'Miscellaneous'), + ( + master_doc, + "google-auth", + "google-auth Documentation", + author, + "google-auth", + "One line description of project.", + "Miscellaneous", + ) ] # Documents to append as an appendix to all manuals. @@ -366,14 +366,13 @@ # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = { - 'python': ('https://docs.python.org/3.5', None), - 'urllib3': ('https://urllib3.readthedocs.io/en/stable', None), - 'requests': ('http://docs.python-requests.org/en/stable', None), - 'requests-oauthlib': ( - 'http://requests-oauthlib.readthedocs.io/en/stable', None), + "python": ("https://docs.python.org/3.5", None), + "urllib3": ("https://urllib3.readthedocs.io/en/stable", None), + "requests": ("http://docs.python-requests.org/en/stable", None), + "requests-oauthlib": ("http://requests-oauthlib.readthedocs.io/en/stable", None), } # Autodoc config -autoclass_content = 'both' -autodoc_member_order = 'bysource' -autodoc_mock_imports = ['grpc'] +autoclass_content = "both" +autodoc_member_order = "bysource" +autodoc_mock_imports = ["grpc"] diff --git a/packages/google-auth/google/__init__.py b/packages/google-auth/google/__init__.py index a35569c36339..f36d791564fb 100644 --- a/packages/google-auth/google/__init__.py +++ b/packages/google-auth/google/__init__.py @@ -16,7 +16,9 @@ try: import pkg_resources + pkg_resources.declare_namespace(__name__) except ImportError: import pkgutil + __path__ = pkgutil.extend_path(__path__, __name__) diff --git a/packages/google-auth/google/auth/__init__.py b/packages/google-auth/google/auth/__init__.py index 65e13951ca48..5f78cd17e108 100644 --- a/packages/google-auth/google/auth/__init__.py +++ b/packages/google-auth/google/auth/__init__.py @@ -19,9 +19,7 @@ from google.auth._default import default -__all__ = [ - 'default', -] +__all__ = ["default"] # Set default logging handler to avoid "No handler found" warnings. diff --git a/packages/google-auth/google/auth/_cloud_sdk.py b/packages/google-auth/google/auth/_cloud_sdk.py index 0d4b222f216e..61ffd4f5c563 100644 --- a/packages/google-auth/google/auth/_cloud_sdk.py +++ b/packages/google-auth/google/auth/_cloud_sdk.py @@ -23,20 +23,21 @@ # The ~/.config subdirectory containing gcloud credentials. -_CONFIG_DIRECTORY = 'gcloud' +_CONFIG_DIRECTORY = "gcloud" # Windows systems store config at %APPDATA%\gcloud -_WINDOWS_CONFIG_ROOT_ENV_VAR = 'APPDATA' +_WINDOWS_CONFIG_ROOT_ENV_VAR = "APPDATA" # The name of the file in the Cloud SDK config that contains default # credentials. -_CREDENTIALS_FILENAME = 'application_default_credentials.json' +_CREDENTIALS_FILENAME = "application_default_credentials.json" # The name of the Cloud SDK shell script -_CLOUD_SDK_POSIX_COMMAND = 'gcloud' -_CLOUD_SDK_WINDOWS_COMMAND = 'gcloud.cmd' +_CLOUD_SDK_POSIX_COMMAND = "gcloud" +_CLOUD_SDK_WINDOWS_COMMAND = "gcloud.cmd" # The command to get the Cloud SDK configuration -_CLOUD_SDK_CONFIG_COMMAND = ('config', 'config-helper', '--format', 'json') +_CLOUD_SDK_CONFIG_COMMAND = ("config", "config-helper", "--format", "json") # Cloud SDK's application-default client ID CLOUD_SDK_CLIENT_ID = ( - '764086051850-6qr4p6gpi6hn506pt8ejuq83di341hur.apps.googleusercontent.com') + "764086051850-6qr4p6gpi6hn506pt8ejuq83di341hur.apps.googleusercontent.com" +) def get_config_path(): @@ -52,21 +53,19 @@ def get_config_path(): pass # Non-windows systems store this at ~/.config/gcloud - if os.name != 'nt': - return os.path.join( - os.path.expanduser('~'), '.config', _CONFIG_DIRECTORY) + if os.name != "nt": + return os.path.join(os.path.expanduser("~"), ".config", _CONFIG_DIRECTORY) # Windows systems store config at %APPDATA%\gcloud else: try: return os.path.join( - os.environ[_WINDOWS_CONFIG_ROOT_ENV_VAR], - _CONFIG_DIRECTORY) + os.environ[_WINDOWS_CONFIG_ROOT_ENV_VAR], _CONFIG_DIRECTORY + ) except KeyError: # This should never happen unless someone is really # messing with things, but we'll cover the case anyway. - drive = os.environ.get('SystemDrive', 'C:') - return os.path.join( - drive, '\\', _CONFIG_DIRECTORY) + drive = os.environ.get("SystemDrive", "C:") + return os.path.join(drive, "\\", _CONFIG_DIRECTORY) def get_application_default_credentials_path(): @@ -93,8 +92,7 @@ def load_authorized_user_credentials(info): Raises: ValueError: if the info is in the wrong format or missing data. """ - return google.oauth2.credentials.Credentials.from_authorized_user_info( - info) + return google.oauth2.credentials.Credentials.from_authorized_user_info(info) def get_project_id(): @@ -103,24 +101,24 @@ def get_project_id(): Returns: Optional[str]: The project ID. """ - if os.name == 'nt': + if os.name == "nt": command = _CLOUD_SDK_WINDOWS_COMMAND else: command = _CLOUD_SDK_POSIX_COMMAND try: output = subprocess.check_output( - (command,) + _CLOUD_SDK_CONFIG_COMMAND, - stderr=subprocess.STDOUT) + (command,) + _CLOUD_SDK_CONFIG_COMMAND, stderr=subprocess.STDOUT + ) except (subprocess.CalledProcessError, OSError, IOError): return None try: - configuration = json.loads(output.decode('utf-8')) + configuration = json.loads(output.decode("utf-8")) except ValueError: return None try: - return configuration['configuration']['properties']['core']['project'] + return configuration["configuration"]["properties"]["core"]["project"] except KeyError: return None diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index 27de58de7397..32e81ba5faa4 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -32,8 +32,8 @@ _LOGGER = logging.getLogger(__name__) # Valid types accepted for file-based credentials. -_AUTHORIZED_USER_TYPE = 'authorized_user' -_SERVICE_ACCOUNT_TYPE = 'service_account' +_AUTHORIZED_USER_TYPE = "authorized_user" +_SERVICE_ACCOUNT_TYPE = "service_account" _VALID_TYPES = (_AUTHORIZED_USER_TYPE, _SERVICE_ACCOUNT_TYPE) # Help message when no credentials can be found. @@ -42,7 +42,9 @@ explicitly create credentials and re-run the application. For more \ information, please see \ https://cloud.google.com/docs/authentication/getting-started -""".format(env=environment_vars.CREDENTIALS).strip() +""".format( + env=environment_vars.CREDENTIALS +).strip() # Warning when using Cloud SDK user credentials _CLOUD_SDK_CREDENTIALS_WARNING = """\ @@ -62,6 +64,7 @@ def _warn_about_problematic_credentials(credentials): quota. If this is the case, warn about it. """ from google.auth import _cloud_sdk + if credentials.client_id == _cloud_sdk.CLOUD_SDK_CLIENT_ID: warnings.warn(_CLOUD_SDK_CREDENTIALS_WARNING) @@ -86,20 +89,21 @@ def _load_credentials_from_file(filename): """ if not os.path.exists(filename): raise exceptions.DefaultCredentialsError( - 'File {} was not found.'.format(filename)) + "File {} was not found.".format(filename) + ) - with io.open(filename, 'r') as file_obj: + with io.open(filename, "r") as file_obj: try: info = json.load(file_obj) except ValueError as caught_exc: new_exc = exceptions.DefaultCredentialsError( - 'File {} is not a valid json file.'.format(filename), - caught_exc) + "File {} is not a valid json file.".format(filename), caught_exc + ) six.raise_from(new_exc, caught_exc) # The type key should indicate that the file is either a service account # credentials file or an authorized user credentials file. - credential_type = info.get('type') + credential_type = info.get("type") if credential_type == _AUTHORIZED_USER_TYPE: from google.auth import _cloud_sdk @@ -107,8 +111,7 @@ def _load_credentials_from_file(filename): try: credentials = _cloud_sdk.load_authorized_user_credentials(info) except ValueError as caught_exc: - msg = 'Failed to load authorized user credentials from {}'.format( - filename) + msg = "Failed to load authorized user credentials from {}".format(filename) new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) six.raise_from(new_exc, caught_exc) # Authorized user credentials do not contain the project ID. @@ -119,20 +122,20 @@ def _load_credentials_from_file(filename): from google.oauth2 import service_account try: - credentials = ( - service_account.Credentials.from_service_account_info(info)) + credentials = service_account.Credentials.from_service_account_info(info) except ValueError as caught_exc: - msg = 'Failed to load service account credentials from {}'.format( - filename) + msg = "Failed to load service account credentials from {}".format(filename) new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) six.raise_from(new_exc, caught_exc) - return credentials, info.get('project_id') + return credentials, info.get("project_id") else: raise exceptions.DefaultCredentialsError( - 'The file {file} does not have a valid type. ' - 'Type is {type}, expected one of {valid_types}.'.format( - file=filename, type=credential_type, valid_types=_VALID_TYPES)) + "The file {file} does not have a valid type. " + "Type is {type}, expected one of {valid_types}.".format( + file=filename, type=credential_type, valid_types=_VALID_TYPES + ) + ) def _get_gcloud_sdk_credentials(): @@ -140,14 +143,12 @@ def _get_gcloud_sdk_credentials(): from google.auth import _cloud_sdk # Check if application default credentials exist. - credentials_filename = ( - _cloud_sdk.get_application_default_credentials_path()) + credentials_filename = _cloud_sdk.get_application_default_credentials_path() if not os.path.isfile(credentials_filename): return None, None - credentials, project_id = _load_credentials_from_file( - credentials_filename) + credentials, project_id = _load_credentials_from_file(credentials_filename) if not project_id: project_id = _cloud_sdk.get_project_id() @@ -162,7 +163,8 @@ def _get_explicit_environ_credentials(): if explicit_file is not None: credentials, project_id = _load_credentials_from_file( - os.environ[environment_vars.CREDENTIALS]) + os.environ[environment_vars.CREDENTIALS] + ) return credentials, project_id @@ -292,14 +294,15 @@ def default(scopes=None, request=None): from google.auth.credentials import with_scopes_if_required explicit_project_id = os.environ.get( - environment_vars.PROJECT, - os.environ.get(environment_vars.LEGACY_PROJECT)) + environment_vars.PROJECT, os.environ.get(environment_vars.LEGACY_PROJECT) + ) checkers = ( _get_explicit_environ_credentials, _get_gcloud_sdk_credentials, _get_gae_credentials, - lambda: _get_gce_credentials(request)) + lambda: _get_gce_credentials(request), + ) for checker in checkers: credentials, project_id = checker() @@ -308,10 +311,11 @@ def default(scopes=None, request=None): effective_project_id = explicit_project_id or project_id if not effective_project_id: _LOGGER.warning( - 'No project ID could be determined. Consider running ' - '`gcloud config set project` or setting the %s ' - 'environment variable', - environment_vars.PROJECT) + "No project ID could be determined. Consider running " + "`gcloud config set project` or setting the %s " + "environment variable", + environment_vars.PROJECT, + ) return credentials, effective_project_id raise exceptions.DefaultCredentialsError(_HELP_MESSAGE) diff --git a/packages/google-auth/google/auth/_helpers.py b/packages/google-auth/google/auth/_helpers.py index b32801a32039..ecb88ffda455 100644 --- a/packages/google-auth/google/auth/_helpers.py +++ b/packages/google-auth/google/auth/_helpers.py @@ -36,6 +36,7 @@ def copy_docstring(source_class): Callable: A decorator that will copy the docstring of the same named method in the source class to the decorated method. """ + def decorator(method): """Decorator implementation. @@ -49,12 +50,13 @@ def decorator(method): ValueError: if the method already has a docstring. """ if method.__doc__: - raise ValueError('Method already has a docstring.') + raise ValueError("Method already has a docstring.") source_method = getattr(source_class, method.__name__) method.__doc__ = source_method.__doc__ return method + return decorator @@ -79,7 +81,7 @@ def datetime_to_secs(value): return calendar.timegm(value.utctimetuple()) -def to_bytes(value, encoding='utf-8'): +def to_bytes(value, encoding="utf-8"): """Converts a string value to bytes, if necessary. Unfortunately, ``six.b`` is insufficient for this task since in @@ -97,12 +99,11 @@ def to_bytes(value, encoding='utf-8'): Raises: ValueError: If the value could not be converted to bytes. """ - result = (value.encode(encoding) - if isinstance(value, six.text_type) else value) + result = value.encode(encoding) if isinstance(value, six.text_type) else value if isinstance(result, six.binary_type): return result else: - raise ValueError('{0!r} could not be converted to bytes'.format(value)) + raise ValueError("{0!r} could not be converted to bytes".format(value)) def from_bytes(value): @@ -118,13 +119,11 @@ def from_bytes(value): Raises: ValueError: If the value could not be converted to unicode. """ - result = (value.decode('utf-8') - if isinstance(value, six.binary_type) else value) + result = value.decode("utf-8") if isinstance(value, six.binary_type) else value if isinstance(result, six.text_type): return result else: - raise ValueError( - '{0!r} could not be converted to unicode'.format(value)) + raise ValueError("{0!r} could not be converted to unicode".format(value)) def update_query(url, params, remove=None): @@ -163,9 +162,8 @@ def update_query(url, params, remove=None): query_params.update(params) # Remove any values specified in remove. query_params = { - key: value for key, value - in six.iteritems(query_params) - if key not in remove} + key: value for key, value in six.iteritems(query_params) if key not in remove + } # Re-encoded the query string. new_query = urllib.parse.urlencode(query_params, doseq=True) # Unsplit the url. @@ -183,7 +181,7 @@ def scopes_to_string(scopes): Returns: str: The scopes formatted as a single string. """ - return ' '.join(scopes) + return " ".join(scopes) def string_to_scopes(scopes): @@ -198,7 +196,7 @@ def string_to_scopes(scopes): if not scopes: return [] - return scopes.split(' ') + return scopes.split(" ") def padded_urlsafe_b64decode(value): @@ -213,7 +211,7 @@ def padded_urlsafe_b64decode(value): bytes: The decoded value """ b64string = to_bytes(value) - padded = b64string + b'=' * (-len(b64string) % 4) + padded = b64string + b"=" * (-len(b64string) % 4) return base64.urlsafe_b64decode(padded) @@ -231,4 +229,4 @@ def unpadded_urlsafe_b64encode(value): Returns: Union[str|bytes]: The encoded value """ - return base64.urlsafe_b64encode(value).rstrip(b'=') + return base64.urlsafe_b64encode(value).rstrip(b"=") diff --git a/packages/google-auth/google/auth/_oauth2client.py b/packages/google-auth/google/auth/_oauth2client.py index afe7dc45dc39..b14a38254191 100644 --- a/packages/google-auth/google/auth/_oauth2client.py +++ b/packages/google-auth/google/auth/_oauth2client.py @@ -34,18 +34,17 @@ import oauth2client.contrib.gce import oauth2client.service_account except ImportError as caught_exc: - six.raise_from( - ImportError('oauth2client is not installed.'), caught_exc) + six.raise_from(ImportError("oauth2client is not installed."), caught_exc) try: import oauth2client.contrib.appengine # pytype: disable=import-error + _HAS_APPENGINE = True except ImportError: _HAS_APPENGINE = False -_CONVERT_ERROR_TMPL = ( - 'Unable to convert {} to a google-auth credentials class.') +_CONVERT_ERROR_TMPL = "Unable to convert {} to a google-auth credentials class." def _convert_oauth2_credentials(credentials): @@ -65,7 +64,8 @@ def _convert_oauth2_credentials(credentials): token_uri=credentials.token_uri, client_id=credentials.client_id, client_secret=credentials.client_secret, - scopes=credentials.scopes) + scopes=credentials.scopes, + ) new_credentials._expires = credentials.token_expiry @@ -85,9 +85,8 @@ def _convert_service_account_credentials(credentials): google.oauth2.service_account.Credentials: The converted credentials. """ info = credentials.serialization_data.copy() - info['token_uri'] = credentials.token_uri - return google.oauth2.service_account.Credentials.from_service_account_info( - info) + info["token_uri"] = credentials.token_uri + return google.oauth2.service_account.Credentials.from_service_account_info(info) def _convert_gce_app_assertion_credentials(credentials): @@ -101,7 +100,8 @@ def _convert_gce_app_assertion_credentials(credentials): google.oauth2.service_account.Credentials: The converted credentials. """ return google.auth.compute_engine.Credentials( - service_account_email=credentials.service_account_email) + service_account_email=credentials.service_account_email + ) def _convert_appengine_app_assertion_credentials(credentials): @@ -117,24 +117,22 @@ def _convert_appengine_app_assertion_credentials(credentials): # pylint: disable=invalid-name return google.auth.app_engine.Credentials( scopes=_helpers.string_to_scopes(credentials.scope), - service_account_id=credentials.service_account_id) + service_account_id=credentials.service_account_id, + ) _CLASS_CONVERSION_MAP = { oauth2client.client.OAuth2Credentials: _convert_oauth2_credentials, oauth2client.client.GoogleCredentials: _convert_oauth2_credentials, - oauth2client.service_account.ServiceAccountCredentials: - _convert_service_account_credentials, - oauth2client.service_account._JWTAccessCredentials: - _convert_service_account_credentials, - oauth2client.contrib.gce.AppAssertionCredentials: - _convert_gce_app_assertion_credentials, + oauth2client.service_account.ServiceAccountCredentials: _convert_service_account_credentials, + oauth2client.service_account._JWTAccessCredentials: _convert_service_account_credentials, + oauth2client.contrib.gce.AppAssertionCredentials: _convert_gce_app_assertion_credentials, } if _HAS_APPENGINE: _CLASS_CONVERSION_MAP[ - oauth2client.contrib.appengine.AppAssertionCredentials] = ( - _convert_appengine_app_assertion_credentials) + oauth2client.contrib.appengine.AppAssertionCredentials + ] = _convert_appengine_app_assertion_credentials def convert(credentials): diff --git a/packages/google-auth/google/auth/_service_account_info.py b/packages/google-auth/google/auth/_service_account_info.py index dd39ea7b253b..790be92e1e25 100644 --- a/packages/google-auth/google/auth/_service_account_info.py +++ b/packages/google-auth/google/auth/_service_account_info.py @@ -47,8 +47,9 @@ def from_dict(data, require=None): if missing: raise ValueError( - 'Service account info was not in the expected format, missing ' - 'fields {}.'.format(', '.join(missing))) + "Service account info was not in the expected format, missing " + "fields {}.".format(", ".join(missing)) + ) # Create a signer. signer = crypt.RSASigner.from_service_account_info(data) @@ -68,6 +69,6 @@ def from_filename(filename, require=None): Tuple[ Mapping[str, str], google.auth.crypt.Signer ]: The verified info and a signer instance. """ - with io.open(filename, 'r', encoding='utf-8') as json_file: + with io.open(filename, "r", encoding="utf-8") as json_file: data = json.load(json_file) return data, from_dict(data, require=require) diff --git a/packages/google-auth/google/auth/app_engine.py b/packages/google-auth/google/auth/app_engine.py index 91ba8427d106..aec86a620663 100644 --- a/packages/google-auth/google/auth/app_engine.py +++ b/packages/google-auth/google/auth/app_engine.py @@ -73,13 +73,11 @@ def get_project_id(): # Pylint rightfully thinks EnvironmentError is OSError, but doesn't # realize it's a valid alias. if app_identity is None: - raise EnvironmentError( - 'The App Engine APIs are not available.') + raise EnvironmentError("The App Engine APIs are not available.") return app_identity.get_application_id() -class Credentials(credentials.Scoped, credentials.Signing, - credentials.Credentials): +class Credentials(credentials.Scoped, credentials.Signing, credentials.Credentials): """App Engine standard environment credentials. These credentials use the App Engine App Identity API to obtain access @@ -103,8 +101,7 @@ def __init__(self, scopes=None, service_account_id=None): # Pylint rightfully thinks EnvironmentError is OSError, but doesn't # realize it's a valid alias. if app_identity is None: - raise EnvironmentError( - 'The App Engine APIs are not available.') + raise EnvironmentError("The App Engine APIs are not available.") super(Credentials, self).__init__() self._scopes = scopes @@ -115,7 +112,8 @@ def __init__(self, scopes=None, service_account_id=None): def refresh(self, request): # pylint: disable=unused-argument token, ttl = app_identity.get_access_token( - self._scopes, self._service_account_id) + self._scopes, self._service_account_id + ) expiry = datetime.datetime.utcfromtimestamp(ttl) self.token, self.expiry = token, expiry @@ -139,7 +137,8 @@ def requires_scopes(self): @_helpers.copy_docstring(credentials.Scoped) def with_scopes(self, scopes): return self.__class__( - scopes=scopes, service_account_id=self._service_account_id) + scopes=scopes, service_account_id=self._service_account_id + ) @_helpers.copy_docstring(credentials.Signing) def sign_bytes(self, message): diff --git a/packages/google-auth/google/auth/compute_engine/__init__.py b/packages/google-auth/google/auth/compute_engine/__init__.py index ca31b4643b43..461f104ddd10 100644 --- a/packages/google-auth/google/auth/compute_engine/__init__.py +++ b/packages/google-auth/google/auth/compute_engine/__init__.py @@ -18,7 +18,4 @@ from google.auth.compute_engine.credentials import IDTokenCredentials -__all__ = [ - 'Credentials', - 'IDTokenCredentials', -] +__all__ = ["Credentials", "IDTokenCredentials"] diff --git a/packages/google-auth/google/auth/compute_engine/_metadata.py b/packages/google-auth/google/auth/compute_engine/_metadata.py index d8004bb249e5..f4fae7298a8d 100644 --- a/packages/google-auth/google/auth/compute_engine/_metadata.py +++ b/packages/google-auth/google/auth/compute_engine/_metadata.py @@ -32,21 +32,23 @@ _LOGGER = logging.getLogger(__name__) -_METADATA_ROOT = 'http://{}/computeMetadata/v1/'.format( - os.getenv(environment_vars.GCE_METADATA_ROOT, 'metadata.google.internal')) +_METADATA_ROOT = "http://{}/computeMetadata/v1/".format( + os.getenv(environment_vars.GCE_METADATA_ROOT, "metadata.google.internal") +) # This is used to ping the metadata server, it avoids the cost of a DNS # lookup. -_METADATA_IP_ROOT = 'http://{}'.format( - os.getenv(environment_vars.GCE_METADATA_IP, '169.254.169.254')) -_METADATA_FLAVOR_HEADER = 'metadata-flavor' -_METADATA_FLAVOR_VALUE = 'Google' +_METADATA_IP_ROOT = "http://{}".format( + os.getenv(environment_vars.GCE_METADATA_IP, "169.254.169.254") +) +_METADATA_FLAVOR_HEADER = "metadata-flavor" +_METADATA_FLAVOR_VALUE = "Google" _METADATA_HEADERS = {_METADATA_FLAVOR_HEADER: _METADATA_FLAVOR_VALUE} # Timeout in seconds to wait for the GCE metadata server when detecting the # GCE environment. try: - _METADATA_DEFAULT_TIMEOUT = int(os.getenv('GCE_METADATA_TIMEOUT', 3)) + _METADATA_DEFAULT_TIMEOUT = int(os.getenv("GCE_METADATA_TIMEOUT", 3)) except ValueError: # pragma: NO COVER _METADATA_DEFAULT_TIMEOUT = 3 @@ -74,16 +76,24 @@ def ping(request, timeout=_METADATA_DEFAULT_TIMEOUT, retry_count=3): while retries < retry_count: try: response = request( - url=_METADATA_IP_ROOT, method='GET', headers=_METADATA_HEADERS, - timeout=timeout) + url=_METADATA_IP_ROOT, + method="GET", + headers=_METADATA_HEADERS, + timeout=timeout, + ) metadata_flavor = response.headers.get(_METADATA_FLAVOR_HEADER) - return (response.status == http_client.OK and - metadata_flavor == _METADATA_FLAVOR_VALUE) + return ( + response.status == http_client.OK + and metadata_flavor == _METADATA_FLAVOR_VALUE + ) except exceptions.TransportError: - _LOGGER.info('Compute Engine Metadata server unavailable on' - 'attempt %s of %s', retries+1, retry_count) + _LOGGER.info( + "Compute Engine Metadata server unavailable on" "attempt %s of %s", + retries + 1, + retry_count, + ) retries += 1 return False @@ -115,29 +125,33 @@ def get(request, path, root=_METADATA_ROOT, recursive=False): query_params = {} if recursive: - query_params['recursive'] = 'true' + query_params["recursive"] = "true" url = _helpers.update_query(base_url, query_params) - response = request(url=url, method='GET', headers=_METADATA_HEADERS) + response = request(url=url, method="GET", headers=_METADATA_HEADERS) if response.status == http_client.OK: content = _helpers.from_bytes(response.data) - if response.headers['content-type'] == 'application/json': + if response.headers["content-type"] == "application/json": try: return json.loads(content) except ValueError as caught_exc: new_exc = exceptions.TransportError( - 'Received invalid JSON from the Google Compute Engine' - 'metadata service: {:.20}'.format(content)) + "Received invalid JSON from the Google Compute Engine" + "metadata service: {:.20}".format(content) + ) six.raise_from(new_exc, caught_exc) else: return content else: raise exceptions.TransportError( - 'Failed to retrieve {} from the Google Compute Engine' - 'metadata service. Status: {} Response:\n{}'.format( - url, response.status, response.data), response) + "Failed to retrieve {} from the Google Compute Engine" + "metadata service. Status: {} Response:\n{}".format( + url, response.status, response.data + ), + response, + ) def get_project_id(request): @@ -154,10 +168,10 @@ def get_project_id(request): google.auth.exceptions.TransportError: if an error occurred while retrieving metadata. """ - return get(request, 'project/project-id') + return get(request, "project/project-id") -def get_service_account_info(request, service_account='default'): +def get_service_account_info(request, service_account="default"): """Get information about a service account from the metadata server. Args: @@ -182,11 +196,12 @@ def get_service_account_info(request, service_account='default'): """ return get( request, - 'instance/service-accounts/{0}/'.format(service_account), - recursive=True) + "instance/service-accounts/{0}/".format(service_account), + recursive=True, + ) -def get_service_account_token(request, service_account='default'): +def get_service_account_token(request, service_account="default"): """Get the OAuth 2.0 access token for a service account. Args: @@ -204,8 +219,9 @@ def get_service_account_token(request, service_account='default'): retrieving metadata. """ token_json = get( - request, - 'instance/service-accounts/{0}/token'.format(service_account)) + request, "instance/service-accounts/{0}/token".format(service_account) + ) token_expiry = _helpers.utcnow() + datetime.timedelta( - seconds=token_json['expires_in']) - return token_json['access_token'], token_expiry + seconds=token_json["expires_in"] + ) + return token_json["access_token"], token_expiry diff --git a/packages/google-auth/google/auth/compute_engine/credentials.py b/packages/google-auth/google/auth/compute_engine/credentials.py index d9c6e26d6d8f..eeb92f5159a2 100644 --- a/packages/google-auth/google/auth/compute_engine/credentials.py +++ b/packages/google-auth/google/auth/compute_engine/credentials.py @@ -54,7 +54,7 @@ class Credentials(credentials.ReadOnlyScoped, credentials.Credentials): https://cloud.google.com/compute/docs/authentication#using """ - def __init__(self, service_account_email='default'): + def __init__(self, service_account_email="default"): """ Args: service_account_email (str): The service account email to use, or @@ -74,11 +74,11 @@ def _retrieve_info(self, request): HTTP requests. """ info = _metadata.get_service_account_info( - request, - service_account=self._service_account_email) + request, service_account=self._service_account_email + ) - self._service_account_email = info['email'] - self._scopes = info['scopes'] + self._service_account_email = info["email"] + self._scopes = info["scopes"] def refresh(self, request): """Refresh the access token and scopes. @@ -95,8 +95,8 @@ def refresh(self, request): try: self._retrieve_info(request) self.token, self.expiry = _metadata.get_service_account_token( - request, - service_account=self._service_account_email) + request, service_account=self._service_account_email + ) except exceptions.TransportError as caught_exc: new_exc = exceptions.RefreshError(caught_exc) six.raise_from(new_exc, caught_exc) @@ -117,7 +117,7 @@ def requires_scopes(self): _DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds -_DEFAULT_TOKEN_URI = 'https://www.googleapis.com/oauth2/v4/token' +_DEFAULT_TOKEN_URI = "https://www.googleapis.com/oauth2/v4/token" class IDTokenCredentials(credentials.Credentials, credentials.Signing): @@ -128,10 +128,15 @@ class IDTokenCredentials(credentials.Credentials, credentials.Signing): In order for this to work, the GCE instance must have been started with a service account that has access to the IAM Cloud API. """ - def __init__(self, request, target_audience, - token_uri=_DEFAULT_TOKEN_URI, - additional_claims=None, - service_account_email=None): + + def __init__( + self, + request, + target_audience, + token_uri=_DEFAULT_TOKEN_URI, + additional_claims=None, + service_account_email=None, + ): """ Args: request (google.auth.transport.Request): The object used to make @@ -150,13 +155,14 @@ def __init__(self, request, target_audience, if service_account_email is None: sa_info = _metadata.get_service_account_info(request) - service_account_email = sa_info['email'] + service_account_email = sa_info["email"] self._service_account_email = service_account_email self._signer = iam.Signer( request=request, credentials=Credentials(), - service_account_email=service_account_email) + service_account_email=service_account_email, + ) self._token_uri = token_uri self._target_audience = target_audience @@ -181,7 +187,8 @@ def with_target_audience(self, target_audience): service_account_email=self._service_account_email, token_uri=self._token_uri, target_audience=target_audience, - additional_claims=self._additional_claims.copy()) + additional_claims=self._additional_claims.copy(), + ) def _make_authorization_grant_assertion(self): """Create the OAuth 2.0 assertion. @@ -195,15 +202,15 @@ def _make_authorization_grant_assertion(self): expiry = now + lifetime payload = { - 'iat': _helpers.datetime_to_secs(now), - 'exp': _helpers.datetime_to_secs(expiry), + "iat": _helpers.datetime_to_secs(now), + "exp": _helpers.datetime_to_secs(expiry), # The issuer must be the service account email. - 'iss': self.service_account_email, + "iss": self.service_account_email, # The audience must be the auth token endpoint's URI - 'aud': self._token_uri, + "aud": self._token_uri, # The target audience specifies which service the ID token is # intended for. - 'target_audience': self._target_audience + "target_audience": self._target_audience, } payload.update(self._additional_claims) @@ -216,7 +223,8 @@ def _make_authorization_grant_assertion(self): def refresh(self, request): assertion = self._make_authorization_grant_assertion() access_token, expiry, _ = _client.id_token_jwt_grant( - request, self._token_uri, assertion) + request, self._token_uri, assertion + ) self.token = access_token self.expiry = expiry diff --git a/packages/google-auth/google/auth/credentials.py b/packages/google-auth/google/auth/credentials.py index 8ff1f0251b16..81bbd0316804 100644 --- a/packages/google-auth/google/auth/credentials.py +++ b/packages/google-auth/google/auth/credentials.py @@ -41,6 +41,7 @@ class Credentials(object): construction. Some classes will provide mechanisms to copy the credentials with modifications such as :meth:`ScopedCredentials.with_scopes`. """ + def __init__(self): self.token = None """str: The bearer token that can be used in HTTP headers to make @@ -88,7 +89,7 @@ def refresh(self, request): """ # pylint: disable=missing-raises-doc # (pylint doesn't recognize that this is abstract) - raise NotImplementedError('Refresh must be implemented') + raise NotImplementedError("Refresh must be implemented") def apply(self, headers, token=None): """Apply the token to the authentication header. @@ -98,8 +99,9 @@ def apply(self, headers, token=None): token (Optional[str]): If specified, overrides the current access token. """ - headers['authorization'] = 'Bearer {}'.format( - _helpers.from_bytes(token or self.token)) + headers["authorization"] = "Bearer {}".format( + _helpers.from_bytes(token or self.token) + ) def before_request(self, request, method, url, headers): """Performs credential-specific before request logic. @@ -189,6 +191,7 @@ class ReadOnlyScoped(object): .. _RFC6749 Section 3.3: https://tools.ietf.org/html/rfc6749#section-3.3 """ + def __init__(self): super(ReadOnlyScoped, self).__init__() self._scopes = None @@ -247,6 +250,7 @@ class Scoped(ReadOnlyScoped): .. _RFC6749 Section 3.3: https://tools.ietf.org/html/rfc6749#section-3.3 """ + @abc.abstractmethod def with_scopes(self, scopes): """Create a copy of these credentials with the specified scopes. @@ -260,7 +264,7 @@ def with_scopes(self, scopes): This can be avoided by checking :attr:`requires_scopes` before calling this method. """ - raise NotImplementedError('This class does not require scoping.') + raise NotImplementedError("This class does not require scoping.") def with_scopes_if_required(credentials, scopes): @@ -305,18 +309,18 @@ def sign_bytes(self, message): """ # pylint: disable=missing-raises-doc,redundant-returns-doc # (pylint doesn't recognize that this is abstract) - raise NotImplementedError('Sign bytes must be implemented.') + raise NotImplementedError("Sign bytes must be implemented.") @abc.abstractproperty def signer_email(self): """Optional[str]: An email address that identifies the signer.""" # pylint: disable=missing-raises-doc # (pylint doesn't recognize that this is abstract) - raise NotImplementedError('Signer email must be implemented.') + raise NotImplementedError("Signer email must be implemented.") @abc.abstractproperty def signer(self): """google.auth.crypt.Signer: The signer used to sign bytes.""" # pylint: disable=missing-raises-doc # (pylint doesn't recognize that this is abstract) - raise NotImplementedError('Signer must be implemented.') + raise NotImplementedError("Signer must be implemented.") diff --git a/packages/google-auth/google/auth/crypt/__init__.py b/packages/google-auth/google/auth/crypt/__init__.py index 7baa206ecf45..bf66e710053e 100644 --- a/packages/google-auth/google/auth/crypt/__init__.py +++ b/packages/google-auth/google/auth/crypt/__init__.py @@ -39,12 +39,7 @@ from google.auth.crypt import rsa -__all__ = [ - 'RSASigner', - 'RSAVerifier', - 'Signer', - 'Verifier', -] +__all__ = ["RSASigner", "RSAVerifier", "Signer", "Verifier"] # Aliases to maintain the v1.0.0 interface, as the crypt module was split # into submodules. diff --git a/packages/google-auth/google/auth/crypt/_cryptography_rsa.py b/packages/google-auth/google/auth/crypt/_cryptography_rsa.py index 87076b0ab9cf..285cf5ccf0d7 100644 --- a/packages/google-auth/google/auth/crypt/_cryptography_rsa.py +++ b/packages/google-auth/google/auth/crypt/_cryptography_rsa.py @@ -31,18 +31,18 @@ from google.auth.crypt import base _IMPORT_ERROR_MSG = ( - 'cryptography>=1.4.0 is required to use cryptography-based RSA ' - 'implementation.') + "cryptography>=1.4.0 is required to use cryptography-based RSA " "implementation." +) try: # pragma: NO COVER - release = pkg_resources.get_distribution('cryptography').parsed_version - if release < pkg_resources.parse_version('1.4.0'): + release = pkg_resources.get_distribution("cryptography").parsed_version + if release < pkg_resources.parse_version("1.4.0"): raise ImportError(_IMPORT_ERROR_MSG) except pkg_resources.DistributionNotFound: # pragma: NO COVER raise ImportError(_IMPORT_ERROR_MSG) -_CERTIFICATE_MARKER = b'-----BEGIN CERTIFICATE-----' +_CERTIFICATE_MARKER = b"-----BEGIN CERTIFICATE-----" _BACKEND = backends.default_backend() _PADDING = padding.PKCS1v15() _SHA256 = hashes.SHA256() @@ -88,12 +88,12 @@ def from_string(cls, public_key): if _CERTIFICATE_MARKER in public_key_data: cert = cryptography.x509.load_pem_x509_certificate( - public_key_data, _BACKEND) + public_key_data, _BACKEND + ) pubkey = cert.public_key() else: - pubkey = serialization.load_pem_public_key( - public_key_data, _BACKEND) + pubkey = serialization.load_pem_public_key(public_key_data, _BACKEND) return cls(pubkey) @@ -122,8 +122,7 @@ def key_id(self): @_helpers.copy_docstring(base.Signer) def sign(self, message): message = _helpers.to_bytes(message) - return self._key.sign( - message, _PADDING, _SHA256) + return self._key.sign(message, _PADDING, _SHA256) @classmethod def from_string(cls, key, key_id=None): @@ -145,5 +144,6 @@ def from_string(cls, key, key_id=None): """ key = _helpers.to_bytes(key) private_key = serialization.load_pem_private_key( - key, password=None, backend=_BACKEND) + key, password=None, backend=_BACKEND + ) return cls(private_key, key_id=key_id) diff --git a/packages/google-auth/google/auth/crypt/_python_rsa.py b/packages/google-auth/google/auth/crypt/_python_rsa.py index 44aa79191317..d53991c38348 100644 --- a/packages/google-auth/google/auth/crypt/_python_rsa.py +++ b/packages/google-auth/google/auth/crypt/_python_rsa.py @@ -32,11 +32,9 @@ from google.auth.crypt import base _POW2 = (128, 64, 32, 16, 8, 4, 2, 1) -_CERTIFICATE_MARKER = b'-----BEGIN CERTIFICATE-----' -_PKCS1_MARKER = ('-----BEGIN RSA PRIVATE KEY-----', - '-----END RSA PRIVATE KEY-----') -_PKCS8_MARKER = ('-----BEGIN PRIVATE KEY-----', - '-----END PRIVATE KEY-----') +_CERTIFICATE_MARKER = b"-----BEGIN CERTIFICATE-----" +_PKCS1_MARKER = ("-----BEGIN RSA PRIVATE KEY-----", "-----END RSA PRIVATE KEY-----") +_PKCS8_MARKER = ("-----BEGIN PRIVATE KEY-----", "-----END PRIVATE KEY-----") _PKCS8_SPEC = PrivateKeyInfo() @@ -55,9 +53,8 @@ def _bit_list_to_bytes(bit_list): num_bits = len(bit_list) byte_vals = bytearray() for start in six.moves.xrange(0, num_bits, 8): - curr_bits = bit_list[start:start + 8] - char_val = sum( - val * digit for val, digit in six.moves.zip(_POW2, curr_bits)) + curr_bits = bit_list[start : start + 8] + char_val = sum(val * digit for val, digit in six.moves.zip(_POW2, curr_bits)) byte_vals.append(char_val) return bytes(byte_vals) @@ -101,16 +98,16 @@ def from_string(cls, public_key): # If this is a certificate, extract the public key info. if is_x509_cert: - der = rsa.pem.load_pem(public_key, 'CERTIFICATE') + der = rsa.pem.load_pem(public_key, "CERTIFICATE") asn1_cert, remaining = decoder.decode(der, asn1Spec=Certificate()) - if remaining != b'': - raise ValueError('Unused bytes', remaining) + if remaining != b"": + raise ValueError("Unused bytes", remaining) - cert_info = asn1_cert['tbsCertificate']['subjectPublicKeyInfo'] - key_bytes = _bit_list_to_bytes(cert_info['subjectPublicKey']) - pubkey = rsa.PublicKey.load_pkcs1(key_bytes, 'DER') + cert_info = asn1_cert["tbsCertificate"]["subjectPublicKeyInfo"] + key_bytes = _bit_list_to_bytes(cert_info["subjectPublicKey"]) + pubkey = rsa.PublicKey.load_pkcs1(key_bytes, "DER") else: - pubkey = rsa.PublicKey.load_pkcs1(public_key, 'PEM') + pubkey = rsa.PublicKey.load_pkcs1(public_key, "PEM") return cls(pubkey) @@ -136,7 +133,7 @@ def key_id(self): @_helpers.copy_docstring(base.Signer) def sign(self, message): message = _helpers.to_bytes(message) - return rsa.pkcs1.sign(message, self._key, 'SHA-256') + return rsa.pkcs1.sign(message, self._key, "SHA-256") @classmethod def from_string(cls, key, key_id=None): @@ -155,22 +152,22 @@ def from_string(cls, key, key_id=None): """ key = _helpers.from_bytes(key) # PEM expects str in Python 3 marker_id, key_bytes = pem.readPemBlocksFromFile( - six.StringIO(key), _PKCS1_MARKER, _PKCS8_MARKER) + six.StringIO(key), _PKCS1_MARKER, _PKCS8_MARKER + ) # Key is in pkcs1 format. if marker_id == 0: - private_key = rsa.key.PrivateKey.load_pkcs1( - key_bytes, format='DER') + private_key = rsa.key.PrivateKey.load_pkcs1(key_bytes, format="DER") # Key is in pkcs8. elif marker_id == 1: - key_info, remaining = decoder.decode( - key_bytes, asn1Spec=_PKCS8_SPEC) - if remaining != b'': - raise ValueError('Unused bytes', remaining) - private_key_info = key_info.getComponentByName('privateKey') + key_info, remaining = decoder.decode(key_bytes, asn1Spec=_PKCS8_SPEC) + if remaining != b"": + raise ValueError("Unused bytes", remaining) + private_key_info = key_info.getComponentByName("privateKey") private_key = rsa.key.PrivateKey.load_pkcs1( - private_key_info.asOctets(), format='DER') + private_key_info.asOctets(), format="DER" + ) else: - raise ValueError('No key could be detected.') + raise ValueError("No key could be detected.") return cls(private_key, key_id=key_id) diff --git a/packages/google-auth/google/auth/crypt/base.py b/packages/google-auth/google/auth/crypt/base.py index c6c042721df7..ceb6e9c060f7 100644 --- a/packages/google-auth/google/auth/crypt/base.py +++ b/packages/google-auth/google/auth/crypt/base.py @@ -21,8 +21,8 @@ import six -_JSON_FILE_PRIVATE_KEY = 'private_key' -_JSON_FILE_PRIVATE_KEY_ID = 'private_key_id' +_JSON_FILE_PRIVATE_KEY = "private_key" +_JSON_FILE_PRIVATE_KEY_ID = "private_key_id" @six.add_metaclass(abc.ABCMeta) @@ -43,7 +43,7 @@ def verify(self, message, signature): """ # pylint: disable=missing-raises-doc,redundant-returns-doc # (pylint doesn't recognize that this is abstract) - raise NotImplementedError('Verify must be implemented') + raise NotImplementedError("Verify must be implemented") @six.add_metaclass(abc.ABCMeta) @@ -53,7 +53,7 @@ class Signer(object): @abc.abstractproperty def key_id(self): """Optional[str]: The key ID used to identify this private key.""" - raise NotImplementedError('Key id must be implemented') + raise NotImplementedError("Key id must be implemented") @abc.abstractmethod def sign(self, message): @@ -67,7 +67,7 @@ def sign(self, message): """ # pylint: disable=missing-raises-doc,redundant-returns-doc # (pylint doesn't recognize that this is abstract) - raise NotImplementedError('Sign must be implemented') + raise NotImplementedError("Sign must be implemented") @six.add_metaclass(abc.ABCMeta) @@ -88,7 +88,7 @@ def from_string(cls, key, key_id=None): Raises: ValueError: If the key cannot be parsed. """ - raise NotImplementedError('from_string must be implemented') + raise NotImplementedError("from_string must be implemented") @classmethod def from_service_account_info(cls, info): @@ -107,12 +107,12 @@ def from_service_account_info(cls, info): """ if _JSON_FILE_PRIVATE_KEY not in info: raise ValueError( - 'The private_key field was not found in the service account ' - 'info.') + "The private_key field was not found in the service account " "info." + ) return cls.from_string( - info[_JSON_FILE_PRIVATE_KEY], - info.get(_JSON_FILE_PRIVATE_KEY_ID)) + info[_JSON_FILE_PRIVATE_KEY], info.get(_JSON_FILE_PRIVATE_KEY_ID) + ) @classmethod def from_service_account_file(cls, filename): @@ -125,7 +125,7 @@ def from_service_account_file(cls, filename): Returns: google.auth.crypt.Signer: The constructed signer. """ - with io.open(filename, 'r', encoding='utf-8') as json_file: + with io.open(filename, "r", encoding="utf-8") as json_file: data = json.load(json_file) return cls.from_service_account_info(data) diff --git a/packages/google-auth/google/auth/environment_vars.py b/packages/google-auth/google/auth/environment_vars.py index 0110e6a3c5bc..96d2ffc28752 100644 --- a/packages/google-auth/google/auth/environment_vars.py +++ b/packages/google-auth/google/auth/environment_vars.py @@ -15,35 +15,35 @@ """Environment variables used by :mod:`google.auth`.""" -PROJECT = 'GOOGLE_CLOUD_PROJECT' +PROJECT = "GOOGLE_CLOUD_PROJECT" """Environment variable defining default project. This used by :func:`google.auth.default` to explicitly set a project ID. This environment variable is also used by the Google Cloud Python Library. """ -LEGACY_PROJECT = 'GCLOUD_PROJECT' +LEGACY_PROJECT = "GCLOUD_PROJECT" """Previously used environment variable defining the default project. This environment variable is used instead of the current one in some situations (such as Google App Engine). """ -CREDENTIALS = 'GOOGLE_APPLICATION_CREDENTIALS' +CREDENTIALS = "GOOGLE_APPLICATION_CREDENTIALS" """Environment variable defining the location of Google application default credentials.""" # The environment variable name which can replace ~/.config if set. -CLOUD_SDK_CONFIG_DIR = 'CLOUDSDK_CONFIG' +CLOUD_SDK_CONFIG_DIR = "CLOUDSDK_CONFIG" """Environment variable defines the location of Google Cloud SDK's config files.""" # These two variables allow for customization of the addresses used when # contacting the GCE metadata service. -GCE_METADATA_ROOT = 'GCE_METADATA_ROOT' +GCE_METADATA_ROOT = "GCE_METADATA_ROOT" """Environment variable providing an alternate hostname or host:port to be used for GCE metadata requests.""" -GCE_METADATA_IP = 'GCE_METADATA_IP' +GCE_METADATA_IP = "GCE_METADATA_IP" """Environment variable providing an alternate ip:port to be used for ip-only GCE metadata requests.""" diff --git a/packages/google-auth/google/auth/iam.py b/packages/google-auth/google/auth/iam.py index e091e47f371d..a43872658ee3 100644 --- a/packages/google-auth/google/auth/iam.py +++ b/packages/google-auth/google/auth/iam.py @@ -28,9 +28,8 @@ from google.auth import crypt from google.auth import exceptions -_IAM_API_ROOT_URI = 'https://iam.googleapis.com/v1' -_SIGN_BLOB_URI = ( - _IAM_API_ROOT_URI + '/projects/-/serviceAccounts/{}:signBlob?alt=json') +_IAM_API_ROOT_URI = "https://iam.googleapis.com/v1" +_SIGN_BLOB_URI = _IAM_API_ROOT_URI + "/projects/-/serviceAccounts/{}:signBlob?alt=json" class Signer(crypt.Signer): @@ -68,23 +67,20 @@ def _make_signing_request(self, message): """Makes a request to the API signBlob API.""" message = _helpers.to_bytes(message) - method = 'POST' + method = "POST" url = _SIGN_BLOB_URI.format(self._service_account_email) headers = {} - body = json.dumps({ - 'bytesToSign': base64.b64encode(message).decode('utf-8'), - }) + body = json.dumps({"bytesToSign": base64.b64encode(message).decode("utf-8")}) self._credentials.before_request(self._request, method, url, headers) - response = self._request( - url=url, method=method, body=body, headers=headers) + response = self._request(url=url, method=method, body=body, headers=headers) if response.status != http_client.OK: raise exceptions.TransportError( - 'Error calling the IAM signBytes API: {}'.format( - response.data)) + "Error calling the IAM signBytes API: {}".format(response.data) + ) - return json.loads(response.data.decode('utf-8')) + return json.loads(response.data.decode("utf-8")) @property def key_id(self): @@ -99,4 +95,4 @@ def key_id(self): @_helpers.copy_docstring(crypt.Signer) def sign(self, message): response = self._make_signing_request(message) - return base64.b64decode(response['signature']) + return base64.b64decode(response["signature"]) diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index bb2bbf26a139..70fa5dc9c3cb 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -41,22 +41,28 @@ _DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds -_IAM_SCOPE = ['https://www.googleapis.com/auth/iam'] +_IAM_SCOPE = ["https://www.googleapis.com/auth/iam"] -_IAM_ENDPOINT = ('https://iamcredentials.googleapis.com/v1/projects/-' + - '/serviceAccounts/{}:generateAccessToken') +_IAM_ENDPOINT = ( + "https://iamcredentials.googleapis.com/v1/projects/-" + + "/serviceAccounts/{}:generateAccessToken" +) -_IAM_SIGN_ENDPOINT = ('https://iamcredentials.googleapis.com/v1/projects/-' + - '/serviceAccounts/{}:signBlob') +_IAM_SIGN_ENDPOINT = ( + "https://iamcredentials.googleapis.com/v1/projects/-" + + "/serviceAccounts/{}:signBlob" +) -_IAM_IDTOKEN_ENDPOINT = ('https://iamcredentials.googleapis.com/v1/' + - 'projects/-/serviceAccounts/{}:generateIdToken') +_IAM_IDTOKEN_ENDPOINT = ( + "https://iamcredentials.googleapis.com/v1/" + + "projects/-/serviceAccounts/{}:generateIdToken" +) -_REFRESH_ERROR = 'Unable to acquire impersonated credentials' +_REFRESH_ERROR = "Unable to acquire impersonated credentials" _DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds -_DEFAULT_TOKEN_URI = 'https://oauth2.googleapis.com/token' +_DEFAULT_TOKEN_URI = "https://oauth2.googleapis.com/token" def _make_iam_token_request(request, principal, headers, body): @@ -80,34 +86,31 @@ def _make_iam_token_request(request, principal, headers, body): body = json.dumps(body) - response = request( - url=iam_endpoint, - method='POST', - headers=headers, - body=body) + response = request(url=iam_endpoint, method="POST", headers=headers, body=body) - response_body = response.data.decode('utf-8') + response_body = response.data.decode("utf-8") if response.status != http_client.OK: exceptions.RefreshError(_REFRESH_ERROR, response_body) try: - token_response = json.loads(response.data.decode('utf-8')) - token = token_response['accessToken'] - expiry = datetime.strptime( - token_response['expireTime'], '%Y-%m-%dT%H:%M:%SZ') + token_response = json.loads(response.data.decode("utf-8")) + token = token_response["accessToken"] + expiry = datetime.strptime(token_response["expireTime"], "%Y-%m-%dT%H:%M:%SZ") return token, expiry except (KeyError, ValueError) as caught_exc: new_exc = exceptions.RefreshError( - '{}: No access token or invalid expiration in response.'.format( - _REFRESH_ERROR), - response_body) + "{}: No access token or invalid expiration in response.".format( + _REFRESH_ERROR + ), + response_body, + ) six.raise_from(new_exc, caught_exc) -class Credentials(credentials.Credentials, credentials.Signing): +class Credentials(credentials.Credentials, credentials.Signing): """This module defines impersonated credentials which are essentially impersonated identities. @@ -169,9 +172,14 @@ class Credentials(credentials.Credentials, credentials.Signing): print(bucket.name) """ - def __init__(self, source_credentials, target_principal, - target_scopes, delegates=None, - lifetime=_DEFAULT_TOKEN_LIFETIME_SECS): + def __init__( + self, + source_credentials, + target_principal, + target_scopes, + delegates=None, + lifetime=_DEFAULT_TOKEN_LIFETIME_SECS, + ): """ Args: source_credentials (google.auth.Credentials): The source credential @@ -228,12 +236,10 @@ def _update_token(self, request): body = { "delegates": self._delegates, "scope": self._target_scopes, - "lifetime": str(self._lifetime) + "s" + "lifetime": str(self._lifetime) + "s", } - headers = { - 'Content-Type': 'application/json', - } + headers = {"Content-Type": "application/json"} # Apply the source credentials authentication info. self._source_credentials.apply(headers) @@ -242,29 +248,24 @@ def _update_token(self, request): request=request, principal=self._target_principal, headers=headers, - body=body) + body=body, + ) def sign_bytes(self, message): iam_sign_endpoint = _IAM_SIGN_ENDPOINT.format(self._target_principal) - body = { - "payload": base64.b64encode(message), - "delegates": self._delegates - } + body = {"payload": base64.b64encode(message), "delegates": self._delegates} - headers = { - 'Content-Type': 'application/json', - } + headers = {"Content-Type": "application/json"} authed_session = AuthorizedSession(self._source_credentials) response = authed_session.post( - url=iam_sign_endpoint, - headers=headers, - json=body) + url=iam_sign_endpoint, headers=headers, json=body + ) - return base64.b64decode(response.json()['signedBlob']) + return base64.b64decode(response.json()["signedBlob"]) @property def signer_email(self): @@ -283,8 +284,8 @@ class IDTokenCredentials(credentials.Credentials): """Open ID Connect ID Token-based service account credentials. """ - def __init__(self, target_credentials, - target_audience=None, include_email=False): + + def __init__(self, target_credentials, target_audience=None, include_email=False): """ Args: target_credentials (google.auth.Credentials): The target @@ -294,57 +295,54 @@ def __init__(self, target_credentials, """ super(IDTokenCredentials, self).__init__() - if not isinstance(target_credentials, - Credentials): - raise exceptions.GoogleAuthError("Provided Credential must be " - "impersonated_credentials") + if not isinstance(target_credentials, Credentials): + raise exceptions.GoogleAuthError( + "Provided Credential must be " "impersonated_credentials" + ) self._target_credentials = target_credentials self._target_audience = target_audience self._include_email = include_email - def from_credentials(self, target_credentials, - target_audience=None): + def from_credentials(self, target_credentials, target_audience=None): return self.__class__( - target_credentials=self._target_credentials, - target_audience=target_audience) + target_credentials=self._target_credentials, target_audience=target_audience + ) def with_target_audience(self, target_audience): return self.__class__( - target_credentials=self._target_credentials, - target_audience=target_audience) + target_credentials=self._target_credentials, target_audience=target_audience + ) def with_include_email(self, include_email): return self.__class__( target_credentials=self._target_credentials, target_audience=self._target_audience, - include_email=include_email) + include_email=include_email, + ) @_helpers.copy_docstring(credentials.Credentials) def refresh(self, request): - iam_sign_endpoint = _IAM_IDTOKEN_ENDPOINT.format(self. - _target_credentials. - signer_email) + iam_sign_endpoint = _IAM_IDTOKEN_ENDPOINT.format( + self._target_credentials.signer_email + ) body = { "audience": self._target_audience, "delegates": self._target_credentials._delegates, - "includeEmail": self._include_email + "includeEmail": self._include_email, } - headers = { - 'Content-Type': 'application/json', - } + headers = {"Content-Type": "application/json"} - authed_session = AuthorizedSession(self._target_credentials. - _source_credentials) + authed_session = AuthorizedSession(self._target_credentials._source_credentials) response = authed_session.post( url=iam_sign_endpoint, headers=headers, - data=json.dumps(body).encode('utf-8')) + data=json.dumps(body).encode("utf-8"), + ) - id_token = response.json()['token'] + id_token = response.json()["token"] self.token = id_token - self.expiry = datetime.fromtimestamp(jwt.decode(id_token, - verify=False)['exp']) + self.expiry = datetime.fromtimestamp(jwt.decode(id_token, verify=False)["exp"]) diff --git a/packages/google-auth/google/auth/jwt.py b/packages/google-auth/google/auth/jwt.py index d63c50bb9f39..a30c575b2c3a 100644 --- a/packages/google-auth/google/auth/jwt.py +++ b/packages/google-auth/google/auth/jwt.py @@ -79,36 +79,30 @@ def encode(signer, payload, header=None, key_id=None): if key_id is None: key_id = signer.key_id - header.update({'typ': 'JWT', 'alg': 'RS256'}) + header.update({"typ": "JWT", "alg": "RS256"}) if key_id is not None: - header['kid'] = key_id + header["kid"] = key_id segments = [ - _helpers.unpadded_urlsafe_b64encode( - json.dumps(header).encode('utf-8') - ), - _helpers.unpadded_urlsafe_b64encode( - json.dumps(payload).encode('utf-8') - ), + _helpers.unpadded_urlsafe_b64encode(json.dumps(header).encode("utf-8")), + _helpers.unpadded_urlsafe_b64encode(json.dumps(payload).encode("utf-8")), ] - signing_input = b'.'.join(segments) + signing_input = b".".join(segments) signature = signer.sign(signing_input) - segments.append( - _helpers.unpadded_urlsafe_b64encode(signature) - ) + segments.append(_helpers.unpadded_urlsafe_b64encode(signature)) - return b'.'.join(segments) + return b".".join(segments) def _decode_jwt_segment(encoded_section): """Decodes a single JWT segment.""" section_bytes = _helpers.padded_urlsafe_b64decode(encoded_section) try: - return json.loads(section_bytes.decode('utf-8')) + return json.loads(section_bytes.decode("utf-8")) except ValueError as caught_exc: - new_exc = ValueError('Can\'t parse segment: {0}'.format(section_bytes)) + new_exc = ValueError("Can't parse segment: {0}".format(section_bytes)) six.raise_from(new_exc, caught_exc) @@ -127,12 +121,11 @@ def _unverified_decode(token): """ token = _helpers.to_bytes(token) - if token.count(b'.') != 2: - raise ValueError( - 'Wrong number of segments in token: {0}'.format(token)) + if token.count(b".") != 2: + raise ValueError("Wrong number of segments in token: {0}".format(token)) - encoded_header, encoded_payload, signature = token.split(b'.') - signed_section = encoded_header + b'.' + encoded_payload + encoded_header, encoded_payload, signature = token.split(b".") + signed_section = encoded_header + b"." + encoded_payload signature = _helpers.padded_urlsafe_b64decode(signature) # Parse segments @@ -172,26 +165,25 @@ def _verify_iat_and_exp(payload): now = _helpers.datetime_to_secs(_helpers.utcnow()) # Make sure the iat and exp claims are present. - for key in ('iat', 'exp'): + for key in ("iat", "exp"): if key not in payload: - raise ValueError( - 'Token does not contain required claim {}'.format(key)) + raise ValueError("Token does not contain required claim {}".format(key)) # Make sure the token wasn't issued in the future. - iat = payload['iat'] + iat = payload["iat"] # Err on the side of accepting a token that is slightly early to account # for clock skew. earliest = iat - _helpers.CLOCK_SKEW_SECS if now < earliest: - raise ValueError('Token used too early, {} < {}'.format(now, iat)) + raise ValueError("Token used too early, {} < {}".format(now, iat)) # Make sure the token wasn't issued in the past. - exp = payload['exp'] + exp = payload["exp"] # Err on the side of accepting a token that is slightly out of date # to account for clow skew. latest = exp + _helpers.CLOCK_SKEW_SECS if latest < now: - raise ValueError('Token expired, {} < {}'.format(latest, now)) + raise ValueError("Token expired, {} < {}".format(latest, now)) def decode(token, certs=None, verify=True, audience=None): @@ -224,11 +216,10 @@ def decode(token, certs=None, verify=True, audience=None): # If certs is specified as a dictionary of key IDs to certificates, then # use the certificate identified by the key ID in the token header. if isinstance(certs, collections.Mapping): - key_id = header.get('kid') + key_id = header.get("kid") if key_id: if key_id not in certs: - raise ValueError( - 'Certificate for key id {} not found.'.format(key_id)) + raise ValueError("Certificate for key id {} not found.".format(key_id)) certs_to_check = [certs[key_id]] # If there's no key id in the header, check against all of the certs. else: @@ -238,24 +229,25 @@ def decode(token, certs=None, verify=True, audience=None): # Verify that the signature matches the message. if not crypt.verify_signature(signed_section, signature, certs_to_check): - raise ValueError('Could not verify token signature.') + raise ValueError("Could not verify token signature.") # Verify the issued at and created times in the payload. _verify_iat_and_exp(payload) # Check audience. if audience is not None: - claim_audience = payload.get('aud') + claim_audience = payload.get("aud") if audience != claim_audience: raise ValueError( - 'Token has wrong audience {}, expected {}'.format( - claim_audience, audience)) + "Token has wrong audience {}, expected {}".format( + claim_audience, audience + ) + ) return payload -class Credentials(google.auth.credentials.Signing, - google.auth.credentials.Credentials): +class Credentials(google.auth.credentials.Signing, google.auth.credentials.Credentials): """Credentials that use a JWT as the bearer token. These credentials require an "audience" claim. This claim identifies the @@ -305,9 +297,15 @@ class Credentials(google.auth.credentials.Signing, new_credentials = credentials.with_claims(audience=new_audience) """ - def __init__(self, signer, issuer, subject, audience, - additional_claims=None, - token_lifetime=_DEFAULT_TOKEN_LIFETIME_SECS): + def __init__( + self, + signer, + issuer, + subject, + audience, + additional_claims=None, + token_lifetime=_DEFAULT_TOKEN_LIFETIME_SECS, + ): """ Args: signer (google.auth.crypt.Signer): The signer used to sign JWTs. @@ -348,8 +346,8 @@ def _from_signer_and_info(cls, signer, info, **kwargs): Raises: ValueError: If the info is not in the expected format. """ - kwargs.setdefault('subject', info['client_email']) - kwargs.setdefault('issuer', info['client_email']) + kwargs.setdefault("subject", info["client_email"]) + kwargs.setdefault("issuer", info["client_email"]) return cls(signer, **kwargs) @classmethod @@ -367,8 +365,7 @@ def from_service_account_info(cls, info, **kwargs): Raises: ValueError: If the info is not in the expected format. """ - signer = _service_account_info.from_dict( - info, require=['client_email']) + signer = _service_account_info.from_dict(info, require=["client_email"]) return cls._from_signer_and_info(signer, info, **kwargs) @classmethod @@ -384,7 +381,8 @@ def from_service_account_file(cls, filename, **kwargs): google.auth.jwt.Credentials: The constructed credentials. """ info, signer = _service_account_info.from_filename( - filename, require=['client_email']) + filename, require=["client_email"] + ) return cls._from_signer_and_info(signer, info, **kwargs) @classmethod @@ -415,15 +413,13 @@ def from_signing_credentials(cls, credentials, audience, **kwargs): Returns: google.auth.jwt.Credentials: A new Credentials instance. """ - kwargs.setdefault('issuer', credentials.signer_email) - kwargs.setdefault('subject', credentials.signer_email) - return cls( - credentials.signer, - audience=audience, - **kwargs) + kwargs.setdefault("issuer", credentials.signer_email) + kwargs.setdefault("subject", credentials.signer_email) + return cls(credentials.signer, audience=audience, **kwargs) - def with_claims(self, issuer=None, subject=None, audience=None, - additional_claims=None): + def with_claims( + self, issuer=None, subject=None, audience=None, additional_claims=None + ): """Returns a copy of these credentials with modified claims. Args: @@ -448,7 +444,8 @@ def with_claims(self, issuer=None, subject=None, audience=None, issuer=issuer if issuer is not None else self._issuer, subject=subject if subject is not None else self._subject, audience=audience if audience is not None else self._audience, - additional_claims=new_additional_claims) + additional_claims=new_additional_claims, + ) def _make_jwt(self): """Make a signed JWT. @@ -461,11 +458,11 @@ def _make_jwt(self): expiry = now + lifetime payload = { - 'iss': self._issuer, - 'sub': self._subject, - 'iat': _helpers.datetime_to_secs(now), - 'exp': _helpers.datetime_to_secs(expiry), - 'aud': self._audience, + "iss": self._issuer, + "sub": self._subject, + "iat": _helpers.datetime_to_secs(now), + "exp": _helpers.datetime_to_secs(expiry), + "aud": self._audience, } payload.update(self._additional_claims) @@ -500,8 +497,8 @@ def signer(self): class OnDemandCredentials( - google.auth.credentials.Signing, - google.auth.credentials.Credentials): + google.auth.credentials.Signing, google.auth.credentials.Credentials +): """On-demand JWT credentials. Like :class:`Credentials`, this class uses a JWT as the bearer token for @@ -519,10 +516,15 @@ class OnDemandCredentials( .. _grpc: http://www.grpc.io/ """ - def __init__(self, signer, issuer, subject, - additional_claims=None, - token_lifetime=_DEFAULT_TOKEN_LIFETIME_SECS, - max_cache_size=_DEFAULT_MAX_CACHE_SIZE): + def __init__( + self, + signer, + issuer, + subject, + additional_claims=None, + token_lifetime=_DEFAULT_TOKEN_LIFETIME_SECS, + max_cache_size=_DEFAULT_MAX_CACHE_SIZE, + ): """ Args: signer (google.auth.crypt.Signer): The signer used to sign JWTs. @@ -563,8 +565,8 @@ def _from_signer_and_info(cls, signer, info, **kwargs): Raises: ValueError: If the info is not in the expected format. """ - kwargs.setdefault('subject', info['client_email']) - kwargs.setdefault('issuer', info['client_email']) + kwargs.setdefault("subject", info["client_email"]) + kwargs.setdefault("issuer", info["client_email"]) return cls(signer, **kwargs) @classmethod @@ -582,8 +584,7 @@ def from_service_account_info(cls, info, **kwargs): Raises: ValueError: If the info is not in the expected format. """ - signer = _service_account_info.from_dict( - info, require=['client_email']) + signer = _service_account_info.from_dict(info, require=["client_email"]) return cls._from_signer_and_info(signer, info, **kwargs) @classmethod @@ -599,7 +600,8 @@ def from_service_account_file(cls, filename, **kwargs): google.auth.jwt.OnDemandCredentials: The constructed credentials. """ info, signer = _service_account_info.from_filename( - filename, require=['client_email']) + filename, require=["client_email"] + ) return cls._from_signer_and_info(signer, info, **kwargs) @classmethod @@ -626,8 +628,8 @@ def from_signing_credentials(cls, credentials, **kwargs): Returns: google.auth.jwt.Credentials: A new Credentials instance. """ - kwargs.setdefault('issuer', credentials.signer_email) - kwargs.setdefault('subject', credentials.signer_email) + kwargs.setdefault("issuer", credentials.signer_email) + kwargs.setdefault("subject", credentials.signer_email) return cls(credentials.signer, **kwargs) def with_claims(self, issuer=None, subject=None, additional_claims=None): @@ -653,7 +655,8 @@ def with_claims(self, issuer=None, subject=None, additional_claims=None): issuer=issuer if issuer is not None else self._issuer, subject=subject if subject is not None else self._subject, additional_claims=new_additional_claims, - max_cache_size=self._cache.maxsize) + max_cache_size=self._cache.maxsize, + ) @property def valid(self): @@ -678,11 +681,11 @@ def _make_jwt_for_audience(self, audience): expiry = now + lifetime payload = { - 'iss': self._issuer, - 'sub': self._subject, - 'iat': _helpers.datetime_to_secs(now), - 'exp': _helpers.datetime_to_secs(expiry), - 'aud': audience, + "iss": self._issuer, + "sub": self._subject, + "iat": _helpers.datetime_to_secs(now), + "exp": _helpers.datetime_to_secs(expiry), + "aud": audience, } payload.update(self._additional_claims) @@ -725,7 +728,8 @@ def refresh(self, request): # pylint: disable=unused-argument # (pylint doesn't correctly recognize overridden methods.) raise exceptions.RefreshError( - 'OnDemandCredentials can not be directly refreshed.') + "OnDemandCredentials can not be directly refreshed." + ) def before_request(self, request, method, url, headers): """Performs credential-specific before request logic. @@ -743,7 +747,8 @@ def before_request(self, request, method, url, headers): parts = urllib.parse.urlsplit(url) # Strip query string and fragment audience = urllib.parse.urlunsplit( - (parts.scheme, parts.netloc, parts.path, "", "")) + (parts.scheme, parts.netloc, parts.path, "", "") + ) token = self._get_jwt_for_audience(audience) self.apply(headers, token=token) diff --git a/packages/google-auth/google/auth/transport/__init__.py b/packages/google-auth/google/auth/transport/__init__.py index d73c63cd7581..53aa4ba1401f 100644 --- a/packages/google-auth/google/auth/transport/__init__.py +++ b/packages/google-auth/google/auth/transport/__init__.py @@ -45,17 +45,17 @@ class Response(object): @abc.abstractproperty def status(self): """int: The HTTP status code.""" - raise NotImplementedError('status must be implemented.') + raise NotImplementedError("status must be implemented.") @abc.abstractproperty def headers(self): """Mapping[str, str]: The HTTP response headers.""" - raise NotImplementedError('headers must be implemented.') + raise NotImplementedError("headers must be implemented.") @abc.abstractproperty def data(self): """bytes: The response body.""" - raise NotImplementedError('data must be implemented.') + raise NotImplementedError("data must be implemented.") @six.add_metaclass(abc.ABCMeta) @@ -69,8 +69,9 @@ class Request(object): """ @abc.abstractmethod - def __call__(self, url, method='GET', body=None, headers=None, - timeout=None, **kwargs): + def __call__( + self, url, method="GET", body=None, headers=None, timeout=None, **kwargs + ): """Make an HTTP request. Args: @@ -93,4 +94,4 @@ def __call__(self, url, method='GET', body=None, headers=None, """ # pylint: disable=redundant-returns-doc, missing-raises-doc # (pylint doesn't play well with abstract docstrings.) - raise NotImplementedError('__call__ must be implemented.') + raise NotImplementedError("__call__ must be implemented.") diff --git a/packages/google-auth/google/auth/transport/_http_client.py b/packages/google-auth/google/auth/transport/_http_client.py index 08b1ab6c7cae..b52ca7b50dd5 100644 --- a/packages/google-auth/google/auth/transport/_http_client.py +++ b/packages/google-auth/google/auth/transport/_http_client.py @@ -33,10 +33,10 @@ class Response(transport.Response): Args: response (http.client.HTTPResponse): The raw http client response. """ + def __init__(self, response): self._status = response.status - self._headers = { - key.lower(): value for key, value in response.getheaders()} + self._headers = {key.lower(): value for key, value in response.getheaders()} self._data = response.read() @property @@ -55,8 +55,9 @@ def data(self): class Request(transport.Request): """http.client transport request adapter.""" - def __call__(self, url, method='GET', body=None, headers=None, - timeout=None, **kwargs): + def __call__( + self, url, method="GET", body=None, headers=None, timeout=None, **kwargs + ): """Make an HTTP request using http.client. Args: @@ -88,20 +89,21 @@ def __call__(self, url, method='GET', body=None, headers=None, # http.client needs the host and path parts specified separately. parts = urllib.parse.urlsplit(url) path = urllib.parse.urlunsplit( - ('', '', parts.path, parts.query, parts.fragment)) + ("", "", parts.path, parts.query, parts.fragment) + ) - if parts.scheme != 'http': + if parts.scheme != "http": raise exceptions.TransportError( - 'http.client transport only supports the http scheme, {}' - 'was specified'.format(parts.scheme)) + "http.client transport only supports the http scheme, {}" + "was specified".format(parts.scheme) + ) connection = http_client.HTTPConnection(parts.netloc, timeout=timeout) try: - _LOGGER.debug('Making request: %s %s', method, url) + _LOGGER.debug("Making request: %s %s", method, url) - connection.request( - method, path, body=body, headers=headers, **kwargs) + connection.request(method, path, body=body, headers=headers, **kwargs) response = connection.getresponse() return Response(response) diff --git a/packages/google-auth/google/auth/transport/grpc.py b/packages/google-auth/google/auth/transport/grpc.py index 0d44f6458eac..9a1bc6d18bd8 100644 --- a/packages/google-auth/google/auth/transport/grpc.py +++ b/packages/google-auth/google/auth/transport/grpc.py @@ -17,13 +17,14 @@ from __future__ import absolute_import import six + try: import grpc except ImportError as caught_exc: # pragma: NO COVER six.raise_from( ImportError( - 'gRPC is not installed, please install the grpcio package ' - 'to use the gRPC transport.' + "gRPC is not installed, please install the grpcio package " + "to use the gRPC transport." ), caught_exc, ) @@ -42,6 +43,7 @@ class AuthMetadataPlugin(grpc.AuthMetadataPlugin): request (google.auth.transport.Request): A HTTP transport request object used to refresh credentials as needed. """ + def __init__(self, credentials, request): # pylint: disable=no-value-for-parameter # pylint doesn't realize that the super method takes no arguments @@ -59,10 +61,8 @@ def _get_authorization_headers(self, context): """ headers = {} self._credentials.before_request( - self._request, - context.method_name, - context.service_url, - headers) + self._request, context.method_name, context.service_url, headers + ) return list(six.iteritems(headers)) @@ -78,7 +78,8 @@ def __call__(self, context, callback): def secure_authorized_channel( - credentials, request, target, ssl_credentials=None, **kwargs): + credentials, request, target, ssl_credentials=None, **kwargs +): """Creates a secure authorized gRPC channel. This creates a channel with SSL and :class:`AuthMetadataPlugin`. This @@ -130,6 +131,7 @@ def secure_authorized_channel( # Combine the ssl credentials and the authorization credentials. composite_credentials = grpc.composite_channel_credentials( - ssl_credentials, google_auth_credentials) + ssl_credentials, google_auth_credentials + ) return grpc.secure_channel(target, composite_credentials, **kwargs) diff --git a/packages/google-auth/google/auth/transport/requests.py b/packages/google-auth/google/auth/transport/requests.py index 8250c74c6f02..564a0cd04ae1 100644 --- a/packages/google-auth/google/auth/transport/requests.py +++ b/packages/google-auth/google/auth/transport/requests.py @@ -23,10 +23,11 @@ import requests except ImportError as caught_exc: # pragma: NO COVER import six + six.raise_from( ImportError( - 'The requests library is not installed, please install the ' - 'requests package to use the requests transport.' + "The requests library is not installed, please install the " + "requests package to use the requests transport." ), caught_exc, ) @@ -46,6 +47,7 @@ class _Response(transport.Response): Args: response (requests.Response): The raw Requests response. """ + def __init__(self, response): self._response = response @@ -85,14 +87,16 @@ class Request(transport.Request): .. automethod:: __call__ """ + def __init__(self, session=None): if not session: session = requests.Session() self.session = session - def __call__(self, url, method='GET', body=None, headers=None, - timeout=None, **kwargs): + def __call__( + self, url, method="GET", body=None, headers=None, timeout=None, **kwargs + ): """Make an HTTP request using requests. Args: @@ -114,10 +118,10 @@ def __call__(self, url, method='GET', body=None, headers=None, google.auth.exceptions.TransportError: If any exception occurred. """ try: - _LOGGER.debug('Making request: %s %s', method, url) + _LOGGER.debug("Making request: %s %s", method, url) response = self.session.request( - method, url, data=body, headers=headers, timeout=timeout, - **kwargs) + method, url, data=body, headers=headers, timeout=timeout, **kwargs + ) return _Response(response) except requests.exceptions.RequestException as caught_exc: new_exc = exceptions.TransportError(caught_exc) @@ -157,11 +161,15 @@ class AuthorizedSession(requests.Session): an instance of :class:`~google.auth.transport.requests.Request` is created. """ - def __init__(self, credentials, - refresh_status_codes=transport.DEFAULT_REFRESH_STATUS_CODES, - max_refresh_attempts=transport.DEFAULT_MAX_REFRESH_ATTEMPTS, - refresh_timeout=None, - auth_request=None): + + def __init__( + self, + credentials, + refresh_status_codes=transport.DEFAULT_REFRESH_STATUS_CODES, + max_refresh_attempts=transport.DEFAULT_MAX_REFRESH_ATTEMPTS, + refresh_timeout=None, + auth_request=None, + ): super(AuthorizedSession, self).__init__() self.credentials = credentials self._refresh_status_codes = refresh_status_codes @@ -194,40 +202,50 @@ def request(self, method, url, data=None, headers=None, **kwargs): # Use a kwarg for this instead of an attribute to maintain # thread-safety. - _credential_refresh_attempt = kwargs.pop( - '_credential_refresh_attempt', 0) + _credential_refresh_attempt = kwargs.pop("_credential_refresh_attempt", 0) # Make a copy of the headers. They will be modified by the credentials # and we want to pass the original headers if we recurse. request_headers = headers.copy() if headers is not None else {} self.credentials.before_request( - self._auth_request, method, url, request_headers) + self._auth_request, method, url, request_headers + ) response = super(AuthorizedSession, self).request( - method, url, data=data, headers=request_headers, **kwargs) + method, url, data=data, headers=request_headers, **kwargs + ) # If the response indicated that the credentials needed to be # refreshed, then refresh the credentials and re-attempt the # request. # A stored token may expire between the time it is retrieved and # the time the request is made, so we may need to try twice. - if (response.status_code in self._refresh_status_codes - and _credential_refresh_attempt < self._max_refresh_attempts): + if ( + response.status_code in self._refresh_status_codes + and _credential_refresh_attempt < self._max_refresh_attempts + ): _LOGGER.info( - 'Refreshing credentials due to a %s response. Attempt %s/%s.', - response.status_code, _credential_refresh_attempt + 1, - self._max_refresh_attempts) + "Refreshing credentials due to a %s response. Attempt %s/%s.", + response.status_code, + _credential_refresh_attempt + 1, + self._max_refresh_attempts, + ) auth_request_with_timeout = functools.partial( - self._auth_request, timeout=self._refresh_timeout) + self._auth_request, timeout=self._refresh_timeout + ) self.credentials.refresh(auth_request_with_timeout) # Recurse. Pass in the original headers, not our modified set. return self.request( - method, url, data=data, headers=headers, + method, + url, + data=data, + headers=headers, _credential_refresh_attempt=_credential_refresh_attempt + 1, - **kwargs) + **kwargs + ) return response diff --git a/packages/google-auth/google/auth/transport/urllib3.py b/packages/google-auth/google/auth/transport/urllib3.py index 37eb31757bd0..dbb186bb13a3 100644 --- a/packages/google-auth/google/auth/transport/urllib3.py +++ b/packages/google-auth/google/auth/transport/urllib3.py @@ -34,10 +34,11 @@ import urllib3 except ImportError as caught_exc: # pragma: NO COVER import six + six.raise_from( ImportError( - 'The urllib3 library is not installed, please install the ' - 'urllib3 package to use the urllib3 transport.' + "The urllib3 library is not installed, please install the " + "urllib3 package to use the urllib3 transport." ), caught_exc, ) @@ -56,6 +57,7 @@ class _Response(transport.Response): Args: response (urllib3.response.HTTPResponse): The raw urllib3 response. """ + def __init__(self, response): self._response = response @@ -97,11 +99,13 @@ class that implements :class:`~urllib3.request.RequestMethods`, .. automethod:: __call__ """ + def __init__(self, http): self.http = http - def __call__(self, url, method='GET', body=None, headers=None, - timeout=None, **kwargs): + def __call__( + self, url, method="GET", body=None, headers=None, timeout=None, **kwargs + ): """Make an HTTP request using urllib3. Args: @@ -125,12 +129,13 @@ def __call__(self, url, method='GET', body=None, headers=None, # urllib3 uses a sentinel default value for timeout, so only set it if # specified. if timeout is not None: - kwargs['timeout'] = timeout + kwargs["timeout"] = timeout try: - _LOGGER.debug('Making request: %s %s', method, url) + _LOGGER.debug("Making request: %s %s", method, url) response = self.http.request( - method, url, body=body, headers=headers, **kwargs) + method, url, body=body, headers=headers, **kwargs + ) return _Response(response) except urllib3.exceptions.HTTPError as caught_exc: new_exc = exceptions.TransportError(caught_exc) @@ -139,9 +144,7 @@ def __call__(self, url, method='GET', body=None, headers=None, def _make_default_http(): if certifi is not None: - return urllib3.PoolManager( - cert_reqs='CERT_REQUIRED', - ca_certs=certifi.where()) + return urllib3.PoolManager(cert_reqs="CERT_REQUIRED", ca_certs=certifi.where()) else: return urllib3.PoolManager() @@ -178,9 +181,14 @@ class AuthorizedHttp(urllib3.request.RequestMethods): max_refresh_attempts (int): The maximum number of times to attempt to refresh the credentials and retry the request. """ - def __init__(self, credentials, http=None, - refresh_status_codes=transport.DEFAULT_REFRESH_STATUS_CODES, - max_refresh_attempts=transport.DEFAULT_MAX_REFRESH_ATTEMPTS): + + def __init__( + self, + credentials, + http=None, + refresh_status_codes=transport.DEFAULT_REFRESH_STATUS_CODES, + max_refresh_attempts=transport.DEFAULT_MAX_REFRESH_ATTEMPTS, + ): if http is None: http = _make_default_http() @@ -204,8 +212,7 @@ def urlopen(self, method, url, body=None, headers=None, **kwargs): # Use a kwarg for this instead of an attribute to maintain # thread-safety. - _credential_refresh_attempt = kwargs.pop( - '_credential_refresh_attempt', 0) + _credential_refresh_attempt = kwargs.pop("_credential_refresh_attempt", 0) if headers is None: headers = self.headers @@ -214,11 +221,11 @@ def urlopen(self, method, url, body=None, headers=None, **kwargs): # and we want to pass the original headers if we recurse. request_headers = headers.copy() - self.credentials.before_request( - self._request, method, url, request_headers) + self.credentials.before_request(self._request, method, url, request_headers) response = self.http.urlopen( - method, url, body=body, headers=request_headers, **kwargs) + method, url, body=body, headers=request_headers, **kwargs + ) # If the response indicated that the credentials needed to be # refreshed, then refresh the credentials and re-attempt the @@ -227,21 +234,29 @@ def urlopen(self, method, url, body=None, headers=None, **kwargs): # the time the request is made, so we may need to try twice. # The reason urllib3's retries aren't used is because they # don't allow you to modify the request headers. :/ - if (response.status in self._refresh_status_codes - and _credential_refresh_attempt < self._max_refresh_attempts): + if ( + response.status in self._refresh_status_codes + and _credential_refresh_attempt < self._max_refresh_attempts + ): _LOGGER.info( - 'Refreshing credentials due to a %s response. Attempt %s/%s.', - response.status, _credential_refresh_attempt + 1, - self._max_refresh_attempts) + "Refreshing credentials due to a %s response. Attempt %s/%s.", + response.status, + _credential_refresh_attempt + 1, + self._max_refresh_attempts, + ) self.credentials.refresh(self._request) # Recurse. Pass in the original headers, not our modified set. return self.urlopen( - method, url, body=body, headers=headers, + method, + url, + body=body, + headers=headers, _credential_refresh_attempt=_credential_refresh_attempt + 1, - **kwargs) + **kwargs + ) return response diff --git a/packages/google-auth/google/oauth2/_client.py b/packages/google-auth/google/oauth2/_client.py index eac01b7ecd9e..996f9b7e2961 100644 --- a/packages/google-auth/google/oauth2/_client.py +++ b/packages/google-auth/google/oauth2/_client.py @@ -34,9 +34,9 @@ from google.auth import exceptions from google.auth import jwt -_URLENCODED_CONTENT_TYPE = 'application/x-www-form-urlencoded' -_JWT_GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:jwt-bearer' -_REFRESH_GRANT_TYPE = 'refresh_token' +_URLENCODED_CONTENT_TYPE = "application/x-www-form-urlencoded" +_JWT_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer" +_REFRESH_GRANT_TYPE = "refresh_token" def _handle_error_response(response_body): @@ -50,15 +50,14 @@ def _handle_error_response(response_body): """ try: error_data = json.loads(response_body) - error_details = '{}: {}'.format( - error_data['error'], - error_data.get('error_description')) + error_details = "{}: {}".format( + error_data["error"], error_data.get("error_description") + ) # If no details could be extracted, use the response data. except (KeyError, ValueError): error_details = response_body - raise exceptions.RefreshError( - error_details, response_body) + raise exceptions.RefreshError(error_details, response_body) def _parse_expiry(response_data): @@ -71,11 +70,10 @@ def _parse_expiry(response_data): Optional[datetime]: The expiration or ``None`` if no expiration was specified. """ - expires_in = response_data.get('expires_in', None) + expires_in = response_data.get("expires_in", None) if expires_in is not None: - return _helpers.utcnow() + datetime.timedelta( - seconds=expires_in) + return _helpers.utcnow() + datetime.timedelta(seconds=expires_in) else: return None @@ -98,24 +96,20 @@ def _token_endpoint_request(request, token_uri, body): an error. """ body = urllib.parse.urlencode(body) - headers = { - 'content-type': _URLENCODED_CONTENT_TYPE, - } + headers = {"content-type": _URLENCODED_CONTENT_TYPE} retry = 0 # retry to fetch token for maximum of two times if any internal failure # occurs. while True: - response = request( - method='POST', url=token_uri, headers=headers, body=body) - response_body = response.data.decode('utf-8') + response = request(method="POST", url=token_uri, headers=headers, body=body) + response_body = response.data.decode("utf-8") if response.status == http_client.OK: break else: - error_desc = json.loads( - response_body).get('error_description') or '' - if error_desc == 'internal_failure' and retry < 1: + error_desc = json.loads(response_body).get("error_description") or "" + if error_desc == "internal_failure" and retry < 1: retry += 1 continue _handle_error_response(response_body) @@ -147,18 +141,14 @@ def jwt_grant(request, token_uri, assertion): .. _rfc7523 section 4: https://tools.ietf.org/html/rfc7523#section-4 """ - body = { - 'assertion': assertion, - 'grant_type': _JWT_GRANT_TYPE, - } + body = {"assertion": assertion, "grant_type": _JWT_GRANT_TYPE} response_data = _token_endpoint_request(request, token_uri, body) try: - access_token = response_data['access_token'] + access_token = response_data["access_token"] except KeyError as caught_exc: - new_exc = exceptions.RefreshError( - 'No access token in response.', response_data) + new_exc = exceptions.RefreshError("No access token in response.", response_data) six.raise_from(new_exc, caught_exc) expiry = _parse_expiry(response_data) @@ -191,28 +181,25 @@ def id_token_jwt_grant(request, token_uri, assertion): google.auth.exceptions.RefreshError: If the token endpoint returned an error. """ - body = { - 'assertion': assertion, - 'grant_type': _JWT_GRANT_TYPE, - } + body = {"assertion": assertion, "grant_type": _JWT_GRANT_TYPE} response_data = _token_endpoint_request(request, token_uri, body) try: - id_token = response_data['id_token'] + id_token = response_data["id_token"] except KeyError as caught_exc: - new_exc = exceptions.RefreshError( - 'No ID token in response.', response_data) + new_exc = exceptions.RefreshError("No ID token in response.", response_data) six.raise_from(new_exc, caught_exc) payload = jwt.decode(id_token, verify=False) - expiry = datetime.datetime.utcfromtimestamp(payload['exp']) + expiry = datetime.datetime.utcfromtimestamp(payload["exp"]) return id_token, expiry, response_data -def refresh_grant(request, token_uri, refresh_token, client_id, client_secret, - scopes=None): +def refresh_grant( + request, token_uri, refresh_token, client_id, client_secret, scopes=None +): """Implements the OAuth 2.0 refresh token grant. For more details, see `rfc678 section 6`_. @@ -243,24 +230,23 @@ def refresh_grant(request, token_uri, refresh_token, client_id, client_secret, .. _rfc6748 section 6: https://tools.ietf.org/html/rfc6749#section-6 """ body = { - 'grant_type': _REFRESH_GRANT_TYPE, - 'client_id': client_id, - 'client_secret': client_secret, - 'refresh_token': refresh_token, + "grant_type": _REFRESH_GRANT_TYPE, + "client_id": client_id, + "client_secret": client_secret, + "refresh_token": refresh_token, } if scopes: - body['scope'] = ' '.join(scopes) + body["scope"] = " ".join(scopes) response_data = _token_endpoint_request(request, token_uri, body) try: - access_token = response_data['access_token'] + access_token = response_data["access_token"] except KeyError as caught_exc: - new_exc = exceptions.RefreshError( - 'No access token in response.', response_data) + new_exc = exceptions.RefreshError("No access token in response.", response_data) six.raise_from(new_exc, caught_exc) - refresh_token = response_data.get('refresh_token', refresh_token) + refresh_token = response_data.get("refresh_token", refresh_token) expiry = _parse_expiry(response_data) return access_token, refresh_token, expiry, response_data diff --git a/packages/google-auth/google/oauth2/credentials.py b/packages/google-auth/google/oauth2/credentials.py index 9e1141646b64..676a4324eb39 100644 --- a/packages/google-auth/google/oauth2/credentials.py +++ b/packages/google-auth/google/oauth2/credentials.py @@ -43,15 +43,22 @@ # The Google OAuth 2.0 token endpoint. Used for authorized user credentials. -_GOOGLE_OAUTH2_TOKEN_ENDPOINT = 'https://oauth2.googleapis.com/token' +_GOOGLE_OAUTH2_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token" class Credentials(credentials.ReadOnlyScoped, credentials.Credentials): """Credentials using OAuth 2.0 access and refresh tokens.""" - def __init__(self, token, refresh_token=None, id_token=None, - token_uri=None, client_id=None, client_secret=None, - scopes=None): + def __init__( + self, + token, + refresh_token=None, + id_token=None, + token_uri=None, + client_id=None, + client_secret=None, + scopes=None, + ): """ Args: token (Optional(str)): The OAuth 2.0 access token. Can be None @@ -124,35 +131,43 @@ def requires_scopes(self): @_helpers.copy_docstring(credentials.Credentials) def refresh(self, request): - if (self._refresh_token is None or - self._token_uri is None or - self._client_id is None or - self._client_secret is None): + if ( + self._refresh_token is None + or self._token_uri is None + or self._client_id is None + or self._client_secret is None + ): raise exceptions.RefreshError( - 'The credentials do not contain the necessary fields need to ' - 'refresh the access token. You must specify refresh_token, ' - 'token_uri, client_id, and client_secret.') - - access_token, refresh_token, expiry, grant_response = ( - _client.refresh_grant( - request, self._token_uri, self._refresh_token, self._client_id, - self._client_secret, self._scopes)) + "The credentials do not contain the necessary fields need to " + "refresh the access token. You must specify refresh_token, " + "token_uri, client_id, and client_secret." + ) + + access_token, refresh_token, expiry, grant_response = _client.refresh_grant( + request, + self._token_uri, + self._refresh_token, + self._client_id, + self._client_secret, + self._scopes, + ) self.token = access_token self.expiry = expiry self._refresh_token = refresh_token - self._id_token = grant_response.get('id_token') + self._id_token = grant_response.get("id_token") - if self._scopes and 'scopes' in grant_response: + if self._scopes and "scopes" in grant_response: requested_scopes = frozenset(self._scopes) - granted_scopes = frozenset(grant_response['scopes'].split()) - scopes_requested_but_not_granted = ( - requested_scopes - granted_scopes) + granted_scopes = frozenset(grant_response["scopes"].split()) + scopes_requested_but_not_granted = requested_scopes - granted_scopes if scopes_requested_but_not_granted: raise exceptions.RefreshError( - 'Not all requested scopes were granted by the ' - 'authorization server, missing scopes {}.'.format( - ', '.join(scopes_requested_but_not_granted))) + "Not all requested scopes were granted by the " + "authorization server, missing scopes {}.".format( + ", ".join(scopes_requested_but_not_granted) + ) + ) @classmethod def from_authorized_user_info(cls, info, scopes=None): @@ -171,21 +186,23 @@ def from_authorized_user_info(cls, info, scopes=None): Raises: ValueError: If the info is not in the expected format. """ - keys_needed = set(('refresh_token', 'client_id', 'client_secret')) + keys_needed = set(("refresh_token", "client_id", "client_secret")) missing = keys_needed.difference(six.iterkeys(info)) if missing: raise ValueError( - 'Authorized user info was not in the expected format, missing ' - 'fields {}.'.format(', '.join(missing))) + "Authorized user info was not in the expected format, missing " + "fields {}.".format(", ".join(missing)) + ) return cls( None, # No access token, must be refreshed. - refresh_token=info['refresh_token'], + refresh_token=info["refresh_token"], token_uri=_GOOGLE_OAUTH2_TOKEN_ENDPOINT, scopes=scopes, - client_id=info['client_id'], - client_secret=info['client_secret']) + client_id=info["client_id"], + client_secret=info["client_secret"], + ) @classmethod def from_authorized_user_file(cls, filename, scopes=None): @@ -203,6 +220,6 @@ def from_authorized_user_file(cls, filename, scopes=None): Raises: ValueError: If the file is not in the expected format. """ - with io.open(filename, 'r', encoding='utf-8') as json_file: + with io.open(filename, "r", encoding="utf-8") as json_file: data = json.load(json_file) return cls.from_authorized_user_info(data, scopes) diff --git a/packages/google-auth/google/oauth2/id_token.py b/packages/google-auth/google/oauth2/id_token.py index 208ab62240d6..bc4844513a40 100644 --- a/packages/google-auth/google/oauth2/id_token.py +++ b/packages/google-auth/google/oauth2/id_token.py @@ -67,13 +67,14 @@ # The URL that provides public certificates for verifying ID tokens issued # by Google's OAuth 2.0 authorization server. -_GOOGLE_OAUTH2_CERTS_URL = 'https://www.googleapis.com/oauth2/v1/certs' +_GOOGLE_OAUTH2_CERTS_URL = "https://www.googleapis.com/oauth2/v1/certs" # The URL that provides public certificates for verifying ID tokens issued # by Firebase and the Google APIs infrastructure _GOOGLE_APIS_CERTS_URL = ( - 'https://www.googleapis.com/robot/v1/metadata/x509' - '/securetoken@system.gserviceaccount.com') + "https://www.googleapis.com/robot/v1/metadata/x509" + "/securetoken@system.gserviceaccount.com" +) def _fetch_certs(request, certs_url): @@ -91,17 +92,17 @@ def _fetch_certs(request, certs_url): Mapping[str, str]: A mapping of public key ID to x.509 certificate data. """ - response = request(certs_url, method='GET') + response = request(certs_url, method="GET") if response.status != http_client.OK: raise exceptions.TransportError( - 'Could not fetch certificates at {}'.format(certs_url)) + "Could not fetch certificates at {}".format(certs_url) + ) - return json.loads(response.data.decode('utf-8')) + return json.loads(response.data.decode("utf-8")) -def verify_token(id_token, request, audience=None, - certs_url=_GOOGLE_OAUTH2_CERTS_URL): +def verify_token(id_token, request, audience=None, certs_url=_GOOGLE_OAUTH2_CERTS_URL): """Verifies an ID token and returns the decoded token. Args: @@ -137,8 +138,8 @@ def verify_oauth2_token(id_token, request, audience=None): Mapping[str, Any]: The decoded token. """ return verify_token( - id_token, request, audience=audience, - certs_url=_GOOGLE_OAUTH2_CERTS_URL) + id_token, request, audience=audience, certs_url=_GOOGLE_OAUTH2_CERTS_URL + ) def verify_firebase_token(id_token, request, audience=None): @@ -156,4 +157,5 @@ def verify_firebase_token(id_token, request, audience=None): Mapping[str, Any]: The decoded token. """ return verify_token( - id_token, request, audience=audience, certs_url=_GOOGLE_APIS_CERTS_URL) + id_token, request, audience=audience, certs_url=_GOOGLE_APIS_CERTS_URL + ) diff --git a/packages/google-auth/google/oauth2/service_account.py b/packages/google-auth/google/oauth2/service_account.py index c60c56546443..17fdd516d208 100644 --- a/packages/google-auth/google/oauth2/service_account.py +++ b/packages/google-auth/google/oauth2/service_account.py @@ -82,9 +82,7 @@ _DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds -class Credentials(credentials.Signing, - credentials.Scoped, - credentials.Credentials): +class Credentials(credentials.Signing, credentials.Scoped, credentials.Credentials): """Service account credentials Usually, you'll create these credentials with one of the helper @@ -116,8 +114,16 @@ class Credentials(credentials.Signing, delegated_credentials = credentials.with_subject(subject) """ - def __init__(self, signer, service_account_email, token_uri, scopes=None, - subject=None, project_id=None, additional_claims=None): + def __init__( + self, + signer, + service_account_email, + token_uri, + scopes=None, + subject=None, + project_id=None, + additional_claims=None, + ): """ Args: signer (google.auth.crypt.Signer): The signer used to sign JWTs. @@ -169,9 +175,11 @@ def _from_signer_and_info(cls, signer, info, **kwargs): """ return cls( signer, - service_account_email=info['client_email'], - token_uri=info['token_uri'], - project_id=info.get('project_id'), **kwargs) + service_account_email=info["client_email"], + token_uri=info["token_uri"], + project_id=info.get("project_id"), + **kwargs + ) @classmethod def from_service_account_info(cls, info, **kwargs): @@ -190,7 +198,8 @@ def from_service_account_info(cls, info, **kwargs): ValueError: If the info is not in the expected format. """ signer = _service_account_info.from_dict( - info, require=['client_email', 'token_uri']) + info, require=["client_email", "token_uri"] + ) return cls._from_signer_and_info(signer, info, **kwargs) @classmethod @@ -206,7 +215,8 @@ def from_service_account_file(cls, filename, **kwargs): credentials. """ info, signer = _service_account_info.from_filename( - filename, require=['client_email', 'token_uri']) + filename, require=["client_email", "token_uri"] + ) return cls._from_signer_and_info(signer, info, **kwargs) @property @@ -237,7 +247,8 @@ def with_scopes(self, scopes): token_uri=self._token_uri, subject=self._subject, project_id=self._project_id, - additional_claims=self._additional_claims.copy()) + additional_claims=self._additional_claims.copy(), + ) def with_subject(self, subject): """Create a copy of these credentials with the specified subject. @@ -256,7 +267,8 @@ def with_subject(self, subject): token_uri=self._token_uri, subject=subject, project_id=self._project_id, - additional_claims=self._additional_claims.copy()) + additional_claims=self._additional_claims.copy(), + ) def with_claims(self, additional_claims): """Returns a copy of these credentials with modified claims. @@ -280,7 +292,8 @@ def with_claims(self, additional_claims): token_uri=self._token_uri, subject=self._subject, project_id=self._project_id, - additional_claims=new_additional_claims) + additional_claims=new_additional_claims, + ) def _make_authorization_grant_assertion(self): """Create the OAuth 2.0 assertion. @@ -296,20 +309,20 @@ def _make_authorization_grant_assertion(self): expiry = now + lifetime payload = { - 'iat': _helpers.datetime_to_secs(now), - 'exp': _helpers.datetime_to_secs(expiry), + "iat": _helpers.datetime_to_secs(now), + "exp": _helpers.datetime_to_secs(expiry), # The issuer must be the service account email. - 'iss': self._service_account_email, + "iss": self._service_account_email, # The audience must be the auth token endpoint's URI - 'aud': self._token_uri, - 'scope': _helpers.scopes_to_string(self._scopes or ()) + "aud": self._token_uri, + "scope": _helpers.scopes_to_string(self._scopes or ()), } payload.update(self._additional_claims) # The subject can be a user email for domain-wide delegation. if self._subject: - payload.setdefault('sub', self._subject) + payload.setdefault("sub", self._subject) token = jwt.encode(self._signer, payload) @@ -318,8 +331,7 @@ def _make_authorization_grant_assertion(self): @_helpers.copy_docstring(credentials.Credentials) def refresh(self, request): assertion = self._make_authorization_grant_assertion() - access_token, expiry, _ = _client.jwt_grant( - request, self._token_uri, assertion) + access_token, expiry, _ = _client.jwt_grant(request, self._token_uri, assertion) self.token = access_token self.expiry = expiry @@ -379,8 +391,15 @@ class IDTokenCredentials(credentials.Signing, credentials.Credentials): delegated_credentials = credentials.with_subject(subject) """ - def __init__(self, signer, service_account_email, token_uri, - target_audience, additional_claims=None): + + def __init__( + self, + signer, + service_account_email, + token_uri, + target_audience, + additional_claims=None, + ): """ Args: signer (google.auth.crypt.Signer): The signer used to sign JWTs. @@ -424,8 +443,8 @@ def _from_signer_and_info(cls, signer, info, **kwargs): Raises: ValueError: If the info is not in the expected format. """ - kwargs.setdefault('service_account_email', info['client_email']) - kwargs.setdefault('token_uri', info['token_uri']) + kwargs.setdefault("service_account_email", info["client_email"]) + kwargs.setdefault("token_uri", info["token_uri"]) return cls(signer, **kwargs) @classmethod @@ -445,7 +464,8 @@ def from_service_account_info(cls, info, **kwargs): ValueError: If the info is not in the expected format. """ signer = _service_account_info.from_dict( - info, require=['client_email', 'token_uri']) + info, require=["client_email", "token_uri"] + ) return cls._from_signer_and_info(signer, info, **kwargs) @classmethod @@ -461,7 +481,8 @@ def from_service_account_file(cls, filename, **kwargs): credentials. """ info, signer = _service_account_info.from_filename( - filename, require=['client_email', 'token_uri']) + filename, require=["client_email", "token_uri"] + ) return cls._from_signer_and_info(signer, info, **kwargs) def with_target_audience(self, target_audience): @@ -481,7 +502,8 @@ def with_target_audience(self, target_audience): service_account_email=self._service_account_email, token_uri=self._token_uri, target_audience=target_audience, - additional_claims=self._additional_claims.copy()) + additional_claims=self._additional_claims.copy(), + ) def _make_authorization_grant_assertion(self): """Create the OAuth 2.0 assertion. @@ -497,15 +519,15 @@ def _make_authorization_grant_assertion(self): expiry = now + lifetime payload = { - 'iat': _helpers.datetime_to_secs(now), - 'exp': _helpers.datetime_to_secs(expiry), + "iat": _helpers.datetime_to_secs(now), + "exp": _helpers.datetime_to_secs(expiry), # The issuer must be the service account email. - 'iss': self.service_account_email, + "iss": self.service_account_email, # The audience must be the auth token endpoint's URI - 'aud': self._token_uri, + "aud": self._token_uri, # The target audience specifies which service the ID token is # intended for. - 'target_audience': self._target_audience + "target_audience": self._target_audience, } payload.update(self._additional_claims) @@ -518,7 +540,8 @@ def _make_authorization_grant_assertion(self): def refresh(self, request): assertion = self._make_authorization_grant_assertion() access_token, expiry, _ = _client.id_token_jwt_grant( - request, self._token_uri, assertion) + request, self._token_uri, assertion + ) self.token = access_token self.expiry = expiry diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 60c7df43bed9..d303cb306d07 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -19,46 +19,46 @@ DEPENDENCIES = ( - 'cachetools>=2.0.0,<3.2', - 'pyasn1-modules>=0.2.1', - 'rsa>=3.1.4,<4.1', - 'setuptools>=40.3.0', - 'six>=1.9.0', + "cachetools>=2.0.0,<3.2", + "pyasn1-modules>=0.2.1", + "rsa>=3.1.4,<4.1", + "setuptools>=40.3.0", + "six>=1.9.0", ) -with io.open('README.rst', 'r') as fh: +with io.open("README.rst", "r") as fh: long_description = fh.read() setup( - name='google-auth', - version='1.6.3', - author='Google Cloud Platform', - author_email='jonwayne+google-auth@google.com', - description='Google Authentication Library', + name="google-auth", + version="1.6.3", + author="Google Cloud Platform", + author_email="jonwayne+google-auth@google.com", + description="Google Authentication Library", long_description=long_description, - url='https://github.com/GoogleCloudPlatform/google-auth-library-python', - packages=find_packages(exclude=('tests*', 'system_tests*')), - namespace_packages=('google',), + url="https://github.com/GoogleCloudPlatform/google-auth-library-python", + packages=find_packages(exclude=("tests*", "system_tests*")), + namespace_packages=("google",), install_requires=DEPENDENCIES, - python_requires='>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*', - license='Apache 2.0', - keywords='google auth oauth client', + python_requires=">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*", + license="Apache 2.0", + keywords="google auth oauth client", classifiers=[ - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Development Status :: 5 - Production/Stable', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: Apache Software License', - 'Operating System :: POSIX', - 'Operating System :: Microsoft :: Windows', - 'Operating System :: MacOS :: MacOS X', - 'Operating System :: OS Independent', - 'Topic :: Internet :: WWW/HTTP', + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Operating System :: POSIX", + "Operating System :: Microsoft :: Windows", + "Operating System :: MacOS :: MacOS X", + "Operating System :: OS Independent", + "Topic :: Internet :: WWW/HTTP", ], ) diff --git a/packages/google-auth/system_tests/app_engine_test_app/appengine_config.py b/packages/google-auth/system_tests/app_engine_test_app/appengine_config.py index da02e10d0691..6339909e519d 100644 --- a/packages/google-auth/system_tests/app_engine_test_app/appengine_config.py +++ b/packages/google-auth/system_tests/app_engine_test_app/appengine_config.py @@ -15,7 +15,7 @@ from google.appengine.ext import vendor # Add any libraries installed in the "lib" folder. -vendor.add('lib') +vendor.add("lib") # Patch os.path.expanduser. This should be fixed in GAE @@ -26,4 +26,5 @@ def patched_expanduser(path): return path + os.path.expanduser = patched_expanduser diff --git a/packages/google-auth/system_tests/app_engine_test_app/main.py b/packages/google-auth/system_tests/app_engine_test_app/main.py index 122b505fb9e0..a3354acde327 100644 --- a/packages/google-auth/system_tests/app_engine_test_app/main.py +++ b/packages/google-auth/system_tests/app_engine_test_app/main.py @@ -42,8 +42,8 @@ Captured output: {} """ -TOKEN_INFO_URL = 'https://www.googleapis.com/oauth2/v3/tokeninfo' -EMAIL_SCOPE = 'https://www.googleapis.com/auth/userinfo.email' +TOKEN_INFO_URL = "https://www.googleapis.com/oauth2/v3/tokeninfo" +EMAIL_SCOPE = "https://www.googleapis.com/auth/userinfo.email" HTTP = urllib3.contrib.appengine.AppEngineManager() HTTP_REQUEST = google.auth.transport.urllib3.Request(HTTP) @@ -58,13 +58,13 @@ def test_credentials(): assert scoped_credentials.token is not None # Get token info and verify scope - url = _helpers.update_query(TOKEN_INFO_URL, { - 'access_token': scoped_credentials.token, - }) - response = HTTP_REQUEST(url=url, method='GET') - token_info = json.loads(response.data.decode('utf-8')) + url = _helpers.update_query( + TOKEN_INFO_URL, {"access_token": scoped_credentials.token} + ) + response = HTTP_REQUEST(url=url, method="GET") + token_info = json.loads(response.data.decode("utf-8")) - assert token_info['scope'] == EMAIL_SCOPE + assert token_info["scope"] == EMAIL_SCOPE def test_default(): @@ -90,11 +90,11 @@ def run_test_func(func): with capture() as capsys: try: func() - return True, '' + return True, "" except Exception as exc: output = FAILED_TEST_TMPL.format( - func.func_name, exc, traceback.format_exc(), - capsys.getvalue()) + func.func_name, exc, traceback.format_exc(), capsys.getvalue() + ) return False, output @@ -106,7 +106,7 @@ def run_tests(): otherwise, and any captured output from the tests. """ status = True - output = '' + output = "" tests = (test_credentials, test_default) @@ -120,7 +120,7 @@ def run_tests(): class MainHandler(webapp2.RequestHandler): def get(self): - self.response.headers['content-type'] = 'text/plain' + self.response.headers["content-type"] = "text/plain" status, output = run_tests() @@ -130,6 +130,4 @@ def get(self): self.response.write(output) -app = webapp2.WSGIApplication([ - ('/', MainHandler), -], debug=True) +app = webapp2.WSGIApplication([("/", MainHandler)], debug=True) diff --git a/packages/google-auth/system_tests/conftest.py b/packages/google-auth/system_tests/conftest.py index d0f7cc02317b..3f089c4b4c19 100644 --- a/packages/google-auth/system_tests/conftest.py +++ b/packages/google-auth/system_tests/conftest.py @@ -24,13 +24,13 @@ HERE = os.path.dirname(__file__) -DATA_DIR = os.path.join(HERE, 'data') -SERVICE_ACCOUNT_FILE = os.path.join(DATA_DIR, 'service_account.json') -AUTHORIZED_USER_FILE = os.path.join(DATA_DIR, 'authorized_user.json') +DATA_DIR = os.path.join(HERE, "data") +SERVICE_ACCOUNT_FILE = os.path.join(DATA_DIR, "service_account.json") +AUTHORIZED_USER_FILE = os.path.join(DATA_DIR, "authorized_user.json") URLLIB3_HTTP = urllib3.PoolManager(retries=False) REQUESTS_SESSION = requests.Session() REQUESTS_SESSION.verify = False -TOKEN_INFO_URL = 'https://www.googleapis.com/oauth2/v3/tokeninfo' +TOKEN_INFO_URL = "https://www.googleapis.com/oauth2/v3/tokeninfo" @pytest.fixture @@ -45,33 +45,34 @@ def authorized_user_file(): yield AUTHORIZED_USER_FILE -@pytest.fixture(params=['urllib3', 'requests']) +@pytest.fixture(params=["urllib3", "requests"]) def http_request(request): """A transport.request object.""" - if request.param == 'urllib3': + if request.param == "urllib3": yield google.auth.transport.urllib3.Request(URLLIB3_HTTP) - elif request.param == 'requests': + elif request.param == "requests": yield google.auth.transport.requests.Request(REQUESTS_SESSION) @pytest.fixture def token_info(http_request): """Returns a function that obtains OAuth2 token info.""" + def _token_info(access_token=None, id_token=None): query_params = {} if access_token is not None: - query_params['access_token'] = access_token + query_params["access_token"] = access_token elif id_token is not None: - query_params['id_token'] = id_token + query_params["id_token"] = id_token else: - raise ValueError('No token specified.') + raise ValueError("No token specified.") url = _helpers.update_query(TOKEN_INFO_URL, query_params) - response = http_request(url=url, method='GET') + response = http_request(url=url, method="GET") - return json.loads(response.data.decode('utf-8')) + return json.loads(response.data.decode("utf-8")) yield _token_info @@ -79,9 +80,10 @@ def _token_info(access_token=None, id_token=None): @pytest.fixture def verify_refresh(http_request): """Returns a function that verifies that credentials can be refreshed.""" + def _verify_refresh(credentials): if credentials.requires_scopes: - credentials = credentials.with_scopes(['email', 'profile']) + credentials = credentials.with_scopes(["email", "profile"]) credentials.refresh(http_request) @@ -95,8 +97,9 @@ def verify_environment(): """Checks to make sure that requisite data files are available.""" if not os.path.isdir(DATA_DIR): raise EnvironmentError( - 'In order to run system tests, test data must exist in ' - 'system_tests/data. See CONTRIBUTING.rst for details.') + "In order to run system tests, test data must exist in " + "system_tests/data. See CONTRIBUTING.rst for details." + ) def pytest_configure(config): diff --git a/packages/google-auth/system_tests/noxfile.py b/packages/google-auth/system_tests/noxfile.py index fa0422ad2c2a..5f9291a2f6d7 100644 --- a/packages/google-auth/system_tests/noxfile.py +++ b/packages/google-auth/system_tests/noxfile.py @@ -30,31 +30,31 @@ HERE = os.path.abspath(os.path.dirname(__file__)) -DATA_DIR = os.path.join(HERE, 'data') -SERVICE_ACCOUNT_FILE = os.path.join(DATA_DIR, 'service_account.json') -AUTHORIZED_USER_FILE = os.path.join(DATA_DIR, 'authorized_user.json') -EXPLICIT_CREDENTIALS_ENV = 'GOOGLE_APPLICATION_CREDENTIALS' -EXPLICIT_PROJECT_ENV = 'GOOGLE_CLOUD_PROJECT' -EXPECT_PROJECT_ENV = 'EXPECT_PROJECT_ID' +DATA_DIR = os.path.join(HERE, "data") +SERVICE_ACCOUNT_FILE = os.path.join(DATA_DIR, "service_account.json") +AUTHORIZED_USER_FILE = os.path.join(DATA_DIR, "authorized_user.json") +EXPLICIT_CREDENTIALS_ENV = "GOOGLE_APPLICATION_CREDENTIALS" +EXPLICIT_PROJECT_ENV = "GOOGLE_CLOUD_PROJECT" +EXPECT_PROJECT_ENV = "EXPECT_PROJECT_ID" -SKIP_GAE_TEST_ENV = 'SKIP_APP_ENGINE_SYSTEM_TEST' -GAE_APP_URL_TMPL = 'https://{}-dot-{}.appspot.com' -GAE_TEST_APP_SERVICE = 'google-auth-system-tests' +SKIP_GAE_TEST_ENV = "SKIP_APP_ENGINE_SYSTEM_TEST" +GAE_APP_URL_TMPL = "https://{}-dot-{}.appspot.com" +GAE_TEST_APP_SERVICE = "google-auth-system-tests" # The download location for the Cloud SDK -CLOUD_SDK_DIST_FILENAME = 'google-cloud-sdk.tar.gz' -CLOUD_SDK_DOWNLOAD_URL = ( - 'https://dl.google.com/dl/cloudsdk/release/{}'.format( - CLOUD_SDK_DIST_FILENAME)) +CLOUD_SDK_DIST_FILENAME = "google-cloud-sdk.tar.gz" +CLOUD_SDK_DOWNLOAD_URL = "https://dl.google.com/dl/cloudsdk/release/{}".format( + CLOUD_SDK_DIST_FILENAME +) # This environment variable is recognized by the Cloud SDK and overrides # the location of the SDK's configuration files (which is usually at # ${HOME}/.config). -CLOUD_SDK_CONFIG_ENV = 'CLOUDSDK_CONFIG' +CLOUD_SDK_CONFIG_ENV = "CLOUDSDK_CONFIG" # If set, this is where the environment setup will install the Cloud SDK. # If unset, it will download the SDK to a temporary directory. -CLOUD_SDK_ROOT = os.environ.get('CLOUD_SDK_ROOT') +CLOUD_SDK_ROOT = os.environ.get("CLOUD_SDK_ROOT") if CLOUD_SDK_ROOT is not None: CLOUD_SDK_ROOT = py.path.local(CLOUD_SDK_ROOT) @@ -63,15 +63,15 @@ CLOUD_SDK_ROOT = py.path.local.mkdtemp() # The full path the cloud sdk install directory -CLOUD_SDK_INSTALL_DIR = CLOUD_SDK_ROOT.join('google-cloud-sdk') +CLOUD_SDK_INSTALL_DIR = CLOUD_SDK_ROOT.join("google-cloud-sdk") # The full path to the gcloud cli executable. -GCLOUD = str(CLOUD_SDK_INSTALL_DIR.join('bin', 'gcloud')) +GCLOUD = str(CLOUD_SDK_INSTALL_DIR.join("bin", "gcloud")) # gcloud requires Python 2 and doesn't work on 3, so we need to tell it # where to find 2 when we're running in a 3 environment. -CLOUD_SDK_PYTHON_ENV = 'CLOUDSDK_PYTHON' -CLOUD_SDK_PYTHON = which('python2', None) +CLOUD_SDK_PYTHON_ENV = "CLOUDSDK_PYTHON" +CLOUD_SDK_PYTHON = which("python2", None) # Cloud SDK helpers @@ -87,46 +87,47 @@ def install_cloud_sdk(session): session.env[CLOUD_SDK_PYTHON_ENV] = CLOUD_SDK_PYTHON # This set the $PATH for the subprocesses so they can find the gcloud # executable. - session.env['PATH'] = ( - str(CLOUD_SDK_INSTALL_DIR.join('bin')) + os.pathsep + - os.environ['PATH']) + session.env["PATH"] = ( + str(CLOUD_SDK_INSTALL_DIR.join("bin")) + os.pathsep + os.environ["PATH"] + ) # If gcloud cli executable already exists, just update it. if py.path.local(GCLOUD).exists(): - session.run(GCLOUD, 'components', 'update', '-q') + session.run(GCLOUD, "components", "update", "-q") return tar_path = CLOUD_SDK_ROOT.join(CLOUD_SDK_DIST_FILENAME) # Download the release. - session.run( - 'wget', CLOUD_SDK_DOWNLOAD_URL, '-O', str(tar_path), silent=True) + session.run("wget", CLOUD_SDK_DOWNLOAD_URL, "-O", str(tar_path), silent=True) # Extract the release. - session.run( - 'tar', 'xzf', str(tar_path), '-C', str(CLOUD_SDK_ROOT)) + session.run("tar", "xzf", str(tar_path), "-C", str(CLOUD_SDK_ROOT)) session.run(tar_path.remove) # Run the install script. session.run( - str(CLOUD_SDK_INSTALL_DIR.join('install.sh')), - '--usage-reporting', 'false', - '--path-update', 'false', - '--command-completion', 'false', - silent=True) + str(CLOUD_SDK_INSTALL_DIR.join("install.sh")), + "--usage-reporting", + "false", + "--path-update", + "false", + "--command-completion", + "false", + silent=True, + ) def copy_credentials(credentials_path): """Copies credentials into the SDK root as the application default credentials.""" - dest = CLOUD_SDK_ROOT.join('application_default_credentials.json') + dest = CLOUD_SDK_ROOT.join("application_default_credentials.json") if dest.exists(): dest.remove() py.path.local(credentials_path).copy(dest) -def configure_cloud_sdk( - session, application_default_credentials, project=False): +def configure_cloud_sdk(session, application_default_credentials, project=False): """Installs and configures the Cloud SDK with the given application default credentials. @@ -140,13 +141,13 @@ def configure_cloud_sdk( # change the application default credentials file, which is user # credentials instead of service account credentials sometimes. session.run( - GCLOUD, 'auth', 'activate-service-account', '--key-file', - SERVICE_ACCOUNT_FILE) + GCLOUD, "auth", "activate-service-account", "--key-file", SERVICE_ACCOUNT_FILE + ) if project: - session.run(GCLOUD, 'config', 'set', 'project', 'example-project') + session.run(GCLOUD, "config", "set", "project", "example-project") else: - session.run(GCLOUD, 'config', 'unset', 'project') + session.run(GCLOUD, "config", "unset", "project") # Copy the credentials file to the config root. This is needed because # unfortunately gcloud doesn't provide a clean way to tell it to use @@ -160,8 +161,8 @@ def configure_cloud_sdk( # that our credentials matches the format expected by gcloud. # Silent is set to True to prevent leaking secrets in test logs. session.run( - GCLOUD, 'auth', 'application-default', 'print-access-token', - silent=True) + GCLOUD, "auth", "application-default", "print-access-token", silent=True + ) # Test sesssions @@ -169,99 +170,102 @@ def configure_cloud_sdk( def session_service_account(session): session.virtualenv = False - session.run('pytest', 'test_service_account.py') + session.run("pytest", "test_service_account.py") def session_oauth2_credentials(session): session.virtualenv = False - session.run('pytest', 'test_oauth2_credentials.py') + session.run("pytest", "test_oauth2_credentials.py") def session_default_explicit_service_account(session): session.virtualenv = False session.env[EXPLICIT_CREDENTIALS_ENV] = SERVICE_ACCOUNT_FILE - session.env[EXPECT_PROJECT_ENV] = '1' - session.run('pytest', 'test_default.py') + session.env[EXPECT_PROJECT_ENV] = "1" + session.run("pytest", "test_default.py") def session_default_explicit_authorized_user(session): session.virtualenv = False session.env[EXPLICIT_CREDENTIALS_ENV] = AUTHORIZED_USER_FILE - session.run('pytest', 'test_default.py') + session.run("pytest", "test_default.py") def session_default_explicit_authorized_user_explicit_project(session): session.virtualenv = False session.env[EXPLICIT_CREDENTIALS_ENV] = AUTHORIZED_USER_FILE - session.env[EXPLICIT_PROJECT_ENV] = 'example-project' - session.env[EXPECT_PROJECT_ENV] = '1' - session.run('pytest', 'test_default.py') + session.env[EXPLICIT_PROJECT_ENV] = "example-project" + session.env[EXPECT_PROJECT_ENV] = "1" + session.run("pytest", "test_default.py") def session_default_cloud_sdk_service_account(session): session.virtualenv = False configure_cloud_sdk(session, SERVICE_ACCOUNT_FILE) - session.env[EXPECT_PROJECT_ENV] = '1' - session.run('pytest', 'test_default.py') + session.env[EXPECT_PROJECT_ENV] = "1" + session.run("pytest", "test_default.py") def session_default_cloud_sdk_authorized_user(session): session.virtualenv = False configure_cloud_sdk(session, AUTHORIZED_USER_FILE) - session.run('pytest', 'test_default.py') + session.run("pytest", "test_default.py") def session_default_cloud_sdk_authorized_user_configured_project(session): session.virtualenv = False configure_cloud_sdk(session, AUTHORIZED_USER_FILE, project=True) - session.env[EXPECT_PROJECT_ENV] = '1' - session.run('pytest', 'test_default.py') + session.env[EXPECT_PROJECT_ENV] = "1" + session.run("pytest", "test_default.py") def session_compute_engine(session): session.virtualenv = False - session.run('pytest', 'test_compute_engine.py') + session.run("pytest", "test_compute_engine.py") def session_app_engine(session): session.virtualenv = False if SKIP_GAE_TEST_ENV in os.environ: - session.log('Skipping App Engine tests.') + session.log("Skipping App Engine tests.") return # Unlike the default tests above, the App Engine system test require a # 'real' gcloud sdk installation that is configured to deploy to an # app engine project. # Grab the project ID from the cloud sdk. - project_id = subprocess.check_output([ - 'gcloud', 'config', 'list', 'project', '--format', - 'value(core.project)']).decode('utf-8').strip() + project_id = ( + subprocess.check_output( + ["gcloud", "config", "list", "project", "--format", "value(core.project)"] + ) + .decode("utf-8") + .strip() + ) if not project_id: session.error( - 'The Cloud SDK must be installed and configured to deploy to App ' - 'Engine.') + "The Cloud SDK must be installed and configured to deploy to App " "Engine." + ) - application_url = GAE_APP_URL_TMPL.format( - GAE_TEST_APP_SERVICE, project_id) + application_url = GAE_APP_URL_TMPL.format(GAE_TEST_APP_SERVICE, project_id) # Vendor in the test application's dependencies - session.chdir(os.path.join(HERE, 'app_engine_test_app')) + session.chdir(os.path.join(HERE, "app_engine_test_app")) session.run( - 'pip', 'install', '--target', 'lib', '-r', 'requirements.txt', - silent=True) + "pip", "install", "--target", "lib", "-r", "requirements.txt", silent=True + ) # Deploy the application. - session.run('gcloud', 'app', 'deploy', '-q', 'app.yaml') + session.run("gcloud", "app", "deploy", "-q", "app.yaml") # Run the tests - session.env['TEST_APP_URL'] = application_url + session.env["TEST_APP_URL"] = application_url session.chdir(HERE) - session.run('pytest', 'test_app_engine.py') + session.run("pytest", "test_app_engine.py") def session_grpc(session): session.virtualenv = False session.env[EXPLICIT_CREDENTIALS_ENV] = SERVICE_ACCOUNT_FILE - session.run('pytest', 'test_grpc.py') + session.run("pytest", "test_grpc.py") diff --git a/packages/google-auth/system_tests/test_app_engine.py b/packages/google-auth/system_tests/test_app_engine.py index 834f9c87021c..cdf2be436bd8 100644 --- a/packages/google-auth/system_tests/test_app_engine.py +++ b/packages/google-auth/system_tests/test_app_engine.py @@ -14,9 +14,9 @@ import os -TEST_APP_URL = os.environ['TEST_APP_URL'] +TEST_APP_URL = os.environ["TEST_APP_URL"] def test_live_application(http_request): - response = http_request(method='GET', url=TEST_APP_URL) - assert response.status == 200, response.data.decode('utf-8') + response = http_request(method="GET", url=TEST_APP_URL) + assert response.status == 200, response.data.decode("utf-8") diff --git a/packages/google-auth/system_tests/test_compute_engine.py b/packages/google-auth/system_tests/test_compute_engine.py index 38733276e22d..3fd420c1eb31 100644 --- a/packages/google-auth/system_tests/test_compute_engine.py +++ b/packages/google-auth/system_tests/test_compute_engine.py @@ -26,7 +26,7 @@ def check_gce_environment(http_request): try: _metadata.get_service_account_info(http_request) except exceptions.TransportError: - pytest.skip('Compute Engine metadata service is not available.') + pytest.skip("Compute Engine metadata service is not available.") def test_refresh(http_request, token_info): @@ -38,7 +38,7 @@ def test_refresh(http_request, token_info): assert credentials.service_account_email is not None info = token_info(credentials.token) - info_scopes = _helpers.string_to_scopes(info['scope']) + info_scopes = _helpers.string_to_scopes(info["scope"]) assert set(info_scopes) == set(credentials.scopes) diff --git a/packages/google-auth/system_tests/test_default.py b/packages/google-auth/system_tests/test_default.py index 23f65435113c..22213e60fa19 100644 --- a/packages/google-auth/system_tests/test_default.py +++ b/packages/google-auth/system_tests/test_default.py @@ -16,7 +16,7 @@ import google.auth -EXPECT_PROJECT_ID = os.environ.get('EXPECT_PROJECT_ID') +EXPECT_PROJECT_ID = os.environ.get("EXPECT_PROJECT_ID") def test_application_default_credentials(verify_refresh): diff --git a/packages/google-auth/system_tests/test_grpc.py b/packages/google-auth/system_tests/test_grpc.py index 365bc91d3481..ea528307cb9c 100644 --- a/packages/google-auth/system_tests/test_grpc.py +++ b/packages/google-auth/system_tests/test_grpc.py @@ -22,61 +22,58 @@ def test_grpc_request_with_regular_credentials(http_request): credentials, project_id = google.auth.default() credentials = google.auth.credentials.with_scopes_if_required( - credentials, ['https://www.googleapis.com/auth/pubsub']) + credentials, ["https://www.googleapis.com/auth/pubsub"] + ) channel = google.auth.transport.grpc.secure_authorized_channel( - credentials, - http_request, - publisher_client.PublisherClient.SERVICE_ADDRESS) + credentials, http_request, publisher_client.PublisherClient.SERVICE_ADDRESS + ) # Create a pub/sub client. client = publisher_client.PublisherClient(channel=channel) # list the topics and drain the iterator to test that an authorized API # call works. - list_topics_iter = client.list_topics( - project='projects/{}'.format(project_id)) + list_topics_iter = client.list_topics(project="projects/{}".format(project_id)) list(list_topics_iter) def test_grpc_request_with_jwt_credentials(): credentials, project_id = google.auth.default() - audience = 'https://{}/google.pubsub.v1.Publisher'.format( - publisher_client.PublisherClient.SERVICE_ADDRESS) + audience = "https://{}/google.pubsub.v1.Publisher".format( + publisher_client.PublisherClient.SERVICE_ADDRESS + ) credentials = google.auth.jwt.Credentials.from_signing_credentials( - credentials, - audience=audience) + credentials, audience=audience + ) channel = google.auth.transport.grpc.secure_authorized_channel( - credentials, - None, - publisher_client.PublisherClient.SERVICE_ADDRESS) + credentials, None, publisher_client.PublisherClient.SERVICE_ADDRESS + ) # Create a pub/sub client. client = publisher_client.PublisherClient(channel=channel) # list the topics and drain the iterator to test that an authorized API # call works. - list_topics_iter = client.list_topics( - project='projects/{}'.format(project_id)) + list_topics_iter = client.list_topics(project="projects/{}".format(project_id)) list(list_topics_iter) def test_grpc_request_with_on_demand_jwt_credentials(): credentials, project_id = google.auth.default() credentials = google.auth.jwt.OnDemandCredentials.from_signing_credentials( - credentials) + credentials + ) channel = google.auth.transport.grpc.secure_authorized_channel( - credentials, - None, - publisher_client.PublisherClient.SERVICE_ADDRESS) + credentials, None, publisher_client.PublisherClient.SERVICE_ADDRESS + ) # Create a pub/sub client. client = publisher_client.PublisherClient(channel=channel) # list the topics and drain the iterator to test that an authorized API # call works. - list_topics_iter = client.list_topics( - project='projects/{}'.format(project_id)) + list_topics_iter = client.list_topics(project="projects/{}".format(project_id)) list(list_topics_iter) diff --git a/packages/google-auth/system_tests/test_oauth2_credentials.py b/packages/google-auth/system_tests/test_oauth2_credentials.py index ded0630d4534..a33b89fba51f 100644 --- a/packages/google-auth/system_tests/test_oauth2_credentials.py +++ b/packages/google-auth/system_tests/test_oauth2_credentials.py @@ -17,19 +17,20 @@ from google.auth import _helpers import google.oauth2.credentials -GOOGLE_OAUTH2_TOKEN_ENDPOINT = 'https://accounts.google.com/o/oauth2/token' +GOOGLE_OAUTH2_TOKEN_ENDPOINT = "https://accounts.google.com/o/oauth2/token" def test_refresh(authorized_user_file, http_request, token_info): - with open(authorized_user_file, 'r') as fh: + with open(authorized_user_file, "r") as fh: info = json.load(fh) credentials = google.oauth2.credentials.Credentials( None, # No access token, must be refreshed. - refresh_token=info['refresh_token'], + refresh_token=info["refresh_token"], token_uri=GOOGLE_OAUTH2_TOKEN_ENDPOINT, - client_id=info['client_id'], - client_secret=info['client_secret']) + client_id=info["client_id"], + client_secret=info["client_secret"], + ) credentials.refresh(http_request) @@ -37,7 +38,10 @@ def test_refresh(authorized_user_file, http_request, token_info): info = token_info(credentials.token) - info_scopes = _helpers.string_to_scopes(info['scope']) - assert set(info_scopes) == set([ - 'https://www.googleapis.com/auth/userinfo.email', - 'https://www.googleapis.com/auth/userinfo.profile']) + info_scopes = _helpers.string_to_scopes(info["scope"]) + assert set(info_scopes) == set( + [ + "https://www.googleapis.com/auth/userinfo.email", + "https://www.googleapis.com/auth/userinfo.profile", + ] + ) diff --git a/packages/google-auth/system_tests/test_service_account.py b/packages/google-auth/system_tests/test_service_account.py index aad1497e9bd3..793760199935 100644 --- a/packages/google-auth/system_tests/test_service_account.py +++ b/packages/google-auth/system_tests/test_service_account.py @@ -21,8 +21,7 @@ @pytest.fixture def credentials(service_account_file): - yield service_account.Credentials.from_service_account_file( - service_account_file) + yield service_account.Credentials.from_service_account_file(service_account_file) def test_refresh_no_scopes(http_request, credentials): @@ -31,7 +30,7 @@ def test_refresh_no_scopes(http_request, credentials): def test_refresh_success(http_request, credentials, token_info): - credentials = credentials.with_scopes(['email', 'profile']) + credentials = credentials.with_scopes(["email", "profile"]) credentials.refresh(http_request) @@ -39,8 +38,11 @@ def test_refresh_success(http_request, credentials, token_info): info = token_info(credentials.token) - assert info['email'] == credentials.service_account_email - info_scopes = _helpers.string_to_scopes(info['scope']) - assert set(info_scopes) == set([ - 'https://www.googleapis.com/auth/userinfo.email', - 'https://www.googleapis.com/auth/userinfo.profile']) + assert info["email"] == credentials.service_account_email + info_scopes = _helpers.string_to_scopes(info["scope"]) + assert set(info_scopes) == set( + [ + "https://www.googleapis.com/auth/userinfo.email", + "https://www.googleapis.com/auth/userinfo.profile", + ] + ) diff --git a/packages/google-auth/tests/compute_engine/test__metadata.py b/packages/google-auth/tests/compute_engine/test__metadata.py index bf48882cad5d..bd06b7402b88 100644 --- a/packages/google-auth/tests/compute_engine/test__metadata.py +++ b/packages/google-auth/tests/compute_engine/test__metadata.py @@ -27,7 +27,7 @@ from google.auth import transport from google.auth.compute_engine import _metadata -PATH = 'instance/service-accounts/default' +PATH = "instance/service-accounts/default" def make_request(data, status=http_client.OK, headers=None): @@ -43,35 +43,35 @@ def make_request(data, status=http_client.OK, headers=None): def test_ping_success(): - request = make_request('', headers=_metadata._METADATA_HEADERS) + request = make_request("", headers=_metadata._METADATA_HEADERS) assert _metadata.ping(request) request.assert_called_once_with( - method='GET', + method="GET", url=_metadata._METADATA_IP_ROOT, headers=_metadata._METADATA_HEADERS, - timeout=_metadata._METADATA_DEFAULT_TIMEOUT) + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, + ) def test_ping_failure_bad_flavor(): - request = make_request( - '', headers={_metadata._METADATA_FLAVOR_HEADER: 'meep'}) + request = make_request("", headers={_metadata._METADATA_FLAVOR_HEADER: "meep"}) assert not _metadata.ping(request) def test_ping_failure_connection_failed(): - request = make_request('') + request = make_request("") request.side_effect = exceptions.TransportError() assert not _metadata.ping(request) def test_ping_success_custom_root(): - request = make_request('', headers=_metadata._METADATA_HEADERS) + request = make_request("", headers=_metadata._METADATA_HEADERS) - fake_ip = '1.2.3.4' + fake_ip = "1.2.3.4" os.environ[environment_vars.GCE_METADATA_IP] = fake_ip reload_module(_metadata) @@ -82,46 +82,47 @@ def test_ping_success_custom_root(): reload_module(_metadata) request.assert_called_once_with( - method='GET', - url='http://' + fake_ip, + method="GET", + url="http://" + fake_ip, headers=_metadata._METADATA_HEADERS, - timeout=_metadata._METADATA_DEFAULT_TIMEOUT) + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, + ) def test_get_success_json(): - key, value = 'foo', 'bar' + key, value = "foo", "bar" data = json.dumps({key: value}) - request = make_request( - data, headers={'content-type': 'application/json'}) + request = make_request(data, headers={"content-type": "application/json"}) result = _metadata.get(request, PATH) request.assert_called_once_with( - method='GET', + method="GET", url=_metadata._METADATA_ROOT + PATH, - headers=_metadata._METADATA_HEADERS) + headers=_metadata._METADATA_HEADERS, + ) assert result[key] == value def test_get_success_text(): - data = 'foobar' - request = make_request(data, headers={'content-type': 'text/plain'}) + data = "foobar" + request = make_request(data, headers={"content-type": "text/plain"}) result = _metadata.get(request, PATH) request.assert_called_once_with( - method='GET', + method="GET", url=_metadata._METADATA_ROOT + PATH, - headers=_metadata._METADATA_HEADERS) + headers=_metadata._METADATA_HEADERS, + ) assert result == data def test_get_success_custom_root(): - request = make_request( - '{}', headers={'content-type': 'application/json'}) + request = make_request("{}", headers={"content-type": "application/json"}) - fake_root = 'another.metadata.service' + fake_root = "another.metadata.service" os.environ[environment_vars.GCE_METADATA_ROOT] = fake_root reload_module(_metadata) @@ -132,83 +133,87 @@ def test_get_success_custom_root(): reload_module(_metadata) request.assert_called_once_with( - method='GET', - url='http://{}/computeMetadata/v1/{}'.format(fake_root, PATH), - headers=_metadata._METADATA_HEADERS) + method="GET", + url="http://{}/computeMetadata/v1/{}".format(fake_root, PATH), + headers=_metadata._METADATA_HEADERS, + ) def test_get_failure(): - request = make_request( - 'Metadata error', status=http_client.NOT_FOUND) + request = make_request("Metadata error", status=http_client.NOT_FOUND) with pytest.raises(exceptions.TransportError) as excinfo: _metadata.get(request, PATH) - assert excinfo.match(r'Metadata error') + assert excinfo.match(r"Metadata error") request.assert_called_once_with( - method='GET', + method="GET", url=_metadata._METADATA_ROOT + PATH, - headers=_metadata._METADATA_HEADERS) + headers=_metadata._METADATA_HEADERS, + ) def test_get_failure_bad_json(): - request = make_request( - '{', headers={'content-type': 'application/json'}) + request = make_request("{", headers={"content-type": "application/json"}) with pytest.raises(exceptions.TransportError) as excinfo: _metadata.get(request, PATH) - assert excinfo.match(r'invalid JSON') + assert excinfo.match(r"invalid JSON") request.assert_called_once_with( - method='GET', + method="GET", url=_metadata._METADATA_ROOT + PATH, - headers=_metadata._METADATA_HEADERS) + headers=_metadata._METADATA_HEADERS, + ) def test_get_project_id(): - project = 'example-project' - request = make_request( - project, headers={'content-type': 'text/plain'}) + project = "example-project" + request = make_request(project, headers={"content-type": "text/plain"}) project_id = _metadata.get_project_id(request) request.assert_called_once_with( - method='GET', - url=_metadata._METADATA_ROOT + 'project/project-id', - headers=_metadata._METADATA_HEADERS) + method="GET", + url=_metadata._METADATA_ROOT + "project/project-id", + headers=_metadata._METADATA_HEADERS, + ) assert project_id == project -@mock.patch('google.auth._helpers.utcnow', return_value=datetime.datetime.min) +@mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) def test_get_service_account_token(utcnow): ttl = 500 request = make_request( - json.dumps({'access_token': 'token', 'expires_in': ttl}), - headers={'content-type': 'application/json'}) + json.dumps({"access_token": "token", "expires_in": ttl}), + headers={"content-type": "application/json"}, + ) token, expiry = _metadata.get_service_account_token(request) request.assert_called_once_with( - method='GET', - url=_metadata._METADATA_ROOT + PATH + '/token', - headers=_metadata._METADATA_HEADERS) - assert token == 'token' + method="GET", + url=_metadata._METADATA_ROOT + PATH + "/token", + headers=_metadata._METADATA_HEADERS, + ) + assert token == "token" assert expiry == utcnow() + datetime.timedelta(seconds=ttl) def test_get_service_account_info(): - key, value = 'foo', 'bar' + key, value = "foo", "bar" request = make_request( - json.dumps({key: value}), - headers={'content-type': 'application/json'}) + json.dumps({key: value}), headers={"content-type": "application/json"} + ) info = _metadata.get_service_account_info(request) request.assert_called_once_with( - method='GET', - url=_metadata._METADATA_ROOT + PATH + '/?recursive=true', - headers=_metadata._METADATA_HEADERS) + method="GET", + url=_metadata._METADATA_ROOT + PATH + "/?recursive=true", + headers=_metadata._METADATA_HEADERS, + ) assert info[key] == value diff --git a/packages/google-auth/tests/compute_engine/test_credentials.py b/packages/google-auth/tests/compute_engine/test_credentials.py index ee415db98780..ec9d13b63f29 100644 --- a/packages/google-auth/tests/compute_engine/test_credentials.py +++ b/packages/google-auth/tests/compute_engine/test_credentials.py @@ -38,68 +38,72 @@ def test_default_state(self): # Scopes aren't needed assert not self.credentials.requires_scopes # Service account email hasn't been populated - assert self.credentials.service_account_email == 'default' + assert self.credentials.service_account_email == "default" @mock.patch( - 'google.auth._helpers.utcnow', - return_value=datetime.datetime.min + _helpers.CLOCK_SKEW) - @mock.patch('google.auth.compute_engine._metadata.get', autospec=True) + "google.auth._helpers.utcnow", + return_value=datetime.datetime.min + _helpers.CLOCK_SKEW, + ) + @mock.patch("google.auth.compute_engine._metadata.get", autospec=True) def test_refresh_success(self, get, utcnow): - get.side_effect = [{ - # First request is for sevice account info. - 'email': 'service-account@example.com', - 'scopes': ['one', 'two'] - }, { - # Second request is for the token. - 'access_token': 'token', - 'expires_in': 500 - }] + get.side_effect = [ + { + # First request is for sevice account info. + "email": "service-account@example.com", + "scopes": ["one", "two"], + }, + { + # Second request is for the token. + "access_token": "token", + "expires_in": 500, + }, + ] # Refresh credentials self.credentials.refresh(None) # Check that the credentials have the token and proper expiration - assert self.credentials.token == 'token' - assert self.credentials.expiry == ( - utcnow() + datetime.timedelta(seconds=500)) + assert self.credentials.token == "token" + assert self.credentials.expiry == (utcnow() + datetime.timedelta(seconds=500)) # Check the credential info - assert (self.credentials.service_account_email == - 'service-account@example.com') - assert self.credentials._scopes == ['one', 'two'] + assert self.credentials.service_account_email == "service-account@example.com" + assert self.credentials._scopes == ["one", "two"] # Check that the credentials are valid (have a token and are not # expired) assert self.credentials.valid - @mock.patch('google.auth.compute_engine._metadata.get', autospec=True) + @mock.patch("google.auth.compute_engine._metadata.get", autospec=True) def test_refresh_error(self, get): - get.side_effect = exceptions.TransportError('http error') + get.side_effect = exceptions.TransportError("http error") with pytest.raises(exceptions.RefreshError) as excinfo: self.credentials.refresh(None) - assert excinfo.match(r'http error') + assert excinfo.match(r"http error") - @mock.patch('google.auth.compute_engine._metadata.get', autospec=True) + @mock.patch("google.auth.compute_engine._metadata.get", autospec=True) def test_before_request_refreshes(self, get): - get.side_effect = [{ - # First request is for sevice account info. - 'email': 'service-account@example.com', - 'scopes': 'one two' - }, { - # Second request is for the token. - 'access_token': 'token', - 'expires_in': 500 - }] + get.side_effect = [ + { + # First request is for sevice account info. + "email": "service-account@example.com", + "scopes": "one two", + }, + { + # Second request is for the token. + "access_token": "token", + "expires_in": 500, + }, + ] # Credentials should start as invalid assert not self.credentials.valid # before_request should cause a refresh request = mock.create_autospec(transport.Request, instance=True) - self.credentials.before_request( - request, 'GET', 'http://example.com?a=1#3', {}) + self.credentials.before_request(request, "GET", "http://example.com?a=1#3", {}) # The refresh endpoint should've been called. assert get.called @@ -111,201 +115,207 @@ def test_before_request_refreshes(self, get): class TestIDTokenCredentials(object): credentials = None - @mock.patch('google.auth.compute_engine._metadata.get', autospec=True) + @mock.patch("google.auth.compute_engine._metadata.get", autospec=True) def test_default_state(self, get): - get.side_effect = [{ - 'email': 'service-account@example.com', - 'scope': ['one', 'two'], - }] + get.side_effect = [ + {"email": "service-account@example.com", "scope": ["one", "two"]} + ] request = mock.create_autospec(transport.Request, instance=True) self.credentials = credentials.IDTokenCredentials( - request=request, target_audience="https://example.com") + request=request, target_audience="https://example.com" + ) assert not self.credentials.valid # Expiration hasn't been set yet assert not self.credentials.expired # Service account email hasn't been populated - assert (self.credentials.service_account_email - == 'service-account@example.com') + assert self.credentials.service_account_email == "service-account@example.com" # Signer is initialized assert self.credentials.signer - assert self.credentials.signer_email == 'service-account@example.com' + assert self.credentials.signer_email == "service-account@example.com" @mock.patch( - 'google.auth._helpers.utcnow', - return_value=datetime.datetime.utcfromtimestamp(0)) - @mock.patch('google.auth.compute_engine._metadata.get', autospec=True) - @mock.patch('google.auth.iam.Signer.sign', autospec=True) + "google.auth._helpers.utcnow", + return_value=datetime.datetime.utcfromtimestamp(0), + ) + @mock.patch("google.auth.compute_engine._metadata.get", autospec=True) + @mock.patch("google.auth.iam.Signer.sign", autospec=True) def test_make_authorization_grant_assertion(self, sign, get, utcnow): - get.side_effect = [{ - 'email': 'service-account@example.com', - 'scopes': ['one', 'two'] - }] - sign.side_effect = [b'signature'] + get.side_effect = [ + {"email": "service-account@example.com", "scopes": ["one", "two"]} + ] + sign.side_effect = [b"signature"] request = mock.create_autospec(transport.Request, instance=True) self.credentials = credentials.IDTokenCredentials( - request=request, target_audience="https://audience.com") + request=request, target_audience="https://audience.com" + ) # Generate authorization grant: token = self.credentials._make_authorization_grant_assertion() payload = jwt.decode(token, verify=False) # The JWT token signature is 'signature' encoded in base 64: - assert token.endswith(b'.c2lnbmF0dXJl') + assert token.endswith(b".c2lnbmF0dXJl") # Check that the credentials have the token and proper expiration assert payload == { - 'aud': 'https://www.googleapis.com/oauth2/v4/token', - 'exp': 3600, - 'iat': 0, - 'iss': 'service-account@example.com', - 'target_audience': 'https://audience.com'} + "aud": "https://www.googleapis.com/oauth2/v4/token", + "exp": 3600, + "iat": 0, + "iss": "service-account@example.com", + "target_audience": "https://audience.com", + } @mock.patch( - 'google.auth._helpers.utcnow', - return_value=datetime.datetime.utcfromtimestamp(0)) - @mock.patch('google.auth.compute_engine._metadata.get', autospec=True) - @mock.patch('google.auth.iam.Signer.sign', autospec=True) + "google.auth._helpers.utcnow", + return_value=datetime.datetime.utcfromtimestamp(0), + ) + @mock.patch("google.auth.compute_engine._metadata.get", autospec=True) + @mock.patch("google.auth.iam.Signer.sign", autospec=True) def test_with_service_account(self, sign, get, utcnow): - sign.side_effect = [b'signature'] + sign.side_effect = [b"signature"] request = mock.create_autospec(transport.Request, instance=True) self.credentials = credentials.IDTokenCredentials( - request=request, target_audience="https://audience.com", - service_account_email="service-account@other.com") + request=request, + target_audience="https://audience.com", + service_account_email="service-account@other.com", + ) # Generate authorization grant: token = self.credentials._make_authorization_grant_assertion() payload = jwt.decode(token, verify=False) # The JWT token signature is 'signature' encoded in base 64: - assert token.endswith(b'.c2lnbmF0dXJl') + assert token.endswith(b".c2lnbmF0dXJl") # Check that the credentials have the token and proper expiration assert payload == { - 'aud': 'https://www.googleapis.com/oauth2/v4/token', - 'exp': 3600, - 'iat': 0, - 'iss': 'service-account@other.com', - 'target_audience': 'https://audience.com'} + "aud": "https://www.googleapis.com/oauth2/v4/token", + "exp": 3600, + "iat": 0, + "iss": "service-account@other.com", + "target_audience": "https://audience.com", + } @mock.patch( - 'google.auth._helpers.utcnow', - return_value=datetime.datetime.utcfromtimestamp(0)) - @mock.patch('google.auth.compute_engine._metadata.get', autospec=True) - @mock.patch('google.auth.iam.Signer.sign', autospec=True) + "google.auth._helpers.utcnow", + return_value=datetime.datetime.utcfromtimestamp(0), + ) + @mock.patch("google.auth.compute_engine._metadata.get", autospec=True) + @mock.patch("google.auth.iam.Signer.sign", autospec=True) def test_additional_claims(self, sign, get, utcnow): - get.side_effect = [{ - 'email': 'service-account@example.com', - 'scopes': ['one', 'two'] - }] - sign.side_effect = [b'signature'] + get.side_effect = [ + {"email": "service-account@example.com", "scopes": ["one", "two"]} + ] + sign.side_effect = [b"signature"] request = mock.create_autospec(transport.Request, instance=True) self.credentials = credentials.IDTokenCredentials( - request=request, target_audience="https://audience.com", - additional_claims={'foo': 'bar'}) + request=request, + target_audience="https://audience.com", + additional_claims={"foo": "bar"}, + ) # Generate authorization grant: token = self.credentials._make_authorization_grant_assertion() payload = jwt.decode(token, verify=False) # The JWT token signature is 'signature' encoded in base 64: - assert token.endswith(b'.c2lnbmF0dXJl') + assert token.endswith(b".c2lnbmF0dXJl") # Check that the credentials have the token and proper expiration assert payload == { - 'aud': 'https://www.googleapis.com/oauth2/v4/token', - 'exp': 3600, - 'iat': 0, - 'iss': 'service-account@example.com', - 'target_audience': 'https://audience.com', - 'foo': 'bar'} + "aud": "https://www.googleapis.com/oauth2/v4/token", + "exp": 3600, + "iat": 0, + "iss": "service-account@example.com", + "target_audience": "https://audience.com", + "foo": "bar", + } @mock.patch( - 'google.auth._helpers.utcnow', - return_value=datetime.datetime.utcfromtimestamp(0)) - @mock.patch('google.auth.compute_engine._metadata.get', autospec=True) - @mock.patch('google.auth.iam.Signer.sign', autospec=True) + "google.auth._helpers.utcnow", + return_value=datetime.datetime.utcfromtimestamp(0), + ) + @mock.patch("google.auth.compute_engine._metadata.get", autospec=True) + @mock.patch("google.auth.iam.Signer.sign", autospec=True) def test_with_target_audience(self, sign, get, utcnow): - get.side_effect = [{ - 'email': 'service-account@example.com', - 'scopes': ['one', 'two'] - }] - sign.side_effect = [b'signature'] + get.side_effect = [ + {"email": "service-account@example.com", "scopes": ["one", "two"]} + ] + sign.side_effect = [b"signature"] request = mock.create_autospec(transport.Request, instance=True) self.credentials = credentials.IDTokenCredentials( - request=request, target_audience="https://audience.com") - self.credentials = ( - self.credentials.with_target_audience("https://actually.not")) + request=request, target_audience="https://audience.com" + ) + self.credentials = self.credentials.with_target_audience("https://actually.not") # Generate authorization grant: token = self.credentials._make_authorization_grant_assertion() payload = jwt.decode(token, verify=False) # The JWT token signature is 'signature' encoded in base 64: - assert token.endswith(b'.c2lnbmF0dXJl') + assert token.endswith(b".c2lnbmF0dXJl") # Check that the credentials have the token and proper expiration assert payload == { - 'aud': 'https://www.googleapis.com/oauth2/v4/token', - 'exp': 3600, - 'iat': 0, - 'iss': 'service-account@example.com', - 'target_audience': 'https://actually.not'} + "aud": "https://www.googleapis.com/oauth2/v4/token", + "exp": 3600, + "iat": 0, + "iss": "service-account@example.com", + "target_audience": "https://actually.not", + } @mock.patch( - 'google.auth._helpers.utcnow', - return_value=datetime.datetime.utcfromtimestamp(0)) - @mock.patch('google.auth.compute_engine._metadata.get', autospec=True) - @mock.patch('google.auth.iam.Signer.sign', autospec=True) - @mock.patch('google.oauth2._client.id_token_jwt_grant', autospec=True) + "google.auth._helpers.utcnow", + return_value=datetime.datetime.utcfromtimestamp(0), + ) + @mock.patch("google.auth.compute_engine._metadata.get", autospec=True) + @mock.patch("google.auth.iam.Signer.sign", autospec=True) + @mock.patch("google.oauth2._client.id_token_jwt_grant", autospec=True) def test_refresh_success(self, id_token_jwt_grant, sign, get, utcnow): - get.side_effect = [{ - 'email': 'service-account@example.com', - 'scopes': ['one', 'two'] - }] - sign.side_effect = [b'signature'] - id_token_jwt_grant.side_effect = [( - 'idtoken', - datetime.datetime.utcfromtimestamp(3600), - {}, - )] + get.side_effect = [ + {"email": "service-account@example.com", "scopes": ["one", "two"]} + ] + sign.side_effect = [b"signature"] + id_token_jwt_grant.side_effect = [ + ("idtoken", datetime.datetime.utcfromtimestamp(3600), {}) + ] request = mock.create_autospec(transport.Request, instance=True) self.credentials = credentials.IDTokenCredentials( - request=request, target_audience="https://audience.com") + request=request, target_audience="https://audience.com" + ) # Refresh credentials self.credentials.refresh(None) # Check that the credentials have the token and proper expiration - assert self.credentials.token == 'idtoken' - assert self.credentials.expiry == ( - datetime.datetime.utcfromtimestamp(3600)) + assert self.credentials.token == "idtoken" + assert self.credentials.expiry == (datetime.datetime.utcfromtimestamp(3600)) # Check the credential info - assert (self.credentials.service_account_email == - 'service-account@example.com') + assert self.credentials.service_account_email == "service-account@example.com" # Check that the credentials are valid (have a token and are not # expired) assert self.credentials.valid @mock.patch( - 'google.auth._helpers.utcnow', - return_value=datetime.datetime.utcfromtimestamp(0)) - @mock.patch('google.auth.compute_engine._metadata.get', autospec=True) - @mock.patch('google.auth.iam.Signer.sign', autospec=True) + "google.auth._helpers.utcnow", + return_value=datetime.datetime.utcfromtimestamp(0), + ) + @mock.patch("google.auth.compute_engine._metadata.get", autospec=True) + @mock.patch("google.auth.iam.Signer.sign", autospec=True) def test_refresh_error(self, sign, get, utcnow): - get.side_effect = [{ - 'email': 'service-account@example.com', - 'scopes': ['one', 'two'], - }] - sign.side_effect = [b'signature'] + get.side_effect = [ + {"email": "service-account@example.com", "scopes": ["one", "two"]} + ] + sign.side_effect = [b"signature"] request = mock.create_autospec(transport.Request, instance=True) response = mock.Mock() @@ -314,43 +324,41 @@ def test_refresh_error(self, sign, get, utcnow): request.side_effect = [response] self.credentials = credentials.IDTokenCredentials( - request=request, target_audience="https://audience.com") + request=request, target_audience="https://audience.com" + ) with pytest.raises(exceptions.RefreshError) as excinfo: self.credentials.refresh(request) - assert excinfo.match(r'http error') + assert excinfo.match(r"http error") @mock.patch( - 'google.auth._helpers.utcnow', - return_value=datetime.datetime.utcfromtimestamp(0)) - @mock.patch('google.auth.compute_engine._metadata.get', autospec=True) - @mock.patch('google.auth.iam.Signer.sign', autospec=True) - @mock.patch('google.oauth2._client.id_token_jwt_grant', autospec=True) - def test_before_request_refreshes( - self, id_token_jwt_grant, sign, get, utcnow): - get.side_effect = [{ - 'email': 'service-account@example.com', - 'scopes': 'one two' - }] - sign.side_effect = [b'signature'] - id_token_jwt_grant.side_effect = [( - 'idtoken', - datetime.datetime.utcfromtimestamp(3600), - {}, - )] + "google.auth._helpers.utcnow", + return_value=datetime.datetime.utcfromtimestamp(0), + ) + @mock.patch("google.auth.compute_engine._metadata.get", autospec=True) + @mock.patch("google.auth.iam.Signer.sign", autospec=True) + @mock.patch("google.oauth2._client.id_token_jwt_grant", autospec=True) + def test_before_request_refreshes(self, id_token_jwt_grant, sign, get, utcnow): + get.side_effect = [ + {"email": "service-account@example.com", "scopes": "one two"} + ] + sign.side_effect = [b"signature"] + id_token_jwt_grant.side_effect = [ + ("idtoken", datetime.datetime.utcfromtimestamp(3600), {}) + ] request = mock.create_autospec(transport.Request, instance=True) self.credentials = credentials.IDTokenCredentials( - request=request, target_audience="https://audience.com") + request=request, target_audience="https://audience.com" + ) # Credentials should start as invalid assert not self.credentials.valid # before_request should cause a refresh request = mock.create_autospec(transport.Request, instance=True) - self.credentials.before_request( - request, 'GET', 'http://example.com?a=1#3', {}) + self.credentials.before_request(request, "GET", "http://example.com?a=1#3", {}) # The refresh endpoint should've been called. assert get.called @@ -358,14 +366,13 @@ def test_before_request_refreshes( # Credentials should now be valid. assert self.credentials.valid - @mock.patch('google.auth.compute_engine._metadata.get', autospec=True) - @mock.patch('google.auth.iam.Signer.sign', autospec=True) + @mock.patch("google.auth.compute_engine._metadata.get", autospec=True) + @mock.patch("google.auth.iam.Signer.sign", autospec=True) def test_sign_bytes(self, sign, get): - get.side_effect = [{ - 'email': 'service-account@example.com', - 'scopes': ['one', 'two'] - }] - sign.side_effect = [b'signature'] + get.side_effect = [ + {"email": "service-account@example.com", "scopes": ["one", "two"]} + ] + sign.side_effect = [b"signature"] request = mock.create_autospec(transport.Request, instance=True) response = mock.Mock() @@ -374,10 +381,11 @@ def test_sign_bytes(self, sign, get): request.side_effect = [response] self.credentials = credentials.IDTokenCredentials( - request=request, target_audience="https://audience.com") + request=request, target_audience="https://audience.com" + ) # Generate authorization grant: signature = self.credentials.sign_bytes(b"some bytes") # The JWT token signature is 'signature' encoded in base 64: - assert signature == b'signature' + assert signature == b"signature" diff --git a/packages/google-auth/tests/conftest.py b/packages/google-auth/tests/conftest.py index c9e3f8492b67..2ccc132e23b1 100644 --- a/packages/google-auth/tests/conftest.py +++ b/packages/google-auth/tests/conftest.py @@ -24,14 +24,14 @@ def mock_non_existent_module(monkeypatch): Additionally mocks any non-existing modules specified in the dotted path. """ + def _mock_non_existent_module(path): - parts = path.split('.') + parts = path.split(".") partial = [] for part in parts: partial.append(part) - current_module = '.'.join(partial) + current_module = ".".join(partial) if current_module not in sys.modules: - monkeypatch.setitem( - sys.modules, current_module, mock.MagicMock()) + monkeypatch.setitem(sys.modules, current_module, mock.MagicMock()) return _mock_non_existent_module diff --git a/packages/google-auth/tests/crypt/test__cryptography_rsa.py b/packages/google-auth/tests/crypt/test__cryptography_rsa.py index a7ebb64a7810..10f926bc7ebc 100644 --- a/packages/google-auth/tests/crypt/test__cryptography_rsa.py +++ b/packages/google-auth/tests/crypt/test__cryptography_rsa.py @@ -23,21 +23,21 @@ from google.auth.crypt import base -DATA_DIR = os.path.join(os.path.dirname(__file__), '..', 'data') +DATA_DIR = os.path.join(os.path.dirname(__file__), "..", "data") # To generate privatekey.pem, privatekey.pub, and public_cert.pem: # $ openssl req -new -newkey rsa:1024 -x509 -nodes -out public_cert.pem \ # > -keyout privatekey.pem # $ openssl rsa -in privatekey.pem -pubout -out privatekey.pub -with open(os.path.join(DATA_DIR, 'privatekey.pem'), 'rb') as fh: +with open(os.path.join(DATA_DIR, "privatekey.pem"), "rb") as fh: PRIVATE_KEY_BYTES = fh.read() PKCS1_KEY_BYTES = PRIVATE_KEY_BYTES -with open(os.path.join(DATA_DIR, 'privatekey.pub'), 'rb') as fh: +with open(os.path.join(DATA_DIR, "privatekey.pub"), "rb") as fh: PUBLIC_KEY_BYTES = fh.read() -with open(os.path.join(DATA_DIR, 'public_cert.pem'), 'rb') as fh: +with open(os.path.join(DATA_DIR, "public_cert.pem"), "rb") as fh: PUBLIC_CERT_BYTES = fh.read() # To generate pem_from_pkcs12.pem and privatekey.p12: @@ -46,22 +46,22 @@ # $ openssl pkcs12 -in privatekey.p12 -nocerts -nodes \ # > -out pem_from_pkcs12.pem -with open(os.path.join(DATA_DIR, 'pem_from_pkcs12.pem'), 'rb') as fh: +with open(os.path.join(DATA_DIR, "pem_from_pkcs12.pem"), "rb") as fh: PKCS8_KEY_BYTES = fh.read() -with open(os.path.join(DATA_DIR, 'privatekey.p12'), 'rb') as fh: +with open(os.path.join(DATA_DIR, "privatekey.p12"), "rb") as fh: PKCS12_KEY_BYTES = fh.read() # The service account JSON file can be generated from the Google Cloud Console. -SERVICE_ACCOUNT_JSON_FILE = os.path.join(DATA_DIR, 'service_account.json') +SERVICE_ACCOUNT_JSON_FILE = os.path.join(DATA_DIR, "service_account.json") -with open(SERVICE_ACCOUNT_JSON_FILE, 'r') as fh: +with open(SERVICE_ACCOUNT_JSON_FILE, "r") as fh: SERVICE_ACCOUNT_INFO = json.load(fh) class TestRSAVerifier(object): def test_verify_success(self): - to_sign = b'foo' + to_sign = b"foo" signer = _cryptography_rsa.RSASigner.from_string(PRIVATE_KEY_BYTES) actual_signature = signer.sign(to_sign) @@ -69,7 +69,7 @@ def test_verify_success(self): assert verifier.verify(to_sign, actual_signature) def test_verify_unicode_success(self): - to_sign = u'foo' + to_sign = u"foo" signer = _cryptography_rsa.RSASigner.from_string(PRIVATE_KEY_BYTES) actual_signature = signer.sign(to_sign) @@ -78,10 +78,10 @@ def test_verify_unicode_success(self): def test_verify_failure(self): verifier = _cryptography_rsa.RSAVerifier.from_string(PUBLIC_KEY_BYTES) - bad_signature1 = b'' - assert not verifier.verify(b'foo', bad_signature1) - bad_signature2 = b'a' - assert not verifier.verify(b'foo', bad_signature2) + bad_signature1 = b"" + assert not verifier.verify(b"foo", bad_signature1) + bad_signature2 = b"a" + assert not verifier.verify(b"foo", bad_signature2) def test_from_string_pub_key(self): verifier = _cryptography_rsa.RSAVerifier.from_string(PUBLIC_KEY_BYTES) @@ -134,16 +134,16 @@ def test_from_string_pkcs12(self): _cryptography_rsa.RSASigner.from_string(PKCS12_KEY_BYTES) def test_from_string_bogus_key(self): - key_bytes = 'bogus-key' + key_bytes = "bogus-key" with pytest.raises(ValueError): _cryptography_rsa.RSASigner.from_string(key_bytes) def test_from_service_account_info(self): signer = _cryptography_rsa.RSASigner.from_service_account_info( - SERVICE_ACCOUNT_INFO) + SERVICE_ACCOUNT_INFO + ) - assert signer.key_id == SERVICE_ACCOUNT_INFO[ - base._JSON_FILE_PRIVATE_KEY_ID] + assert signer.key_id == SERVICE_ACCOUNT_INFO[base._JSON_FILE_PRIVATE_KEY_ID] assert isinstance(signer._key, rsa.RSAPrivateKey) def test_from_service_account_info_missing_key(self): @@ -154,8 +154,8 @@ def test_from_service_account_info_missing_key(self): def test_from_service_account_file(self): signer = _cryptography_rsa.RSASigner.from_service_account_file( - SERVICE_ACCOUNT_JSON_FILE) + SERVICE_ACCOUNT_JSON_FILE + ) - assert signer.key_id == SERVICE_ACCOUNT_INFO[ - base._JSON_FILE_PRIVATE_KEY_ID] + assert signer.key_id == SERVICE_ACCOUNT_INFO[base._JSON_FILE_PRIVATE_KEY_ID] assert isinstance(signer._key, rsa.RSAPrivateKey) diff --git a/packages/google-auth/tests/crypt/test__python_rsa.py b/packages/google-auth/tests/crypt/test__python_rsa.py index d13105f47f92..08b9503a27a6 100644 --- a/packages/google-auth/tests/crypt/test__python_rsa.py +++ b/packages/google-auth/tests/crypt/test__python_rsa.py @@ -26,21 +26,21 @@ from google.auth.crypt import base -DATA_DIR = os.path.join(os.path.dirname(__file__), '..', 'data') +DATA_DIR = os.path.join(os.path.dirname(__file__), "..", "data") # To generate privatekey.pem, privatekey.pub, and public_cert.pem: # $ openssl req -new -newkey rsa:1024 -x509 -nodes -out public_cert.pem \ # > -keyout privatekey.pem # $ openssl rsa -in privatekey.pem -pubout -out privatekey.pub -with open(os.path.join(DATA_DIR, 'privatekey.pem'), 'rb') as fh: +with open(os.path.join(DATA_DIR, "privatekey.pem"), "rb") as fh: PRIVATE_KEY_BYTES = fh.read() PKCS1_KEY_BYTES = PRIVATE_KEY_BYTES -with open(os.path.join(DATA_DIR, 'privatekey.pub'), 'rb') as fh: +with open(os.path.join(DATA_DIR, "privatekey.pub"), "rb") as fh: PUBLIC_KEY_BYTES = fh.read() -with open(os.path.join(DATA_DIR, 'public_cert.pem'), 'rb') as fh: +with open(os.path.join(DATA_DIR, "public_cert.pem"), "rb") as fh: PUBLIC_CERT_BYTES = fh.read() # To generate pem_from_pkcs12.pem and privatekey.p12: @@ -49,22 +49,22 @@ # $ openssl pkcs12 -in privatekey.p12 -nocerts -nodes \ # > -out pem_from_pkcs12.pem -with open(os.path.join(DATA_DIR, 'pem_from_pkcs12.pem'), 'rb') as fh: +with open(os.path.join(DATA_DIR, "pem_from_pkcs12.pem"), "rb") as fh: PKCS8_KEY_BYTES = fh.read() -with open(os.path.join(DATA_DIR, 'privatekey.p12'), 'rb') as fh: +with open(os.path.join(DATA_DIR, "privatekey.p12"), "rb") as fh: PKCS12_KEY_BYTES = fh.read() # The service account JSON file can be generated from the Google Cloud Console. -SERVICE_ACCOUNT_JSON_FILE = os.path.join(DATA_DIR, 'service_account.json') +SERVICE_ACCOUNT_JSON_FILE = os.path.join(DATA_DIR, "service_account.json") -with open(SERVICE_ACCOUNT_JSON_FILE, 'r') as fh: +with open(SERVICE_ACCOUNT_JSON_FILE, "r") as fh: SERVICE_ACCOUNT_INFO = json.load(fh) class TestRSAVerifier(object): def test_verify_success(self): - to_sign = b'foo' + to_sign = b"foo" signer = _python_rsa.RSASigner.from_string(PRIVATE_KEY_BYTES) actual_signature = signer.sign(to_sign) @@ -72,7 +72,7 @@ def test_verify_success(self): assert verifier.verify(to_sign, actual_signature) def test_verify_unicode_success(self): - to_sign = u'foo' + to_sign = u"foo" signer = _python_rsa.RSASigner.from_string(PRIVATE_KEY_BYTES) actual_signature = signer.sign(to_sign) @@ -81,10 +81,10 @@ def test_verify_unicode_success(self): def test_verify_failure(self): verifier = _python_rsa.RSAVerifier.from_string(PUBLIC_KEY_BYTES) - bad_signature1 = b'' - assert not verifier.verify(b'foo', bad_signature1) - bad_signature2 = b'a' - assert not verifier.verify(b'foo', bad_signature2) + bad_signature1 = b"" + assert not verifier.verify(b"foo", bad_signature1) + bad_signature2 = b"a" + assert not verifier.verify(b"foo", bad_signature2) def test_from_string_pub_key(self): verifier = _python_rsa.RSAVerifier.from_string(PUBLIC_KEY_BYTES) @@ -110,15 +110,15 @@ def test_from_string_pub_cert_unicode(self): def test_from_string_pub_cert_failure(self): cert_bytes = PUBLIC_CERT_BYTES - true_der = rsa.pem.load_pem(cert_bytes, 'CERTIFICATE') + true_der = rsa.pem.load_pem(cert_bytes, "CERTIFICATE") load_pem_patch = mock.patch( - 'rsa.pem.load_pem', return_value=true_der + b'extra', - autospec=True) + "rsa.pem.load_pem", return_value=true_der + b"extra", autospec=True + ) with load_pem_patch as load_pem: with pytest.raises(ValueError): _python_rsa.RSAVerifier.from_string(cert_bytes) - load_pem.assert_called_once_with(cert_bytes, 'CERTIFICATE') + load_pem.assert_called_once_with(cert_bytes, "CERTIFICATE") class TestRSASigner(object): @@ -141,21 +141,21 @@ def test_from_string_pkcs8(self): def test_from_string_pkcs8_extra_bytes(self): key_bytes = PKCS8_KEY_BYTES _, pem_bytes = pem.readPemBlocksFromFile( - six.StringIO(_helpers.from_bytes(key_bytes)), - _python_rsa._PKCS8_MARKER) + six.StringIO(_helpers.from_bytes(key_bytes)), _python_rsa._PKCS8_MARKER + ) - key_info, remaining = None, 'extra' + key_info, remaining = None, "extra" decode_patch = mock.patch( - 'pyasn1.codec.der.decoder.decode', + "pyasn1.codec.der.decoder.decode", return_value=(key_info, remaining), - autospec=True) + autospec=True, + ) with decode_patch as decode: with pytest.raises(ValueError): _python_rsa.RSASigner.from_string(key_bytes) # Verify mock was called. - decode.assert_called_once_with( - pem_bytes, asn1Spec=_python_rsa._PKCS8_SPEC) + decode.assert_called_once_with(pem_bytes, asn1Spec=_python_rsa._PKCS8_SPEC) def test_from_string_pkcs8_unicode(self): key_bytes = _helpers.from_bytes(PKCS8_KEY_BYTES) @@ -168,16 +168,14 @@ def test_from_string_pkcs12(self): _python_rsa.RSASigner.from_string(PKCS12_KEY_BYTES) def test_from_string_bogus_key(self): - key_bytes = 'bogus-key' + key_bytes = "bogus-key" with pytest.raises(ValueError): _python_rsa.RSASigner.from_string(key_bytes) def test_from_service_account_info(self): - signer = _python_rsa.RSASigner.from_service_account_info( - SERVICE_ACCOUNT_INFO) + signer = _python_rsa.RSASigner.from_service_account_info(SERVICE_ACCOUNT_INFO) - assert signer.key_id == SERVICE_ACCOUNT_INFO[ - base._JSON_FILE_PRIVATE_KEY_ID] + assert signer.key_id == SERVICE_ACCOUNT_INFO[base._JSON_FILE_PRIVATE_KEY_ID] assert isinstance(signer._key, rsa.key.PrivateKey) def test_from_service_account_info_missing_key(self): @@ -188,8 +186,8 @@ def test_from_service_account_info_missing_key(self): def test_from_service_account_file(self): signer = _python_rsa.RSASigner.from_service_account_file( - SERVICE_ACCOUNT_JSON_FILE) + SERVICE_ACCOUNT_JSON_FILE + ) - assert signer.key_id == SERVICE_ACCOUNT_INFO[ - base._JSON_FILE_PRIVATE_KEY_ID] + assert signer.key_id == SERVICE_ACCOUNT_INFO[base._JSON_FILE_PRIVATE_KEY_ID] assert isinstance(signer._key, rsa.key.PrivateKey) diff --git a/packages/google-auth/tests/crypt/test_crypt.py b/packages/google-auth/tests/crypt/test_crypt.py index d8b1d00a852c..16ff2e0bddc9 100644 --- a/packages/google-auth/tests/crypt/test_crypt.py +++ b/packages/google-auth/tests/crypt/test_crypt.py @@ -17,43 +17,42 @@ from google.auth import crypt -DATA_DIR = os.path.join(os.path.dirname(__file__), '..', 'data') +DATA_DIR = os.path.join(os.path.dirname(__file__), "..", "data") # To generate privatekey.pem, privatekey.pub, and public_cert.pem: # $ openssl req -new -newkey rsa:1024 -x509 -nodes -out public_cert.pem \ # > -keyout privatekey.pem # $ openssl rsa -in privatekey.pem -pubout -out privatekey.pub -with open(os.path.join(DATA_DIR, 'privatekey.pem'), 'rb') as fh: +with open(os.path.join(DATA_DIR, "privatekey.pem"), "rb") as fh: PRIVATE_KEY_BYTES = fh.read() -with open(os.path.join(DATA_DIR, 'public_cert.pem'), 'rb') as fh: +with open(os.path.join(DATA_DIR, "public_cert.pem"), "rb") as fh: PUBLIC_CERT_BYTES = fh.read() # To generate other_cert.pem: # $ openssl req -new -newkey rsa:1024 -x509 -nodes -out other_cert.pem -with open(os.path.join(DATA_DIR, 'other_cert.pem'), 'rb') as fh: +with open(os.path.join(DATA_DIR, "other_cert.pem"), "rb") as fh: OTHER_CERT_BYTES = fh.read() def test_verify_signature(): - to_sign = b'foo' + to_sign = b"foo" signer = crypt.RSASigner.from_string(PRIVATE_KEY_BYTES) signature = signer.sign(to_sign) - assert crypt.verify_signature( - to_sign, signature, PUBLIC_CERT_BYTES) + assert crypt.verify_signature(to_sign, signature, PUBLIC_CERT_BYTES) # List of certs assert crypt.verify_signature( - to_sign, signature, [OTHER_CERT_BYTES, PUBLIC_CERT_BYTES]) + to_sign, signature, [OTHER_CERT_BYTES, PUBLIC_CERT_BYTES] + ) def test_verify_signature_failure(): - to_sign = b'foo' + to_sign = b"foo" signer = crypt.RSASigner.from_string(PRIVATE_KEY_BYTES) signature = signer.sign(to_sign) - assert not crypt.verify_signature( - to_sign, signature, OTHER_CERT_BYTES) + assert not crypt.verify_signature(to_sign, signature, OTHER_CERT_BYTES) diff --git a/packages/google-auth/tests/oauth2/test__client.py b/packages/google-auth/tests/oauth2/test__client.py index 6fc4c3b12b0f..c415a1f44a38 100644 --- a/packages/google-auth/tests/oauth2/test__client.py +++ b/packages/google-auth/tests/oauth2/test__client.py @@ -30,42 +30,44 @@ from google.oauth2 import _client -DATA_DIR = os.path.join(os.path.dirname(__file__), '..', 'data') +DATA_DIR = os.path.join(os.path.dirname(__file__), "..", "data") -with open(os.path.join(DATA_DIR, 'privatekey.pem'), 'rb') as fh: +with open(os.path.join(DATA_DIR, "privatekey.pem"), "rb") as fh: PRIVATE_KEY_BYTES = fh.read() -SIGNER = crypt.RSASigner.from_string(PRIVATE_KEY_BYTES, '1') +SIGNER = crypt.RSASigner.from_string(PRIVATE_KEY_BYTES, "1") -SCOPES_AS_LIST = ['https://www.googleapis.com/auth/pubsub', - 'https://www.googleapis.com/auth/logging.write'] -SCOPES_AS_STRING = ('https://www.googleapis.com/auth/pubsub' - ' https://www.googleapis.com/auth/logging.write') +SCOPES_AS_LIST = [ + "https://www.googleapis.com/auth/pubsub", + "https://www.googleapis.com/auth/logging.write", +] +SCOPES_AS_STRING = ( + "https://www.googleapis.com/auth/pubsub" + " https://www.googleapis.com/auth/logging.write" +) def test__handle_error_response(): - response_data = json.dumps({ - 'error': 'help', - 'error_description': 'I\'m alive'}) + response_data = json.dumps({"error": "help", "error_description": "I'm alive"}) with pytest.raises(exceptions.RefreshError) as excinfo: _client._handle_error_response(response_data) - assert excinfo.match(r'help: I\'m alive') + assert excinfo.match(r"help: I\'m alive") def test__handle_error_response_non_json(): - response_data = 'Help, I\'m alive' + response_data = "Help, I'm alive" with pytest.raises(exceptions.RefreshError) as excinfo: _client._handle_error_response(response_data) - assert excinfo.match(r'Help, I\'m alive') + assert excinfo.match(r"Help, I\'m alive") -@mock.patch('google.auth._helpers.utcnow', return_value=datetime.datetime.min) +@mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) def test__parse_expiry(unused_utcnow): - result = _client._parse_expiry({'expires_in': 500}) + result = _client._parse_expiry({"expires_in": 500}) assert result == datetime.datetime.min + datetime.timedelta(seconds=500) @@ -76,188 +78,214 @@ def test__parse_expiry_none(): def make_request(response_data, status=http_client.OK): response = mock.create_autospec(transport.Response, instance=True) response.status = status - response.data = json.dumps(response_data).encode('utf-8') + response.data = json.dumps(response_data).encode("utf-8") request = mock.create_autospec(transport.Request) request.return_value = response return request def test__token_endpoint_request(): - request = make_request({'test': 'response'}) + request = make_request({"test": "response"}) result = _client._token_endpoint_request( - request, 'http://example.com', {'test': 'params'}) + request, "http://example.com", {"test": "params"} + ) # Check request call request.assert_called_with( - method='POST', - url='http://example.com', - headers={'content-type': 'application/x-www-form-urlencoded'}, - body='test=params') + method="POST", + url="http://example.com", + headers={"content-type": "application/x-www-form-urlencoded"}, + body="test=params", + ) # Check result - assert result == {'test': 'response'} + assert result == {"test": "response"} def test__token_endpoint_request_error(): request = make_request({}, status=http_client.BAD_REQUEST) with pytest.raises(exceptions.RefreshError): - _client._token_endpoint_request(request, 'http://example.com', {}) + _client._token_endpoint_request(request, "http://example.com", {}) def test__token_endpoint_request_internal_failure_error(): - request = make_request({'error': 'internal_failure', - 'error_description': 'internal_failure'}, - status=http_client.BAD_REQUEST) + request = make_request( + {"error": "internal_failure", "error_description": "internal_failure"}, + status=http_client.BAD_REQUEST, + ) with pytest.raises(exceptions.RefreshError): _client._token_endpoint_request( - request, 'http://example.com', - {'error': 'internal_failure', - 'error_description': 'internal_failure'}) + request, + "http://example.com", + {"error": "internal_failure", "error_description": "internal_failure"}, + ) def verify_request_params(request, params): - request_body = request.call_args[1]['body'] + request_body = request.call_args[1]["body"] request_params = urllib.parse.parse_qs(request_body) for key, value in six.iteritems(params): assert request_params[key][0] == value -@mock.patch('google.auth._helpers.utcnow', return_value=datetime.datetime.min) +@mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) def test_jwt_grant(utcnow): - request = make_request({ - 'access_token': 'token', - 'expires_in': 500, - 'extra': 'data'}) + request = make_request( + {"access_token": "token", "expires_in": 500, "extra": "data"} + ) token, expiry, extra_data = _client.jwt_grant( - request, 'http://example.com', 'assertion_value') + request, "http://example.com", "assertion_value" + ) # Check request call - verify_request_params(request, { - 'grant_type': _client._JWT_GRANT_TYPE, - 'assertion': 'assertion_value' - }) + verify_request_params( + request, {"grant_type": _client._JWT_GRANT_TYPE, "assertion": "assertion_value"} + ) # Check result - assert token == 'token' + assert token == "token" assert expiry == utcnow() + datetime.timedelta(seconds=500) - assert extra_data['extra'] == 'data' + assert extra_data["extra"] == "data" def test_jwt_grant_no_access_token(): - request = make_request({ - # No access token. - 'expires_in': 500, - 'extra': 'data'}) + request = make_request( + { + # No access token. + "expires_in": 500, + "extra": "data", + } + ) with pytest.raises(exceptions.RefreshError): - _client.jwt_grant(request, 'http://example.com', 'assertion_value') + _client.jwt_grant(request, "http://example.com", "assertion_value") def test_id_token_jwt_grant(): now = _helpers.utcnow() id_token_expiry = _helpers.datetime_to_secs(now) - id_token = jwt.encode(SIGNER, {'exp': id_token_expiry}).decode('utf-8') - request = make_request({ - 'id_token': id_token, - 'extra': 'data'}) + id_token = jwt.encode(SIGNER, {"exp": id_token_expiry}).decode("utf-8") + request = make_request({"id_token": id_token, "extra": "data"}) token, expiry, extra_data = _client.id_token_jwt_grant( - request, 'http://example.com', 'assertion_value') + request, "http://example.com", "assertion_value" + ) # Check request call - verify_request_params(request, { - 'grant_type': _client._JWT_GRANT_TYPE, - 'assertion': 'assertion_value' - }) + verify_request_params( + request, {"grant_type": _client._JWT_GRANT_TYPE, "assertion": "assertion_value"} + ) # Check result assert token == id_token # JWT does not store microseconds now = now.replace(microsecond=0) assert expiry == now - assert extra_data['extra'] == 'data' + assert extra_data["extra"] == "data" def test_id_token_jwt_grant_no_access_token(): - request = make_request({ - # No access token. - 'expires_in': 500, - 'extra': 'data'}) + request = make_request( + { + # No access token. + "expires_in": 500, + "extra": "data", + } + ) with pytest.raises(exceptions.RefreshError): - _client.id_token_jwt_grant( - request, 'http://example.com', 'assertion_value') + _client.id_token_jwt_grant(request, "http://example.com", "assertion_value") -@mock.patch('google.auth._helpers.utcnow', return_value=datetime.datetime.min) +@mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) def test_refresh_grant(unused_utcnow): - request = make_request({ - 'access_token': 'token', - 'refresh_token': 'new_refresh_token', - 'expires_in': 500, - 'extra': 'data'}) + request = make_request( + { + "access_token": "token", + "refresh_token": "new_refresh_token", + "expires_in": 500, + "extra": "data", + } + ) token, refresh_token, expiry, extra_data = _client.refresh_grant( - request, 'http://example.com', 'refresh_token', 'client_id', - 'client_secret') + request, "http://example.com", "refresh_token", "client_id", "client_secret" + ) # Check request call - verify_request_params(request, { - 'grant_type': _client._REFRESH_GRANT_TYPE, - 'refresh_token': 'refresh_token', - 'client_id': 'client_id', - 'client_secret': 'client_secret' - }) + verify_request_params( + request, + { + "grant_type": _client._REFRESH_GRANT_TYPE, + "refresh_token": "refresh_token", + "client_id": "client_id", + "client_secret": "client_secret", + }, + ) # Check result - assert token == 'token' - assert refresh_token == 'new_refresh_token' + assert token == "token" + assert refresh_token == "new_refresh_token" assert expiry == datetime.datetime.min + datetime.timedelta(seconds=500) - assert extra_data['extra'] == 'data' + assert extra_data["extra"] == "data" -@mock.patch('google.auth._helpers.utcnow', return_value=datetime.datetime.min) +@mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) def test_refresh_grant_with_scopes(unused_utcnow): - request = make_request({ - 'access_token': 'token', - 'refresh_token': 'new_refresh_token', - 'expires_in': 500, - 'extra': 'data', - 'scope': SCOPES_AS_STRING}) + request = make_request( + { + "access_token": "token", + "refresh_token": "new_refresh_token", + "expires_in": 500, + "extra": "data", + "scope": SCOPES_AS_STRING, + } + ) token, refresh_token, expiry, extra_data = _client.refresh_grant( - request, 'http://example.com', 'refresh_token', 'client_id', - 'client_secret', SCOPES_AS_LIST) + request, + "http://example.com", + "refresh_token", + "client_id", + "client_secret", + SCOPES_AS_LIST, + ) # Check request call. - verify_request_params(request, { - 'grant_type': _client._REFRESH_GRANT_TYPE, - 'refresh_token': 'refresh_token', - 'client_id': 'client_id', - 'client_secret': 'client_secret', - 'scope': SCOPES_AS_STRING - }) + verify_request_params( + request, + { + "grant_type": _client._REFRESH_GRANT_TYPE, + "refresh_token": "refresh_token", + "client_id": "client_id", + "client_secret": "client_secret", + "scope": SCOPES_AS_STRING, + }, + ) # Check result. - assert token == 'token' - assert refresh_token == 'new_refresh_token' + assert token == "token" + assert refresh_token == "new_refresh_token" assert expiry == datetime.datetime.min + datetime.timedelta(seconds=500) - assert extra_data['extra'] == 'data' + assert extra_data["extra"] == "data" def test_refresh_grant_no_access_token(): - request = make_request({ - # No access token. - 'refresh_token': 'new_refresh_token', - 'expires_in': 500, - 'extra': 'data'}) + request = make_request( + { + # No access token. + "refresh_token": "new_refresh_token", + "expires_in": 500, + "extra": "data", + } + ) with pytest.raises(exceptions.RefreshError): _client.refresh_grant( - request, 'http://example.com', 'refresh_token', 'client_id', - 'client_secret') + request, "http://example.com", "refresh_token", "client_id", "client_secret" + ) diff --git a/packages/google-auth/tests/oauth2/test_credentials.py b/packages/google-auth/tests/oauth2/test_credentials.py index 32315096a8a3..8bfdd7e0af75 100644 --- a/packages/google-auth/tests/oauth2/test_credentials.py +++ b/packages/google-auth/tests/oauth2/test_credentials.py @@ -25,26 +25,29 @@ from google.oauth2 import credentials -DATA_DIR = os.path.join(os.path.dirname(__file__), '..', 'data') +DATA_DIR = os.path.join(os.path.dirname(__file__), "..", "data") -AUTH_USER_JSON_FILE = os.path.join(DATA_DIR, 'authorized_user.json') +AUTH_USER_JSON_FILE = os.path.join(DATA_DIR, "authorized_user.json") -with open(AUTH_USER_JSON_FILE, 'r') as fh: +with open(AUTH_USER_JSON_FILE, "r") as fh: AUTH_USER_INFO = json.load(fh) class TestCredentials(object): - TOKEN_URI = 'https://example.com/oauth2/token' - REFRESH_TOKEN = 'refresh_token' - CLIENT_ID = 'client_id' - CLIENT_SECRET = 'client_secret' + TOKEN_URI = "https://example.com/oauth2/token" + REFRESH_TOKEN = "refresh_token" + CLIENT_ID = "client_id" + CLIENT_SECRET = "client_secret" @classmethod def make_credentials(cls): return credentials.Credentials( - token=None, refresh_token=cls.REFRESH_TOKEN, - token_uri=cls.TOKEN_URI, client_id=cls.CLIENT_ID, - client_secret=cls.CLIENT_SECRET) + token=None, + refresh_token=cls.REFRESH_TOKEN, + token_uri=cls.TOKEN_URI, + client_id=cls.CLIENT_ID, + client_secret=cls.CLIENT_SECRET, + ) def test_default_state(self): credentials = self.make_credentials() @@ -59,14 +62,15 @@ def test_default_state(self): assert credentials.client_id == self.CLIENT_ID assert credentials.client_secret == self.CLIENT_SECRET - @mock.patch('google.oauth2._client.refresh_grant', autospec=True) + @mock.patch("google.oauth2._client.refresh_grant", autospec=True) @mock.patch( - 'google.auth._helpers.utcnow', - return_value=datetime.datetime.min + _helpers.CLOCK_SKEW) + "google.auth._helpers.utcnow", + return_value=datetime.datetime.min + _helpers.CLOCK_SKEW, + ) def test_refresh_success(self, unused_utcnow, refresh_grant): - token = 'token' + token = "token" expiry = _helpers.utcnow() + datetime.timedelta(seconds=500) - grant_response = {'id_token': mock.sentinel.id_token} + grant_response = {"id_token": mock.sentinel.id_token} refresh_grant.return_value = ( # Access token token, @@ -75,7 +79,8 @@ def test_refresh_success(self, unused_utcnow, refresh_grant): # Expiry, expiry, # Extra data - grant_response) + grant_response, + ) request = mock.create_autospec(transport.Request) credentials = self.make_credentials() @@ -85,8 +90,13 @@ def test_refresh_success(self, unused_utcnow, refresh_grant): # Check jwt grant call. refresh_grant.assert_called_with( - request, self.TOKEN_URI, self.REFRESH_TOKEN, self.CLIENT_ID, - self.CLIENT_SECRET, None) + request, + self.TOKEN_URI, + self.REFRESH_TOKEN, + self.CLIENT_ID, + self.CLIENT_SECRET, + None, + ) # Check that the credentials have the token and expiry assert credentials.token == token @@ -99,24 +109,25 @@ def test_refresh_success(self, unused_utcnow, refresh_grant): def test_refresh_no_refresh_token(self): request = mock.create_autospec(transport.Request) - credentials_ = credentials.Credentials( - token=None, refresh_token=None) + credentials_ = credentials.Credentials(token=None, refresh_token=None) - with pytest.raises(exceptions.RefreshError, match='necessary fields'): + with pytest.raises(exceptions.RefreshError, match="necessary fields"): credentials_.refresh(request) request.assert_not_called() - @mock.patch('google.oauth2._client.refresh_grant', autospec=True) + @mock.patch("google.oauth2._client.refresh_grant", autospec=True) @mock.patch( - 'google.auth._helpers.utcnow', - return_value=datetime.datetime.min + _helpers.CLOCK_SKEW) + "google.auth._helpers.utcnow", + return_value=datetime.datetime.min + _helpers.CLOCK_SKEW, + ) def test_credentials_with_scopes_requested_refresh_success( - self, unused_utcnow, refresh_grant): - scopes = ['email', 'profile'] - token = 'token' + self, unused_utcnow, refresh_grant + ): + scopes = ["email", "profile"] + token = "token" expiry = _helpers.utcnow() + datetime.timedelta(seconds=500) - grant_response = {'id_token': mock.sentinel.id_token} + grant_response = {"id_token": mock.sentinel.id_token} refresh_grant.return_value = ( # Access token token, @@ -125,21 +136,31 @@ def test_credentials_with_scopes_requested_refresh_success( # Expiry, expiry, # Extra data - grant_response) + grant_response, + ) request = mock.create_autospec(transport.Request) creds = credentials.Credentials( - token=None, refresh_token=self.REFRESH_TOKEN, - token_uri=self.TOKEN_URI, client_id=self.CLIENT_ID, - client_secret=self.CLIENT_SECRET, scopes=scopes) + token=None, + refresh_token=self.REFRESH_TOKEN, + token_uri=self.TOKEN_URI, + client_id=self.CLIENT_ID, + client_secret=self.CLIENT_SECRET, + scopes=scopes, + ) # Refresh credentials creds.refresh(request) # Check jwt grant call. refresh_grant.assert_called_with( - request, self.TOKEN_URI, self.REFRESH_TOKEN, self.CLIENT_ID, - self.CLIENT_SECRET, scopes) + request, + self.TOKEN_URI, + self.REFRESH_TOKEN, + self.CLIENT_ID, + self.CLIENT_SECRET, + scopes, + ) # Check that the credentials have the token and expiry assert creds.token == token @@ -151,17 +172,21 @@ def test_credentials_with_scopes_requested_refresh_success( # expired.) assert creds.valid - @mock.patch('google.oauth2._client.refresh_grant', autospec=True) + @mock.patch("google.oauth2._client.refresh_grant", autospec=True) @mock.patch( - 'google.auth._helpers.utcnow', - return_value=datetime.datetime.min + _helpers.CLOCK_SKEW) + "google.auth._helpers.utcnow", + return_value=datetime.datetime.min + _helpers.CLOCK_SKEW, + ) def test_credentials_with_scopes_returned_refresh_success( - self, unused_utcnow, refresh_grant): - scopes = ['email', 'profile'] - token = 'token' + self, unused_utcnow, refresh_grant + ): + scopes = ["email", "profile"] + token = "token" expiry = _helpers.utcnow() + datetime.timedelta(seconds=500) - grant_response = {'id_token': mock.sentinel.id_token, - 'scopes': ' '.join(scopes)} + grant_response = { + "id_token": mock.sentinel.id_token, + "scopes": " ".join(scopes), + } refresh_grant.return_value = ( # Access token token, @@ -170,21 +195,31 @@ def test_credentials_with_scopes_returned_refresh_success( # Expiry, expiry, # Extra data - grant_response) + grant_response, + ) request = mock.create_autospec(transport.Request) creds = credentials.Credentials( - token=None, refresh_token=self.REFRESH_TOKEN, - token_uri=self.TOKEN_URI, client_id=self.CLIENT_ID, - client_secret=self.CLIENT_SECRET, scopes=scopes) + token=None, + refresh_token=self.REFRESH_TOKEN, + token_uri=self.TOKEN_URI, + client_id=self.CLIENT_ID, + client_secret=self.CLIENT_SECRET, + scopes=scopes, + ) # Refresh credentials creds.refresh(request) # Check jwt grant call. refresh_grant.assert_called_with( - request, self.TOKEN_URI, self.REFRESH_TOKEN, self.CLIENT_ID, - self.CLIENT_SECRET, scopes) + request, + self.TOKEN_URI, + self.REFRESH_TOKEN, + self.CLIENT_ID, + self.CLIENT_SECRET, + scopes, + ) # Check that the credentials have the token and expiry assert creds.token == token @@ -196,18 +231,22 @@ def test_credentials_with_scopes_returned_refresh_success( # expired.) assert creds.valid - @mock.patch('google.oauth2._client.refresh_grant', autospec=True) + @mock.patch("google.oauth2._client.refresh_grant", autospec=True) @mock.patch( - 'google.auth._helpers.utcnow', - return_value=datetime.datetime.min + _helpers.CLOCK_SKEW) + "google.auth._helpers.utcnow", + return_value=datetime.datetime.min + _helpers.CLOCK_SKEW, + ) def test_credentials_with_scopes_refresh_failure_raises_refresh_error( - self, unused_utcnow, refresh_grant): - scopes = ['email', 'profile'] - scopes_returned = ['email'] - token = 'token' + self, unused_utcnow, refresh_grant + ): + scopes = ["email", "profile"] + scopes_returned = ["email"] + token = "token" expiry = _helpers.utcnow() + datetime.timedelta(seconds=500) - grant_response = {'id_token': mock.sentinel.id_token, - 'scopes': ' '.join(scopes_returned)} + grant_response = { + "id_token": mock.sentinel.id_token, + "scopes": " ".join(scopes_returned), + } refresh_grant.return_value = ( # Access token token, @@ -216,23 +255,34 @@ def test_credentials_with_scopes_refresh_failure_raises_refresh_error( # Expiry, expiry, # Extra data - grant_response) + grant_response, + ) request = mock.create_autospec(transport.Request) creds = credentials.Credentials( - token=None, refresh_token=self.REFRESH_TOKEN, - token_uri=self.TOKEN_URI, client_id=self.CLIENT_ID, - client_secret=self.CLIENT_SECRET, scopes=scopes) + token=None, + refresh_token=self.REFRESH_TOKEN, + token_uri=self.TOKEN_URI, + client_id=self.CLIENT_ID, + client_secret=self.CLIENT_SECRET, + scopes=scopes, + ) # Refresh credentials - with pytest.raises(exceptions.RefreshError, - match='Not all requested scopes were granted'): + with pytest.raises( + exceptions.RefreshError, match="Not all requested scopes were granted" + ): creds.refresh(request) # Check jwt grant call. refresh_grant.assert_called_with( - request, self.TOKEN_URI, self.REFRESH_TOKEN, self.CLIENT_ID, - self.CLIENT_SECRET, scopes) + request, + self.TOKEN_URI, + self.REFRESH_TOKEN, + self.CLIENT_ID, + self.CLIENT_SECRET, + scopes, + ) # Check that the credentials have the token and expiry assert creds.token == token @@ -248,37 +298,36 @@ def test_from_authorized_user_info(self): info = AUTH_USER_INFO.copy() creds = credentials.Credentials.from_authorized_user_info(info) - assert creds.client_secret == info['client_secret'] - assert creds.client_id == info['client_id'] - assert creds.refresh_token == info['refresh_token'] + assert creds.client_secret == info["client_secret"] + assert creds.client_id == info["client_id"] + assert creds.refresh_token == info["refresh_token"] assert creds.token_uri == credentials._GOOGLE_OAUTH2_TOKEN_ENDPOINT assert creds.scopes is None - scopes = ['email', 'profile'] - creds = credentials.Credentials.from_authorized_user_info( - info, scopes) - assert creds.client_secret == info['client_secret'] - assert creds.client_id == info['client_id'] - assert creds.refresh_token == info['refresh_token'] + scopes = ["email", "profile"] + creds = credentials.Credentials.from_authorized_user_info(info, scopes) + assert creds.client_secret == info["client_secret"] + assert creds.client_id == info["client_id"] + assert creds.refresh_token == info["refresh_token"] assert creds.token_uri == credentials._GOOGLE_OAUTH2_TOKEN_ENDPOINT assert creds.scopes == scopes def test_from_authorized_user_file(self): info = AUTH_USER_INFO.copy() - creds = credentials.Credentials.from_authorized_user_file( - AUTH_USER_JSON_FILE) - assert creds.client_secret == info['client_secret'] - assert creds.client_id == info['client_id'] - assert creds.refresh_token == info['refresh_token'] + creds = credentials.Credentials.from_authorized_user_file(AUTH_USER_JSON_FILE) + assert creds.client_secret == info["client_secret"] + assert creds.client_id == info["client_id"] + assert creds.refresh_token == info["refresh_token"] assert creds.token_uri == credentials._GOOGLE_OAUTH2_TOKEN_ENDPOINT assert creds.scopes is None - scopes = ['email', 'profile'] + scopes = ["email", "profile"] creds = credentials.Credentials.from_authorized_user_file( - AUTH_USER_JSON_FILE, scopes) - assert creds.client_secret == info['client_secret'] - assert creds.client_id == info['client_id'] - assert creds.refresh_token == info['refresh_token'] + AUTH_USER_JSON_FILE, scopes + ) + assert creds.client_secret == info["client_secret"] + assert creds.client_id == info["client_id"] + assert creds.refresh_token == info["refresh_token"] assert creds.token_uri == credentials._GOOGLE_OAUTH2_TOKEN_ENDPOINT assert creds.scopes == scopes diff --git a/packages/google-auth/tests/oauth2/test_id_token.py b/packages/google-auth/tests/oauth2/test_id_token.py index 360f92f53561..980a8e982035 100644 --- a/packages/google-auth/tests/oauth2/test_id_token.py +++ b/packages/google-auth/tests/oauth2/test_id_token.py @@ -27,7 +27,7 @@ def make_request(status, data=None): response.status = status if data is not None: - response.data = json.dumps(data).encode('utf-8') + response.data = json.dumps(data).encode("utf-8") request = mock.create_autospec(transport.Request) request.return_value = response @@ -35,12 +35,12 @@ def make_request(status, data=None): def test__fetch_certs_success(): - certs = {'1': 'cert'} + certs = {"1": "cert"} request = make_request(200, certs) returned_certs = id_token._fetch_certs(request, mock.sentinel.cert_url) - request.assert_called_once_with(mock.sentinel.cert_url, method='GET') + request.assert_called_once_with(mock.sentinel.cert_url, method="GET") assert returned_certs == certs @@ -50,66 +50,67 @@ def test__fetch_certs_failure(): with pytest.raises(exceptions.TransportError): id_token._fetch_certs(request, mock.sentinel.cert_url) - request.assert_called_once_with(mock.sentinel.cert_url, method='GET') + request.assert_called_once_with(mock.sentinel.cert_url, method="GET") -@mock.patch('google.auth.jwt.decode', autospec=True) -@mock.patch('google.oauth2.id_token._fetch_certs', autospec=True) +@mock.patch("google.auth.jwt.decode", autospec=True) +@mock.patch("google.oauth2.id_token._fetch_certs", autospec=True) def test_verify_token(_fetch_certs, decode): result = id_token.verify_token(mock.sentinel.token, mock.sentinel.request) assert result == decode.return_value _fetch_certs.assert_called_once_with( - mock.sentinel.request, id_token._GOOGLE_OAUTH2_CERTS_URL) + mock.sentinel.request, id_token._GOOGLE_OAUTH2_CERTS_URL + ) decode.assert_called_once_with( - mock.sentinel.token, - certs=_fetch_certs.return_value, - audience=None) + mock.sentinel.token, certs=_fetch_certs.return_value, audience=None + ) -@mock.patch('google.auth.jwt.decode', autospec=True) -@mock.patch('google.oauth2.id_token._fetch_certs', autospec=True) +@mock.patch("google.auth.jwt.decode", autospec=True) +@mock.patch("google.oauth2.id_token._fetch_certs", autospec=True) def test_verify_token_args(_fetch_certs, decode): result = id_token.verify_token( mock.sentinel.token, mock.sentinel.request, audience=mock.sentinel.audience, - certs_url=mock.sentinel.certs_url) + certs_url=mock.sentinel.certs_url, + ) assert result == decode.return_value - _fetch_certs.assert_called_once_with( - mock.sentinel.request, mock.sentinel.certs_url) + _fetch_certs.assert_called_once_with(mock.sentinel.request, mock.sentinel.certs_url) decode.assert_called_once_with( mock.sentinel.token, certs=_fetch_certs.return_value, - audience=mock.sentinel.audience) + audience=mock.sentinel.audience, + ) -@mock.patch('google.oauth2.id_token.verify_token', autospec=True) +@mock.patch("google.oauth2.id_token.verify_token", autospec=True) def test_verify_oauth2_token(verify_token): result = id_token.verify_oauth2_token( - mock.sentinel.token, - mock.sentinel.request, - audience=mock.sentinel.audience) + mock.sentinel.token, mock.sentinel.request, audience=mock.sentinel.audience + ) assert result == verify_token.return_value verify_token.assert_called_once_with( mock.sentinel.token, mock.sentinel.request, audience=mock.sentinel.audience, - certs_url=id_token._GOOGLE_OAUTH2_CERTS_URL) + certs_url=id_token._GOOGLE_OAUTH2_CERTS_URL, + ) -@mock.patch('google.oauth2.id_token.verify_token', autospec=True) +@mock.patch("google.oauth2.id_token.verify_token", autospec=True) def test_verify_firebase_token(verify_token): result = id_token.verify_firebase_token( - mock.sentinel.token, - mock.sentinel.request, - audience=mock.sentinel.audience) + mock.sentinel.token, mock.sentinel.request, audience=mock.sentinel.audience + ) assert result == verify_token.return_value verify_token.assert_called_once_with( mock.sentinel.token, mock.sentinel.request, audience=mock.sentinel.audience, - certs_url=id_token._GOOGLE_APIS_CERTS_URL) + certs_url=id_token._GOOGLE_APIS_CERTS_URL, + ) diff --git a/packages/google-auth/tests/oauth2/test_service_account.py b/packages/google-auth/tests/oauth2/test_service_account.py index 54ac0f5e9c98..0f9d4600cedd 100644 --- a/packages/google-auth/tests/oauth2/test_service_account.py +++ b/packages/google-auth/tests/oauth2/test_service_account.py @@ -25,58 +25,58 @@ from google.oauth2 import service_account -DATA_DIR = os.path.join(os.path.dirname(__file__), '..', 'data') +DATA_DIR = os.path.join(os.path.dirname(__file__), "..", "data") -with open(os.path.join(DATA_DIR, 'privatekey.pem'), 'rb') as fh: +with open(os.path.join(DATA_DIR, "privatekey.pem"), "rb") as fh: PRIVATE_KEY_BYTES = fh.read() -with open(os.path.join(DATA_DIR, 'public_cert.pem'), 'rb') as fh: +with open(os.path.join(DATA_DIR, "public_cert.pem"), "rb") as fh: PUBLIC_CERT_BYTES = fh.read() -with open(os.path.join(DATA_DIR, 'other_cert.pem'), 'rb') as fh: +with open(os.path.join(DATA_DIR, "other_cert.pem"), "rb") as fh: OTHER_CERT_BYTES = fh.read() -SERVICE_ACCOUNT_JSON_FILE = os.path.join(DATA_DIR, 'service_account.json') +SERVICE_ACCOUNT_JSON_FILE = os.path.join(DATA_DIR, "service_account.json") -with open(SERVICE_ACCOUNT_JSON_FILE, 'r') as fh: +with open(SERVICE_ACCOUNT_JSON_FILE, "r") as fh: SERVICE_ACCOUNT_INFO = json.load(fh) -SIGNER = crypt.RSASigner.from_string(PRIVATE_KEY_BYTES, '1') +SIGNER = crypt.RSASigner.from_string(PRIVATE_KEY_BYTES, "1") class TestCredentials(object): - SERVICE_ACCOUNT_EMAIL = 'service-account@example.com' - TOKEN_URI = 'https://example.com/oauth2/token' + SERVICE_ACCOUNT_EMAIL = "service-account@example.com" + TOKEN_URI = "https://example.com/oauth2/token" @classmethod def make_credentials(cls): return service_account.Credentials( - SIGNER, cls.SERVICE_ACCOUNT_EMAIL, cls.TOKEN_URI) + SIGNER, cls.SERVICE_ACCOUNT_EMAIL, cls.TOKEN_URI + ) def test_from_service_account_info(self): credentials = service_account.Credentials.from_service_account_info( - SERVICE_ACCOUNT_INFO) + SERVICE_ACCOUNT_INFO + ) - assert (credentials._signer.key_id == - SERVICE_ACCOUNT_INFO['private_key_id']) - assert (credentials.service_account_email == - SERVICE_ACCOUNT_INFO['client_email']) - assert credentials._token_uri == SERVICE_ACCOUNT_INFO['token_uri'] + assert credentials._signer.key_id == SERVICE_ACCOUNT_INFO["private_key_id"] + assert credentials.service_account_email == SERVICE_ACCOUNT_INFO["client_email"] + assert credentials._token_uri == SERVICE_ACCOUNT_INFO["token_uri"] def test_from_service_account_info_args(self): info = SERVICE_ACCOUNT_INFO.copy() - scopes = ['email', 'profile'] - subject = 'subject' - additional_claims = {'meta': 'data'} + scopes = ["email", "profile"] + subject = "subject" + additional_claims = {"meta": "data"} credentials = service_account.Credentials.from_service_account_info( - info, scopes=scopes, subject=subject, - additional_claims=additional_claims) + info, scopes=scopes, subject=subject, additional_claims=additional_claims + ) - assert credentials.service_account_email == info['client_email'] - assert credentials.project_id == info['project_id'] - assert credentials._signer.key_id == info['private_key_id'] - assert credentials._token_uri == info['token_uri'] + assert credentials.service_account_email == info["client_email"] + assert credentials.project_id == info["project_id"] + assert credentials._signer.key_id == info["private_key_id"] + assert credentials._token_uri == info["token_uri"] assert credentials._scopes == scopes assert credentials._subject == subject assert credentials._additional_claims == additional_claims @@ -85,27 +85,31 @@ def test_from_service_account_file(self): info = SERVICE_ACCOUNT_INFO.copy() credentials = service_account.Credentials.from_service_account_file( - SERVICE_ACCOUNT_JSON_FILE) + SERVICE_ACCOUNT_JSON_FILE + ) - assert credentials.service_account_email == info['client_email'] - assert credentials.project_id == info['project_id'] - assert credentials._signer.key_id == info['private_key_id'] - assert credentials._token_uri == info['token_uri'] + assert credentials.service_account_email == info["client_email"] + assert credentials.project_id == info["project_id"] + assert credentials._signer.key_id == info["private_key_id"] + assert credentials._token_uri == info["token_uri"] def test_from_service_account_file_args(self): info = SERVICE_ACCOUNT_INFO.copy() - scopes = ['email', 'profile'] - subject = 'subject' - additional_claims = {'meta': 'data'} + scopes = ["email", "profile"] + subject = "subject" + additional_claims = {"meta": "data"} credentials = service_account.Credentials.from_service_account_file( - SERVICE_ACCOUNT_JSON_FILE, subject=subject, - scopes=scopes, additional_claims=additional_claims) - - assert credentials.service_account_email == info['client_email'] - assert credentials.project_id == info['project_id'] - assert credentials._signer.key_id == info['private_key_id'] - assert credentials._token_uri == info['token_uri'] + SERVICE_ACCOUNT_JSON_FILE, + subject=subject, + scopes=scopes, + additional_claims=additional_claims, + ) + + assert credentials.service_account_email == info["client_email"] + assert credentials.project_id == info["project_id"] + assert credentials._signer.key_id == info["private_key_id"] + assert credentials._token_uri == info["token_uri"] assert credentials._scopes == scopes assert credentials._subject == subject assert credentials._additional_claims == additional_claims @@ -120,7 +124,7 @@ def test_default_state(self): def test_sign_bytes(self): credentials = self.make_credentials() - to_sign = b'123' + to_sign = b"123" signature = credentials.sign_bytes(to_sign) assert crypt.verify_signature(to_sign, signature, PUBLIC_CERT_BYTES) @@ -134,46 +138,47 @@ def test_signer_email(self): def test_create_scoped(self): credentials = self.make_credentials() - scopes = ['email', 'profile'] + scopes = ["email", "profile"] credentials = credentials.with_scopes(scopes) assert credentials._scopes == scopes def test_with_claims(self): credentials = self.make_credentials() - new_credentials = credentials.with_claims({'meep': 'moop'}) - assert new_credentials._additional_claims == {'meep': 'moop'} + new_credentials = credentials.with_claims({"meep": "moop"}) + assert new_credentials._additional_claims == {"meep": "moop"} def test__make_authorization_grant_assertion(self): credentials = self.make_credentials() token = credentials._make_authorization_grant_assertion() payload = jwt.decode(token, PUBLIC_CERT_BYTES) - assert payload['iss'] == self.SERVICE_ACCOUNT_EMAIL - assert payload['aud'] == self.TOKEN_URI + assert payload["iss"] == self.SERVICE_ACCOUNT_EMAIL + assert payload["aud"] == self.TOKEN_URI def test__make_authorization_grant_assertion_scoped(self): credentials = self.make_credentials() - scopes = ['email', 'profile'] + scopes = ["email", "profile"] credentials = credentials.with_scopes(scopes) token = credentials._make_authorization_grant_assertion() payload = jwt.decode(token, PUBLIC_CERT_BYTES) - assert payload['scope'] == 'email profile' + assert payload["scope"] == "email profile" def test__make_authorization_grant_assertion_subject(self): credentials = self.make_credentials() - subject = 'user@example.com' + subject = "user@example.com" credentials = credentials.with_subject(subject) token = credentials._make_authorization_grant_assertion() payload = jwt.decode(token, PUBLIC_CERT_BYTES) - assert payload['sub'] == subject + assert payload["sub"] == subject - @mock.patch('google.oauth2._client.jwt_grant', autospec=True) + @mock.patch("google.oauth2._client.jwt_grant", autospec=True) def test_refresh_success(self, jwt_grant): credentials = self.make_credentials() - token = 'token' + token = "token" jwt_grant.return_value = ( token, _helpers.utcnow() + datetime.timedelta(seconds=500), - {}) + {}, + ) request = mock.create_autospec(transport.Request, instance=True) # Refresh credentials @@ -196,20 +201,22 @@ def test_refresh_success(self, jwt_grant): # expired) assert credentials.valid - @mock.patch('google.oauth2._client.jwt_grant', autospec=True) + @mock.patch("google.oauth2._client.jwt_grant", autospec=True) def test_before_request_refreshes(self, jwt_grant): credentials = self.make_credentials() - token = 'token' + token = "token" jwt_grant.return_value = ( - token, _helpers.utcnow() + datetime.timedelta(seconds=500), None) + token, + _helpers.utcnow() + datetime.timedelta(seconds=500), + None, + ) request = mock.create_autospec(transport.Request, instance=True) # Credentials should start as invalid assert not credentials.valid # before_request should cause a refresh - credentials.before_request( - request, 'GET', 'http://example.com?a=1#3', {}) + credentials.before_request(request, "GET", "http://example.com?a=1#3", {}) # The refresh endpoint should've been called. assert jwt_grant.called @@ -219,40 +226,36 @@ def test_before_request_refreshes(self, jwt_grant): class TestIDTokenCredentials(object): - SERVICE_ACCOUNT_EMAIL = 'service-account@example.com' - TOKEN_URI = 'https://example.com/oauth2/token' - TARGET_AUDIENCE = 'https://example.com' + SERVICE_ACCOUNT_EMAIL = "service-account@example.com" + TOKEN_URI = "https://example.com/oauth2/token" + TARGET_AUDIENCE = "https://example.com" @classmethod def make_credentials(cls): return service_account.IDTokenCredentials( - SIGNER, cls.SERVICE_ACCOUNT_EMAIL, cls.TOKEN_URI, - cls.TARGET_AUDIENCE) + SIGNER, cls.SERVICE_ACCOUNT_EMAIL, cls.TOKEN_URI, cls.TARGET_AUDIENCE + ) def test_from_service_account_info(self): - credentials = ( - service_account.IDTokenCredentials.from_service_account_info( - SERVICE_ACCOUNT_INFO, - target_audience=self.TARGET_AUDIENCE)) - - assert (credentials._signer.key_id == - SERVICE_ACCOUNT_INFO['private_key_id']) - assert (credentials.service_account_email == - SERVICE_ACCOUNT_INFO['client_email']) - assert credentials._token_uri == SERVICE_ACCOUNT_INFO['token_uri'] + credentials = service_account.IDTokenCredentials.from_service_account_info( + SERVICE_ACCOUNT_INFO, target_audience=self.TARGET_AUDIENCE + ) + + assert credentials._signer.key_id == SERVICE_ACCOUNT_INFO["private_key_id"] + assert credentials.service_account_email == SERVICE_ACCOUNT_INFO["client_email"] + assert credentials._token_uri == SERVICE_ACCOUNT_INFO["token_uri"] assert credentials._target_audience == self.TARGET_AUDIENCE def test_from_service_account_file(self): info = SERVICE_ACCOUNT_INFO.copy() - credentials = ( - service_account.IDTokenCredentials.from_service_account_file( - SERVICE_ACCOUNT_JSON_FILE, - target_audience=self.TARGET_AUDIENCE)) + credentials = service_account.IDTokenCredentials.from_service_account_file( + SERVICE_ACCOUNT_JSON_FILE, target_audience=self.TARGET_AUDIENCE + ) - assert credentials.service_account_email == info['client_email'] - assert credentials._signer.key_id == info['private_key_id'] - assert credentials._token_uri == info['token_uri'] + assert credentials.service_account_email == info["client_email"] + assert credentials._signer.key_id == info["private_key_id"] + assert credentials._token_uri == info["token_uri"] assert credentials._target_audience == self.TARGET_AUDIENCE def test_default_state(self): @@ -263,7 +266,7 @@ def test_default_state(self): def test_sign_bytes(self): credentials = self.make_credentials() - to_sign = b'123' + to_sign = b"123" signature = credentials.sign_bytes(to_sign) assert crypt.verify_signature(to_sign, signature, PUBLIC_CERT_BYTES) @@ -277,26 +280,26 @@ def test_signer_email(self): def test_with_target_audience(self): credentials = self.make_credentials() - new_credentials = credentials.with_target_audience( - 'https://new.example.com') - assert new_credentials._target_audience == 'https://new.example.com' + new_credentials = credentials.with_target_audience("https://new.example.com") + assert new_credentials._target_audience == "https://new.example.com" def test__make_authorization_grant_assertion(self): credentials = self.make_credentials() token = credentials._make_authorization_grant_assertion() payload = jwt.decode(token, PUBLIC_CERT_BYTES) - assert payload['iss'] == self.SERVICE_ACCOUNT_EMAIL - assert payload['aud'] == self.TOKEN_URI - assert payload['target_audience'] == self.TARGET_AUDIENCE + assert payload["iss"] == self.SERVICE_ACCOUNT_EMAIL + assert payload["aud"] == self.TOKEN_URI + assert payload["target_audience"] == self.TARGET_AUDIENCE - @mock.patch('google.oauth2._client.id_token_jwt_grant', autospec=True) + @mock.patch("google.oauth2._client.id_token_jwt_grant", autospec=True) def test_refresh_success(self, id_token_jwt_grant): credentials = self.make_credentials() - token = 'token' + token = "token" id_token_jwt_grant.return_value = ( token, _helpers.utcnow() + datetime.timedelta(seconds=500), - {}) + {}, + ) request = mock.create_autospec(transport.Request, instance=True) # Refresh credentials @@ -319,20 +322,22 @@ def test_refresh_success(self, id_token_jwt_grant): # expired) assert credentials.valid - @mock.patch('google.oauth2._client.id_token_jwt_grant', autospec=True) + @mock.patch("google.oauth2._client.id_token_jwt_grant", autospec=True) def test_before_request_refreshes(self, id_token_jwt_grant): credentials = self.make_credentials() - token = 'token' + token = "token" id_token_jwt_grant.return_value = ( - token, _helpers.utcnow() + datetime.timedelta(seconds=500), None) + token, + _helpers.utcnow() + datetime.timedelta(seconds=500), + None, + ) request = mock.create_autospec(transport.Request, instance=True) # Credentials should start as invalid assert not credentials.valid # before_request should cause a refresh - credentials.before_request( - request, 'GET', 'http://example.com?a=1#3', {}) + credentials.before_request(request, "GET", "http://example.com?a=1#3", {}) # The refresh endpoint should've been called. assert id_token_jwt_grant.called diff --git a/packages/google-auth/tests/test__cloud_sdk.py b/packages/google-auth/tests/test__cloud_sdk.py index 58c7270b6b9d..c5c5c2769fb3 100644 --- a/packages/google-auth/tests/test__cloud_sdk.py +++ b/packages/google-auth/tests/test__cloud_sdk.py @@ -25,29 +25,33 @@ import google.oauth2.credentials -DATA_DIR = os.path.join(os.path.dirname(__file__), 'data') -AUTHORIZED_USER_FILE = os.path.join(DATA_DIR, 'authorized_user.json') +DATA_DIR = os.path.join(os.path.dirname(__file__), "data") +AUTHORIZED_USER_FILE = os.path.join(DATA_DIR, "authorized_user.json") with io.open(AUTHORIZED_USER_FILE) as fh: AUTHORIZED_USER_FILE_DATA = json.load(fh) -SERVICE_ACCOUNT_FILE = os.path.join(DATA_DIR, 'service_account.json') +SERVICE_ACCOUNT_FILE = os.path.join(DATA_DIR, "service_account.json") with io.open(SERVICE_ACCOUNT_FILE) as fh: SERVICE_ACCOUNT_FILE_DATA = json.load(fh) -with io.open(os.path.join(DATA_DIR, 'cloud_sdk_config.json'), 'rb') as fh: +with io.open(os.path.join(DATA_DIR, "cloud_sdk_config.json"), "rb") as fh: CLOUD_SDK_CONFIG_FILE_DATA = fh.read() -@pytest.mark.parametrize('data, expected_project_id', [ - (CLOUD_SDK_CONFIG_FILE_DATA, 'example-project'), - (b'I am some bad json', None), - (b'{}', None) -]) +@pytest.mark.parametrize( + "data, expected_project_id", + [ + (CLOUD_SDK_CONFIG_FILE_DATA, "example-project"), + (b"I am some bad json", None), + (b"{}", None), + ], +) def test_get_project_id(data, expected_project_id): check_output_patch = mock.patch( - 'subprocess.check_output', autospec=True, return_value=data) + "subprocess.check_output", autospec=True, return_value=data + ) with check_output_patch as check_output: project_id = _cloud_sdk.get_project_id() @@ -57,100 +61,99 @@ def test_get_project_id(data, expected_project_id): @mock.patch( - 'subprocess.check_output', autospec=True, - side_effect=subprocess.CalledProcessError(-1, None)) + "subprocess.check_output", + autospec=True, + side_effect=subprocess.CalledProcessError(-1, None), +) def test_get_project_id_call_error(check_output): project_id = _cloud_sdk.get_project_id() assert project_id is None assert check_output.called -@mock.patch('os.name', new='nt') +@mock.patch("os.name", new="nt") def test_get_project_id_windows(): check_output_patch = mock.patch( - 'subprocess.check_output', autospec=True, - return_value=CLOUD_SDK_CONFIG_FILE_DATA) + "subprocess.check_output", + autospec=True, + return_value=CLOUD_SDK_CONFIG_FILE_DATA, + ) with check_output_patch as check_output: project_id = _cloud_sdk.get_project_id() - assert project_id == 'example-project' + assert project_id == "example-project" assert check_output.called # Make sure the executable is `gcloud.cmd`. args = check_output.call_args[0] command = args[0] executable = command[0] - assert executable == 'gcloud.cmd' + assert executable == "gcloud.cmd" -@mock.patch( - 'google.auth._cloud_sdk.get_config_path', autospec=True) +@mock.patch("google.auth._cloud_sdk.get_config_path", autospec=True) def test_get_application_default_credentials_path(get_config_dir): - config_path = 'config_path' + config_path = "config_path" get_config_dir.return_value = config_path credentials_path = _cloud_sdk.get_application_default_credentials_path() assert credentials_path == os.path.join( - config_path, _cloud_sdk._CREDENTIALS_FILENAME) + config_path, _cloud_sdk._CREDENTIALS_FILENAME + ) def test_get_config_path_env_var(monkeypatch): - config_path_sentinel = 'config_path' - monkeypatch.setenv( - environment_vars.CLOUD_SDK_CONFIG_DIR, config_path_sentinel) + config_path_sentinel = "config_path" + monkeypatch.setenv(environment_vars.CLOUD_SDK_CONFIG_DIR, config_path_sentinel) config_path = _cloud_sdk.get_config_path() assert config_path == config_path_sentinel -@mock.patch('os.path.expanduser') +@mock.patch("os.path.expanduser") def test_get_config_path_unix(expanduser): expanduser.side_effect = lambda path: path config_path = _cloud_sdk.get_config_path() - assert os.path.split(config_path) == ( - '~/.config', _cloud_sdk._CONFIG_DIRECTORY) + assert os.path.split(config_path) == ("~/.config", _cloud_sdk._CONFIG_DIRECTORY) -@mock.patch('os.name', new='nt') +@mock.patch("os.name", new="nt") def test_get_config_path_windows(monkeypatch): - appdata = 'appdata' + appdata = "appdata" monkeypatch.setenv(_cloud_sdk._WINDOWS_CONFIG_ROOT_ENV_VAR, appdata) config_path = _cloud_sdk.get_config_path() - assert os.path.split(config_path) == ( - appdata, _cloud_sdk._CONFIG_DIRECTORY) + assert os.path.split(config_path) == (appdata, _cloud_sdk._CONFIG_DIRECTORY) -@mock.patch('os.name', new='nt') +@mock.patch("os.name", new="nt") def test_get_config_path_no_appdata(monkeypatch): monkeypatch.delenv(_cloud_sdk._WINDOWS_CONFIG_ROOT_ENV_VAR, raising=False) - monkeypatch.setenv('SystemDrive', 'G:') + monkeypatch.setenv("SystemDrive", "G:") config_path = _cloud_sdk.get_config_path() - assert os.path.split(config_path) == ( - 'G:/\\', _cloud_sdk._CONFIG_DIRECTORY) + assert os.path.split(config_path) == ("G:/\\", _cloud_sdk._CONFIG_DIRECTORY) def test_load_authorized_user_credentials(): - credentials = _cloud_sdk.load_authorized_user_credentials( - AUTHORIZED_USER_FILE_DATA) + credentials = _cloud_sdk.load_authorized_user_credentials(AUTHORIZED_USER_FILE_DATA) assert isinstance(credentials, google.oauth2.credentials.Credentials) assert credentials.token is None - assert (credentials._refresh_token == - AUTHORIZED_USER_FILE_DATA['refresh_token']) - assert credentials._client_id == AUTHORIZED_USER_FILE_DATA['client_id'] - assert (credentials._client_secret == - AUTHORIZED_USER_FILE_DATA['client_secret']) - assert (credentials._token_uri == - google.oauth2.credentials._GOOGLE_OAUTH2_TOKEN_ENDPOINT) + assert credentials._refresh_token == AUTHORIZED_USER_FILE_DATA["refresh_token"] + assert credentials._client_id == AUTHORIZED_USER_FILE_DATA["client_id"] + assert credentials._client_secret == AUTHORIZED_USER_FILE_DATA["client_secret"] + assert ( + credentials._token_uri + == google.oauth2.credentials._GOOGLE_OAUTH2_TOKEN_ENDPOINT + ) def test_load_authorized_user_credentials_bad_format(): with pytest.raises(ValueError) as excinfo: _cloud_sdk.load_authorized_user_credentials({}) - assert excinfo.match(r'missing fields') + assert excinfo.match(r"missing fields") diff --git a/packages/google-auth/tests/test__default.py b/packages/google-auth/tests/test__default.py index d1434796f9a0..2c86f3ffbf56 100644 --- a/packages/google-auth/tests/test__default.py +++ b/packages/google-auth/tests/test__default.py @@ -27,94 +27,96 @@ import google.oauth2.credentials -DATA_DIR = os.path.join(os.path.dirname(__file__), 'data') -AUTHORIZED_USER_FILE = os.path.join(DATA_DIR, 'authorized_user.json') +DATA_DIR = os.path.join(os.path.dirname(__file__), "data") +AUTHORIZED_USER_FILE = os.path.join(DATA_DIR, "authorized_user.json") with open(AUTHORIZED_USER_FILE) as fh: AUTHORIZED_USER_FILE_DATA = json.load(fh) AUTHORIZED_USER_CLOUD_SDK_FILE = os.path.join( - DATA_DIR, 'authorized_user_cloud_sdk.json') + DATA_DIR, "authorized_user_cloud_sdk.json" +) -SERVICE_ACCOUNT_FILE = os.path.join(DATA_DIR, 'service_account.json') +SERVICE_ACCOUNT_FILE = os.path.join(DATA_DIR, "service_account.json") with open(SERVICE_ACCOUNT_FILE) as fh: SERVICE_ACCOUNT_FILE_DATA = json.load(fh) LOAD_FILE_PATCH = mock.patch( - 'google.auth._default._load_credentials_from_file', return_value=( - mock.sentinel.credentials, mock.sentinel.project_id), autospec=True) + "google.auth._default._load_credentials_from_file", + return_value=(mock.sentinel.credentials, mock.sentinel.project_id), + autospec=True, +) def test__load_credentials_from_missing_file(): with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: - _default._load_credentials_from_file('') + _default._load_credentials_from_file("") - assert excinfo.match(r'not found') + assert excinfo.match(r"not found") def test__load_credentials_from_file_invalid_json(tmpdir): - jsonfile = tmpdir.join('invalid.json') - jsonfile.write('{') + jsonfile = tmpdir.join("invalid.json") + jsonfile.write("{") with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: _default._load_credentials_from_file(str(jsonfile)) - assert excinfo.match(r'not a valid json file') + assert excinfo.match(r"not a valid json file") def test__load_credentials_from_file_invalid_type(tmpdir): - jsonfile = tmpdir.join('invalid.json') - jsonfile.write(json.dumps({'type': 'not-a-real-type'})) + jsonfile = tmpdir.join("invalid.json") + jsonfile.write(json.dumps({"type": "not-a-real-type"})) with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: _default._load_credentials_from_file(str(jsonfile)) - assert excinfo.match(r'does not have a valid type') + assert excinfo.match(r"does not have a valid type") def test__load_credentials_from_file_authorized_user(): - credentials, project_id = _default._load_credentials_from_file( - AUTHORIZED_USER_FILE) + credentials, project_id = _default._load_credentials_from_file(AUTHORIZED_USER_FILE) assert isinstance(credentials, google.oauth2.credentials.Credentials) assert project_id is None def test__load_credentials_from_file_authorized_user_bad_format(tmpdir): - filename = tmpdir.join('authorized_user_bad.json') - filename.write(json.dumps({'type': 'authorized_user'})) + filename = tmpdir.join("authorized_user_bad.json") + filename.write(json.dumps({"type": "authorized_user"})) with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: _default._load_credentials_from_file(str(filename)) - assert excinfo.match(r'Failed to load authorized user') - assert excinfo.match(r'missing fields') + assert excinfo.match(r"Failed to load authorized user") + assert excinfo.match(r"missing fields") def test__load_credentials_from_file_authorized_user_cloud_sdk(): - with pytest.warns(UserWarning, match='Cloud SDK'): + with pytest.warns(UserWarning, match="Cloud SDK"): credentials, project_id = _default._load_credentials_from_file( - AUTHORIZED_USER_CLOUD_SDK_FILE) + AUTHORIZED_USER_CLOUD_SDK_FILE + ) assert isinstance(credentials, google.oauth2.credentials.Credentials) assert project_id is None def test__load_credentials_from_file_service_account(): - credentials, project_id = _default._load_credentials_from_file( - SERVICE_ACCOUNT_FILE) + credentials, project_id = _default._load_credentials_from_file(SERVICE_ACCOUNT_FILE) assert isinstance(credentials, service_account.Credentials) - assert project_id == SERVICE_ACCOUNT_FILE_DATA['project_id'] + assert project_id == SERVICE_ACCOUNT_FILE_DATA["project_id"] def test__load_credentials_from_file_service_account_bad_format(tmpdir): - filename = tmpdir.join('serivce_account_bad.json') - filename.write(json.dumps({'type': 'service_account'})) + filename = tmpdir.join("serivce_account_bad.json") + filename.write(json.dumps({"type": "service_account"})) with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: _default._load_credentials_from_file(str(filename)) - assert excinfo.match(r'Failed to load service account') - assert excinfo.match(r'missing fields') + assert excinfo.match(r"Failed to load service account") + assert excinfo.match(r"missing fields") @mock.patch.dict(os.environ, {}, clear=True) @@ -124,19 +126,19 @@ def test__get_explicit_environ_credentials_no_env(): @LOAD_FILE_PATCH def test__get_explicit_environ_credentials(load, monkeypatch): - monkeypatch.setenv(environment_vars.CREDENTIALS, 'filename') + monkeypatch.setenv(environment_vars.CREDENTIALS, "filename") credentials, project_id = _default._get_explicit_environ_credentials() assert credentials is mock.sentinel.credentials assert project_id is mock.sentinel.project_id - load.assert_called_with('filename') + load.assert_called_with("filename") @LOAD_FILE_PATCH def test__get_explicit_environ_credentials_no_project_id(load, monkeypatch): load.return_value = mock.sentinel.credentials, None - monkeypatch.setenv(environment_vars.CREDENTIALS, 'filename') + monkeypatch.setenv(environment_vars.CREDENTIALS, "filename") credentials, project_id = _default._get_explicit_environ_credentials() @@ -146,8 +148,8 @@ def test__get_explicit_environ_credentials_no_project_id(load, monkeypatch): @LOAD_FILE_PATCH @mock.patch( - 'google.auth._cloud_sdk.get_application_default_credentials_path', - autospec=True) + "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True +) def test__get_gcloud_sdk_credentials(get_adc_path, load): get_adc_path.return_value = SERVICE_ACCOUNT_FILE @@ -159,10 +161,10 @@ def test__get_gcloud_sdk_credentials(get_adc_path, load): @mock.patch( - 'google.auth._cloud_sdk.get_application_default_credentials_path', - autospec=True) + "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True +) def test__get_gcloud_sdk_credentials_non_existent(get_adc_path, tmpdir): - non_existent = tmpdir.join('non-existent') + non_existent = tmpdir.join("non-existent") get_adc_path.return_value = str(non_existent) credentials, project_id = _default._get_gcloud_sdk_credentials() @@ -172,12 +174,13 @@ def test__get_gcloud_sdk_credentials_non_existent(get_adc_path, tmpdir): @mock.patch( - 'google.auth._cloud_sdk.get_project_id', - return_value=mock.sentinel.project_id, autospec=True) -@mock.patch('os.path.isfile', return_value=True, autospec=True) + "google.auth._cloud_sdk.get_project_id", + return_value=mock.sentinel.project_id, + autospec=True, +) +@mock.patch("os.path.isfile", return_value=True, autospec=True) @LOAD_FILE_PATCH -def test__get_gcloud_sdk_credentials_project_id( - load, unused_isfile, get_project_id): +def test__get_gcloud_sdk_credentials_project_id(load, unused_isfile, get_project_id): # Don't return a project ID from load file, make the function check # the Cloud SDK project. load.return_value = mock.sentinel.credentials, None @@ -189,13 +192,10 @@ def test__get_gcloud_sdk_credentials_project_id( assert get_project_id.called -@mock.patch( - 'google.auth._cloud_sdk.get_project_id', - return_value=None, autospec=True) -@mock.patch('os.path.isfile', return_value=True) +@mock.patch("google.auth._cloud_sdk.get_project_id", return_value=None, autospec=True) +@mock.patch("os.path.isfile", return_value=True) @LOAD_FILE_PATCH -def test__get_gcloud_sdk_credentials_no_project_id( - load, unused_isfile, get_project_id): +def test__get_gcloud_sdk_credentials_no_project_id(load, unused_isfile, get_project_id): # Don't return a project ID from load file, make the function check # the Cloud SDK project. load.return_value = mock.sentinel.credentials, None @@ -212,6 +212,7 @@ class _AppIdentityModule(object): See https://cloud.google.com/appengine/docs/standard/python/refdocs\ /google.appengine.api.app_identity.app_identity """ + def get_application_id(self): raise NotImplementedError() @@ -219,10 +220,8 @@ def get_application_id(self): @pytest.fixture def app_identity(monkeypatch): """Mocks the app_identity module for google.auth.app_engine.""" - app_identity_module = mock.create_autospec( - _AppIdentityModule, instance=True) - monkeypatch.setattr( - app_engine, 'app_identity', app_identity_module) + app_identity_module = mock.create_autospec(_AppIdentityModule, instance=True) + monkeypatch.setattr(app_engine, "app_identity", app_identity_module) yield app_identity_module @@ -237,8 +236,9 @@ def test__get_gae_credentials(app_identity): def test__get_gae_credentials_no_app_engine(): import sys - with mock.patch.dict('sys.modules'): - sys.modules['google.auth.app_engine'] = None + + with mock.patch.dict("sys.modules"): + sys.modules["google.auth.app_engine"] = None credentials, project_id = _default._get_gae_credentials() assert credentials is None assert project_id is None @@ -249,21 +249,23 @@ def test__get_gae_credentials_no_apis(): @mock.patch( - 'google.auth.compute_engine._metadata.ping', return_value=True, - autospec=True) + "google.auth.compute_engine._metadata.ping", return_value=True, autospec=True +) @mock.patch( - 'google.auth.compute_engine._metadata.get_project_id', - return_value='example-project', autospec=True) + "google.auth.compute_engine._metadata.get_project_id", + return_value="example-project", + autospec=True, +) def test__get_gce_credentials(unused_get, unused_ping): credentials, project_id = _default._get_gce_credentials() assert isinstance(credentials, compute_engine.Credentials) - assert project_id == 'example-project' + assert project_id == "example-project" @mock.patch( - 'google.auth.compute_engine._metadata.ping', return_value=False, - autospec=True) + "google.auth.compute_engine._metadata.ping", return_value=False, autospec=True +) def test__get_gce_credentials_no_ping(unused_ping): credentials, project_id = _default._get_gce_credentials() @@ -272,11 +274,13 @@ def test__get_gce_credentials_no_ping(unused_ping): @mock.patch( - 'google.auth.compute_engine._metadata.ping', return_value=True, - autospec=True) + "google.auth.compute_engine._metadata.ping", return_value=True, autospec=True +) @mock.patch( - 'google.auth.compute_engine._metadata.get_project_id', - side_effect=exceptions.TransportError(), autospec=True) + "google.auth.compute_engine._metadata.get_project_id", + side_effect=exceptions.TransportError(), + autospec=True, +) def test__get_gce_credentials_no_project_id(unused_get, unused_ping): credentials, project_id = _default._get_gce_credentials() @@ -286,110 +290,125 @@ def test__get_gce_credentials_no_project_id(unused_get, unused_ping): def test__get_gce_credentials_no_compute_engine(): import sys - with mock.patch.dict('sys.modules'): - sys.modules['google.auth.compute_engine'] = None + + with mock.patch.dict("sys.modules"): + sys.modules["google.auth.compute_engine"] = None credentials, project_id = _default._get_gce_credentials() assert credentials is None assert project_id is None @mock.patch( - 'google.auth.compute_engine._metadata.ping', return_value=False, - autospec=True) + "google.auth.compute_engine._metadata.ping", return_value=False, autospec=True +) def test__get_gce_credentials_explicit_request(ping): _default._get_gce_credentials(mock.sentinel.request) ping.assert_called_with(request=mock.sentinel.request) @mock.patch( - 'google.auth._default._get_explicit_environ_credentials', + "google.auth._default._get_explicit_environ_credentials", return_value=(mock.sentinel.credentials, mock.sentinel.project_id), - autospec=True) + autospec=True, +) def test_default_early_out(unused_get): - assert _default.default() == ( - mock.sentinel.credentials, mock.sentinel.project_id) + assert _default.default() == (mock.sentinel.credentials, mock.sentinel.project_id) @mock.patch( - 'google.auth._default._get_explicit_environ_credentials', + "google.auth._default._get_explicit_environ_credentials", return_value=(mock.sentinel.credentials, mock.sentinel.project_id), - autospec=True) + autospec=True, +) def test_default_explict_project_id(unused_get, monkeypatch): - monkeypatch.setenv(environment_vars.PROJECT, 'explicit-env') - assert _default.default() == ( - mock.sentinel.credentials, 'explicit-env') + monkeypatch.setenv(environment_vars.PROJECT, "explicit-env") + assert _default.default() == (mock.sentinel.credentials, "explicit-env") @mock.patch( - 'google.auth._default._get_explicit_environ_credentials', + "google.auth._default._get_explicit_environ_credentials", return_value=(mock.sentinel.credentials, mock.sentinel.project_id), - autospec=True) + autospec=True, +) def test_default_explict_legacy_project_id(unused_get, monkeypatch): - monkeypatch.setenv(environment_vars.LEGACY_PROJECT, 'explicit-env') - assert _default.default() == ( - mock.sentinel.credentials, 'explicit-env') + monkeypatch.setenv(environment_vars.LEGACY_PROJECT, "explicit-env") + assert _default.default() == (mock.sentinel.credentials, "explicit-env") +@mock.patch("logging.Logger.warning", autospec=True) @mock.patch( - 'logging.Logger.warning', - autospec=True) -@mock.patch( - 'google.auth._default._get_explicit_environ_credentials', - return_value=(mock.sentinel.credentials, None), autospec=True) + "google.auth._default._get_explicit_environ_credentials", + return_value=(mock.sentinel.credentials, None), + autospec=True, +) @mock.patch( - 'google.auth._default._get_gcloud_sdk_credentials', - return_value=(mock.sentinel.credentials, None), autospec=True) + "google.auth._default._get_gcloud_sdk_credentials", + return_value=(mock.sentinel.credentials, None), + autospec=True, +) @mock.patch( - 'google.auth._default._get_gae_credentials', - return_value=(mock.sentinel.credentials, None), autospec=True) + "google.auth._default._get_gae_credentials", + return_value=(mock.sentinel.credentials, None), + autospec=True, +) @mock.patch( - 'google.auth._default._get_gce_credentials', - return_value=(mock.sentinel.credentials, None), autospec=True) + "google.auth._default._get_gce_credentials", + return_value=(mock.sentinel.credentials, None), + autospec=True, +) def test_default_without_project_id( - unused_gce, unused_gae, unused_sdk, unused_explicit, logger_warning): - assert _default.default() == ( - mock.sentinel.credentials, None) + unused_gce, unused_gae, unused_sdk, unused_explicit, logger_warning +): + assert _default.default() == (mock.sentinel.credentials, None) logger_warning.assert_called_with(mock.ANY, mock.ANY, mock.ANY) @mock.patch( - 'google.auth._default._get_explicit_environ_credentials', - return_value=(None, None), autospec=True) + "google.auth._default._get_explicit_environ_credentials", + return_value=(None, None), + autospec=True, +) @mock.patch( - 'google.auth._default._get_gcloud_sdk_credentials', - return_value=(None, None), autospec=True) + "google.auth._default._get_gcloud_sdk_credentials", + return_value=(None, None), + autospec=True, +) @mock.patch( - 'google.auth._default._get_gae_credentials', - return_value=(None, None), autospec=True) + "google.auth._default._get_gae_credentials", + return_value=(None, None), + autospec=True, +) @mock.patch( - 'google.auth._default._get_gce_credentials', - return_value=(None, None), autospec=True) + "google.auth._default._get_gce_credentials", + return_value=(None, None), + autospec=True, +) def test_default_fail(unused_gce, unused_gae, unused_sdk, unused_explicit): with pytest.raises(exceptions.DefaultCredentialsError): assert _default.default() @mock.patch( - 'google.auth._default._get_explicit_environ_credentials', + "google.auth._default._get_explicit_environ_credentials", return_value=(mock.sentinel.credentials, mock.sentinel.project_id), - autospec=True) -@mock.patch( - 'google.auth.credentials.with_scopes_if_required', autospec=True) + autospec=True, +) +@mock.patch("google.auth.credentials.with_scopes_if_required", autospec=True) def test_default_scoped(with_scopes, unused_get): - scopes = ['one', 'two'] + scopes = ["one", "two"] credentials, project_id = _default.default(scopes=scopes) assert credentials == with_scopes.return_value assert project_id == mock.sentinel.project_id - with_scopes.assert_called_once_with( - mock.sentinel.credentials, scopes) + with_scopes.assert_called_once_with(mock.sentinel.credentials, scopes) @mock.patch( - 'google.auth._default._get_explicit_environ_credentials', + "google.auth._default._get_explicit_environ_credentials", return_value=(mock.sentinel.credentials, mock.sentinel.project_id), - autospec=True) + autospec=True, +) def test_default_no_app_engine_compute_engine_module(unused_get): """ google.auth.compute_engine and google.auth.app_engine are both optional @@ -397,8 +416,11 @@ def test_default_no_app_engine_compute_engine_module(unused_get): that default fails gracefully if these modules are absent """ import sys - with mock.patch.dict('sys.modules'): - sys.modules['google.auth.compute_engine'] = None - sys.modules['google.auth.app_engine'] = None + + with mock.patch.dict("sys.modules"): + sys.modules["google.auth.compute_engine"] = None + sys.modules["google.auth.app_engine"] = None assert _default.default() == ( - mock.sentinel.credentials, mock.sentinel.project_id) + mock.sentinel.credentials, + mock.sentinel.project_id, + ) diff --git a/packages/google-auth/tests/test__helpers.py b/packages/google-auth/tests/test__helpers.py index 79bdef3d7bfd..3714af7d3306 100644 --- a/packages/google-auth/tests/test__helpers.py +++ b/packages/google-auth/tests/test__helpers.py @@ -56,20 +56,18 @@ def test_utcnow(): def test_datetime_to_secs(): - assert _helpers.datetime_to_secs( - datetime.datetime(1970, 1, 1)) == 0 - assert _helpers.datetime_to_secs( - datetime.datetime(1990, 5, 29)) == 643939200 + assert _helpers.datetime_to_secs(datetime.datetime(1970, 1, 1)) == 0 + assert _helpers.datetime_to_secs(datetime.datetime(1990, 5, 29)) == 643939200 def test_to_bytes_with_bytes(): - value = b'bytes-val' + value = b"bytes-val" assert _helpers.to_bytes(value) == value def test_to_bytes_with_unicode(): - value = u'string-val' - encoded_value = b'string-val' + value = u"string-val" + encoded_value = b"string-val" assert _helpers.to_bytes(value) == encoded_value @@ -79,13 +77,13 @@ def test_to_bytes_with_nonstring_type(): def test_from_bytes_with_unicode(): - value = u'bytes-val' + value = u"bytes-val" assert _helpers.from_bytes(value) == value def test_from_bytes_with_bytes(): - value = b'string-val' - decoded_value = u'string-val' + value = b"string-val" + decoded_value = u"string-val" assert _helpers.from_bytes(value) == decoded_value @@ -101,53 +99,49 @@ def _assert_query(url, expected): def test_update_query_params_no_params(): - uri = 'http://www.google.com' - updated = _helpers.update_query(uri, {'a': 'b'}) - assert updated == uri + '?a=b' + uri = "http://www.google.com" + updated = _helpers.update_query(uri, {"a": "b"}) + assert updated == uri + "?a=b" def test_update_query_existing_params(): - uri = 'http://www.google.com?x=y' - updated = _helpers.update_query(uri, {'a': 'b', 'c': 'd&'}) - _assert_query(updated, {'x': ['y'], 'a': ['b'], 'c': ['d&']}) + uri = "http://www.google.com?x=y" + updated = _helpers.update_query(uri, {"a": "b", "c": "d&"}) + _assert_query(updated, {"x": ["y"], "a": ["b"], "c": ["d&"]}) def test_update_query_replace_param(): - base_uri = 'http://www.google.com' - uri = base_uri + '?x=a' - updated = _helpers.update_query(uri, {'x': 'b', 'y': 'c'}) - _assert_query(updated, {'x': ['b'], 'y': ['c']}) + base_uri = "http://www.google.com" + uri = base_uri + "?x=a" + updated = _helpers.update_query(uri, {"x": "b", "y": "c"}) + _assert_query(updated, {"x": ["b"], "y": ["c"]}) def test_update_query_remove_param(): - base_uri = 'http://www.google.com' - uri = base_uri + '?x=a' - updated = _helpers.update_query(uri, {'y': 'c'}, remove=['x']) - _assert_query(updated, {'y': ['c']}) + base_uri = "http://www.google.com" + uri = base_uri + "?x=a" + updated = _helpers.update_query(uri, {"y": "c"}, remove=["x"]) + _assert_query(updated, {"y": ["c"]}) def test_scopes_to_string(): cases = [ - ('', ()), - ('', []), - ('', ('',)), - ('', ['', ]), - ('a', ('a',)), - ('b', ['b', ]), - ('a b', ['a', 'b']), - ('a b', ('a', 'b')), - ('a b', (s for s in ['a', 'b'])), + ("", ()), + ("", []), + ("", ("",)), + ("", [""]), + ("a", ("a",)), + ("b", ["b"]), + ("a b", ["a", "b"]), + ("a b", ("a", "b")), + ("a b", (s for s in ["a", "b"])), ] for expected, case in cases: assert _helpers.scopes_to_string(case) == expected def test_string_to_scopes(): - cases = [ - ('', []), - ('a', ['a']), - ('a b c d e f', ['a', 'b', 'c', 'd', 'e', 'f']), - ] + cases = [("", []), ("a", ["a"]), ("a b c d e f", ["a", "b", "c", "d", "e", "f"])] for case, expected in cases: assert _helpers.string_to_scopes(case) == expected @@ -155,14 +149,14 @@ def test_string_to_scopes(): def test_padded_urlsafe_b64decode(): cases = [ - ('YQ==', b'a'), - ('YQ', b'a'), - ('YWE=', b'aa'), - ('YWE', b'aa'), - ('YWFhYQ==', b'aaaa'), - ('YWFhYQ', b'aaaa'), - ('YWFhYWE=', b'aaaaa'), - ('YWFhYWE', b'aaaaa'), + ("YQ==", b"a"), + ("YQ", b"a"), + ("YWE=", b"aa"), + ("YWE", b"aa"), + ("YWFhYQ==", b"aaaa"), + ("YWFhYQ", b"aaaa"), + ("YWFhYWE=", b"aaaaa"), + ("YWFhYWE", b"aaaaa"), ] for case, expected in cases: @@ -170,12 +164,7 @@ def test_padded_urlsafe_b64decode(): def test_unpadded_urlsafe_b64encode(): - cases = [ - (b'', b''), - (b'a', b'YQ'), - (b'aa', b'YWE'), - (b'aaa', b'YWFh'), - ] + cases = [(b"", b""), (b"a", b"YQ"), (b"aa", b"YWE"), (b"aaa", b"YWFh")] for case, expected in cases: assert _helpers.unpadded_urlsafe_b64encode(case) == expected diff --git a/packages/google-auth/tests/test__oauth2client.py b/packages/google-auth/tests/test__oauth2client.py index 832b24f100e6..520f9432a958 100644 --- a/packages/google-auth/tests/test__oauth2client.py +++ b/packages/google-auth/tests/test__oauth2client.py @@ -26,17 +26,23 @@ from google.auth import _oauth2client -DATA_DIR = os.path.join(os.path.dirname(__file__), 'data') -SERVICE_ACCOUNT_JSON_FILE = os.path.join(DATA_DIR, 'service_account.json') +DATA_DIR = os.path.join(os.path.dirname(__file__), "data") +SERVICE_ACCOUNT_JSON_FILE = os.path.join(DATA_DIR, "service_account.json") def test__convert_oauth2_credentials(): old_credentials = oauth2client.client.OAuth2Credentials( - 'access_token', 'client_id', 'client_secret', 'refresh_token', - datetime.datetime.min, 'token_uri', 'user_agent', scopes='one two') - - new_credentials = _oauth2client._convert_oauth2_credentials( - old_credentials) + "access_token", + "client_id", + "client_secret", + "refresh_token", + datetime.datetime.min, + "token_uri", + "user_agent", + scopes="one two", + ) + + new_credentials = _oauth2client._convert_oauth2_credentials(old_credentials) assert new_credentials.token == old_credentials.access_token assert new_credentials._refresh_token == old_credentials.refresh_token @@ -48,68 +54,74 @@ def test__convert_oauth2_credentials(): def test__convert_service_account_credentials(): old_class = oauth2client.service_account.ServiceAccountCredentials - old_credentials = old_class.from_json_keyfile_name( - SERVICE_ACCOUNT_JSON_FILE) + old_credentials = old_class.from_json_keyfile_name(SERVICE_ACCOUNT_JSON_FILE) new_credentials = _oauth2client._convert_service_account_credentials( - old_credentials) + old_credentials + ) - assert (new_credentials.service_account_email == - old_credentials.service_account_email) + assert ( + new_credentials.service_account_email == old_credentials.service_account_email + ) assert new_credentials._signer.key_id == old_credentials._private_key_id assert new_credentials._token_uri == old_credentials.token_uri def test__convert_service_account_credentials_with_jwt(): old_class = oauth2client.service_account._JWTAccessCredentials - old_credentials = old_class.from_json_keyfile_name( - SERVICE_ACCOUNT_JSON_FILE) + old_credentials = old_class.from_json_keyfile_name(SERVICE_ACCOUNT_JSON_FILE) new_credentials = _oauth2client._convert_service_account_credentials( - old_credentials) + old_credentials + ) - assert (new_credentials.service_account_email == - old_credentials.service_account_email) + assert ( + new_credentials.service_account_email == old_credentials.service_account_email + ) assert new_credentials._signer.key_id == old_credentials._private_key_id assert new_credentials._token_uri == old_credentials.token_uri def test__convert_gce_app_assertion_credentials(): old_credentials = oauth2client.contrib.gce.AppAssertionCredentials( - email='some_email') + email="some_email" + ) new_credentials = _oauth2client._convert_gce_app_assertion_credentials( - old_credentials) + old_credentials + ) - assert (new_credentials.service_account_email == - old_credentials.service_account_email) + assert ( + new_credentials.service_account_email == old_credentials.service_account_email + ) @pytest.fixture def mock_oauth2client_gae_imports(mock_non_existent_module): - mock_non_existent_module('google.appengine.api.app_identity') - mock_non_existent_module('google.appengine.ext.ndb') - mock_non_existent_module('google.appengine.ext.webapp.util') - mock_non_existent_module('webapp2') + mock_non_existent_module("google.appengine.api.app_identity") + mock_non_existent_module("google.appengine.ext.ndb") + mock_non_existent_module("google.appengine.ext.webapp.util") + mock_non_existent_module("webapp2") -@mock.patch('google.auth.app_engine.app_identity') +@mock.patch("google.auth.app_engine.app_identity") def test__convert_appengine_app_assertion_credentials( - app_identity, mock_oauth2client_gae_imports): + app_identity, mock_oauth2client_gae_imports +): import oauth2client.contrib.appengine - service_account_id = 'service_account_id' + service_account_id = "service_account_id" old_credentials = oauth2client.contrib.appengine.AppAssertionCredentials( - scope='one two', service_account_id=service_account_id) + scope="one two", service_account_id=service_account_id + ) - new_credentials = ( - _oauth2client._convert_appengine_app_assertion_credentials( - old_credentials)) + new_credentials = _oauth2client._convert_appengine_app_assertion_credentials( + old_credentials + ) - assert new_credentials.scopes == ['one', 'two'] - assert (new_credentials._service_account_id == - old_credentials.service_account_id) + assert new_credentials.scopes == ["one", "two"] + assert new_credentials._service_account_id == old_credentials.service_account_id class FakeCredentials(object): @@ -117,10 +129,10 @@ class FakeCredentials(object): def test_convert_success(): - convert_function = mock.Mock(spec=['__call__']) + convert_function = mock.Mock(spec=["__call__"]) conversion_map_patch = mock.patch.object( - _oauth2client, '_CLASS_CONVERSION_MAP', - {FakeCredentials: convert_function}) + _oauth2client, "_CLASS_CONVERSION_MAP", {FakeCredentials: convert_function} + ) credentials = FakeCredentials() with conversion_map_patch: @@ -132,9 +144,9 @@ def test_convert_success(): def test_convert_not_found(): with pytest.raises(ValueError) as excinfo: - _oauth2client.convert('a string is not a real credentials class') + _oauth2client.convert("a string is not a real credentials class") - assert excinfo.match('Unable to convert') + assert excinfo.match("Unable to convert") @pytest.fixture @@ -144,14 +156,15 @@ def reset__oauth2client_module(): def test_import_has_app_engine( - mock_oauth2client_gae_imports, reset__oauth2client_module): + mock_oauth2client_gae_imports, reset__oauth2client_module +): reload_module(_oauth2client) assert _oauth2client._HAS_APPENGINE def test_import_without_oauth2client(monkeypatch, reset__oauth2client_module): - monkeypatch.setitem(sys.modules, 'oauth2client', None) + monkeypatch.setitem(sys.modules, "oauth2client", None) with pytest.raises(ImportError) as excinfo: reload_module(_oauth2client) - assert excinfo.match('oauth2client') + assert excinfo.match("oauth2client") diff --git a/packages/google-auth/tests/test__service_account_info.py b/packages/google-auth/tests/test__service_account_info.py index ef41e27571b0..4419f670b728 100644 --- a/packages/google-auth/tests/test__service_account_info.py +++ b/packages/google-auth/tests/test__service_account_info.py @@ -22,42 +22,41 @@ from google.auth import crypt -DATA_DIR = os.path.join(os.path.dirname(__file__), 'data') -SERVICE_ACCOUNT_JSON_FILE = os.path.join(DATA_DIR, 'service_account.json') +DATA_DIR = os.path.join(os.path.dirname(__file__), "data") +SERVICE_ACCOUNT_JSON_FILE = os.path.join(DATA_DIR, "service_account.json") -with open(SERVICE_ACCOUNT_JSON_FILE, 'r') as fh: +with open(SERVICE_ACCOUNT_JSON_FILE, "r") as fh: SERVICE_ACCOUNT_INFO = json.load(fh) def test_from_dict(): signer = _service_account_info.from_dict(SERVICE_ACCOUNT_INFO) assert isinstance(signer, crypt.RSASigner) - assert signer.key_id == SERVICE_ACCOUNT_INFO['private_key_id'] + assert signer.key_id == SERVICE_ACCOUNT_INFO["private_key_id"] def test_from_dict_bad_private_key(): info = SERVICE_ACCOUNT_INFO.copy() - info['private_key'] = 'garbage' + info["private_key"] = "garbage" with pytest.raises(ValueError) as excinfo: _service_account_info.from_dict(info) - assert excinfo.match(r'key') + assert excinfo.match(r"key") def test_from_dict_bad_format(): with pytest.raises(ValueError) as excinfo: - _service_account_info.from_dict({}, require=('meep',)) + _service_account_info.from_dict({}, require=("meep",)) - assert excinfo.match(r'missing fields') + assert excinfo.match(r"missing fields") def test_from_filename(): - info, signer = _service_account_info.from_filename( - SERVICE_ACCOUNT_JSON_FILE) + info, signer = _service_account_info.from_filename(SERVICE_ACCOUNT_JSON_FILE) for key, value in six.iteritems(SERVICE_ACCOUNT_INFO): assert info[key] == value assert isinstance(signer, crypt.RSASigner) - assert signer.key_id == SERVICE_ACCOUNT_INFO['private_key_id'] + assert signer.key_id == SERVICE_ACCOUNT_INFO["private_key_id"] diff --git a/packages/google-auth/tests/test_app_engine.py b/packages/google-auth/tests/test_app_engine.py index 70fa5ca3352a..9559a2cd22f6 100644 --- a/packages/google-auth/tests/test_app_engine.py +++ b/packages/google-auth/tests/test_app_engine.py @@ -25,6 +25,7 @@ class _AppIdentityModule(object): See https://cloud.google.com/appengine/docs/standard/python/refdocs /google.appengine.api.app_identity.app_identity """ + def get_application_id(self): raise NotImplementedError() @@ -41,10 +42,8 @@ def get_access_token(self, scopes, service_account_id=None): @pytest.fixture def app_identity(monkeypatch): """Mocks the app_identity module for google.auth.app_engine.""" - app_identity_module = mock.create_autospec( - _AppIdentityModule, instance=True) - monkeypatch.setattr( - app_engine, 'app_identity', app_identity_module) + app_identity_module = mock.create_autospec(_AppIdentityModule, instance=True) + monkeypatch.setattr(app_engine, "app_identity", app_identity_module) yield app_identity_module @@ -57,13 +56,15 @@ def test_get_project_id_missing_apis(): with pytest.raises(EnvironmentError) as excinfo: assert app_engine.get_project_id() - assert excinfo.match(r'App Engine APIs are not available') + assert excinfo.match(r"App Engine APIs are not available") class TestSigner(object): def test_key_id(self, app_identity): app_identity.sign_blob.return_value = ( - mock.sentinel.key_id, mock.sentinel.signature) + mock.sentinel.key_id, + mock.sentinel.signature, + ) signer = app_engine.Signer() @@ -71,10 +72,12 @@ def test_key_id(self, app_identity): def test_sign(self, app_identity): app_identity.sign_blob.return_value = ( - mock.sentinel.key_id, mock.sentinel.signature) + mock.sentinel.key_id, + mock.sentinel.signature, + ) signer = app_engine.Signer() - to_sign = b'123' + to_sign = b"123" signature = signer.sign(to_sign) @@ -87,7 +90,7 @@ def test_missing_apis(self): with pytest.raises(EnvironmentError) as excinfo: app_engine.Credentials() - assert excinfo.match(r'App Engine APIs are not available') + assert excinfo.match(r"App Engine APIs are not available") def test_default_state(self, app_identity): credentials = app_engine.Credentials() @@ -106,52 +109,52 @@ def test_with_scopes(self, app_identity): assert not credentials.scopes assert credentials.requires_scopes - scoped_credentials = credentials.with_scopes(['email']) + scoped_credentials = credentials.with_scopes(["email"]) - assert scoped_credentials.has_scopes(['email']) + assert scoped_credentials.has_scopes(["email"]) assert not scoped_credentials.requires_scopes def test_service_account_email_implicit(self, app_identity): app_identity.get_service_account_name.return_value = ( - mock.sentinel.service_account_email) + mock.sentinel.service_account_email + ) credentials = app_engine.Credentials() - assert (credentials.service_account_email == - mock.sentinel.service_account_email) + assert credentials.service_account_email == mock.sentinel.service_account_email assert app_identity.get_service_account_name.called def test_service_account_email_explicit(self, app_identity): credentials = app_engine.Credentials( - service_account_id=mock.sentinel.service_account_email) + service_account_id=mock.sentinel.service_account_email + ) - assert (credentials.service_account_email == - mock.sentinel.service_account_email) + assert credentials.service_account_email == mock.sentinel.service_account_email assert not app_identity.get_service_account_name.called - @mock.patch( - 'google.auth._helpers.utcnow', - return_value=datetime.datetime.min) + @mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) def test_refresh(self, utcnow, app_identity): - token = 'token' + token = "token" ttl = 643942923 app_identity.get_access_token.return_value = token, ttl - credentials = app_engine.Credentials(scopes=['email']) + credentials = app_engine.Credentials(scopes=["email"]) credentials.refresh(None) app_identity.get_access_token.assert_called_with( - credentials.scopes, credentials._service_account_id) + credentials.scopes, credentials._service_account_id + ) assert credentials.token == token - assert credentials.expiry == datetime.datetime( - 1990, 5, 29, 1, 2, 3) + assert credentials.expiry == datetime.datetime(1990, 5, 29, 1, 2, 3) assert credentials.valid assert not credentials.expired def test_sign_bytes(self, app_identity): app_identity.sign_blob.return_value = ( - mock.sentinel.key_id, mock.sentinel.signature) + mock.sentinel.key_id, + mock.sentinel.signature, + ) credentials = app_engine.Credentials() - to_sign = b'123' + to_sign = b"123" signature = credentials.sign_bytes(to_sign) diff --git a/packages/google-auth/tests/test_credentials.py b/packages/google-auth/tests/test_credentials.py index b302989fbf19..2a89b01b6b66 100644 --- a/packages/google-auth/tests/test_credentials.py +++ b/packages/google-auth/tests/test_credentials.py @@ -35,7 +35,7 @@ def test_credentials_constructor(): def test_expired_and_valid(): credentials = CredentialsImpl() - credentials.token = 'token' + credentials.token = "token" assert credentials.valid assert not credentials.expired @@ -43,9 +43,8 @@ def test_expired_and_valid(): # Set the expiration to one second more than now plus the clock skew # accomodation. These credentials should be valid. credentials.expiry = ( - datetime.datetime.utcnow() + - _helpers.CLOCK_SKEW + - datetime.timedelta(seconds=1)) + datetime.datetime.utcnow() + _helpers.CLOCK_SKEW + datetime.timedelta(seconds=1) + ) assert credentials.valid assert not credentials.expired @@ -60,23 +59,23 @@ def test_expired_and_valid(): def test_before_request(): credentials = CredentialsImpl() - request = 'token' + request = "token" headers = {} # First call should call refresh, setting the token. - credentials.before_request(request, 'http://example.com', 'GET', headers) + credentials.before_request(request, "http://example.com", "GET", headers) assert credentials.valid - assert credentials.token == 'token' - assert headers['authorization'] == 'Bearer token' + assert credentials.token == "token" + assert headers["authorization"] == "Bearer token" - request = 'token2' + request = "token2" headers = {} # Second call shouldn't call refresh. - credentials.before_request(request, 'http://example.com', 'GET', headers) + credentials.before_request(request, "http://example.com", "GET", headers) assert credentials.valid - assert credentials.token == 'token' - assert headers['authorization'] == 'Bearer token' + assert credentials.token == "token" + assert headers["authorization"] == "Bearer token" def test_anonymous_credentials_ctor(): @@ -100,21 +99,20 @@ def test_anonymous_credentials_apply_default(): anon.apply(headers) assert headers == {} with pytest.raises(ValueError): - anon.apply(headers, token='TOKEN') + anon.apply(headers, token="TOKEN") def test_anonymous_credentials_before_request(): anon = credentials.AnonymousCredentials() request = object() - method = 'GET' - url = 'https://example.com/api/endpoint' + method = "GET" + url = "https://example.com/api/endpoint" headers = {} anon.before_request(request, method, url, headers) assert headers == {} -class ReadOnlyScopedCredentialsImpl( - credentials.ReadOnlyScoped, CredentialsImpl): +class ReadOnlyScopedCredentialsImpl(credentials.ReadOnlyScoped, CredentialsImpl): @property def requires_scopes(self): return super(ReadOnlyScopedCredentialsImpl, self).requires_scopes @@ -127,12 +125,12 @@ def test_readonly_scoped_credentials_constructor(): def test_readonly_scoped_credentials_scopes(): credentials = ReadOnlyScopedCredentialsImpl() - credentials._scopes = ['one', 'two'] - assert credentials.scopes == ['one', 'two'] - assert credentials.has_scopes(['one']) - assert credentials.has_scopes(['two']) - assert credentials.has_scopes(['one', 'two']) - assert not credentials.has_scopes(['three']) + credentials._scopes = ["one", "two"] + assert credentials.scopes == ["one", "two"] + assert credentials.has_scopes(["one"]) + assert credentials.has_scopes(["two"]) + assert credentials.has_scopes(["one", "two"]) + assert not credentials.has_scopes(["three"]) def test_readonly_scoped_credentials_requires_scopes(): @@ -156,16 +154,18 @@ def with_scopes(self, scopes): def test_create_scoped_if_required_scoped(): unscoped_credentials = RequiresScopedCredentialsImpl() scoped_credentials = credentials.with_scopes_if_required( - unscoped_credentials, ['one', 'two']) + unscoped_credentials, ["one", "two"] + ) assert scoped_credentials is not unscoped_credentials assert not scoped_credentials.requires_scopes - assert scoped_credentials.has_scopes(['one', 'two']) + assert scoped_credentials.has_scopes(["one", "two"]) def test_create_scoped_if_required_not_scopes(): unscoped_credentials = CredentialsImpl() scoped_credentials = credentials.with_scopes_if_required( - unscoped_credentials, ['one', 'two']) + unscoped_credentials, ["one", "two"] + ) assert scoped_credentials is unscoped_credentials diff --git a/packages/google-auth/tests/test_iam.py b/packages/google-auth/tests/test_iam.py index cc090850f13f..52ab9bd825fb 100644 --- a/packages/google-auth/tests/test_iam.py +++ b/packages/google-auth/tests/test_iam.py @@ -32,7 +32,7 @@ def make_request(status, data=None): response.status = status if data is not None: - response.data = json.dumps(data).encode('utf-8') + response.data = json.dumps(data).encode("utf-8") request = mock.create_autospec(transport.Request) request.return_value = response @@ -43,7 +43,7 @@ def make_credentials(): class CredentialsImpl(google.auth.credentials.Credentials): def __init__(self): super(CredentialsImpl, self).__init__() - self.token = 'token' + self.token = "token" # Force refresh self.expiry = datetime.datetime.min + _helpers.CLOCK_SKEW @@ -57,35 +57,33 @@ class TestSigner(object): def test_constructor(self): request = mock.sentinel.request credentials = mock.create_autospec( - google.auth.credentials.Credentials, instance=True) + google.auth.credentials.Credentials, instance=True + ) - signer = iam.Signer( - request, credentials, mock.sentinel.service_account_email) + signer = iam.Signer(request, credentials, mock.sentinel.service_account_email) assert signer._request == mock.sentinel.request assert signer._credentials == credentials - assert (signer._service_account_email == - mock.sentinel.service_account_email) + assert signer._service_account_email == mock.sentinel.service_account_email def test_key_id(self): signer = iam.Signer( mock.sentinel.request, mock.sentinel.credentials, - mock.sentinel.service_account_email) + mock.sentinel.service_account_email, + ) assert signer.key_id is None def test_sign_bytes(self): - signature = b'DEADBEEF' - encoded_signature = base64.b64encode(signature).decode('utf-8') - request = make_request( - http_client.OK, data={'signature': encoded_signature}) + signature = b"DEADBEEF" + encoded_signature = base64.b64encode(signature).decode("utf-8") + request = make_request(http_client.OK, data={"signature": encoded_signature}) credentials = make_credentials() - signer = iam.Signer( - request, credentials, mock.sentinel.service_account_email) + signer = iam.Signer(request, credentials, mock.sentinel.service_account_email) - returned_signature = signer.sign('123') + returned_signature = signer.sign("123") assert returned_signature == signature @@ -93,8 +91,7 @@ def test_sign_bytes_failure(self): request = make_request(http_client.UNAUTHORIZED) credentials = make_credentials() - signer = iam.Signer( - request, credentials, mock.sentinel.service_account_email) + signer = iam.Signer(request, credentials, mock.sentinel.service_account_email) with pytest.raises(exceptions.TransportError): - signer.sign('123') + signer.sign("123") diff --git a/packages/google-auth/tests/test_impersonated_credentials.py b/packages/google-auth/tests/test_impersonated_credentials.py index 9945401d4ffc..1cfcc7c6c1aa 100644 --- a/packages/google-auth/tests/test_impersonated_credentials.py +++ b/packages/google-auth/tests/test_impersonated_credentials.py @@ -28,35 +28,38 @@ from google.auth.impersonated_credentials import Credentials from google.oauth2 import service_account -DATA_DIR = os.path.join(os.path.dirname(__file__), '', 'data') +DATA_DIR = os.path.join(os.path.dirname(__file__), "", "data") -with open(os.path.join(DATA_DIR, 'privatekey.pem'), 'rb') as fh: +with open(os.path.join(DATA_DIR, "privatekey.pem"), "rb") as fh: PRIVATE_KEY_BYTES = fh.read() -SERVICE_ACCOUNT_JSON_FILE = os.path.join(DATA_DIR, 'service_account.json') +SERVICE_ACCOUNT_JSON_FILE = os.path.join(DATA_DIR, "service_account.json") -ID_TOKEN_DATA = ('eyJhbGciOiJSUzI1NiIsImtpZCI6ImRmMzc1ODkwOGI3OTIyOTNhZDk3N2Ew' - 'Yjk5MWQ5OGE3N2Y0ZWVlY2QiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJodHRwc' - 'zovL2Zvby5iYXIiLCJhenAiOiIxMDIxMDE1NTA4MzQyMDA3MDg1NjgiLCJle' - 'HAiOjE1NjQ0NzUwNTEsImlhdCI6MTU2NDQ3MTQ1MSwiaXNzIjoiaHR0cHM6L' - 'y9hY2NvdW50cy5nb29nbGUuY29tIiwic3ViIjoiMTAyMTAxNTUwODM0MjAwN' - 'zA4NTY4In0.redacted') +ID_TOKEN_DATA = ( + "eyJhbGciOiJSUzI1NiIsImtpZCI6ImRmMzc1ODkwOGI3OTIyOTNhZDk3N2Ew" + "Yjk5MWQ5OGE3N2Y0ZWVlY2QiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJodHRwc" + "zovL2Zvby5iYXIiLCJhenAiOiIxMDIxMDE1NTA4MzQyMDA3MDg1NjgiLCJle" + "HAiOjE1NjQ0NzUwNTEsImlhdCI6MTU2NDQ3MTQ1MSwiaXNzIjoiaHR0cHM6L" + "y9hY2NvdW50cy5nb29nbGUuY29tIiwic3ViIjoiMTAyMTAxNTUwODM0MjAwN" + "zA4NTY4In0.redacted" +) ID_TOKEN_EXPIRY = 1564475051 -with open(SERVICE_ACCOUNT_JSON_FILE, 'r') as fh: +with open(SERVICE_ACCOUNT_JSON_FILE, "r") as fh: SERVICE_ACCOUNT_INFO = json.load(fh) -SIGNER = crypt.RSASigner.from_string(PRIVATE_KEY_BYTES, '1') -TOKEN_URI = 'https://example.com/oauth2/token' +SIGNER = crypt.RSASigner.from_string(PRIVATE_KEY_BYTES, "1") +TOKEN_URI = "https://example.com/oauth2/token" @pytest.fixture def mock_donor_credentials(): - with mock.patch('google.oauth2._client.jwt_grant', autospec=True) as grant: + with mock.patch("google.oauth2._client.jwt_grant", autospec=True) as grant: grant.return_value = ( "source token", _helpers.utcnow() + datetime.timedelta(seconds=500), - {}) + {}, + ) yield grant @@ -71,54 +74,51 @@ def json(self): @pytest.fixture def mock_authorizedsession_sign(): - with mock.patch('google.auth.transport.requests.AuthorizedSession.request', - autospec=True) as auth_session: - data = { - "keyId": "1", - "signedBlob": "c2lnbmF0dXJl" - } + with mock.patch( + "google.auth.transport.requests.AuthorizedSession.request", autospec=True + ) as auth_session: + data = {"keyId": "1", "signedBlob": "c2lnbmF0dXJl"} auth_session.return_value = MockResponse(data, http_client.OK) yield auth_session @pytest.fixture def mock_authorizedsession_idtoken(): - with mock.patch('google.auth.transport.requests.AuthorizedSession.request', - autospec=True) as auth_session: - data = { - "token": ID_TOKEN_DATA - } + with mock.patch( + "google.auth.transport.requests.AuthorizedSession.request", autospec=True + ) as auth_session: + data = {"token": ID_TOKEN_DATA} auth_session.return_value = MockResponse(data, http_client.OK) yield auth_session class TestImpersonatedCredentials(object): - SERVICE_ACCOUNT_EMAIL = 'service-account@example.com' - TARGET_PRINCIPAL = 'impersonated@project.iam.gserviceaccount.com' - TARGET_SCOPES = ['https://www.googleapis.com/auth/devstorage.read_only'] + SERVICE_ACCOUNT_EMAIL = "service-account@example.com" + TARGET_PRINCIPAL = "impersonated@project.iam.gserviceaccount.com" + TARGET_SCOPES = ["https://www.googleapis.com/auth/devstorage.read_only"] DELEGATES = [] LIFETIME = 3600 SOURCE_CREDENTIALS = service_account.Credentials( - SIGNER, SERVICE_ACCOUNT_EMAIL, TOKEN_URI) + SIGNER, SERVICE_ACCOUNT_EMAIL, TOKEN_URI + ) - def make_credentials(self, lifetime=LIFETIME, - target_principal=TARGET_PRINCIPAL): + def make_credentials(self, lifetime=LIFETIME, target_principal=TARGET_PRINCIPAL): return Credentials( source_credentials=self.SOURCE_CREDENTIALS, target_principal=target_principal, target_scopes=self.TARGET_SCOPES, delegates=self.DELEGATES, - lifetime=lifetime) + lifetime=lifetime, + ) def test_default_state(self): credentials = self.make_credentials() assert not credentials.valid assert credentials.expired - def make_request(self, data, status=http_client.OK, - headers=None, side_effect=None): + def make_request(self, data, status=http_client.OK, headers=None, side_effect=None): response = mock.create_autospec(transport.Response, instance=False) response.status = status response.data = _helpers.to_bytes(data) @@ -132,40 +132,34 @@ def make_request(self, data, status=http_client.OK, def test_refresh_success(self, mock_donor_credentials): credentials = self.make_credentials(lifetime=None) - token = 'token' + token = "token" expire_time = ( - _helpers.utcnow().replace(microsecond=0) + - datetime.timedelta(seconds=500)).isoformat('T') + 'Z' - response_body = { - "accessToken": token, - "expireTime": expire_time - } + _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=500) + ).isoformat("T") + "Z" + response_body = {"accessToken": token, "expireTime": expire_time} request = self.make_request( - data=json.dumps(response_body), - status=http_client.OK) + data=json.dumps(response_body), status=http_client.OK + ) credentials.refresh(request) assert credentials.valid assert not credentials.expired - def test_refresh_failure_malformed_expire_time( - self, mock_donor_credentials): + def test_refresh_failure_malformed_expire_time(self, mock_donor_credentials): credentials = self.make_credentials(lifetime=None) - token = 'token' + token = "token" - expire_time = ( - _helpers.utcnow() + datetime.timedelta(seconds=500)).isoformat('T') - response_body = { - "accessToken": token, - "expireTime": expire_time - } + expire_time = (_helpers.utcnow() + datetime.timedelta(seconds=500)).isoformat( + "T" + ) + response_body = {"accessToken": token, "expireTime": expire_time} request = self.make_request( - data=json.dumps(response_body), - status=http_client.OK) + data=json.dumps(response_body), status=http_client.OK + ) with pytest.raises(exceptions.RefreshError) as excinfo: credentials.refresh(request) @@ -180,15 +174,15 @@ def test_refresh_failure_unauthorzed(self, mock_donor_credentials): response_body = { "error": { - "code": 403, - "message": "The caller does not have permission", - "status": "PERMISSION_DENIED" + "code": 403, + "message": "The caller does not have permission", + "status": "PERMISSION_DENIED", } } request = self.make_request( - data=json.dumps(response_body), - status=http_client.UNAUTHORIZED) + data=json.dumps(response_body), status=http_client.UNAUTHORIZED + ) with pytest.raises(exceptions.RefreshError) as excinfo: credentials.refresh(request) @@ -204,8 +198,8 @@ def test_refresh_failure_http_error(self, mock_donor_credentials): response_body = {} request = self.make_request( - data=json.dumps(response_body), - status=http_client.HTTPException) + data=json.dumps(response_body), status=http_client.HTTPException + ) with pytest.raises(exceptions.RefreshError) as excinfo: credentials.refresh(request) @@ -221,31 +215,24 @@ def test_expired(self): def test_signer(self): credentials = self.make_credentials() - assert isinstance(credentials.signer, - impersonated_credentials.Credentials) + assert isinstance(credentials.signer, impersonated_credentials.Credentials) def test_signer_email(self): - credentials = self.make_credentials( - target_principal=self.TARGET_PRINCIPAL) + credentials = self.make_credentials(target_principal=self.TARGET_PRINCIPAL) assert credentials.signer_email == self.TARGET_PRINCIPAL def test_service_account_email(self): - credentials = self.make_credentials( - target_principal=self.TARGET_PRINCIPAL) + credentials = self.make_credentials(target_principal=self.TARGET_PRINCIPAL) assert credentials.service_account_email == self.TARGET_PRINCIPAL - def test_sign_bytes(self, mock_donor_credentials, - mock_authorizedsession_sign): + def test_sign_bytes(self, mock_donor_credentials, mock_authorizedsession_sign): credentials = self.make_credentials(lifetime=None) - token = 'token' + token = "token" expire_time = ( - _helpers.utcnow().replace(microsecond=0) + - datetime.timedelta(seconds=500)).isoformat('T') + 'Z' - token_response_body = { - "accessToken": token, - "expireTime": expire_time - } + _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=500) + ).isoformat("T") + "Z" + token_response_body = {"accessToken": token, "expireTime": expire_time} response = mock.create_autospec(transport.Response, instance=False) response.status = http_client.OK @@ -259,26 +246,24 @@ def test_sign_bytes(self, mock_donor_credentials, assert credentials.valid assert not credentials.expired - signature = credentials.sign_bytes(b'signed bytes') - assert signature == b'signature' + signature = credentials.sign_bytes(b"signed bytes") + assert signature == b"signature" - def test_id_token_success(self, mock_donor_credentials, - mock_authorizedsession_idtoken): + def test_id_token_success( + self, mock_donor_credentials, mock_authorizedsession_idtoken + ): credentials = self.make_credentials(lifetime=None) - token = 'token' - target_audience = 'https://foo.bar' + token = "token" + target_audience = "https://foo.bar" expire_time = ( - _helpers.utcnow().replace(microsecond=0) + - datetime.timedelta(seconds=500)).isoformat('T') + 'Z' - response_body = { - "accessToken": token, - "expireTime": expire_time - } + _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=500) + ).isoformat("T") + "Z" + response_body = {"accessToken": token, "expireTime": expire_time} request = self.make_request( - data=json.dumps(response_body), - status=http_client.OK) + data=json.dumps(response_body), status=http_client.OK + ) credentials.refresh(request) @@ -286,30 +271,28 @@ def test_id_token_success(self, mock_donor_credentials, assert not credentials.expired id_creds = impersonated_credentials.IDTokenCredentials( - credentials, target_audience=target_audience) + credentials, target_audience=target_audience + ) id_creds.refresh(request) assert id_creds.token == ID_TOKEN_DATA - assert id_creds.expiry == datetime.datetime.fromtimestamp( - ID_TOKEN_EXPIRY) + assert id_creds.expiry == datetime.datetime.fromtimestamp(ID_TOKEN_EXPIRY) - def test_id_token_from_credential(self, mock_donor_credentials, - mock_authorizedsession_idtoken): + def test_id_token_from_credential( + self, mock_donor_credentials, mock_authorizedsession_idtoken + ): credentials = self.make_credentials(lifetime=None) - token = 'token' - target_audience = 'https://foo.bar' + token = "token" + target_audience = "https://foo.bar" expire_time = ( - _helpers.utcnow().replace(microsecond=0) + - datetime.timedelta(seconds=500)).isoformat('T') + 'Z' - response_body = { - "accessToken": token, - "expireTime": expire_time - } + _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=500) + ).isoformat("T") + "Z" + response_body = {"accessToken": token, "expireTime": expire_time} request = self.make_request( - data=json.dumps(response_body), - status=http_client.OK) + data=json.dumps(response_body), status=http_client.OK + ) credentials.refresh(request) @@ -317,72 +300,66 @@ def test_id_token_from_credential(self, mock_donor_credentials, assert not credentials.expired id_creds = impersonated_credentials.IDTokenCredentials( - credentials, target_audience=target_audience) + credentials, target_audience=target_audience + ) id_creds = id_creds.from_credentials(target_credentials=credentials) id_creds.refresh(request) assert id_creds.token == ID_TOKEN_DATA - def test_id_token_with_target_audience(self, mock_donor_credentials, - mock_authorizedsession_idtoken): + def test_id_token_with_target_audience( + self, mock_donor_credentials, mock_authorizedsession_idtoken + ): credentials = self.make_credentials(lifetime=None) - token = 'token' - target_audience = 'https://foo.bar' + token = "token" + target_audience = "https://foo.bar" expire_time = ( - _helpers.utcnow().replace(microsecond=0) + - datetime.timedelta(seconds=500)).isoformat('T') + 'Z' - response_body = { - "accessToken": token, - "expireTime": expire_time - } + _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=500) + ).isoformat("T") + "Z" + response_body = {"accessToken": token, "expireTime": expire_time} request = self.make_request( - data=json.dumps(response_body), - status=http_client.OK) + data=json.dumps(response_body), status=http_client.OK + ) credentials.refresh(request) assert credentials.valid assert not credentials.expired - id_creds = impersonated_credentials.IDTokenCredentials( - credentials) - id_creds = id_creds.with_target_audience( - target_audience=target_audience) + id_creds = impersonated_credentials.IDTokenCredentials(credentials) + id_creds = id_creds.with_target_audience(target_audience=target_audience) id_creds.refresh(request) assert id_creds.token == ID_TOKEN_DATA - assert id_creds.expiry == datetime.datetime.fromtimestamp( - ID_TOKEN_EXPIRY) + assert id_creds.expiry == datetime.datetime.fromtimestamp(ID_TOKEN_EXPIRY) - def test_id_token_invalid_cred(self, mock_donor_credentials, - mock_authorizedsession_idtoken): + def test_id_token_invalid_cred( + self, mock_donor_credentials, mock_authorizedsession_idtoken + ): credentials = None with pytest.raises(exceptions.GoogleAuthError) as excinfo: impersonated_credentials.IDTokenCredentials(credentials) - assert excinfo.match('Provided Credential must be' - ' impersonated_credentials') + assert excinfo.match("Provided Credential must be" " impersonated_credentials") - def test_id_token_with_include_email(self, mock_donor_credentials, - mock_authorizedsession_idtoken): + def test_id_token_with_include_email( + self, mock_donor_credentials, mock_authorizedsession_idtoken + ): credentials = self.make_credentials(lifetime=None) - token = 'token' - target_audience = 'https://foo.bar' + token = "token" + target_audience = "https://foo.bar" expire_time = ( - _helpers.utcnow().replace(microsecond=0) + - datetime.timedelta(seconds=500)).isoformat('T') + 'Z' - response_body = { - "accessToken": token, - "expireTime": expire_time - } + _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=500) + ).isoformat("T") + "Z" + response_body = {"accessToken": token, "expireTime": expire_time} request = self.make_request( - data=json.dumps(response_body), - status=http_client.OK) + data=json.dumps(response_body), status=http_client.OK + ) credentials.refresh(request) @@ -390,7 +367,8 @@ def test_id_token_with_include_email(self, mock_donor_credentials, assert not credentials.expired id_creds = impersonated_credentials.IDTokenCredentials( - credentials, target_audience=target_audience) + credentials, target_audience=target_audience + ) id_creds = id_creds.with_include_email(True) id_creds.refresh(request) diff --git a/packages/google-auth/tests/test_jwt.py b/packages/google-auth/tests/test_jwt.py index 4a64717ff0fb..b0c6e48e9d1e 100644 --- a/packages/google-auth/tests/test_jwt.py +++ b/packages/google-auth/tests/test_jwt.py @@ -26,41 +26,45 @@ from google.auth import jwt -DATA_DIR = os.path.join(os.path.dirname(__file__), 'data') +DATA_DIR = os.path.join(os.path.dirname(__file__), "data") -with open(os.path.join(DATA_DIR, 'privatekey.pem'), 'rb') as fh: +with open(os.path.join(DATA_DIR, "privatekey.pem"), "rb") as fh: PRIVATE_KEY_BYTES = fh.read() -with open(os.path.join(DATA_DIR, 'public_cert.pem'), 'rb') as fh: +with open(os.path.join(DATA_DIR, "public_cert.pem"), "rb") as fh: PUBLIC_CERT_BYTES = fh.read() -with open(os.path.join(DATA_DIR, 'other_cert.pem'), 'rb') as fh: +with open(os.path.join(DATA_DIR, "other_cert.pem"), "rb") as fh: OTHER_CERT_BYTES = fh.read() -SERVICE_ACCOUNT_JSON_FILE = os.path.join(DATA_DIR, 'service_account.json') +SERVICE_ACCOUNT_JSON_FILE = os.path.join(DATA_DIR, "service_account.json") -with open(SERVICE_ACCOUNT_JSON_FILE, 'r') as fh: +with open(SERVICE_ACCOUNT_JSON_FILE, "r") as fh: SERVICE_ACCOUNT_INFO = json.load(fh) @pytest.fixture def signer(): - return crypt.RSASigner.from_string(PRIVATE_KEY_BYTES, '1') + return crypt.RSASigner.from_string(PRIVATE_KEY_BYTES, "1") def test_encode_basic(signer): - test_payload = {'test': 'value'} + test_payload = {"test": "value"} encoded = jwt.encode(signer, test_payload) header, payload, _, _ = jwt._unverified_decode(encoded) assert payload == test_payload - assert header == {'typ': 'JWT', 'alg': 'RS256', 'kid': signer.key_id} + assert header == {"typ": "JWT", "alg": "RS256", "kid": signer.key_id} def test_encode_extra_headers(signer): - encoded = jwt.encode(signer, {}, header={'extra': 'value'}) + encoded = jwt.encode(signer, {}, header={"extra": "value"}) header = jwt.decode_header(encoded) assert header == { - 'typ': 'JWT', 'alg': 'RS256', 'kid': signer.key_id, 'extra': 'value'} + "typ": "JWT", + "alg": "RS256", + "kid": signer.key_id, + "extra": "value", + } @pytest.fixture @@ -68,11 +72,11 @@ def token_factory(signer): def factory(claims=None, key_id=None): now = _helpers.datetime_to_secs(_helpers.utcnow()) payload = { - 'aud': 'audience@example.com', - 'iat': now, - 'exp': now + 300, - 'user': 'billy bob', - 'metadata': {'meta': 'data'} + "aud": "audience@example.com", + "iat": now, + "exp": now + 300, + "user": "billy bob", + "metadata": {"meta": "data"}, } payload.update(claims or {}) @@ -83,154 +87,168 @@ def factory(claims=None, key_id=None): key_id = None return jwt.encode(signer, payload, key_id=key_id) + return factory def test_decode_valid(token_factory): payload = jwt.decode(token_factory(), certs=PUBLIC_CERT_BYTES) - assert payload['aud'] == 'audience@example.com' - assert payload['user'] == 'billy bob' - assert payload['metadata']['meta'] == 'data' + assert payload["aud"] == "audience@example.com" + assert payload["user"] == "billy bob" + assert payload["metadata"]["meta"] == "data" def test_decode_valid_with_audience(token_factory): payload = jwt.decode( - token_factory(), certs=PUBLIC_CERT_BYTES, - audience='audience@example.com') - assert payload['aud'] == 'audience@example.com' - assert payload['user'] == 'billy bob' - assert payload['metadata']['meta'] == 'data' + token_factory(), certs=PUBLIC_CERT_BYTES, audience="audience@example.com" + ) + assert payload["aud"] == "audience@example.com" + assert payload["user"] == "billy bob" + assert payload["metadata"]["meta"] == "data" def test_decode_valid_unverified(token_factory): payload = jwt.decode(token_factory(), certs=OTHER_CERT_BYTES, verify=False) - assert payload['aud'] == 'audience@example.com' - assert payload['user'] == 'billy bob' - assert payload['metadata']['meta'] == 'data' + assert payload["aud"] == "audience@example.com" + assert payload["user"] == "billy bob" + assert payload["metadata"]["meta"] == "data" def test_decode_bad_token_wrong_number_of_segments(): with pytest.raises(ValueError) as excinfo: - jwt.decode('1.2', PUBLIC_CERT_BYTES) - assert excinfo.match(r'Wrong number of segments') + jwt.decode("1.2", PUBLIC_CERT_BYTES) + assert excinfo.match(r"Wrong number of segments") def test_decode_bad_token_not_base64(): with pytest.raises((ValueError, TypeError)) as excinfo: - jwt.decode('1.2.3', PUBLIC_CERT_BYTES) - assert excinfo.match(r'Incorrect padding|more than a multiple of 4') + jwt.decode("1.2.3", PUBLIC_CERT_BYTES) + assert excinfo.match(r"Incorrect padding|more than a multiple of 4") def test_decode_bad_token_not_json(): - token = b'.'.join([base64.urlsafe_b64encode(b'123!')] * 3) + token = b".".join([base64.urlsafe_b64encode(b"123!")] * 3) with pytest.raises(ValueError) as excinfo: jwt.decode(token, PUBLIC_CERT_BYTES) - assert excinfo.match(r'Can\'t parse segment') + assert excinfo.match(r"Can\'t parse segment") def test_decode_bad_token_no_iat_or_exp(signer): - token = jwt.encode(signer, {'test': 'value'}) + token = jwt.encode(signer, {"test": "value"}) with pytest.raises(ValueError) as excinfo: jwt.decode(token, PUBLIC_CERT_BYTES) - assert excinfo.match(r'Token does not contain required claim') + assert excinfo.match(r"Token does not contain required claim") def test_decode_bad_token_too_early(token_factory): - token = token_factory(claims={ - 'iat': _helpers.datetime_to_secs( - _helpers.utcnow() + datetime.timedelta(hours=1)) - }) + token = token_factory( + claims={ + "iat": _helpers.datetime_to_secs( + _helpers.utcnow() + datetime.timedelta(hours=1) + ) + } + ) with pytest.raises(ValueError) as excinfo: jwt.decode(token, PUBLIC_CERT_BYTES) - assert excinfo.match(r'Token used too early') + assert excinfo.match(r"Token used too early") def test_decode_bad_token_expired(token_factory): - token = token_factory(claims={ - 'exp': _helpers.datetime_to_secs( - _helpers.utcnow() - datetime.timedelta(hours=1)) - }) + token = token_factory( + claims={ + "exp": _helpers.datetime_to_secs( + _helpers.utcnow() - datetime.timedelta(hours=1) + ) + } + ) with pytest.raises(ValueError) as excinfo: jwt.decode(token, PUBLIC_CERT_BYTES) - assert excinfo.match(r'Token expired') + assert excinfo.match(r"Token expired") def test_decode_bad_token_wrong_audience(token_factory): token = token_factory() - audience = 'audience2@example.com' + audience = "audience2@example.com" with pytest.raises(ValueError) as excinfo: jwt.decode(token, PUBLIC_CERT_BYTES, audience=audience) - assert excinfo.match(r'Token has wrong audience') + assert excinfo.match(r"Token has wrong audience") def test_decode_wrong_cert(token_factory): with pytest.raises(ValueError) as excinfo: jwt.decode(token_factory(), OTHER_CERT_BYTES) - assert excinfo.match(r'Could not verify token signature') + assert excinfo.match(r"Could not verify token signature") def test_decode_multicert_bad_cert(token_factory): - certs = {'1': OTHER_CERT_BYTES, '2': PUBLIC_CERT_BYTES} + certs = {"1": OTHER_CERT_BYTES, "2": PUBLIC_CERT_BYTES} with pytest.raises(ValueError) as excinfo: jwt.decode(token_factory(), certs) - assert excinfo.match(r'Could not verify token signature') + assert excinfo.match(r"Could not verify token signature") def test_decode_no_cert(token_factory): - certs = {'2': PUBLIC_CERT_BYTES} + certs = {"2": PUBLIC_CERT_BYTES} with pytest.raises(ValueError) as excinfo: jwt.decode(token_factory(), certs) - assert excinfo.match(r'Certificate for key id 1 not found') + assert excinfo.match(r"Certificate for key id 1 not found") def test_decode_no_key_id(token_factory): token = token_factory(key_id=False) - certs = {'2': PUBLIC_CERT_BYTES} + certs = {"2": PUBLIC_CERT_BYTES} payload = jwt.decode(token, certs) - assert payload['user'] == 'billy bob' + assert payload["user"] == "billy bob" def test_roundtrip_explicit_key_id(token_factory): - token = token_factory(key_id='3') - certs = {'2': OTHER_CERT_BYTES, '3': PUBLIC_CERT_BYTES} + token = token_factory(key_id="3") + certs = {"2": OTHER_CERT_BYTES, "3": PUBLIC_CERT_BYTES} payload = jwt.decode(token, certs) - assert payload['user'] == 'billy bob' + assert payload["user"] == "billy bob" class TestCredentials(object): - SERVICE_ACCOUNT_EMAIL = 'service-account@example.com' - SUBJECT = 'subject' - AUDIENCE = 'audience' - ADDITIONAL_CLAIMS = {'meta': 'data'} + SERVICE_ACCOUNT_EMAIL = "service-account@example.com" + SUBJECT = "subject" + AUDIENCE = "audience" + ADDITIONAL_CLAIMS = {"meta": "data"} credentials = None @pytest.fixture(autouse=True) def credentials_fixture(self, signer): self.credentials = jwt.Credentials( - signer, self.SERVICE_ACCOUNT_EMAIL, self.SERVICE_ACCOUNT_EMAIL, - self.AUDIENCE) + signer, + self.SERVICE_ACCOUNT_EMAIL, + self.SERVICE_ACCOUNT_EMAIL, + self.AUDIENCE, + ) def test_from_service_account_info(self): - with open(SERVICE_ACCOUNT_JSON_FILE, 'r') as fh: + with open(SERVICE_ACCOUNT_JSON_FILE, "r") as fh: info = json.load(fh) credentials = jwt.Credentials.from_service_account_info( - info, audience=self.AUDIENCE) + info, audience=self.AUDIENCE + ) - assert credentials._signer.key_id == info['private_key_id'] - assert credentials._issuer == info['client_email'] - assert credentials._subject == info['client_email'] + assert credentials._signer.key_id == info["private_key_id"] + assert credentials._issuer == info["client_email"] + assert credentials._subject == info["client_email"] assert credentials._audience == self.AUDIENCE def test_from_service_account_info_args(self): info = SERVICE_ACCOUNT_INFO.copy() credentials = jwt.Credentials.from_service_account_info( - info, subject=self.SUBJECT, audience=self.AUDIENCE, - additional_claims=self.ADDITIONAL_CLAIMS) - - assert credentials._signer.key_id == info['private_key_id'] - assert credentials._issuer == info['client_email'] + info, + subject=self.SUBJECT, + audience=self.AUDIENCE, + additional_claims=self.ADDITIONAL_CLAIMS, + ) + + assert credentials._signer.key_id == info["private_key_id"] + assert credentials._issuer == info["client_email"] assert credentials._subject == self.SUBJECT assert credentials._audience == self.AUDIENCE assert credentials._additional_claims == self.ADDITIONAL_CLAIMS @@ -239,33 +257,37 @@ def test_from_service_account_file(self): info = SERVICE_ACCOUNT_INFO.copy() credentials = jwt.Credentials.from_service_account_file( - SERVICE_ACCOUNT_JSON_FILE, audience=self.AUDIENCE) + SERVICE_ACCOUNT_JSON_FILE, audience=self.AUDIENCE + ) - assert credentials._signer.key_id == info['private_key_id'] - assert credentials._issuer == info['client_email'] - assert credentials._subject == info['client_email'] + assert credentials._signer.key_id == info["private_key_id"] + assert credentials._issuer == info["client_email"] + assert credentials._subject == info["client_email"] assert credentials._audience == self.AUDIENCE def test_from_service_account_file_args(self): info = SERVICE_ACCOUNT_INFO.copy() credentials = jwt.Credentials.from_service_account_file( - SERVICE_ACCOUNT_JSON_FILE, subject=self.SUBJECT, - audience=self.AUDIENCE, additional_claims=self.ADDITIONAL_CLAIMS) - - assert credentials._signer.key_id == info['private_key_id'] - assert credentials._issuer == info['client_email'] + SERVICE_ACCOUNT_JSON_FILE, + subject=self.SUBJECT, + audience=self.AUDIENCE, + additional_claims=self.ADDITIONAL_CLAIMS, + ) + + assert credentials._signer.key_id == info["private_key_id"] + assert credentials._issuer == info["client_email"] assert credentials._subject == self.SUBJECT assert credentials._audience == self.AUDIENCE assert credentials._additional_claims == self.ADDITIONAL_CLAIMS def test_from_signing_credentials(self): jwt_from_signing = self.credentials.from_signing_credentials( - self.credentials, - audience=mock.sentinel.new_audience) + self.credentials, audience=mock.sentinel.new_audience + ) jwt_from_info = jwt.Credentials.from_service_account_info( - SERVICE_ACCOUNT_INFO, - audience=mock.sentinel.new_audience) + SERVICE_ACCOUNT_INFO, audience=mock.sentinel.new_audience + ) assert isinstance(jwt_from_signing, jwt.Credentials) assert jwt_from_signing._signer.key_id == jwt_from_info._signer.key_id @@ -279,19 +301,17 @@ def test_default_state(self): assert not self.credentials.expired def test_with_claims(self): - new_audience = 'new_audience' - new_credentials = self.credentials.with_claims( - audience=new_audience) + new_audience = "new_audience" + new_credentials = self.credentials.with_claims(audience=new_audience) assert new_credentials._signer == self.credentials._signer assert new_credentials._issuer == self.credentials._issuer assert new_credentials._subject == self.credentials._subject assert new_credentials._audience == new_audience - assert (new_credentials._additional_claims == - self.credentials._additional_claims) + assert new_credentials._additional_claims == self.credentials._additional_claims def test_sign_bytes(self): - to_sign = b'123' + to_sign = b"123" signature = self.credentials.sign_bytes(to_sign) assert crypt.verify_signature(to_sign, signature, PUBLIC_CERT_BYTES) @@ -299,12 +319,11 @@ def test_signer(self): assert isinstance(self.credentials.signer, crypt.RSASigner) def test_signer_email(self): - assert (self.credentials.signer_email == - SERVICE_ACCOUNT_INFO['client_email']) + assert self.credentials.signer_email == SERVICE_ACCOUNT_INFO["client_email"] def _verify_token(self, token): payload = jwt.decode(token, PUBLIC_CERT_BYTES) - assert payload['iss'] == self.SERVICE_ACCOUNT_EMAIL + assert payload["iss"] == self.SERVICE_ACCOUNT_EMAIL return payload def test_refresh(self): @@ -318,7 +337,7 @@ def test_expired(self): self.credentials.refresh(None) assert not self.credentials.expired - with mock.patch('google.auth._helpers.utcnow') as now: + with mock.patch("google.auth._helpers.utcnow") as now: one_day = datetime.timedelta(days=1) now.return_value = self.credentials.expiry + one_day assert self.credentials.expired @@ -328,55 +347,58 @@ def test_before_request(self): self.credentials.refresh(None) self.credentials.before_request( - None, 'GET', 'http://example.com?a=1#3', headers) + None, "GET", "http://example.com?a=1#3", headers + ) - header_value = headers['authorization'] - _, token = header_value.split(' ') + header_value = headers["authorization"] + _, token = header_value.split(" ") # Since the audience is set, it should use the existing token. - assert token.encode('utf-8') == self.credentials.token + assert token.encode("utf-8") == self.credentials.token payload = self._verify_token(token) - assert payload['aud'] == self.AUDIENCE + assert payload["aud"] == self.AUDIENCE def test_before_request_refreshes(self): assert not self.credentials.valid - self.credentials.before_request( - None, 'GET', 'http://example.com?a=1#3', {}) + self.credentials.before_request(None, "GET", "http://example.com?a=1#3", {}) assert self.credentials.valid class TestOnDemandCredentials(object): - SERVICE_ACCOUNT_EMAIL = 'service-account@example.com' - SUBJECT = 'subject' - ADDITIONAL_CLAIMS = {'meta': 'data'} + SERVICE_ACCOUNT_EMAIL = "service-account@example.com" + SUBJECT = "subject" + ADDITIONAL_CLAIMS = {"meta": "data"} credentials = None @pytest.fixture(autouse=True) def credentials_fixture(self, signer): self.credentials = jwt.OnDemandCredentials( - signer, self.SERVICE_ACCOUNT_EMAIL, self.SERVICE_ACCOUNT_EMAIL, - max_cache_size=2) + signer, + self.SERVICE_ACCOUNT_EMAIL, + self.SERVICE_ACCOUNT_EMAIL, + max_cache_size=2, + ) def test_from_service_account_info(self): - with open(SERVICE_ACCOUNT_JSON_FILE, 'r') as fh: + with open(SERVICE_ACCOUNT_JSON_FILE, "r") as fh: info = json.load(fh) credentials = jwt.OnDemandCredentials.from_service_account_info(info) - assert credentials._signer.key_id == info['private_key_id'] - assert credentials._issuer == info['client_email'] - assert credentials._subject == info['client_email'] + assert credentials._signer.key_id == info["private_key_id"] + assert credentials._issuer == info["client_email"] + assert credentials._subject == info["client_email"] def test_from_service_account_info_args(self): info = SERVICE_ACCOUNT_INFO.copy() credentials = jwt.OnDemandCredentials.from_service_account_info( - info, subject=self.SUBJECT, - additional_claims=self.ADDITIONAL_CLAIMS) + info, subject=self.SUBJECT, additional_claims=self.ADDITIONAL_CLAIMS + ) - assert credentials._signer.key_id == info['private_key_id'] - assert credentials._issuer == info['client_email'] + assert credentials._signer.key_id == info["private_key_id"] + assert credentials._issuer == info["client_email"] assert credentials._subject == self.SUBJECT assert credentials._additional_claims == self.ADDITIONAL_CLAIMS @@ -384,29 +406,32 @@ def test_from_service_account_file(self): info = SERVICE_ACCOUNT_INFO.copy() credentials = jwt.OnDemandCredentials.from_service_account_file( - SERVICE_ACCOUNT_JSON_FILE) + SERVICE_ACCOUNT_JSON_FILE + ) - assert credentials._signer.key_id == info['private_key_id'] - assert credentials._issuer == info['client_email'] - assert credentials._subject == info['client_email'] + assert credentials._signer.key_id == info["private_key_id"] + assert credentials._issuer == info["client_email"] + assert credentials._subject == info["client_email"] def test_from_service_account_file_args(self): info = SERVICE_ACCOUNT_INFO.copy() credentials = jwt.OnDemandCredentials.from_service_account_file( - SERVICE_ACCOUNT_JSON_FILE, subject=self.SUBJECT, - additional_claims=self.ADDITIONAL_CLAIMS) + SERVICE_ACCOUNT_JSON_FILE, + subject=self.SUBJECT, + additional_claims=self.ADDITIONAL_CLAIMS, + ) - assert credentials._signer.key_id == info['private_key_id'] - assert credentials._issuer == info['client_email'] + assert credentials._signer.key_id == info["private_key_id"] + assert credentials._issuer == info["client_email"] assert credentials._subject == self.SUBJECT assert credentials._additional_claims == self.ADDITIONAL_CLAIMS def test_from_signing_credentials(self): - jwt_from_signing = self.credentials.from_signing_credentials( - self.credentials) + jwt_from_signing = self.credentials.from_signing_credentials(self.credentials) jwt_from_info = jwt.OnDemandCredentials.from_service_account_info( - SERVICE_ACCOUNT_INFO) + SERVICE_ACCOUNT_INFO + ) assert isinstance(jwt_from_signing, jwt.OnDemandCredentials) assert jwt_from_signing._signer.key_id == jwt_from_info._signer.key_id @@ -420,9 +445,8 @@ def test_default_state(self): assert not self.credentials.expired def test_with_claims(self): - new_claims = {'meep': 'moop'} - new_credentials = self.credentials.with_claims( - additional_claims=new_claims) + new_claims = {"meep": "moop"} + new_credentials = self.credentials.with_claims(additional_claims=new_claims) assert new_credentials._signer == self.credentials._signer assert new_credentials._issuer == self.credentials._issuer @@ -430,7 +454,7 @@ def test_with_claims(self): assert new_credentials._additional_claims == new_claims def test_sign_bytes(self): - to_sign = b'123' + to_sign = b"123" signature = self.credentials.sign_bytes(to_sign) assert crypt.verify_signature(to_sign, signature, PUBLIC_CERT_BYTES) @@ -438,12 +462,11 @@ def test_signer(self): assert isinstance(self.credentials.signer, crypt.RSASigner) def test_signer_email(self): - assert (self.credentials.signer_email == - SERVICE_ACCOUNT_INFO['client_email']) + assert self.credentials.signer_email == SERVICE_ACCOUNT_INFO["client_email"] def _verify_token(self, token): payload = jwt.decode(token, PUBLIC_CERT_BYTES) - assert payload['iss'] == self.SERVICE_ACCOUNT_EMAIL + assert payload["iss"] == self.SERVICE_ACCOUNT_EMAIL return payload def test_refresh(self): @@ -454,25 +477,27 @@ def test_before_request(self): headers = {} self.credentials.before_request( - None, 'GET', 'http://example.com?a=1#3', headers) + None, "GET", "http://example.com?a=1#3", headers + ) - _, token = headers['authorization'].split(' ') + _, token = headers["authorization"].split(" ") payload = self._verify_token(token) - assert payload['aud'] == 'http://example.com' + assert payload["aud"] == "http://example.com" # Making another request should re-use the same token. - self.credentials.before_request( - None, 'GET', 'http://example.com?b=2', headers) + self.credentials.before_request(None, "GET", "http://example.com?b=2", headers) - _, new_token = headers['authorization'].split(' ') + _, new_token = headers["authorization"].split(" ") assert new_token == token def test_expired_token(self): - self.credentials._cache['audience'] = ( - mock.sentinel.token, datetime.datetime.min) + self.credentials._cache["audience"] = ( + mock.sentinel.token, + datetime.datetime.min, + ) - token = self.credentials._get_jwt_for_audience('audience') + token = self.credentials._get_jwt_for_audience("audience") assert token != mock.sentinel.token diff --git a/packages/google-auth/tests/transport/compliance.py b/packages/google-auth/tests/transport/compliance.py index 50c6d7ccfe7f..dc7c58be5c4f 100644 --- a/packages/google-auth/tests/transport/compliance.py +++ b/packages/google-auth/tests/transport/compliance.py @@ -22,12 +22,11 @@ from google.auth import exceptions # .invalid will never resolve, see https://tools.ietf.org/html/rfc2606 -NXDOMAIN = 'test.invalid' +NXDOMAIN = "test.invalid" class RequestResponseTests(object): - - @pytest.fixture(scope='module') + @pytest.fixture(scope="module") def server(self): """Provides a test HTTP server. @@ -40,20 +39,21 @@ def server(self): # pylint: disable=unused-variable # (pylint thinks the flask routes are unusued.) - @app.route('/basic') + @app.route("/basic") def index(): - header_value = flask.request.headers.get('x-test-header', 'value') - headers = {'X-Test-Header': header_value} - return 'Basic Content', http_client.OK, headers + header_value = flask.request.headers.get("x-test-header", "value") + headers = {"X-Test-Header": header_value} + return "Basic Content", http_client.OK, headers - @app.route('/server_error') + @app.route("/server_error") def server_error(): - return 'Error', http_client.INTERNAL_SERVER_ERROR + return "Error", http_client.INTERNAL_SERVER_ERROR - @app.route('/wait') + @app.route("/wait") def wait(): time.sleep(3) - return 'Waited' + return "Waited" + # pylint: enable=unused-variable server = WSGIServer(application=app.wsgi_app) @@ -63,44 +63,46 @@ def wait(): def test_request_basic(self, server): request = self.make_request() - response = request(url=server.url + '/basic', method='GET') + response = request(url=server.url + "/basic", method="GET") assert response.status == http_client.OK - assert response.headers['x-test-header'] == 'value' - assert response.data == b'Basic Content' + assert response.headers["x-test-header"] == "value" + assert response.data == b"Basic Content" def test_request_with_timeout_success(self, server): request = self.make_request() - response = request(url=server.url + '/basic', method='GET', timeout=2) + response = request(url=server.url + "/basic", method="GET", timeout=2) assert response.status == http_client.OK - assert response.headers['x-test-header'] == 'value' - assert response.data == b'Basic Content' + assert response.headers["x-test-header"] == "value" + assert response.data == b"Basic Content" def test_request_with_timeout_failure(self, server): request = self.make_request() with pytest.raises(exceptions.TransportError): - request(url=server.url + '/wait', method='GET', timeout=1) + request(url=server.url + "/wait", method="GET", timeout=1) def test_request_headers(self, server): request = self.make_request() response = request( - url=server.url + '/basic', method='GET', headers={ - 'x-test-header': 'hello world'}) + url=server.url + "/basic", + method="GET", + headers={"x-test-header": "hello world"}, + ) assert response.status == http_client.OK - assert response.headers['x-test-header'] == 'hello world' - assert response.data == b'Basic Content' + assert response.headers["x-test-header"] == "hello world" + assert response.data == b"Basic Content" def test_request_error(self, server): request = self.make_request() - response = request(url=server.url + '/server_error', method='GET') + response = request(url=server.url + "/server_error", method="GET") assert response.status == http_client.INTERNAL_SERVER_ERROR - assert response.data == b'Error' + assert response.data == b"Error" def test_connection_error(self): request = self.make_request() with pytest.raises(exceptions.TransportError): - request(url='http://{}'.format(NXDOMAIN), method='GET') + request(url="http://{}".format(NXDOMAIN), method="GET") diff --git a/packages/google-auth/tests/transport/test__http_client.py b/packages/google-auth/tests/transport/test__http_client.py index 6b69088b824f..9e7f108aea2b 100644 --- a/packages/google-auth/tests/transport/test__http_client.py +++ b/packages/google-auth/tests/transport/test__http_client.py @@ -26,6 +26,6 @@ def make_request(self): def test_non_http(self): request = self.make_request() with pytest.raises(exceptions.TransportError) as excinfo: - request(url='https://{}'.format(compliance.NXDOMAIN), method='GET') + request(url="https://{}".format(compliance.NXDOMAIN), method="GET") - assert excinfo.match('https') + assert excinfo.match("https") diff --git a/packages/google-auth/tests/transport/test_grpc.py b/packages/google-auth/tests/transport/test_grpc.py index c98dcff675ee..810d038aa491 100644 --- a/packages/google-auth/tests/transport/test_grpc.py +++ b/packages/google-auth/tests/transport/test_grpc.py @@ -25,22 +25,23 @@ # pylint: disable=ungrouped-imports import grpc import google.auth.transport.grpc + HAS_GRPC = True except ImportError: # pragma: NO COVER HAS_GRPC = False -pytestmark = pytest.mark.skipif(not HAS_GRPC, reason='gRPC is unavailable.') +pytestmark = pytest.mark.skipif(not HAS_GRPC, reason="gRPC is unavailable.") class CredentialsStub(credentials.Credentials): - def __init__(self, token='token'): + def __init__(self, token="token"): super(CredentialsStub, self).__init__() self.token = token self.expiry = None def refresh(self, request): - self.token += '1' + self.token += "1" class TestAuthMetadataPlugin(object): @@ -48,8 +49,7 @@ def test_call_no_refresh(self): credentials = CredentialsStub() request = mock.create_autospec(transport.Request) - plugin = google.auth.transport.grpc.AuthMetadataPlugin( - credentials, request) + plugin = google.auth.transport.grpc.AuthMetadataPlugin(credentials, request) context = mock.create_autospec(grpc.AuthMetadataContext, instance=True) context.method_name = mock.sentinel.method_name @@ -59,15 +59,15 @@ def test_call_no_refresh(self): plugin(context, callback) callback.assert_called_once_with( - [(u'authorization', u'Bearer {}'.format(credentials.token))], None) + [(u"authorization", u"Bearer {}".format(credentials.token))], None + ) def test_call_refresh(self): credentials = CredentialsStub() credentials.expiry = datetime.datetime.min + _helpers.CLOCK_SKEW request = mock.create_autospec(transport.Request) - plugin = google.auth.transport.grpc.AuthMetadataPlugin( - credentials, request) + plugin = google.auth.transport.grpc.AuthMetadataPlugin(credentials, request) context = mock.create_autospec(grpc.AuthMetadataContext, instance=True) context.method_name = mock.sentinel.method_name @@ -76,29 +76,33 @@ def test_call_refresh(self): plugin(context, callback) - assert credentials.token == 'token1' + assert credentials.token == "token1" callback.assert_called_once_with( - [(u'authorization', u'Bearer {}'.format(credentials.token))], None) + [(u"authorization", u"Bearer {}".format(credentials.token))], None + ) -@mock.patch('grpc.composite_channel_credentials', autospec=True) -@mock.patch('grpc.metadata_call_credentials', autospec=True) -@mock.patch('grpc.ssl_channel_credentials', autospec=True) -@mock.patch('grpc.secure_channel', autospec=True) +@mock.patch("grpc.composite_channel_credentials", autospec=True) +@mock.patch("grpc.metadata_call_credentials", autospec=True) +@mock.patch("grpc.ssl_channel_credentials", autospec=True) +@mock.patch("grpc.secure_channel", autospec=True) def test_secure_authorized_channel( - secure_channel, ssl_channel_credentials, metadata_call_credentials, - composite_channel_credentials): + secure_channel, + ssl_channel_credentials, + metadata_call_credentials, + composite_channel_credentials, +): credentials = CredentialsStub() request = mock.create_autospec(transport.Request) - target = 'example.com:80' + target = "example.com:80" channel = google.auth.transport.grpc.secure_authorized_channel( - credentials, request, target, options=mock.sentinel.options) + credentials, request, target, options=mock.sentinel.options + ) # Check the auth plugin construction. auth_plugin = metadata_call_credentials.call_args[0][0] - assert isinstance( - auth_plugin, google.auth.transport.grpc.AuthMetadataPlugin) + assert isinstance(auth_plugin, google.auth.transport.grpc.AuthMetadataPlugin) assert auth_plugin._credentials == credentials assert auth_plugin._request == request @@ -107,35 +111,41 @@ def test_secure_authorized_channel( # Check the composite credentials call. composite_channel_credentials.assert_called_once_with( - ssl_channel_credentials.return_value, - metadata_call_credentials.return_value) + ssl_channel_credentials.return_value, metadata_call_credentials.return_value + ) # Check the channel call. secure_channel.assert_called_once_with( - target, composite_channel_credentials.return_value, - options=mock.sentinel.options) + target, + composite_channel_credentials.return_value, + options=mock.sentinel.options, + ) assert channel == secure_channel.return_value -@mock.patch('grpc.composite_channel_credentials', autospec=True) -@mock.patch('grpc.metadata_call_credentials', autospec=True) -@mock.patch('grpc.ssl_channel_credentials', autospec=True) -@mock.patch('grpc.secure_channel', autospec=True) +@mock.patch("grpc.composite_channel_credentials", autospec=True) +@mock.patch("grpc.metadata_call_credentials", autospec=True) +@mock.patch("grpc.ssl_channel_credentials", autospec=True) +@mock.patch("grpc.secure_channel", autospec=True) def test_secure_authorized_channel_explicit_ssl( - secure_channel, ssl_channel_credentials, metadata_call_credentials, - composite_channel_credentials): + secure_channel, + ssl_channel_credentials, + metadata_call_credentials, + composite_channel_credentials, +): credentials = mock.Mock() request = mock.Mock() - target = 'example.com:80' + target = "example.com:80" ssl_credentials = mock.Mock() google.auth.transport.grpc.secure_authorized_channel( - credentials, request, target, ssl_credentials=ssl_credentials) + credentials, request, target, ssl_credentials=ssl_credentials + ) # Check the ssl channel call. assert not ssl_channel_credentials.called # Check the composite credentials call. composite_channel_credentials.assert_called_once_with( - ssl_credentials, - metadata_call_credentials.return_value) + ssl_credentials, metadata_call_credentials.return_value + ) diff --git a/packages/google-auth/tests/transport/test_requests.py b/packages/google-auth/tests/transport/test_requests.py index 311992ae97eb..0e165ac54b17 100644 --- a/packages/google-auth/tests/transport/test_requests.py +++ b/packages/google-auth/tests/transport/test_requests.py @@ -29,24 +29,24 @@ def make_request(self): def test_timeout(self): http = mock.create_autospec(requests.Session, instance=True) request = google.auth.transport.requests.Request(http) - request(url='http://example.com', method='GET', timeout=5) + request(url="http://example.com", method="GET", timeout=5) - assert http.request.call_args[1]['timeout'] == 5 + assert http.request.call_args[1]["timeout"] == 5 class CredentialsStub(google.auth.credentials.Credentials): - def __init__(self, token='token'): + def __init__(self, token="token"): super(CredentialsStub, self).__init__() self.token = token def apply(self, headers, token=None): - headers['authorization'] = self.token + headers["authorization"] = self.token def before_request(self, request, method, url, headers): self.apply(headers) def refresh(self, request): - self.token += '1' + self.token += "1" class AdapterStub(requests.adapters.BaseAdapter): @@ -77,11 +77,12 @@ def make_response(status=http_client.OK, data=None): class TestAuthorizedHttp(object): - TEST_URL = 'http://example.com/' + TEST_URL = "http://example.com/" def test_constructor(self): authed_session = google.auth.transport.requests.AuthorizedSession( - mock.sentinel.credentials) + mock.sentinel.credentials + ) assert authed_session.credentials == mock.sentinel.credentials @@ -90,7 +91,8 @@ def test_constructor_with_auth_request(self): auth_request = google.auth.transport.requests.Request(http) authed_session = google.auth.transport.requests.AuthorizedSession( - mock.sentinel.credentials, auth_request=auth_request) + mock.sentinel.credentials, auth_request=auth_request + ) assert authed_session._auth_request == auth_request @@ -99,32 +101,30 @@ def test_request_no_refresh(self): response = make_response() adapter = AdapterStub([response]) - authed_session = google.auth.transport.requests.AuthorizedSession( - credentials) + authed_session = google.auth.transport.requests.AuthorizedSession(credentials) authed_session.mount(self.TEST_URL, adapter) - result = authed_session.request('GET', self.TEST_URL) + result = authed_session.request("GET", self.TEST_URL) assert response == result assert credentials.before_request.called assert not credentials.refresh.called assert len(adapter.requests) == 1 assert adapter.requests[0].url == self.TEST_URL - assert adapter.requests[0].headers['authorization'] == 'token' + assert adapter.requests[0].headers["authorization"] == "token" def test_request_refresh(self): credentials = mock.Mock(wraps=CredentialsStub()) final_response = make_response(status=http_client.OK) # First request will 401, second request will succeed. - adapter = AdapterStub([ - make_response(status=http_client.UNAUTHORIZED), - final_response]) + adapter = AdapterStub( + [make_response(status=http_client.UNAUTHORIZED), final_response] + ) - authed_session = google.auth.transport.requests.AuthorizedSession( - credentials) + authed_session = google.auth.transport.requests.AuthorizedSession(credentials) authed_session.mount(self.TEST_URL, adapter) - result = authed_session.request('GET', self.TEST_URL) + result = authed_session.request("GET", self.TEST_URL) assert result == final_response assert credentials.before_request.call_count == 2 @@ -132,7 +132,7 @@ def test_request_refresh(self): assert len(adapter.requests) == 2 assert adapter.requests[0].url == self.TEST_URL - assert adapter.requests[0].headers['authorization'] == 'token' + assert adapter.requests[0].headers["authorization"] == "token" assert adapter.requests[1].url == self.TEST_URL - assert adapter.requests[1].headers['authorization'] == 'token1' + assert adapter.requests[1].headers["authorization"] == "token1" diff --git a/packages/google-auth/tests/transport/test_urllib3.py b/packages/google-auth/tests/transport/test_urllib3.py index a119b74c9558..1d7ce5a0ed9f 100644 --- a/packages/google-auth/tests/transport/test_urllib3.py +++ b/packages/google-auth/tests/transport/test_urllib3.py @@ -29,35 +29,35 @@ def make_request(self): def test_timeout(self): http = mock.create_autospec(urllib3.PoolManager) request = google.auth.transport.urllib3.Request(http) - request(url='http://example.com', method='GET', timeout=5) + request(url="http://example.com", method="GET", timeout=5) - assert http.request.call_args[1]['timeout'] == 5 + assert http.request.call_args[1]["timeout"] == 5 def test__make_default_http_with_certifi(): http = google.auth.transport.urllib3._make_default_http() - assert 'cert_reqs' in http.connection_pool_kw + assert "cert_reqs" in http.connection_pool_kw -@mock.patch.object(google.auth.transport.urllib3, 'certifi', new=None) +@mock.patch.object(google.auth.transport.urllib3, "certifi", new=None) def test__make_default_http_without_certifi(): http = google.auth.transport.urllib3._make_default_http() - assert 'cert_reqs' not in http.connection_pool_kw + assert "cert_reqs" not in http.connection_pool_kw class CredentialsStub(google.auth.credentials.Credentials): - def __init__(self, token='token'): + def __init__(self, token="token"): super(CredentialsStub, self).__init__() self.token = token def apply(self, headers, token=None): - headers['authorization'] = self.token + headers["authorization"] = self.token def before_request(self, request, method, url, headers): self.apply(headers) def refresh(self, request): - self.token += '1' + self.token += "1" class HttpStub(object): @@ -78,11 +78,12 @@ def __init__(self, status=http_client.OK, data=None): class TestAuthorizedHttp(object): - TEST_URL = 'http://example.com' + TEST_URL = "http://example.com" def test_authed_http_defaults(self): authed_http = google.auth.transport.urllib3.AuthorizedHttp( - mock.sentinel.credentials) + mock.sentinel.credentials + ) assert authed_http.credentials == mock.sentinel.credentials assert isinstance(authed_http.http, urllib3.PoolManager) @@ -93,40 +94,41 @@ def test_urlopen_no_refresh(self): http = HttpStub([response]) authed_http = google.auth.transport.urllib3.AuthorizedHttp( - credentials, http=http) + credentials, http=http + ) - result = authed_http.urlopen('GET', self.TEST_URL) + result = authed_http.urlopen("GET", self.TEST_URL) assert result == response assert credentials.before_request.called assert not credentials.refresh.called assert http.requests == [ - ('GET', self.TEST_URL, None, {'authorization': 'token'}, {})] + ("GET", self.TEST_URL, None, {"authorization": "token"}, {}) + ] def test_urlopen_refresh(self): credentials = mock.Mock(wraps=CredentialsStub()) final_response = ResponseStub(status=http_client.OK) # First request will 401, second request will succeed. - http = HttpStub([ - ResponseStub(status=http_client.UNAUTHORIZED), - final_response]) + http = HttpStub([ResponseStub(status=http_client.UNAUTHORIZED), final_response]) authed_http = google.auth.transport.urllib3.AuthorizedHttp( - credentials, http=http) + credentials, http=http + ) - authed_http = authed_http.urlopen('GET', 'http://example.com') + authed_http = authed_http.urlopen("GET", "http://example.com") assert authed_http == final_response assert credentials.before_request.call_count == 2 assert credentials.refresh.called assert http.requests == [ - ('GET', self.TEST_URL, None, {'authorization': 'token'}, {}), - ('GET', self.TEST_URL, None, {'authorization': 'token1'}, {})] + ("GET", self.TEST_URL, None, {"authorization": "token"}, {}), + ("GET", self.TEST_URL, None, {"authorization": "token1"}, {}), + ] def test_proxies(self): http = mock.create_autospec(urllib3.PoolManager) - authed_http = google.auth.transport.urllib3.AuthorizedHttp( - None, http=http) + authed_http = google.auth.transport.urllib3.AuthorizedHttp(None, http=http) with authed_http: pass From bebb0b89e0c485798a56b6572982281ad91b5f2d Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Fri, 25 Oct 2019 10:45:00 -0700 Subject: [PATCH 228/966] build: fix system tests, move to Kokoro (#372) --- packages/google-auth/.kokoro/build.sh | 50 ++++++++ packages/google-auth/.kokoro/common.cfg | 5 +- .../google-auth/.kokoro/continuous/common.cfg | 27 ++++ .../.kokoro/continuous/continuous.cfg | 1 + packages/google-auth/.kokoro/docs/common.cfg | 48 +++++++ packages/google-auth/.kokoro/docs/docs.cfg | 1 + .../google-auth/.kokoro/presubmit/common.cfg | 27 ++++ .../.kokoro/presubmit/presubmit.cfg | 1 + packages/google-auth/.kokoro/publish-docs.sh | 42 +++++++ packages/google-auth/.kokoro/release.sh | 19 +++ .../google-auth/.kokoro/release/common.cfg | 64 ++++++++++ .../google-auth/.kokoro/release/release.cfg | 1 + packages/google-auth/.kokoro/system_tests.cfg | 16 --- packages/google-auth/.kokoro/system_tests.sh | 33 ----- packages/google-auth/.kokoro/trampoline.sh | 13 +- packages/google-auth/.repo-metadata.json | 10 ++ packages/google-auth/.travis.yml | 47 ------- packages/google-auth/CONTRIBUTING.rst | 80 ++++++++++-- packages/google-auth/README.rst | 6 - packages/google-auth/docs/conf.py | 2 +- packages/google-auth/noxfile.py | 118 ++++++++++++++++++ .../google-auth/scripts/decrypt-secrets.sh | 9 +- .../google-auth/scripts/encrypt-secrets.sh | 16 +-- .../google-auth/scripts/obtain_user_auth.py | 65 ---------- packages/google-auth/scripts/travis.sh | 6 +- packages/google-auth/setup.cfg | 17 +-- packages/google-auth/synth.metadata | 12 ++ packages/google-auth/synth.py | 10 ++ packages/google-auth/system_tests/.gitignore | 2 +- packages/google-auth/system_tests/noxfile.py | 74 +++++++---- .../google-auth/system_tests/secrets.tar.enc | Bin 18464 -> 10323 bytes .../system_tests/test_compute_engine.py | 2 +- .../google-auth/system_tests/test_grpc.py | 29 +++-- .../system_tests/test_oauth2_credentials.py | 8 +- packages/google-auth/tox.ini | 92 -------------- 35 files changed, 601 insertions(+), 352 deletions(-) create mode 100755 packages/google-auth/.kokoro/build.sh create mode 100644 packages/google-auth/.kokoro/continuous/common.cfg create mode 100644 packages/google-auth/.kokoro/continuous/continuous.cfg create mode 100644 packages/google-auth/.kokoro/docs/common.cfg create mode 100644 packages/google-auth/.kokoro/docs/docs.cfg create mode 100644 packages/google-auth/.kokoro/presubmit/common.cfg create mode 100644 packages/google-auth/.kokoro/presubmit/presubmit.cfg create mode 100755 packages/google-auth/.kokoro/publish-docs.sh create mode 100755 packages/google-auth/.kokoro/release.sh create mode 100644 packages/google-auth/.kokoro/release/common.cfg create mode 100644 packages/google-auth/.kokoro/release/release.cfg delete mode 100644 packages/google-auth/.kokoro/system_tests.cfg delete mode 100755 packages/google-auth/.kokoro/system_tests.sh create mode 100644 packages/google-auth/.repo-metadata.json delete mode 100644 packages/google-auth/.travis.yml create mode 100644 packages/google-auth/noxfile.py delete mode 100644 packages/google-auth/scripts/obtain_user_auth.py create mode 100644 packages/google-auth/synth.metadata create mode 100644 packages/google-auth/synth.py delete mode 100644 packages/google-auth/tox.ini diff --git a/packages/google-auth/.kokoro/build.sh b/packages/google-auth/.kokoro/build.sh new file mode 100755 index 000000000000..e80311313e4b --- /dev/null +++ b/packages/google-auth/.kokoro/build.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -eo pipefail + +cd github/google-auth-library-python + +# Disable buffering, so that the logs stream through. +export PYTHONUNBUFFERED=1 + +# Debug: show build environment +env | grep KOKORO + +# Setup service account credentials. + +# add creds to gfile dir +export GOOGLE_APPLICATION_CREDENTIALS=${KOKORO_GFILE_DIR}/service-account.json + +# Setup project id. +export PROJECT_ID=$(cat "${KOKORO_GFILE_DIR}/project-id.txt") + +# Activate gcloud with service account credentials +gcloud auth activate-service-account --key-file=$GOOGLE_APPLICATION_CREDENTIALS +gcloud config set project $PROJECT_ID + +# Decrypt system test secrets +./scripts/decrypt-secrets.sh + +# Remove old nox +python3.6 -m pip uninstall --yes --quiet nox-automation + +# Install nox +python3.6 -m pip install --upgrade --quiet nox +python3.6 -m nox --version + +python3.6 -m nox +python3.6 -m nox -f system_tests/noxfile.py \ No newline at end of file diff --git a/packages/google-auth/.kokoro/common.cfg b/packages/google-auth/.kokoro/common.cfg index 7dbafc200a81..81f431a99733 100644 --- a/packages/google-auth/.kokoro/common.cfg +++ b/packages/google-auth/.kokoro/common.cfg @@ -3,11 +3,14 @@ # Download trampoline resources. These will be in ${KOKORO_GFILE_DIR} gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" +# Download resources for tests +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-auth-library-python" + # All builds use the trampoline script to run in docker. build_file: "google-auth-library-python/.kokoro/trampoline.sh" # Use the Python worker docker iamge. env_vars: { key: "TRAMPOLINE_IMAGE" - value: "gcr.io/silver-python2/python-worker" + value: "gcr.io/cloud-devrel-public-resources/python-multi" } diff --git a/packages/google-auth/.kokoro/continuous/common.cfg b/packages/google-auth/.kokoro/continuous/common.cfg new file mode 100644 index 000000000000..10910e357558 --- /dev/null +++ b/packages/google-auth/.kokoro/continuous/common.cfg @@ -0,0 +1,27 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Build logs will be here +action { + define_artifacts { + regex: "**/*sponge_log.xml" + } +} + +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" + +# Download resources for system tests (service account key, etc.) +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-auth-library-python" + +# Use the trampoline script to run in docker. +build_file: "google-auth-library-python/.kokoro/trampoline.sh" + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/python-multi" +} +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/google-auth-library-python/.kokoro/build.sh" +} diff --git a/packages/google-auth/.kokoro/continuous/continuous.cfg b/packages/google-auth/.kokoro/continuous/continuous.cfg new file mode 100644 index 000000000000..8f43917d92fe --- /dev/null +++ b/packages/google-auth/.kokoro/continuous/continuous.cfg @@ -0,0 +1 @@ +# Format: //devtools/kokoro/config/proto/build.proto \ No newline at end of file diff --git a/packages/google-auth/.kokoro/docs/common.cfg b/packages/google-auth/.kokoro/docs/common.cfg new file mode 100644 index 000000000000..e49c23215db8 --- /dev/null +++ b/packages/google-auth/.kokoro/docs/common.cfg @@ -0,0 +1,48 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Build logs will be here +action { + define_artifacts { + regex: "**/*sponge_log.xml" + } +} + +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" + +# Use the trampoline script to run in docker. +build_file: "google-auth-library-python/.kokoro/trampoline.sh" + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/python-multi" +} +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/google-auth-library-python/.kokoro/publish-docs.sh" +} + +env_vars: { + key: "STAGING_BUCKET" + value: "docs-staging" +} + +# Fetch the token needed for reporting release status to GitHub +before_action { + fetch_keystore { + keystore_resource { + keystore_config_id: 73713 + keyname: "yoshi-automation-github-key" + } + } +} + +before_action { + fetch_keystore { + keystore_resource { + keystore_config_id: 73713 + keyname: "docuploader_service_account" + } + } +} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/docs/docs.cfg b/packages/google-auth/.kokoro/docs/docs.cfg new file mode 100644 index 000000000000..8f43917d92fe --- /dev/null +++ b/packages/google-auth/.kokoro/docs/docs.cfg @@ -0,0 +1 @@ +# Format: //devtools/kokoro/config/proto/build.proto \ No newline at end of file diff --git a/packages/google-auth/.kokoro/presubmit/common.cfg b/packages/google-auth/.kokoro/presubmit/common.cfg new file mode 100644 index 000000000000..7dbee1cfeabf --- /dev/null +++ b/packages/google-auth/.kokoro/presubmit/common.cfg @@ -0,0 +1,27 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Build logs will be here +action { + define_artifacts { + regex: "**/*sponge_log.xml" + } +} + +# Download trampoline resources. These will be in ${KOKORO_GFILE_DIR} +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" + +# Download resources for tests +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-auth-library-python" + +# Use the trampoline script to run in docker. +build_file: "google-auth-library-python/.kokoro/trampoline.sh" + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/python-multi" +} +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/google-auth-library-python/.kokoro/build.sh" +} diff --git a/packages/google-auth/.kokoro/presubmit/presubmit.cfg b/packages/google-auth/.kokoro/presubmit/presubmit.cfg new file mode 100644 index 000000000000..8f43917d92fe --- /dev/null +++ b/packages/google-auth/.kokoro/presubmit/presubmit.cfg @@ -0,0 +1 @@ +# Format: //devtools/kokoro/config/proto/build.proto \ No newline at end of file diff --git a/packages/google-auth/.kokoro/publish-docs.sh b/packages/google-auth/.kokoro/publish-docs.sh new file mode 100755 index 000000000000..0d97db8f1553 --- /dev/null +++ b/packages/google-auth/.kokoro/publish-docs.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +set -eo pipefail + +# Disable buffering, so that the logs stream through. +export PYTHONUNBUFFERED=1 + +cd github/google-auth-library-python + +# Remove old nox +python3.6 -m pip uninstall --yes --quiet nox-automation + +# Install nox +python3.6 -m pip install --upgrade --quiet nox +python3.6 -m nox --version + +# build docs +nox -s docs + +python3 -m pip install gcp-docuploader + +# install a json parser +sudo apt-get update +sudo apt-get -y install software-properties-common +sudo add-apt-repository universe +sudo apt-get update +sudo apt-get -y install jq + +# create metadata +python3 -m docuploader create-metadata \ + --name=$(jq --raw-output '.name // empty' .repo-metadata.json) \ + --version=$(python3 setup.py --version) \ + --language=$(jq --raw-output '.language // empty' .repo-metadata.json) \ + --distribution-name=$(python3 setup.py --name) \ + --product-page=$(jq --raw-output '.product_documentation // empty' .repo-metadata.json) \ + --github-repository=$(jq --raw-output '.repo // empty' .repo-metadata.json) \ + --issue-tracker=$(jq --raw-output '.issue_tracker // empty' .repo-metadata.json) + +cat docs.metadata + +# upload docs +python3 -m docuploader upload docs/_build/html --metadata-file docs.metadata --staging-bucket docs-staging diff --git a/packages/google-auth/.kokoro/release.sh b/packages/google-auth/.kokoro/release.sh new file mode 100755 index 000000000000..cf855559a2c5 --- /dev/null +++ b/packages/google-auth/.kokoro/release.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +set -eo pipefail + +# Start the releasetool reporter +python3 -m pip install gcp-releasetool +python3 -m releasetool publish-reporter-script > /tmp/publisher-script; source /tmp/publisher-script + +# Ensure that we have the latest versions of Twine, Wheel, and Setuptools. +python3 -m pip install --upgrade twine wheel setuptools + +# Disable buffering, so that the logs stream through. +export PYTHONUNBUFFERED=1 + +# Move into the package, build the distribution and upload. +TWINE_PASSWORD=$(cat "${KOKORO_KEYSTORE_DIR}/73713_google_cloud_pypi_password") +cd github/google-auth-library-python +python3 setup.py sdist bdist_wheel +twine upload --username gcloudpypi --password "${TWINE_PASSWORD}" dist/* diff --git a/packages/google-auth/.kokoro/release/common.cfg b/packages/google-auth/.kokoro/release/common.cfg new file mode 100644 index 000000000000..b2088d009fdf --- /dev/null +++ b/packages/google-auth/.kokoro/release/common.cfg @@ -0,0 +1,64 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Build logs will be here +action { + define_artifacts { + regex: "**/*sponge_log.xml" + } +} + +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" + +# Use the trampoline script to run in docker. +build_file: "google-auth-library-python/.kokoro/trampoline.sh" + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/python-multi" +} +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/google-auth-library-python/.kokoro/release.sh" +} + +# Fetch the token needed for reporting release status to GitHub +before_action { + fetch_keystore { + keystore_resource { + keystore_config_id: 73713 + keyname: "yoshi-automation-github-key" + } + } +} + +# Fetch PyPI password +before_action { + fetch_keystore { + keystore_resource { + keystore_config_id: 73713 + keyname: "google_cloud_pypi_password" + } + } +} + +# Fetch magictoken to use with Magic Github Proxy +before_action { + fetch_keystore { + keystore_resource { + keystore_config_id: 73713 + keyname: "releasetool-magictoken" + } + } +} + +# Fetch api key to use with Magic Github Proxy +before_action { + fetch_keystore { + keystore_resource { + keystore_config_id: 73713 + keyname: "magic-github-proxy-api-key" + } + } +} diff --git a/packages/google-auth/.kokoro/release/release.cfg b/packages/google-auth/.kokoro/release/release.cfg new file mode 100644 index 000000000000..8f43917d92fe --- /dev/null +++ b/packages/google-auth/.kokoro/release/release.cfg @@ -0,0 +1 @@ +# Format: //devtools/kokoro/config/proto/build.proto \ No newline at end of file diff --git a/packages/google-auth/.kokoro/system_tests.cfg b/packages/google-auth/.kokoro/system_tests.cfg deleted file mode 100644 index aa3e6c6be3f9..000000000000 --- a/packages/google-auth/.kokoro/system_tests.cfg +++ /dev/null @@ -1,16 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -# Download secrets from Cloud Storage. -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-auth-library-python" - -# Tell the trampoline which build file to use. -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/google-auth-library-python/.kokoro/system_tests.sh" -} - -# Tell the system tests which Google Cloud project to use. -env_vars: { - key: "TEST_PROJECT" - value: "python-docs-samples-tests" -} diff --git a/packages/google-auth/.kokoro/system_tests.sh b/packages/google-auth/.kokoro/system_tests.sh deleted file mode 100755 index 29d509708254..000000000000 --- a/packages/google-auth/.kokoro/system_tests.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/bash - -# Copyright 2017 Google Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -set -eo pipefail - -export PATH=${PATH}:${HOME}/gcloud/google-cloud-sdk/bin - -cd github/google-auth-library-python - -# Unencrypt and extract secrets -SECRETS_PASSWORD=$(cat "${KOKORO_GFILE_DIR}/secrets-password.txt") -./scripts/decrypt-secrets.sh "${SECRETS_PASSWORD}" - -# Setup gcloud, this is needed for the App Engine system test. -gcloud auth activate-service-account --key-file system_tests/data/service_account.json -gcloud config set project "${TEST_PROJECT}" - -# Run tests -tox -e py27-system -tox -e py36-system diff --git a/packages/google-auth/.kokoro/trampoline.sh b/packages/google-auth/.kokoro/trampoline.sh index fef7c24de02d..e8c4251f3ed4 100755 --- a/packages/google-auth/.kokoro/trampoline.sh +++ b/packages/google-auth/.kokoro/trampoline.sh @@ -15,14 +15,9 @@ set -eo pipefail -# Always run the cleanup script, regardless of the success of bouncing into -# the container. +python3 "${KOKORO_GFILE_DIR}/trampoline_v1.py" || ret_code=$? -function cleanup() { - chmod +x ${KOKORO_GFILE_DIR}/trampoline_cleanup.sh - ${KOKORO_GFILE_DIR}/trampoline_cleanup.sh - echo "cleanup"; -} -trap cleanup EXIT +chmod +x ${KOKORO_GFILE_DIR}/trampoline_cleanup.sh +${KOKORO_GFILE_DIR}/trampoline_cleanup.sh || true -python3 "${KOKORO_GFILE_DIR}/trampoline_v1.py" +exit ${ret_code} diff --git a/packages/google-auth/.repo-metadata.json b/packages/google-auth/.repo-metadata.json new file mode 100644 index 000000000000..4c8ebe1bbf29 --- /dev/null +++ b/packages/google-auth/.repo-metadata.json @@ -0,0 +1,10 @@ +{ + "name": "google-auth-library-python", + "name_pretty": "Google Auth Python Library", + "client_documentation": "https://googleapis.dev/python/google-auth-library-python/latest", + "issue_tracker": "https://github.com/googleapis/google-auth-library-python/issues", + "release_level": "ga", + "language": "python", + "repo": "googleapis/google-auth-library-python", + "distribution_name": "google-auth" +} \ No newline at end of file diff --git a/packages/google-auth/.travis.yml b/packages/google-auth/.travis.yml deleted file mode 100644 index 7335fd72f4a9..000000000000 --- a/packages/google-auth/.travis.yml +++ /dev/null @@ -1,47 +0,0 @@ -language: python -sudo: false -matrix: - include: - - python: 3.6 - env: TOXENV=lint - - python: 3.6 - env: TOXENV=docs - - python: 2.7 - env: TOXENV=py27 - - python: 3.4 - env: TOXENV=py34 - - python: 3.5 - env: TOXENV=py35 - - python: 3.6 - env: TOXENV=py36 - - python: pypy - env: TOXENV=pypy - - python: 3.6 - env: TOXENV=cover - - python: 3.6 - env: TOXENV=py36-system SYSTEM_TEST=1 SKIP_APP_ENGINE_SYSTEM_TEST=1 - - python: 2.7 - env: TOXENV=py27-system SYSTEM_TEST=1 SKIP_APP_ENGINE_SYSTEM_TEST=1 - - python: 3.6 - env: TOXENV=pytype -cache: - directories: - - ${HOME}/.cache -install: -- pip install --upgrade tox -script: -- scripts/travis.sh -deploy: - provider: pypi - user: google_opensource - password: - secure: bThsMsG/1fg2NlAygLI8GkDc/kGD3TmXUrqmPU9cc6Tym8sMzPYMiDpJk76ttLAV+gfEI++ROvcul4cgW6Jz5d5xJp4sbQAN4eZOyO3Q3hiVPvhuu6wPMM22j8YfT7rmh710moyBujxJccVDCNAJo/3nAbAQVScP8l89YDW5LF9ckgLPLKG0gDEfwLk8ZVlJg8lRsJ7PTWnAp7nfrvXRiBNPEe8yie654wcxV/xzQwAHLSJhAfpBdTHxvUoFazynn+DvGF/zF5R3n3XSPvctGT6tQNr7GDmdp1EqsOL7y2NbunzIDEoN/wUKlmTRcJVownVyLdISNBo3GjbsyOZGwsj86bKpVIgwhFrkZe7BOV9Fqq5wBl7kTvVf5j/FnLrsQaPyGlgHHxNqXSRqjUosT7BGnuTErmhO130Z6q6iXErrCNfBM+sbuLv+LF+vlMuVSdeU/TBGjf5j0ODmsGGF3EuKtudgD64O8L81/ybKo1CxcRoXbK7+pJhvhSAmkFdZ99A22+wiLNe7B1FVmu6ZfO0WO0GPLKsE+1Xn3xxcrX0wO6rE+uXH4CSQEgLza/CltKNL671dZZMJAUsNAVanwKIKCsNu7viOpUVejESyK7I1SA6B5yX74iA/yhunLLnPuPLANbkug7Ob/i5Z1IruWzpEyqm0CC4HfMPvg7FA+Kc= - on: - tags: true - distributions: sdist bdist_wheel - repo: googleapis/google-auth-library-python - condition: "$TOXENV = \"cover\"" -env: - global: - - secure: s6GdhJklftl8w/9WoETwLtvtKL4ledPA/TuBuqCXQxSuYWaPuTdRVcvoejGkHJpp7i/7v2T/0etYl+5koyskKm5+QZZweaaL7MAyjPGp+hmIaIlWQRz6w481NOf3i9uSmoQycssT0mNmwScNIqo+igbA2y14mr/e9aBuOcxNNzNzFQp2vaRMEju6q7xZMjYdcudUWL48vq9CoNa3X2ZArpqjkApR/TfYlG7glOj43NxuVDN4z9wIyUjaMHBfPgEhjaOaRyEFgEYITRwX1qDoXqcZdTVIq4Cn0uCH+Mvrz6Y+oUJGTJqH1k7N/DhzbSN9lJnVYaQW/yuvGHiGAwbb6Tcxiq2UqqhA9MfbPpmstDECs46v9Z3BT252KvYEQY7Q1v9g2gFhHvFGWISUxs80rnnPhEYfa11JoLvj2t8cowkE4pvj4OH32Eoyvc5H07hW3F5xpuF7Jt7N09TNZkUrpmiRJEhfrVNgjsrWO77/q5h8mXGd+9vYmz++yzKu+63x8x1MpeigGCG73Dpu9Otm5eydOZfpJ39ZfZWUb7G2JahgHaGweM9dmnpJtzHQgijmHjjfAx9jgnQ8IQz9nkFmyMI8H7HouwalnrJtpSSbvMqOQ0kiZhMzdBKH5pD3tjLgSlgA0pKelBwlooY6jGlj4LrtbDAxa6cZyXiFoqWpT1w= - - CLOUD_SDK_ROOT: ${HOME}/.cache/cloud-sdk diff --git a/packages/google-auth/CONTRIBUTING.rst b/packages/google-auth/CONTRIBUTING.rst index 8581688b5af8..f95b1f1dc45a 100644 --- a/packages/google-auth/CONTRIBUTING.rst +++ b/packages/google-auth/CONTRIBUTING.rst @@ -11,36 +11,92 @@ Here are some guidelines for hacking on ``google-auth-library-python``. Making changes -------------- -A few notes on making changes to ``google-auth-libary-python``. +A few notes on making changes to ``google-auth-library-python``. - If you've added a new feature or modified an existing feature, be sure to add or update any applicable documentation in docstrings and in the documentation (in ``docs/``). You can re-generate the reference documentation - using ``tox -e docgen``. + using ``nox -s docgen``. - The change must work fully on the following CPython versions: 2.7, - 3.4, and 3.5 across macOS, Linux, and Windows. + 3.5, 3.6, 3.7 across macOS, Linux, and Windows. - The codebase *must* have 100% test statement coverage after each commit. - You can test coverage via ``tox -e cover``. + You can test coverage via ``nox -e cover``. Testing changes --------------- -To test your changes, run unit tests with ``tox``:: +To test your changes, run unit tests with ``nox``:: - $ tox -e py27 - $ tox -e py34 - $ tox -e py35 + $ nox -s unit + + +Running system tests +-------------------- + +You can run the system tests with ``nox``:: + + $ nox -f system_tests/noxfile.py + +To run a single session, specify it with ``nox -s``:: + + $ nox -f system_tests/noxfile.py -s service_account + +To run system tests locally, you will need to set up a data directory :: + + $ mkdir system_tests/data + +Add a service account file and authorized user file to the data directory. +Your directory should look like this :: + + system_tests/ + data/ + service_account.json + authorized_user.json + +The files must be named exactly ``service_account.json`` +and ``authorized_user.json``. See `Creating and Managing Service Account Keys`_ for how to +obtain a service account. + +Use the `gcloud CLI`_ to get an authorized user file :: + + $ gcloud auth application-default login --scopes=https://www.googleapis.com/auth/userinfo.email,https://www.googleapis.com/auth/cloud-platform,openid + +You will see something like:: + + Credentials saved to file: [/usr/local/home/.config/gcloud/application_default_credentials.json]``` + +Copy the contents of the file to ``authorized_user.json``. + +.. _Creating and Managing Service Account Keys: https://cloud.google.com/iam/docs/creating-managing-service-account-keys +.. _gcloud CLI: https://cloud.google.com/sdk/gcloud/ + +App Engine System Tests +^^^^^^^^^^^^^^^^^^^^^^^ + +To run the App Engine tests, you wil need to deploy a default App Engine service. +If you already have a default service associated with your project, you can skip this step. + +Edit ``app.yaml`` so ``service`` is ``default`` instead of ``google-auth-system-tests``. +From ``system_tests/app_engine_test_app`` run the following commands :: + + $ pip install --target-lib -r requirements.txt + $ gcloud app deploy -q app.yaml + +After the app is deployed, change ``service`` in ``app.yaml`` back to ``google-auth-system-tests``. +You can now run the App Engine tests: :: + + $ nox -f system_tests/noxfile.py -s app_engine Coding Style ------------ This library is PEP8 & Pylint compliant. Our Pylint config is defined at ``pylintrc`` for package code and ``pylintrc.tests`` for test code. Use -``tox`` to check for non-compliant code:: +``nox`` to check for non-compliant code:: - $ tox -e lint + $ nox -s lint Documentation Coverage and Building HTML Documentation ------------------------------------------------------ @@ -50,9 +106,9 @@ documentation in this package which references that API or behavior must be changed to reflect the bug fix, ideally in the same commit that fixes the bug or adds the feature. -To build and review docs use ``tox``:: +To build and review docs use ``nox``:: - $ tox -e docs + $ nox -s docs The HTML version of the docs will be built in ``docs/_build/html`` diff --git a/packages/google-auth/README.rst b/packages/google-auth/README.rst index 3d7ba76afa83..c7dfcea6b567 100644 --- a/packages/google-auth/README.rst +++ b/packages/google-auth/README.rst @@ -6,16 +6,10 @@ Google Auth Python Library This library simplifies using Google's various server-to-server authentication mechanisms to access Google APIs. -.. |build| image:: https://travis-ci.org/GoogleCloudPlatform/google-auth-library-python.svg?branch=master - :target: https://travis-ci.org/GoogleCloudPlatform/google-auth-library-python .. |docs| image:: https://readthedocs.org/projects/google-auth/badge/?version=latest :target: https://google-auth.readthedocs.io/en/latest/ .. |pypi| image:: https://img.shields.io/pypi/v/google-auth.svg :target: https://pypi.python.org/pypi/google-auth -.. |compat_check_pypi| image:: https://python-compatibility-tools.appspot.com/one_badge_image?package=google-auth - :target: https://python-compatibility-tools.appspot.com/one_badge_target?package=google-auth -.. |compat_check_github| image:: https://python-compatibility-tools.appspot.com/one_badge_image?package=git%2Bgit%3A//github.com/googleapis/google-auth-library-python.git - :target: https://python-compatibility-tools.appspot.com/one_badge_target?package=git%2Bgit%3A//github.com/googleapis/google-auth-library-python.git Installing ---------- diff --git a/packages/google-auth/docs/conf.py b/packages/google-auth/docs/conf.py index 831c752cf2a9..db1872e5dbf2 100644 --- a/packages/google-auth/docs/conf.py +++ b/packages/google-auth/docs/conf.py @@ -368,7 +368,7 @@ intersphinx_mapping = { "python": ("https://docs.python.org/3.5", None), "urllib3": ("https://urllib3.readthedocs.io/en/stable", None), - "requests": ("http://docs.python-requests.org/en/stable", None), + "requests": ("https://requests.kennethreitz.org/en/master/", None), "requests-oauthlib": ("http://requests-oauthlib.readthedocs.io/en/stable", None), } diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py new file mode 100644 index 000000000000..aaf1bc57da31 --- /dev/null +++ b/packages/google-auth/noxfile.py @@ -0,0 +1,118 @@ +# Copyright 2019 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import nox + +TEST_DEPENDENCIES = [ + "flask", + "mock", + "oauth2client", + "pytest", + "pytest-cov", + "pytest-localserver", + "requests", + "urllib3", + "cryptography", + "grpcio", +] +BLACK_VERSION = "black==19.3b0" +BLACK_PATHS = ["google", "tests", "noxfile.py", "setup.py", "docs/conf.py"] + + +@nox.session(python="3.7") +def lint(session): + session.install("flake8", "flake8-import-order", "docutils", BLACK_VERSION) + session.install(".") + session.run("black", "--check", *BLACK_PATHS) + session.run( + "flake8", + "--import-order-style=google", + "--application-import-names=google,tests,system_tests", + "google", + "tests", + ) + session.run( + "python", "setup.py", "check", "--metadata", "--restructuredtext", "--strict" + ) + + +@nox.session(python="3.6") +def blacken(session): + """Run black. + + Format code to uniform standard. + + This currently uses Python 3.6 due to the automated Kokoro run of synthtool. + That run uses an image that doesn't have 3.6 installed. Before updating this + check the state of the `gcp_ubuntu_config` we use for that Kokoro run. + """ + session.install(BLACK_VERSION) + session.run("black", *BLACK_PATHS) + + +@nox.session(python=["2.7", "3.5", "3.6", "3.7"]) +def unit(session): + session.install(*TEST_DEPENDENCIES) + session.install(".") + session.run( + "pytest", "--cov=google.auth", "--cov=google.oauth2", "--cov=tests", "tests" + ) + + +@nox.session(python="3.7") +def cover(session): + session.install(*TEST_DEPENDENCIES) + session.install(".") + session.run( + "pytest", + "--cov=google.auth", + "--cov=google.oauth2", + "--cov=tests", + "--cov-report=", + "tests", + ) + session.run("coverage", "report", "--show-missing", "--fail-under=100") + + +@nox.session(python="3.7") +def docgen(session): + session.env["SPHINX_APIDOC_OPTIONS"] = "members,inherited-members,show-inheritance" + session.install(*TEST_DEPENDENCIES) + session.install("sphinx") + session.install(".") + session.run("rm", "-r", "docs/reference") + session.run( + "sphinx-apidoc", + "--output-dir", + "docs/reference", + "--separate", + "--module-first", + "google", + ) + + +@nox.session(python="3.7") +def docs(session): + session.install("sphinx", "-r", "docs/requirements-docs.txt") + session.install(".") + session.run("make", "-C", "docs", "html") + + +@nox.session(python="pypy") +def pypy(session): + session.install(*TEST_DEPENDENCIES) + session.install(".") + session.run( + "pytest", "--cov=google.auth", "--cov=google.oauth2", "--cov=tests", "tests" + ) diff --git a/packages/google-auth/scripts/decrypt-secrets.sh b/packages/google-auth/scripts/decrypt-secrets.sh index e02bfc10bf95..f0ef994edcd7 100755 --- a/packages/google-auth/scripts/decrypt-secrets.sh +++ b/packages/google-auth/scripts/decrypt-secrets.sh @@ -20,8 +20,11 @@ ROOT=$( dirname "$DIR" ) # Work from the project root. cd $ROOT -openssl aes-256-cbc -k "$1" \ - -in system_tests/secrets.tar.enc \ - -out system_tests/secrets.tar -d +gcloud kms decrypt \ + --location=global \ + --keyring=ci \ + --key=kokoro-secrets \ + --ciphertext-file=system_tests/secrets.tar.enc \ + --plaintext-file=system_tests/secrets.tar tar xvf system_tests/secrets.tar rm system_tests/secrets.tar diff --git a/packages/google-auth/scripts/encrypt-secrets.sh b/packages/google-auth/scripts/encrypt-secrets.sh index c6291b7f3347..b6521e8f596f 100755 --- a/packages/google-auth/scripts/encrypt-secrets.sh +++ b/packages/google-auth/scripts/encrypt-secrets.sh @@ -20,13 +20,13 @@ ROOT=$( dirname "$DIR" ) # Work from the project root. cd $ROOT -read -s -p "Enter password for encryption: " PASSWORD -echo - tar cvf system_tests/secrets.tar system_tests/data -openssl aes-256-cbc -k "$PASSWORD" \ - -in system_tests/secrets.tar \ - -out system_tests/secrets.tar.enc -rm system_tests/secrets.tar -travis encrypt "SECRETS_PASSWORD=$PASSWORD" --add --override +gcloud kms encrypt \ + --location=global \ + --keyring=ci \ + --key=kokoro-secrets \ + --plaintext-file=system_tests/secrets.tar \ + --ciphertext-file=system_tests/secrets.tar.enc + +rm system_tests/secrets.tar \ No newline at end of file diff --git a/packages/google-auth/scripts/obtain_user_auth.py b/packages/google-auth/scripts/obtain_user_auth.py deleted file mode 100644 index ae864666a5ec..000000000000 --- a/packages/google-auth/scripts/obtain_user_auth.py +++ /dev/null @@ -1,65 +0,0 @@ -# Copyright 2016 Google Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""This program obtains a set of user credentials. - -These credentials are needed to run the system test for OAuth2 credentials. -It's expected that a developer will run this program manually once to obtain -a refresh token. It's highly recommended to use a Google account created -specifically for testing. -""" - -import json -import os - -from oauth2client import client -from oauth2client import tools - -HERE = os.path.dirname(__file__) -CLIENT_SECRETS_PATH = os.path.abspath(os.path.join( - HERE, '..', 'system_tests', 'data', 'client_secret.json')) -AUTHORIZED_USER_PATH = os.path.abspath(os.path.join( - HERE, '..', 'system_tests', 'data', 'authorized_user.json')) -SCOPES = ['email', 'profile'] - - -class NullStorage(client.Storage): - """Null storage implementation to prevent oauth2client from failing - on storage.put.""" - def locked_put(self, credentials): - pass - - -def main(): - flow = client.flow_from_clientsecrets(CLIENT_SECRETS_PATH, SCOPES) - - print('Starting credentials flow...') - credentials = tools.run_flow(flow, NullStorage()) - - # Save the credentials in the same format as the Cloud SDK's authorized - # user file. - data = { - 'type': 'authorized_user', - 'client_id': flow.client_id, - 'client_secret': flow.client_secret, - 'refresh_token': credentials.refresh_token - } - - with open(AUTHORIZED_USER_PATH, 'w') as fh: - json.dump(data, fh, indent=4) - - print('Created {}.'.format(AUTHORIZED_USER_PATH)) - -if __name__ == '__main__': - main() diff --git a/packages/google-auth/scripts/travis.sh b/packages/google-auth/scripts/travis.sh index 28bf5faab468..2c34091c588c 100755 --- a/packages/google-auth/scripts/travis.sh +++ b/packages/google-auth/scripts/travis.sh @@ -37,6 +37,6 @@ if [[ -n $SYSTEM_TEST ]]; then fi fi -# Run tox. -echo "Running tox..." -tox +# Run nox. +echo "Running nox..." +nox \ No newline at end of file diff --git a/packages/google-auth/setup.cfg b/packages/google-auth/setup.cfg index 0f15a3826863..7c2b2874c477 100644 --- a/packages/google-auth/setup.cfg +++ b/packages/google-auth/setup.cfg @@ -1,17 +1,2 @@ [bdist_wheel] -universal = 1 - -[pytype] -# Where to start analysis. -inputs = . -# Some files aren't worth analyzing, namely tests. Hidden directories (e.g. -# .tox) are automatically filtered out. -exclude = tests system_tests -# All pytype output goes here. -output = pytype_output -# Python version (major.minor) of the target code. -python_version = 3.6 -# Paths to source code directories, separated by ':'. -pythonpath = . -# Errors to disable. -disable = pyi-error +universal = 1 \ No newline at end of file diff --git a/packages/google-auth/synth.metadata b/packages/google-auth/synth.metadata new file mode 100644 index 000000000000..ebfeb7f1211d --- /dev/null +++ b/packages/google-auth/synth.metadata @@ -0,0 +1,12 @@ +{ + "updateTime": "2019-09-12T22:40:28.148018Z", + "sources": [ + { + "template": { + "name": "python_library", + "origin": "synthtool.gcp", + "version": "2019.5.2" + } + } + ] +} \ No newline at end of file diff --git a/packages/google-auth/synth.py b/packages/google-auth/synth.py new file mode 100644 index 000000000000..069b21472d31 --- /dev/null +++ b/packages/google-auth/synth.py @@ -0,0 +1,10 @@ +import synthtool as s +from synthtool import gcp + +common = gcp.CommonTemplates() + +# ---------------------------------------------------------------------------- +# Add templated files +# ---------------------------------------------------------------------------- +templated_files = common.py_library(unit_cov_level=100, cov_level=100) +s.move(templated_files / '.kokoro') # just move kokoro configs \ No newline at end of file diff --git a/packages/google-auth/system_tests/.gitignore b/packages/google-auth/system_tests/.gitignore index f6bf39d14b0c..be605500b184 100644 --- a/packages/google-auth/system_tests/.gitignore +++ b/packages/google-auth/system_tests/.gitignore @@ -1,2 +1,2 @@ data -secrets.tar +secrets.tar \ No newline at end of file diff --git a/packages/google-auth/system_tests/noxfile.py b/packages/google-auth/system_tests/noxfile.py index 5f9291a2f6d7..864a5be32704 100644 --- a/packages/google-auth/system_tests/noxfile.py +++ b/packages/google-auth/system_tests/noxfile.py @@ -26,10 +26,12 @@ import subprocess from nox.command import which +import nox import py.path HERE = os.path.abspath(os.path.dirname(__file__)) +LIBRARY_DIR = os.path.join(HERE, "..") DATA_DIR = os.path.join(HERE, "data") SERVICE_ACCOUNT_FILE = os.path.join(DATA_DIR, "service_account.json") AUTHORIZED_USER_FILE = os.path.join(DATA_DIR, "authorized_user.json") @@ -167,66 +169,88 @@ def configure_cloud_sdk(session, application_default_credentials, project=False) # Test sesssions +TEST_DEPENDENCIES = ["pytest", "requests"] +PYTHON_VERSIONS=['2.7', '3.7'] -def session_service_account(session): - session.virtualenv = False +@nox.session(python=PYTHON_VERSIONS) +def service_account(session): + session.install(*TEST_DEPENDENCIES) + session.install(LIBRARY_DIR) session.run("pytest", "test_service_account.py") -def session_oauth2_credentials(session): - session.virtualenv = False +@nox.session(python=PYTHON_VERSIONS) +def oauth2_credentials(session): + session.install(*TEST_DEPENDENCIES) + session.install(LIBRARY_DIR) session.run("pytest", "test_oauth2_credentials.py") -def session_default_explicit_service_account(session): - session.virtualenv = False +@nox.session(python=PYTHON_VERSIONS) +def default_explicit_service_account(session): session.env[EXPLICIT_CREDENTIALS_ENV] = SERVICE_ACCOUNT_FILE session.env[EXPECT_PROJECT_ENV] = "1" + session.install(*TEST_DEPENDENCIES) + session.install(LIBRARY_DIR) session.run("pytest", "test_default.py") -def session_default_explicit_authorized_user(session): - session.virtualenv = False +@nox.session(python=PYTHON_VERSIONS) +def default_explicit_authorized_user(session): session.env[EXPLICIT_CREDENTIALS_ENV] = AUTHORIZED_USER_FILE + session.install(*TEST_DEPENDENCIES) + session.install(LIBRARY_DIR) session.run("pytest", "test_default.py") -def session_default_explicit_authorized_user_explicit_project(session): - session.virtualenv = False +@nox.session(python=PYTHON_VERSIONS) +def default_explicit_authorized_user_explicit_project(session): session.env[EXPLICIT_CREDENTIALS_ENV] = AUTHORIZED_USER_FILE session.env[EXPLICIT_PROJECT_ENV] = "example-project" session.env[EXPECT_PROJECT_ENV] = "1" + session.install(*TEST_DEPENDENCIES) + session.install(LIBRARY_DIR) session.run("pytest", "test_default.py") -def session_default_cloud_sdk_service_account(session): - session.virtualenv = False +@nox.session(python=PYTHON_VERSIONS) +def default_cloud_sdk_service_account(session): configure_cloud_sdk(session, SERVICE_ACCOUNT_FILE) session.env[EXPECT_PROJECT_ENV] = "1" + session.install(*TEST_DEPENDENCIES) + session.install(LIBRARY_DIR) session.run("pytest", "test_default.py") -def session_default_cloud_sdk_authorized_user(session): - session.virtualenv = False +@nox.session(python=PYTHON_VERSIONS) +def default_cloud_sdk_authorized_user(session): configure_cloud_sdk(session, AUTHORIZED_USER_FILE) + session.install(*TEST_DEPENDENCIES) + session.install(LIBRARY_DIR) session.run("pytest", "test_default.py") -def session_default_cloud_sdk_authorized_user_configured_project(session): - session.virtualenv = False +@nox.session(python=PYTHON_VERSIONS) +def default_cloud_sdk_authorized_user_configured_project(session): configure_cloud_sdk(session, AUTHORIZED_USER_FILE, project=True) session.env[EXPECT_PROJECT_ENV] = "1" + session.install(*TEST_DEPENDENCIES) + session.install(LIBRARY_DIR) session.run("pytest", "test_default.py") -def session_compute_engine(session): - session.virtualenv = False +@nox.session(python=PYTHON_VERSIONS) +def compute_engine(session): + session.install(*TEST_DEPENDENCIES) + # unset Application Default Credentials so + # credentials are detected from environment + del session.virtualenv.env["GOOGLE_APPLICATION_CREDENTIALS"] + session.install(LIBRARY_DIR) session.run("pytest", "test_compute_engine.py") -def session_app_engine(session): - session.virtualenv = False - +@nox.session(python=["2.7"]) +def app_engine(session): if SKIP_GAE_TEST_ENV in os.environ: session.log("Skipping App Engine tests.") return @@ -252,6 +276,8 @@ def session_app_engine(session): # Vendor in the test application's dependencies session.chdir(os.path.join(HERE, "app_engine_test_app")) + session.install(*TEST_DEPENDENCIES) + session.install(LIBRARY_DIR) session.run( "pip", "install", "--target", "lib", "-r", "requirements.txt", silent=True ) @@ -265,7 +291,9 @@ def session_app_engine(session): session.run("pytest", "test_app_engine.py") -def session_grpc(session): - session.virtualenv = False +@nox.session(python=PYTHON_VERSIONS) +def grpc(session): + session.install(LIBRARY_DIR) + session.install(*TEST_DEPENDENCIES, "google-cloud-pubsub==1.0.0") session.env[EXPLICIT_CREDENTIALS_ENV] = SERVICE_ACCOUNT_FILE session.run("pytest", "test_grpc.py") diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index e61707e5aac970b93b67a68c63927aa862875efc..1106f8a9154dada1eda23da37373f78eeb683bd9 100644 GIT binary patch literal 10323 zcmV-ZD6H2CBmnkJRTE|uK;s}+8cF0tRQ6BKbc8T0KF?7oZU%3*)qTiNHc zQ^kV*Q~VsQx2=%8jd)Y!%P3|c)%X`o;XsSis@SV$v@6Gm4Rm_5niuM9 z{chzmKqDq%Kvc$w!Gq_10Iu8D1Yn#_^am+8<80E3A|Si-4%qJorUlVl*0b$t04JqA zyx=##7`=t9ljM-o!#3+2mumjwnPXJEbtr^PC_#|3gbIqJK55@-lsgn+FEGpzN)`{f z1BR`0ge#MmOLpLIvC(c<{VRit2PoeaZmezm=c8Sx+*c-6nora`FiR;wE-p&Qolw>oiVZ!JgFx^D8Z+<}LZ9Eob|ho!v7x@9b}0F4&F zG4u(7Tk(zF3?^e6(B*l>>5%y9KP4l7sF_;5C^Vgwb<6Zn0`X5RvZfNGT6a! zPIX=I-gR}Moz8Z{Lb5<)YV$}2Hz`%*@?ZlSs=nHL?e%+!B}s%6aG!sj^@xnAkS<| zr^@7Idt7zM_~mq~>2%EAmX23zMvaUBFzAV|@l7*N-}|s;l$ACAbT`1gO$B?J8Mn7w zo_bt=h|>K!Qfv;@;ON_Dx4A42>I#?5E-|gB%e@Nb0u*2P1i4;gTnWOvs1Y5do_4)*?y?k zS+()vAll?=w9G|-Rt6hzSIqk*4sBKT$3Kejj@pk48>7b3cgAa$S#E%6DE@eJvz#Tl z+7JAlnRy;S=QO>@QkHAAB9Z?4qX51aM5kO!vZl^NI+ zU8>*vS7;)!*Bz0;zr7RwM`}{zXCc5&oaRgij|+TsUzjX|K|6aCx}bGFm0{!1)gWesK@X}R0+6h^ZR$d^mf=@tcPMIIMwj2@t507Ode(|l*eiRb|!@sjL#V-bmLHEa2Zh!1qh&w>R~}N`j1IJAI9o3j&^uwmaV4l<7Gu1 zfv3!ueaLzH`SS*&9(Q%*1z7xWboD1AuGdlb3=z1WN*Ahr@TxJ6A<`i)MFUZ}R~LtQ z%?bVy0*HW_?Uy`2fOJrc_yx2`d6V;j8jAp*V?HwjTvQRIt%%65*fcI0=E^Y%u6ZC+ zfEKyPxTT#Q;31=B#i)3CI~jQiz>^(HHZ9Vs<+N1OEkGReEm(j?tkJ6^IH(#WlPa0H zV-8u87LLH00xp!ZRfUm5njqK~H;*_LkyQXcm;e62Kvq*eVF^+XqKt#Hs-VEldLTXe z^F{~|GoxXQ&0#WK5&t~IqDrHJ{JPp6Srr&9Hf&r{&8btV`7B8n!VX#wiuH-;Q|V%7 zL^HHOB ziS_ohPK>yKn} z5hHc*81+;Xu-uAVzMH`mcNeb&u%Nk=4C{NKDU`tp$bh#rul4XQ0uvG~T+3f%TeYi2>|nX2xDQ@3uW z;gJn*L=ovIH0hziLdw^vdUwVkHe^N?rty8<)?4K4fM%LlNSt2)N&8uH8f`J^WKJ3G zZbFr+`3BMd*Sw4gz681zSofvKgT$R$*bWwW#r1g2n}}aRD{_7Yrl+jGiXt`B(Qw|1 zo+Dg6ccrm(4rbQ&xE#Iv$Cl&9EZ1@u_l?=G#Zipx)VM!;xL(0xLLqGfvr%Xb+o2}W zBM*Wiqz}jI@|7R1rsJ}~6HMZg(?WpK12OEvD0Mh6eMc4XltbP1n~UWS9xp>*n2!hh zx-(otzZrA!+35@A4M{@>-JP|Wt3ql?()W+P3);42e-JyM01i`AE_#z|W7I7^<#0Cu zbxzyt{v(!`U=Ny_z1L(z;tSF_QdAJ?wn0m8HX-dUjKKl&cUU#_Ei$MrfXJ-Js`C(V z2`|y7r?uLnXF%3=+oAtTN-e+c?ozIM4$8T$-5>D-1ma9JXUy1CD3MWpN0o;Pgb{CB zBJtYgWD=TwFSH1{QjAii+&pkvm-ItlB(JC*E`5fF7^?OkF3vwmX$-uljt=E?15}$N z6i6>zLqYpjrEIc?0K*|2*&KdXW@zP+!01L220O~G%BIE(4YVLA-x8A`t~+JFHXI@I z<*-!Ihe{zVL{!95=O3DIX=vze+(?IVS=I1MGC4!jY<8o@JVbt165|1d_5oseO4-oB z)XcxNkDjPb@(z{fVB9+rZa# z;i6NZQv|7{{d9x{Z=HUx1-vufupb%EdQqIFj*zOT16B)b{uim zwLSes0uS+_O}dFBGU1W*a~Y~akC&z5SgtLLfn-97+tX@QP_^qL`1e2UTLLqXDf4Jy ze*B3m7YVw#$a3io8r|(+v8o$2wZD!<5-&#`gLqQR95FoBXKE?3xW05CIkwP)@6N6E zq6e=(3`|Ba!JjCRa;A{NE)R>B)Oo~y9?EetkQkT|faVB?!_-e-y$?o_}9UN?VIsPUO(5`y+ZplE@AW%4T9 zq%ak-KfbHNqxj=wQ2ULy78NZVcO}>j8?ms&s6Dybtg-xX=OApDWW}E-}GtVp%nA^A{MZFp4EhNQGD`Iljz# z=`wb{tUx&;KQ;G|!qq`b)MDAzG8PJuh27{tj5i;jlT3SBdrD!{cmFjwWVR`%v!Bx+ zHj}*|RS4SHp*2ktoq}3G{Viw#-xM-M-SWo?Ql~RN?a;OSg1e9JyE9&+!ZPF1< zD_J;SwAw=+(??pVcl|qf4U)vs!jB=AC?j|83^BP$72Oc&oPequMPoI5$#nnZC|Vr` z>p~>}M&L*dHDa-(a;0wjTmYdX{RdCfVUsSxw9yNmSa}y1hm$VTz(UXj?fX!!lnBN<`aOR-LvTskvF;Ap_RsU z50+i!BHs0$*i%JTZt6ZL1Ye9r_rNCvHE+3_<4rK7&s*#xJ5y&N;w3P0!AgHW1M%p6 zYAgK`>QH_>4!&B<4!YovFodUS&+|v5`F@*EC-V+RO#qJ1mYB=ay0VGp&XgX~=aj7FUrz?K#c=b zVu013E!^N)c8)%=qgF^=;(rn&1=wz?Aei4&5IKzW{k`Z=Ztnn6S$OK|*g ziw_kCJs#c2xNPjF)yhf?4*n4q&1Xrz2bt#7b*a3CB(Llu&OoHo97+H1pp&-o1Y6s< zOwsTD8cy$iILF35OGI85S?_YphQ@_v3r6A+UFlLpep~68r5f65G|li$a*w1H;~hp? z%9fJ#{V$h4w{VB&eqqc0fDqK5PONJgvV`2>mNJ6$Sn^_QOo3NY^oyuXTm&06wl} zNiBP>NFuWhd3Es}c$$P-0-4s!f75KKbaNv}FRvQPNzjf*|E+dE7fR9w!ZEJ?@Bc)) z7Cf?U?GDzf%0ZJ8d$#`X>oS+Gz0V68li$`4YxSFr+>sp zCFh~D%Ri%x&K*2lhR_#s#w}*0v2t0-sqkv&^lUC=PtB)qhGGt~BA-;Nnz-*aX8{~s zi1_Mp$-23r@m@&}r_!~*2)X{(NT@EL4*6G4JoAyEPZmPP z;&mgI#}!;?XeEIsIZy+x1pDsjk<0z0l!)=;O!~DKS|MLfU%_?{?p1FbZy(qG9mEf1 zk9!!vtzOdIB1dlTUvId{f@`-6C0E)z_?f!dSw$HtrWv)zp?7!I&DrQy>NCbuLwZ_$ zO-gcKDhQHDIVFvQs-9$jfe!C#HGF*4IkmNXM(8f>gD}2t5nK#tNwEO2E2Q!My~X9ahjemi3}h+eGw{TdjVT~R z{~{hjjaCqMDg;uHJnNjkz7J;}&XHm;*U-DbZ_U6w zCGVEiy~xY)J#anr&QgjS)KM3m8#>E1#trI<8mgknf~zRHY-x09h~*61S)Bo7u3p@} zLp0rHnJkTm_(@r`Rqy#GNKI=&;h8{YZwElU%xEl7!2g&!h(!ng+}En0^MD)a3; zG6su$=}B}9JK;AcO{@+jG%-F6P6ks@Eq*=X$z+aY)M3mu_xRr_`BQRDhVSa>EXg zOS`A}0p08lkgRjT-1h~+xDb=m!>nC;<>~SQOLaCK^#PJ)z&;pKTh%~boEQ(m%;U`d z64xdARHT+0auewn1yz=sp4you5S zL%p6S)zpgW6@?df)V&xoYP~@YaR2*{TCeGt!HT9P;HtJfF9b2ltgrA-z}~OB%sT#} zU3FrDA-g>AqZN`1he`1Etd%-%r~M8LP; z=)xDB?-2Y!M$M+FFDEI-M*6_z;!IZY(~Q)}=RuPI_t3~@T)6nMyQ$K!;pRpCDR3Av zsJ?O_AMvIggtb^B{IYKP3OdH>2F(L0|8V1SSAHGPQBjE@lx_4K90&0UtnkLYV{%wx zXComC8Y5>4m9;tY0&e47M(60|u?wr2VXFe|_)=@xuW$3#MQxZCzY&HaA8wdvqQ4L3 z@c4zXKaRCGt|ovRjHfl=jPPT)^UMuCz5!^N&6ZkteJY93jAcr{F3i1VB#a*PJe7@_ z;Zlcbi77;v+y7_Q-I1%#=ioQWEstfC3!>(0{Oy7stA%>|AuOS>s&YdW`|ffxrYtfs z3d!Sz>;h2X96Vj@coCUrFq~-o`&U%Nq*b57a?r=_qS)1#>*X%M+sx`mL-N_pVMl0a z>1dLKTddTkI=k`dS=_-Ox9rNocC2e)BKeTeE@1#wsP(sYs$D`0cB-P8y{UAsi!i>( zlBgvPZ0w{YvnppLxr)yQEM5zD@Z}6UpkFuA+p&1H*{$>V3LdAx`As3-cpKCkcxC6O zhPP28t3<=nzK@7*7_ADsjbxW;t{_ zewMEv_G}LCDNwU+;_6th48K*-bz<5?z70_ROiby(Zr~@SyG5n3R5w)q8CLcuChhK1 z79o$AJ%Ym#HXC>}L|-!blv+Ut##ow1zz7B;g?Uzl|t!ME7WD~?cXn3*}eP@#7 z&>wt^hMo3zo<M>hO6=GxLx@get{#Pa#_MBWU3$eofqU)U{p@D8%m9`NQ+E1Mwj0G&URzNwE=rr&+`GRiW^loPA<2wp) zXI{iy?9*4g`64GkzoeUxHS<(L@3K7|rm$4i$;_DB*~;&=UF!(fO^sDy(G&fl2r&HY zdc;yzKPHI-LKm}QynF{dSSvkyZA~=+;aIylkH&ghn+QHs4Hs7lpQZ{ND@$wfo{S>| zkZ1Y{qK;jg^0E9_UZGfI9q216U=mVIqq9+;2xw#12Mqq!FIR)t!{0jWnGsi?$?H9n zsa|&OTVeG<-R$eN@)e(C@)86~z0gMuW#ZYLI+88}yS|>|tnezPo~FNcNHcXH)Kz&h zoGBUV#X|OI=K2v=w?Ikv0nNKy_=VM==Pci#N6|mGdg!F+$e_(Wk?>`-Uy$Z$1x4A4 zxdsQSIz&qkI#;*mK7qpCElkZsqbWFLZyzT>cX>RW+BSa z*?TR`gGV2h&d`!c;A-R8$YKK4ApiO-e$;aH359XRO4ITTh{VbX|AW#|>rjuH8#$b@ z3#DRg7HqA#m$h|E8TDt;qm-~yskXyJSDU9Gmsw`7gQRYj|KGST#@WRorL~qN&)gCh zO`FzmTJNWCC4U`er3J5L}J9=TlR>=_oA)4MgB9JD{;~N?Ix>y2(PVao}Cae7pT>)GWvtVrVY5KCY#RSU| zIj&PyeGTyxFmu;Sv6NoGSsd&24`{GQTE2jS&$x0iMYaOxn@sAP-7}2W>^k*yrQ}D& z&8dW6+Y0g4^H6lHl|M9xYp*%7*+ zF#_%Swegnk{v$CoA_T0Wk8TAw0^Fgi?A;#0oYXgi)U+iO6$A56r z)ubrm-og?|lMBW)+P_zAr(AU)D;@}LCnP863YtYM_OVhuHcMZ2W6_uQ07EQ9U~*_8 zJ_xOaJEr5?Mu$k+!q=+SH7du$!SzRm7SI6%`!=LApf` zyp*?%Xy*+V9MzmcQgffE5}a+ z$_w8EckHc$G!pU6I#0k5R6h8%R8*a9rP3&`-}15KhPu8#lIrg|xT*4Ok7Cw(wRA4B zxyh9R^gJv*nI)KY2;Iik!qYtPv2VOOEi$UP?8|dGN(z7ptQzXBaA-&JMCon>qyY>B4s~S_i;~jb_i6s>rANY4jX>L21};>x)iNRljujMF_6z zehcM^UhJ&|V88#ujK-+Q_f90q+9A{575{qQtwvwf=Exu)dYjv=@-R=t zjMs;y=*QR$E8j?}km^J|&lhd1g}hQpNJb%tS|JGKKnMGOVrG;SqQ-BBq3nw}S)BhSA$SDlha zPe4wAV90H8IBCo!l-VA@H{-6OnvM`45Qc`h8>aGYesY>eCqY}#%F%ICza2dRCXaRx zBMzNC_bdWgo z4{NAdh$jJOt6B}Z|B*w2$d9^0=ENr`b=+0X>m4aas~@GCBdX}OI5=k3hrxob;>*Gx zNv0{@W>;6y18pVP)`FQEa|8-bc%2D)bQu8XDkE{+iA=>3S696NKL zSx5^m*8hkP)Erg0IaQy#ZhDwx5vI>Tw-BpAoD8|ia!=_Rx1oHrIi@L;G=&aeR2TwC z8JaDOAVw{#3D>tD;7Vq^Cv#*uO%Z{w+gm`oZ!Y~l;m&Xq^^Ktnn9}9>O`}$53XSab z2j)`Q6;kU1je(fmLi+e%fLhs& z%XAA1IWSv|u-t&>0;WyOxwQ-A>I_N_lISVprXxn0$#HT%gY0kkHcGR`vuBY^5$B}Z zy-)~jMPL=7T0oHXvKBo+59HQ-tY2);QC0Lrtv%niGvhloQL_P~cg7qSUoT zQo-;LiiUhBt$le+ndUBde#D=mn`-6OJ&_>$wUYQ6?2nKb+0LtQ91LPU=5XtRx&9fro-ci-C!tz6Cn)wjX29dTRM3XG4A#x*PeghDd!1gGXEva%-XI*nEL`n3k+E#sJlKXwqFf%FHVsPyA=*fLeEivvtN zZ!c2MT@}!muL649+lwMB4p93Pms$za%_uur#XC0V;b@q5z&sj1GroEP1&FY3!si21 z;~OOBWeIW#;6uAg^w{F)XQ2xI9u1ers zeih$abZ9OeRX4$0UnMm4mBKw$Q$g|J(ix$AE&jpPF_&lDyIe|37qx*Ipo&e2o>gO+ zX60xhz`|}W&;Ul6VCF>}$hkgXj^FdfYnxt9`Gjd@5L*zT*hWE-Hp`!i_5|+{ z_dJgh-=uG1;eAH%A&tFF! zXFqjib9$b!90e+@puPj_4OAxj=s?sGu`m2ONb;eCnttFjsf(>NRSMZ9hucG&qSXzv zT3DQp3_>PEf8?r%RSXDbmBjiEKkK;{QEnb>g4qVifYL|H`Fx& z*J)AF?Y7mE%If(fkdRTw1HiLUcX#C%0*;(8+r82CNJrY5!v-v9gN6fX#Y zaubYnvuFJDn4GKz#A9?(EOEKA`01jUexdjvtmLpvQiqP7lRKQ};kEyk;w$!@IT`ou zqGntLPM~8O$*oiI%8C;2MD~1Gi*&+G)((^f#DJ7`wUa>;XC>fZpcN5g#1G{V zes7JlADS}b&h&7B4FqE6gw?wPLdauS74ZbnuR_^B-2F3L+c+p1u|*d(BT-DT2&@|g z8tXEA;QBHu-T8eM{I#ifB!P|2Nd9ySpvOMTPVN>YZqTaXs8>%L$#Y-hgMZQNrA413 zzF8=BN^P!%a0s0$JBRPlfI>$Pw{Zt8lOS2VBV&6p#cWm}(l^f8#TgcfUfJpJ1n8D0 zDX>ox*<-OXcUyMP1~EVkViP1Gmv!4iREopxfU3```plAJA6psd;~Xoz!}}sIj&(Wj zJ@qjSKZpoh3h%;9cD?Xrux~F_sRze|HAtN;M41a>K27`>iLr;AyEG$0v8dy&{&Hew zzG293LS7Wc>yIfPd9`qU_tm6Wu{1=cHv^Q-fNNYM*uwnHVa+g3orVW!`U!}yZ&i?K zX&jo$80!Jx;^lNfsbU90m8yhOh=FYq(eNQ#Pkf%{>?*)7C%oF~;fS zu*r^UD8TKxX=Xw`DyvwscjB{zsZdPPcGcIt!P)*S#V{`Pp^rf$R5=d9avi}YCbQRY zr8*6zg@=VRx@8^UeK!XF5)cIiR))4)EaC4LH(}3Cm~NZ@M(c~ z+j)vQm*4?M6a?h?ZdWE;|0sBY4D9uRfdq{Kb ln#y2j*5ExtA`Jil literal 18464 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 T?j`+JzSN(>jVck@!DaJx-Xsx3 diff --git a/packages/google-auth/system_tests/test_compute_engine.py b/packages/google-auth/system_tests/test_compute_engine.py index 3fd420c1eb31..44f162792ef1 100644 --- a/packages/google-auth/system_tests/test_compute_engine.py +++ b/packages/google-auth/system_tests/test_compute_engine.py @@ -15,8 +15,8 @@ import pytest import google.auth -from google.auth import _helpers from google.auth import compute_engine +from google.auth import _helpers from google.auth import exceptions from google.auth.compute_engine import _metadata diff --git a/packages/google-auth/system_tests/test_grpc.py b/packages/google-auth/system_tests/test_grpc.py index ea528307cb9c..f025fc0b7969 100644 --- a/packages/google-auth/system_tests/test_grpc.py +++ b/packages/google-auth/system_tests/test_grpc.py @@ -16,7 +16,9 @@ import google.auth.credentials import google.auth.jwt import google.auth.transport.grpc -from google.cloud.gapic.pubsub.v1 import publisher_client +from google.cloud import pubsub_v1 +from google.cloud.pubsub_v1.gapic import publisher_client +from google.cloud.pubsub_v1.gapic.transports import publisher_grpc_transport def test_grpc_request_with_regular_credentials(http_request): @@ -25,12 +27,13 @@ def test_grpc_request_with_regular_credentials(http_request): credentials, ["https://www.googleapis.com/auth/pubsub"] ) - channel = google.auth.transport.grpc.secure_authorized_channel( - credentials, http_request, publisher_client.PublisherClient.SERVICE_ADDRESS + transport = publisher_grpc_transport.PublisherGrpcTransport( + address=publisher_client.PublisherClient.SERVICE_ADDRESS, + credentials=credentials, ) # Create a pub/sub client. - client = publisher_client.PublisherClient(channel=channel) + client = pubsub_v1.PublisherClient(transport=transport) # list the topics and drain the iterator to test that an authorized API # call works. @@ -40,19 +43,18 @@ def test_grpc_request_with_regular_credentials(http_request): def test_grpc_request_with_jwt_credentials(): credentials, project_id = google.auth.default() - audience = "https://{}/google.pubsub.v1.Publisher".format( - publisher_client.PublisherClient.SERVICE_ADDRESS - ) + audience = "https://pubsub.googleapis.com/google.pubsub.v1.Publisher" credentials = google.auth.jwt.Credentials.from_signing_credentials( credentials, audience=audience ) - channel = google.auth.transport.grpc.secure_authorized_channel( - credentials, None, publisher_client.PublisherClient.SERVICE_ADDRESS + transport = publisher_grpc_transport.PublisherGrpcTransport( + address=publisher_client.PublisherClient.SERVICE_ADDRESS, + credentials=credentials, ) # Create a pub/sub client. - client = publisher_client.PublisherClient(channel=channel) + client = pubsub_v1.PublisherClient(transport=transport) # list the topics and drain the iterator to test that an authorized API # call works. @@ -66,12 +68,13 @@ def test_grpc_request_with_on_demand_jwt_credentials(): credentials ) - channel = google.auth.transport.grpc.secure_authorized_channel( - credentials, None, publisher_client.PublisherClient.SERVICE_ADDRESS + transport = publisher_grpc_transport.PublisherGrpcTransport( + address=publisher_client.PublisherClient.SERVICE_ADDRESS, + credentials=credentials, ) # Create a pub/sub client. - client = publisher_client.PublisherClient(channel=channel) + client = pubsub_v1.PublisherClient(transport=transport) # list the topics and drain the iterator to test that an authorized API # call works. diff --git a/packages/google-auth/system_tests/test_oauth2_credentials.py b/packages/google-auth/system_tests/test_oauth2_credentials.py index a33b89fba51f..3ecd850788a6 100644 --- a/packages/google-auth/system_tests/test_oauth2_credentials.py +++ b/packages/google-auth/system_tests/test_oauth2_credentials.py @@ -17,7 +17,7 @@ from google.auth import _helpers import google.oauth2.credentials -GOOGLE_OAUTH2_TOKEN_ENDPOINT = "https://accounts.google.com/o/oauth2/token" +GOOGLE_OAUTH2_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token" def test_refresh(authorized_user_file, http_request, token_info): @@ -39,9 +39,13 @@ def test_refresh(authorized_user_file, http_request, token_info): info = token_info(credentials.token) info_scopes = _helpers.string_to_scopes(info["scope"]) + + # Canonical list of scopes at https://cloud.google.com/sdk/gcloud/reference/auth/application-default/login + # or do `gcloud auth application-defaut login --help` assert set(info_scopes) == set( [ "https://www.googleapis.com/auth/userinfo.email", - "https://www.googleapis.com/auth/userinfo.profile", + "https://www.googleapis.com/auth/cloud-platform", + "openid", ] ) diff --git a/packages/google-auth/tox.ini b/packages/google-auth/tox.ini deleted file mode 100644 index 59fd6aba1706..000000000000 --- a/packages/google-auth/tox.ini +++ /dev/null @@ -1,92 +0,0 @@ -[tox] -envlist = lint,py27,py34,py35,py36,pypy,cover,pytype - -[testenv] -deps = - certifi - flask - mock - oauth2client - pytest - pytest-cov - pytest-localserver - requests - requests-oauthlib - urllib3 - cryptography - grpcio; platform_python_implementation != 'PyPy' -commands = - pytest --cov=google.auth --cov=google.oauth2 --cov=tests {posargs:tests} - -[testenv:cover] -basepython = python3.6 -commands = - pytest --cov=google.auth --cov=google.oauth2 --cov=tests --cov-report= tests - coverage report --show-missing --fail-under=100 -deps = - {[testenv]deps} - -[testenv:py36-system] -basepython = python3.6 -changedir = {toxinidir}/system_tests -commands = - nox {posargs} -deps = - {[testenv]deps} - nox-automation - gapic-google-cloud-pubsub-v1==0.15.0 -passenv = - SKIP_APP_ENGINE_SYSTEM_TEST - CLOUD_SDK_ROOT - -[testenv:py27-system] -basepython = python2.7 -changedir = {toxinidir}/system_tests -commands = - nox {posargs} -deps = - {[testenv]deps} - nox-automation - gapic-google-cloud-pubsub-v1==0.15.0 -passenv = - SKIP_APP_ENGINE_SYSTEM_TEST - CLOUD_SDK_ROOT - -[testenv:docgen] -basepython = python3.6 -deps = - {[testenv]deps} - sphinx -setenv = - SPHINX_APIDOC_OPTIONS=members,inherited-members,show-inheritance -commands = - rm -r docs/reference - sphinx-apidoc --output-dir docs/reference --separate --module-first google - -[testenv:docs] -basepython = python3.6 -deps = - sphinx - -r{toxinidir}/docs/requirements-docs.txt -commands = make -C docs html - -[testenv:lint] -basepython = python3.6 -commands = - flake8 \ - --import-order-style=google \ - --application-import-names="google,tests,system_tests" \ - google tests - python setup.py check --metadata --restructuredtext --strict -deps = - flake8 - flake8-import-order - docutils - -[testenv:pytype] -basepython = python3.6 -commands = - pytype -deps = - {[testenv]deps} - pytype From 2405819fde8a5949aa171cbbc131668afd3558e7 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Tue, 29 Oct 2019 14:30:08 -0700 Subject: [PATCH 229/966] docs: change 'name' to distribution name (#379) --- packages/google-auth/.repo-metadata.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/google-auth/.repo-metadata.json b/packages/google-auth/.repo-metadata.json index 4c8ebe1bbf29..ac3191ce8721 100644 --- a/packages/google-auth/.repo-metadata.json +++ b/packages/google-auth/.repo-metadata.json @@ -1,10 +1,10 @@ { - "name": "google-auth-library-python", + "name": "google-auth", "name_pretty": "Google Auth Python Library", - "client_documentation": "https://googleapis.dev/python/google-auth-library-python/latest", + "client_documentation": "https://googleapis.dev/python/google-auth/latest", "issue_tracker": "https://github.com/googleapis/google-auth-library-python/issues", "release_level": "ga", "language": "python", "repo": "googleapis/google-auth-library-python", "distribution_name": "google-auth" -} \ No newline at end of file +} From f97bf08de76b1b90dfd5a75adea16162b84d6df2 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Mon, 4 Nov 2019 15:40:13 -0800 Subject: [PATCH 230/966] chore: release v1.7.0 (#380) --- packages/google-auth/CHANGELOG.md | 266 +++++++++++++++++++++++++++++ packages/google-auth/CHANGELOG.rst | 265 ---------------------------- packages/google-auth/setup.py | 4 +- 3 files changed, 269 insertions(+), 266 deletions(-) create mode 100644 packages/google-auth/CHANGELOG.md delete mode 100644 packages/google-auth/CHANGELOG.rst diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md new file mode 100644 index 000000000000..b74017f4e9a1 --- /dev/null +++ b/packages/google-auth/CHANGELOG.md @@ -0,0 +1,266 @@ +# Changelog + +[PyPI History][1] + +[1]: https://pypi.org/project/google-auth/#history + +## 1.7.0 + +10-30-2019 17:11 PDT + + +### Implementation Changes +- Add retry loop for fetching authentication token if any 'Internal Failure' occurs ([#368](https://github.com/googleapis/google-auth-library-python/pull/368)) +- Use cls parameter instead of class ([#341](https://github.com/googleapis/google-auth-library-python/pull/341)) + +### New Features +- Add support for `impersonated_credentials.Sign`, `IDToken` ([#348](https://github.com/googleapis/google-auth-library-python/pull/348)) +- Add downscoping to OAuth2 credentials ([#309](https://github.com/googleapis/google-auth-library-python/pull/309)) + +### Dependencies +- Update dependency cachetools to v3 ([#357](https://github.com/googleapis/google-auth-library-python/pull/357)) +- Update dependency rsa to v4 ([#358](https://github.com/googleapis/google-auth-library-python/pull/358)) +- Set an upper bound on dependencies version ([#352](https://github.com/googleapis/google-auth-library-python/pull/352)) +- Require a minimum version of setuptools ([#322](https://github.com/googleapis/google-auth-library-python/pull/322)) + +### Documentation +- Add busunkim96 as maintainer ([#373](https://github.com/googleapis/google-auth-library-python/pull/373)) +- Update user-guide.rst ([#337](https://github.com/googleapis/google-auth-library-python/pull/337)) +- Fix typo in jwt docs ([#332](https://github.com/googleapis/google-auth-library-python/pull/332)) +- Clarify which SA has Token Creator role ([#330](https://github.com/googleapis/google-auth-library-python/pull/330)) + +### Internal / Testing Changes +- Change 'name' to distribution name ([#379](https://github.com/googleapis/google-auth-library-python/pull/379)) +- Fix system tests, move to Kokoro ([#372](https://github.com/googleapis/google-auth-library-python/pull/372)) +- Blacken ([#375](https://github.com/googleapis/google-auth-library-python/pull/375)) +- Rename nox.py -> noxfile.py ([#369](https://github.com/googleapis/google-auth-library-python/pull/369)) +- Add initial renovate config ([#356](https://github.com/googleapis/google-auth-library-python/pull/356)) +- Use new pytest api to keep building with pytest 5 ([#353](https://github.com/googleapis/google-auth-library-python/pull/353)) + + +## 1.6.3 + +02-15-2019 9:31 PST + +### Implementation Changes + +- follow rfc 7515 : strip padding from JWS segments ([#324](https://github.com/googleapis/google-auth-library-python/pull/324)) +- Add retry to `_metadata.ping()` ([#323](https://github.com/googleapis/google-auth-library-python/pull/323)) + +## 1.6.2 + +12-17-2018 10:51 PST + +### Documentation + +- Announce deprecation of Python 2.7 ([#311](https://github.com/googleapis/google-auth-library-python/pull/311)) +- Link all the PRs in CHANGELOG ([#307](https://github.com/googleapis/google-auth-library-python/pull/307)) + +## 1.6.1 + +11-12-2018 10:10 PST + +### Implementation Changes + +- Automatically refresh impersonated credentials ([#304](https://github.com/googleapis/google-auth-library-python/pull/304)) + +## 1.6.0 + +11-09-2018 11:07 PST + +### New Features + +- Add `google.auth.impersonated_credentials` ([#299](https://github.com/googleapis/google-auth-library-python/pull/299)) + +### Documentation + +- Update link to documentation for default credentials ([#296](https://github.com/googleapis/google-auth-library-python/pull/296)) +- Update github issue templates ([#300](https://github.com/googleapis/google-auth-library-python/pull/300)) +- Remove punctuation which becomes part of the url ([#284](https://github.com/googleapis/google-auth-library-python/pull/284)) + +### Internal / Testing Changes + +- Update trampoline.sh ([302](https://github.com/googleapis/google-auth-library-python/pull/302)) +- Enable static type checking with pytype ([#298](https://github.com/googleapis/google-auth-library-python/pull/298)) +- Make classifiers in setup.py an array. ([#280](https://github.com/googleapis/google-auth-library-python/pull/280)) + + +## 1.5.1 + +- Fix check for error text on Python 3.7. ([#278](https://github.com/googleapis/google-auth-library-python/pull/#278)) +- Use new Auth URIs. ([#281](https://github.com/googleapis/google-auth-library-python/pull/#281)) +- Add code-of-conduct document. ([#270](https://github.com/googleapis/google-auth-library-python/pull/#270)) +- Fix some typos in test_urllib3.py ([#268](https://github.com/googleapis/google-auth-library-python/pull/#268)) + +## 1.5.0 + +- Warn when using user credentials from the Cloud SDK ([#266](https://github.com/googleapis/google-auth-library-python/pull/266)) +- Add compute engine-based IDTokenCredentials ([#236](https://github.com/googleapis/google-auth-library-python/pull/236)) +- Corrected some typos ([#265](https://github.com/googleapis/google-auth-library-python/pull/265)) + +## 1.4.2 + +- Raise a helpful exception when trying to refresh credentials without a refresh token. ([#262](https://github.com/googleapis/google-auth-library-python/pull/262)) +- Fix links to README and CONTRIBUTING in docs/index.rst. ([#260](https://github.com/googleapis/google-auth-library-python/pull/260)) +- Fix a typo in credentials.py. ([#256](https://github.com/googleapis/google-auth-library-python/pull/256)) +- Use pytest instead of py.test per upstream recommendation, #dropthedot. ([#255](https://github.com/googleapis/google-auth-library-python/pull/255)) +- Fix typo on exemple of jwt usage ([#245](https://github.com/googleapis/google-auth-library-python/pull/245)) + +## 1.4.1 + +- Added a check for the cryptography version before attempting to use it. ([#243](https://github.com/googleapis/google-auth-library-python/pull/243)) + +## 1.4.0 + +- Added `cryptography`-based RSA signer and verifier. ([#185](https://github.com/googleapis/google-auth-library-python/pull/185)) +- Added `google.oauth2.service_account.IDTokenCredentials`. ([#234](https://github.com/googleapis/google-auth-library-python/pull/234)) +- Improved documentation around ID Tokens ([#224](https://github.com/googleapis/google-auth-library-python/pull/224)) + +## 1.3.0 + +- Added ``google.oauth2.credentials.Credentials.from_authorized_user_file`` ([#226](https://github.com/googleapis/google-auth-library-python/pull/#226)) +- Dropped direct pyasn1 dependency in favor of letting ``pyasn1-modules`` specify the right version. ([#230](https://github.com/googleapis/google-auth-library-python/pull/#230)) +- ``default()`` now checks for the project ID environment var before warning about missing project ID. ([#227](https://github.com/googleapis/google-auth-library-python/pull/#227)) +- Fixed the docstrings for ``has_scopes()`` and ``with_scopes()``. ([#228](https://github.com/googleapis/google-auth-library-python/pull/#228)) +- Fixed example in docstring for ``ReadOnlyScoped``. ([#219](https://github.com/googleapis/google-auth-library-python/pull/#219)) +- Made ``transport.requests`` use timeouts and retries to improve reliability. ([#220](https://github.com/googleapis/google-auth-library-python/pull/#220)) + +## 1.2.1 + +- Excluded compiled Python files in source distributions. ([#215](https://github.com/googleapis/google-auth-library-python/pull/#215)) +- Updated docs for creating RSASigner from string. ([#213](https://github.com/googleapis/google-auth-library-python/pull/#213)) +- Use ``six.raise_from`` wherever possible. ([#212](https://github.com/googleapis/google-auth-library-python/pull/#212)) +- Fixed a typo in a comment ``seconds`` not ``sections``. ([#210](https://github.com/googleapis/google-auth-library-python/pull/#210)) + +## 1.2.0 + +- Added ``google.auth.credentials.AnonymousCredentials``. ([#206](https://github.com/googleapis/google-auth-library-python/pull/#206)) +- Updated the documentation to link to the Google Cloud Platform Python setup guide ([#204](https://github.com/googleapis/google-auth-library-python/pull/#204)) + +## 1.1.1 + +- ``google.oauth.credentials.Credentials`` now correctly inherits from ``ReadOnlyScoped`` instead of ``Scoped``. ([#200](https://github.com/googleapis/google-auth-library-python/pull/#200)) + +## 1.1.0 + +- Added ``service_account.Credentials.project_id``. ([#187](https://github.com/googleapis/google-auth-library-python/pull/#187)) +- Move read-only methods of ``credentials.Scoped`` into new interface ``credentials.ReadOnlyScoped``. ([#195](https://github.com/googleapis/google-auth-library-python/pull/#195), [#196](https://github.com/googleapis/google-auth-library-python/pull/#196)) +- Make ``compute_engine.Credentials`` derive from ``ReadOnlyScoped`` instead of ``Scoped``. ([#195](https://github.com/googleapis/google-auth-library-python/pull/#195)) +- Fix App Engine's expiration calculation ([#197](https://github.com/googleapis/google-auth-library-python/pull/#197)) +- Split ``crypt`` module into a package to allow alternative implementations. ([#189](https://github.com/googleapis/google-auth-library-python/pull/#189)) +- Add error message to handle case of empty string or missing file for `GOOGLE_APPLICATION_CREDENTIALS` ([#188](https://github.com/googleapis/google-auth-library-python/pull/#188)) + +## 1.0.2 + +- Fixed a bug where the Cloud SDK executable could not be found on Windows, leading to project ID detection failing. ([#179](https://github.com/googleapis/google-auth-library-python/pull/#179)) +- Fixed a bug where the timeout argument wasn't being passed through the httplib transport correctly. ([#175](https://github.com/googleapis/google-auth-library-python/pull/#175)) +- Added documentation for using the library on Google App Engine standard. ([#172](https://github.com/googleapis/google-auth-library-python/pull/#172)) +- Testing style updates. ([#168](https://github.com/googleapis/google-auth-library-python/pull/#168)) +- Added documentation around the oauth2client deprecation. ([#165](https://github.com/googleapis/google-auth-library-python/pull/#165)) +- Fixed a few lint issues caught by newer versions of pylint. ([#166](https://github.com/googleapis/google-auth-library-python/pull/#166)) + +## 1.0.1 + +- Fixed a bug in the clock skew accommodation logic where expired credentials could be used for up to 5 minutes. ([#158](https://github.com/googleapis/google-auth-library-python/pull/158)) + +## 1.0.0 + +Milestone release for v1.0.0. +No significant changes since v0.10.0 + +## 0.10.0 + +- Added ``jwt.OnDemandCredentials``. ([#142](https://github.com/googleapis/google-auth-library-python/pull/142)) +- Added new public property ``id_token`` to ``oauth2.credentials.Credentials``. ([#150](https://github.com/googleapis/google-auth-library-python/pull/150)) +- Added the ability to set the address used to communicate with the Compute Engine metadata server via the ``GCE_METADATA_ROOT`` and ``GCE_METADATA_IP`` environment variables. ([#148](https://github.com/googleapis/google-auth-library-python/pull/148)) +- Changed the way cloud project IDs are ascertained from the Google Cloud SDK. ([#147](https://github.com/googleapis/google-auth-library-python/pull/147)) +- Modified expiration logic to add a 5 minute clock skew accommodation. ([#145](https://github.com/googleapis/google-auth-library-python/pull/145)) + +## 0.9.0 + +- Added ``service_account.Credentials.with_claims``. ([#140](https://github.com/googleapis/google-auth-library-python/pull/140)) +- Moved ``google.auth.oauthlib`` and ``google.auth.flow`` to a new separate package ``google_auth_oauthlib``. ([#137](https://github.com/googleapis/google-auth-library-python/pull/137), [#139](https://github.com/googleapis/google-auth-library-python/pull/139), [#135](https://github.com/googleapis/google-auth-library-python/pull/135), [#126](https://github.com/googleapis/google-auth-library-python/pull/126)) +- Added ``InstalledAppFlow`` to ``google_auth_oauthlib``. ([#128](https://github.com/googleapis/google-auth-library-python/pull/128)) +- Fixed some packaging and documentation issues. ([#131](https://github.com/googleapis/google-auth-library-python/pull/131)) +- Added a helpful error message when importing optional dependencies. ([#125](https://github.com/googleapis/google-auth-library-python/pull/125)) +- Made all properties required to reconstruct ``google.oauth2.credentials.Credentials`` public. ([#124](https://github.com/googleapis/google-auth-library-python/pull/124)) +- Added official Python 3.6 support. ([#102](https://github.com/googleapis/google-auth-library-python/pull/102)) +- Added ``jwt.Credentials.from_signing_credentials`` and removed ``service_account.Credentials.to_jwt_credentials``. ([#120](https://github.com/googleapis/google-auth-library-python/pull/120)) + +## 0.8.0 + +- Removed one-time token behavior from ``jwt.Credentials``, audience claim is now required and fixed. ([#117](https://github.com/googleapis/google-auth-library-python/pull/117)) +- ``crypt.Signer`` and ``crypt.Verifier`` are now abstract base classes. The concrete implementations have been renamed to ``crypt.RSASigner`` and ``crypt.RSAVerifier``. ``app_engine.Signer`` and ``iam.Signer`` now inherit from ``crypt.Signer``. ([#115](https://github.com/googleapis/google-auth-library-python/pull/115)) +- ``transport.grpc`` now correctly calls ``Credentials.before_request``. ([#116](https://github.com/googleapis/google-auth-library-python/pull/116)) + +## 0.7.0 + +- Added ``google.auth.iam.Signer``. ([#108](https://github.com/googleapis/google-auth-library-python/pull/108)) +- Fixed issue where ``google.auth.app_engine.Signer`` erroneously returns a tuple from ``sign()``. ([#109](https://github.com/googleapis/google-auth-library-python/pull/109)) +- Added public property ``google.auth.credentials.Signing.signer``. ([#110](https://github.com/googleapis/google-auth-library-python/pull/110)) + +## 0.6.0 + +- Added experimental integration with ``requests-oauthlib`` in ``google.oauth2.oauthlib`` and ``google.oauth2.flow``. ([#100](https://github.com/googleapis/google-auth-library-python/pull/100), [#105](https://github.com/googleapis/google-auth-library-python/pull/105), [#106](https://github.com/googleapis/google-auth-library-python/pull/106)) +- Fixed typo in ``google_auth_httplib2``'s README. ([#105](https://github.com/googleapis/google-auth-library-python/pull/105)) + +## 0.5.0 + +- Added ``app_engine.Signer``. ([#97](https://github.com/googleapis/google-auth-library-python/pull/97)) +- Added ``crypt.Signer.from_service_account_file``. ([#95](https://github.com/googleapis/google-auth-library-python/pull/95)) +- Fixed error handling in the oauth2 client. ([#96](https://github.com/googleapis/google-auth-library-python/pull/96)) +- Fixed the App Engine system tests. + +## 0.4.0 + +- ``transports.grpc.secure_authorized_channel`` now passes ``kwargs`` to ``grpc.secure_channel``. ([#90](https://github.com/googleapis/google-auth-library-python/pull/90)) +- Added new property ``credentials.Singing.signer_email`` which can be used to identify the signer of a message. ([#89](https://github.com/googleapis/google-auth-library-python/pull/89)) +- (google_auth_httplib2) Added a proxy to ``httplib2.Http.connections``. + +## 0.3.2 + +- Fixed an issue where an ``ImportError`` would occur if ``google.oauth2`` was imported before ``google.auth``. ([#88](https://github.com/googleapis/google-auth-library-python/pull/88)) + +## 0.3.1 + +- Fixed a bug where non-padded base64 encoded strings were not accepted. ([#87](https://github.com/googleapis/google-auth-library-python/pull/87)) +- Fixed a bug where ID token verification did not correctly call the HTTP request function. ([#87](https://github.com/googleapis/google-auth-library-python/pull/87)) + +## 0.3.0 + +- Added Google ID token verification helpers. ([#82](https://github.com/googleapis/google-auth-library-python/pull/82)) +- Swapped the ``target`` and ``request`` argument order for ``grpc.secure_authorized_channel``. ([#81](https://github.com/googleapis/google-auth-library-python/pull/81)) +- Added a user's guide. ([#79](https://github.com/googleapis/google-auth-library-python/pull/79)) +- Made ``service_account_email`` a public property on several credential classes. ([#76](https://github.com/googleapis/google-auth-library-python/pull/76)) +- Added a ``scope`` argument to ``google.auth.default``. ([#75](https://github.com/googleapis/google-auth-library-python/pull/75)) +- Added support for the ``GCLOUD_PROJECT`` environment variable. ([#73](https://github.com/googleapis/google-auth-library-python/pull/73)) + +## 0.2.0 + +- Added gRPC support. ([#67](https://github.com/googleapis/google-auth-library-python/pull/67)) +- Added Requests support. ([#66](https://github.com/googleapis/google-auth-library-python/pull/66)) +- Added ``google.auth.credentials.with_scopes_if_required`` helper. ([#65](https://github.com/googleapis/google-auth-library-python/pull/65)) +- Added private helper for oauth2client migration. ([#70](https://github.com/googleapis/google-auth-library-python/pull/70)) + +## 0.1.0 + +First release with core functionality available. This version is ready for +initial usage and testing. + +- Added ``google.auth.credentials``, public interfaces for Credential types. ([#8](https://github.com/googleapis/google-auth-library-python/pull/8)) +- Added ``google.oauth2.credentials``, credentials that use OAuth 2.0 access and refresh tokens ([#24](https://github.com/googleapis/google-auth-library-python/pull/24)) +- Added ``google.oauth2.service_account``, credentials that use Service Account private keys to obtain OAuth 2.0 access tokens. ([#25](https://github.com/googleapis/google-auth-library-python/pull/25)) +- Added ``google.auth.compute_engine``, credentials that use the Compute Engine metadata service to obtain OAuth 2.0 access tokens. ([#22](https://github.com/googleapis/google-auth-library-python/pull/22)) +- Added ``google.auth.jwt.Credentials``, credentials that use a JWT as a bearer token. +- Added ``google.auth.app_engine``, credentials that use the Google App Engine App Identity service to obtain OAuth 2.0 access tokens. ([#46](https://github.com/googleapis/google-auth-library-python/pull/46)) +- Added ``google.auth.default()``, an implementation of Google Application Default Credentials that supports automatic Project ID detection. ([#32](https://github.com/googleapis/google-auth-library-python/pull/32)) +- Added system tests for all credential types. ([#51](https://github.com/googleapis/google-auth-library-python/pull/51), [#54](https://github.com/googleapis/google-auth-library-python/pull/54), [#56](https://github.com/googleapis/google-auth-library-python/pull/56), [#58](https://github.com/googleapis/google-auth-library-python/pull/58), [#59](https://github.com/googleapis/google-auth-library-python/pull/59), [#60](https://github.com/googleapis/google-auth-library-python/pull/60), [#61](https://github.com/googleapis/google-auth-library-python/pull/61), [#62](https://github.com/googleapis/google-auth-library-python/pull/62)) +- Added ``google.auth.transports.urllib3.AuthorizedHttp``, an HTTP client that includes authentication provided by credentials. ([#19](https://github.com/googleapis/google-auth-library-python/pull/19)) +- Documentation style and formatting updates. + +## 0.0.1 + +Initial release with foundational functionality for cryptography and JWTs. + +- ``google.auth.crypt`` for creating and verifying cryptographic signatures. +- ``google.auth.jwt`` for creating (encoding) and verifying (decoding) JSON Web tokens. diff --git a/packages/google-auth/CHANGELOG.rst b/packages/google-auth/CHANGELOG.rst deleted file mode 100644 index 9a33bfcd09c0..000000000000 --- a/packages/google-auth/CHANGELOG.rst +++ /dev/null @@ -1,265 +0,0 @@ -Changelog -========= - -v1.6.3 ------- - -02-15-2019 9:31 PST - -Implementation Changes -+++++++++++++ - -- follow rfc 7515 : strip padding from JWS segments #324 (`#324 `_) -- Add retry to _metadata.ping() (`#323 `_) - -v1.6.2 ------- - -12-17-2018 10:51 PST - -Documentation -+++++++++++++ - -- Announce deprecation of Python 2.7 (`#311 `_) -- Link all the PRs in CHANGELOG (`#307 `_) - -v1.6.1 ------- - -11-12-2018 10:10 PST - -Implementation Changes -++++++++++++++++++++++ - -- Automatically refresh impersonated credentials (`#304 `_) - -v1.6.0 ------- - -11-09-2018 11:07 PST - -New Features -++++++++++++ - -- Add google.auth.impersonated_credentials (`#299 `_) - -Documentation -+++++++++++++ - -- Update link to documentation for default credentials (`#296 `_) -- Update github issue templates (`#300 `_) -- Remove punctuation which becomes part of the url (`#284 `_) - -Internal / Testing Changes -++++++++++++++++++++++++++ - -- Update trampoline.sh (`#302 `_) -- Enable static type checking with pytype (`#298 `_) -- Make classifiers in setup.py an array. (`#280 `_) - - -v1.5.1 ------- - -- Fix check for error text on Python 3.7. (`#278 `_) -- Use new Auth URIs. (`#281 `_) -- Add code-of-conduct document. (`#270 `_) -- Fix some typos in test_urllib3.py (`#268 `_) - -v1.5.0 ------- - -- Warn when using user credentials from the Cloud SDK (`#266 `_) -- Add compute engine-based IDTokenCredentials (`#236 `_) -- Corrected some typos (`#265 `_) - -v1.4.2 ------- - -- Raise a helpful exception when trying to refresh credentials without a refresh token. (`#262 `_) -- Fix links to README and CONTRIBUTING in docs/index.rst. (`#260 `_) -- Fix a typo in credentials.py. (`#256 `_) -- Use pytest instead of py.test per upstream recommendation, #dropthedot. (`#255 `_) -- Fix typo on exemple of jwt usage (`#245 `_) - -v1.4.1 ------- - -- Added a check for the cryptography version before attempting to use it. (`#243 `_) - -v1.4.0 ------- - -- Added `cryptography`-based RSA signer and verifier. (`#185 `_) -- Added `google.oauth2.service_account.IDTokenCredentials`. (`#234 `_) -- Improved documentation around ID Tokens (`#224 `_) - -v1.3.0 ------- - -- Added ``google.oauth2.credentials.Credentials.from_authorized_user_file`` (`#226 `_) -- Dropped direct pyasn1 dependency in favor of letting ``pyasn1-modules`` specify the right version. (`#230 `_) -- ``default()`` now checks for the project ID environment var before warning about missing project ID. (`#227 `_) -- Fixed the docstrings for ``has_scopes()`` and ``with_scopes()``. (`#228 `_) -- Fixed example in docstring for ``ReadOnlyScoped``. (`#219 `_) -- Made ``transport.requests`` use timeouts and retries to improve reliability. (`#220 `_) - -v1.2.1 ------- - -- Excluded compiled Python files in source distributions. (`#215 `_) -- Updated docs for creating RSASigner from string. (`#213 `_) -- Use ``six.raise_from`` wherever possible. (`#212 `_) -- Fixed a typo in a comment ``seconds`` not ``sections``. (`#210 `_) - -v1.2.0 ------- - -- Added ``google.auth.credentials.AnonymousCredentials``. (`#206 `_) -- Updated the documentation to link to the Google Cloud Platform Python setup guide (`#204 `_) - -v1.1.1 ------- - -- ``google.oauth.credentials.Credentials`` now correctly inherits from ``ReadOnlyScoped`` instead of ``Scoped``. (`#200 `_) - -v1.1.0 ------- - -- Added ``service_account.Credentials.project_id``. (`#187 `_) -- Move read-only methods of ``credentials.Scoped`` into new interface ``credentials.ReadOnlyScoped``. (`#195 `_, `#196 `_) -- Make ``compute_engine.Credentials`` derive from ``ReadOnlyScoped`` instead of ``Scoped``. (`#195 `_) -- Fix App Engine's expiration calculation (`#197 `_) -- Split ``crypt`` module into a package to allow alternative implementations. (`#189 `_) -- Add error message to handle case of empty string or missing file for GOOGLE_APPLICATION_CREDENTIALS (`#188 `_) - -v1.0.2 ------- - -- Fixed a bug where the Cloud SDK executable could not be found on Windows, leading to project ID detection failing. (`#179 `_) -- Fixed a bug where the timeout argument wasn't being passed through the httplib transport correctly. (`#175 `_) -- Added documentation for using the library on Google App Engine standard. (`#172 `_) -- Testing style updates. (`#168 `_) -- Added documentation around the oauth2client deprecation. (`#165 `_) -- Fixed a few lint issues caught by newer versions of pylint. (`#166 `_) - -v1.0.1 ------- - -- Fixed a bug in the clock skew accommodation logic where expired credentials could be used for up to 5 minutes. (`#158 `_) - -v1.0.0 ------- - -Milestone release for v1.0.0. -No significant changes since v0.10.0 - -v0.10.0 -------- - -- Added ``jwt.OnDemandCredentials``. (`#142 `_) -- Added new public property ``id_token`` to ``oauth2.credentials.Credentials``. (`#150 `_) -- Added the ability to set the address used to communicate with the Compute Engine metadata server via the ``GCE_METADATA_ROOT`` and ``GCE_METADATA_IP`` environment variables. (`#148 `_) -- Changed the way cloud project IDs are ascertained from the Google Cloud SDK. (`#147 `_) -- Modified expiration logic to add a 5 minute clock skew accommodation. (`#145 `_) - -v0.9.0 ------- - -- Added ``service_account.Credentials.with_claims``. (`#140 `_) -- Moved ``google.auth.oauthlib`` and ``google.auth.flow`` to a new separate package ``google_auth_oauthlib``. (`#137 `_, `#139 `_, `#135 `_, `#126 `_) -- Added ``InstalledAppFlow`` to ``google_auth_oauthlib``. (`#128 `_) -- Fixed some packaging and documentation issues. (`#131 `_) -- Added a helpful error message when importing optional dependencies. (`#125 `_) -- Made all properties required to reconstruct ``google.oauth2.credentials.Credentials`` public. (`#124 `_) -- Added official Python 3.6 support. (`#102 `_) -- Added ``jwt.Credentials.from_signing_credentials`` and removed ``service_account.Credentials.to_jwt_credentials``. (`#120 `_) - -v0.8.0 ------- - -- Removed one-time token behavior from ``jwt.Credentials``, audience claim is now required and fixed. (`#117 `_) -- ``crypt.Signer`` and ``crypt.Verifier`` are now abstract base classes. The concrete implementations have been renamed to ``crypt.RSASigner`` and ``crypt.RSAVerifier``. ``app_engine.Signer`` and ``iam.Signer`` now inherit from ``crypt.Signer``. (`#115 `_) -- ``transport.grpc`` now correctly calls ``Credentials.before_request``. (`#116 `_) - -v0.7.0 ------- - -- Added ``google.auth.iam.Signer``. (`#108 `_) -- Fixed issue where ``google.auth.app_engine.Signer`` erroneously returns a tuple from ``sign()``. (`#109 `_) -- Added public property ``google.auth.credentials.Signing.signer``. (`#110 `_) - -v0.6.0 ------- - -- Added experimental integration with ``requests-oauthlib`` in ``google.oauth2.oauthlib`` and ``google.oauth2.flow``. (`#100 `_, `#105 `_, `#106 `_) -- Fixed typo in ``google_auth_httplib2``'s README. (`#105 `_) - -v0.5.0 ------- - -- Added ``app_engine.Signer``. (`#97 `_) -- Added ``crypt.Signer.from_service_account_file``. (`#95 `_) -- Fixed error handling in the oauth2 client. (`#96 `_) -- Fixed the App Engine system tests. - -v0.4.0 ------- - -- ``transports.grpc.secure_authorized_channel`` now passes ``kwargs`` to ``grpc.secure_channel``. (`#90 `_) -- Added new property ``credentials.Singing.signer_email`` which can be used to identify the signer of a message. (`#89 `_) -- (google_auth_httplib2) Added a proxy to ``httplib2.Http.connections``. - -v0.3.2 ------- - -- Fixed an issue where an ``ImportError`` would occur if ``google.oauth2`` was imported before ``google.auth``. (`#88 `_) - -v0.3.1 ------- - -- Fixed a bug where non-padded base64 encoded strings were not accepted. (`#87 `_) -- Fixed a bug where ID token verification did not correctly call the HTTP request function. (`#87 `_) - -v0.3.0 ------- - -- Added Google ID token verification helpers. (`#82 `_) -- Swapped the ``target`` and ``request`` argument order for ``grpc.secure_authorized_channel``. (`#81 `_) -- Added a user's guide. (`#79 `_) -- Made ``service_account_email`` a public property on several credential classes. (`#76 `_) -- Added a ``scope`` argument to ``google.auth.default``. (`#75 `_) -- Added support for the ``GCLOUD_PROJECT`` environment variable. (`#73 `_) - -v0.2.0 ------- - -- Added gRPC support. (`#67 `_) -- Added Requests support. (`#66 `_) -- Added ``google.auth.credentials.with_scopes_if_required`` helper. (`#65 `_) -- Added private helper for oauth2client migration. (`#70 `_) - -v0.1.0 ------- - -First release with core functionality available. This version is ready for -initial usage and testing. - -- Added ``google.auth.credentials``, public interfaces for Credential types. (`#8 `_) -- Added ``google.oauth2.credentials``, credentials that use OAuth 2.0 access and refresh tokens (`#24 `_) -- Added ``google.oauth2.service_account``, credentials that use Service Account private keys to obtain OAuth 2.0 access tokens. (`#25 `_) -- Added ``google.auth.compute_engine``, credentials that use the Compute Engine metadata service to obtain OAuth 2.0 access tokens. (`#22 `_) -- Added ``google.auth.jwt.Credentials``, credentials that use a JWT as a bearer token. -- Added ``google.auth.app_engine``, credentials that use the Google App Engine App Identity service to obtain OAuth 2.0 access tokens. (`#46 `_) -- Added ``google.auth.default()``, an implementation of Google Application Default Credentials that supports automatic Project ID detection. (`#32 `_) -- Added system tests for all credential types. (`#51 `_, `#54 `_, `#56 `_, `#58 `_, `#59 `_, `#60 `_, `#61 `_, `#62 `_) -- Added ``google.auth.transports.urllib3.AuthorizedHttp``, an HTTP client that includes authentication provided by credentials. (`#19 `_) -- Documentation style and formatting updates. - -v0.0.1 ------- - -Initial release with foundational functionality for cryptography and JWTs. - -- ``google.auth.crypt`` for creating and verifying cryptographic signatures. -- ``google.auth.jwt`` for creating (encoding) and verifying (decoding) JSON Web tokens. diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index d303cb306d07..42cf4fb405ee 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -30,9 +30,11 @@ with io.open("README.rst", "r") as fh: long_description = fh.read() +version = "1.7.0" + setup( name="google-auth", - version="1.6.3", + version=version, author="Google Cloud Platform", author_email="jonwayne+google-auth@google.com", description="Google Authentication Library", From 060fa06ea07759b780c921dd800035682ab9821c Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Mon, 11 Nov 2019 11:04:38 -0800 Subject: [PATCH 231/966] docs: fix note rendering for service_account_email (#385) --- packages/google-auth/google/auth/compute_engine/credentials.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/compute_engine/credentials.py b/packages/google-auth/google/auth/compute_engine/credentials.py index eeb92f5159a2..0abfc053002f 100644 --- a/packages/google-auth/google/auth/compute_engine/credentials.py +++ b/packages/google-auth/google/auth/compute_engine/credentials.py @@ -105,7 +105,7 @@ def refresh(self, request): def service_account_email(self): """The service account email. - .. note: This is not guaranteed to be set until :meth`refresh` has been + .. note:: This is not guaranteed to be set until :meth:`refresh` has been called. """ return self._service_account_email From 74e014a876154352fa7baff7a62423bb1094856e Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Mon, 11 Nov 2019 11:26:00 -0800 Subject: [PATCH 232/966] chore: remove unused badges, update author_email (#381) --- packages/google-auth/README.rst | 2 +- packages/google-auth/setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/README.rst b/packages/google-auth/README.rst index c7dfcea6b567..afc47880f92c 100644 --- a/packages/google-auth/README.rst +++ b/packages/google-auth/README.rst @@ -1,7 +1,7 @@ Google Auth Python Library ========================== -|build| |docs| |pypi| |compat_check_pypi| |compat_check_github| +|docs| |pypi| This library simplifies using Google's various server-to-server authentication mechanisms to access Google APIs. diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 42cf4fb405ee..28b302f0b0e5 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -36,7 +36,7 @@ name="google-auth", version=version, author="Google Cloud Platform", - author_email="jonwayne+google-auth@google.com", + author_email="googleapis-packages@google.com", description="Google Authentication Library", long_description=long_description, url="https://github.com/GoogleCloudPlatform/google-auth-library-python", From 59862c57945e9ee932b6b241921f8a6a136be85d Mon Sep 17 00:00:00 2001 From: Georgy Savva Date: Wed, 13 Nov 2019 22:21:57 +0300 Subject: [PATCH 233/966] fix: change 'internal_failure' condition to also use `error' field (#387) * fix: change `internal_failure` condition to also use `error' field In some cases response doesn't contain `error_description` field. So the retry won't work. The solution is to look at both `error` and `error_description` fields. --- packages/google-auth/google/oauth2/_client.py | 11 +++++++---- .../google-auth/tests/oauth2/test__client.py | 16 +++++++++++----- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/packages/google-auth/google/oauth2/_client.py b/packages/google-auth/google/oauth2/_client.py index 996f9b7e2961..f92b0970ad69 100644 --- a/packages/google-auth/google/oauth2/_client.py +++ b/packages/google-auth/google/oauth2/_client.py @@ -104,18 +104,21 @@ def _token_endpoint_request(request, token_uri, body): while True: response = request(method="POST", url=token_uri, headers=headers, body=body) response_body = response.data.decode("utf-8") + response_data = json.loads(response_body) if response.status == http_client.OK: break else: - error_desc = json.loads(response_body).get("error_description") or "" - if error_desc == "internal_failure" and retry < 1: + error_desc = response_data.get("error_description") or "" + error_code = response_data.get("error") or "" + if ( + any(e == "internal_failure" for e in (error_code, error_desc)) + and retry < 1 + ): retry += 1 continue _handle_error_response(response_body) - response_data = json.loads(response_body) - return response_data diff --git a/packages/google-auth/tests/oauth2/test__client.py b/packages/google-auth/tests/oauth2/test__client.py index c415a1f44a38..9cf59eb981be 100644 --- a/packages/google-auth/tests/oauth2/test__client.py +++ b/packages/google-auth/tests/oauth2/test__client.py @@ -112,15 +112,21 @@ def test__token_endpoint_request_error(): def test__token_endpoint_request_internal_failure_error(): request = make_request( - {"error": "internal_failure", "error_description": "internal_failure"}, - status=http_client.BAD_REQUEST, + {"error_description": "internal_failure"}, status=http_client.BAD_REQUEST ) with pytest.raises(exceptions.RefreshError): _client._token_endpoint_request( - request, - "http://example.com", - {"error": "internal_failure", "error_description": "internal_failure"}, + request, "http://example.com", {"error_description": "internal_failure"} + ) + + request = make_request( + {"error": "internal_failure"}, status=http_client.BAD_REQUEST + ) + + with pytest.raises(exceptions.RefreshError): + _client._token_endpoint_request( + request, "http://example.com", {"error": "internal_failure"} ) From 78b4655a127b005522404c383de0db472c430ea0 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Wed, 13 Nov 2019 11:24:53 -0800 Subject: [PATCH 234/966] build: enable release-please (#378) --- packages/google-auth/.github/release-please.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 packages/google-auth/.github/release-please.yml diff --git a/packages/google-auth/.github/release-please.yml b/packages/google-auth/.github/release-please.yml new file mode 100644 index 000000000000..4507ad0598a5 --- /dev/null +++ b/packages/google-auth/.github/release-please.yml @@ -0,0 +1 @@ +releaseType: python From 7ea1fdd5397f047dbfb9e3c4d5254847663d4b4c Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Wed, 13 Nov 2019 12:03:38 -0800 Subject: [PATCH 235/966] chore: release 1.7.1 (#388) --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index b74017f4e9a1..fab6fe355214 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +### [1.7.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.7.0...v1.7.1) (2019-11-13) + + +### Bug Fixes + +* change 'internal_failure' condition to also use `error' field ([#387](https://www.github.com/googleapis/google-auth-library-python/issues/387)) ([46bb58e](https://www.github.com/googleapis/google-auth-library-python/commit/46bb58e694716908a5ed00f05dbb794cdec667dd)) + ## 1.7.0 10-30-2019 17:11 PDT diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 28b302f0b0e5..321788175444 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -30,7 +30,7 @@ with io.open("README.rst", "r") as fh: long_description = fh.read() -version = "1.7.0" +version = "1.7.1" setup( name="google-auth", From c245182283afb5fc8e9e29de099ff7005b2f6af4 Mon Sep 17 00:00:00 2001 From: chenyumic Date: Mon, 25 Nov 2019 14:21:43 -0800 Subject: [PATCH 236/966] fix: make gRPC auth plugin non-blocking + add default timeout value for requests transport (#390) This commit includes the following changes: - `transport.grpc.AuthMetadataPlugin` is now non-blocking as gRPC requires - `transport.requests.Request` now has a default timeout value of 120 seconds so that token refreshing will not be stuck Resolves: #351 --- .../google-auth/google/auth/transport/grpc.py | 16 +++++++++++++++- .../google/auth/transport/requests.py | 2 +- .../google-auth/tests/transport/test_grpc.py | 5 +++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/google/auth/transport/grpc.py b/packages/google-auth/google/auth/transport/grpc.py index 9a1bc6d18bd8..80f6e81ba8cf 100644 --- a/packages/google-auth/google/auth/transport/grpc.py +++ b/packages/google-auth/google/auth/transport/grpc.py @@ -16,6 +16,8 @@ from __future__ import absolute_import +from concurrent import futures + import six try: @@ -51,6 +53,7 @@ def __init__(self, credentials, request): super(AuthMetadataPlugin, self).__init__() self._credentials = credentials self._request = request + self._pool = futures.ThreadPoolExecutor(max_workers=1) def _get_authorization_headers(self, context): """Gets the authorization headers for a request. @@ -66,6 +69,13 @@ def _get_authorization_headers(self, context): return list(six.iteritems(headers)) + @staticmethod + def _callback_wrapper(callback): + def wrapped(future): + callback(future.result(), None) + + return wrapped + def __call__(self, context, callback): """Passes authorization metadata into the given callback. @@ -74,7 +84,11 @@ def __call__(self, context, callback): callback (grpc.AuthMetadataPluginCallback): The callback that will be invoked to pass in the authorization metadata. """ - callback(self._get_authorization_headers(context), None) + future = self._pool.submit(self._get_authorization_headers, context) + future.add_done_callback(self._callback_wrapper(callback)) + + def __del__(self): + self._pool.shutdown(wait=False) def secure_authorized_channel( diff --git a/packages/google-auth/google/auth/transport/requests.py b/packages/google-auth/google/auth/transport/requests.py index 564a0cd04ae1..d1971cd88118 100644 --- a/packages/google-auth/google/auth/transport/requests.py +++ b/packages/google-auth/google/auth/transport/requests.py @@ -95,7 +95,7 @@ def __init__(self, session=None): self.session = session def __call__( - self, url, method="GET", body=None, headers=None, timeout=None, **kwargs + self, url, method="GET", body=None, headers=None, timeout=120, **kwargs ): """Make an HTTP request using requests. diff --git a/packages/google-auth/tests/transport/test_grpc.py b/packages/google-auth/tests/transport/test_grpc.py index 810d038aa491..ca12385dd7ba 100644 --- a/packages/google-auth/tests/transport/test_grpc.py +++ b/packages/google-auth/tests/transport/test_grpc.py @@ -13,6 +13,7 @@ # limitations under the License. import datetime +import time import mock import pytest @@ -58,6 +59,8 @@ def test_call_no_refresh(self): plugin(context, callback) + time.sleep(2) + callback.assert_called_once_with( [(u"authorization", u"Bearer {}".format(credentials.token))], None ) @@ -76,6 +79,8 @@ def test_call_refresh(self): plugin(context, callback) + time.sleep(2) + assert credentials.token == "token1" callback.assert_called_once_with( [(u"authorization", u"Bearer {}".format(credentials.token))], None From 6fb54346c7ff6b698a74c21cf1a8cd845433076f Mon Sep 17 00:00:00 2001 From: Steve Date: Mon, 2 Dec 2019 10:57:30 -0800 Subject: [PATCH 237/966] fix: in token endpoint request, do not decode the response data if it is not encoded (#393) * fix: in token endpoint request, do not decode the response data if it is not encoded The interface of the underlying transport 'google.auth.transport.Request' that makes the token request does not guarantee the response is encoded. In Python 3, the non-encoded strings do not have 'decode' attribute. Blindly decoding all the response could have the token refresh throw here. --- packages/google-auth/google/oauth2/_client.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/google/oauth2/_client.py b/packages/google-auth/google/oauth2/_client.py index f92b0970ad69..4cf7a7fe90f4 100644 --- a/packages/google-auth/google/oauth2/_client.py +++ b/packages/google-auth/google/oauth2/_client.py @@ -103,7 +103,11 @@ def _token_endpoint_request(request, token_uri, body): # occurs. while True: response = request(method="POST", url=token_uri, headers=headers, body=body) - response_body = response.data.decode("utf-8") + response_body = ( + response.data.decode("utf-8") + if hasattr(response.data, "decode") + else response.data + ) response_data = json.loads(response_body) if response.status == http_client.OK: From 6c519c7cd7ed9ab26d6cfdbbc84a78b65f883bfd Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2019 14:07:39 -0800 Subject: [PATCH 238/966] chore: release 1.7.2 (#391) --- packages/google-auth/CHANGELOG.md | 8 ++++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index fab6fe355214..8c7a940455af 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,14 @@ [1]: https://pypi.org/project/google-auth/#history +### [1.7.2](https://www.github.com/googleapis/google-auth-library-python/compare/v1.7.1...v1.7.2) (2019-12-02) + + +### Bug Fixes + +* in token endpoint request, do not decode the response data if it is not encoded ([#393](https://www.github.com/googleapis/google-auth-library-python/issues/393)) ([3b5d3e2](https://www.github.com/googleapis/google-auth-library-python/commit/3b5d3e2192ce0cdc97854a1d70d5e382e454275c)) +* make gRPC auth plugin non-blocking + add default timeout value for requests transport ([#390](https://www.github.com/googleapis/google-auth-library-python/issues/390)) ([0c33e9c](https://www.github.com/googleapis/google-auth-library-python/commit/0c33e9c0fe4f87fa46c8f1a5afe725a467ac5fcc)), closes [#351](https://www.github.com/googleapis/google-auth-library-python/issues/351) + ### [1.7.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.7.0...v1.7.1) (2019-11-13) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 321788175444..0fd04c6bddee 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -30,7 +30,7 @@ with io.open("README.rst", "r") as fh: long_description = fh.read() -version = "1.7.1" +version = "1.7.2" setup( name="google-auth", From d47da7e666a2d5b4b99853af7329f83787b2f0f8 Mon Sep 17 00:00:00 2001 From: patkasper <45458434+patkasper@users.noreply.github.com> Date: Thu, 5 Dec 2019 22:03:44 +0100 Subject: [PATCH 239/966] feat: add `to_json` method to google.oauth2.credentials.Credentials (#367) --- .../google-auth/google/oauth2/credentials.py | 30 +++++++++++++++++++ .../tests/oauth2/test_credentials.py | 24 +++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/packages/google-auth/google/oauth2/credentials.py b/packages/google-auth/google/oauth2/credentials.py index 676a4324eb39..3a32c0631a5a 100644 --- a/packages/google-auth/google/oauth2/credentials.py +++ b/packages/google-auth/google/oauth2/credentials.py @@ -223,3 +223,33 @@ def from_authorized_user_file(cls, filename, scopes=None): with io.open(filename, "r", encoding="utf-8") as json_file: data = json.load(json_file) return cls.from_authorized_user_info(data, scopes) + + def to_json(self, strip=None): + """Utility function that creates a JSON representation of a Credentials + object. + + Args: + strip (Sequence[str]): Optional list of members to exclude from the + generated JSON. + + Returns: + str: A JSON representation of this instance, suitable to pass to + from_json(). + """ + prep = { + "token": self.token, + "refresh_token": self.refresh_token, + "token_uri": self.token_uri, + "client_id": self.client_id, + "client_secret": self.client_secret, + "scopes": self.scopes, + } + + # Remove empty entries + prep = {k: v for k, v in prep.items() if v is not None} + + # Remove entries that explicitely need to be removed + if strip is not None: + prep = {k: v for k, v in prep.items() if k not in strip} + + return json.dumps(prep) diff --git a/packages/google-auth/tests/oauth2/test_credentials.py b/packages/google-auth/tests/oauth2/test_credentials.py index 8bfdd7e0af75..bb70f151664c 100644 --- a/packages/google-auth/tests/oauth2/test_credentials.py +++ b/packages/google-auth/tests/oauth2/test_credentials.py @@ -331,3 +331,27 @@ def test_from_authorized_user_file(self): assert creds.refresh_token == info["refresh_token"] assert creds.token_uri == credentials._GOOGLE_OAUTH2_TOKEN_ENDPOINT assert creds.scopes == scopes + + def test_to_json(self): + info = AUTH_USER_INFO.copy() + creds = credentials.Credentials.from_authorized_user_info(info) + + # Test with no `strip` arg + json_output = creds.to_json() + json_asdict = json.loads(json_output) + assert json_asdict.get("token") == creds.token + assert json_asdict.get("refresh_token") == creds.refresh_token + assert json_asdict.get("token_uri") == creds.token_uri + assert json_asdict.get("client_id") == creds.client_id + assert json_asdict.get("scopes") == creds.scopes + assert json_asdict.get("client_secret") == creds.client_secret + + # Test with a `strip` arg + json_output = creds.to_json(strip=["client_secret"]) + json_asdict = json.loads(json_output) + assert json_asdict.get("token") == creds.token + assert json_asdict.get("refresh_token") == creds.refresh_token + assert json_asdict.get("token_uri") == creds.token_uri + assert json_asdict.get("client_id") == creds.client_id + assert json_asdict.get("scopes") == creds.scopes + assert json_asdict.get("client_secret") is None From 729935dd36ed17ee11434a5aacebfc0f6d9547c4 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Fri, 6 Dec 2019 15:56:24 -0500 Subject: [PATCH 240/966] feat: send quota project id in x-goog-user-project header for OAuth2 credentials (#400) When the 3LO credentials are used, the quota project ("quota_project_id") is sent on every outgoing request in the x-goog-user-project HTTP header/grpc metadata. The quota project is used for billing and quota purposes. --- .../docs/reference/google.auth.crypt.base.rst | 7 +++++ .../docs/reference/google.auth.crypt.rsa.rst | 7 +++++ .../google-auth/google/oauth2/credentials.py | 19 +++++++++++++ .../tests/oauth2/test_credentials.py | 27 +++++++++++++++++++ 4 files changed, 60 insertions(+) create mode 100644 packages/google-auth/docs/reference/google.auth.crypt.base.rst create mode 100644 packages/google-auth/docs/reference/google.auth.crypt.rsa.rst diff --git a/packages/google-auth/docs/reference/google.auth.crypt.base.rst b/packages/google-auth/docs/reference/google.auth.crypt.base.rst new file mode 100644 index 000000000000..a8996501a7a2 --- /dev/null +++ b/packages/google-auth/docs/reference/google.auth.crypt.base.rst @@ -0,0 +1,7 @@ +google.auth.crypt.base module +============================= + +.. automodule:: google.auth.crypt.base + :members: + :inherited-members: + :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.auth.crypt.rsa.rst b/packages/google-auth/docs/reference/google.auth.crypt.rsa.rst new file mode 100644 index 000000000000..7060b03c82e3 --- /dev/null +++ b/packages/google-auth/docs/reference/google.auth.crypt.rsa.rst @@ -0,0 +1,7 @@ +google.auth.crypt.rsa module +============================ + +.. automodule:: google.auth.crypt.rsa + :members: + :inherited-members: + :show-inheritance: diff --git a/packages/google-auth/google/oauth2/credentials.py b/packages/google-auth/google/oauth2/credentials.py index 3a32c0631a5a..422c8ab1054a 100644 --- a/packages/google-auth/google/oauth2/credentials.py +++ b/packages/google-auth/google/oauth2/credentials.py @@ -58,6 +58,7 @@ def __init__( client_id=None, client_secret=None, scopes=None, + quota_project_id=None, ): """ Args: @@ -81,6 +82,9 @@ def __init__( token if refresh information is provided (e.g. The refresh token scopes are a superset of this or contain a wild card scope like 'https://www.googleapis.com/auth/any-api'). + quota_project_id (Optional[str]): The project ID used for quota and billing. + This project may be different from the project used to + create the credentials. """ super(Credentials, self).__init__() self.token = token @@ -90,6 +94,7 @@ def __init__( self._token_uri = token_uri self._client_id = client_id self._client_secret = client_secret + self._quota_project_id = quota_project_id @property def refresh_token(self): @@ -123,6 +128,11 @@ def client_secret(self): """Optional[str]: The OAuth 2.0 client secret.""" return self._client_secret + @property + def quota_project_id(self): + """Optional[str]: The project to use for quota and billing purposes.""" + return self._quota_project_id + @property def requires_scopes(self): """False: OAuth 2.0 credentials have their scopes set when @@ -169,6 +179,12 @@ def refresh(self, request): ) ) + @_helpers.copy_docstring(credentials.Credentials) + def apply(self, headers, token=None): + super(Credentials, self).apply(headers, token=token) + if self.quota_project_id is not None: + headers["x-goog-user-project"] = self.quota_project_id + @classmethod def from_authorized_user_info(cls, info, scopes=None): """Creates a Credentials instance from parsed authorized user info. @@ -202,6 +218,9 @@ def from_authorized_user_info(cls, info, scopes=None): scopes=scopes, client_id=info["client_id"], client_secret=info["client_secret"], + quota_project_id=info.get( + "quota_project_id" + ), # quota project may not exist ) @classmethod diff --git a/packages/google-auth/tests/oauth2/test_credentials.py b/packages/google-auth/tests/oauth2/test_credentials.py index bb70f151664c..59031d7defcd 100644 --- a/packages/google-auth/tests/oauth2/test_credentials.py +++ b/packages/google-auth/tests/oauth2/test_credentials.py @@ -294,6 +294,33 @@ def test_credentials_with_scopes_refresh_failure_raises_refresh_error( # expired.) assert creds.valid + def test_apply_with_quota_project_id(self): + creds = credentials.Credentials( + token="token", + refresh_token=self.REFRESH_TOKEN, + token_uri=self.TOKEN_URI, + client_id=self.CLIENT_ID, + client_secret=self.CLIENT_SECRET, + quota_project_id="quota-project-123", + ) + + headers = {} + creds.apply(headers) + assert headers["x-goog-user-project"] == "quota-project-123" + + def test_apply_with_no_quota_project_id(self): + creds = credentials.Credentials( + token="token", + refresh_token=self.REFRESH_TOKEN, + token_uri=self.TOKEN_URI, + client_id=self.CLIENT_ID, + client_secret=self.CLIENT_SECRET, + ) + + headers = {} + creds.apply(headers) + assert "x-goog-user-project" not in headers + def test_from_authorized_user_info(self): info = AUTH_USER_INFO.copy() From 0de428e9e2a67db648ce827cdb9567213ce4681c Mon Sep 17 00:00:00 2001 From: Peter Lamut Date: Mon, 9 Dec 2019 21:25:37 +0100 Subject: [PATCH 241/966] feat: add timeout to AuthorizedSession.request() (#397) --- .../google/auth/transport/requests.py | 85 +++++++++++-- packages/google-auth/noxfile.py | 1 + .../tests/transport/test_requests.py | 115 +++++++++++++++++- 3 files changed, 189 insertions(+), 12 deletions(-) diff --git a/packages/google-auth/google/auth/transport/requests.py b/packages/google-auth/google/auth/transport/requests.py index d1971cd88118..f21c524daf59 100644 --- a/packages/google-auth/google/auth/transport/requests.py +++ b/packages/google-auth/google/auth/transport/requests.py @@ -18,6 +18,7 @@ import functools import logging +import time try: import requests @@ -64,6 +65,33 @@ def data(self): return self._response.content +class TimeoutGuard(object): + """A context manager raising an error if the suite execution took too long. + """ + + def __init__(self, timeout, timeout_error_type=requests.exceptions.Timeout): + self._timeout = timeout + self.remaining_timeout = timeout + self._timeout_error_type = timeout_error_type + + def __enter__(self): + self._start = time.time() + return self + + def __exit__(self, exc_type, exc_value, traceback): + if exc_value: + return # let the error bubble up automatically + + if self._timeout is None: + return # nothing to do, the timeout was not specified + + elapsed = time.time() - self._start + self.remaining_timeout = self._timeout - elapsed + + if self.remaining_timeout <= 0: + raise self._timeout_error_type() + + class Request(transport.Request): """Requests request adapter. @@ -193,8 +221,12 @@ def __init__( # credentials.refresh). self._auth_request = auth_request - def request(self, method, url, data=None, headers=None, **kwargs): - """Implementation of Requests' request.""" + def request(self, method, url, data=None, headers=None, timeout=None, **kwargs): + """Implementation of Requests' request. + + The ``timeout`` argument is interpreted as the approximate total time + of **all** requests that are made under the hood. + """ # pylint: disable=arguments-differ # Requests has a ton of arguments to request, but only two # (method, url) are required. We pass through all of the other @@ -208,13 +240,28 @@ def request(self, method, url, data=None, headers=None, **kwargs): # and we want to pass the original headers if we recurse. request_headers = headers.copy() if headers is not None else {} - self.credentials.before_request( - self._auth_request, method, url, request_headers + # Do not apply the timeout unconditionally in order to not override the + # _auth_request's default timeout. + auth_request = ( + self._auth_request + if timeout is None + else functools.partial(self._auth_request, timeout=timeout) ) - response = super(AuthorizedSession, self).request( - method, url, data=data, headers=request_headers, **kwargs - ) + with TimeoutGuard(timeout) as guard: + self.credentials.before_request(auth_request, method, url, request_headers) + timeout = guard.remaining_timeout + + with TimeoutGuard(timeout) as guard: + response = super(AuthorizedSession, self).request( + method, + url, + data=data, + headers=request_headers, + timeout=timeout, + **kwargs + ) + timeout = guard.remaining_timeout # If the response indicated that the credentials needed to be # refreshed, then refresh the credentials and re-attempt the @@ -233,17 +280,33 @@ def request(self, method, url, data=None, headers=None, **kwargs): self._max_refresh_attempts, ) - auth_request_with_timeout = functools.partial( - self._auth_request, timeout=self._refresh_timeout + if self._refresh_timeout is not None: + timeout = ( + self._refresh_timeout + if timeout is None + else min(timeout, self._refresh_timeout) + ) + + # Do not apply the timeout unconditionally in order to not override the + # _auth_request's default timeout. + auth_request = ( + self._auth_request + if timeout is None + else functools.partial(self._auth_request, timeout=timeout) ) - self.credentials.refresh(auth_request_with_timeout) - # Recurse. Pass in the original headers, not our modified set. + with TimeoutGuard(timeout) as guard: + self.credentials.refresh(auth_request) + timeout = guard.remaining_timeout + + # Recurse. Pass in the original headers, not our modified set, but + # do pass the adjusted timeout (i.e. the remaining time). return self.request( method, url, data=data, headers=headers, + timeout=timeout, _credential_refresh_attempt=_credential_refresh_attempt + 1, **kwargs ) diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index aaf1bc57da31..e170ee51d5a9 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -16,6 +16,7 @@ TEST_DEPENDENCIES = [ "flask", + "freezegun", "mock", "oauth2client", "pytest", diff --git a/packages/google-auth/tests/transport/test_requests.py b/packages/google-auth/tests/transport/test_requests.py index 0e165ac54b17..252e4a67e1d4 100644 --- a/packages/google-auth/tests/transport/test_requests.py +++ b/packages/google-auth/tests/transport/test_requests.py @@ -12,7 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. +import datetime +import functools + +import freezegun import mock +import pytest import requests import requests.adapters from six.moves import http_client @@ -22,6 +27,12 @@ from tests.transport import compliance +@pytest.fixture +def frozen_time(): + with freezegun.freeze_time("1970-01-01 00:00:00", tick=False) as frozen: + yield frozen + + class TestRequestResponse(compliance.RequestResponseTests): def make_request(self): return google.auth.transport.requests.Request() @@ -34,6 +45,41 @@ def test_timeout(self): assert http.request.call_args[1]["timeout"] == 5 +class TestTimeoutGuard(object): + def make_guard(self, *args, **kwargs): + return google.auth.transport.requests.TimeoutGuard(*args, **kwargs) + + def test_tracks_elapsed_time(self, frozen_time): + with self.make_guard(timeout=10) as guard: + frozen_time.tick(delta=3.8) + assert guard.remaining_timeout == 6.2 + + def test_noop_if_no_timeout(self, frozen_time): + with self.make_guard(timeout=None) as guard: + frozen_time.tick(delta=datetime.timedelta(days=3650)) + # NOTE: no timeout error raised, despite years have passed + assert guard.remaining_timeout is None + + def test_error_on_timeout(self, frozen_time): + with pytest.raises(requests.exceptions.Timeout): + with self.make_guard(timeout=10) as guard: + frozen_time.tick(delta=10.001) + assert guard.remaining_timeout == pytest.approx(-0.001) + + def test_custom_timeout_error_type(self, frozen_time): + class FooError(Exception): + pass + + with pytest.raises(FooError): + with self.make_guard(timeout=1, timeout_error_type=FooError): + frozen_time.tick(2) + + def test_lets_errors_bubble_up(self, frozen_time): + with pytest.raises(IndexError): + with self.make_guard(timeout=1): + [1, 2, 3][3] + + class CredentialsStub(google.auth.credentials.Credentials): def __init__(self, token="token"): super(CredentialsStub, self).__init__() @@ -49,6 +95,18 @@ def refresh(self, request): self.token += "1" +class TimeTickCredentialsStub(CredentialsStub): + """Credentials that spend some (mocked) time when refreshing a token.""" + + def __init__(self, time_tick, token="token"): + self._time_tick = time_tick + super(TimeTickCredentialsStub, self).__init__(token=token) + + def refresh(self, request): + self._time_tick() + super(TimeTickCredentialsStub, self).refresh(requests) + + class AdapterStub(requests.adapters.BaseAdapter): def __init__(self, responses, headers=None): super(AdapterStub, self).__init__() @@ -69,6 +127,18 @@ def close(self): # pragma: NO COVER return +class TimeTickAdapterStub(AdapterStub): + """Adapter that spends some (mocked) time when making a request.""" + + def __init__(self, time_tick, responses, headers=None): + self._time_tick = time_tick + super(TimeTickAdapterStub, self).__init__(responses, headers=headers) + + def send(self, request, **kwargs): + self._time_tick() + return super(TimeTickAdapterStub, self).send(request, **kwargs) + + def make_response(status=http_client.OK, data=None): response = requests.Response() response.status_code = status @@ -121,7 +191,9 @@ def test_request_refresh(self): [make_response(status=http_client.UNAUTHORIZED), final_response] ) - authed_session = google.auth.transport.requests.AuthorizedSession(credentials) + authed_session = google.auth.transport.requests.AuthorizedSession( + credentials, refresh_timeout=60 + ) authed_session.mount(self.TEST_URL, adapter) result = authed_session.request("GET", self.TEST_URL) @@ -136,3 +208,44 @@ def test_request_refresh(self): assert adapter.requests[1].url == self.TEST_URL assert adapter.requests[1].headers["authorization"] == "token1" + + def test_request_timout(self, frozen_time): + tick_one_second = functools.partial(frozen_time.tick, delta=1.0) + + credentials = mock.Mock( + wraps=TimeTickCredentialsStub(time_tick=tick_one_second) + ) + adapter = TimeTickAdapterStub( + time_tick=tick_one_second, + responses=[ + make_response(status=http_client.UNAUTHORIZED), + make_response(status=http_client.OK), + ], + ) + + authed_session = google.auth.transport.requests.AuthorizedSession(credentials) + authed_session.mount(self.TEST_URL, adapter) + + # Because at least two requests have to be made, and each takes one + # second, the total timeout specified will be exceeded. + with pytest.raises(requests.exceptions.Timeout): + authed_session.request("GET", self.TEST_URL, timeout=1.9) + + def test_request_timeout_w_refresh_timeout(self, frozen_time): + credentials = mock.Mock(wraps=CredentialsStub()) + adapter = TimeTickAdapterStub( + time_tick=functools.partial(frozen_time.tick, delta=1.0), # one second + responses=[ + make_response(status=http_client.UNAUTHORIZED), + make_response(status=http_client.OK), + ], + ) + + authed_session = google.auth.transport.requests.AuthorizedSession( + credentials, refresh_timeout=0.9 + ) + authed_session.mount(self.TEST_URL, adapter) + + # The timeout is long, but the short refresh timeout will prevail. + with pytest.raises(requests.exceptions.Timeout): + authed_session.request("GET", self.TEST_URL, timeout=60) From 731ad66a8c6d38b1b3770314b892aa683ca5ce9c Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2019 14:01:00 -0800 Subject: [PATCH 242/966] chore: release 1.8.0 (#399) --- packages/google-auth/CHANGELOG.md | 9 +++++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 8c7a940455af..481296b7132b 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,15 @@ [1]: https://pypi.org/project/google-auth/#history +## [1.8.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.7.2...v1.8.0) (2019-12-09) + + +### Features + +* add `to_json` method to google.oauth2.credentials.Credentials ([#367](https://www.github.com/googleapis/google-auth-library-python/issues/367)) ([bfb1f8c](https://www.github.com/googleapis/google-auth-library-python/commit/bfb1f8cc8a706ce5ca2a14886c920ca2220ec349)) +* add timeout to AuthorizedSession.request() ([#397](https://www.github.com/googleapis/google-auth-library-python/issues/397)) ([381dd40](https://www.github.com/googleapis/google-auth-library-python/commit/381dd400911d29926ffbf04e0f2ba53ef7bb997e)) +* send quota project id in x-goog-user-project header for OAuth2 credentials ([#400](https://www.github.com/googleapis/google-auth-library-python/issues/400)) ([ab3dc1e](https://www.github.com/googleapis/google-auth-library-python/commit/ab3dc1e26f5240ea3456de364c7c5cb8f40f9583)) + ### [1.7.2](https://www.github.com/googleapis/google-auth-library-python/compare/v1.7.1...v1.7.2) (2019-12-02) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 0fd04c6bddee..bad946fff51a 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -30,7 +30,7 @@ with io.open("README.rst", "r") as fh: long_description = fh.read() -version = "1.7.2" +version = "1.8.0" setup( name="google-auth", From 5fdb15d312edcbce74abde4b5018b297efa68fd2 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Mon, 9 Dec 2019 18:12:47 -0500 Subject: [PATCH 243/966] fix: revert "feat: add timeout to AuthorizedSession.request() (#397)" (#401) This reverts commit 381dd400911d29926ffbf04e0f2ba53ef7bb997e. --- .../google/auth/transport/requests.py | 85 ++----------- packages/google-auth/noxfile.py | 1 - .../tests/transport/test_requests.py | 115 +----------------- 3 files changed, 12 insertions(+), 189 deletions(-) diff --git a/packages/google-auth/google/auth/transport/requests.py b/packages/google-auth/google/auth/transport/requests.py index f21c524daf59..d1971cd88118 100644 --- a/packages/google-auth/google/auth/transport/requests.py +++ b/packages/google-auth/google/auth/transport/requests.py @@ -18,7 +18,6 @@ import functools import logging -import time try: import requests @@ -65,33 +64,6 @@ def data(self): return self._response.content -class TimeoutGuard(object): - """A context manager raising an error if the suite execution took too long. - """ - - def __init__(self, timeout, timeout_error_type=requests.exceptions.Timeout): - self._timeout = timeout - self.remaining_timeout = timeout - self._timeout_error_type = timeout_error_type - - def __enter__(self): - self._start = time.time() - return self - - def __exit__(self, exc_type, exc_value, traceback): - if exc_value: - return # let the error bubble up automatically - - if self._timeout is None: - return # nothing to do, the timeout was not specified - - elapsed = time.time() - self._start - self.remaining_timeout = self._timeout - elapsed - - if self.remaining_timeout <= 0: - raise self._timeout_error_type() - - class Request(transport.Request): """Requests request adapter. @@ -221,12 +193,8 @@ def __init__( # credentials.refresh). self._auth_request = auth_request - def request(self, method, url, data=None, headers=None, timeout=None, **kwargs): - """Implementation of Requests' request. - - The ``timeout`` argument is interpreted as the approximate total time - of **all** requests that are made under the hood. - """ + def request(self, method, url, data=None, headers=None, **kwargs): + """Implementation of Requests' request.""" # pylint: disable=arguments-differ # Requests has a ton of arguments to request, but only two # (method, url) are required. We pass through all of the other @@ -240,28 +208,13 @@ def request(self, method, url, data=None, headers=None, timeout=None, **kwargs): # and we want to pass the original headers if we recurse. request_headers = headers.copy() if headers is not None else {} - # Do not apply the timeout unconditionally in order to not override the - # _auth_request's default timeout. - auth_request = ( - self._auth_request - if timeout is None - else functools.partial(self._auth_request, timeout=timeout) + self.credentials.before_request( + self._auth_request, method, url, request_headers ) - with TimeoutGuard(timeout) as guard: - self.credentials.before_request(auth_request, method, url, request_headers) - timeout = guard.remaining_timeout - - with TimeoutGuard(timeout) as guard: - response = super(AuthorizedSession, self).request( - method, - url, - data=data, - headers=request_headers, - timeout=timeout, - **kwargs - ) - timeout = guard.remaining_timeout + response = super(AuthorizedSession, self).request( + method, url, data=data, headers=request_headers, **kwargs + ) # If the response indicated that the credentials needed to be # refreshed, then refresh the credentials and re-attempt the @@ -280,33 +233,17 @@ def request(self, method, url, data=None, headers=None, timeout=None, **kwargs): self._max_refresh_attempts, ) - if self._refresh_timeout is not None: - timeout = ( - self._refresh_timeout - if timeout is None - else min(timeout, self._refresh_timeout) - ) - - # Do not apply the timeout unconditionally in order to not override the - # _auth_request's default timeout. - auth_request = ( - self._auth_request - if timeout is None - else functools.partial(self._auth_request, timeout=timeout) + auth_request_with_timeout = functools.partial( + self._auth_request, timeout=self._refresh_timeout ) + self.credentials.refresh(auth_request_with_timeout) - with TimeoutGuard(timeout) as guard: - self.credentials.refresh(auth_request) - timeout = guard.remaining_timeout - - # Recurse. Pass in the original headers, not our modified set, but - # do pass the adjusted timeout (i.e. the remaining time). + # Recurse. Pass in the original headers, not our modified set. return self.request( method, url, data=data, headers=headers, - timeout=timeout, _credential_refresh_attempt=_credential_refresh_attempt + 1, **kwargs ) diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index e170ee51d5a9..aaf1bc57da31 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -16,7 +16,6 @@ TEST_DEPENDENCIES = [ "flask", - "freezegun", "mock", "oauth2client", "pytest", diff --git a/packages/google-auth/tests/transport/test_requests.py b/packages/google-auth/tests/transport/test_requests.py index 252e4a67e1d4..0e165ac54b17 100644 --- a/packages/google-auth/tests/transport/test_requests.py +++ b/packages/google-auth/tests/transport/test_requests.py @@ -12,12 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import datetime -import functools - -import freezegun import mock -import pytest import requests import requests.adapters from six.moves import http_client @@ -27,12 +22,6 @@ from tests.transport import compliance -@pytest.fixture -def frozen_time(): - with freezegun.freeze_time("1970-01-01 00:00:00", tick=False) as frozen: - yield frozen - - class TestRequestResponse(compliance.RequestResponseTests): def make_request(self): return google.auth.transport.requests.Request() @@ -45,41 +34,6 @@ def test_timeout(self): assert http.request.call_args[1]["timeout"] == 5 -class TestTimeoutGuard(object): - def make_guard(self, *args, **kwargs): - return google.auth.transport.requests.TimeoutGuard(*args, **kwargs) - - def test_tracks_elapsed_time(self, frozen_time): - with self.make_guard(timeout=10) as guard: - frozen_time.tick(delta=3.8) - assert guard.remaining_timeout == 6.2 - - def test_noop_if_no_timeout(self, frozen_time): - with self.make_guard(timeout=None) as guard: - frozen_time.tick(delta=datetime.timedelta(days=3650)) - # NOTE: no timeout error raised, despite years have passed - assert guard.remaining_timeout is None - - def test_error_on_timeout(self, frozen_time): - with pytest.raises(requests.exceptions.Timeout): - with self.make_guard(timeout=10) as guard: - frozen_time.tick(delta=10.001) - assert guard.remaining_timeout == pytest.approx(-0.001) - - def test_custom_timeout_error_type(self, frozen_time): - class FooError(Exception): - pass - - with pytest.raises(FooError): - with self.make_guard(timeout=1, timeout_error_type=FooError): - frozen_time.tick(2) - - def test_lets_errors_bubble_up(self, frozen_time): - with pytest.raises(IndexError): - with self.make_guard(timeout=1): - [1, 2, 3][3] - - class CredentialsStub(google.auth.credentials.Credentials): def __init__(self, token="token"): super(CredentialsStub, self).__init__() @@ -95,18 +49,6 @@ def refresh(self, request): self.token += "1" -class TimeTickCredentialsStub(CredentialsStub): - """Credentials that spend some (mocked) time when refreshing a token.""" - - def __init__(self, time_tick, token="token"): - self._time_tick = time_tick - super(TimeTickCredentialsStub, self).__init__(token=token) - - def refresh(self, request): - self._time_tick() - super(TimeTickCredentialsStub, self).refresh(requests) - - class AdapterStub(requests.adapters.BaseAdapter): def __init__(self, responses, headers=None): super(AdapterStub, self).__init__() @@ -127,18 +69,6 @@ def close(self): # pragma: NO COVER return -class TimeTickAdapterStub(AdapterStub): - """Adapter that spends some (mocked) time when making a request.""" - - def __init__(self, time_tick, responses, headers=None): - self._time_tick = time_tick - super(TimeTickAdapterStub, self).__init__(responses, headers=headers) - - def send(self, request, **kwargs): - self._time_tick() - return super(TimeTickAdapterStub, self).send(request, **kwargs) - - def make_response(status=http_client.OK, data=None): response = requests.Response() response.status_code = status @@ -191,9 +121,7 @@ def test_request_refresh(self): [make_response(status=http_client.UNAUTHORIZED), final_response] ) - authed_session = google.auth.transport.requests.AuthorizedSession( - credentials, refresh_timeout=60 - ) + authed_session = google.auth.transport.requests.AuthorizedSession(credentials) authed_session.mount(self.TEST_URL, adapter) result = authed_session.request("GET", self.TEST_URL) @@ -208,44 +136,3 @@ def test_request_refresh(self): assert adapter.requests[1].url == self.TEST_URL assert adapter.requests[1].headers["authorization"] == "token1" - - def test_request_timout(self, frozen_time): - tick_one_second = functools.partial(frozen_time.tick, delta=1.0) - - credentials = mock.Mock( - wraps=TimeTickCredentialsStub(time_tick=tick_one_second) - ) - adapter = TimeTickAdapterStub( - time_tick=tick_one_second, - responses=[ - make_response(status=http_client.UNAUTHORIZED), - make_response(status=http_client.OK), - ], - ) - - authed_session = google.auth.transport.requests.AuthorizedSession(credentials) - authed_session.mount(self.TEST_URL, adapter) - - # Because at least two requests have to be made, and each takes one - # second, the total timeout specified will be exceeded. - with pytest.raises(requests.exceptions.Timeout): - authed_session.request("GET", self.TEST_URL, timeout=1.9) - - def test_request_timeout_w_refresh_timeout(self, frozen_time): - credentials = mock.Mock(wraps=CredentialsStub()) - adapter = TimeTickAdapterStub( - time_tick=functools.partial(frozen_time.tick, delta=1.0), # one second - responses=[ - make_response(status=http_client.UNAUTHORIZED), - make_response(status=http_client.OK), - ], - ) - - authed_session = google.auth.transport.requests.AuthorizedSession( - credentials, refresh_timeout=0.9 - ) - authed_session.mount(self.TEST_URL, adapter) - - # The timeout is long, but the short refresh timeout will prevail. - with pytest.raises(requests.exceptions.Timeout): - authed_session.request("GET", self.TEST_URL, timeout=60) From 0ad9d86ac233b6d33c256143e19dc09f4e66219d Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2019 15:27:59 -0800 Subject: [PATCH 244/966] chore: release 1.8.1 (#402) --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 481296b7132b..300de2f0c6cc 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +### [1.8.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.8.0...v1.8.1) (2019-12-09) + + +### Bug Fixes + +* revert "feat: add timeout to AuthorizedSession.request() ([#397](https://www.github.com/googleapis/google-auth-library-python/issues/397))" ([#401](https://www.github.com/googleapis/google-auth-library-python/issues/401)) ([451ecbd](https://www.github.com/googleapis/google-auth-library-python/commit/451ecbd48a910348bbf7a7b38162a044fad6e6e1)) + ## [1.8.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.7.2...v1.8.0) (2019-12-09) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index bad946fff51a..d8ba62cbe11a 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -30,7 +30,7 @@ with io.open("README.rst", "r") as fh: long_description = fh.read() -version = "1.8.0" +version = "1.8.1" setup( name="google-auth", From e9d73ac4b34406cda87001ea42ff9bfb2a98bb7a Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Wed, 11 Dec 2019 16:39:35 -0500 Subject: [PATCH 245/966] fix: revert "feat: send quota project id in x-goog-user-project header for OAuth2 credentials (#400)" (#407) This reverts commit ab3dc1e26f5240ea3456de364c7c5cb8f40f9583. --- .../docs/reference/google.auth.crypt.base.rst | 7 ----- .../docs/reference/google.auth.crypt.rsa.rst | 7 ----- .../google-auth/google/oauth2/credentials.py | 19 ------------- .../tests/oauth2/test_credentials.py | 27 ------------------- 4 files changed, 60 deletions(-) delete mode 100644 packages/google-auth/docs/reference/google.auth.crypt.base.rst delete mode 100644 packages/google-auth/docs/reference/google.auth.crypt.rsa.rst diff --git a/packages/google-auth/docs/reference/google.auth.crypt.base.rst b/packages/google-auth/docs/reference/google.auth.crypt.base.rst deleted file mode 100644 index a8996501a7a2..000000000000 --- a/packages/google-auth/docs/reference/google.auth.crypt.base.rst +++ /dev/null @@ -1,7 +0,0 @@ -google.auth.crypt.base module -============================= - -.. automodule:: google.auth.crypt.base - :members: - :inherited-members: - :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.auth.crypt.rsa.rst b/packages/google-auth/docs/reference/google.auth.crypt.rsa.rst deleted file mode 100644 index 7060b03c82e3..000000000000 --- a/packages/google-auth/docs/reference/google.auth.crypt.rsa.rst +++ /dev/null @@ -1,7 +0,0 @@ -google.auth.crypt.rsa module -============================ - -.. automodule:: google.auth.crypt.rsa - :members: - :inherited-members: - :show-inheritance: diff --git a/packages/google-auth/google/oauth2/credentials.py b/packages/google-auth/google/oauth2/credentials.py index 422c8ab1054a..3a32c0631a5a 100644 --- a/packages/google-auth/google/oauth2/credentials.py +++ b/packages/google-auth/google/oauth2/credentials.py @@ -58,7 +58,6 @@ def __init__( client_id=None, client_secret=None, scopes=None, - quota_project_id=None, ): """ Args: @@ -82,9 +81,6 @@ def __init__( token if refresh information is provided (e.g. The refresh token scopes are a superset of this or contain a wild card scope like 'https://www.googleapis.com/auth/any-api'). - quota_project_id (Optional[str]): The project ID used for quota and billing. - This project may be different from the project used to - create the credentials. """ super(Credentials, self).__init__() self.token = token @@ -94,7 +90,6 @@ def __init__( self._token_uri = token_uri self._client_id = client_id self._client_secret = client_secret - self._quota_project_id = quota_project_id @property def refresh_token(self): @@ -128,11 +123,6 @@ def client_secret(self): """Optional[str]: The OAuth 2.0 client secret.""" return self._client_secret - @property - def quota_project_id(self): - """Optional[str]: The project to use for quota and billing purposes.""" - return self._quota_project_id - @property def requires_scopes(self): """False: OAuth 2.0 credentials have their scopes set when @@ -179,12 +169,6 @@ def refresh(self, request): ) ) - @_helpers.copy_docstring(credentials.Credentials) - def apply(self, headers, token=None): - super(Credentials, self).apply(headers, token=token) - if self.quota_project_id is not None: - headers["x-goog-user-project"] = self.quota_project_id - @classmethod def from_authorized_user_info(cls, info, scopes=None): """Creates a Credentials instance from parsed authorized user info. @@ -218,9 +202,6 @@ def from_authorized_user_info(cls, info, scopes=None): scopes=scopes, client_id=info["client_id"], client_secret=info["client_secret"], - quota_project_id=info.get( - "quota_project_id" - ), # quota project may not exist ) @classmethod diff --git a/packages/google-auth/tests/oauth2/test_credentials.py b/packages/google-auth/tests/oauth2/test_credentials.py index 59031d7defcd..bb70f151664c 100644 --- a/packages/google-auth/tests/oauth2/test_credentials.py +++ b/packages/google-auth/tests/oauth2/test_credentials.py @@ -294,33 +294,6 @@ def test_credentials_with_scopes_refresh_failure_raises_refresh_error( # expired.) assert creds.valid - def test_apply_with_quota_project_id(self): - creds = credentials.Credentials( - token="token", - refresh_token=self.REFRESH_TOKEN, - token_uri=self.TOKEN_URI, - client_id=self.CLIENT_ID, - client_secret=self.CLIENT_SECRET, - quota_project_id="quota-project-123", - ) - - headers = {} - creds.apply(headers) - assert headers["x-goog-user-project"] == "quota-project-123" - - def test_apply_with_no_quota_project_id(self): - creds = credentials.Credentials( - token="token", - refresh_token=self.REFRESH_TOKEN, - token_uri=self.TOKEN_URI, - client_id=self.CLIENT_ID, - client_secret=self.CLIENT_SECRET, - ) - - headers = {} - creds.apply(headers) - assert "x-goog-user-project" not in headers - def test_from_authorized_user_info(self): info = AUTH_USER_INFO.copy() From c63fa244ed91145d8b62b24535f6f338c7447d7d Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Wed, 11 Dec 2019 13:59:41 -0800 Subject: [PATCH 246/966] chore: release 1.8.2 (#409) --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 300de2f0c6cc..c9e6364a1863 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +### [1.8.2](https://www.github.com/googleapis/google-auth-library-python/compare/v1.8.1...v1.8.2) (2019-12-11) + + +### Bug Fixes + +* revert "feat: send quota project id in x-goog-user-project header for OAuth2 credentials ([#400](https://www.github.com/googleapis/google-auth-library-python/issues/400))" ([#407](https://www.github.com/googleapis/google-auth-library-python/issues/407)) ([25ea942](https://www.github.com/googleapis/google-auth-library-python/commit/25ea942cef4378ff22adf235dd1baf1ca0d595f8)) + ### [1.8.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.8.0...v1.8.1) (2019-12-09) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index d8ba62cbe11a..d98e89cc6527 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -30,7 +30,7 @@ with io.open("README.rst", "r") as fh: long_description = fh.read() -version = "1.8.1" +version = "1.8.2" setup( name="google-auth", From 5a32f06dc39c85e9e59969b835f7144aee1c82e9 Mon Sep 17 00:00:00 2001 From: Peter Lamut Date: Thu, 12 Dec 2019 01:33:45 +0100 Subject: [PATCH 247/966] feat: add timeout parameter to `AuthorizedSession.request()` (#406) * feat: add timeout to AuthorisedSession.request() * Add suport for timeout as a tuple to timeout guard The `request.Request` class also accepts a timeout as a pair (connect_timeout, read_timeout), and some downstream libraries use this form. This commit makes sure that the timeout logic correctly handles timeouts as a two-tuple. See also: https://2.python-requests.org/en/master/user/advanced/#timeouts --- .../google/auth/transport/requests.py | 111 +++++++++++-- packages/google-auth/noxfile.py | 1 + .../tests/transport/test_requests.py | 154 +++++++++++++++++- 3 files changed, 254 insertions(+), 12 deletions(-) diff --git a/packages/google-auth/google/auth/transport/requests.py b/packages/google-auth/google/auth/transport/requests.py index d1971cd88118..1caec0b47c07 100644 --- a/packages/google-auth/google/auth/transport/requests.py +++ b/packages/google-auth/google/auth/transport/requests.py @@ -18,6 +18,8 @@ import functools import logging +import numbers +import time try: import requests @@ -64,6 +66,50 @@ def data(self): return self._response.content +class TimeoutGuard(object): + """A context manager raising an error if the suite execution took too long. + + Args: + timeout ([Union[None, float, Tuple[float, float]]]): + The maximum number of seconds a suite can run without the context + manager raising a timeout exception on exit. If passed as a tuple, + the smaller of the values is taken as a timeout. If ``None``, a + timeout error is never raised. + timeout_error_type (Optional[Exception]): + The type of the error to raise on timeout. Defaults to + :class:`requests.exceptions.Timeout`. + """ + + def __init__(self, timeout, timeout_error_type=requests.exceptions.Timeout): + self._timeout = timeout + self.remaining_timeout = timeout + self._timeout_error_type = timeout_error_type + + def __enter__(self): + self._start = time.time() + return self + + def __exit__(self, exc_type, exc_value, traceback): + if exc_value: + return # let the error bubble up automatically + + if self._timeout is None: + return # nothing to do, the timeout was not specified + + elapsed = time.time() - self._start + deadline_hit = False + + if isinstance(self._timeout, numbers.Number): + self.remaining_timeout = self._timeout - elapsed + deadline_hit = self.remaining_timeout <= 0 + else: + self.remaining_timeout = tuple(x - elapsed for x in self._timeout) + deadline_hit = min(self.remaining_timeout) <= 0 + + if deadline_hit: + raise self._timeout_error_type() + + class Request(transport.Request): """Requests request adapter. @@ -193,8 +239,19 @@ def __init__( # credentials.refresh). self._auth_request = auth_request - def request(self, method, url, data=None, headers=None, **kwargs): - """Implementation of Requests' request.""" + def request(self, method, url, data=None, headers=None, timeout=None, **kwargs): + """Implementation of Requests' request. + + Args: + timeout (Optional[Union[float, Tuple[float, float]]]): The number + of seconds to wait before raising a ``Timeout`` exception. If + multiple requests are made under the hood, ``timeout`` is + interpreted as the approximate total time of **all** requests. + + If passed as a tuple ``(connect_timeout, read_timeout)``, the + smaller of the values is taken as the total allowed time across + all requests. + """ # pylint: disable=arguments-differ # Requests has a ton of arguments to request, but only two # (method, url) are required. We pass through all of the other @@ -208,13 +265,28 @@ def request(self, method, url, data=None, headers=None, **kwargs): # and we want to pass the original headers if we recurse. request_headers = headers.copy() if headers is not None else {} - self.credentials.before_request( - self._auth_request, method, url, request_headers + # Do not apply the timeout unconditionally in order to not override the + # _auth_request's default timeout. + auth_request = ( + self._auth_request + if timeout is None + else functools.partial(self._auth_request, timeout=timeout) ) - response = super(AuthorizedSession, self).request( - method, url, data=data, headers=request_headers, **kwargs - ) + with TimeoutGuard(timeout) as guard: + self.credentials.before_request(auth_request, method, url, request_headers) + timeout = guard.remaining_timeout + + with TimeoutGuard(timeout) as guard: + response = super(AuthorizedSession, self).request( + method, + url, + data=data, + headers=request_headers, + timeout=timeout, + **kwargs + ) + timeout = guard.remaining_timeout # If the response indicated that the credentials needed to be # refreshed, then refresh the credentials and re-attempt the @@ -233,17 +305,34 @@ def request(self, method, url, data=None, headers=None, **kwargs): self._max_refresh_attempts, ) - auth_request_with_timeout = functools.partial( - self._auth_request, timeout=self._refresh_timeout + if self._refresh_timeout is not None: + if timeout is None: + timeout = self._refresh_timeout + elif isinstance(timeout, numbers.Number): + timeout = min(timeout, self._refresh_timeout) + else: + timeout = tuple(min(x, self._refresh_timeout) for x in timeout) + + # Do not apply the timeout unconditionally in order to not override the + # _auth_request's default timeout. + auth_request = ( + self._auth_request + if timeout is None + else functools.partial(self._auth_request, timeout=timeout) ) - self.credentials.refresh(auth_request_with_timeout) - # Recurse. Pass in the original headers, not our modified set. + with TimeoutGuard(timeout) as guard: + self.credentials.refresh(auth_request) + timeout = guard.remaining_timeout + + # Recurse. Pass in the original headers, not our modified set, but + # do pass the adjusted timeout (i.e. the remaining time). return self.request( method, url, data=data, headers=headers, + timeout=timeout, _credential_refresh_attempt=_credential_refresh_attempt + 1, **kwargs ) diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index aaf1bc57da31..e170ee51d5a9 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -16,6 +16,7 @@ TEST_DEPENDENCIES = [ "flask", + "freezegun", "mock", "oauth2client", "pytest", diff --git a/packages/google-auth/tests/transport/test_requests.py b/packages/google-auth/tests/transport/test_requests.py index 0e165ac54b17..00269740f8e9 100644 --- a/packages/google-auth/tests/transport/test_requests.py +++ b/packages/google-auth/tests/transport/test_requests.py @@ -12,7 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. +import datetime +import functools + +import freezegun import mock +import pytest import requests import requests.adapters from six.moves import http_client @@ -22,6 +27,12 @@ from tests.transport import compliance +@pytest.fixture +def frozen_time(): + with freezegun.freeze_time("1970-01-01 00:00:00", tick=False) as frozen: + yield frozen + + class TestRequestResponse(compliance.RequestResponseTests): def make_request(self): return google.auth.transport.requests.Request() @@ -34,6 +45,52 @@ def test_timeout(self): assert http.request.call_args[1]["timeout"] == 5 +class TestTimeoutGuard(object): + def make_guard(self, *args, **kwargs): + return google.auth.transport.requests.TimeoutGuard(*args, **kwargs) + + def test_tracks_elapsed_time_w_numeric_timeout(self, frozen_time): + with self.make_guard(timeout=10) as guard: + frozen_time.tick(delta=3.8) + assert guard.remaining_timeout == 6.2 + + def test_tracks_elapsed_time_w_tuple_timeout(self, frozen_time): + with self.make_guard(timeout=(16, 19)) as guard: + frozen_time.tick(delta=3.8) + assert guard.remaining_timeout == (12.2, 15.2) + + def test_noop_if_no_timeout(self, frozen_time): + with self.make_guard(timeout=None) as guard: + frozen_time.tick(delta=datetime.timedelta(days=3650)) + # NOTE: no timeout error raised, despite years have passed + assert guard.remaining_timeout is None + + def test_timeout_error_w_numeric_timeout(self, frozen_time): + with pytest.raises(requests.exceptions.Timeout): + with self.make_guard(timeout=10) as guard: + frozen_time.tick(delta=10.001) + assert guard.remaining_timeout == pytest.approx(-0.001) + + def test_timeout_error_w_tuple_timeout(self, frozen_time): + with pytest.raises(requests.exceptions.Timeout): + with self.make_guard(timeout=(11, 10)) as guard: + frozen_time.tick(delta=10.001) + assert guard.remaining_timeout == pytest.approx((0.999, -0.001)) + + def test_custom_timeout_error_type(self, frozen_time): + class FooError(Exception): + pass + + with pytest.raises(FooError): + with self.make_guard(timeout=1, timeout_error_type=FooError): + frozen_time.tick(2) + + def test_lets_suite_errors_bubble_up(self, frozen_time): + with pytest.raises(IndexError): + with self.make_guard(timeout=1): + [1, 2, 3][3] + + class CredentialsStub(google.auth.credentials.Credentials): def __init__(self, token="token"): super(CredentialsStub, self).__init__() @@ -49,6 +106,18 @@ def refresh(self, request): self.token += "1" +class TimeTickCredentialsStub(CredentialsStub): + """Credentials that spend some (mocked) time when refreshing a token.""" + + def __init__(self, time_tick, token="token"): + self._time_tick = time_tick + super(TimeTickCredentialsStub, self).__init__(token=token) + + def refresh(self, request): + self._time_tick() + super(TimeTickCredentialsStub, self).refresh(requests) + + class AdapterStub(requests.adapters.BaseAdapter): def __init__(self, responses, headers=None): super(AdapterStub, self).__init__() @@ -69,6 +138,18 @@ def close(self): # pragma: NO COVER return +class TimeTickAdapterStub(AdapterStub): + """Adapter that spends some (mocked) time when making a request.""" + + def __init__(self, time_tick, responses, headers=None): + self._time_tick = time_tick + super(TimeTickAdapterStub, self).__init__(responses, headers=headers) + + def send(self, request, **kwargs): + self._time_tick() + return super(TimeTickAdapterStub, self).send(request, **kwargs) + + def make_response(status=http_client.OK, data=None): response = requests.Response() response.status_code = status @@ -121,7 +202,9 @@ def test_request_refresh(self): [make_response(status=http_client.UNAUTHORIZED), final_response] ) - authed_session = google.auth.transport.requests.AuthorizedSession(credentials) + authed_session = google.auth.transport.requests.AuthorizedSession( + credentials, refresh_timeout=60 + ) authed_session.mount(self.TEST_URL, adapter) result = authed_session.request("GET", self.TEST_URL) @@ -136,3 +219,72 @@ def test_request_refresh(self): assert adapter.requests[1].url == self.TEST_URL assert adapter.requests[1].headers["authorization"] == "token1" + + def test_request_timeout(self, frozen_time): + tick_one_second = functools.partial(frozen_time.tick, delta=1.0) + + credentials = mock.Mock( + wraps=TimeTickCredentialsStub(time_tick=tick_one_second) + ) + adapter = TimeTickAdapterStub( + time_tick=tick_one_second, + responses=[ + make_response(status=http_client.UNAUTHORIZED), + make_response(status=http_client.OK), + ], + ) + + authed_session = google.auth.transport.requests.AuthorizedSession(credentials) + authed_session.mount(self.TEST_URL, adapter) + + # Because at least two requests have to be made, and each takes one + # second, the total timeout specified will be exceeded. + with pytest.raises(requests.exceptions.Timeout): + authed_session.request("GET", self.TEST_URL, timeout=1.9) + + def test_request_timeout_w_refresh_timeout(self, frozen_time): + tick_one_second = functools.partial(frozen_time.tick, delta=1.0) + + credentials = mock.Mock( + wraps=TimeTickCredentialsStub(time_tick=tick_one_second) + ) + adapter = TimeTickAdapterStub( + time_tick=tick_one_second, + responses=[ + make_response(status=http_client.UNAUTHORIZED), + make_response(status=http_client.OK), + ], + ) + + authed_session = google.auth.transport.requests.AuthorizedSession( + credentials, refresh_timeout=1.9 + ) + authed_session.mount(self.TEST_URL, adapter) + + # The timeout is long, but the short refresh timeout will prevail. + with pytest.raises(requests.exceptions.Timeout): + authed_session.request("GET", self.TEST_URL, timeout=60) + + def test_request_timeout_w_refresh_timeout_and_tuple_timeout(self, frozen_time): + tick_one_second = functools.partial(frozen_time.tick, delta=1.0) + + credentials = mock.Mock( + wraps=TimeTickCredentialsStub(time_tick=tick_one_second) + ) + adapter = TimeTickAdapterStub( + time_tick=tick_one_second, + responses=[ + make_response(status=http_client.UNAUTHORIZED), + make_response(status=http_client.OK), + ], + ) + + authed_session = google.auth.transport.requests.AuthorizedSession( + credentials, refresh_timeout=100 + ) + authed_session.mount(self.TEST_URL, adapter) + + # The shortest timeout will prevail and cause a Timeout error, despite + # other timeouts being quite long. + with pytest.raises(requests.exceptions.Timeout): + authed_session.request("GET", self.TEST_URL, timeout=(100, 2.9)) From 4d9cc6f0ba3fe147aa7ef76da5f5e25fb5b9e53c Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 16 Dec 2019 15:27:23 -0800 Subject: [PATCH 248/966] chore: release 1.9.0 (#410) --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index c9e6364a1863..4e2631643f23 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +## [1.9.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.8.2...v1.9.0) (2019-12-12) + + +### Features + +* add timeout parameter to `AuthorizedSession.request()` ([#406](https://www.github.com/googleapis/google-auth-library-python/issues/406)) ([d86d7b8](https://www.github.com/googleapis/google-auth-library-python/commit/d86d7b8c43df152765c7fc59a54015361b46dcde)) + ### [1.8.2](https://www.github.com/googleapis/google-auth-library-python/compare/v1.8.1...v1.8.2) (2019-12-11) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index d98e89cc6527..f4a8b1748d65 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -30,7 +30,7 @@ with io.open("README.rst", "r") as fh: long_description = fh.read() -version = "1.8.2" +version = "1.9.0" setup( name="google-auth", From 7faa3cae798afb17b3acd5336c401047884d1437 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Tue, 17 Dec 2019 01:43:01 +0200 Subject: [PATCH 249/966] chore(deps): update dependency cachetools to v4 (#411) --- packages/google-auth/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index f4a8b1748d65..29abec2a5c45 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -19,7 +19,7 @@ DEPENDENCIES = ( - "cachetools>=2.0.0,<3.2", + "cachetools>=2.0.0,<5.0", "pyasn1-modules>=0.2.1", "rsa>=3.1.4,<4.1", "setuptools>=40.3.0", From 23493c79e2647dd4a529e1ab16c4f74ca47cda41 Mon Sep 17 00:00:00 2001 From: Tim Swast Date: Tue, 17 Dec 2019 16:45:54 -0800 Subject: [PATCH 250/966] docs: update project URL (#413) --- packages/google-auth/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 29abec2a5c45..0078aaf02c25 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -39,7 +39,7 @@ author_email="googleapis-packages@google.com", description="Google Authentication Library", long_description=long_description, - url="https://github.com/GoogleCloudPlatform/google-auth-library-python", + url="https://github.com/googleapis/google-auth-library-python", packages=find_packages(exclude=("tests*", "system_tests*")), namespace_packages=("google",), install_requires=DEPENDENCIES, From 66658bc76b784ee7ce851bb7a47777cfcdea2c56 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Wed, 18 Dec 2019 11:30:46 -0800 Subject: [PATCH 251/966] feat: send quota project id in x-goog-user-project for OAuth2 credentials (#412) * feat: send quota project id in x-goog-user-project header for OAuth2 credentials (#400) When the 3LO credentials are used, the quota project ("quota_project_id") is sent on every outgoing request in the x-goog-user-project HTTP header/grpc metadata. The quota project is used for billing and quota purposes. * feat: add `__setstate__` and `__getstate__` to `oauth2.credentials` class --- .../docs/reference/google.auth.crypt.base.rst | 7 ++ .../docs/reference/google.auth.crypt.rsa.rst | 7 ++ .../google-auth/google/oauth2/credentials.py | 39 +++++++++++ .../data/old_oauth_credentials_py3.pickle | Bin 0 -> 283 bytes .../tests/oauth2/test_credentials.py | 66 ++++++++++++++++++ 5 files changed, 119 insertions(+) create mode 100644 packages/google-auth/docs/reference/google.auth.crypt.base.rst create mode 100644 packages/google-auth/docs/reference/google.auth.crypt.rsa.rst create mode 100644 packages/google-auth/tests/data/old_oauth_credentials_py3.pickle diff --git a/packages/google-auth/docs/reference/google.auth.crypt.base.rst b/packages/google-auth/docs/reference/google.auth.crypt.base.rst new file mode 100644 index 000000000000..a8996501a7a2 --- /dev/null +++ b/packages/google-auth/docs/reference/google.auth.crypt.base.rst @@ -0,0 +1,7 @@ +google.auth.crypt.base module +============================= + +.. automodule:: google.auth.crypt.base + :members: + :inherited-members: + :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.auth.crypt.rsa.rst b/packages/google-auth/docs/reference/google.auth.crypt.rsa.rst new file mode 100644 index 000000000000..7060b03c82e3 --- /dev/null +++ b/packages/google-auth/docs/reference/google.auth.crypt.rsa.rst @@ -0,0 +1,7 @@ +google.auth.crypt.rsa module +============================ + +.. automodule:: google.auth.crypt.rsa + :members: + :inherited-members: + :show-inheritance: diff --git a/packages/google-auth/google/oauth2/credentials.py b/packages/google-auth/google/oauth2/credentials.py index 3a32c0631a5a..71d2f6107e55 100644 --- a/packages/google-auth/google/oauth2/credentials.py +++ b/packages/google-auth/google/oauth2/credentials.py @@ -58,6 +58,7 @@ def __init__( client_id=None, client_secret=None, scopes=None, + quota_project_id=None, ): """ Args: @@ -81,6 +82,9 @@ def __init__( token if refresh information is provided (e.g. The refresh token scopes are a superset of this or contain a wild card scope like 'https://www.googleapis.com/auth/any-api'). + quota_project_id (Optional[str]): The project ID used for quota and billing. + This project may be different from the project used to + create the credentials. """ super(Credentials, self).__init__() self.token = token @@ -90,6 +94,27 @@ def __init__( self._token_uri = token_uri self._client_id = client_id self._client_secret = client_secret + self._quota_project_id = quota_project_id + + def __getstate__(self): + """A __getstate__ method must exist for the __setstate__ to be called + This is identical to the default implementation. + See https://docs.python.org/3.7/library/pickle.html#object.__setstate__ + """ + return self.__dict__ + + def __setstate__(self, d): + """Credentials pickled with older versions of the class do not have + all the attributes.""" + self.token = d.get("token") + self.expiry = d.get("expiry") + self._refresh_token = d.get("_refresh_token") + self._id_token = d.get("_id_token") + self._scopes = d.get("_scopes") + self._token_uri = d.get("_token_uri") + self._client_id = d.get("_client_id") + self._client_secret = d.get("_client_secret") + self._quota_project_id = d.get("_quota_project_id") @property def refresh_token(self): @@ -123,6 +148,11 @@ def client_secret(self): """Optional[str]: The OAuth 2.0 client secret.""" return self._client_secret + @property + def quota_project_id(self): + """Optional[str]: The project to use for quota and billing purposes.""" + return self._quota_project_id + @property def requires_scopes(self): """False: OAuth 2.0 credentials have their scopes set when @@ -169,6 +199,12 @@ def refresh(self, request): ) ) + @_helpers.copy_docstring(credentials.Credentials) + def apply(self, headers, token=None): + super(Credentials, self).apply(headers, token=token) + if self.quota_project_id is not None: + headers["x-goog-user-project"] = self.quota_project_id + @classmethod def from_authorized_user_info(cls, info, scopes=None): """Creates a Credentials instance from parsed authorized user info. @@ -202,6 +238,9 @@ def from_authorized_user_info(cls, info, scopes=None): scopes=scopes, client_id=info["client_id"], client_secret=info["client_secret"], + quota_project_id=info.get( + "quota_project_id" + ), # quota project may not exist ) @classmethod diff --git a/packages/google-auth/tests/data/old_oauth_credentials_py3.pickle b/packages/google-auth/tests/data/old_oauth_credentials_py3.pickle new file mode 100644 index 0000000000000000000000000000000000000000..c8a05599b1b778b983609c3a6921394fadd33035 GIT binary patch literal 283 zcmY*UyAFat5Jd5buV1j#1|oL0_SD-EJXX1g9(#8rTA1jcyKp9IH#564nR)u+q|~Xz zP-V3(;!tSJ@oHsOnqdEf$y>ju^Puhu`kv=mb;c|C;4|LDTk64p5#B1P@6=CV$SgHN zRcL6Cz`UUvj~Y#pI`6RcZsQG&VBDN#{<(}32pXaNOKUADfF%oswUyaL5#F*(#f^o! mM6Pw@A}7u#O34d2kEU(%m(N_0k6@6;TAIU*!~eBKeF)!4f>b>K literal 0 HcmV?d00001 diff --git a/packages/google-auth/tests/oauth2/test_credentials.py b/packages/google-auth/tests/oauth2/test_credentials.py index bb70f151664c..af10f2fa45fe 100644 --- a/packages/google-auth/tests/oauth2/test_credentials.py +++ b/packages/google-auth/tests/oauth2/test_credentials.py @@ -15,6 +15,8 @@ import datetime import json import os +import pickle +import sys import mock import pytest @@ -294,6 +296,33 @@ def test_credentials_with_scopes_refresh_failure_raises_refresh_error( # expired.) assert creds.valid + def test_apply_with_quota_project_id(self): + creds = credentials.Credentials( + token="token", + refresh_token=self.REFRESH_TOKEN, + token_uri=self.TOKEN_URI, + client_id=self.CLIENT_ID, + client_secret=self.CLIENT_SECRET, + quota_project_id="quota-project-123", + ) + + headers = {} + creds.apply(headers) + assert headers["x-goog-user-project"] == "quota-project-123" + + def test_apply_with_no_quota_project_id(self): + creds = credentials.Credentials( + token="token", + refresh_token=self.REFRESH_TOKEN, + token_uri=self.TOKEN_URI, + client_id=self.CLIENT_ID, + client_secret=self.CLIENT_SECRET, + ) + + headers = {} + creds.apply(headers) + assert "x-goog-user-project" not in headers + def test_from_authorized_user_info(self): info = AUTH_USER_INFO.copy() @@ -355,3 +384,40 @@ def test_to_json(self): assert json_asdict.get("client_id") == creds.client_id assert json_asdict.get("scopes") == creds.scopes assert json_asdict.get("client_secret") is None + + def test_pickle_and_unpickle(self): + creds = self.make_credentials() + unpickled = pickle.loads(pickle.dumps(creds)) + + # make sure attributes aren't lost during pickling + assert list(creds.__dict__).sort() == list(unpickled.__dict__).sort() + + for attr in list(creds.__dict__): + assert getattr(creds, attr) == getattr(unpickled, attr) + + def test_pickle_with_missing_attribute(self): + creds = self.make_credentials() + + # remove an optional attribute before pickling + # this mimics a pickle created with a previous class definition with + # fewer attributes + del creds.__dict__["_quota_project_id"] + + unpickled = pickle.loads(pickle.dumps(creds)) + + # Attribute should be initialized by `__setstate__` + assert unpickled.quota_project_id is None + + # pickles are not compatible across versions + @pytest.mark.skipif( + sys.version_info < (3, 5), + reason="pickle file can only be loaded with Python >= 3.5", + ) + def test_unpickle_old_credentials_pickle(self): + # make sure a credentials file pickled with an older + # library version (google-auth==1.5.1) can be unpickled + with open( + os.path.join(DATA_DIR, "old_oauth_credentials_py3.pickle"), "rb" + ) as f: + credentials = pickle.load(f) + assert credentials.quota_project_id is None From 3402e43e46628535c6ab7a7ab9d3c1fef25be0f5 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Wed, 18 Dec 2019 13:51:56 -0800 Subject: [PATCH 252/966] chore: release 1.10.0 (#415) --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 4e2631643f23..9762d0c0383c 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +## [1.10.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.9.0...v1.10.0) (2019-12-18) + + +### Features + +* send quota project id in x-goog-user-project for OAuth2 credentials ([#412](https://www.github.com/googleapis/google-auth-library-python/issues/412)) ([32d71a5](https://www.github.com/googleapis/google-auth-library-python/commit/32d71a5858435af0818a705b754404882bb7bb9e)), closes [#400](https://www.github.com/googleapis/google-auth-library-python/issues/400) + ## [1.9.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.8.2...v1.9.0) (2019-12-12) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 0078aaf02c25..954ce17631dd 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -30,7 +30,7 @@ with io.open("README.rst", "r") as fh: long_description = fh.read() -version = "1.9.0" +version = "1.10.0" setup( name="google-auth", From 56ce0a3e7d749f22dbedaa65aba7237aad8a9346 Mon Sep 17 00:00:00 2001 From: Daniil Date: Thu, 9 Jan 2020 19:55:31 +0200 Subject: [PATCH 253/966] fix(google.auth.compute_engine.metadata): add retry to google.auth.compute_engine._metadata.get() (#398) Initial fix of issue #211 was done in CL #323, but only for .ping() This one is adding same behaviour & tests for .get() method, as the problem still occurres See the issue for details Refs: #323 Resolves: #211 --- .../google/auth/compute_engine/_metadata.py | 23 +++++++- .../tests/compute_engine/test__metadata.py | 57 ++++++++++++++++++- 2 files changed, 76 insertions(+), 4 deletions(-) diff --git a/packages/google-auth/google/auth/compute_engine/_metadata.py b/packages/google-auth/google/auth/compute_engine/_metadata.py index f4fae7298a8d..30cd3d43b59f 100644 --- a/packages/google-auth/google/auth/compute_engine/_metadata.py +++ b/packages/google-auth/google/auth/compute_engine/_metadata.py @@ -99,7 +99,7 @@ def ping(request, timeout=_METADATA_DEFAULT_TIMEOUT, retry_count=3): return False -def get(request, path, root=_METADATA_ROOT, recursive=False): +def get(request, path, root=_METADATA_ROOT, recursive=False, retry_count=5): """Fetch a resource from the metadata server. Args: @@ -111,6 +111,8 @@ def get(request, path, root=_METADATA_ROOT, recursive=False): recursive (bool): Whether to do a recursive query of metadata. See https://cloud.google.com/compute/docs/metadata#aggcontents for more details. + retry_count (int): How many times to attempt connecting to metadata + server using above timeout. Returns: Union[Mapping, str]: If the metadata server returns JSON, a mapping of @@ -129,7 +131,24 @@ def get(request, path, root=_METADATA_ROOT, recursive=False): url = _helpers.update_query(base_url, query_params) - response = request(url=url, method="GET", headers=_METADATA_HEADERS) + retries = 0 + while retries < retry_count: + try: + response = request(url=url, method="GET", headers=_METADATA_HEADERS) + break + + except exceptions.TransportError: + _LOGGER.info( + "Compute Engine Metadata server unavailable on" "attempt %s of %s", + retries + 1, + retry_count, + ) + retries += 1 + else: + raise exceptions.TransportError( + "Failed to retrieve {} from the Google Compute Engine" + "metadata service. Compute Engine Metadata server unavailable".format(url) + ) if response.status == http_client.OK: content = _helpers.from_bytes(response.data) diff --git a/packages/google-auth/tests/compute_engine/test__metadata.py b/packages/google-auth/tests/compute_engine/test__metadata.py index bd06b7402b88..0898e1f4e5fb 100644 --- a/packages/google-auth/tests/compute_engine/test__metadata.py +++ b/packages/google-auth/tests/compute_engine/test__metadata.py @@ -30,14 +30,17 @@ PATH = "instance/service-accounts/default" -def make_request(data, status=http_client.OK, headers=None): +def make_request(data, status=http_client.OK, headers=None, retry=False): response = mock.create_autospec(transport.Response, instance=True) response.status = status response.data = _helpers.to_bytes(data) response.headers = headers or {} request = mock.create_autospec(transport.Request) - request.return_value = response + if retry: + request.side_effect = [exceptions.TransportError(), response] + else: + request.return_value = response return request @@ -55,6 +58,20 @@ def test_ping_success(): ) +def test_ping_success_retry(): + request = make_request("", headers=_metadata._METADATA_HEADERS, retry=True) + + assert _metadata.ping(request) + + request.assert_called_with( + method="GET", + url=_metadata._METADATA_IP_ROOT, + headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, + ) + assert request.call_count == 2 + + def test_ping_failure_bad_flavor(): request = make_request("", headers={_metadata._METADATA_FLAVOR_HEADER: "meep"}) @@ -105,6 +122,25 @@ def test_get_success_json(): assert result[key] == value +def test_get_success_retry(): + key, value = "foo", "bar" + + data = json.dumps({key: value}) + request = make_request( + data, headers={"content-type": "application/json"}, retry=True + ) + + result = _metadata.get(request, PATH) + + request.assert_called_with( + method="GET", + url=_metadata._METADATA_ROOT + PATH, + headers=_metadata._METADATA_HEADERS, + ) + assert request.call_count == 2 + assert result[key] == value + + def test_get_success_text(): data = "foobar" request = make_request(data, headers={"content-type": "text/plain"}) @@ -154,6 +190,23 @@ def test_get_failure(): ) +def test_get_failure_connection_failed(): + request = make_request("") + request.side_effect = exceptions.TransportError() + + with pytest.raises(exceptions.TransportError) as excinfo: + _metadata.get(request, PATH) + + assert excinfo.match(r"Compute Engine Metadata server unavailable") + + request.assert_called_with( + method="GET", + url=_metadata._METADATA_ROOT + PATH, + headers=_metadata._METADATA_HEADERS, + ) + assert request.call_count == 5 + + def test_get_failure_bad_json(): request = make_request("{", headers={"content-type": "application/json"}) From cae3d639f3fd71ae7adce3ec396cf6e62ad54429 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Fri, 10 Jan 2020 13:17:34 -0800 Subject: [PATCH 254/966] fix: always pass body of type bytes to `google.auth.transport.Request` (#421) [`google.auth.transport.Request`](https://google-auth.readthedocs.io/en/latest/reference/google.auth.transport.html#google.auth.transport.Request) says that the body should be of type bytes. Some of our code was passing in strings as reported in #318. In practice this was not causing issues, as the two http transports [requests](https://google-auth.readthedocs.io/en/latest/reference/google.auth.transport.requests.html) and [urllib3](https://google-auth.readthedocs.io/en/latest/reference/google.auth.transport.urllib3.html) are able to handle bodies passed as strings. --- packages/google-auth/google/auth/iam.py | 4 +++- packages/google-auth/google/auth/impersonated_credentials.py | 2 +- packages/google-auth/google/oauth2/_client.py | 2 +- packages/google-auth/tests/oauth2/test__client.py | 4 ++-- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/google-auth/google/auth/iam.py b/packages/google-auth/google/auth/iam.py index a43872658ee3..0ab5b5549b40 100644 --- a/packages/google-auth/google/auth/iam.py +++ b/packages/google-auth/google/auth/iam.py @@ -70,7 +70,9 @@ def _make_signing_request(self, message): method = "POST" url = _SIGN_BLOB_URI.format(self._service_account_email) headers = {} - body = json.dumps({"bytesToSign": base64.b64encode(message).decode("utf-8")}) + body = json.dumps( + {"bytesToSign": base64.b64encode(message).decode("utf-8")} + ).encode("utf-8") self._credentials.before_request(self._request, method, url, headers) response = self._request(url=url, method=method, body=body, headers=headers) diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index 70fa5dc9c3cb..bc7031e78c31 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -84,7 +84,7 @@ def _make_iam_token_request(request, principal, headers, body): """ iam_endpoint = _IAM_ENDPOINT.format(principal) - body = json.dumps(body) + body = json.dumps(body).encode("utf-8") response = request(url=iam_endpoint, method="POST", headers=headers, body=body) diff --git a/packages/google-auth/google/oauth2/_client.py b/packages/google-auth/google/oauth2/_client.py index 4cf7a7fe90f4..4ba31a87a9dd 100644 --- a/packages/google-auth/google/oauth2/_client.py +++ b/packages/google-auth/google/oauth2/_client.py @@ -95,7 +95,7 @@ def _token_endpoint_request(request, token_uri, body): google.auth.exceptions.RefreshError: If the token endpoint returned an error. """ - body = urllib.parse.urlencode(body) + body = urllib.parse.urlencode(body).encode("utf-8") headers = {"content-type": _URLENCODED_CONTENT_TYPE} retry = 0 diff --git a/packages/google-auth/tests/oauth2/test__client.py b/packages/google-auth/tests/oauth2/test__client.py index 9cf59eb981be..052390aa8113 100644 --- a/packages/google-auth/tests/oauth2/test__client.py +++ b/packages/google-auth/tests/oauth2/test__client.py @@ -96,7 +96,7 @@ def test__token_endpoint_request(): method="POST", url="http://example.com", headers={"content-type": "application/x-www-form-urlencoded"}, - body="test=params", + body="test=params".encode("utf-8"), ) # Check result @@ -131,7 +131,7 @@ def test__token_endpoint_request_internal_failure_error(): def verify_request_params(request, params): - request_body = request.call_args[1]["body"] + request_body = request.call_args[1]["body"].decode("utf-8") request_params = urllib.parse.parse_qs(request_body) for key, value in six.iteritems(params): From c3b17e313319e2db0908dc4396c5bb841a2c239a Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 14 Jan 2020 09:23:06 -0800 Subject: [PATCH 255/966] chore: release 1.10.1 (#420) --- packages/google-auth/CHANGELOG.md | 8 ++++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 9762d0c0383c..70032efcba44 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,14 @@ [1]: https://pypi.org/project/google-auth/#history +### [1.10.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.10.0...v1.10.1) (2020-01-10) + + +### Bug Fixes + +* **google.auth.compute_engine.metadata:** add retry to google.auth.compute_engine._metadata.get() ([#398](https://www.github.com/googleapis/google-auth-library-python/issues/398)) ([af29c1a](https://www.github.com/googleapis/google-auth-library-python/commit/af29c1a9fd9282b38867961e4053f74f018a3815)), closes [#211](https://www.github.com/googleapis/google-auth-library-python/issues/211) [#323](https://www.github.com/googleapis/google-auth-library-python/issues/323) [#323](https://www.github.com/googleapis/google-auth-library-python/issues/323) [#211](https://www.github.com/googleapis/google-auth-library-python/issues/211) +* always pass body of type bytes to `google.auth.transport.Request` ([#421](https://www.github.com/googleapis/google-auth-library-python/issues/421)) ([a57a770](https://www.github.com/googleapis/google-auth-library-python/commit/a57a7708cfea635b5030f8c7ba10c967715f9a87)), closes [#318](https://www.github.com/googleapis/google-auth-library-python/issues/318) + ## [1.10.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.9.0...v1.10.0) (2019-12-18) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 954ce17631dd..1b5eac4bcd97 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -30,7 +30,7 @@ with io.open("README.rst", "r") as fh: long_description = fh.read() -version = "1.10.0" +version = "1.10.1" setup( name="google-auth", From 251ea73f5503cb55c05a8831a826a3bfcff3bfc2 Mon Sep 17 00:00:00 2001 From: Jay Lee Date: Fri, 17 Jan 2020 11:18:47 -0800 Subject: [PATCH 256/966] fix: make collections import compatible across Python versions (#419) * Use collections.abc, fixes #418 * Python 2.7 compat fix * Fix check --- packages/google-auth/google/auth/jwt.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/google/auth/jwt.py b/packages/google-auth/google/auth/jwt.py index a30c575b2c3a..361c4567ae7c 100644 --- a/packages/google-auth/google/auth/jwt.py +++ b/packages/google-auth/google/auth/jwt.py @@ -40,7 +40,10 @@ """ -import collections +try: + from collections.abc import Mapping +except ImportError: # Python 2.7 compatibility + from collections import Mapping import copy import datetime import json @@ -215,7 +218,7 @@ def decode(token, certs=None, verify=True, audience=None): # If certs is specified as a dictionary of key IDs to certificates, then # use the certificate identified by the key ID in the token header. - if isinstance(certs, collections.Mapping): + if isinstance(certs, Mapping): key_id = header.get("kid") if key_id: if key_id not in certs: From 41a2f4431a56247a926eb518fe541208488c448d Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Sat, 18 Jan 2020 00:38:49 -0800 Subject: [PATCH 257/966] chore: add no cover for py2 collections import (#426) --- packages/google-auth/google/auth/jwt.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/jwt.py b/packages/google-auth/google/auth/jwt.py index 361c4567ae7c..06e7679689c9 100644 --- a/packages/google-auth/google/auth/jwt.py +++ b/packages/google-auth/google/auth/jwt.py @@ -42,7 +42,8 @@ try: from collections.abc import Mapping -except ImportError: # Python 2.7 compatibility +# Python 2.7 compatibility +except ImportError: # pragma: NO COVER from collections import Mapping import copy import datetime From 93d073008b66c51ce08a0895ab5051afdcc90372 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 21 Jan 2020 14:02:12 -0800 Subject: [PATCH 258/966] chore: release 1.10.2 (#425) --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 70032efcba44..bc1d040d1034 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +### [1.10.2](https://www.github.com/googleapis/google-auth-library-python/compare/v1.10.1...v1.10.2) (2020-01-18) + + +### Bug Fixes + +* make collections import compatible across Python versions ([#419](https://www.github.com/googleapis/google-auth-library-python/issues/419)) ([c5a3395](https://www.github.com/googleapis/google-auth-library-python/commit/c5a3395b8781e14c4566cf0e476b234d6a1c1224)), closes [#418](https://www.github.com/googleapis/google-auth-library-python/issues/418) + ### [1.10.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.10.0...v1.10.1) (2020-01-10) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 1b5eac4bcd97..708a5d164079 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -30,7 +30,7 @@ with io.open("README.rst", "r") as fh: long_description = fh.read() -version = "1.10.1" +version = "1.10.2" setup( name="google-auth", From 40bf7588847b1b26b51c5c74f03e64de8545f1f3 Mon Sep 17 00:00:00 2001 From: Peter Lamut Date: Wed, 22 Jan 2020 19:07:03 +0000 Subject: [PATCH 259/966] feat: distinguish transport and execution time timeouts (#424) In Requests transport treat ``timeout`` stricly as a transport timeout, while the total allowed method execution time can be set with an optional ``max_allowed_time`` argument. --- .../google/auth/transport/requests.py | 63 ++++++++++++------- .../tests/transport/test_requests.py | 51 ++++++++++----- 2 files changed, 76 insertions(+), 38 deletions(-) diff --git a/packages/google-auth/google/auth/transport/requests.py b/packages/google-auth/google/auth/transport/requests.py index 1caec0b47c07..ce78e63a8559 100644 --- a/packages/google-auth/google/auth/transport/requests.py +++ b/packages/google-auth/google/auth/transport/requests.py @@ -239,18 +239,38 @@ def __init__( # credentials.refresh). self._auth_request = auth_request - def request(self, method, url, data=None, headers=None, timeout=None, **kwargs): + def request( + self, + method, + url, + data=None, + headers=None, + max_allowed_time=None, + timeout=None, + **kwargs + ): """Implementation of Requests' request. Args: - timeout (Optional[Union[float, Tuple[float, float]]]): The number - of seconds to wait before raising a ``Timeout`` exception. If - multiple requests are made under the hood, ``timeout`` is - interpreted as the approximate total time of **all** requests. - - If passed as a tuple ``(connect_timeout, read_timeout)``, the - smaller of the values is taken as the total allowed time across - all requests. + timeout (Optional[Union[float, Tuple[float, float]]]): + The amount of time in seconds to wait for the server response + with each individual request. + + Can also be passed as a tuple (connect_timeout, read_timeout). + See :meth:`requests.Session.request` documentation for details. + + max_allowed_time (Optional[float]): + If the method runs longer than this, a ``Timeout`` exception is + automatically raised. Unlike the ``timeout` parameter, this + value applies to the total method execution time, even if + multiple requests are made under the hood. + + Mind that it is not guaranteed that the timeout error is raised + at ``max_allowed_time`. It might take longer, for example, if + an underlying request takes a lot of time, but the request + itself does not timeout, e.g. if a large file is being + transmitted. The timout error will be raised after such + request completes. """ # pylint: disable=arguments-differ # Requests has a ton of arguments to request, but only two @@ -273,11 +293,13 @@ def request(self, method, url, data=None, headers=None, timeout=None, **kwargs): else functools.partial(self._auth_request, timeout=timeout) ) - with TimeoutGuard(timeout) as guard: + remaining_time = max_allowed_time + + with TimeoutGuard(remaining_time) as guard: self.credentials.before_request(auth_request, method, url, request_headers) - timeout = guard.remaining_timeout + remaining_time = guard.remaining_timeout - with TimeoutGuard(timeout) as guard: + with TimeoutGuard(remaining_time) as guard: response = super(AuthorizedSession, self).request( method, url, @@ -286,7 +308,7 @@ def request(self, method, url, data=None, headers=None, timeout=None, **kwargs): timeout=timeout, **kwargs ) - timeout = guard.remaining_timeout + remaining_time = guard.remaining_timeout # If the response indicated that the credentials needed to be # refreshed, then refresh the credentials and re-attempt the @@ -305,14 +327,6 @@ def request(self, method, url, data=None, headers=None, timeout=None, **kwargs): self._max_refresh_attempts, ) - if self._refresh_timeout is not None: - if timeout is None: - timeout = self._refresh_timeout - elif isinstance(timeout, numbers.Number): - timeout = min(timeout, self._refresh_timeout) - else: - timeout = tuple(min(x, self._refresh_timeout) for x in timeout) - # Do not apply the timeout unconditionally in order to not override the # _auth_request's default timeout. auth_request = ( @@ -321,17 +335,18 @@ def request(self, method, url, data=None, headers=None, timeout=None, **kwargs): else functools.partial(self._auth_request, timeout=timeout) ) - with TimeoutGuard(timeout) as guard: + with TimeoutGuard(remaining_time) as guard: self.credentials.refresh(auth_request) - timeout = guard.remaining_timeout + remaining_time = guard.remaining_timeout # Recurse. Pass in the original headers, not our modified set, but - # do pass the adjusted timeout (i.e. the remaining time). + # do pass the adjusted max allowed time (i.e. the remaining total time). return self.request( method, url, data=data, headers=headers, + max_allowed_time=remaining_time, timeout=timeout, _credential_refresh_attempt=_credential_refresh_attempt + 1, **kwargs diff --git a/packages/google-auth/tests/transport/test_requests.py b/packages/google-auth/tests/transport/test_requests.py index 00269740f8e9..8f73d4bd5d2a 100644 --- a/packages/google-auth/tests/transport/test_requests.py +++ b/packages/google-auth/tests/transport/test_requests.py @@ -220,7 +220,25 @@ def test_request_refresh(self): assert adapter.requests[1].url == self.TEST_URL assert adapter.requests[1].headers["authorization"] == "token1" - def test_request_timeout(self, frozen_time): + def test_request_max_allowed_time_timeout_error(self, frozen_time): + tick_one_second = functools.partial(frozen_time.tick, delta=1.0) + + credentials = mock.Mock( + wraps=TimeTickCredentialsStub(time_tick=tick_one_second) + ) + adapter = TimeTickAdapterStub( + time_tick=tick_one_second, responses=[make_response(status=http_client.OK)] + ) + + authed_session = google.auth.transport.requests.AuthorizedSession(credentials) + authed_session.mount(self.TEST_URL, adapter) + + # Because a request takes a full mocked second, max_allowed_time shorter + # than that will cause a timeout error. + with pytest.raises(requests.exceptions.Timeout): + authed_session.request("GET", self.TEST_URL, max_allowed_time=0.9) + + def test_request_max_allowed_time_w_transport_timeout_no_error(self, frozen_time): tick_one_second = functools.partial(frozen_time.tick, delta=1.0) credentials = mock.Mock( @@ -237,12 +255,12 @@ def test_request_timeout(self, frozen_time): authed_session = google.auth.transport.requests.AuthorizedSession(credentials) authed_session.mount(self.TEST_URL, adapter) - # Because at least two requests have to be made, and each takes one - # second, the total timeout specified will be exceeded. - with pytest.raises(requests.exceptions.Timeout): - authed_session.request("GET", self.TEST_URL, timeout=1.9) + # A short configured transport timeout does not affect max_allowed_time. + # The latter is not adjusted to it and is only concerned with the actual + # execution time. The call below should thus not raise a timeout error. + authed_session.request("GET", self.TEST_URL, timeout=0.5, max_allowed_time=3.1) - def test_request_timeout_w_refresh_timeout(self, frozen_time): + def test_request_max_allowed_time_w_refresh_timeout_no_error(self, frozen_time): tick_one_second = functools.partial(frozen_time.tick, delta=1.0) credentials = mock.Mock( @@ -257,15 +275,17 @@ def test_request_timeout_w_refresh_timeout(self, frozen_time): ) authed_session = google.auth.transport.requests.AuthorizedSession( - credentials, refresh_timeout=1.9 + credentials, refresh_timeout=1.1 ) authed_session.mount(self.TEST_URL, adapter) - # The timeout is long, but the short refresh timeout will prevail. - with pytest.raises(requests.exceptions.Timeout): - authed_session.request("GET", self.TEST_URL, timeout=60) + # A short configured refresh timeout does not affect max_allowed_time. + # The latter is not adjusted to it and is only concerned with the actual + # execution time. The call below should thus not raise a timeout error + # (and `timeout` does not come into play either, as it's very long). + authed_session.request("GET", self.TEST_URL, timeout=60, max_allowed_time=3.1) - def test_request_timeout_w_refresh_timeout_and_tuple_timeout(self, frozen_time): + def test_request_timeout_w_refresh_timeout_timeout_error(self, frozen_time): tick_one_second = functools.partial(frozen_time.tick, delta=1.0) credentials = mock.Mock( @@ -284,7 +304,10 @@ def test_request_timeout_w_refresh_timeout_and_tuple_timeout(self, frozen_time): ) authed_session.mount(self.TEST_URL, adapter) - # The shortest timeout will prevail and cause a Timeout error, despite - # other timeouts being quite long. + # An UNAUTHORIZED response triggers a refresh (an extra request), thus + # the final request that otherwise succeeds results in a timeout error + # (all three requests together last 3 mocked seconds). with pytest.raises(requests.exceptions.Timeout): - authed_session.request("GET", self.TEST_URL, timeout=(100, 2.9)) + authed_session.request( + "GET", self.TEST_URL, timeout=60, max_allowed_time=2.9 + ) From 656f44a1d1e2c324749aacb00e1dd2950bc0c15b Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Wed, 22 Jan 2020 16:09:31 -0800 Subject: [PATCH 260/966] docs: link to docs on googleapis, edit old links (#432) * Link to docs on googleapis.dev, not readthedocs * Edit old repo links to use org googleapis, not GoogleCloudPlatform --- packages/google-auth/README.rst | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/google-auth/README.rst b/packages/google-auth/README.rst index afc47880f92c..c9c411f08ff1 100644 --- a/packages/google-auth/README.rst +++ b/packages/google-auth/README.rst @@ -1,13 +1,11 @@ Google Auth Python Library ========================== -|docs| |pypi| +|pypi| This library simplifies using Google's various server-to-server authentication mechanisms to access Google APIs. -.. |docs| image:: https://readthedocs.org/projects/google-auth/badge/?version=latest - :target: https://google-auth.readthedocs.io/en/latest/ .. |pypi| image:: https://img.shields.io/pypi/v/google-auth.svg :target: https://pypi.python.org/pypi/google-auth @@ -35,7 +33,7 @@ Python == 2.7. Python 2.7 support will be removed on January 1, 2020. Documentation ------------- -Google Auth Python Library has usage and reference documentation at `google-auth.readthedocs.io `_. +Google Auth Python Library has usage and reference documentation at https://googleapis.dev/python/google-auth/latest/index.html. Current Maintainers ------------------- @@ -55,11 +53,11 @@ Contributions to this library are always welcome and highly encouraged. See `CONTRIBUTING.rst`_ for more information on how to get started. -.. _CONTRIBUTING.rst: https://github.com/GoogleCloudPlatform/google-auth-library-python/blob/master/CONTRIBUTING.rst +.. _CONTRIBUTING.rst: https://github.com/googleapis/google-auth-library-python/blob/master/CONTRIBUTING.rst License ------- Apache 2.0 - See `the LICENSE`_ for more information. -.. _the LICENSE: https://github.com/GoogleCloudPlatform/google-auth-library-python/blob/master/LICENSE +.. _the LICENSE: https://github.com/googleapis/google-auth-library-python/blob/master/LICENSE From 57bc6e13168ab6681688fe48e4c72f72ba7f5aba Mon Sep 17 00:00:00 2001 From: Peter Lamut Date: Thu, 23 Jan 2020 22:43:59 +0000 Subject: [PATCH 261/966] feat: add non-None default timeout to AuthorizedSession.request() (#435) Closes #434. This PR adds a non-None default timeout to `AuthorizedSession.request()` to prevent requests from hanging indefinitely in a default case. Should help with googleapis/google-cloud-python#10182 --- .../google-auth/google/auth/transport/requests.py | 12 ++++++++++-- .../google-auth/tests/transport/test_requests.py | 15 +++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/google/auth/transport/requests.py b/packages/google-auth/google/auth/transport/requests.py index ce78e63a8559..1c709d4f7024 100644 --- a/packages/google-auth/google/auth/transport/requests.py +++ b/packages/google-auth/google/auth/transport/requests.py @@ -42,6 +42,8 @@ _LOGGER = logging.getLogger(__name__) +_DEFAULT_TIMEOUT = 120 # in seconds + class _Response(transport.Response): """Requests transport response adapter. @@ -141,7 +143,13 @@ def __init__(self, session=None): self.session = session def __call__( - self, url, method="GET", body=None, headers=None, timeout=120, **kwargs + self, + url, + method="GET", + body=None, + headers=None, + timeout=_DEFAULT_TIMEOUT, + **kwargs ): """Make an HTTP request using requests. @@ -246,7 +254,7 @@ def request( data=None, headers=None, max_allowed_time=None, - timeout=None, + timeout=_DEFAULT_TIMEOUT, **kwargs ): """Implementation of Requests' request. diff --git a/packages/google-auth/tests/transport/test_requests.py b/packages/google-auth/tests/transport/test_requests.py index 8f73d4bd5d2a..f0321c81b41d 100644 --- a/packages/google-auth/tests/transport/test_requests.py +++ b/packages/google-auth/tests/transport/test_requests.py @@ -177,6 +177,21 @@ def test_constructor_with_auth_request(self): assert authed_session._auth_request == auth_request + def test_request_default_timeout(self): + credentials = mock.Mock(wraps=CredentialsStub()) + response = make_response() + adapter = AdapterStub([response]) + + authed_session = google.auth.transport.requests.AuthorizedSession(credentials) + authed_session.mount(self.TEST_URL, adapter) + + patcher = mock.patch("google.auth.transport.requests.requests.Session.request") + with patcher as patched_request: + authed_session.request("GET", self.TEST_URL) + + expected_timeout = google.auth.transport.requests._DEFAULT_TIMEOUT + assert patched_request.call_args.kwargs.get("timeout") == expected_timeout + def test_request_no_refresh(self): credentials = mock.Mock(wraps=CredentialsStub()) response = make_response() From 8a03b9074cea04f7eef7a2a6a954da8436c71485 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 23 Jan 2020 15:21:49 -0800 Subject: [PATCH 262/966] chore: release 1.11.0 (#430) --- packages/google-auth/CHANGELOG.md | 8 ++++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index bc1d040d1034..1613f06876d8 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,14 @@ [1]: https://pypi.org/project/google-auth/#history +## [1.11.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.10.2...v1.11.0) (2020-01-23) + + +### Features + +* add non-None default timeout to AuthorizedSession.request() ([#435](https://www.github.com/googleapis/google-auth-library-python/issues/435)) ([d274a3a](https://www.github.com/googleapis/google-auth-library-python/commit/d274a3a2b3f913bc2cab4ca51f9c7fdef94b8f31)), closes [#434](https://www.github.com/googleapis/google-auth-library-python/issues/434) [googleapis/google-cloud-python#10182](https://www.github.com/googleapis/google-cloud-python/issues/10182) +* distinguish transport and execution time timeouts ([#424](https://www.github.com/googleapis/google-auth-library-python/issues/424)) ([52a733d](https://www.github.com/googleapis/google-auth-library-python/commit/52a733d604528fa86d05321bb74241a43aea4211)), closes [#423](https://github.com/googleapis/google-auth-library-python/issues/423) + ### [1.10.2](https://www.github.com/googleapis/google-auth-library-python/compare/v1.10.1...v1.10.2) (2020-01-18) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 708a5d164079..bbed85e5021b 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -30,7 +30,7 @@ with io.open("README.rst", "r") as fh: long_description = fh.read() -version = "1.10.2" +version = "1.11.0" setup( name="google-auth", From 15c45bbfcc4335bba7ac3ccdff3cc7acc310c3e0 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Mon, 27 Jan 2020 15:02:35 -0800 Subject: [PATCH 263/966] fix: update `_GOOGLE_OAUTH2_CERTS_URL` (#365) * Update _GOOGLE_OAUTH2_CERTS_URL OpenID Config lists jwks_uri as https://www.googleapis.com/oauth2/v3/certs https://accounts.google.com/.well-known/openid-configuration --- packages/google-auth/google/oauth2/id_token.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/google/oauth2/id_token.py b/packages/google-auth/google/oauth2/id_token.py index bc4844513a40..e80c48f61636 100644 --- a/packages/google-auth/google/oauth2/id_token.py +++ b/packages/google-auth/google/oauth2/id_token.py @@ -67,7 +67,7 @@ # The URL that provides public certificates for verifying ID tokens issued # by Google's OAuth 2.0 authorization server. -_GOOGLE_OAUTH2_CERTS_URL = "https://www.googleapis.com/oauth2/v1/certs" +_GOOGLE_OAUTH2_CERTS_URL = "https://www.googleapis.com/oauth2/v3/certs" # The URL that provides public certificates for verifying ID tokens issued # by Firebase and the Google APIs infrastructure From 3bca955390f0c104a58d09ddfb14df36cdb7f14e Mon Sep 17 00:00:00 2001 From: "C.J. Collier" Date: Thu, 13 Feb 2020 13:49:49 -0800 Subject: [PATCH 264/966] docs(copyright): correct name of company for commits after 2015 (#440) --- packages/google-auth/.kokoro/trampoline.sh | 2 +- packages/google-auth/google/__init__.py | 2 +- packages/google-auth/google/auth/__init__.py | 2 +- packages/google-auth/google/auth/_oauth2client.py | 2 +- packages/google-auth/google/auth/_service_account_info.py | 2 +- packages/google-auth/google/auth/app_engine.py | 2 +- packages/google-auth/google/auth/compute_engine/__init__.py | 2 +- packages/google-auth/google/auth/compute_engine/_metadata.py | 2 +- packages/google-auth/google/auth/compute_engine/credentials.py | 2 +- packages/google-auth/google/auth/credentials.py | 2 +- packages/google-auth/google/auth/crypt/__init__.py | 2 +- packages/google-auth/google/auth/crypt/_cryptography_rsa.py | 2 +- packages/google-auth/google/auth/crypt/_python_rsa.py | 2 +- packages/google-auth/google/auth/crypt/base.py | 2 +- packages/google-auth/google/auth/crypt/rsa.py | 2 +- packages/google-auth/google/auth/environment_vars.py | 2 +- packages/google-auth/google/auth/exceptions.py | 2 +- packages/google-auth/google/auth/iam.py | 2 +- packages/google-auth/google/auth/jwt.py | 2 +- packages/google-auth/google/auth/transport/__init__.py | 2 +- packages/google-auth/google/auth/transport/_http_client.py | 2 +- packages/google-auth/google/auth/transport/grpc.py | 2 +- packages/google-auth/google/auth/transport/requests.py | 2 +- packages/google-auth/google/auth/transport/urllib3.py | 2 +- packages/google-auth/google/oauth2/__init__.py | 2 +- packages/google-auth/google/oauth2/_client.py | 2 +- packages/google-auth/google/oauth2/credentials.py | 2 +- packages/google-auth/google/oauth2/id_token.py | 2 +- packages/google-auth/google/oauth2/service_account.py | 2 +- .../system_tests/app_engine_test_app/appengine_config.py | 2 +- packages/google-auth/system_tests/app_engine_test_app/main.py | 2 +- packages/google-auth/system_tests/conftest.py | 2 +- packages/google-auth/system_tests/noxfile.py | 2 +- packages/google-auth/system_tests/test_app_engine.py | 2 +- packages/google-auth/system_tests/test_compute_engine.py | 2 +- packages/google-auth/system_tests/test_default.py | 2 +- packages/google-auth/system_tests/test_grpc.py | 2 +- packages/google-auth/system_tests/test_oauth2_credentials.py | 2 +- packages/google-auth/system_tests/test_service_account.py | 2 +- packages/google-auth/tests/compute_engine/test__metadata.py | 2 +- packages/google-auth/tests/compute_engine/test_credentials.py | 2 +- packages/google-auth/tests/conftest.py | 2 +- packages/google-auth/tests/crypt/test__cryptography_rsa.py | 2 +- packages/google-auth/tests/crypt/test__python_rsa.py | 2 +- packages/google-auth/tests/crypt/test_crypt.py | 2 +- packages/google-auth/tests/oauth2/test__client.py | 2 +- packages/google-auth/tests/oauth2/test_credentials.py | 2 +- packages/google-auth/tests/oauth2/test_service_account.py | 2 +- packages/google-auth/tests/test__cloud_sdk.py | 2 +- packages/google-auth/tests/test__default.py | 2 +- packages/google-auth/tests/test__helpers.py | 2 +- packages/google-auth/tests/test__oauth2client.py | 2 +- packages/google-auth/tests/test__service_account_info.py | 2 +- packages/google-auth/tests/test_app_engine.py | 2 +- packages/google-auth/tests/test_credentials.py | 2 +- packages/google-auth/tests/test_iam.py | 2 +- packages/google-auth/tests/transport/compliance.py | 2 +- packages/google-auth/tests/transport/test__http_client.py | 2 +- packages/google-auth/tests/transport/test_grpc.py | 2 +- packages/google-auth/tests/transport/test_requests.py | 2 +- packages/google-auth/tests/transport/test_urllib3.py | 2 +- 61 files changed, 61 insertions(+), 61 deletions(-) diff --git a/packages/google-auth/.kokoro/trampoline.sh b/packages/google-auth/.kokoro/trampoline.sh index e8c4251f3ed4..4b4ba9f81a0a 100755 --- a/packages/google-auth/.kokoro/trampoline.sh +++ b/packages/google-auth/.kokoro/trampoline.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright 2017 Google Inc. +# Copyright 2017 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/google/__init__.py b/packages/google-auth/google/__init__.py index f36d791564fb..0d0a4c3ab273 100644 --- a/packages/google-auth/google/__init__.py +++ b/packages/google-auth/google/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/google/auth/__init__.py b/packages/google-auth/google/auth/__init__.py index 5f78cd17e108..6b4b78b29d91 100644 --- a/packages/google-auth/google/auth/__init__.py +++ b/packages/google-auth/google/auth/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/google/auth/_oauth2client.py b/packages/google-auth/google/auth/_oauth2client.py index b14a38254191..95a9876f3151 100644 --- a/packages/google-auth/google/auth/_oauth2client.py +++ b/packages/google-auth/google/auth/_oauth2client.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/google/auth/_service_account_info.py b/packages/google-auth/google/auth/_service_account_info.py index 790be92e1e25..3d340c78d40c 100644 --- a/packages/google-auth/google/auth/_service_account_info.py +++ b/packages/google-auth/google/auth/_service_account_info.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/google/auth/app_engine.py b/packages/google-auth/google/auth/app_engine.py index aec86a620663..ab69951039d0 100644 --- a/packages/google-auth/google/auth/app_engine.py +++ b/packages/google-auth/google/auth/app_engine.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/google/auth/compute_engine/__init__.py b/packages/google-auth/google/auth/compute_engine/__init__.py index 461f104ddd10..5c84234e9370 100644 --- a/packages/google-auth/google/auth/compute_engine/__init__.py +++ b/packages/google-auth/google/auth/compute_engine/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/google/auth/compute_engine/_metadata.py b/packages/google-auth/google/auth/compute_engine/_metadata.py index 30cd3d43b59f..2861192d0849 100644 --- a/packages/google-auth/google/auth/compute_engine/_metadata.py +++ b/packages/google-auth/google/auth/compute_engine/_metadata.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/google/auth/compute_engine/credentials.py b/packages/google-auth/google/auth/compute_engine/credentials.py index 0abfc053002f..fc14fcc77ec8 100644 --- a/packages/google-auth/google/auth/compute_engine/credentials.py +++ b/packages/google-auth/google/auth/compute_engine/credentials.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/google/auth/credentials.py b/packages/google-auth/google/auth/credentials.py index 81bbd0316804..3cc976b525da 100644 --- a/packages/google-auth/google/auth/credentials.py +++ b/packages/google-auth/google/auth/credentials.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/google/auth/crypt/__init__.py b/packages/google-auth/google/auth/crypt/__init__.py index bf66e710053e..39929fa0a922 100644 --- a/packages/google-auth/google/auth/crypt/__init__.py +++ b/packages/google-auth/google/auth/crypt/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/google/auth/crypt/_cryptography_rsa.py b/packages/google-auth/google/auth/crypt/_cryptography_rsa.py index 285cf5ccf0d7..e94bc681ecbd 100644 --- a/packages/google-auth/google/auth/crypt/_cryptography_rsa.py +++ b/packages/google-auth/google/auth/crypt/_cryptography_rsa.py @@ -1,4 +1,4 @@ -# Copyright 2017 Google Inc. +# Copyright 2017 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/google/auth/crypt/_python_rsa.py b/packages/google-auth/google/auth/crypt/_python_rsa.py index d53991c38348..e288c501624e 100644 --- a/packages/google-auth/google/auth/crypt/_python_rsa.py +++ b/packages/google-auth/google/auth/crypt/_python_rsa.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/google/auth/crypt/base.py b/packages/google-auth/google/auth/crypt/base.py index ceb6e9c060f7..c98d5bf64fc4 100644 --- a/packages/google-auth/google/auth/crypt/base.py +++ b/packages/google-auth/google/auth/crypt/base.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/google/auth/crypt/rsa.py b/packages/google-auth/google/auth/crypt/rsa.py index 5da1ba6087db..8b2d64c1034a 100644 --- a/packages/google-auth/google/auth/crypt/rsa.py +++ b/packages/google-auth/google/auth/crypt/rsa.py @@ -1,4 +1,4 @@ -# Copyright 2017 Google Inc. +# Copyright 2017 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/google/auth/environment_vars.py b/packages/google-auth/google/auth/environment_vars.py index 96d2ffc28752..6a596f22da90 100644 --- a/packages/google-auth/google/auth/environment_vars.py +++ b/packages/google-auth/google/auth/environment_vars.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/google/auth/exceptions.py b/packages/google-auth/google/auth/exceptions.py index 2be9fd6df403..e034c55cde6e 100644 --- a/packages/google-auth/google/auth/exceptions.py +++ b/packages/google-auth/google/auth/exceptions.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/google/auth/iam.py b/packages/google-auth/google/auth/iam.py index 0ab5b5549b40..bd0500457819 100644 --- a/packages/google-auth/google/auth/iam.py +++ b/packages/google-auth/google/auth/iam.py @@ -1,4 +1,4 @@ -# Copyright 2017 Google Inc. +# Copyright 2017 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/google/auth/jwt.py b/packages/google-auth/google/auth/jwt.py index 06e7679689c9..cdd69ac8a398 100644 --- a/packages/google-auth/google/auth/jwt.py +++ b/packages/google-auth/google/auth/jwt.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/google/auth/transport/__init__.py b/packages/google-auth/google/auth/transport/__init__.py index 53aa4ba1401f..374e7b4d7228 100644 --- a/packages/google-auth/google/auth/transport/__init__.py +++ b/packages/google-auth/google/auth/transport/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/google/auth/transport/_http_client.py b/packages/google-auth/google/auth/transport/_http_client.py index b52ca7b50dd5..c153763efa2c 100644 --- a/packages/google-auth/google/auth/transport/_http_client.py +++ b/packages/google-auth/google/auth/transport/_http_client.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/google/auth/transport/grpc.py b/packages/google-auth/google/auth/transport/grpc.py index 80f6e81ba8cf..fb90fbb4b2b8 100644 --- a/packages/google-auth/google/auth/transport/grpc.py +++ b/packages/google-auth/google/auth/transport/grpc.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/google/auth/transport/requests.py b/packages/google-auth/google/auth/transport/requests.py index 1c709d4f7024..32f59e56bb99 100644 --- a/packages/google-auth/google/auth/transport/requests.py +++ b/packages/google-auth/google/auth/transport/requests.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/google/auth/transport/urllib3.py b/packages/google-auth/google/auth/transport/urllib3.py index dbb186bb13a3..d1905e94eea9 100644 --- a/packages/google-auth/google/auth/transport/urllib3.py +++ b/packages/google-auth/google/auth/transport/urllib3.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/google/oauth2/__init__.py b/packages/google-auth/google/oauth2/__init__.py index 6d3ee7f98db8..4fb71fd1ad3b 100644 --- a/packages/google-auth/google/oauth2/__init__.py +++ b/packages/google-auth/google/oauth2/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/google/oauth2/_client.py b/packages/google-auth/google/oauth2/_client.py index 4ba31a87a9dd..4487163295f0 100644 --- a/packages/google-auth/google/oauth2/_client.py +++ b/packages/google-auth/google/oauth2/_client.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/google/oauth2/credentials.py b/packages/google-auth/google/oauth2/credentials.py index 71d2f6107e55..1adcbf6755f9 100644 --- a/packages/google-auth/google/oauth2/credentials.py +++ b/packages/google-auth/google/oauth2/credentials.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/google/oauth2/id_token.py b/packages/google-auth/google/oauth2/id_token.py index e80c48f61636..5113c992605b 100644 --- a/packages/google-auth/google/oauth2/id_token.py +++ b/packages/google-auth/google/oauth2/id_token.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/google/oauth2/service_account.py b/packages/google-auth/google/oauth2/service_account.py index 17fdd516d208..af86588d532a 100644 --- a/packages/google-auth/google/oauth2/service_account.py +++ b/packages/google-auth/google/oauth2/service_account.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/system_tests/app_engine_test_app/appengine_config.py b/packages/google-auth/system_tests/app_engine_test_app/appengine_config.py index 6339909e519d..5a832ac6fd6f 100644 --- a/packages/google-auth/system_tests/app_engine_test_app/appengine_config.py +++ b/packages/google-auth/system_tests/app_engine_test_app/appengine_config.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/system_tests/app_engine_test_app/main.py b/packages/google-auth/system_tests/app_engine_test_app/main.py index a3354acde327..33e61d07b532 100644 --- a/packages/google-auth/system_tests/app_engine_test_app/main.py +++ b/packages/google-auth/system_tests/app_engine_test_app/main.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. All Rights Reserved. +# Copyright 2016 Google LLC All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/system_tests/conftest.py b/packages/google-auth/system_tests/conftest.py index 3f089c4b4c19..1893007077de 100644 --- a/packages/google-auth/system_tests/conftest.py +++ b/packages/google-auth/system_tests/conftest.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/system_tests/noxfile.py b/packages/google-auth/system_tests/noxfile.py index 864a5be32704..e37049e52315 100644 --- a/packages/google-auth/system_tests/noxfile.py +++ b/packages/google-auth/system_tests/noxfile.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/system_tests/test_app_engine.py b/packages/google-auth/system_tests/test_app_engine.py index cdf2be436bd8..45a1989a4ad8 100644 --- a/packages/google-auth/system_tests/test_app_engine.py +++ b/packages/google-auth/system_tests/test_app_engine.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/system_tests/test_compute_engine.py b/packages/google-auth/system_tests/test_compute_engine.py index 44f162792ef1..3217c958a75b 100644 --- a/packages/google-auth/system_tests/test_compute_engine.py +++ b/packages/google-auth/system_tests/test_compute_engine.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/system_tests/test_default.py b/packages/google-auth/system_tests/test_default.py index 22213e60fa19..560ab3284924 100644 --- a/packages/google-auth/system_tests/test_default.py +++ b/packages/google-auth/system_tests/test_default.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/system_tests/test_grpc.py b/packages/google-auth/system_tests/test_grpc.py index f025fc0b7969..650fa96a40a3 100644 --- a/packages/google-auth/system_tests/test_grpc.py +++ b/packages/google-auth/system_tests/test_grpc.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/system_tests/test_oauth2_credentials.py b/packages/google-auth/system_tests/test_oauth2_credentials.py index 3ecd850788a6..663d4fc21ed0 100644 --- a/packages/google-auth/system_tests/test_oauth2_credentials.py +++ b/packages/google-auth/system_tests/test_oauth2_credentials.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/system_tests/test_service_account.py b/packages/google-auth/system_tests/test_service_account.py index 793760199935..262ce84f5387 100644 --- a/packages/google-auth/system_tests/test_service_account.py +++ b/packages/google-auth/system_tests/test_service_account.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/tests/compute_engine/test__metadata.py b/packages/google-auth/tests/compute_engine/test__metadata.py index 0898e1f4e5fb..8b5eece7a80e 100644 --- a/packages/google-auth/tests/compute_engine/test__metadata.py +++ b/packages/google-auth/tests/compute_engine/test__metadata.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/tests/compute_engine/test_credentials.py b/packages/google-auth/tests/compute_engine/test_credentials.py index ec9d13b63f29..f05a5661adcd 100644 --- a/packages/google-auth/tests/compute_engine/test_credentials.py +++ b/packages/google-auth/tests/compute_engine/test_credentials.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/tests/conftest.py b/packages/google-auth/tests/conftest.py index 2ccc132e23b1..7f9a968b79d3 100644 --- a/packages/google-auth/tests/conftest.py +++ b/packages/google-auth/tests/conftest.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/tests/crypt/test__cryptography_rsa.py b/packages/google-auth/tests/crypt/test__cryptography_rsa.py index 10f926bc7ebc..dbf07c7805b3 100644 --- a/packages/google-auth/tests/crypt/test__cryptography_rsa.py +++ b/packages/google-auth/tests/crypt/test__cryptography_rsa.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/tests/crypt/test__python_rsa.py b/packages/google-auth/tests/crypt/test__python_rsa.py index 08b9503a27a6..886ee55a2388 100644 --- a/packages/google-auth/tests/crypt/test__python_rsa.py +++ b/packages/google-auth/tests/crypt/test__python_rsa.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/tests/crypt/test_crypt.py b/packages/google-auth/tests/crypt/test_crypt.py index 16ff2e0bddc9..e80502e9be59 100644 --- a/packages/google-auth/tests/crypt/test_crypt.py +++ b/packages/google-auth/tests/crypt/test_crypt.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/tests/oauth2/test__client.py b/packages/google-auth/tests/oauth2/test__client.py index 052390aa8113..c3ae2af98864 100644 --- a/packages/google-auth/tests/oauth2/test__client.py +++ b/packages/google-auth/tests/oauth2/test__client.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/tests/oauth2/test_credentials.py b/packages/google-auth/tests/oauth2/test_credentials.py index af10f2fa45fe..bdb63e9dd82c 100644 --- a/packages/google-auth/tests/oauth2/test_credentials.py +++ b/packages/google-auth/tests/oauth2/test_credentials.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/tests/oauth2/test_service_account.py b/packages/google-auth/tests/oauth2/test_service_account.py index 0f9d4600cedd..897374a6fa56 100644 --- a/packages/google-auth/tests/oauth2/test_service_account.py +++ b/packages/google-auth/tests/oauth2/test_service_account.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/tests/test__cloud_sdk.py b/packages/google-auth/tests/test__cloud_sdk.py index c5c5c2769fb3..049ed9978275 100644 --- a/packages/google-auth/tests/test__cloud_sdk.py +++ b/packages/google-auth/tests/test__cloud_sdk.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/tests/test__default.py b/packages/google-auth/tests/test__default.py index 2c86f3ffbf56..35000b04d743 100644 --- a/packages/google-auth/tests/test__default.py +++ b/packages/google-auth/tests/test__default.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/tests/test__helpers.py b/packages/google-auth/tests/test__helpers.py index 3714af7d3306..0c0bad2d2fdc 100644 --- a/packages/google-auth/tests/test__helpers.py +++ b/packages/google-auth/tests/test__helpers.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/tests/test__oauth2client.py b/packages/google-auth/tests/test__oauth2client.py index 520f9432a958..6b1112b50e75 100644 --- a/packages/google-auth/tests/test__oauth2client.py +++ b/packages/google-auth/tests/test__oauth2client.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/tests/test__service_account_info.py b/packages/google-auth/tests/test__service_account_info.py index 4419f670b728..13b2f85a2944 100644 --- a/packages/google-auth/tests/test__service_account_info.py +++ b/packages/google-auth/tests/test__service_account_info.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/tests/test_app_engine.py b/packages/google-auth/tests/test_app_engine.py index 9559a2cd22f6..9dfdfa65be14 100644 --- a/packages/google-auth/tests/test_app_engine.py +++ b/packages/google-auth/tests/test_app_engine.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/tests/test_credentials.py b/packages/google-auth/tests/test_credentials.py index 2a89b01b6b66..16ddd9b44120 100644 --- a/packages/google-auth/tests/test_credentials.py +++ b/packages/google-auth/tests/test_credentials.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/tests/test_iam.py b/packages/google-auth/tests/test_iam.py index 52ab9bd825fb..c98a138f9e42 100644 --- a/packages/google-auth/tests/test_iam.py +++ b/packages/google-auth/tests/test_iam.py @@ -1,4 +1,4 @@ -# Copyright 2017 Google Inc. +# Copyright 2017 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/tests/transport/compliance.py b/packages/google-auth/tests/transport/compliance.py index dc7c58be5c4f..e093d761df3d 100644 --- a/packages/google-auth/tests/transport/compliance.py +++ b/packages/google-auth/tests/transport/compliance.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/tests/transport/test__http_client.py b/packages/google-auth/tests/transport/test__http_client.py index 9e7f108aea2b..c176cb2f4c69 100644 --- a/packages/google-auth/tests/transport/test__http_client.py +++ b/packages/google-auth/tests/transport/test__http_client.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/tests/transport/test_grpc.py b/packages/google-auth/tests/transport/test_grpc.py index ca12385dd7ba..857c32bb9ec2 100644 --- a/packages/google-auth/tests/transport/test_grpc.py +++ b/packages/google-auth/tests/transport/test_grpc.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/tests/transport/test_requests.py b/packages/google-auth/tests/transport/test_requests.py index f0321c81b41d..9aafd88b1f49 100644 --- a/packages/google-auth/tests/transport/test_requests.py +++ b/packages/google-auth/tests/transport/test_requests.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/tests/transport/test_urllib3.py b/packages/google-auth/tests/transport/test_urllib3.py index 1d7ce5a0ed9f..8a307332acaf 100644 --- a/packages/google-auth/tests/transport/test_urllib3.py +++ b/packages/google-auth/tests/transport/test_urllib3.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google Inc. +# Copyright 2016 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. From 03a9b4ac5985a1cf7ce91023ed9b9e026d1fcebe Mon Sep 17 00:00:00 2001 From: Brent Shaffer Date: Thu, 13 Feb 2020 14:11:34 -0800 Subject: [PATCH 265/966] fix: compute engine id token credentials "with_target_audience" method (#438) Co-authored-by: Lorenzo Migliorino <50544028+lmiglio@users.noreply.github.com> Co-authored-by: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> --- packages/google-auth/.gitignore | 3 + .../google/auth/compute_engine/credentials.py | 21 +++-- packages/google-auth/noxfile.py | 1 + .../tests/compute_engine/test_credentials.py | 82 ++++++++++++++++++- 4 files changed, 100 insertions(+), 7 deletions(-) diff --git a/packages/google-auth/.gitignore b/packages/google-auth/.gitignore index 88a8b8bc4f5d..598752fa60c0 100644 --- a/packages/google-auth/.gitignore +++ b/packages/google-auth/.gitignore @@ -31,8 +31,11 @@ tests/data/user-key.json # PyCharm configuration: .idea +venv/ # Generated files pylintrc pylintrc.test pytype_output/ + +.python-version diff --git a/packages/google-auth/google/auth/compute_engine/credentials.py b/packages/google-auth/google/auth/compute_engine/credentials.py index fc14fcc77ec8..e35907abcebc 100644 --- a/packages/google-auth/google/auth/compute_engine/credentials.py +++ b/packages/google-auth/google/auth/compute_engine/credentials.py @@ -136,6 +136,7 @@ def __init__( token_uri=_DEFAULT_TOKEN_URI, additional_claims=None, service_account_email=None, + signer=None, ): """ Args: @@ -150,6 +151,9 @@ def __init__( service_account_email (str): Optional explicit service account to use to sign JWT tokens. By default, this is the default GCE service account. + signer (google.auth.crypt.Signer): The signer used to sign JWTs. + In case the signer is specified, the request argument will be + ignored. """ super(IDTokenCredentials, self).__init__() @@ -158,11 +162,13 @@ def __init__( service_account_email = sa_info["email"] self._service_account_email = service_account_email - self._signer = iam.Signer( - request=request, - credentials=Credentials(), - service_account_email=service_account_email, - ) + if signer is None: + signer = iam.Signer( + request=request, + credentials=Credentials(), + service_account_email=service_account_email, + ) + self._signer = signer self._token_uri = token_uri self._target_audience = target_audience @@ -182,12 +188,15 @@ def with_target_audience(self, target_audience): google.auth.service_account.IDTokenCredentials: A new credentials instance. """ + # since the signer is already instantiated, + # the request is not needed return self.__class__( - self._signer, + None, service_account_email=self._service_account_email, token_uri=self._token_uri, target_audience=target_audience, additional_claims=self._additional_claims.copy(), + signer=self.signer, ) def _make_authorization_grant_assertion(self): diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index e170ee51d5a9..d75361f73dfd 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -25,6 +25,7 @@ "requests", "urllib3", "cryptography", + "responses", "grpcio", ] BLACK_VERSION = "black==19.3b0" diff --git a/packages/google-auth/tests/compute_engine/test_credentials.py b/packages/google-auth/tests/compute_engine/test_credentials.py index f05a5661adcd..b861984e046a 100644 --- a/packages/google-auth/tests/compute_engine/test_credentials.py +++ b/packages/google-auth/tests/compute_engine/test_credentials.py @@ -11,17 +11,19 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - +import base64 import datetime import mock import pytest +import responses from google.auth import _helpers from google.auth import exceptions from google.auth import jwt from google.auth import transport from google.auth.compute_engine import credentials +from google.auth.transport import requests class TestCredentials(object): @@ -270,6 +272,84 @@ def test_with_target_audience(self, sign, get, utcnow): "target_audience": "https://actually.not", } + # Check that the signer have been initialized with a Request object + assert isinstance(self.credentials._signer._request, transport.Request) + + @responses.activate + def test_with_target_audience_integration(self): + """ Test that it is possible to refresh credentials + generated from `with_target_audience`. + + Instead of mocking the methods, the HTTP responses + have been mocked. + """ + + # mock information about credentials + responses.add( + responses.GET, + "http://metadata.google.internal/computeMetadata/v1/instance/" + "service-accounts/default/?recursive=true", + status=200, + content_type="application/json", + json={ + "scopes": "email", + "email": "service-account@example.com", + "aliases": ["default"], + }, + ) + + # mock token for credentials + responses.add( + responses.GET, + "http://metadata.google.internal/computeMetadata/v1/instance/" + "service-accounts/service-account@example.com/token", + status=200, + content_type="application/json", + json={ + "access_token": "some-token", + "expires_in": 3210, + "token_type": "Bearer", + }, + ) + + # mock sign blob endpoint + signature = base64.b64encode(b"some-signature").decode("utf-8") + responses.add( + responses.POST, + "https://iam.googleapis.com/v1/projects/-/serviceAccounts/" + "service-account@example.com:signBlob?alt=json", + status=200, + content_type="application/json", + json={"keyId": "some-key-id", "signature": signature}, + ) + + id_token = "{}.{}.{}".format( + base64.b64encode(b'{"some":"some"}').decode("utf-8"), + base64.b64encode(b'{"exp": 3210}').decode("utf-8"), + base64.b64encode(b"token").decode("utf-8"), + ) + + # mock id token endpoint + responses.add( + responses.POST, + "https://www.googleapis.com/oauth2/v4/token", + status=200, + content_type="application/json", + json={"id_token": id_token, "expiry": 3210}, + ) + + self.credentials = credentials.IDTokenCredentials( + request=requests.Request(), + service_account_email="service-account@example.com", + target_audience="https://audience.com", + ) + + self.credentials = self.credentials.with_target_audience("https://actually.not") + + self.credentials.refresh(requests.Request()) + + assert self.credentials.token is not None + @mock.patch( "google.auth._helpers.utcnow", return_value=datetime.datetime.utcfromtimestamp(0), From ed79a49d10e6132bf06c3f656e80bc095d4511f5 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 13 Feb 2020 14:31:44 -0800 Subject: [PATCH 266/966] chore: release 1.11.1 (#437) --- packages/google-auth/CHANGELOG.md | 8 ++++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 1613f06876d8..cc9919eade89 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,14 @@ [1]: https://pypi.org/project/google-auth/#history +### [1.11.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.11.0...v1.11.1) (2020-02-13) + + +### Bug Fixes + +* compute engine id token credentials "with_target_audience" method ([#438](https://www.github.com/googleapis/google-auth-library-python/issues/438)) ([bc0ec93](https://www.github.com/googleapis/google-auth-library-python/commit/bc0ec93dc66fdcaa6a82222386623fa44f24ddfe)) +* update `_GOOGLE_OAUTH2_CERTS_URL` ([#365](https://www.github.com/googleapis/google-auth-library-python/issues/365)) ([054db75](https://www.github.com/googleapis/google-auth-library-python/commit/054db75734756b0e82e7984ca07fa80025edc908)) + ## [1.11.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.10.2...v1.11.0) (2020-01-23) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index bbed85e5021b..09ef6d14af25 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -30,7 +30,7 @@ with io.open("README.rst", "r") as fh: long_description = fh.read() -version = "1.11.0" +version = "1.11.1" setup( name="google-auth", From 9996ae3451c6c14f43d618faa7943da58050d809 Mon Sep 17 00:00:00 2001 From: k-yone <9046344+k-yone@users.noreply.github.com> Date: Sat, 15 Feb 2020 02:44:04 +0900 Subject: [PATCH 267/966] Revert "fix: update `_GOOGLE_OAUTH2_CERTS_URL` (#365)" (#444) This reverts commit 054db75734756b0e82e7984ca07fa80025edc908. --- packages/google-auth/google/oauth2/id_token.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/google/oauth2/id_token.py b/packages/google-auth/google/oauth2/id_token.py index 5113c992605b..1dbfb20add8f 100644 --- a/packages/google-auth/google/oauth2/id_token.py +++ b/packages/google-auth/google/oauth2/id_token.py @@ -67,7 +67,7 @@ # The URL that provides public certificates for verifying ID tokens issued # by Google's OAuth 2.0 authorization server. -_GOOGLE_OAUTH2_CERTS_URL = "https://www.googleapis.com/oauth2/v3/certs" +_GOOGLE_OAUTH2_CERTS_URL = "https://www.googleapis.com/oauth2/v1/certs" # The URL that provides public certificates for verifying ID tokens issued # by Firebase and the Google APIs infrastructure From c4344d47177da8be4973c4c807c14ac919bbc9cb Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Fri, 14 Feb 2020 09:59:50 -0800 Subject: [PATCH 268/966] chore: release 1.11.2 (#446) --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index cc9919eade89..c69a8be10f58 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +### [1.11.2](https://www.github.com/googleapis/google-auth-library-python/compare/v1.11.1...v1.11.2) (2020-02-14) + + +### Reverts + +* Revert "fix: update `_GOOGLE_OAUTH2_CERTS_URL` (#365)" (#444) ([901c259](https://www.github.com/googleapis/google-auth-library-python/commit/901c259b1764f5a305a542cbae14d926ba7a57db)), closes [#365](https://www.github.com/googleapis/google-auth-library-python/issues/365) [#444](https://www.github.com/googleapis/google-auth-library-python/issues/444) + ### [1.11.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.11.0...v1.11.1) (2020-02-13) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 09ef6d14af25..f0a20aa77ee8 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -30,7 +30,7 @@ with io.open("README.rst", "r") as fh: long_description = fh.read() -version = "1.11.1" +version = "1.11.2" setup( name="google-auth", From a26a200a756e77f886da15a7371cab812cf28ef2 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Fri, 6 Mar 2020 13:32:21 -0800 Subject: [PATCH 269/966] fix: fix the scopes so test can pass for a local run (#450) --- .../google-auth/system_tests/test_oauth2_credentials.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/system_tests/test_oauth2_credentials.py b/packages/google-auth/system_tests/test_oauth2_credentials.py index 663d4fc21ed0..908db3145c56 100644 --- a/packages/google-auth/system_tests/test_oauth2_credentials.py +++ b/packages/google-auth/system_tests/test_oauth2_credentials.py @@ -42,10 +42,14 @@ def test_refresh(authorized_user_file, http_request, token_info): # Canonical list of scopes at https://cloud.google.com/sdk/gcloud/reference/auth/application-default/login # or do `gcloud auth application-defaut login --help` - assert set(info_scopes) == set( + canonical_scopes = set( [ "https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/cloud-platform", "openid", ] ) + # When running the test locally, we always have an additional "accounts.reauth" scope. + canonical_scopes_with_reauth = canonical_scopes.copy() + canonical_scopes_with_reauth.add("https://www.googleapis.com/auth/accounts.reauth") + assert set(info_scopes) == canonical_scopes or set(info_scopes) == canonical_scopes_with_reauth From 0cdb723df7622dab0fe65539212e99b6c012aafe Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Fri, 13 Mar 2020 13:21:18 -0700 Subject: [PATCH 270/966] fix: only add IAM scope to credentials that can change scopes (#451) --- packages/google-auth/CONTRIBUTING.rst | 50 +++++++-- .../google/auth/impersonated_credentials.py | 6 +- packages/google-auth/system_tests/conftest.py | 9 ++ packages/google-auth/system_tests/noxfile.py | 10 +- .../google-auth/system_tests/secrets.tar.enc | Bin 10323 -> 10323 bytes .../test_impersonated_credentials.py | 99 ++++++++++++++++++ .../tests/test_impersonated_credentials.py | 18 +++- 7 files changed, 179 insertions(+), 13 deletions(-) create mode 100644 packages/google-auth/system_tests/test_impersonated_credentials.py diff --git a/packages/google-auth/CONTRIBUTING.rst b/packages/google-auth/CONTRIBUTING.rst index f95b1f1dc45a..bd92ca8d4ece 100644 --- a/packages/google-auth/CONTRIBUTING.rst +++ b/packages/google-auth/CONTRIBUTING.rst @@ -43,21 +43,27 @@ To run a single session, specify it with ``nox -s``:: $ nox -f system_tests/noxfile.py -s service_account + +Project and Credentials Setup +------------------------------- + +Enable the IAM Service Account Credentials API on the project. + To run system tests locally, you will need to set up a data directory :: $ mkdir system_tests/data -Add a service account file and authorized user file to the data directory. -Your directory should look like this :: +Your directory should look like this. Follow the instructions below for creating each file. :: system_tests/ data/ - service_account.json authorized_user.json + impersonated_service_account.json + service_account.json -The files must be named exactly ``service_account.json`` -and ``authorized_user.json``. See `Creating and Managing Service Account Keys`_ for how to -obtain a service account. + +``authorized_user.json`` +~~~~~~~~~~~~~~~~~~~~~~~~ Use the `gcloud CLI`_ to get an authorized user file :: @@ -65,15 +71,41 @@ Use the `gcloud CLI`_ to get an authorized user file :: You will see something like:: - Credentials saved to file: [/usr/local/home/.config/gcloud/application_default_credentials.json]``` + Credentials saved to file: [/usr/local/home/.config/gcloud/application_default_credentials.json] Copy the contents of the file to ``authorized_user.json``. -.. _Creating and Managing Service Account Keys: https://cloud.google.com/iam/docs/creating-managing-service-account-keys +Open the IAM page of the Google Cloud Console. Grant the user the `Service Account Token Creator Role`. +This will allow the user to impersonate service accounts on the project. + .. _gcloud CLI: https://cloud.google.com/sdk/gcloud/ + +``service_account.json`` +~~~~~~~~~~~~~~~~~~~~~~~~ + +Follow `Creating and Managing Service Account Keys`_ to create a service account. + +Copy the credentials file to ``service_account.json``. + +Grant the account associated with ``service_account.json`` the following roles. + +- App Engine Admin (for App Engine tests) +- Service Account Token Creator (for impersonated credentials tests) +- Pub/Sub Viewer (for gRPC tests) +- Storage Object Viewer (for impersonated credentials tests) + +``impersonated_service_account.json`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Follow `Creating and Managing Service Account Keys`_ to create a service account. + +Copy the credentials file to ``impersonated_service_account.json``. + +.. _Creating and Managing Service Account Keys: https://cloud.google.com/iam/docs/creating-managing-service-account-keys + App Engine System Tests -^^^^^^^^^^^^^^^^^^^^^^^ +~~~~~~~~~~~~~~~~~~~~~~~~ To run the App Engine tests, you wil need to deploy a default App Engine service. If you already have a default service associated with your project, you can skip this step. diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index bc7031e78c31..1bb6b826861d 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -205,7 +205,11 @@ def __init__( super(Credentials, self).__init__() self._source_credentials = copy.copy(source_credentials) - self._source_credentials._scopes = _IAM_SCOPE + # Service account source credentials must have the _IAM_SCOPE + # added to refresh correctly. User credentials cannot have + # their original scopes modified. + if isinstance(self._source_credentials, credentials.Scoped): + self._source_credentials = self._source_credentials.with_scopes(_IAM_SCOPE) self._target_principal = target_principal self._target_scopes = target_scopes self._delegates = delegates diff --git a/packages/google-auth/system_tests/conftest.py b/packages/google-auth/system_tests/conftest.py index 1893007077de..02de846642af 100644 --- a/packages/google-auth/system_tests/conftest.py +++ b/packages/google-auth/system_tests/conftest.py @@ -25,6 +25,9 @@ HERE = os.path.dirname(__file__) DATA_DIR = os.path.join(HERE, "data") +IMPERSONATED_SERVICE_ACCOUNT_FILE = os.path.join( + DATA_DIR, "impersonated_service_account.json" +) SERVICE_ACCOUNT_FILE = os.path.join(DATA_DIR, "service_account.json") AUTHORIZED_USER_FILE = os.path.join(DATA_DIR, "authorized_user.json") URLLIB3_HTTP = urllib3.PoolManager(retries=False) @@ -39,6 +42,12 @@ def service_account_file(): yield SERVICE_ACCOUNT_FILE +@pytest.fixture +def impersonated_service_account_file(): + """The full path to a valid service account key file.""" + yield IMPERSONATED_SERVICE_ACCOUNT_FILE + + @pytest.fixture def authorized_user_file(): """The full path to a valid authorized user file.""" diff --git a/packages/google-auth/system_tests/noxfile.py b/packages/google-auth/system_tests/noxfile.py index e37049e52315..811063223871 100644 --- a/packages/google-auth/system_tests/noxfile.py +++ b/packages/google-auth/system_tests/noxfile.py @@ -170,7 +170,8 @@ def configure_cloud_sdk(session, application_default_credentials, project=False) # Test sesssions TEST_DEPENDENCIES = ["pytest", "requests"] -PYTHON_VERSIONS=['2.7', '3.7'] +PYTHON_VERSIONS = ["2.7", "3.7"] + @nox.session(python=PYTHON_VERSIONS) def service_account(session): @@ -186,6 +187,13 @@ def oauth2_credentials(session): session.run("pytest", "test_oauth2_credentials.py") +@nox.session(python=PYTHON_VERSIONS) +def impersonated_credentials(session): + session.install(*TEST_DEPENDENCIES) + session.install(LIBRARY_DIR) + session.run("pytest", "test_impersonated_credentials.py") + + @nox.session(python=PYTHON_VERSIONS) def default_explicit_service_account(session): session.env[EXPLICIT_CREDENTIALS_ENV] = SERVICE_ACCOUNT_FILE diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 1106f8a9154dada1eda23da37373f78eeb683bd9..af10c7134ad07e3e574392426671a0c48628a658 100644 GIT binary patch literal 10323 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 literal 10323 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 ln#y2j*5ExtA`Jil diff --git a/packages/google-auth/system_tests/test_impersonated_credentials.py b/packages/google-auth/system_tests/test_impersonated_credentials.py new file mode 100644 index 000000000000..6689e8943b2a --- /dev/null +++ b/packages/google-auth/system_tests/test_impersonated_credentials.py @@ -0,0 +1,99 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import pytest + +import google.oauth2.credentials +from google.oauth2 import service_account +import google.auth.impersonated_credentials +from google.auth import _helpers + + +GOOGLE_OAUTH2_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token" + + +@pytest.fixture +def service_account_credentials(service_account_file): + yield service_account.Credentials.from_service_account_file(service_account_file) + + +@pytest.fixture +def impersonated_service_account_credentials(impersonated_service_account_file): + yield service_account.Credentials.from_service_account_file( + impersonated_service_account_file + ) + + +def test_refresh_with_user_credentials_as_source( + authorized_user_file, + impersonated_service_account_credentials, + http_request, + token_info, +): + with open(authorized_user_file, "r") as fh: + info = json.load(fh) + + source_credentials = google.oauth2.credentials.Credentials( + None, + refresh_token=info["refresh_token"], + token_uri=GOOGLE_OAUTH2_TOKEN_ENDPOINT, + client_id=info["client_id"], + client_secret=info["client_secret"], + # The source credential needs this scope for the generateAccessToken request + # The user must also have `Service Account Token Creator` on the project + # that owns the impersonated service account. + # See https://cloud.google.com/iam/docs/creating-short-lived-service-account-credentials + scopes=["https://www.googleapis.com/auth/cloud-platform"], + ) + + source_credentials.refresh(http_request) + + target_scopes = [ + "https://www.googleapis.com/auth/devstorage.read_only", + "https://www.googleapis.com/auth/analytics", + ] + target_credentials = google.auth.impersonated_credentials.Credentials( + source_credentials=source_credentials, + target_principal=impersonated_service_account_credentials.service_account_email, + target_scopes=target_scopes, + lifetime=100, + ) + + target_credentials.refresh(http_request) + assert target_credentials.token + + +def test_refresh_with_service_account_credentials_as_source( + http_request, + service_account_credentials, + impersonated_service_account_credentials, + token_info, +): + source_credentials = service_account_credentials.with_scopes(["email"]) + source_credentials.refresh(http_request) + assert source_credentials.token + + target_scopes = [ + "https://www.googleapis.com/auth/devstorage.read_only", + "https://www.googleapis.com/auth/analytics", + ] + target_credentials = google.auth.impersonated_credentials.Credentials( + source_credentials=source_credentials, + target_principal=impersonated_service_account_credentials.service_account_email, + target_scopes=target_scopes, + ) + + target_credentials.refresh(http_request) + assert target_credentials.token diff --git a/packages/google-auth/tests/test_impersonated_credentials.py b/packages/google-auth/tests/test_impersonated_credentials.py index 1cfcc7c6c1aa..31075ca84922 100644 --- a/packages/google-auth/tests/test_impersonated_credentials.py +++ b/packages/google-auth/tests/test_impersonated_credentials.py @@ -26,6 +26,7 @@ from google.auth import impersonated_credentials from google.auth import transport from google.auth.impersonated_credentials import Credentials +from google.oauth2 import credentials from google.oauth2 import service_account DATA_DIR = os.path.join(os.path.dirname(__file__), "", "data") @@ -102,17 +103,30 @@ class TestImpersonatedCredentials(object): SOURCE_CREDENTIALS = service_account.Credentials( SIGNER, SERVICE_ACCOUNT_EMAIL, TOKEN_URI ) + USER_SOURCE_CREDENTIALS = credentials.Credentials(token="ABCDE") - def make_credentials(self, lifetime=LIFETIME, target_principal=TARGET_PRINCIPAL): + def make_credentials( + self, + source_credentials=SOURCE_CREDENTIALS, + lifetime=LIFETIME, + target_principal=TARGET_PRINCIPAL, + ): return Credentials( - source_credentials=self.SOURCE_CREDENTIALS, + source_credentials=source_credentials, target_principal=target_principal, target_scopes=self.TARGET_SCOPES, delegates=self.DELEGATES, lifetime=lifetime, ) + def test_make_from_user_credentials(self): + credentials = self.make_credentials( + source_credentials=self.USER_SOURCE_CREDENTIALS + ) + assert not credentials.valid + assert credentials.expired + def test_default_state(self): credentials = self.make_credentials() assert not credentials.valid From 046fcf764e0121cf383e906266a07e20fb0c7c05 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Fri, 13 Mar 2020 13:38:21 -0700 Subject: [PATCH 271/966] chore: release 1.11.3 (#452) --- packages/google-auth/CHANGELOG.md | 8 ++++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index c69a8be10f58..96a15f463b5d 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,14 @@ [1]: https://pypi.org/project/google-auth/#history +### [1.11.3](https://www.github.com/googleapis/google-auth-library-python/compare/v1.11.2...v1.11.3) (2020-03-13) + + +### Bug Fixes + +* fix the scopes so test can pass for a local run ([#450](https://www.github.com/googleapis/google-auth-library-python/issues/450)) ([b2dd77f](https://www.github.com/googleapis/google-auth-library-python/commit/b2dd77fe4a538e1d165fc9d859c9a299f6832cda)) +* only add IAM scope to credentials that can change scopes ([#451](https://www.github.com/googleapis/google-auth-library-python/issues/451)) ([82e224b](https://www.github.com/googleapis/google-auth-library-python/commit/82e224b0854950a5607cd028edbcbcdc3e9e6505)) + ### [1.11.2](https://www.github.com/googleapis/google-auth-library-python/compare/v1.11.1...v1.11.2) (2020-02-14) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index f0a20aa77ee8..f80dc1de7608 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -30,7 +30,7 @@ with io.open("README.rst", "r") as fh: long_description = fh.read() -version = "1.11.2" +version = "1.11.3" setup( name="google-auth", From b5a9d0e8cfabc8326d8d8f19e0f3cfc27e92fb03 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Thu, 19 Mar 2020 09:54:19 -0700 Subject: [PATCH 272/966] feat: add SslCredentials class for mTLS ADC (#448) feat: add SslCredentials class for mTLS ADC (linux) --- .../google/auth/transport/_mtls_helper.py | 116 +++++++ .../google-auth/google/auth/transport/grpc.py | 188 ++++++++++- .../tests/data/context_aware_metadata.json | 6 + .../tests/transport/test__mtls_helper.py | 177 +++++++++++ .../google-auth/tests/transport/test_grpc.py | 292 ++++++++++++++---- 5 files changed, 723 insertions(+), 56 deletions(-) create mode 100644 packages/google-auth/google/auth/transport/_mtls_helper.py create mode 100644 packages/google-auth/tests/data/context_aware_metadata.json create mode 100644 packages/google-auth/tests/transport/test__mtls_helper.py diff --git a/packages/google-auth/google/auth/transport/_mtls_helper.py b/packages/google-auth/google/auth/transport/_mtls_helper.py new file mode 100644 index 000000000000..1ce9fa554ff3 --- /dev/null +++ b/packages/google-auth/google/auth/transport/_mtls_helper.py @@ -0,0 +1,116 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Helper functions for getting mTLS cert and key, for internal use only.""" + +import json +import logging +from os import path +import re +import subprocess + +CONTEXT_AWARE_METADATA_PATH = "~/.secureConnect/context_aware_metadata.json" +_CERT_PROVIDER_COMMAND = "cert_provider_command" +_CERT_REGEX = re.compile( + b"-----BEGIN CERTIFICATE-----.+-----END CERTIFICATE-----\r?\n?", re.DOTALL +) + +# support various format of key files, e.g. +# "-----BEGIN PRIVATE KEY-----...", +# "-----BEGIN EC PRIVATE KEY-----...", +# "-----BEGIN RSA PRIVATE KEY-----..." +_KEY_REGEX = re.compile( + b"-----BEGIN [A-Z ]*PRIVATE KEY-----.+-----END [A-Z ]*PRIVATE KEY-----\r?\n?", + re.DOTALL, +) + +_LOGGER = logging.getLogger(__name__) + + +def _check_dca_metadata_path(metadata_path): + """Checks for context aware metadata. If it exists, returns the absolute path; + otherwise returns None. + + Args: + metadata_path (str): context aware metadata path. + + Returns: + str: absolute path if exists and None otherwise. + """ + metadata_path = path.expanduser(metadata_path) + if not path.exists(metadata_path): + _LOGGER.debug("%s is not found, skip client SSL authentication.", metadata_path) + return None + return metadata_path + + +def _read_dca_metadata_file(metadata_path): + """Loads context aware metadata from the given path. + + Args: + metadata_path (str): context aware metadata path. + + Returns: + Dict[str, str]: The metadata. + + Raises: + ValueError: If failed to parse metadata as JSON. + """ + with open(metadata_path) as f: + metadata = json.load(f) + + return metadata + + +def get_client_ssl_credentials(metadata_json): + """Returns the client side mTLS cert and key. + + Args: + metadata_json (Dict[str, str]): metadata JSON file which contains the cert + provider command. + + Returns: + Tuple[bytes, bytes]: client certificate and key, both in PEM format. + + Raises: + OSError: If the cert provider command failed to run. + RuntimeError: If the cert provider command has a runtime error. + ValueError: If the metadata json file doesn't contain the cert provider command or if the command doesn't produce both the client certificate and client key. + """ + # TODO: implement an in-memory cache of cert and key so we don't have to + # run cert provider command every time. + + # Check the cert provider command existence in the metadata json file. + if _CERT_PROVIDER_COMMAND not in metadata_json: + raise ValueError("Cert provider command is not found") + + # Execute the command. It throws OsError in case of system failure. + command = metadata_json[_CERT_PROVIDER_COMMAND] + process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdout, stderr = process.communicate() + + # Check cert provider command execution error. + if process.returncode != 0: + raise RuntimeError( + "Cert provider command returns non-zero status code %s" % process.returncode + ) + + # Extract certificate (chain) and key. + cert_match = re.findall(_CERT_REGEX, stdout) + if len(cert_match) != 1: + raise ValueError("Client SSL certificate is missing or invalid") + key_match = re.findall(_KEY_REGEX, stdout) + if len(key_match) != 1: + raise ValueError("Client SSL key is missing or invalid") + return cert_match[0], key_match[0] diff --git a/packages/google-auth/google/auth/transport/grpc.py b/packages/google-auth/google/auth/transport/grpc.py index fb90fbb4b2b8..ca387392eb59 100644 --- a/packages/google-auth/google/auth/transport/grpc.py +++ b/packages/google-auth/google/auth/transport/grpc.py @@ -17,9 +17,12 @@ from __future__ import absolute_import from concurrent import futures +import logging import six +from google.auth.transport import _mtls_helper + try: import grpc except ImportError as caught_exc: # pragma: NO COVER @@ -31,6 +34,8 @@ caught_exc, ) +_LOGGER = logging.getLogger(__name__) + class AuthMetadataPlugin(grpc.AuthMetadataPlugin): """A `gRPC AuthMetadataPlugin`_ that inserts the credentials into each @@ -92,7 +97,12 @@ def __del__(self): def secure_authorized_channel( - credentials, request, target, ssl_credentials=None, **kwargs + credentials, + request, + target, + ssl_credentials=None, + client_cert_callback=None, + **kwargs ): """Creates a secure authorized gRPC channel. @@ -114,11 +124,86 @@ def secure_authorized_channel( # Create a channel. channel = google.auth.transport.grpc.secure_authorized_channel( - credentials, 'speech.googleapis.com:443', request) + credentials, regular_endpoint, request, + ssl_credentials=grpc.ssl_channel_credentials()) # Use the channel to create a stub. cloud_speech.create_Speech_stub(channel) + Usage: + + There are actually a couple of options to create a channel, depending on if + you want to create a regular or mutual TLS channel. + + First let's list the endpoints (regular vs mutual TLS) to choose from:: + + regular_endpoint = 'speech.googleapis.com:443' + mtls_endpoint = 'speech.mtls.googleapis.com:443' + + Option 1: create a regular (non-mutual) TLS channel by explicitly setting + the ssl_credentials:: + + regular_ssl_credentials = grpc.ssl_channel_credentials() + + channel = google.auth.transport.grpc.secure_authorized_channel( + credentials, regular_endpoint, request, + ssl_credentials=regular_ssl_credentials) + + Option 2: create a mutual TLS channel by calling a callback which returns + the client side certificate and the key:: + + def my_client_cert_callback(): + code_to_load_client_cert_and_key() + if loaded: + return (pem_cert_bytes, pem_key_bytes) + raise MyClientCertFailureException() + + try: + channel = google.auth.transport.grpc.secure_authorized_channel( + credentials, mtls_endpoint, request, + client_cert_callback=my_client_cert_callback) + except MyClientCertFailureException: + # handle the exception + + Option 3: use application default SSL credentials. It searches and uses + the command in a context aware metadata file, which is available on devices + with endpoint verification support. + See https://cloud.google.com/endpoint-verification/docs/overview:: + + try: + default_ssl_credentials = SslCredentials() + except: + # Exception can be raised if the context aware metadata is malformed. + # See :class:`SslCredentials` for the possible exceptions. + + # Choose the endpoint based on the SSL credentials type. + if default_ssl_credentials.is_mtls: + endpoint_to_use = mtls_endpoint + else: + endpoint_to_use = regular_endpoint + channel = google.auth.transport.grpc.secure_authorized_channel( + credentials, endpoint_to_use, request, + ssl_credentials=default_ssl_credentials) + + Option 4: not setting ssl_credentials and client_cert_callback. For devices + without endpoint verification support, a regular TLS channel is created; + otherwise, a mutual TLS channel is created, however, the call should be + wrapped in a try/except block in case of malformed context aware metadata. + + The following code uses regular_endpoint, it works the same no matter the + created channle is regular or mutual TLS. Regular endpoint ignores client + certificate and key:: + + channel = google.auth.transport.grpc.secure_authorized_channel( + credentials, regular_endpoint, request) + + The following code uses mtls_endpoint, if the created channle is regular, + and API mtls_endpoint is confgured to require client SSL credentials, API + calls using this channel will be rejected:: + + channel = google.auth.transport.grpc.secure_authorized_channel( + credentials, mtls_endpoint, request) + Args: credentials (google.auth.credentials.Credentials): The credentials to add to requests. @@ -129,10 +214,33 @@ def secure_authorized_channel( target (str): The host and port of the service. ssl_credentials (grpc.ChannelCredentials): Optional SSL channel credentials. This can be used to specify different certificates. + This argument is mutually exclusive with client_cert_callback; + providing both will raise an exception. + If ssl_credentials and client_cert_callback are None, application + default SSL credentials will be used. + client_cert_callback (Callable[[], (bytes, bytes)]): Optional + callback function to obtain client certicate and key for mutual TLS + connection. This argument is mutually exclusive with + ssl_credentials; providing both will raise an exception. + If ssl_credentials and client_cert_callback are None, application + default SSL credentials will be used. kwargs: Additional arguments to pass to :func:`grpc.secure_channel`. Returns: grpc.Channel: The created gRPC channel. + + Raises: + OSError: If the cert provider command launch fails during the application + default SSL credentials loading process on devices with endpoint + verification support. + RuntimeError: If the cert provider command has a runtime error during the + application default SSL credentials loading process on devices with + endpoint verification support. + ValueError: + If the context aware metadata file is malformed or if the cert provider + command doesn't produce both client certificate and key during the + application default SSL credentials loading process on devices with + endpoint verification support. """ # Create the metadata plugin for inserting the authorization header. metadata_plugin = AuthMetadataPlugin(credentials, request) @@ -140,8 +248,24 @@ def secure_authorized_channel( # Create a set of grpc.CallCredentials using the metadata plugin. google_auth_credentials = grpc.metadata_call_credentials(metadata_plugin) - if ssl_credentials is None: - ssl_credentials = grpc.ssl_channel_credentials() + if ssl_credentials and client_cert_callback: + raise ValueError( + "Received both ssl_credentials and client_cert_callback; " + "these are mutually exclusive." + ) + + # If SSL credentials are not explicitly set, try client_cert_callback and ADC. + if not ssl_credentials: + if client_cert_callback: + # Use the callback if provided. + cert, key = client_cert_callback() + ssl_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + else: + # Use application default SSL credentials. + adc_ssl_credentils = SslCredentials() + ssl_credentials = adc_ssl_credentils.ssl_credentials # Combine the ssl credentials and the authorization credentials. composite_credentials = grpc.composite_channel_credentials( @@ -149,3 +273,59 @@ def secure_authorized_channel( ) return grpc.secure_channel(target, composite_credentials, **kwargs) + + +class SslCredentials: + """Class for application default SSL credentials. + + For devices with endpoint verification support, a device certificate will be + automatically loaded and mutual TLS will be established. + See https://cloud.google.com/endpoint-verification/docs/overview. + """ + + def __init__(self): + # Load client SSL credentials. + self._context_aware_metadata_path = _mtls_helper._check_dca_metadata_path( + _mtls_helper.CONTEXT_AWARE_METADATA_PATH + ) + if self._context_aware_metadata_path: + self._is_mtls = True + else: + self._is_mtls = False + + @property + def ssl_credentials(self): + """Get the created SSL channel credentials. + + For devices with endpoint verification support, if the device certificate + loading has any problems, corresponding exceptions will be raised. For + a device without endpoint verification support, no exceptions will be + raised. + + Returns: + grpc.ChannelCredentials: The created grpc channel credentials. + + Raises: + OSError: If the cert provider command launch fails. + RuntimeError: If the cert provider command has a runtime error. + ValueError: + If the context aware metadata file is malformed or if the cert provider + command doesn't produce both the client certificate and key. + """ + if self._context_aware_metadata_path: + metadata = _mtls_helper._read_dca_metadata_file( + self._context_aware_metadata_path + ) + cert, key = _mtls_helper.get_client_ssl_credentials(metadata) + self._ssl_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + else: + self._ssl_credentials = grpc.ssl_channel_credentials() + + return self._ssl_credentials + + @property + def is_mtls(self): + """Indicates if the created SSL channel credentials is mutual TLS.""" + return self._is_mtls diff --git a/packages/google-auth/tests/data/context_aware_metadata.json b/packages/google-auth/tests/data/context_aware_metadata.json new file mode 100644 index 000000000000..ec40e783f1be --- /dev/null +++ b/packages/google-auth/tests/data/context_aware_metadata.json @@ -0,0 +1,6 @@ +{ + "cert_provider_command":[ + "/opt/google/endpoint-verification/bin/SecureConnectHelper", + "--print_certificate"], + "device_resource_ids":["11111111-1111-1111"] +} diff --git a/packages/google-auth/tests/transport/test__mtls_helper.py b/packages/google-auth/tests/transport/test__mtls_helper.py new file mode 100644 index 000000000000..6e7175f178be --- /dev/null +++ b/packages/google-auth/tests/transport/test__mtls_helper.py @@ -0,0 +1,177 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import re + +import mock +import pytest + +from google.auth.transport import _mtls_helper + +DATA_DIR = os.path.join(os.path.dirname(__file__), "..", "data") + +with open(os.path.join(DATA_DIR, "privatekey.pem"), "rb") as fh: + PRIVATE_KEY_BYTES = fh.read() + +with open(os.path.join(DATA_DIR, "public_cert.pem"), "rb") as fh: + PUBLIC_CERT_BYTES = fh.read() + +CONTEXT_AWARE_METADATA = {"cert_provider_command": ["some command"]} + +CONTEXT_AWARE_METADATA_NO_CERT_PROVIDER_COMMAND = {} + + +def check_cert_and_key(content, expected_cert, expected_key): + success = True + + cert_match = re.findall(_mtls_helper._CERT_REGEX, content) + success = success and len(cert_match) == 1 and cert_match[0] == expected_cert + + key_match = re.findall(_mtls_helper._KEY_REGEX, content) + success = success and len(key_match) == 1 and key_match[0] == expected_key + + return success + + +class TestCertAndKeyRegex(object): + def test_cert_and_key(self): + # Test single cert and single key + check_cert_and_key( + PUBLIC_CERT_BYTES + PRIVATE_KEY_BYTES, PUBLIC_CERT_BYTES, PRIVATE_KEY_BYTES + ) + check_cert_and_key( + PRIVATE_KEY_BYTES + PUBLIC_CERT_BYTES, PUBLIC_CERT_BYTES, PRIVATE_KEY_BYTES + ) + + # Test cert chain and single key + check_cert_and_key( + PUBLIC_CERT_BYTES + PUBLIC_CERT_BYTES + PRIVATE_KEY_BYTES, + PUBLIC_CERT_BYTES + PUBLIC_CERT_BYTES, + PRIVATE_KEY_BYTES, + ) + check_cert_and_key( + PRIVATE_KEY_BYTES + PUBLIC_CERT_BYTES + PUBLIC_CERT_BYTES, + PUBLIC_CERT_BYTES + PUBLIC_CERT_BYTES, + PRIVATE_KEY_BYTES, + ) + + def test_key(self): + # Create some fake keys for regex check. + KEY = b"""-----BEGIN PRIVATE KEY----- + MIIBCgKCAQEA4ej0p7bQ7L/r4rVGUz9RN4VQWoej1Bg1mYWIDYslvKrk1gpj7wZg + /fy3ZpsL7WqgsZS7Q+0VRK8gKfqkxg5OYQIDAQAB + -----END PRIVATE KEY-----""" + RSA_KEY = b"""-----BEGIN RSA PRIVATE KEY----- + MIIBCgKCAQEA4ej0p7bQ7L/r4rVGUz9RN4VQWoej1Bg1mYWIDYslvKrk1gpj7wZg + /fy3ZpsL7WqgsZS7Q+0VRK8gKfqkxg5OYQIDAQAB + -----END RSA PRIVATE KEY-----""" + EC_KEY = b"""-----BEGIN EC PRIVATE KEY----- + MIIBCgKCAQEA4ej0p7bQ7L/r4rVGUz9RN4VQWoej1Bg1mYWIDYslvKrk1gpj7wZg + /fy3ZpsL7WqgsZS7Q+0VRK8gKfqkxg5OYQIDAQAB + -----END EC PRIVATE KEY-----""" + + check_cert_and_key(PUBLIC_CERT_BYTES + KEY, PUBLIC_CERT_BYTES, KEY) + check_cert_and_key(PUBLIC_CERT_BYTES + RSA_KEY, PUBLIC_CERT_BYTES, RSA_KEY) + check_cert_and_key(PUBLIC_CERT_BYTES + EC_KEY, PUBLIC_CERT_BYTES, EC_KEY) + + +class TestCheckaMetadataPath(object): + def test_success(self): + metadata_path = os.path.join(DATA_DIR, "context_aware_metadata.json") + returned_path = _mtls_helper._check_dca_metadata_path(metadata_path) + assert returned_path is not None + + def test_failure(self): + metadata_path = os.path.join(DATA_DIR, "not_exists.json") + returned_path = _mtls_helper._check_dca_metadata_path(metadata_path) + assert returned_path is None + + +class TestReadMetadataFile(object): + def test_success(self): + metadata_path = os.path.join(DATA_DIR, "context_aware_metadata.json") + metadata = _mtls_helper._read_dca_metadata_file(metadata_path) + + assert "cert_provider_command" in metadata + + def test_file_not_json(self): + # read a file which is not json format. + metadata_path = os.path.join(DATA_DIR, "privatekey.pem") + with pytest.raises(ValueError): + _mtls_helper._read_dca_metadata_file(metadata_path) + + +class TestGetClientSslCredentials(object): + def create_mock_process(self, output, error): + # There are two steps to execute a script with subprocess.Popen. + # (1) process = subprocess.Popen([comannds]) + # (2) stdout, stderr = process.communicate() + # This function creates a mock process which can be returned by a mock + # subprocess.Popen. The mock process returns the given output and error + # when mock_process.communicate() is called. + mock_process = mock.Mock() + attrs = {"communicate.return_value": (output, error), "returncode": 0} + mock_process.configure_mock(**attrs) + return mock_process + + @mock.patch("subprocess.Popen", autospec=True) + def test_success(self, mock_popen): + mock_popen.return_value = self.create_mock_process( + PUBLIC_CERT_BYTES + PRIVATE_KEY_BYTES, b"" + ) + cert, key = _mtls_helper.get_client_ssl_credentials(CONTEXT_AWARE_METADATA) + assert cert == PUBLIC_CERT_BYTES + assert key == PRIVATE_KEY_BYTES + + @mock.patch("subprocess.Popen", autospec=True) + def test_success_with_cert_chain(self, mock_popen): + PUBLIC_CERT_CHAIN_BYTES = PUBLIC_CERT_BYTES + PUBLIC_CERT_BYTES + mock_popen.return_value = self.create_mock_process( + PUBLIC_CERT_CHAIN_BYTES + PRIVATE_KEY_BYTES, b"" + ) + cert, key = _mtls_helper.get_client_ssl_credentials(CONTEXT_AWARE_METADATA) + assert cert == PUBLIC_CERT_CHAIN_BYTES + assert key == PRIVATE_KEY_BYTES + + def test_missing_cert_provider_command(self): + with pytest.raises(ValueError): + assert _mtls_helper.get_client_ssl_credentials( + CONTEXT_AWARE_METADATA_NO_CERT_PROVIDER_COMMAND + ) + + @mock.patch("subprocess.Popen", autospec=True) + def test_missing_cert(self, mock_popen): + mock_popen.return_value = self.create_mock_process(PRIVATE_KEY_BYTES, b"") + with pytest.raises(ValueError): + assert _mtls_helper.get_client_ssl_credentials(CONTEXT_AWARE_METADATA) + + @mock.patch("subprocess.Popen", autospec=True) + def test_missing_key(self, mock_popen): + mock_popen.return_value = self.create_mock_process(PUBLIC_CERT_BYTES, b"") + with pytest.raises(ValueError): + assert _mtls_helper.get_client_ssl_credentials(CONTEXT_AWARE_METADATA) + + @mock.patch("subprocess.Popen", autospec=True) + def test_cert_provider_returns_error(self, mock_popen): + mock_popen.return_value = self.create_mock_process(b"", b"some error") + mock_popen.return_value.returncode = 1 + with pytest.raises(RuntimeError): + assert _mtls_helper.get_client_ssl_credentials(CONTEXT_AWARE_METADATA) + + @mock.patch("subprocess.Popen", autospec=True) + def test_popen_raise_exception(self, mock_popen): + mock_popen.side_effect = OSError() + with pytest.raises(OSError): + assert _mtls_helper.get_client_ssl_credentials(CONTEXT_AWARE_METADATA) diff --git a/packages/google-auth/tests/transport/test_grpc.py b/packages/google-auth/tests/transport/test_grpc.py index 857c32bb9ec2..23e62a213e8e 100644 --- a/packages/google-auth/tests/transport/test_grpc.py +++ b/packages/google-auth/tests/transport/test_grpc.py @@ -13,6 +13,7 @@ # limitations under the License. import datetime +import os import time import mock @@ -31,6 +32,12 @@ except ImportError: # pragma: NO COVER HAS_GRPC = False +DATA_DIR = os.path.join(os.path.dirname(__file__), "..", "data") +METADATA_PATH = os.path.join(DATA_DIR, "context_aware_metadata.json") +with open(os.path.join(DATA_DIR, "privatekey.pem"), "rb") as fh: + PRIVATE_KEY_BYTES = fh.read() +with open(os.path.join(DATA_DIR, "public_cert.pem"), "rb") as fh: + PUBLIC_CERT_BYTES = fh.read() pytestmark = pytest.mark.skipif(not HAS_GRPC, reason="gRPC is unavailable.") @@ -87,70 +94,251 @@ def test_call_refresh(self): ) +@mock.patch( + "google.auth.transport._mtls_helper.get_client_ssl_credentials", autospec=True +) @mock.patch("grpc.composite_channel_credentials", autospec=True) @mock.patch("grpc.metadata_call_credentials", autospec=True) @mock.patch("grpc.ssl_channel_credentials", autospec=True) @mock.patch("grpc.secure_channel", autospec=True) -def test_secure_authorized_channel( - secure_channel, - ssl_channel_credentials, - metadata_call_credentials, - composite_channel_credentials, -): - credentials = CredentialsStub() - request = mock.create_autospec(transport.Request) - target = "example.com:80" - - channel = google.auth.transport.grpc.secure_authorized_channel( - credentials, request, target, options=mock.sentinel.options +class TestSecureAuthorizedChannel(object): + @mock.patch( + "google.auth.transport._mtls_helper._read_dca_metadata_file", autospec=True ) + @mock.patch( + "google.auth.transport._mtls_helper._check_dca_metadata_path", autospec=True + ) + def test_secure_authorized_channel_adc( + self, + check_dca_metadata_path, + read_dca_metadata_file, + secure_channel, + ssl_channel_credentials, + metadata_call_credentials, + composite_channel_credentials, + get_client_ssl_credentials, + ): + credentials = CredentialsStub() + request = mock.create_autospec(transport.Request) + target = "example.com:80" + + # Mock the context aware metadata and client cert/key so mTLS SSL channel + # will be used. + check_dca_metadata_path.return_value = METADATA_PATH + read_dca_metadata_file.return_value = { + "cert_provider_command": ["some command"] + } + get_client_ssl_credentials.return_value = (PUBLIC_CERT_BYTES, PRIVATE_KEY_BYTES) + + channel = google.auth.transport.grpc.secure_authorized_channel( + credentials, request, target, options=mock.sentinel.options + ) - # Check the auth plugin construction. - auth_plugin = metadata_call_credentials.call_args[0][0] - assert isinstance(auth_plugin, google.auth.transport.grpc.AuthMetadataPlugin) - assert auth_plugin._credentials == credentials - assert auth_plugin._request == request + # Check the auth plugin construction. + auth_plugin = metadata_call_credentials.call_args[0][0] + assert isinstance(auth_plugin, google.auth.transport.grpc.AuthMetadataPlugin) + assert auth_plugin._credentials == credentials + assert auth_plugin._request == request - # Check the ssl channel call. - assert ssl_channel_credentials.called + # Check the ssl channel call. + ssl_channel_credentials.assert_called_once_with( + certificate_chain=PUBLIC_CERT_BYTES, private_key=PRIVATE_KEY_BYTES + ) - # Check the composite credentials call. - composite_channel_credentials.assert_called_once_with( - ssl_channel_credentials.return_value, metadata_call_credentials.return_value - ) + # Check the composite credentials call. + composite_channel_credentials.assert_called_once_with( + ssl_channel_credentials.return_value, metadata_call_credentials.return_value + ) - # Check the channel call. - secure_channel.assert_called_once_with( - target, - composite_channel_credentials.return_value, - options=mock.sentinel.options, + # Check the channel call. + secure_channel.assert_called_once_with( + target, + composite_channel_credentials.return_value, + options=mock.sentinel.options, + ) + assert channel == secure_channel.return_value + + def test_secure_authorized_channel_explicit_ssl( + self, + secure_channel, + ssl_channel_credentials, + metadata_call_credentials, + composite_channel_credentials, + get_client_ssl_credentials, + ): + credentials = mock.Mock() + request = mock.Mock() + target = "example.com:80" + ssl_credentials = mock.Mock() + + google.auth.transport.grpc.secure_authorized_channel( + credentials, request, target, ssl_credentials=ssl_credentials + ) + + # Since explicit SSL credentials are provided, get_client_ssl_credentials + # shouldn't be called. + assert not get_client_ssl_credentials.called + + # Check the ssl channel call. + assert not ssl_channel_credentials.called + + # Check the composite credentials call. + composite_channel_credentials.assert_called_once_with( + ssl_credentials, metadata_call_credentials.return_value + ) + + def test_secure_authorized_channel_mutual_exclusive( + self, + secure_channel, + ssl_channel_credentials, + metadata_call_credentials, + composite_channel_credentials, + get_client_ssl_credentials, + ): + credentials = mock.Mock() + request = mock.Mock() + target = "example.com:80" + ssl_credentials = mock.Mock() + client_cert_callback = mock.Mock() + + with pytest.raises(ValueError): + google.auth.transport.grpc.secure_authorized_channel( + credentials, + request, + target, + ssl_credentials=ssl_credentials, + client_cert_callback=client_cert_callback, + ) + + def test_secure_authorized_channel_with_client_cert_callback_success( + self, + secure_channel, + ssl_channel_credentials, + metadata_call_credentials, + composite_channel_credentials, + get_client_ssl_credentials, + ): + credentials = mock.Mock() + request = mock.Mock() + target = "example.com:80" + client_cert_callback = mock.Mock() + client_cert_callback.return_value = (PUBLIC_CERT_BYTES, PRIVATE_KEY_BYTES) + + google.auth.transport.grpc.secure_authorized_channel( + credentials, request, target, client_cert_callback=client_cert_callback + ) + + client_cert_callback.assert_called_once() + + # Check we are using the cert and key provided by client_cert_callback. + ssl_channel_credentials.assert_called_once_with( + certificate_chain=PUBLIC_CERT_BYTES, private_key=PRIVATE_KEY_BYTES + ) + + # Check the composite credentials call. + composite_channel_credentials.assert_called_once_with( + ssl_channel_credentials.return_value, metadata_call_credentials.return_value + ) + + @mock.patch( + "google.auth.transport._mtls_helper._read_dca_metadata_file", autospec=True + ) + @mock.patch( + "google.auth.transport._mtls_helper._check_dca_metadata_path", autospec=True ) - assert channel == secure_channel.return_value + def test_secure_authorized_channel_with_client_cert_callback_failure( + self, + check_dca_metadata_path, + read_dca_metadata_file, + secure_channel, + ssl_channel_credentials, + metadata_call_credentials, + composite_channel_credentials, + get_client_ssl_credentials, + ): + credentials = mock.Mock() + request = mock.Mock() + target = "example.com:80" + + client_cert_callback = mock.Mock() + client_cert_callback.side_effect = Exception("callback exception") + + with pytest.raises(Exception) as excinfo: + google.auth.transport.grpc.secure_authorized_channel( + credentials, request, target, client_cert_callback=client_cert_callback + ) + + assert str(excinfo.value) == "callback exception" -@mock.patch("grpc.composite_channel_credentials", autospec=True) -@mock.patch("grpc.metadata_call_credentials", autospec=True) @mock.patch("grpc.ssl_channel_credentials", autospec=True) -@mock.patch("grpc.secure_channel", autospec=True) -def test_secure_authorized_channel_explicit_ssl( - secure_channel, - ssl_channel_credentials, - metadata_call_credentials, - composite_channel_credentials, -): - credentials = mock.Mock() - request = mock.Mock() - target = "example.com:80" - ssl_credentials = mock.Mock() - - google.auth.transport.grpc.secure_authorized_channel( - credentials, request, target, ssl_credentials=ssl_credentials - ) +@mock.patch( + "google.auth.transport._mtls_helper.get_client_ssl_credentials", autospec=True +) +@mock.patch("google.auth.transport._mtls_helper._read_dca_metadata_file", autospec=True) +@mock.patch( + "google.auth.transport._mtls_helper._check_dca_metadata_path", autospec=True +) +class TestSslCredentials(object): + def test_no_context_aware_metadata( + self, + mock_check_dca_metadata_path, + mock_read_dca_metadata_file, + mock_get_client_ssl_credentials, + mock_ssl_channel_credentials, + ): + # Mock that the metadata file doesn't exist. + mock_check_dca_metadata_path.return_value = None + + ssl_credentials = google.auth.transport.grpc.SslCredentials() + + # Since no context aware metadata is found, we wouldn't call + # get_client_ssl_credentials, and the SSL channel credentials created is + # non mTLS. + assert ssl_credentials.ssl_credentials is not None + assert not ssl_credentials.is_mtls + mock_get_client_ssl_credentials.assert_not_called() + mock_ssl_channel_credentials.assert_called_once_with() + + def test_get_client_ssl_credentials_failure( + self, + mock_check_dca_metadata_path, + mock_read_dca_metadata_file, + mock_get_client_ssl_credentials, + mock_ssl_channel_credentials, + ): + mock_check_dca_metadata_path.return_value = METADATA_PATH + mock_read_dca_metadata_file.return_value = { + "cert_provider_command": ["some command"] + } + + # Mock that client cert and key are not loaded and exception is raised. + mock_get_client_ssl_credentials.side_effect = ValueError() + + with pytest.raises(ValueError): + assert google.auth.transport.grpc.SslCredentials().ssl_credentials + + def test_get_client_ssl_credentials_success( + self, + mock_check_dca_metadata_path, + mock_read_dca_metadata_file, + mock_get_client_ssl_credentials, + mock_ssl_channel_credentials, + ): + mock_check_dca_metadata_path.return_value = METADATA_PATH + mock_read_dca_metadata_file.return_value = { + "cert_provider_command": ["some command"] + } + mock_get_client_ssl_credentials.return_value = ( + PUBLIC_CERT_BYTES, + PRIVATE_KEY_BYTES, + ) - # Check the ssl channel call. - assert not ssl_channel_credentials.called + ssl_credentials = google.auth.transport.grpc.SslCredentials() - # Check the composite credentials call. - composite_channel_credentials.assert_called_once_with( - ssl_credentials, metadata_call_credentials.return_value - ) + assert ssl_credentials.ssl_credentials is not None + assert ssl_credentials.is_mtls + mock_get_client_ssl_credentials.assert_called_once() + mock_ssl_channel_credentials.assert_called_once_with( + certificate_chain=PUBLIC_CERT_BYTES, private_key=PRIVATE_KEY_BYTES + ) From 87ce34de39aaf3848e4b459a27ed50815b74264d Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Thu, 19 Mar 2020 14:21:46 -0700 Subject: [PATCH 273/966] fix: make ThreadPoolExecutor a class var (#461) --- .../google-auth/google/auth/transport/grpc.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/google-auth/google/auth/transport/grpc.py b/packages/google-auth/google/auth/transport/grpc.py index ca387392eb59..5ce6b2b25dd0 100644 --- a/packages/google-auth/google/auth/transport/grpc.py +++ b/packages/google-auth/google/auth/transport/grpc.py @@ -51,6 +51,15 @@ class AuthMetadataPlugin(grpc.AuthMetadataPlugin): object used to refresh credentials as needed. """ + # Python 2.7 has no default for max_workers. + # In Python >= 3.5, ThreadPoolExecutor defaults to the + # number of processors on the machine, multiplied by 5. + if six.PY2: # pragma: NO COVER + max_workers = 5 + else: + max_workers = None + _AUTH_THREAD_POOL = futures.ThreadPoolExecutor(max_workers=max_workers) + def __init__(self, credentials, request): # pylint: disable=no-value-for-parameter # pylint doesn't realize that the super method takes no arguments @@ -58,7 +67,6 @@ def __init__(self, credentials, request): super(AuthMetadataPlugin, self).__init__() self._credentials = credentials self._request = request - self._pool = futures.ThreadPoolExecutor(max_workers=1) def _get_authorization_headers(self, context): """Gets the authorization headers for a request. @@ -89,12 +97,9 @@ def __call__(self, context, callback): callback (grpc.AuthMetadataPluginCallback): The callback that will be invoked to pass in the authorization metadata. """ - future = self._pool.submit(self._get_authorization_headers, context) + future = self._AUTH_THREAD_POOL.submit(self._get_authorization_headers, context) future.add_done_callback(self._callback_wrapper(callback)) - def __del__(self): - self._pool.shutdown(wait=False) - def secure_authorized_channel( credentials, From dce9fc61858df400786934fcaeda3e67f4d58a14 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Fri, 20 Mar 2020 09:49:44 -0700 Subject: [PATCH 274/966] feat: add mTLS ADC support for HTTP (#457) feat: add mTLS ADC support for HTTP --- .../google/auth/transport/_mtls_helper.py | 40 +++++- .../google/auth/transport/requests.py | 131 ++++++++++++++++++ .../google/auth/transport/urllib3.py | 129 ++++++++++++++++- packages/google-auth/noxfile.py | 1 + packages/google-auth/system_tests/noxfile.py | 8 ++ .../system_tests/test_mtls_http.py | 71 ++++++++++ packages/google-auth/tests/conftest.py | 12 ++ .../tests/transport/test__mtls_helper.py | 114 ++++++++++----- .../tests/transport/test_requests.py | 90 +++++++++++- .../tests/transport/test_urllib3.py | 83 +++++++++++ 10 files changed, 642 insertions(+), 37 deletions(-) create mode 100644 packages/google-auth/system_tests/test_mtls_http.py diff --git a/packages/google-auth/google/auth/transport/_mtls_helper.py b/packages/google-auth/google/auth/transport/_mtls_helper.py index 1ce9fa554ff3..c518cc821311 100644 --- a/packages/google-auth/google/auth/transport/_mtls_helper.py +++ b/packages/google-auth/google/auth/transport/_mtls_helper.py @@ -86,7 +86,9 @@ def get_client_ssl_credentials(metadata_json): Raises: OSError: If the cert provider command failed to run. RuntimeError: If the cert provider command has a runtime error. - ValueError: If the metadata json file doesn't contain the cert provider command or if the command doesn't produce both the client certificate and client key. + ValueError: If the metadata json file doesn't contain the cert provider + command or if the command doesn't produce both the client certificate + and client key. """ # TODO: implement an in-memory cache of cert and key so we don't have to # run cert provider command every time. @@ -114,3 +116,39 @@ def get_client_ssl_credentials(metadata_json): if len(key_match) != 1: raise ValueError("Client SSL key is missing or invalid") return cert_match[0], key_match[0] + + +def get_client_cert_and_key(client_cert_callback=None): + """Returns the client side certificate and private key. The function first + tries to get certificate and key from client_cert_callback; if the callback + is None or doesn't provide certificate and key, the function tries application + default SSL credentials. + + Args: + client_cert_callback (Optional[Callable[[], (bytes, bytes)]]): An + optional callback which returns client certificate bytes and private + key bytes both in PEM format. + + Returns: + Tuple[bool, bytes, bytes]: + A boolean indicating if cert and key are obtained, the cert bytes + and key bytes both in PEM format. + + Raises: + OSError: If the cert provider command failed to run. + RuntimeError: If the cert provider command has a runtime error. + ValueError: If the metadata json file doesn't contain the cert provider + command or if the command doesn't produce both the client certificate + and client key. + """ + if client_cert_callback: + cert, key = client_cert_callback() + return True, cert, key + + metadata_path = _check_dca_metadata_path(CONTEXT_AWARE_METADATA_PATH) + if metadata_path: + metadata = _read_dca_metadata_file(metadata_path) + cert, key = get_client_ssl_credentials(metadata) + return True, cert, key + + return False, None, None diff --git a/packages/google-auth/google/auth/transport/requests.py b/packages/google-auth/google/auth/transport/requests.py index 32f59e56bb99..2d31d962e518 100644 --- a/packages/google-auth/google/auth/transport/requests.py +++ b/packages/google-auth/google/auth/transport/requests.py @@ -35,10 +35,14 @@ ) import requests.adapters # pylint: disable=ungrouped-imports import requests.exceptions # pylint: disable=ungrouped-imports +from requests.packages.urllib3.util.ssl_ import ( + create_urllib3_context, +) # pylint: disable=ungrouped-imports import six # pylint: disable=ungrouped-imports from google.auth import exceptions from google.auth import transport +import google.auth.transport._mtls_helper _LOGGER = logging.getLogger(__name__) @@ -182,6 +186,52 @@ def __call__( six.raise_from(new_exc, caught_exc) +class _MutualTlsAdapter(requests.adapters.HTTPAdapter): + """ + A TransportAdapter that enables mutual TLS. + + Args: + cert (bytes): client certificate in PEM format + key (bytes): client private key in PEM format + + Raises: + ImportError: if certifi or pyOpenSSL is not installed + OpenSSL.crypto.Error: if client cert or key is invalid + """ + + def __init__(self, cert, key): + import certifi + from OpenSSL import crypto + import urllib3.contrib.pyopenssl + + urllib3.contrib.pyopenssl.inject_into_urllib3() + + pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, key) + x509 = crypto.load_certificate(crypto.FILETYPE_PEM, cert) + + ctx_poolmanager = create_urllib3_context() + ctx_poolmanager.load_verify_locations(cafile=certifi.where()) + ctx_poolmanager._ctx.use_certificate(x509) + ctx_poolmanager._ctx.use_privatekey(pkey) + self._ctx_poolmanager = ctx_poolmanager + + ctx_proxymanager = create_urllib3_context() + ctx_proxymanager.load_verify_locations(cafile=certifi.where()) + ctx_proxymanager._ctx.use_certificate(x509) + ctx_proxymanager._ctx.use_privatekey(pkey) + self._ctx_proxymanager = ctx_proxymanager + + super(_MutualTlsAdapter, self).__init__() + + def init_poolmanager(self, *args, **kwargs): + kwargs["ssl_context"] = self._ctx_poolmanager + super(_MutualTlsAdapter, self).init_poolmanager(*args, **kwargs) + + def proxy_manager_for(self, *args, **kwargs): + kwargs["ssl_context"] = self._ctx_proxymanager + return super(_MutualTlsAdapter, self).proxy_manager_for(*args, **kwargs) + + class AuthorizedSession(requests.Session): """A Requests Session class with credentials. @@ -198,6 +248,48 @@ class AuthorizedSession(requests.Session): The underlying :meth:`request` implementation handles adding the credentials' headers to the request and refreshing credentials as needed. + This class also supports mutual TLS via :meth:`configure_mtls_channel` + method. If client_cert_callabck is provided, client certificate and private + key are loaded using the callback; if client_cert_callabck is None, + application default SSL credentials will be used. Exceptions are raised if + there are problems with the certificate, private key, or the loading process, + so it should be called within a try/except block. + + First we create an :class:`AuthorizedSession` instance and specify the endpoints:: + + regular_endpoint = 'https://pubsub.googleapis.com/v1/projects/{my_project_id}/topics' + mtls_endpoint = 'https://pubsub.mtls.googleapis.com/v1/projects/{my_project_id}/topics' + + authed_session = AuthorizedSession(credentials) + + Now we can pass a callback to :meth:`configure_mtls_channel`:: + + def my_cert_callback(): + # some code to load client cert bytes and private key bytes, both in + # PEM format. + some_code_to_load_client_cert_and_key() + if loaded: + return cert, key + raise MyClientCertFailureException() + + # Always call configure_mtls_channel within a try/except block. + try: + authed_session.configure_mtls_channel(my_cert_callback) + except: + # handle exceptions. + + if authed_session.is_mtls: + response = authed_session.request('GET', mtls_endpoint) + else: + response = authed_session.request('GET', regular_endpoint) + + You can alternatively use application default SSL credentials like this:: + + try: + authed_session.configure_mtls_channel() + except: + # handle exceptions. + Args: credentials (google.auth.credentials.Credentials): The credentials to add to the request. @@ -229,6 +321,7 @@ def __init__( self._refresh_status_codes = refresh_status_codes self._max_refresh_attempts = max_refresh_attempts self._refresh_timeout = refresh_timeout + self._is_mtls = False if auth_request is None: auth_request_session = requests.Session() @@ -247,6 +340,39 @@ def __init__( # credentials.refresh). self._auth_request = auth_request + def configure_mtls_channel(self, client_cert_callback=None): + """Configure the client certificate and key for SSL connection. + + If client certificate and key are successfully obtained (from the given + client_cert_callabck or from application default SSL credentials), a + :class:`_MutualTlsAdapter` instance will be mounted to "https://" prefix. + + Args: + client_cert_callabck (Optional[Callable[[], (bytes, bytes)]]): + The optional callback returns the client certificate and private + key bytes both in PEM format. + If the callback is None, application default SSL credentials + will be used. + + Raises: + ImportError: If certifi or pyOpenSSL is not installed. + OpenSSL.crypto.Error: If client cert or key is invalid. + OSError: If the cert provider command launch fails during the + application default SSL credentials loading process. + RuntimeError: If the cert provider command has a runtime error during + the application default SSL credentials loading process. + ValueError: If the context aware metadata file is malformed or the + cert provider command doesn't produce both client certicate and + key during the application default SSL credentials loading process. + """ + self._is_mtls, cert, key = google.auth.transport._mtls_helper.get_client_cert_and_key( + client_cert_callback + ) + + if self._is_mtls: + mtls_adapter = _MutualTlsAdapter(cert, key) + self.mount("https://", mtls_adapter) + def request( self, method, @@ -361,3 +487,8 @@ def request( ) return response + + @property + def is_mtls(self): + """Indicates if the created SSL channel is mutual TLS.""" + return self._is_mtls diff --git a/packages/google-auth/google/auth/transport/urllib3.py b/packages/google-auth/google/auth/transport/urllib3.py index d1905e94eea9..3b2ba28bc364 100644 --- a/packages/google-auth/google/auth/transport/urllib3.py +++ b/packages/google-auth/google/auth/transport/urllib3.py @@ -17,7 +17,7 @@ from __future__ import absolute_import import logging - +import warnings # Certifi is Mozilla's certificate bundle. Urllib3 needs a certificate bundle # to verify HTTPS requests, and certifi is the recommended and most reliable @@ -149,6 +149,39 @@ def _make_default_http(): return urllib3.PoolManager() +def _make_mutual_tls_http(cert, key): + """Create a mutual TLS HTTP connection with the given client cert and key. + See https://github.com/urllib3/urllib3/issues/474#issuecomment-253168415 + + Args: + cert (bytes): client certificate in PEM format + key (bytes): client private key in PEM format + + Returns: + urllib3.PoolManager: Mutual TLS HTTP connection. + + Raises: + ImportError: If certifi or pyOpenSSL is not installed. + OpenSSL.crypto.Error: If the cert or key is invalid. + """ + import certifi + from OpenSSL import crypto + import urllib3.contrib.pyopenssl + + urllib3.contrib.pyopenssl.inject_into_urllib3() + ctx = urllib3.util.ssl_.create_urllib3_context() + ctx.load_verify_locations(cafile=certifi.where()) + + pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, key) + x509 = crypto.load_certificate(crypto.FILETYPE_PEM, cert) + + ctx._ctx.use_certificate(x509) + ctx._ctx.use_privatekey(pkey) + + http = urllib3.PoolManager(ssl_context=ctx) + return http + + class AuthorizedHttp(urllib3.request.RequestMethods): """A urllib3 HTTP class with credentials. @@ -168,6 +201,48 @@ class AuthorizedHttp(urllib3.request.RequestMethods): The underlying :meth:`urlopen` implementation handles adding the credentials' headers to the request and refreshing credentials as needed. + This class also supports mutual TLS via :meth:`configure_mtls_channel` + method. If client_cert_callabck is provided, client certificate and private + key are loaded using the callback; if client_cert_callabck is None, + application default SSL credentials will be used. Exceptions are raised if + there are problems with the certificate, private key, or the loading process, + so it should be called within a try/except block. + + First we create an :class:`AuthorizedHttp` instance and specify the endpoints:: + + regular_endpoint = 'https://pubsub.googleapis.com/v1/projects/{my_project_id}/topics' + mtls_endpoint = 'https://pubsub.mtls.googleapis.com/v1/projects/{my_project_id}/topics' + + authed_http = AuthorizedHttp(credentials) + + Now we can pass a callback to :meth:`configure_mtls_channel`:: + + def my_cert_callback(): + # some code to load client cert bytes and private key bytes, both in + # PEM format. + some_code_to_load_client_cert_and_key() + if loaded: + return cert, key + raise MyClientCertFailureException() + + # Always call configure_mtls_channel within a try/except block. + try: + is_mtls = authed_http.configure_mtls_channel(my_cert_callback) + except: + # handle exceptions. + + if is_mtls: + response = authed_http.request('GET', mtls_endpoint) + else: + response = authed_http.request('GET', regular_endpoint) + + You can alternatively use application default SSL credentials like this:: + + try: + is_mtls = authed_http.configure_mtls_channel() + except: + # handle exceptions. + Args: credentials (google.auth.credentials.Credentials): The credentials to add to the request. @@ -189,12 +264,14 @@ def __init__( refresh_status_codes=transport.DEFAULT_REFRESH_STATUS_CODES, max_refresh_attempts=transport.DEFAULT_MAX_REFRESH_ATTEMPTS, ): - if http is None: - http = _make_default_http() + self.http = _make_default_http() + self._has_user_provided_http = False + else: + self.http = http + self._has_user_provided_http = True self.credentials = credentials - self.http = http self._refresh_status_codes = refresh_status_codes self._max_refresh_attempts = max_refresh_attempts # Request instance used by internal methods (for example, @@ -203,6 +280,50 @@ def __init__( super(AuthorizedHttp, self).__init__() + def configure_mtls_channel(self, client_cert_callabck=None): + """Configures mutual TLS channel using the given client_cert_callabck or + application default SSL credentials. Returns True if the channel is + mutual TLS and False otherwise. Note that the `http` provided in the + constructor will be overwritten. + + Args: + client_cert_callabck (Optional[Callable[[], (bytes, bytes)]]): + The optional callback returns the client certificate and private + key bytes both in PEM format. + If the callback is None, application default SSL credentials + will be used. + + Returns: + True if the channel is mutual TLS and False otherwise. + + Raises: + ImportError: If certifi or pyOpenSSL is not installed. + OpenSSL.crypto.Error: If client cert or key is invalid. + OSError: If the cert provider command launch fails during the + application default SSL credentials loading process. + RuntimeError: If the cert provider command has a runtime error during + the application default SSL credentials loading process. + ValueError: If the context aware metadata file is malformed or the + cert provider command doesn't produce both client certicate and + key during the application default SSL credentials loading process. + """ + found_cert_key, cert, key = transport._mtls_helper.get_client_cert_and_key( + client_cert_callabck + ) + + if found_cert_key: + self.http = _make_mutual_tls_http(cert, key) + else: + self.http = _make_default_http() + + if self._has_user_provided_http: + self._has_user_provided_http = False + warnings.warn( + "`http` provided in the constructor is overwritten", UserWarning + ) + + return found_cert_key + def urlopen(self, method, url, body=None, headers=None, **kwargs): """Implementation of urllib3's urlopen.""" # pylint: disable=arguments-differ diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index d75361f73dfd..bcea1fbc8cd2 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -19,6 +19,7 @@ "freezegun", "mock", "oauth2client", + "pyopenssl", "pytest", "pytest-cov", "pytest-localserver", diff --git a/packages/google-auth/system_tests/noxfile.py b/packages/google-auth/system_tests/noxfile.py index 811063223871..6e66eb4ed211 100644 --- a/packages/google-auth/system_tests/noxfile.py +++ b/packages/google-auth/system_tests/noxfile.py @@ -305,3 +305,11 @@ def grpc(session): session.install(*TEST_DEPENDENCIES, "google-cloud-pubsub==1.0.0") session.env[EXPLICIT_CREDENTIALS_ENV] = SERVICE_ACCOUNT_FILE session.run("pytest", "test_grpc.py") + + +@nox.session(python=PYTHON_VERSIONS) +def mtls_http(session): + session.install(LIBRARY_DIR) + session.install(*TEST_DEPENDENCIES, "pyopenssl") + session.env[EXPLICIT_CREDENTIALS_ENV] = SERVICE_ACCOUNT_FILE + session.run("pytest", "test_mtls_http.py") diff --git a/packages/google-auth/system_tests/test_mtls_http.py b/packages/google-auth/system_tests/test_mtls_http.py new file mode 100644 index 000000000000..e7ea0b2423ab --- /dev/null +++ b/packages/google-auth/system_tests/test_mtls_http.py @@ -0,0 +1,71 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +from os import path + +import google.auth +import google.auth.credentials +import google.auth.transport.requests +import google.auth.transport.urllib3 + +MTLS_ENDPOINT = "https://pubsub.mtls.googleapis.com/v1/projects/{}/topics" +REGULAR_ENDPOINT = "https://pubsub.googleapis.com/v1/projects/{}/topics" + + +def check_context_aware_metadata(): + metadata_path = path.expanduser("~/.secureConnect/context_aware_metadata.json") + return path.exists(metadata_path) + + +def test_requests(): + credentials, project_id = google.auth.default() + credentials = google.auth.credentials.with_scopes_if_required( + credentials, ["https://www.googleapis.com/auth/pubsub"] + ) + + authed_session = google.auth.transport.requests.AuthorizedSession(credentials) + authed_session.configure_mtls_channel() + + # If the devices has context aware metadata, then a mutual TLS channel is + # supposed to be created. + assert authed_session.is_mtls == check_context_aware_metadata() + + if authed_session.is_mtls: + response = authed_session.get(MTLS_ENDPOINT.format(project_id)) + else: + response = authed_session.get(REGULAR_ENDPOINT.format(project_id)) + + assert response.ok + + +def test_urllib3(): + credentials, project_id = google.auth.default() + credentials = google.auth.credentials.with_scopes_if_required( + credentials, ["https://www.googleapis.com/auth/pubsub"] + ) + + authed_http = google.auth.transport.urllib3.AuthorizedHttp(credentials) + is_mtls = authed_http.configure_mtls_channel() + + # If the devices has context aware metadata, then a mutual TLS channel is + # supposed to be created. + assert is_mtls == check_context_aware_metadata() + + if is_mtls: + response = authed_http.request("GET", MTLS_ENDPOINT.format(project_id)) + else: + response = authed_http.request("GET", REGULAR_ENDPOINT.format(project_id)) + + assert response.status == 200 diff --git a/packages/google-auth/tests/conftest.py b/packages/google-auth/tests/conftest.py index 7f9a968b79d3..cf8a0f9e552c 100644 --- a/packages/google-auth/tests/conftest.py +++ b/packages/google-auth/tests/conftest.py @@ -12,12 +12,24 @@ # See the License for the specific language governing permissions and # limitations under the License. +import os import sys import mock import pytest +def pytest_configure(): + """Load public certificate and private key.""" + pytest.data_dir = os.path.join(os.path.dirname(__file__), "data") + + with open(os.path.join(pytest.data_dir, "privatekey.pem"), "rb") as fh: + pytest.private_key_bytes = fh.read() + + with open(os.path.join(pytest.data_dir, "public_cert.pem"), "rb") as fh: + pytest.public_cert_bytes = fh.read() + + @pytest.fixture def mock_non_existent_module(monkeypatch): """Mocks a non-existing module in sys.modules. diff --git a/packages/google-auth/tests/transport/test__mtls_helper.py b/packages/google-auth/tests/transport/test__mtls_helper.py index 6e7175f178be..5bf196797894 100644 --- a/packages/google-auth/tests/transport/test__mtls_helper.py +++ b/packages/google-auth/tests/transport/test__mtls_helper.py @@ -20,14 +20,6 @@ from google.auth.transport import _mtls_helper -DATA_DIR = os.path.join(os.path.dirname(__file__), "..", "data") - -with open(os.path.join(DATA_DIR, "privatekey.pem"), "rb") as fh: - PRIVATE_KEY_BYTES = fh.read() - -with open(os.path.join(DATA_DIR, "public_cert.pem"), "rb") as fh: - PUBLIC_CERT_BYTES = fh.read() - CONTEXT_AWARE_METADATA = {"cert_provider_command": ["some command"]} CONTEXT_AWARE_METADATA_NO_CERT_PROVIDER_COMMAND = {} @@ -49,22 +41,30 @@ class TestCertAndKeyRegex(object): def test_cert_and_key(self): # Test single cert and single key check_cert_and_key( - PUBLIC_CERT_BYTES + PRIVATE_KEY_BYTES, PUBLIC_CERT_BYTES, PRIVATE_KEY_BYTES + pytest.public_cert_bytes + pytest.private_key_bytes, + pytest.public_cert_bytes, + pytest.private_key_bytes, ) check_cert_and_key( - PRIVATE_KEY_BYTES + PUBLIC_CERT_BYTES, PUBLIC_CERT_BYTES, PRIVATE_KEY_BYTES + pytest.private_key_bytes + pytest.public_cert_bytes, + pytest.public_cert_bytes, + pytest.private_key_bytes, ) # Test cert chain and single key check_cert_and_key( - PUBLIC_CERT_BYTES + PUBLIC_CERT_BYTES + PRIVATE_KEY_BYTES, - PUBLIC_CERT_BYTES + PUBLIC_CERT_BYTES, - PRIVATE_KEY_BYTES, + pytest.public_cert_bytes + + pytest.public_cert_bytes + + pytest.private_key_bytes, + pytest.public_cert_bytes + pytest.public_cert_bytes, + pytest.private_key_bytes, ) check_cert_and_key( - PRIVATE_KEY_BYTES + PUBLIC_CERT_BYTES + PUBLIC_CERT_BYTES, - PUBLIC_CERT_BYTES + PUBLIC_CERT_BYTES, - PRIVATE_KEY_BYTES, + pytest.private_key_bytes + + pytest.public_cert_bytes + + pytest.public_cert_bytes, + pytest.public_cert_bytes + pytest.public_cert_bytes, + pytest.private_key_bytes, ) def test_key(self): @@ -82,33 +82,39 @@ def test_key(self): /fy3ZpsL7WqgsZS7Q+0VRK8gKfqkxg5OYQIDAQAB -----END EC PRIVATE KEY-----""" - check_cert_and_key(PUBLIC_CERT_BYTES + KEY, PUBLIC_CERT_BYTES, KEY) - check_cert_and_key(PUBLIC_CERT_BYTES + RSA_KEY, PUBLIC_CERT_BYTES, RSA_KEY) - check_cert_and_key(PUBLIC_CERT_BYTES + EC_KEY, PUBLIC_CERT_BYTES, EC_KEY) + check_cert_and_key( + pytest.public_cert_bytes + KEY, pytest.public_cert_bytes, KEY + ) + check_cert_and_key( + pytest.public_cert_bytes + RSA_KEY, pytest.public_cert_bytes, RSA_KEY + ) + check_cert_and_key( + pytest.public_cert_bytes + EC_KEY, pytest.public_cert_bytes, EC_KEY + ) class TestCheckaMetadataPath(object): def test_success(self): - metadata_path = os.path.join(DATA_DIR, "context_aware_metadata.json") + metadata_path = os.path.join(pytest.data_dir, "context_aware_metadata.json") returned_path = _mtls_helper._check_dca_metadata_path(metadata_path) assert returned_path is not None def test_failure(self): - metadata_path = os.path.join(DATA_DIR, "not_exists.json") + metadata_path = os.path.join(pytest.data_dir, "not_exists.json") returned_path = _mtls_helper._check_dca_metadata_path(metadata_path) assert returned_path is None class TestReadMetadataFile(object): def test_success(self): - metadata_path = os.path.join(DATA_DIR, "context_aware_metadata.json") + metadata_path = os.path.join(pytest.data_dir, "context_aware_metadata.json") metadata = _mtls_helper._read_dca_metadata_file(metadata_path) assert "cert_provider_command" in metadata def test_file_not_json(self): # read a file which is not json format. - metadata_path = os.path.join(DATA_DIR, "privatekey.pem") + metadata_path = os.path.join(pytest.data_dir, "privatekey.pem") with pytest.raises(ValueError): _mtls_helper._read_dca_metadata_file(metadata_path) @@ -129,21 +135,21 @@ def create_mock_process(self, output, error): @mock.patch("subprocess.Popen", autospec=True) def test_success(self, mock_popen): mock_popen.return_value = self.create_mock_process( - PUBLIC_CERT_BYTES + PRIVATE_KEY_BYTES, b"" + pytest.public_cert_bytes + pytest.private_key_bytes, b"" ) cert, key = _mtls_helper.get_client_ssl_credentials(CONTEXT_AWARE_METADATA) - assert cert == PUBLIC_CERT_BYTES - assert key == PRIVATE_KEY_BYTES + assert cert == pytest.public_cert_bytes + assert key == pytest.private_key_bytes @mock.patch("subprocess.Popen", autospec=True) def test_success_with_cert_chain(self, mock_popen): - PUBLIC_CERT_CHAIN_BYTES = PUBLIC_CERT_BYTES + PUBLIC_CERT_BYTES + PUBLIC_CERT_CHAIN_BYTES = pytest.public_cert_bytes + pytest.public_cert_bytes mock_popen.return_value = self.create_mock_process( - PUBLIC_CERT_CHAIN_BYTES + PRIVATE_KEY_BYTES, b"" + PUBLIC_CERT_CHAIN_BYTES + pytest.private_key_bytes, b"" ) cert, key = _mtls_helper.get_client_ssl_credentials(CONTEXT_AWARE_METADATA) assert cert == PUBLIC_CERT_CHAIN_BYTES - assert key == PRIVATE_KEY_BYTES + assert key == pytest.private_key_bytes def test_missing_cert_provider_command(self): with pytest.raises(ValueError): @@ -153,13 +159,17 @@ def test_missing_cert_provider_command(self): @mock.patch("subprocess.Popen", autospec=True) def test_missing_cert(self, mock_popen): - mock_popen.return_value = self.create_mock_process(PRIVATE_KEY_BYTES, b"") + mock_popen.return_value = self.create_mock_process( + pytest.private_key_bytes, b"" + ) with pytest.raises(ValueError): assert _mtls_helper.get_client_ssl_credentials(CONTEXT_AWARE_METADATA) @mock.patch("subprocess.Popen", autospec=True) def test_missing_key(self, mock_popen): - mock_popen.return_value = self.create_mock_process(PUBLIC_CERT_BYTES, b"") + mock_popen.return_value = self.create_mock_process( + pytest.public_cert_bytes, b"" + ) with pytest.raises(ValueError): assert _mtls_helper.get_client_ssl_credentials(CONTEXT_AWARE_METADATA) @@ -175,3 +185,45 @@ def test_popen_raise_exception(self, mock_popen): mock_popen.side_effect = OSError() with pytest.raises(OSError): assert _mtls_helper.get_client_ssl_credentials(CONTEXT_AWARE_METADATA) + + +class TestGetClientCertAndKey(object): + def test_callback_success(self): + callback = mock.Mock() + callback.return_value = (pytest.public_cert_bytes, pytest.private_key_bytes) + + found_cert_key, cert, key = _mtls_helper.get_client_cert_and_key(callback) + assert found_cert_key + assert cert == pytest.public_cert_bytes + assert key == pytest.private_key_bytes + + @mock.patch( + "google.auth.transport._mtls_helper._check_dca_metadata_path", autospec=True + ) + def test_no_metadata(self, mock_check_dca_metadata_path): + mock_check_dca_metadata_path.return_value = None + + found_cert_key, cert, key = _mtls_helper.get_client_cert_and_key() + assert not found_cert_key + + @mock.patch( + "google.auth.transport._mtls_helper.get_client_ssl_credentials", autospec=True + ) + @mock.patch( + "google.auth.transport._mtls_helper._check_dca_metadata_path", autospec=True + ) + def test_use_metadata( + self, mock_check_dca_metadata_path, mock_get_client_ssl_credentials + ): + mock_check_dca_metadata_path.return_value = os.path.join( + pytest.data_dir, "context_aware_metadata.json" + ) + mock_get_client_ssl_credentials.return_value = ( + pytest.public_cert_bytes, + pytest.private_key_bytes, + ) + + found_cert_key, cert, key = _mtls_helper.get_client_cert_and_key() + assert found_cert_key + assert cert == pytest.public_cert_bytes + assert key == pytest.private_key_bytes diff --git a/packages/google-auth/tests/transport/test_requests.py b/packages/google-auth/tests/transport/test_requests.py index 9aafd88b1f49..3f3e14c05375 100644 --- a/packages/google-auth/tests/transport/test_requests.py +++ b/packages/google-auth/tests/transport/test_requests.py @@ -17,12 +17,14 @@ import freezegun import mock +import OpenSSL import pytest import requests import requests.adapters from six.moves import http_client import google.auth.credentials +import google.auth.transport._mtls_helper import google.auth.transport.requests from tests.transport import compliance @@ -150,6 +152,34 @@ def send(self, request, **kwargs): return super(TimeTickAdapterStub, self).send(request, **kwargs) +class TestMutualTlsAdapter(object): + @mock.patch.object(requests.adapters.HTTPAdapter, "init_poolmanager") + @mock.patch.object(requests.adapters.HTTPAdapter, "proxy_manager_for") + def test_success(self, mock_proxy_manager_for, mock_init_poolmanager): + adapter = google.auth.transport.requests._MutualTlsAdapter( + pytest.public_cert_bytes, pytest.private_key_bytes + ) + + adapter.init_poolmanager() + mock_init_poolmanager.assert_called_with(ssl_context=adapter._ctx_poolmanager) + + adapter.proxy_manager_for() + mock_proxy_manager_for.assert_called_with(ssl_context=adapter._ctx_proxymanager) + + def test_invalid_cert_or_key(self): + with pytest.raises(OpenSSL.crypto.Error): + google.auth.transport.requests._MutualTlsAdapter( + b"invalid cert", b"invalid key" + ) + + @mock.patch.dict("sys.modules", {"OpenSSL.crypto": None}) + def test_import_error(self): + with pytest.raises(ImportError): + google.auth.transport.requests._MutualTlsAdapter( + pytest.public_cert_bytes, pytest.private_key_bytes + ) + + def make_response(status=http_client.OK, data=None): response = requests.Response() response.status_code = status @@ -157,7 +187,7 @@ def make_response(status=http_client.OK, data=None): return response -class TestAuthorizedHttp(object): +class TestAuthorizedSession(object): TEST_URL = "http://example.com/" def test_constructor(self): @@ -326,3 +356,61 @@ def test_request_timeout_w_refresh_timeout_timeout_error(self, frozen_time): authed_session.request( "GET", self.TEST_URL, timeout=60, max_allowed_time=2.9 ) + + def test_configure_mtls_channel_with_callback(self): + mock_callback = mock.Mock() + mock_callback.return_value = ( + pytest.public_cert_bytes, + pytest.private_key_bytes, + ) + + auth_session = google.auth.transport.requests.AuthorizedSession( + credentials=mock.Mock() + ) + auth_session.configure_mtls_channel(mock_callback) + + assert auth_session.is_mtls + assert isinstance( + auth_session.adapters["https://"], + google.auth.transport.requests._MutualTlsAdapter, + ) + + @mock.patch( + "google.auth.transport._mtls_helper.get_client_cert_and_key", autospec=True + ) + def test_configure_mtls_channel_with_metadata(self, mock_get_client_cert_and_key): + mock_get_client_cert_and_key.return_value = ( + True, + pytest.public_cert_bytes, + pytest.private_key_bytes, + ) + + auth_session = google.auth.transport.requests.AuthorizedSession( + credentials=mock.Mock() + ) + auth_session.configure_mtls_channel() + + assert auth_session.is_mtls + assert isinstance( + auth_session.adapters["https://"], + google.auth.transport.requests._MutualTlsAdapter, + ) + + @mock.patch.object(google.auth.transport.requests._MutualTlsAdapter, "__init__") + @mock.patch( + "google.auth.transport._mtls_helper.get_client_cert_and_key", autospec=True + ) + def test_configure_mtls_channel_non_mtls( + self, mock_get_client_cert_and_key, mock_adapter_ctor + ): + mock_get_client_cert_and_key.return_value = (False, None, None) + + auth_session = google.auth.transport.requests.AuthorizedSession( + credentials=mock.Mock() + ) + auth_session.configure_mtls_channel() + + assert not auth_session.is_mtls + + # Assert _MutualTlsAdapter constructor is not called. + mock_adapter_ctor.assert_not_called() diff --git a/packages/google-auth/tests/transport/test_urllib3.py b/packages/google-auth/tests/transport/test_urllib3.py index 8a307332acaf..0452e9187dc5 100644 --- a/packages/google-auth/tests/transport/test_urllib3.py +++ b/packages/google-auth/tests/transport/test_urllib3.py @@ -13,10 +13,13 @@ # limitations under the License. import mock +import OpenSSL +import pytest from six.moves import http_client import urllib3 import google.auth.credentials +import google.auth.transport._mtls_helper import google.auth.transport.urllib3 from tests.transport import compliance @@ -77,6 +80,27 @@ def __init__(self, status=http_client.OK, data=None): self.data = data +class TestMakeMutualTlsHttp(object): + def test_success(self): + http = google.auth.transport.urllib3._make_mutual_tls_http( + pytest.public_cert_bytes, pytest.private_key_bytes + ) + assert isinstance(http, urllib3.PoolManager) + + def test_crypto_error(self): + with pytest.raises(OpenSSL.crypto.Error): + google.auth.transport.urllib3._make_mutual_tls_http( + b"invalid cert", b"invalid key" + ) + + @mock.patch.dict("sys.modules", {"OpenSSL.crypto": None}) + def test_import_error(self): + with pytest.raises(ImportError): + google.auth.transport.urllib3._make_mutual_tls_http( + pytest.public_cert_bytes, pytest.private_key_bytes + ) + + class TestAuthorizedHttp(object): TEST_URL = "http://example.com" @@ -138,3 +162,62 @@ def test_proxies(self): authed_http.headers = mock.sentinel.headers assert authed_http.headers == http.headers + + @mock.patch("google.auth.transport.urllib3._make_mutual_tls_http", autospec=True) + def test_configure_mtls_channel_with_callback(self, mock_make_mutual_tls_http): + callback = mock.Mock() + callback.return_value = (pytest.public_cert_bytes, pytest.private_key_bytes) + + authed_http = google.auth.transport.urllib3.AuthorizedHttp( + credentials=mock.Mock(), http=mock.Mock() + ) + + with pytest.warns(UserWarning): + is_mtls = authed_http.configure_mtls_channel(callback) + + assert is_mtls + mock_make_mutual_tls_http.assert_called_once_with( + cert=pytest.public_cert_bytes, key=pytest.private_key_bytes + ) + + @mock.patch("google.auth.transport.urllib3._make_mutual_tls_http", autospec=True) + @mock.patch( + "google.auth.transport._mtls_helper.get_client_cert_and_key", autospec=True + ) + def test_configure_mtls_channel_with_metadata( + self, mock_get_client_cert_and_key, mock_make_mutual_tls_http + ): + authed_http = google.auth.transport.urllib3.AuthorizedHttp( + credentials=mock.Mock() + ) + + mock_get_client_cert_and_key.return_value = ( + True, + pytest.public_cert_bytes, + pytest.private_key_bytes, + ) + is_mtls = authed_http.configure_mtls_channel() + + assert is_mtls + mock_get_client_cert_and_key.assert_called_once() + mock_make_mutual_tls_http.assert_called_once_with( + cert=pytest.public_cert_bytes, key=pytest.private_key_bytes + ) + + @mock.patch("google.auth.transport.urllib3._make_mutual_tls_http", autospec=True) + @mock.patch( + "google.auth.transport._mtls_helper.get_client_cert_and_key", autospec=True + ) + def test_configure_mtls_channel_non_mtls( + self, mock_get_client_cert_and_key, mock_make_mutual_tls_http + ): + authed_http = google.auth.transport.urllib3.AuthorizedHttp( + credentials=mock.Mock() + ) + + mock_get_client_cert_and_key.return_value = (False, None, None) + is_mtls = authed_http.configure_mtls_channel() + + assert not is_mtls + mock_get_client_cert_and_key.assert_called_once() + mock_make_mutual_tls_http.assert_not_called() From b0f511d7f09399ee1b9ebf238e085d95e8161436 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Fri, 20 Mar 2020 18:10:05 -0700 Subject: [PATCH 275/966] docs: fix typo for app engine setup, add detail to grpc and GCE test sections (#449) @arithmetic1728 --- packages/google-auth/CONTRIBUTING.rst | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/CONTRIBUTING.rst b/packages/google-auth/CONTRIBUTING.rst index bd92ca8d4ece..c04a6c63e5de 100644 --- a/packages/google-auth/CONTRIBUTING.rst +++ b/packages/google-auth/CONTRIBUTING.rst @@ -42,7 +42,9 @@ You can run the system tests with ``nox``:: To run a single session, specify it with ``nox -s``:: $ nox -f system_tests/noxfile.py -s service_account - + +First, set the environemnt variable ``GOOGLE_APPLICATION_CREDENTIALS`` to a valid service account. +See `Creating and Managing Service Account Keys`_ for how to obtain a service account. Project and Credentials Setup ------------------------------- @@ -113,13 +115,24 @@ If you already have a default service associated with your project, you can skip Edit ``app.yaml`` so ``service`` is ``default`` instead of ``google-auth-system-tests``. From ``system_tests/app_engine_test_app`` run the following commands :: - $ pip install --target-lib -r requirements.txt + $ pip install --target lib -r requirements.txt $ gcloud app deploy -q app.yaml After the app is deployed, change ``service`` in ``app.yaml`` back to ``google-auth-system-tests``. You can now run the App Engine tests: :: $ nox -f system_tests/noxfile.py -s app_engine + +Compute Engine Tests +^^^^^^^^^^^^^^^^^^^^ + +These tests cannot be run locally and will be skipped if they are run outside of Google Compute Engine. + +grpc Tests +^^^^^^^^^^^^ + +These tests use the Pub/Sub API. Grant the service account specified by `GOOGLE_APPLICATION_CREDENTIALS` +permissions to list topics. The service account should have at least `roles/pubsub.viewer`. Coding Style ------------ From 15246b894f5e7cc5091d0b619d8db786a4d705cb Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Mon, 23 Mar 2020 11:53:25 -0700 Subject: [PATCH 276/966] feat: fetch id token from GCE metadata server (#462) feat: fetch id token from GCE metadata server --- .../google/auth/compute_engine/credentials.py | 163 ++++++++++++++---- .../system_tests/test_compute_engine.py | 12 ++ .../tests/compute_engine/test_credentials.py | 139 +++++++++++++++ 3 files changed, 279 insertions(+), 35 deletions(-) diff --git a/packages/google-auth/google/auth/compute_engine/credentials.py b/packages/google-auth/google/auth/compute_engine/credentials.py index e35907abcebc..1927c26bd1f4 100644 --- a/packages/google-auth/google/auth/compute_engine/credentials.py +++ b/packages/google-auth/google/auth/compute_engine/credentials.py @@ -125,18 +125,24 @@ class IDTokenCredentials(credentials.Credentials, credentials.Signing): These credentials relies on the default service account of a GCE instance. - In order for this to work, the GCE instance must have been started with + ID token can be requested from `GCE metadata server identity endpoint`_, IAM + token endpoint or other token endpoints you specify. If metadata server + identity endpoint is not used, the GCE instance must have been started with a service account that has access to the IAM Cloud API. + + .. _GCE metadata server identity endpoint: + https://cloud.google.com/compute/docs/instances/verifying-instance-identity """ def __init__( self, request, target_audience, - token_uri=_DEFAULT_TOKEN_URI, + token_uri=None, additional_claims=None, service_account_email=None, signer=None, + use_metadata_identity_endpoint=False, ): """ Args: @@ -154,29 +160,54 @@ def __init__( signer (google.auth.crypt.Signer): The signer used to sign JWTs. In case the signer is specified, the request argument will be ignored. + use_metadata_identity_endpoint (bool): Whether to use GCE metadata + identity endpoint. For backward compatibility the default value + is False. If set to True, ``token_uri``, ``additional_claims``, + ``service_account_email``, ``signer`` argument should not be set; + otherwise ValueError will be raised. + + Raises: + ValueError: + If ``use_metadata_identity_endpoint`` is set to True, and one of + ``token_uri``, ``additional_claims``, ``service_account_email``, + ``signer`` arguments is set. """ super(IDTokenCredentials, self).__init__() - if service_account_email is None: - sa_info = _metadata.get_service_account_info(request) - service_account_email = sa_info["email"] - self._service_account_email = service_account_email - - if signer is None: - signer = iam.Signer( - request=request, - credentials=Credentials(), - service_account_email=service_account_email, - ) - self._signer = signer - - self._token_uri = token_uri + self._use_metadata_identity_endpoint = use_metadata_identity_endpoint self._target_audience = target_audience - if additional_claims is not None: - self._additional_claims = additional_claims + if use_metadata_identity_endpoint: + if token_uri or additional_claims or service_account_email or signer: + raise ValueError( + "If use_metadata_identity_endpoint is set, token_uri, " + "additional_claims, service_account_email, signer arguments" + " must not be set" + ) + self._token_uri = None + self._additional_claims = None + self._signer = None + + if service_account_email is None: + sa_info = _metadata.get_service_account_info(request) + self._service_account_email = sa_info["email"] else: - self._additional_claims = {} + self._service_account_email = service_account_email + + if not use_metadata_identity_endpoint: + if signer is None: + signer = iam.Signer( + request=request, + credentials=Credentials(), + service_account_email=self._service_account_email, + ) + self._signer = signer + self._token_uri = token_uri or _DEFAULT_TOKEN_URI + + if additional_claims is not None: + self._additional_claims = additional_claims + else: + self._additional_claims = {} def with_target_audience(self, target_audience): """Create a copy of these credentials with the specified target @@ -190,14 +221,22 @@ def with_target_audience(self, target_audience): """ # since the signer is already instantiated, # the request is not needed - return self.__class__( - None, - service_account_email=self._service_account_email, - token_uri=self._token_uri, - target_audience=target_audience, - additional_claims=self._additional_claims.copy(), - signer=self.signer, - ) + if self._use_metadata_identity_endpoint: + return self.__class__( + None, + target_audience=target_audience, + use_metadata_identity_endpoint=True, + ) + else: + return self.__class__( + None, + service_account_email=self._service_account_email, + token_uri=self._token_uri, + target_audience=target_audience, + additional_claims=self._additional_claims.copy(), + signer=self.signer, + use_metadata_identity_endpoint=False, + ) def _make_authorization_grant_assertion(self): """Create the OAuth 2.0 assertion. @@ -228,22 +267,76 @@ def _make_authorization_grant_assertion(self): return token - @_helpers.copy_docstring(credentials.Credentials) + def _call_metadata_identity_endpoint(self, request): + """Request ID token from metadata identity endpoint. + + Args: + request (google.auth.transport.Request): The object used to make + HTTP requests. + + Raises: + google.auth.exceptions.RefreshError: If the Compute Engine metadata + service can't be reached or if the instance has no credentials. + ValueError: If extracting expiry from the obtained ID token fails. + """ + try: + id_token = _metadata.get( + request, + "instance/service-accounts/default/identity?audience={}&format=full".format( + self._target_audience + ), + ) + except exceptions.TransportError as caught_exc: + new_exc = exceptions.RefreshError(caught_exc) + six.raise_from(new_exc, caught_exc) + + _, payload, _, _ = jwt._unverified_decode(id_token) + return id_token, payload["exp"] + def refresh(self, request): - assertion = self._make_authorization_grant_assertion() - access_token, expiry, _ = _client.id_token_jwt_grant( - request, self._token_uri, assertion - ) - self.token = access_token - self.expiry = expiry + """Refreshes the ID token. + + Args: + request (google.auth.transport.Request): The object used to make + HTTP requests. + + Raises: + google.auth.exceptions.RefreshError: If the credentials could + not be refreshed. + ValueError: If extracting expiry from the obtained ID token fails. + """ + if self._use_metadata_identity_endpoint: + self.token, self.expiry = self._call_metadata_identity_endpoint(request) + else: + assertion = self._make_authorization_grant_assertion() + access_token, expiry, _ = _client.id_token_jwt_grant( + request, self._token_uri, assertion + ) + self.token = access_token + self.expiry = expiry @property @_helpers.copy_docstring(credentials.Signing) def signer(self): return self._signer - @_helpers.copy_docstring(credentials.Signing) def sign_bytes(self, message): + """Signs the given message. + + Args: + message (bytes): The message to sign. + + Returns: + bytes: The message's cryptographic signature. + + Raises: + ValueError: + Signer is not available if metadata identity endpoint is used. + """ + if self._use_metadata_identity_endpoint: + raise ValueError( + "Signer is not available if metadata identity endpoint is used" + ) return self._signer.sign(message) @property diff --git a/packages/google-auth/system_tests/test_compute_engine.py b/packages/google-auth/system_tests/test_compute_engine.py index 3217c958a75b..bcfdfd604a34 100644 --- a/packages/google-auth/system_tests/test_compute_engine.py +++ b/packages/google-auth/system_tests/test_compute_engine.py @@ -18,6 +18,7 @@ from google.auth import compute_engine from google.auth import _helpers from google.auth import exceptions +from google.auth import jwt from google.auth.compute_engine import _metadata @@ -48,3 +49,14 @@ def test_default(verify_refresh): assert project_id is not None assert isinstance(credentials, compute_engine.Credentials) verify_refresh(credentials) + + +def test_id_token_from_metadata(http_request): + credentials = compute_engine.IDTokenCredentials( + http_request, "target_audience", use_metadata_identity_endpoint=True + ) + credentials.refresh(http_request) + + _, payload, _, _ = jwt._unverified_decode(credentials.token) + assert payload["aud"] == "target_audience" + assert payload["exp"] == credentials.expiry diff --git a/packages/google-auth/tests/compute_engine/test_credentials.py b/packages/google-auth/tests/compute_engine/test_credentials.py index b861984e046a..264235e49a64 100644 --- a/packages/google-auth/tests/compute_engine/test_credentials.py +++ b/packages/google-auth/tests/compute_engine/test_credentials.py @@ -25,6 +25,24 @@ from google.auth.compute_engine import credentials from google.auth.transport import requests +SAMPLE_ID_TOKEN_EXP = 1584393400 + +# header: {"alg": "RS256", "typ": "JWT", "kid": "1"} +# payload: {"iss": "issuer", "iat": 1584393348, "sub": "subject", +# "exp": 1584393400,"aud": "audience"} +SAMPLE_ID_TOKEN = ( + b"eyJhbGciOiAiUlMyNTYiLCAidHlwIjogIkpXVCIsICJraWQiOiAiMSJ9." + b"eyJpc3MiOiAiaXNzdWVyIiwgImlhdCI6IDE1ODQzOTMzNDgsICJzdWIiO" + b"iAic3ViamVjdCIsICJleHAiOiAxNTg0MzkzNDAwLCAiYXVkIjogImF1ZG" + b"llbmNlIn0." + b"OquNjHKhTmlgCk361omRo18F_uY-7y0f_AmLbzW062Q1Zr61HAwHYP5FM" + b"316CK4_0cH8MUNGASsvZc3VqXAqub6PUTfhemH8pFEwBdAdG0LhrNkU0H" + b"WN1YpT55IiQ31esLdL5q-qDsOPpNZJUti1y1lAreM5nIn2srdWzGXGs4i" + b"TRQsn0XkNUCL4RErpciXmjfhMrPkcAjKA-mXQm2fa4jmTlEZFqFmUlym1" + b"ozJ0yf5grjN6AslN4OGvAv1pS-_Ko_pGBS6IQtSBC6vVKCUuBfaqNjykg" + b"bsxbLa6Fp0SYeYwO8ifEnkRvasVpc1WTQqfRB2JCj5pTBDzJpIpFCMmnQ" +) + class TestCredentials(object): credentials = None @@ -238,6 +256,26 @@ def test_additional_claims(self, sign, get, utcnow): "foo": "bar", } + def test_token_uri(self): + request = mock.create_autospec(transport.Request, instance=True) + + self.credentials = credentials.IDTokenCredentials( + request=request, + signer=mock.Mock(), + service_account_email="foo@example.com", + target_audience="https://audience.com", + ) + assert self.credentials._token_uri == credentials._DEFAULT_TOKEN_URI + + self.credentials = credentials.IDTokenCredentials( + request=request, + signer=mock.Mock(), + service_account_email="foo@example.com", + target_audience="https://audience.com", + token_uri="https://example.com/token", + ) + assert self.credentials._token_uri == "https://example.com/token" + @mock.patch( "google.auth._helpers.utcnow", return_value=datetime.datetime.utcfromtimestamp(0), @@ -469,3 +507,104 @@ def test_sign_bytes(self, sign, get): # The JWT token signature is 'signature' encoded in base 64: assert signature == b"signature" + + @mock.patch( + "google.auth.compute_engine._metadata.get_service_account_info", autospec=True + ) + @mock.patch("google.auth.compute_engine._metadata.get", autospec=True) + def test_get_id_token_from_metadata(self, get, get_service_account_info): + get.return_value = SAMPLE_ID_TOKEN + get_service_account_info.return_value = {"email": "foo@example.com"} + + cred = credentials.IDTokenCredentials( + mock.Mock(), "audience", use_metadata_identity_endpoint=True + ) + cred.refresh(request=mock.Mock()) + + assert cred.token == SAMPLE_ID_TOKEN + assert cred.expiry == SAMPLE_ID_TOKEN_EXP + assert cred._use_metadata_identity_endpoint + assert cred._signer is None + assert cred._token_uri is None + assert cred._service_account_email == "foo@example.com" + assert cred._target_audience == "audience" + with pytest.raises(ValueError): + cred.sign_bytes(b"bytes") + + @mock.patch( + "google.auth.compute_engine._metadata.get_service_account_info", autospec=True + ) + def test_with_target_audience_for_metadata(self, get_service_account_info): + get_service_account_info.return_value = {"email": "foo@example.com"} + + cred = credentials.IDTokenCredentials( + mock.Mock(), "audience", use_metadata_identity_endpoint=True + ) + cred = cred.with_target_audience("new_audience") + + assert cred._target_audience == "new_audience" + assert cred._use_metadata_identity_endpoint + assert cred._signer is None + assert cred._token_uri is None + assert cred._service_account_email == "foo@example.com" + + @mock.patch( + "google.auth.compute_engine._metadata.get_service_account_info", autospec=True + ) + @mock.patch("google.auth.compute_engine._metadata.get", autospec=True) + def test_invalid_id_token_from_metadata(self, get, get_service_account_info): + get.return_value = "invalid_id_token" + get_service_account_info.return_value = {"email": "foo@example.com"} + + cred = credentials.IDTokenCredentials( + mock.Mock(), "audience", use_metadata_identity_endpoint=True + ) + + with pytest.raises(ValueError): + cred.refresh(request=mock.Mock()) + + @mock.patch( + "google.auth.compute_engine._metadata.get_service_account_info", autospec=True + ) + @mock.patch("google.auth.compute_engine._metadata.get", autospec=True) + def test_transport_error_from_metadata(self, get, get_service_account_info): + get.side_effect = exceptions.TransportError("transport error") + get_service_account_info.return_value = {"email": "foo@example.com"} + + cred = credentials.IDTokenCredentials( + mock.Mock(), "audience", use_metadata_identity_endpoint=True + ) + + with pytest.raises(exceptions.RefreshError) as excinfo: + cred.refresh(request=mock.Mock()) + assert excinfo.match(r"transport error") + + def test_get_id_token_from_metadata_constructor(self): + with pytest.raises(ValueError): + credentials.IDTokenCredentials( + mock.Mock(), + "audience", + use_metadata_identity_endpoint=True, + token_uri="token_uri", + ) + with pytest.raises(ValueError): + credentials.IDTokenCredentials( + mock.Mock(), + "audience", + use_metadata_identity_endpoint=True, + signer=mock.Mock(), + ) + with pytest.raises(ValueError): + credentials.IDTokenCredentials( + mock.Mock(), + "audience", + use_metadata_identity_endpoint=True, + additional_claims={"key", "value"}, + ) + with pytest.raises(ValueError): + credentials.IDTokenCredentials( + mock.Mock(), + "audience", + use_metadata_identity_endpoint=True, + service_account_email="foo@example.com", + ) From 8d3d791a9a36c8b0016260a1485784ebfd8a751c Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Wed, 25 Mar 2020 11:17:25 -0700 Subject: [PATCH 277/966] fix: don't use threads for gRPC AuthMetadataPlugin (#467) --- .../google-auth/google/auth/transport/grpc.py | 20 +------------------ 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/packages/google-auth/google/auth/transport/grpc.py b/packages/google-auth/google/auth/transport/grpc.py index 5ce6b2b25dd0..32ffabcae604 100644 --- a/packages/google-auth/google/auth/transport/grpc.py +++ b/packages/google-auth/google/auth/transport/grpc.py @@ -16,7 +16,6 @@ from __future__ import absolute_import -from concurrent import futures import logging import six @@ -51,15 +50,6 @@ class AuthMetadataPlugin(grpc.AuthMetadataPlugin): object used to refresh credentials as needed. """ - # Python 2.7 has no default for max_workers. - # In Python >= 3.5, ThreadPoolExecutor defaults to the - # number of processors on the machine, multiplied by 5. - if six.PY2: # pragma: NO COVER - max_workers = 5 - else: - max_workers = None - _AUTH_THREAD_POOL = futures.ThreadPoolExecutor(max_workers=max_workers) - def __init__(self, credentials, request): # pylint: disable=no-value-for-parameter # pylint doesn't realize that the super method takes no arguments @@ -82,13 +72,6 @@ def _get_authorization_headers(self, context): return list(six.iteritems(headers)) - @staticmethod - def _callback_wrapper(callback): - def wrapped(future): - callback(future.result(), None) - - return wrapped - def __call__(self, context, callback): """Passes authorization metadata into the given callback. @@ -97,8 +80,7 @@ def __call__(self, context, callback): callback (grpc.AuthMetadataPluginCallback): The callback that will be invoked to pass in the authorization metadata. """ - future = self._AUTH_THREAD_POOL.submit(self._get_authorization_headers, context) - future.add_done_callback(self._callback_wrapper(callback)) + callback(self._get_authorization_headers(context), None) def secure_authorized_channel( From 8ada9dc692126348ef599ff30464a248498069e4 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Wed, 25 Mar 2020 20:04:04 +0000 Subject: [PATCH 278/966] chore: release 1.12.0 (#463) :robot: I have created a release \*beep\* \*boop\* --- ## [1.12.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.11.3...v1.12.0) (2020-03-25) ### Features * add mTLS ADC support for HTTP ([#457](https://www.github.com/googleapis/google-auth-library-python/issues/457)) ([bb9215a](https://www.github.com/googleapis/google-auth-library-python/commit/bb9215ad6dee6c1dc7f255a2e4ed7011b85bd6cf)) * add SslCredentials class for mTLS ADC ([#448](https://www.github.com/googleapis/google-auth-library-python/issues/448)) ([dafb41f](https://www.github.com/googleapis/google-auth-library-python/commit/dafb41fae3f513ea9a4f93404f6148bee7dda202)) * fetch id token from GCE metadata server ([#462](https://www.github.com/googleapis/google-auth-library-python/issues/462)) ([97e7700](https://www.github.com/googleapis/google-auth-library-python/commit/97e7700da031bfd80b63b1a3d2abc29c500936ef)) ### Bug Fixes * don't use threads for gRPC AuthMetadataPlugin ([#467](https://www.github.com/googleapis/google-auth-library-python/issues/467)) ([ee373f8](https://www.github.com/googleapis/google-auth-library-python/commit/ee373f88b512a38e791a1c085452c6c6da501eb6)) * make ThreadPoolExecutor a class var ([#461](https://www.github.com/googleapis/google-auth-library-python/issues/461)) ([b526473](https://www.github.com/googleapis/google-auth-library-python/commit/b5264730603947295cc97ecff2f6aef84aa3d6e9)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). --- packages/google-auth/CHANGELOG.md | 15 +++++++++++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 96a15f463b5d..3a4393ea1d4c 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,21 @@ [1]: https://pypi.org/project/google-auth/#history +## [1.12.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.11.3...v1.12.0) (2020-03-25) + + +### Features + +* add mTLS ADC support for HTTP ([#457](https://www.github.com/googleapis/google-auth-library-python/issues/457)) ([bb9215a](https://www.github.com/googleapis/google-auth-library-python/commit/bb9215ad6dee6c1dc7f255a2e4ed7011b85bd6cf)) +* add SslCredentials class for mTLS ADC ([#448](https://www.github.com/googleapis/google-auth-library-python/issues/448)) ([dafb41f](https://www.github.com/googleapis/google-auth-library-python/commit/dafb41fae3f513ea9a4f93404f6148bee7dda202)) +* fetch id token from GCE metadata server ([#462](https://www.github.com/googleapis/google-auth-library-python/issues/462)) ([97e7700](https://www.github.com/googleapis/google-auth-library-python/commit/97e7700da031bfd80b63b1a3d2abc29c500936ef)) + + +### Bug Fixes + +* don't use threads for gRPC AuthMetadataPlugin ([#467](https://www.github.com/googleapis/google-auth-library-python/issues/467)) ([ee373f8](https://www.github.com/googleapis/google-auth-library-python/commit/ee373f88b512a38e791a1c085452c6c6da501eb6)) +* make ThreadPoolExecutor a class var ([#461](https://www.github.com/googleapis/google-auth-library-python/issues/461)) ([b526473](https://www.github.com/googleapis/google-auth-library-python/commit/b5264730603947295cc97ecff2f6aef84aa3d6e9)) + ### [1.11.3](https://www.github.com/googleapis/google-auth-library-python/compare/v1.11.2...v1.11.3) (2020-03-13) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index f80dc1de7608..8ae5e090c554 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -30,7 +30,7 @@ with io.open("README.rst", "r") as fh: long_description = fh.read() -version = "1.11.3" +version = "1.12.0" setup( name="google-auth", From 3056f5c46d04ca917629ae63cd8c4966d9c9936f Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Fri, 27 Mar 2020 14:34:13 -0700 Subject: [PATCH 279/966] feat: add access token credentials (#476) feat: add access token credentials --- .../google-auth/google/auth/_cloud_sdk.py | 60 ++++++++++++++----- packages/google-auth/google/auth/_default.py | 4 +- .../google-auth/google/auth/exceptions.py | 4 ++ .../google-auth/google/oauth2/credentials.py | 48 +++++++++++++++ .../system_tests/test_mtls_http.py | 7 +++ .../tests/oauth2/test_credentials.py | 28 +++++++++ packages/google-auth/tests/test__cloud_sdk.py | 40 ++++++++----- 7 files changed, 158 insertions(+), 33 deletions(-) diff --git a/packages/google-auth/google/auth/_cloud_sdk.py b/packages/google-auth/google/auth/_cloud_sdk.py index 61ffd4f5c563..e772fe964154 100644 --- a/packages/google-auth/google/auth/_cloud_sdk.py +++ b/packages/google-auth/google/auth/_cloud_sdk.py @@ -18,8 +18,10 @@ import os import subprocess +import six + from google.auth import environment_vars -import google.oauth2.credentials +from google.auth import exceptions # The ~/.config subdirectory containing gcloud credentials. @@ -34,6 +36,8 @@ _CLOUD_SDK_WINDOWS_COMMAND = "gcloud.cmd" # The command to get the Cloud SDK configuration _CLOUD_SDK_CONFIG_COMMAND = ("config", "config-helper", "--format", "json") +# The command to get google user access token +_CLOUD_SDK_USER_ACCESS_TOKEN_COMMAND = ("auth", "print-access-token") # Cloud SDK's application-default client ID CLOUD_SDK_CLIENT_ID = ( "764086051850-6qr4p6gpi6hn506pt8ejuq83di341hur.apps.googleusercontent.com" @@ -80,21 +84,6 @@ def get_application_default_credentials_path(): return os.path.join(config_path, _CREDENTIALS_FILENAME) -def load_authorized_user_credentials(info): - """Loads an authorized user credential. - - Args: - info (Mapping[str, str]): The loaded file's data. - - Returns: - google.oauth2.credentials.Credentials: The constructed credentials. - - Raises: - ValueError: if the info is in the wrong format or missing data. - """ - return google.oauth2.credentials.Credentials.from_authorized_user_info(info) - - def get_project_id(): """Gets the project ID from the Cloud SDK. @@ -122,3 +111,42 @@ def get_project_id(): return configuration["configuration"]["properties"]["core"]["project"] except KeyError: return None + + +def get_auth_access_token(account=None): + """Load user access token with the ``gcloud auth print-access-token`` command. + + Args: + account (Optional[str]): Account to get the access token for. If not + specified, the current active account will be used. + + Returns: + str: The user access token. + + Raises: + google.auth.exceptions.UserAccessTokenError: if failed to get access + token from gcloud. + """ + if os.name == "nt": + command = _CLOUD_SDK_WINDOWS_COMMAND + else: + command = _CLOUD_SDK_POSIX_COMMAND + + try: + if account: + command = ( + (command,) + + _CLOUD_SDK_USER_ACCESS_TOKEN_COMMAND + + ("--account=" + account,) + ) + else: + command = (command,) + _CLOUD_SDK_USER_ACCESS_TOKEN_COMMAND + + access_token = subprocess.check_output(command, stderr=subprocess.STDOUT) + # remove the trailing "\n" + return access_token.decode("utf-8").strip() + except (subprocess.CalledProcessError, OSError, IOError) as caught_exc: + new_exc = exceptions.UserAccessTokenError( + "Failed to obtain access token", caught_exc + ) + six.raise_from(new_exc, caught_exc) diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index 32e81ba5faa4..d7110a10dfc2 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -106,10 +106,10 @@ def _load_credentials_from_file(filename): credential_type = info.get("type") if credential_type == _AUTHORIZED_USER_TYPE: - from google.auth import _cloud_sdk + from google.oauth2 import credentials try: - credentials = _cloud_sdk.load_authorized_user_credentials(info) + credentials = credentials.Credentials.from_authorized_user_info(info) except ValueError as caught_exc: msg = "Failed to load authorized user credentials from {}".format(filename) new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) diff --git a/packages/google-auth/google/auth/exceptions.py b/packages/google-auth/google/auth/exceptions.py index e034c55cde6e..4f66dc2a000d 100644 --- a/packages/google-auth/google/auth/exceptions.py +++ b/packages/google-auth/google/auth/exceptions.py @@ -28,5 +28,9 @@ class RefreshError(GoogleAuthError): failed.""" +class UserAccessTokenError(GoogleAuthError): + """Used to indicate ``gcloud auth print-access-token`` command failed.""" + + class DefaultCredentialsError(GoogleAuthError): """Used to indicate that acquiring default credentials failed.""" diff --git a/packages/google-auth/google/oauth2/credentials.py b/packages/google-auth/google/oauth2/credentials.py index 1adcbf6755f9..baf3cf7f48cc 100644 --- a/packages/google-auth/google/oauth2/credentials.py +++ b/packages/google-auth/google/oauth2/credentials.py @@ -36,6 +36,7 @@ import six +from google.auth import _cloud_sdk from google.auth import _helpers from google.auth import credentials from google.auth import exceptions @@ -292,3 +293,50 @@ def to_json(self, strip=None): prep = {k: v for k, v in prep.items() if k not in strip} return json.dumps(prep) + + +class UserAccessTokenCredentials(credentials.Credentials): + """Access token credentials for user account. + + Obtain the access token for a given user account or the current active + user account with the ``gcloud auth print-access-token`` command. + + Args: + account (Optional[str]): Account to get the access token for. If not + specified, the current active account will be used. + """ + + def __init__(self, account=None): + super(UserAccessTokenCredentials, self).__init__() + self._account = account + + def with_account(self, account): + """Create a new instance with the given account. + + Args: + account (str): Account to get the access token for. + + Returns: + google.oauth2.credentials.UserAccessTokenCredentials: The created + credentials with the given account. + """ + return self.__class__(account=account) + + def refresh(self, request): + """Refreshes the access token. + + Args: + request (google.auth.transport.Request): This argument is required + by the base class interface but not used in this implementation, + so just set it to `None`. + + Raises: + google.auth.exceptions.UserAccessTokenError: If the access token + refresh failed. + """ + self.token = _cloud_sdk.get_auth_access_token(self._account) + + @_helpers.copy_docstring(credentials.Credentials) + def before_request(self, request, method, url, headers): + self.refresh(request) + self.apply(headers) diff --git a/packages/google-auth/system_tests/test_mtls_http.py b/packages/google-auth/system_tests/test_mtls_http.py index e7ea0b2423ab..1fd80311d78f 100644 --- a/packages/google-auth/system_tests/test_mtls_http.py +++ b/packages/google-auth/system_tests/test_mtls_http.py @@ -14,6 +14,7 @@ import json from os import path +import time import google.auth import google.auth.credentials @@ -42,6 +43,9 @@ def test_requests(): # supposed to be created. assert authed_session.is_mtls == check_context_aware_metadata() + # Sleep 1 second to avoid 503 error. + time.sleep(1) + if authed_session.is_mtls: response = authed_session.get(MTLS_ENDPOINT.format(project_id)) else: @@ -63,6 +67,9 @@ def test_urllib3(): # supposed to be created. assert is_mtls == check_context_aware_metadata() + # Sleep 1 second to avoid 503 error. + time.sleep(1) + if is_mtls: response = authed_http.request("GET", MTLS_ENDPOINT.format(project_id)) else: diff --git a/packages/google-auth/tests/oauth2/test_credentials.py b/packages/google-auth/tests/oauth2/test_credentials.py index bdb63e9dd82c..76aa463cbfd1 100644 --- a/packages/google-auth/tests/oauth2/test_credentials.py +++ b/packages/google-auth/tests/oauth2/test_credentials.py @@ -421,3 +421,31 @@ def test_unpickle_old_credentials_pickle(self): ) as f: credentials = pickle.load(f) assert credentials.quota_project_id is None + + +class TestUserAccessTokenCredentials(object): + def test_instance(self): + cred = credentials.UserAccessTokenCredentials() + assert cred._account is None + + cred = cred.with_account("account") + assert cred._account == "account" + + @mock.patch("google.auth._cloud_sdk.get_auth_access_token", autospec=True) + def test_refresh(self, get_auth_access_token): + get_auth_access_token.return_value = "access_token" + cred = credentials.UserAccessTokenCredentials() + cred.refresh(None) + assert cred.token == "access_token" + + @mock.patch( + "google.oauth2.credentials.UserAccessTokenCredentials.apply", autospec=True + ) + @mock.patch( + "google.oauth2.credentials.UserAccessTokenCredentials.refresh", autospec=True + ) + def test_before_request(self, refresh, apply): + cred = credentials.UserAccessTokenCredentials() + cred.before_request(mock.Mock(), "GET", "https://example.com", {}) + refresh.assert_called() + apply.assert_called() diff --git a/packages/google-auth/tests/test__cloud_sdk.py b/packages/google-auth/tests/test__cloud_sdk.py index 049ed9978275..337760426056 100644 --- a/packages/google-auth/tests/test__cloud_sdk.py +++ b/packages/google-auth/tests/test__cloud_sdk.py @@ -22,7 +22,7 @@ from google.auth import _cloud_sdk from google.auth import environment_vars -import google.oauth2.credentials +from google.auth import exceptions DATA_DIR = os.path.join(os.path.dirname(__file__), "data") @@ -137,23 +137,33 @@ def test_get_config_path_no_appdata(monkeypatch): assert os.path.split(config_path) == ("G:/\\", _cloud_sdk._CONFIG_DIRECTORY) -def test_load_authorized_user_credentials(): - credentials = _cloud_sdk.load_authorized_user_credentials(AUTHORIZED_USER_FILE_DATA) +@mock.patch("os.name", new="nt") +@mock.patch("subprocess.check_output", autospec=True) +def test_get_auth_access_token_windows(check_output): + check_output.return_value = b"access_token\n" + + token = _cloud_sdk.get_auth_access_token() + assert token == "access_token" + check_output.assert_called_with( + ("gcloud.cmd", "auth", "print-access-token"), stderr=subprocess.STDOUT + ) + - assert isinstance(credentials, google.oauth2.credentials.Credentials) +@mock.patch("subprocess.check_output", autospec=True) +def test_get_auth_access_token_with_account(check_output): + check_output.return_value = b"access_token\n" - assert credentials.token is None - assert credentials._refresh_token == AUTHORIZED_USER_FILE_DATA["refresh_token"] - assert credentials._client_id == AUTHORIZED_USER_FILE_DATA["client_id"] - assert credentials._client_secret == AUTHORIZED_USER_FILE_DATA["client_secret"] - assert ( - credentials._token_uri - == google.oauth2.credentials._GOOGLE_OAUTH2_TOKEN_ENDPOINT + token = _cloud_sdk.get_auth_access_token(account="account") + assert token == "access_token" + check_output.assert_called_with( + ("gcloud", "auth", "print-access-token", "--account=account"), + stderr=subprocess.STDOUT, ) -def test_load_authorized_user_credentials_bad_format(): - with pytest.raises(ValueError) as excinfo: - _cloud_sdk.load_authorized_user_credentials({}) +@mock.patch("subprocess.check_output", autospec=True) +def test_get_auth_access_token_with_exception(check_output): + check_output.side_effect = OSError() - assert excinfo.match(r"missing fields") + with pytest.raises(exceptions.UserAccessTokenError): + _cloud_sdk.get_auth_access_token(account="account") From 1fea72d222dfba5d62b9cfada418ebd25bd2cfba Mon Sep 17 00:00:00 2001 From: Thea Flowers Date: Wed, 1 Apr 2020 10:11:42 -0700 Subject: [PATCH 280/966] feat: Implement ES256 for JWT verification (#340) feat: Implement EC256 for JWT verification --- .../reference/google.auth.crypt.es256.rst | 7 + .../docs/reference/google.auth.crypt.rst | 1 + .../google-auth/docs/requirements-docs.txt | 1 + packages/google-auth/docs/user-guide.rst | 16 +- .../google-auth/google/auth/crypt/__init__.py | 34 +++- .../google-auth/google/auth/crypt/es256.py | 145 ++++++++++++++++++ packages/google-auth/google/auth/jwt.py | 43 +++++- .../google-auth/tests/crypt/test_es256.py | 131 ++++++++++++++++ .../tests/data/es256_privatekey.pem | 5 + .../tests/data/es256_public_cert.pem | 8 + .../tests/data/es256_publickey.pem | 4 + .../tests/data/es256_service_account.json | 10 ++ packages/google-auth/tests/test_jwt.py | 60 +++++++- 13 files changed, 454 insertions(+), 11 deletions(-) create mode 100644 packages/google-auth/docs/reference/google.auth.crypt.es256.rst create mode 100644 packages/google-auth/google/auth/crypt/es256.py create mode 100644 packages/google-auth/tests/crypt/test_es256.py create mode 100644 packages/google-auth/tests/data/es256_privatekey.pem create mode 100644 packages/google-auth/tests/data/es256_public_cert.pem create mode 100644 packages/google-auth/tests/data/es256_publickey.pem create mode 100644 packages/google-auth/tests/data/es256_service_account.json diff --git a/packages/google-auth/docs/reference/google.auth.crypt.es256.rst b/packages/google-auth/docs/reference/google.auth.crypt.es256.rst new file mode 100644 index 000000000000..5a6318482998 --- /dev/null +++ b/packages/google-auth/docs/reference/google.auth.crypt.es256.rst @@ -0,0 +1,7 @@ +google.auth.crypt.es256 module +============================== + +.. automodule:: google.auth.crypt.es256 + :members: + :inherited-members: + :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.auth.crypt.rst b/packages/google-auth/docs/reference/google.auth.crypt.rst index 0833e7f2ff92..be142f428912 100644 --- a/packages/google-auth/docs/reference/google.auth.crypt.rst +++ b/packages/google-auth/docs/reference/google.auth.crypt.rst @@ -12,4 +12,5 @@ Submodules .. toctree:: google.auth.crypt.base + google.auth.crypt.es256 google.auth.crypt.rsa diff --git a/packages/google-auth/docs/requirements-docs.txt b/packages/google-auth/docs/requirements-docs.txt index 8dabaf9d6315..89ad689a719a 100644 --- a/packages/google-auth/docs/requirements-docs.txt +++ b/packages/google-auth/docs/requirements-docs.txt @@ -1,3 +1,4 @@ +cryptography sphinx-docstring-typing urllib3 requests diff --git a/packages/google-auth/docs/user-guide.rst b/packages/google-auth/docs/user-guide.rst index 0abe160a3bee..3877bff5950c 100644 --- a/packages/google-auth/docs/user-guide.rst +++ b/packages/google-auth/docs/user-guide.rst @@ -291,7 +291,21 @@ Impersonated :: target_credentials, target_audience=target_audience) -IDToken verification can be done for various type of IDTokens using the :class:`google.oauth2.id_token` module +IDToken verification can be done for various type of IDTokens using the +:class:`google.oauth2.id_token` module. It supports ID token signed with RS256 +and ES256 algorithms. However, ES256 algorithm won't be available unless +`cryptography` dependency of version at least 1.4.0 is installed. You can check +the dependency with `pip freeze` or try `from google.auth.crypt import es256`. +The following is an example of verifying ID tokens: + + from google.auth2 import id_token + + request = google.auth.transport.requests.Request() + + try: + decoded_token = id_token.verify_token(token_to_verify,request) + except ValueError: + # Verification failed. A sample end-to-end flow using an ID Token against a Cloud Run endpoint maybe :: diff --git a/packages/google-auth/google/auth/crypt/__init__.py b/packages/google-auth/google/auth/crypt/__init__.py index 39929fa0a922..15ac95068625 100644 --- a/packages/google-auth/google/auth/crypt/__init__.py +++ b/packages/google-auth/google/auth/crypt/__init__.py @@ -31,6 +31,10 @@ private_key = open('private_key.pem').read() signer = crypt.RSASigner.from_string(private_key) signature = signer.sign(message) + +The code above also works for :class:`ES256Signer` and :class:`ES256Verifier`. +Note that these two classes are only available if your `cryptography` dependency +version is at least 1.4.0. """ import six @@ -38,8 +42,23 @@ from google.auth.crypt import base from google.auth.crypt import rsa +try: + from google.auth.crypt import es256 +except ImportError: # pragma: NO COVER + es256 = None + +if es256 is not None: # pragma: NO COVER + __all__ = [ + "ES256Signer", + "ES256Verifier", + "RSASigner", + "RSAVerifier", + "Signer", + "Verifier", + ] +else: # pragma: NO COVER + __all__ = ["RSASigner", "RSAVerifier", "Signer", "Verifier"] -__all__ = ["RSASigner", "RSAVerifier", "Signer", "Verifier"] # Aliases to maintain the v1.0.0 interface, as the crypt module was split # into submodules. @@ -48,9 +67,13 @@ RSASigner = rsa.RSASigner RSAVerifier = rsa.RSAVerifier +if es256 is not None: # pragma: NO COVER + ES256Signer = es256.ES256Signer + ES256Verifier = es256.ES256Verifier + -def verify_signature(message, signature, certs): - """Verify an RSA cryptographic signature. +def verify_signature(message, signature, certs, verifier_cls=rsa.RSAVerifier): + """Verify an RSA or ECDSA cryptographic signature. Checks that the provided ``signature`` was generated from ``bytes`` using the private key associated with the ``cert``. @@ -60,6 +83,9 @@ def verify_signature(message, signature, certs): signature (Union[str, bytes]): The cryptographic signature to check. certs (Union[Sequence, str, bytes]): The certificate or certificates to use to check the signature. + verifier_cls (Optional[~google.auth.crypt.base.Signer]): Which verifier + class to use for verification. This can be used to select different + algorithms, such as RSA or ECDSA. Default value is :class:`RSAVerifier`. Returns: bool: True if the signature is valid, otherwise False. @@ -68,7 +94,7 @@ def verify_signature(message, signature, certs): certs = [certs] for cert in certs: - verifier = rsa.RSAVerifier.from_string(cert) + verifier = verifier_cls.from_string(cert) if verifier.verify(message, signature): return True return False diff --git a/packages/google-auth/google/auth/crypt/es256.py b/packages/google-auth/google/auth/crypt/es256.py new file mode 100644 index 000000000000..5bfd57fb8838 --- /dev/null +++ b/packages/google-auth/google/auth/crypt/es256.py @@ -0,0 +1,145 @@ +# Copyright 2017 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""ECDSA (ES256) verifier and signer that use the ``cryptography`` library. +""" + +import cryptography.exceptions +from cryptography.hazmat import backends +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives.asymmetric import padding +import cryptography.x509 +import pkg_resources + +from google.auth import _helpers +from google.auth.crypt import base + +_IMPORT_ERROR_MSG = ( + "cryptography>=1.4.0 is required to use cryptography-based ECDSA " "algorithms" +) + +try: # pragma: NO COVER + release = pkg_resources.get_distribution("cryptography").parsed_version + if release < pkg_resources.parse_version("1.4.0"): + raise ImportError(_IMPORT_ERROR_MSG) +except pkg_resources.DistributionNotFound: # pragma: NO COVER + raise ImportError(_IMPORT_ERROR_MSG) + + +_CERTIFICATE_MARKER = b"-----BEGIN CERTIFICATE-----" +_BACKEND = backends.default_backend() +_PADDING = padding.PKCS1v15() + + +class ES256Verifier(base.Verifier): + """Verifies ECDSA cryptographic signatures using public keys. + + Args: + public_key ( + cryptography.hazmat.primitives.asymmetric.ec.ECDSAPublicKey): + The public key used to verify signatures. + """ + + def __init__(self, public_key): + self._pubkey = public_key + + @_helpers.copy_docstring(base.Verifier) + def verify(self, message, signature): + message = _helpers.to_bytes(message) + try: + self._pubkey.verify(signature, message, ec.ECDSA(hashes.SHA256())) + return True + except (ValueError, cryptography.exceptions.InvalidSignature): + return False + + @classmethod + def from_string(cls, public_key): + """Construct an Verifier instance from a public key or public + certificate string. + + Args: + public_key (Union[str, bytes]): The public key in PEM format or the + x509 public key certificate. + + Returns: + Verifier: The constructed verifier. + + Raises: + ValueError: If the public key can't be parsed. + """ + public_key_data = _helpers.to_bytes(public_key) + + if _CERTIFICATE_MARKER in public_key_data: + cert = cryptography.x509.load_pem_x509_certificate( + public_key_data, _BACKEND + ) + pubkey = cert.public_key() + + else: + pubkey = serialization.load_pem_public_key(public_key_data, _BACKEND) + + return cls(pubkey) + + +class ES256Signer(base.Signer, base.FromServiceAccountMixin): + """Signs messages with an ECDSA private key. + + Args: + private_key ( + cryptography.hazmat.primitives.asymmetric.ec.ECDSAPrivateKey): + The private key to sign with. + key_id (str): Optional key ID used to identify this private key. This + can be useful to associate the private key with its associated + public key or certificate. + """ + + def __init__(self, private_key, key_id=None): + self._key = private_key + self._key_id = key_id + + @property + @_helpers.copy_docstring(base.Signer) + def key_id(self): + return self._key_id + + @_helpers.copy_docstring(base.Signer) + def sign(self, message): + message = _helpers.to_bytes(message) + return self._key.sign(message, ec.ECDSA(hashes.SHA256())) + + @classmethod + def from_string(cls, key, key_id=None): + """Construct a RSASigner from a private key in PEM format. + + Args: + key (Union[bytes, str]): Private key in PEM format. + key_id (str): An optional key id used to identify the private key. + + Returns: + google.auth.crypt._cryptography_rsa.RSASigner: The + constructed signer. + + Raises: + ValueError: If ``key`` is not ``bytes`` or ``str`` (unicode). + UnicodeDecodeError: If ``key`` is ``bytes`` but cannot be decoded + into a UTF-8 ``str``. + ValueError: If ``cryptography`` "Could not deserialize key data." + """ + key = _helpers.to_bytes(key) + private_key = serialization.load_pem_private_key( + key, password=None, backend=_BACKEND + ) + return cls(private_key, key_id=key_id) diff --git a/packages/google-auth/google/auth/jwt.py b/packages/google-auth/google/auth/jwt.py index cdd69ac8a398..9248eb27f133 100644 --- a/packages/google-auth/google/auth/jwt.py +++ b/packages/google-auth/google/auth/jwt.py @@ -59,8 +59,18 @@ from google.auth import exceptions import google.auth.credentials +try: + from google.auth.crypt import es256 +except ImportError: # pragma: NO COVER + es256 = None + _DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds _DEFAULT_MAX_CACHE_SIZE = 10 +_ALGORITHM_TO_VERIFIER_CLASS = {"RS256": crypt.RSAVerifier} +_CRYPTOGRAPHY_BASED_ALGORITHMS = set(["ES256"]) + +if es256 is not None: # pragma: NO COVER + _ALGORITHM_TO_VERIFIER_CLASS["ES256"] = es256.ES256Verifier def encode(signer, payload, header=None, key_id=None): @@ -83,7 +93,12 @@ def encode(signer, payload, header=None, key_id=None): if key_id is None: key_id = signer.key_id - header.update({"typ": "JWT", "alg": "RS256"}) + header.update({"typ": "JWT"}) + + if es256 is not None and isinstance(signer, es256.ES256Signer): + header.update({"alg": "ES256"}) + else: + header.update({"alg": "RS256"}) if key_id is not None: header["kid"] = key_id @@ -217,10 +232,30 @@ def decode(token, certs=None, verify=True, audience=None): if not verify: return payload + # Pluck the key id and algorithm from the header and make sure we have + # a verifier that can support it. + key_alg = header.get("alg") + key_id = header.get("kid") + + try: + verifier_cls = _ALGORITHM_TO_VERIFIER_CLASS[key_alg] + except KeyError as exc: + if key_alg in _CRYPTOGRAPHY_BASED_ALGORITHMS: + six.raise_from( + ValueError( + "The key algorithm {} requires the cryptography package " + "to be installed.".format(key_alg) + ), + exc, + ) + else: + six.raise_from( + ValueError("Unsupported signature algorithm {}".format(key_alg)), exc + ) + # If certs is specified as a dictionary of key IDs to certificates, then # use the certificate identified by the key ID in the token header. if isinstance(certs, Mapping): - key_id = header.get("kid") if key_id: if key_id not in certs: raise ValueError("Certificate for key id {} not found.".format(key_id)) @@ -232,7 +267,9 @@ def decode(token, certs=None, verify=True, audience=None): certs_to_check = certs # Verify that the signature matches the message. - if not crypt.verify_signature(signed_section, signature, certs_to_check): + if not crypt.verify_signature( + signed_section, signature, certs_to_check, verifier_cls + ): raise ValueError("Could not verify token signature.") # Verify the issued at and created times in the payload. diff --git a/packages/google-auth/tests/crypt/test_es256.py b/packages/google-auth/tests/crypt/test_es256.py new file mode 100644 index 000000000000..087ce6e2324f --- /dev/null +++ b/packages/google-auth/tests/crypt/test_es256.py @@ -0,0 +1,131 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import os + +from cryptography.hazmat.primitives.asymmetric import ec +import pytest + +from google.auth import _helpers +from google.auth.crypt import base +from google.auth.crypt import es256 + + +DATA_DIR = os.path.join(os.path.dirname(__file__), "..", "data") + +# To generate es256_privatekey.pem, es256_privatekey.pub, and +# es256_public_cert.pem: +# $ openssl ecparam -genkey -name prime256v1 -noout -out es256_privatekey.pem +# $ openssl ec -in es256-private-key.pem -pubout -out es256-publickey.pem +# $ openssl req -new -x509 -key es256_privatekey.pem -out \ +# > es256_public_cert.pem + +with open(os.path.join(DATA_DIR, "es256_privatekey.pem"), "rb") as fh: + PRIVATE_KEY_BYTES = fh.read() + PKCS1_KEY_BYTES = PRIVATE_KEY_BYTES + +with open(os.path.join(DATA_DIR, "es256_publickey.pem"), "rb") as fh: + PUBLIC_KEY_BYTES = fh.read() + +with open(os.path.join(DATA_DIR, "es256_public_cert.pem"), "rb") as fh: + PUBLIC_CERT_BYTES = fh.read() + +SERVICE_ACCOUNT_JSON_FILE = os.path.join(DATA_DIR, "es256_service_account.json") + +with open(SERVICE_ACCOUNT_JSON_FILE, "r") as fh: + SERVICE_ACCOUNT_INFO = json.load(fh) + + +class TestES256Verifier(object): + def test_verify_success(self): + to_sign = b"foo" + signer = es256.ES256Signer.from_string(PRIVATE_KEY_BYTES) + actual_signature = signer.sign(to_sign) + + verifier = es256.ES256Verifier.from_string(PUBLIC_KEY_BYTES) + assert verifier.verify(to_sign, actual_signature) + + def test_verify_unicode_success(self): + to_sign = u"foo" + signer = es256.ES256Signer.from_string(PRIVATE_KEY_BYTES) + actual_signature = signer.sign(to_sign) + + verifier = es256.ES256Verifier.from_string(PUBLIC_KEY_BYTES) + assert verifier.verify(to_sign, actual_signature) + + def test_verify_failure(self): + verifier = es256.ES256Verifier.from_string(PUBLIC_KEY_BYTES) + bad_signature1 = b"" + assert not verifier.verify(b"foo", bad_signature1) + bad_signature2 = b"a" + assert not verifier.verify(b"foo", bad_signature2) + + def test_from_string_pub_key(self): + verifier = es256.ES256Verifier.from_string(PUBLIC_KEY_BYTES) + assert isinstance(verifier, es256.ES256Verifier) + assert isinstance(verifier._pubkey, ec.EllipticCurvePublicKey) + + def test_from_string_pub_key_unicode(self): + public_key = _helpers.from_bytes(PUBLIC_KEY_BYTES) + verifier = es256.ES256Verifier.from_string(public_key) + assert isinstance(verifier, es256.ES256Verifier) + assert isinstance(verifier._pubkey, ec.EllipticCurvePublicKey) + + def test_from_string_pub_cert(self): + verifier = es256.ES256Verifier.from_string(PUBLIC_CERT_BYTES) + assert isinstance(verifier, es256.ES256Verifier) + assert isinstance(verifier._pubkey, ec.EllipticCurvePublicKey) + + def test_from_string_pub_cert_unicode(self): + public_cert = _helpers.from_bytes(PUBLIC_CERT_BYTES) + verifier = es256.ES256Verifier.from_string(public_cert) + assert isinstance(verifier, es256.ES256Verifier) + assert isinstance(verifier._pubkey, ec.EllipticCurvePublicKey) + + +class TestES256Signer(object): + def test_from_string_pkcs1(self): + signer = es256.ES256Signer.from_string(PKCS1_KEY_BYTES) + assert isinstance(signer, es256.ES256Signer) + assert isinstance(signer._key, ec.EllipticCurvePrivateKey) + + def test_from_string_pkcs1_unicode(self): + key_bytes = _helpers.from_bytes(PKCS1_KEY_BYTES) + signer = es256.ES256Signer.from_string(key_bytes) + assert isinstance(signer, es256.ES256Signer) + assert isinstance(signer._key, ec.EllipticCurvePrivateKey) + + def test_from_string_bogus_key(self): + key_bytes = "bogus-key" + with pytest.raises(ValueError): + es256.ES256Signer.from_string(key_bytes) + + def test_from_service_account_info(self): + signer = es256.ES256Signer.from_service_account_info(SERVICE_ACCOUNT_INFO) + + assert signer.key_id == SERVICE_ACCOUNT_INFO[base._JSON_FILE_PRIVATE_KEY_ID] + assert isinstance(signer._key, ec.EllipticCurvePrivateKey) + + def test_from_service_account_info_missing_key(self): + with pytest.raises(ValueError) as excinfo: + es256.ES256Signer.from_service_account_info({}) + + assert excinfo.match(base._JSON_FILE_PRIVATE_KEY) + + def test_from_service_account_file(self): + signer = es256.ES256Signer.from_service_account_file(SERVICE_ACCOUNT_JSON_FILE) + + assert signer.key_id == SERVICE_ACCOUNT_INFO[base._JSON_FILE_PRIVATE_KEY_ID] + assert isinstance(signer._key, ec.EllipticCurvePrivateKey) diff --git a/packages/google-auth/tests/data/es256_privatekey.pem b/packages/google-auth/tests/data/es256_privatekey.pem new file mode 100644 index 000000000000..5c950b514f3f --- /dev/null +++ b/packages/google-auth/tests/data/es256_privatekey.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIAIC57aTx5ev4T2HBMQk4fXV09AzLDQ3Ju1uNoEB0LngoAoGCCqGSM49 +AwEHoUQDQgAEsACsrmP6Bp216OCFm73C8W/VRHZWcO8yU/bMwx96f05BkTII3KeJ +z2O0IRAnXfso8K6YsjMuUDGCfj+b1IDIoA== +-----END EC PRIVATE KEY----- diff --git a/packages/google-auth/tests/data/es256_public_cert.pem b/packages/google-auth/tests/data/es256_public_cert.pem new file mode 100644 index 000000000000..774ca1484382 --- /dev/null +++ b/packages/google-auth/tests/data/es256_public_cert.pem @@ -0,0 +1,8 @@ +-----BEGIN CERTIFICATE----- +MIIBGDCBwAIJAPUA0H4EQWsdMAoGCCqGSM49BAMCMBUxEzARBgNVBAMMCnVuaXQt +dGVzdHMwHhcNMTkwNTA5MDI1MDExWhcNMTkwNjA4MDI1MDExWjAVMRMwEQYDVQQD +DAp1bml0LXRlc3RzMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEsACsrmP6Bp21 +6OCFm73C8W/VRHZWcO8yU/bMwx96f05BkTII3KeJz2O0IRAnXfso8K6YsjMuUDGC +fj+b1IDIoDAKBggqhkjOPQQDAgNHADBEAh8PcDTMyWk8SHqV/v8FLuMbDxdtAsq2 +dwCpuHQwqCcmAiEAnwtkiyieN+8zozaf1P4QKp2mAqNGqua50y3ua5uVotc= +-----END CERTIFICATE----- diff --git a/packages/google-auth/tests/data/es256_publickey.pem b/packages/google-auth/tests/data/es256_publickey.pem new file mode 100644 index 000000000000..51f2a03fa4a7 --- /dev/null +++ b/packages/google-auth/tests/data/es256_publickey.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEsACsrmP6Bp216OCFm73C8W/VRHZW +cO8yU/bMwx96f05BkTII3KeJz2O0IRAnXfso8K6YsjMuUDGCfj+b1IDIoA== +-----END PUBLIC KEY----- diff --git a/packages/google-auth/tests/data/es256_service_account.json b/packages/google-auth/tests/data/es256_service_account.json new file mode 100644 index 000000000000..dd26719f6215 --- /dev/null +++ b/packages/google-auth/tests/data/es256_service_account.json @@ -0,0 +1,10 @@ +{ + "type": "service_account", + "project_id": "example-project", + "private_key_id": "1", + "private_key": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIAIC57aTx5ev4T2HBMQk4fXV09AzLDQ3Ju1uNoEB0LngoAoGCCqGSM49\nAwEHoUQDQgAEsACsrmP6Bp216OCFm73C8W/VRHZWcO8yU/bMwx96f05BkTII3KeJ\nz2O0IRAnXfso8K6YsjMuUDGCfj+b1IDIoA==\n-----END EC PRIVATE KEY-----", + "client_email": "service-account@example.com", + "client_id": "1234", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://accounts.google.com/o/oauth2/token" +} diff --git a/packages/google-auth/tests/test_jwt.py b/packages/google-auth/tests/test_jwt.py index b0c6e48e9d1e..488aee4671df 100644 --- a/packages/google-auth/tests/test_jwt.py +++ b/packages/google-auth/tests/test_jwt.py @@ -37,6 +37,12 @@ with open(os.path.join(DATA_DIR, "other_cert.pem"), "rb") as fh: OTHER_CERT_BYTES = fh.read() +with open(os.path.join(DATA_DIR, "es256_privatekey.pem"), "rb") as fh: + EC_PRIVATE_KEY_BYTES = fh.read() + +with open(os.path.join(DATA_DIR, "es256_public_cert.pem"), "rb") as fh: + EC_PUBLIC_CERT_BYTES = fh.read() + SERVICE_ACCOUNT_JSON_FILE = os.path.join(DATA_DIR, "service_account.json") with open(SERVICE_ACCOUNT_JSON_FILE, "r") as fh: @@ -68,8 +74,21 @@ def test_encode_extra_headers(signer): @pytest.fixture -def token_factory(signer): - def factory(claims=None, key_id=None): +def es256_signer(): + return crypt.ES256Signer.from_string(EC_PRIVATE_KEY_BYTES, "1") + + +def test_encode_basic_es256(es256_signer): + test_payload = {"test": "value"} + encoded = jwt.encode(es256_signer, test_payload) + header, payload, _, _ = jwt._unverified_decode(encoded) + assert payload == test_payload + assert header == {"typ": "JWT", "alg": "ES256", "kid": es256_signer.key_id} + + +@pytest.fixture +def token_factory(signer, es256_signer): + def factory(claims=None, key_id=None, use_es256_signer=False): now = _helpers.datetime_to_secs(_helpers.utcnow()) payload = { "aud": "audience@example.com", @@ -86,7 +105,10 @@ def factory(claims=None, key_id=None): signer._key_id = None key_id = None - return jwt.encode(signer, payload, key_id=key_id) + if use_es256_signer: + return jwt.encode(es256_signer, payload, key_id=key_id) + else: + return jwt.encode(signer, payload, key_id=key_id) return factory @@ -98,6 +120,15 @@ def test_decode_valid(token_factory): assert payload["metadata"]["meta"] == "data" +def test_decode_valid_es256(token_factory): + payload = jwt.decode( + token_factory(use_es256_signer=True), certs=EC_PUBLIC_CERT_BYTES + ) + assert payload["aud"] == "audience@example.com" + assert payload["user"] == "billy bob" + assert payload["metadata"]["meta"] == "data" + + def test_decode_valid_with_audience(token_factory): payload = jwt.decode( token_factory(), certs=PUBLIC_CERT_BYTES, audience="audience@example.com" @@ -201,6 +232,29 @@ def test_decode_no_key_id(token_factory): assert payload["user"] == "billy bob" +def test_decode_unknown_alg(): + headers = json.dumps({u"kid": u"1", u"alg": u"fakealg"}) + token = b".".join( + map(lambda seg: base64.b64encode(seg.encode("utf-8")), [headers, u"{}", u"sig"]) + ) + + with pytest.raises(ValueError) as excinfo: + jwt.decode(token) + assert excinfo.match(r"fakealg") + + +def test_decode_missing_crytography_alg(monkeypatch): + monkeypatch.delitem(jwt._ALGORITHM_TO_VERIFIER_CLASS, "ES256") + headers = json.dumps({u"kid": u"1", u"alg": u"ES256"}) + token = b".".join( + map(lambda seg: base64.b64encode(seg.encode("utf-8")), [headers, u"{}", u"sig"]) + ) + + with pytest.raises(ValueError) as excinfo: + jwt.decode(token) + assert excinfo.match(r"cryptography") + + def test_roundtrip_explicit_key_id(token_factory): token = token_factory(key_id="3") certs = {"2": OTHER_CERT_BYTES, "3": PUBLIC_CERT_BYTES} From 6dd304bd830ba7fd7528d114778b7139c27ae7b0 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Wed, 1 Apr 2020 10:34:37 -0700 Subject: [PATCH 281/966] feat: add fetch_id_token to support id_token adc (#469) feat: id_token adc with gcloud cred --- packages/google-auth/docs/user-guide.rst | 16 ++++ .../google-auth/google/oauth2/id_token.py | 91 +++++++++++++++++++ packages/google-auth/system_tests/noxfile.py | 2 +- .../system_tests/test_compute_engine.py | 14 ++- .../google-auth/system_tests/test_id_token.py | 25 +++++ .../google-auth/tests/oauth2/test_id_token.py | 68 ++++++++++++++ 6 files changed, 213 insertions(+), 3 deletions(-) create mode 100644 packages/google-auth/system_tests/test_id_token.py diff --git a/packages/google-auth/docs/user-guide.rst b/packages/google-auth/docs/user-guide.rst index 3877bff5950c..08e7167dfe53 100644 --- a/packages/google-auth/docs/user-guide.rst +++ b/packages/google-auth/docs/user-guide.rst @@ -291,6 +291,20 @@ Impersonated :: target_credentials, target_audience=target_audience) +If your application runs on `App Engine`_, `Cloud Run`_, `Compute Engine`_, or +has application default credentials set via `GOOGLE_APPLICATION_CREDENTIALS` +environment variable, you can also use `google.oauth2.id_token.fetch_id_token` +to obtain an ID token from your current running environment. The following is an +example :: + + import google.oauth2.id_token + import google.auth.transport.requests + + request = google.auth.transport.requests.Request() + target_audience = "https://pubsub.googleapis.com" + + id_token = google.oauth2.id_token.fetch_id_token(request, target_audience) + IDToken verification can be done for various type of IDTokens using the :class:`google.oauth2.id_token` module. It supports ID token signed with RS256 and ES256 algorithms. However, ES256 algorithm won't be available unless @@ -334,8 +348,10 @@ A sample end-to-end flow using an ID Token against a Cloud Run endpoint maybe :: print(token) print(id_token.verify_token(token,request)) +.. _App Engine: https://cloud.google.com/appengine/ .. _Cloud Functions: https://cloud.google.com/functions/ .. _Cloud Run: https://cloud.google.com/run/ +.. _Compute Engine: https://cloud.google.com/compute/ .. _Identity Aware Proxy: https://cloud.google.com/iap/ .. _Google OpenID Connect: https://developers.google.com/identity/protocols/OpenIDConnect .. _Google ID Token: https://developers.google.com/identity/protocols/OpenIDConnect#validatinganidtoken diff --git a/packages/google-auth/google/oauth2/id_token.py b/packages/google-auth/google/oauth2/id_token.py index 1dbfb20add8f..f559c6c39bbe 100644 --- a/packages/google-auth/google/oauth2/id_token.py +++ b/packages/google-auth/google/oauth2/id_token.py @@ -59,12 +59,16 @@ """ import json +import os +import six from six.moves import http_client +from google.auth import environment_vars from google.auth import exceptions from google.auth import jwt + # The URL that provides public certificates for verifying ID tokens issued # by Google's OAuth 2.0 authorization server. _GOOGLE_OAUTH2_CERTS_URL = "https://www.googleapis.com/oauth2/v1/certs" @@ -159,3 +163,90 @@ def verify_firebase_token(id_token, request, audience=None): return verify_token( id_token, request, audience=audience, certs_url=_GOOGLE_APIS_CERTS_URL ) + + +def fetch_id_token(request, audience): + """Fetch the ID Token from the current environment. + + This function acquires ID token from the environment in the following order: + + 1. If the application is running in Compute Engine, App Engine or Cloud Run, + then the ID token are obtained from the metadata server. + 2. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set + to the path of a valid service account JSON file, then ID token is + acquired using this service account credentials. + 3. If metadata server doesn't exist and no valid service account credentials + are found, :class:`~google.auth.exceptions.DefaultCredentialsError` will + be raised. + + Example:: + + import google.oauth2.id_token + import google.auth.transport.requests + + request = google.auth.transport.requests.Request() + target_audience = "https://pubsub.googleapis.com" + + id_token = google.oauth2.id_token.fetch_id_token(request, target_audience) + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + audience (str): The audience that this ID token is intended for. + + Returns: + str: The ID token. + + Raises: + ~google.auth.exceptions.DefaultCredentialsError: + If metadata server doesn't exist and no valid service account + credentials are found. + """ + # 1. First try to fetch ID token from metada server if it exists. The code + # works for GAE and Cloud Run metadata server as well. + try: + from google.auth import compute_engine + + credentials = compute_engine.IDTokenCredentials( + request, audience, use_metadata_identity_endpoint=True + ) + credentials.refresh(request) + return credentials.token + except (ImportError, exceptions.TransportError): + pass + + # 2. Try to use service account credentials to get ID token. + + # Try to get credentials from the GOOGLE_APPLICATION_CREDENTIALS environment + # variable. + credentials_filename = os.environ.get(environment_vars.CREDENTIALS) + if not ( + credentials_filename + and os.path.exists(credentials_filename) + and os.path.isfile(credentials_filename) + ): + raise exceptions.DefaultCredentialsError( + "Neither metadata server or valid service account credentials are found." + ) + + try: + with open(credentials_filename, "r") as f: + info = json.load(f) + credentials_content = ( + (info.get("type") == "service_account") and info or None + ) + + from google.oauth2 import service_account + + credentials = service_account.IDTokenCredentials.from_service_account_info( + credentials_content, target_audience=audience + ) + except ValueError as caught_exc: + new_exc = exceptions.DefaultCredentialsError( + "Neither metadata server or valid service account credentials are found.", + caught_exc, + ) + six.raise_from(new_exc, caught_exc) + + credentials.refresh(request) + return credentials.token diff --git a/packages/google-auth/system_tests/noxfile.py b/packages/google-auth/system_tests/noxfile.py index 6e66eb4ed211..14cd3db8e321 100644 --- a/packages/google-auth/system_tests/noxfile.py +++ b/packages/google-auth/system_tests/noxfile.py @@ -200,7 +200,7 @@ def default_explicit_service_account(session): session.env[EXPECT_PROJECT_ENV] = "1" session.install(*TEST_DEPENDENCIES) session.install(LIBRARY_DIR) - session.run("pytest", "test_default.py") + session.run("pytest", "test_default.py", "test_id_token.py") @nox.session(python=PYTHON_VERSIONS) diff --git a/packages/google-auth/system_tests/test_compute_engine.py b/packages/google-auth/system_tests/test_compute_engine.py index bcfdfd604a34..b0d42f3627d7 100644 --- a/packages/google-auth/system_tests/test_compute_engine.py +++ b/packages/google-auth/system_tests/test_compute_engine.py @@ -20,6 +20,9 @@ from google.auth import exceptions from google.auth import jwt from google.auth.compute_engine import _metadata +import google.oauth2.id_token + +AUDIENCE = "https://pubsub.googleapis.com" @pytest.fixture(autouse=True) @@ -53,10 +56,17 @@ def test_default(verify_refresh): def test_id_token_from_metadata(http_request): credentials = compute_engine.IDTokenCredentials( - http_request, "target_audience", use_metadata_identity_endpoint=True + http_request, AUDIENCE, use_metadata_identity_endpoint=True ) credentials.refresh(http_request) _, payload, _, _ = jwt._unverified_decode(credentials.token) - assert payload["aud"] == "target_audience" + assert payload["aud"] == AUDIENCE assert payload["exp"] == credentials.expiry + + +def test_fetch_id_token(http_request): + token = google.oauth2.id_token.fetch_id_token(http_request, AUDIENCE) + + _, payload, _, _ = jwt._unverified_decode(token) + assert payload["aud"] == AUDIENCE diff --git a/packages/google-auth/system_tests/test_id_token.py b/packages/google-auth/system_tests/test_id_token.py new file mode 100644 index 000000000000..b07cefc184c9 --- /dev/null +++ b/packages/google-auth/system_tests/test_id_token.py @@ -0,0 +1,25 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import pytest + +from google.auth import jwt +import google.oauth2.id_token + + +def test_fetch_id_token(http_request): + audience = "https://pubsub.googleapis.com" + token = google.oauth2.id_token.fetch_id_token(http_request, audience) + + _, payload, _, _ = jwt._unverified_decode(token) + assert payload["aud"] == audience diff --git a/packages/google-auth/tests/oauth2/test_id_token.py b/packages/google-auth/tests/oauth2/test_id_token.py index 980a8e982035..ff858078a5e4 100644 --- a/packages/google-auth/tests/oauth2/test_id_token.py +++ b/packages/google-auth/tests/oauth2/test_id_token.py @@ -13,14 +13,21 @@ # limitations under the License. import json +import os import mock import pytest +from google.auth import environment_vars from google.auth import exceptions from google.auth import transport +import google.auth.compute_engine._metadata from google.oauth2 import id_token +SERVICE_ACCOUNT_FILE = os.path.join( + os.path.dirname(__file__), "../data/service_account.json" +) + def make_request(status, data=None): response = mock.create_autospec(transport.Response, instance=True) @@ -114,3 +121,64 @@ def test_verify_firebase_token(verify_token): audience=mock.sentinel.audience, certs_url=id_token._GOOGLE_APIS_CERTS_URL, ) + + +def test_fetch_id_token_from_metadata_server(): + def mock_init(self, request, audience, use_metadata_identity_endpoint): + assert use_metadata_identity_endpoint + self.token = "id_token" + + with mock.patch.multiple( + google.auth.compute_engine.IDTokenCredentials, + __init__=mock_init, + refresh=mock.Mock(), + ): + request = mock.Mock() + token = id_token.fetch_id_token(request, "https://pubsub.googleapis.com") + assert token == "id_token" + + +@mock.patch.object( + google.auth.compute_engine.IDTokenCredentials, + "__init__", + side_effect=exceptions.TransportError(), +) +def test_fetch_id_token_from_explicit_cred_json_file(mock_init, monkeypatch): + monkeypatch.setenv(environment_vars.CREDENTIALS, SERVICE_ACCOUNT_FILE) + + def mock_refresh(self, request): + self.token = "id_token" + + with mock.patch.object( + google.oauth2.service_account.IDTokenCredentials, "refresh", mock_refresh + ): + request = mock.Mock() + token = id_token.fetch_id_token(request, "https://pubsub.googleapis.com") + assert token == "id_token" + + +@mock.patch.object( + google.auth.compute_engine.IDTokenCredentials, + "__init__", + side_effect=exceptions.TransportError(), +) +def test_fetch_id_token_no_cred_json_file(mock_init, monkeypatch): + monkeypatch.delenv(environment_vars.CREDENTIALS, raising=False) + + with pytest.raises(exceptions.DefaultCredentialsError): + request = mock.Mock() + id_token.fetch_id_token(request, "https://pubsub.googleapis.com") + + +@mock.patch.object( + google.auth.compute_engine.IDTokenCredentials, + "__init__", + side_effect=exceptions.TransportError(), +) +def test_fetch_id_token_invalid_cred_file(mock_init, monkeypatch): + not_json_file = os.path.join(os.path.dirname(__file__), "../data/public_cert.pem") + monkeypatch.setenv(environment_vars.CREDENTIALS, not_json_file) + + with pytest.raises(exceptions.DefaultCredentialsError): + request = mock.Mock() + id_token.fetch_id_token(request, "https://pubsub.googleapis.com") From 10f9273e1510b967348dccadb09db6ebe0a12c15 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Wed, 1 Apr 2020 11:27:33 -0700 Subject: [PATCH 282/966] feat: consolidate mTLS channel errors (#480) feat: consolidate mTLS channel errors --- .../google-auth/google/auth/exceptions.py | 4 ++ .../google-auth/google/auth/transport/grpc.py | 39 ++++++++---------- .../google/auth/transport/requests.py | 39 +++++++++++------- .../google/auth/transport/urllib3.py | 41 +++++++++++-------- .../google-auth/tests/transport/test_grpc.py | 3 +- .../tests/transport/test_requests.py | 20 +++++++++ .../tests/transport/test_urllib3.py | 21 ++++++++++ 7 files changed, 112 insertions(+), 55 deletions(-) diff --git a/packages/google-auth/google/auth/exceptions.py b/packages/google-auth/google/auth/exceptions.py index 4f66dc2a000d..95013860ada5 100644 --- a/packages/google-auth/google/auth/exceptions.py +++ b/packages/google-auth/google/auth/exceptions.py @@ -34,3 +34,7 @@ class UserAccessTokenError(GoogleAuthError): class DefaultCredentialsError(GoogleAuthError): """Used to indicate that acquiring default credentials failed.""" + + +class MutualTLSChannelError(GoogleAuthError): + """Used to indicate that mutual TLS channel creation is failed.""" diff --git a/packages/google-auth/google/auth/transport/grpc.py b/packages/google-auth/google/auth/transport/grpc.py index 32ffabcae604..d62c41502ca6 100644 --- a/packages/google-auth/google/auth/transport/grpc.py +++ b/packages/google-auth/google/auth/transport/grpc.py @@ -20,6 +20,7 @@ import six +from google.auth import exceptions from google.auth.transport import _mtls_helper try: @@ -217,17 +218,8 @@ def my_client_cert_callback(): grpc.Channel: The created gRPC channel. Raises: - OSError: If the cert provider command launch fails during the application - default SSL credentials loading process on devices with endpoint - verification support. - RuntimeError: If the cert provider command has a runtime error during the - application default SSL credentials loading process on devices with - endpoint verification support. - ValueError: - If the context aware metadata file is malformed or if the cert provider - command doesn't produce both client certificate and key during the - application default SSL credentials loading process on devices with - endpoint verification support. + google.auth.exceptions.MutualTLSChannelError: If mutual TLS channel + creation failed for any reason. """ # Create the metadata plugin for inserting the authorization header. metadata_plugin = AuthMetadataPlugin(credentials, request) @@ -293,20 +285,21 @@ def ssl_credentials(self): grpc.ChannelCredentials: The created grpc channel credentials. Raises: - OSError: If the cert provider command launch fails. - RuntimeError: If the cert provider command has a runtime error. - ValueError: - If the context aware metadata file is malformed or if the cert provider - command doesn't produce both the client certificate and key. + google.auth.exceptions.MutualTLSChannelError: If mutual TLS channel + creation failed for any reason. """ if self._context_aware_metadata_path: - metadata = _mtls_helper._read_dca_metadata_file( - self._context_aware_metadata_path - ) - cert, key = _mtls_helper.get_client_ssl_credentials(metadata) - self._ssl_credentials = grpc.ssl_channel_credentials( - certificate_chain=cert, private_key=key - ) + try: + metadata = _mtls_helper._read_dca_metadata_file( + self._context_aware_metadata_path + ) + cert, key = _mtls_helper.get_client_ssl_credentials(metadata) + self._ssl_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + except (OSError, RuntimeError, ValueError) as caught_exc: + new_exc = exceptions.MutualTLSChannelError(caught_exc) + six.raise_from(new_exc, caught_exc) else: self._ssl_credentials = grpc.ssl_channel_credentials() diff --git a/packages/google-auth/google/auth/transport/requests.py b/packages/google-auth/google/auth/transport/requests.py index 2d31d962e518..26096e213ff7 100644 --- a/packages/google-auth/google/auth/transport/requests.py +++ b/packages/google-auth/google/auth/transport/requests.py @@ -355,23 +355,32 @@ def configure_mtls_channel(self, client_cert_callback=None): will be used. Raises: - ImportError: If certifi or pyOpenSSL is not installed. - OpenSSL.crypto.Error: If client cert or key is invalid. - OSError: If the cert provider command launch fails during the - application default SSL credentials loading process. - RuntimeError: If the cert provider command has a runtime error during - the application default SSL credentials loading process. - ValueError: If the context aware metadata file is malformed or the - cert provider command doesn't produce both client certicate and - key during the application default SSL credentials loading process. + google.auth.exceptions.MutualTLSChannelError: If mutual TLS channel + creation failed for any reason. """ - self._is_mtls, cert, key = google.auth.transport._mtls_helper.get_client_cert_and_key( - client_cert_callback - ) + try: + import OpenSSL + except ImportError as caught_exc: + new_exc = exceptions.MutualTLSChannelError(caught_exc) + six.raise_from(new_exc, caught_exc) - if self._is_mtls: - mtls_adapter = _MutualTlsAdapter(cert, key) - self.mount("https://", mtls_adapter) + try: + self._is_mtls, cert, key = google.auth.transport._mtls_helper.get_client_cert_and_key( + client_cert_callback + ) + + if self._is_mtls: + mtls_adapter = _MutualTlsAdapter(cert, key) + self.mount("https://", mtls_adapter) + except ( + ImportError, + OpenSSL.crypto.Error, + OSError, + RuntimeError, + ValueError, + ) as caught_exc: + new_exc = exceptions.MutualTLSChannelError(caught_exc) + six.raise_from(new_exc, caught_exc) def request( self, diff --git a/packages/google-auth/google/auth/transport/urllib3.py b/packages/google-auth/google/auth/transport/urllib3.py index 3b2ba28bc364..c359f359226c 100644 --- a/packages/google-auth/google/auth/transport/urllib3.py +++ b/packages/google-auth/google/auth/transport/urllib3.py @@ -297,24 +297,33 @@ def configure_mtls_channel(self, client_cert_callabck=None): True if the channel is mutual TLS and False otherwise. Raises: - ImportError: If certifi or pyOpenSSL is not installed. - OpenSSL.crypto.Error: If client cert or key is invalid. - OSError: If the cert provider command launch fails during the - application default SSL credentials loading process. - RuntimeError: If the cert provider command has a runtime error during - the application default SSL credentials loading process. - ValueError: If the context aware metadata file is malformed or the - cert provider command doesn't produce both client certicate and - key during the application default SSL credentials loading process. + google.auth.exceptions.MutualTLSChannelError: If mutual TLS channel + creation failed for any reason. """ - found_cert_key, cert, key = transport._mtls_helper.get_client_cert_and_key( - client_cert_callabck - ) + try: + import OpenSSL + except ImportError as caught_exc: + new_exc = exceptions.MutualTLSChannelError(caught_exc) + six.raise_from(new_exc, caught_exc) - if found_cert_key: - self.http = _make_mutual_tls_http(cert, key) - else: - self.http = _make_default_http() + try: + found_cert_key, cert, key = transport._mtls_helper.get_client_cert_and_key( + client_cert_callabck + ) + + if found_cert_key: + self.http = _make_mutual_tls_http(cert, key) + else: + self.http = _make_default_http() + except ( + ImportError, + OpenSSL.crypto.Error, + OSError, + RuntimeError, + ValueError, + ) as caught_exc: + new_exc = exceptions.MutualTLSChannelError(caught_exc) + six.raise_from(new_exc, caught_exc) if self._has_user_provided_http: self._has_user_provided_http = False diff --git a/packages/google-auth/tests/transport/test_grpc.py b/packages/google-auth/tests/transport/test_grpc.py index 23e62a213e8e..5c61f96e151c 100644 --- a/packages/google-auth/tests/transport/test_grpc.py +++ b/packages/google-auth/tests/transport/test_grpc.py @@ -21,6 +21,7 @@ from google.auth import _helpers from google.auth import credentials +from google.auth import exceptions from google.auth import transport try: @@ -315,7 +316,7 @@ def test_get_client_ssl_credentials_failure( # Mock that client cert and key are not loaded and exception is raised. mock_get_client_ssl_credentials.side_effect = ValueError() - with pytest.raises(ValueError): + with pytest.raises(exceptions.MutualTLSChannelError): assert google.auth.transport.grpc.SslCredentials().ssl_credentials def test_get_client_ssl_credentials_success( diff --git a/packages/google-auth/tests/transport/test_requests.py b/packages/google-auth/tests/transport/test_requests.py index 3f3e14c05375..d6770de73549 100644 --- a/packages/google-auth/tests/transport/test_requests.py +++ b/packages/google-auth/tests/transport/test_requests.py @@ -14,6 +14,7 @@ import datetime import functools +import sys import freezegun import mock @@ -23,6 +24,7 @@ import requests.adapters from six.moves import http_client +from google.auth import exceptions import google.auth.credentials import google.auth.transport._mtls_helper import google.auth.transport.requests @@ -414,3 +416,21 @@ def test_configure_mtls_channel_non_mtls( # Assert _MutualTlsAdapter constructor is not called. mock_adapter_ctor.assert_not_called() + + @mock.patch( + "google.auth.transport._mtls_helper.get_client_cert_and_key", autospec=True + ) + def test_configure_mtls_channel_exceptions(self, mock_get_client_cert_and_key): + mock_get_client_cert_and_key.side_effect = ValueError() + + auth_session = google.auth.transport.requests.AuthorizedSession( + credentials=mock.Mock() + ) + with pytest.raises(exceptions.MutualTLSChannelError): + auth_session.configure_mtls_channel() + + mock_get_client_cert_and_key.return_value = (False, None, None) + with mock.patch.dict("sys.modules"): + sys.modules["OpenSSL"] = None + with pytest.raises(exceptions.MutualTLSChannelError): + auth_session.configure_mtls_channel() diff --git a/packages/google-auth/tests/transport/test_urllib3.py b/packages/google-auth/tests/transport/test_urllib3.py index 0452e9187dc5..a25fcd7d97af 100644 --- a/packages/google-auth/tests/transport/test_urllib3.py +++ b/packages/google-auth/tests/transport/test_urllib3.py @@ -12,12 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. +import sys + import mock import OpenSSL import pytest from six.moves import http_client import urllib3 +from google.auth import exceptions import google.auth.credentials import google.auth.transport._mtls_helper import google.auth.transport.urllib3 @@ -221,3 +224,21 @@ def test_configure_mtls_channel_non_mtls( assert not is_mtls mock_get_client_cert_and_key.assert_called_once() mock_make_mutual_tls_http.assert_not_called() + + @mock.patch( + "google.auth.transport._mtls_helper.get_client_cert_and_key", autospec=True + ) + def test_configure_mtls_channel_exceptions(self, mock_get_client_cert_and_key): + authed_http = google.auth.transport.urllib3.AuthorizedHttp( + credentials=mock.Mock() + ) + + mock_get_client_cert_and_key.side_effect = ValueError() + with pytest.raises(exceptions.MutualTLSChannelError): + authed_http.configure_mtls_channel() + + mock_get_client_cert_and_key.return_value = (False, None, None) + with mock.patch.dict("sys.modules"): + sys.modules["OpenSSL"] = None + with pytest.raises(exceptions.MutualTLSChannelError): + authed_http.configure_mtls_channel() From a729f9b1aaa449edf8ffb5823538f0227e921ef5 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2020 12:45:24 -0700 Subject: [PATCH 283/966] chore: release 1.13.0 (#477) --- packages/google-auth/CHANGELOG.md | 10 ++++++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 3a4393ea1d4c..ed2b168d6d8d 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,16 @@ [1]: https://pypi.org/project/google-auth/#history +## [1.13.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.12.0...v1.13.0) (2020-04-01) + + +### Features + +* add access token credentials ([#476](https://www.github.com/googleapis/google-auth-library-python/issues/476)) ([772dac6](https://www.github.com/googleapis/google-auth-library-python/commit/772dac6a6512230d32cb0dfae65a1a6aa9015049)) +* add fetch_id_token to support id_token adc ([#469](https://www.github.com/googleapis/google-auth-library-python/issues/469)) ([506c565](https://www.github.com/googleapis/google-auth-library-python/commit/506c565a8c3c23a78fd0f17991bc6deb6f2528a9)) +* consolidate mTLS channel errors ([#480](https://www.github.com/googleapis/google-auth-library-python/issues/480)) ([e83d446](https://www.github.com/googleapis/google-auth-library-python/commit/e83d4462f5c50f8424d9e54be32c29390115a9ed)) +* Implement ES256 for JWT verification ([#340](https://www.github.com/googleapis/google-auth-library-python/issues/340)) ([e290a3d](https://www.github.com/googleapis/google-auth-library-python/commit/e290a3dbecc4767dd25ee14574951cdb6c2157cb)) + ## [1.12.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.11.3...v1.12.0) (2020-03-25) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 8ae5e090c554..0930fd8b5dd6 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -30,7 +30,7 @@ with io.open("README.rst", "r") as fh: long_description = fh.read() -version = "1.12.0" +version = "1.13.0" setup( name="google-auth", From 3cc3f16ec606d04ec79130086ce3e4fa8a72c3ad Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Wed, 1 Apr 2020 16:29:50 -0700 Subject: [PATCH 284/966] fix: invalid expiry type (#481) --- .../google-auth/google/auth/compute_engine/credentials.py | 5 ++++- packages/google-auth/system_tests/test_compute_engine.py | 5 ++++- .../google-auth/tests/compute_engine/test_credentials.py | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/google-auth/google/auth/compute_engine/credentials.py b/packages/google-auth/google/auth/compute_engine/credentials.py index 1927c26bd1f4..155046596dd0 100644 --- a/packages/google-auth/google/auth/compute_engine/credentials.py +++ b/packages/google-auth/google/auth/compute_engine/credentials.py @@ -274,6 +274,9 @@ def _call_metadata_identity_endpoint(self, request): request (google.auth.transport.Request): The object used to make HTTP requests. + Returns: + Tuple[str, datetime.datetime]: The ID token and the expiry of the ID token. + Raises: google.auth.exceptions.RefreshError: If the Compute Engine metadata service can't be reached or if the instance has no credentials. @@ -291,7 +294,7 @@ def _call_metadata_identity_endpoint(self, request): six.raise_from(new_exc, caught_exc) _, payload, _, _ = jwt._unverified_decode(id_token) - return id_token, payload["exp"] + return id_token, datetime.datetime.fromtimestamp(payload["exp"]) def refresh(self, request): """Refreshes the ID token. diff --git a/packages/google-auth/system_tests/test_compute_engine.py b/packages/google-auth/system_tests/test_compute_engine.py index b0d42f3627d7..1e0eaf11db9f 100644 --- a/packages/google-auth/system_tests/test_compute_engine.py +++ b/packages/google-auth/system_tests/test_compute_engine.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from datetime import datetime + import pytest import google.auth @@ -61,8 +63,9 @@ def test_id_token_from_metadata(http_request): credentials.refresh(http_request) _, payload, _, _ = jwt._unverified_decode(credentials.token) + assert credentials.valid assert payload["aud"] == AUDIENCE - assert payload["exp"] == credentials.expiry + assert datetime.fromtimestamp(payload["exp"]) == credentials.expiry def test_fetch_id_token(http_request): diff --git a/packages/google-auth/tests/compute_engine/test_credentials.py b/packages/google-auth/tests/compute_engine/test_credentials.py index 264235e49a64..98def0fc5f7b 100644 --- a/packages/google-auth/tests/compute_engine/test_credentials.py +++ b/packages/google-auth/tests/compute_engine/test_credentials.py @@ -522,7 +522,7 @@ def test_get_id_token_from_metadata(self, get, get_service_account_info): cred.refresh(request=mock.Mock()) assert cred.token == SAMPLE_ID_TOKEN - assert cred.expiry == SAMPLE_ID_TOKEN_EXP + assert cred.expiry == datetime.datetime.fromtimestamp(SAMPLE_ID_TOKEN_EXP) assert cred._use_metadata_identity_endpoint assert cred._signer is None assert cred._token_uri is None From e242a846fad0bb9006de888ceccaae3358401628 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2020 18:04:52 -0700 Subject: [PATCH 285/966] chore: release 1.13.1 (#482) --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index ed2b168d6d8d..c465e6c78f01 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +### [1.13.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.13.0...v1.13.1) (2020-04-01) + + +### Bug Fixes + +* invalid expiry type ([#481](https://www.github.com/googleapis/google-auth-library-python/issues/481)) ([7ae9a28](https://www.github.com/googleapis/google-auth-library-python/commit/7ae9a284dae16d274bfd4d876414f08efd6c3bff)) + ## [1.13.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.12.0...v1.13.0) (2020-04-01) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 0930fd8b5dd6..cb6ed5aa66ba 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -30,7 +30,7 @@ with io.open("README.rst", "r") as fh: long_description = fh.read() -version = "1.13.0" +version = "1.13.1" setup( name="google-auth", From b2be7ebf39ad26c5c6d87f06cf3e84d8803e9788 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Mon, 13 Apr 2020 16:18:11 -0700 Subject: [PATCH 286/966] feat: add default client cert source util (#486) feat: add default client cert source util --- .../google-auth/google/auth/exceptions.py | 3 +- .../google-auth/google/auth/transport/mtls.py | 60 +++++++++++++++++++ .../google/auth/transport/requests.py | 8 +-- .../google/auth/transport/urllib3.py | 12 ++-- .../system_tests/test_mtls_http.py | 60 +++++++++++++++---- .../google-auth/tests/transport/test_mtls.py | 55 +++++++++++++++++ 6 files changed, 176 insertions(+), 22 deletions(-) create mode 100644 packages/google-auth/google/auth/transport/mtls.py create mode 100644 packages/google-auth/tests/transport/test_mtls.py diff --git a/packages/google-auth/google/auth/exceptions.py b/packages/google-auth/google/auth/exceptions.py index 95013860ada5..5b614602be6d 100644 --- a/packages/google-auth/google/auth/exceptions.py +++ b/packages/google-auth/google/auth/exceptions.py @@ -37,4 +37,5 @@ class DefaultCredentialsError(GoogleAuthError): class MutualTLSChannelError(GoogleAuthError): - """Used to indicate that mutual TLS channel creation is failed.""" + """Used to indicate that mutual TLS channel creation is failed, or mutual + TLS channel credentials is missing or invalid.""" diff --git a/packages/google-auth/google/auth/transport/mtls.py b/packages/google-auth/google/auth/transport/mtls.py new file mode 100644 index 000000000000..063b26504f8f --- /dev/null +++ b/packages/google-auth/google/auth/transport/mtls.py @@ -0,0 +1,60 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Utilites for mutual TLS.""" + +import six + +from google.auth import exceptions +from google.auth.transport import _mtls_helper + + +def has_default_client_cert_source(): + """Check if default client SSL credentials exists on the device. + + Returns: + bool: indicating if the default client cert source exists. + """ + metadata_path = _mtls_helper._check_dca_metadata_path( + _mtls_helper.CONTEXT_AWARE_METADATA_PATH + ) + return metadata_path is not None + + +def default_client_cert_source(): + """Get a callback which returns the default client SSL credentials. + + Returns: + Callable[[], [bytes, bytes]]: A callback which returns the default + client certificate bytes and private key bytes, both in PEM format. + + Raises: + google.auth.exceptions.DefaultClientCertSourceError: If the default + client SSL credentials don't exist or are malformed. + """ + if not has_default_client_cert_source(): + raise exceptions.MutualTLSChannelError( + "Default client cert source doesn't exist" + ) + + def callback(): + try: + _, cert_bytes, key_bytes = _mtls_helper.get_client_cert_and_key() + except (OSError, RuntimeError, ValueError) as caught_exc: + new_exc = exceptions.MutualTLSChannelError(caught_exc) + six.raise_from(new_exc, caught_exc) + + return cert_bytes, key_bytes + + return callback diff --git a/packages/google-auth/google/auth/transport/requests.py b/packages/google-auth/google/auth/transport/requests.py index 26096e213ff7..cc0e93b6fc35 100644 --- a/packages/google-auth/google/auth/transport/requests.py +++ b/packages/google-auth/google/auth/transport/requests.py @@ -249,8 +249,8 @@ class AuthorizedSession(requests.Session): credentials' headers to the request and refreshing credentials as needed. This class also supports mutual TLS via :meth:`configure_mtls_channel` - method. If client_cert_callabck is provided, client certificate and private - key are loaded using the callback; if client_cert_callabck is None, + method. If client_cert_callback is provided, client certificate and private + key are loaded using the callback; if client_cert_callback is None, application default SSL credentials will be used. Exceptions are raised if there are problems with the certificate, private key, or the loading process, so it should be called within a try/except block. @@ -344,11 +344,11 @@ def configure_mtls_channel(self, client_cert_callback=None): """Configure the client certificate and key for SSL connection. If client certificate and key are successfully obtained (from the given - client_cert_callabck or from application default SSL credentials), a + client_cert_callback or from application default SSL credentials), a :class:`_MutualTlsAdapter` instance will be mounted to "https://" prefix. Args: - client_cert_callabck (Optional[Callable[[], (bytes, bytes)]]): + client_cert_callback (Optional[Callable[[], (bytes, bytes)]]): The optional callback returns the client certificate and private key bytes both in PEM format. If the callback is None, application default SSL credentials diff --git a/packages/google-auth/google/auth/transport/urllib3.py b/packages/google-auth/google/auth/transport/urllib3.py index c359f359226c..3771d84c43cb 100644 --- a/packages/google-auth/google/auth/transport/urllib3.py +++ b/packages/google-auth/google/auth/transport/urllib3.py @@ -202,8 +202,8 @@ class AuthorizedHttp(urllib3.request.RequestMethods): credentials' headers to the request and refreshing credentials as needed. This class also supports mutual TLS via :meth:`configure_mtls_channel` - method. If client_cert_callabck is provided, client certificate and private - key are loaded using the callback; if client_cert_callabck is None, + method. If client_cert_callback is provided, client certificate and private + key are loaded using the callback; if client_cert_callback is None, application default SSL credentials will be used. Exceptions are raised if there are problems with the certificate, private key, or the loading process, so it should be called within a try/except block. @@ -280,14 +280,14 @@ def __init__( super(AuthorizedHttp, self).__init__() - def configure_mtls_channel(self, client_cert_callabck=None): - """Configures mutual TLS channel using the given client_cert_callabck or + def configure_mtls_channel(self, client_cert_callback=None): + """Configures mutual TLS channel using the given client_cert_callback or application default SSL credentials. Returns True if the channel is mutual TLS and False otherwise. Note that the `http` provided in the constructor will be overwritten. Args: - client_cert_callabck (Optional[Callable[[], (bytes, bytes)]]): + client_cert_callback (Optional[Callable[[], (bytes, bytes)]]): The optional callback returns the client certificate and private key bytes both in PEM format. If the callback is None, application default SSL credentials @@ -308,7 +308,7 @@ def configure_mtls_channel(self, client_cert_callabck=None): try: found_cert_key, cert, key = transport._mtls_helper.get_client_cert_and_key( - client_cert_callabck + client_cert_callback ) if found_cert_key: diff --git a/packages/google-auth/system_tests/test_mtls_http.py b/packages/google-auth/system_tests/test_mtls_http.py index 1fd80311d78f..4a6a9c4bc401 100644 --- a/packages/google-auth/system_tests/test_mtls_http.py +++ b/packages/google-auth/system_tests/test_mtls_http.py @@ -18,6 +18,7 @@ import google.auth import google.auth.credentials +from google.auth.transport import mtls import google.auth.transport.requests import google.auth.transport.urllib3 @@ -25,11 +26,6 @@ REGULAR_ENDPOINT = "https://pubsub.googleapis.com/v1/projects/{}/topics" -def check_context_aware_metadata(): - metadata_path = path.expanduser("~/.secureConnect/context_aware_metadata.json") - return path.exists(metadata_path) - - def test_requests(): credentials, project_id = google.auth.default() credentials = google.auth.credentials.with_scopes_if_required( @@ -39,9 +35,9 @@ def test_requests(): authed_session = google.auth.transport.requests.AuthorizedSession(credentials) authed_session.configure_mtls_channel() - # If the devices has context aware metadata, then a mutual TLS channel is - # supposed to be created. - assert authed_session.is_mtls == check_context_aware_metadata() + # If the devices has default client cert source, then a mutual TLS channel + # is supposed to be created. + assert authed_session.is_mtls == mtls.has_default_client_cert_source() # Sleep 1 second to avoid 503 error. time.sleep(1) @@ -63,9 +59,9 @@ def test_urllib3(): authed_http = google.auth.transport.urllib3.AuthorizedHttp(credentials) is_mtls = authed_http.configure_mtls_channel() - # If the devices has context aware metadata, then a mutual TLS channel is - # supposed to be created. - assert is_mtls == check_context_aware_metadata() + # If the devices has default client cert source, then a mutual TLS channel + # is supposed to be created. + assert is_mtls == mtls.has_default_client_cert_source() # Sleep 1 second to avoid 503 error. time.sleep(1) @@ -76,3 +72,45 @@ def test_urllib3(): response = authed_http.request("GET", REGULAR_ENDPOINT.format(project_id)) assert response.status == 200 + + +def test_requests_with_default_client_cert_source(): + credentials, project_id = google.auth.default() + credentials = google.auth.credentials.with_scopes_if_required( + credentials, ["https://www.googleapis.com/auth/pubsub"] + ) + + authed_session = google.auth.transport.requests.AuthorizedSession(credentials) + + if mtls.has_default_client_cert_source(): + authed_session.configure_mtls_channel( + client_cert_callback=mtls.default_client_cert_source() + ) + + assert authed_session.is_mtls + + # Sleep 1 second to avoid 503 error. + time.sleep(1) + + response = authed_session.get(MTLS_ENDPOINT.format(project_id)) + assert response.ok + + +def test_urllib3_with_default_client_cert_source(): + credentials, project_id = google.auth.default() + credentials = google.auth.credentials.with_scopes_if_required( + credentials, ["https://www.googleapis.com/auth/pubsub"] + ) + + authed_http = google.auth.transport.urllib3.AuthorizedHttp(credentials) + + if mtls.has_default_client_cert_source(): + assert authed_http.configure_mtls_channel( + client_cert_callback=mtls.default_client_cert_source() + ) + + # Sleep 1 second to avoid 503 error. + time.sleep(1) + + response = authed_http.request("GET", MTLS_ENDPOINT.format(project_id)) + assert response.status == 200 diff --git a/packages/google-auth/tests/transport/test_mtls.py b/packages/google-auth/tests/transport/test_mtls.py new file mode 100644 index 000000000000..d3bc3915a291 --- /dev/null +++ b/packages/google-auth/tests/transport/test_mtls.py @@ -0,0 +1,55 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import mock +import pytest + +from google.auth import exceptions +from google.auth.transport import mtls + + +@mock.patch( + "google.auth.transport._mtls_helper._check_dca_metadata_path", autospec=True +) +def test_has_default_client_cert_source(check_dca_metadata_path): + check_dca_metadata_path.return_value = mock.Mock() + assert mtls.has_default_client_cert_source() + + check_dca_metadata_path.return_value = None + assert not mtls.has_default_client_cert_source() + + +@mock.patch("google.auth.transport._mtls_helper.get_client_cert_and_key", autospec=True) +@mock.patch("google.auth.transport.mtls.has_default_client_cert_source", autospec=True) +def test_default_client_cert_source( + has_default_client_cert_source, get_client_cert_and_key +): + # Test default client cert source doesn't exist. + has_default_client_cert_source.return_value = False + with pytest.raises(exceptions.MutualTLSChannelError): + mtls.default_client_cert_source() + + # The following tests will assume default client cert source exists. + has_default_client_cert_source.return_value = True + + # Test good callback. + get_client_cert_and_key.return_value = (True, b"cert", b"key") + callback = mtls.default_client_cert_source() + assert callback() == (b"cert", b"key") + + # Test bad callback which throws exception. + get_client_cert_and_key.side_effect = ValueError() + callback = mtls.default_client_cert_source() + with pytest.raises(exceptions.MutualTLSChannelError): + callback() From 75902094d6921aa47516f57b98c43cc3f96715c8 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 14 Apr 2020 13:26:48 -0700 Subject: [PATCH 287/966] chore: release 1.14.0 (#487) * updated CHANGELOG.md [ci skip] * updated setup.cfg [ci skip] * updated setup.py [ci skip] Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index c465e6c78f01..268077fdff5f 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +## [1.14.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.13.1...v1.14.0) (2020-04-13) + + +### Features + +* add default client cert source util ([#486](https://www.github.com/googleapis/google-auth-library-python/issues/486)) ([ed41b49](https://www.github.com/googleapis/google-auth-library-python/commit/ed41b49e9d7ba7402b27107b7aa47eed06ac6c55)) + ### [1.13.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.13.0...v1.13.1) (2020-04-01) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index cb6ed5aa66ba..2acef446b559 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -30,7 +30,7 @@ with io.open("README.rst", "r") as fh: long_description = fh.read() -version = "1.13.1" +version = "1.14.0" setup( name="google-auth", From 72eef050315fea01cdfded04f29273a87f88c42e Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Tue, 21 Apr 2020 14:33:20 -0700 Subject: [PATCH 288/966] fix: support es256 raw format signature (#490) es256 signature in id_token has raw format, however, cryptography library verification/signing only works for asn1 encoded format. Therefore in verification/signing process, we need to convert between the ans1 encoded format and the raw format. --- .../google-auth/google/auth/crypt/es256.py | 19 +++++++++++++++++-- .../google-auth/tests/crypt/test_es256.py | 12 ++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/google/auth/crypt/es256.py b/packages/google-auth/google/auth/crypt/es256.py index 5bfd57fb8838..6955efcc588f 100644 --- a/packages/google-auth/google/auth/crypt/es256.py +++ b/packages/google-auth/google/auth/crypt/es256.py @@ -15,12 +15,15 @@ """ECDSA (ES256) verifier and signer that use the ``cryptography`` library. """ +from cryptography import utils import cryptography.exceptions from cryptography.hazmat import backends from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.asymmetric import padding +from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature +from cryptography.hazmat.primitives.asymmetric.utils import encode_dss_signature import cryptography.x509 import pkg_resources @@ -58,9 +61,17 @@ def __init__(self, public_key): @_helpers.copy_docstring(base.Verifier) def verify(self, message, signature): + # First convert (r||s) raw signature to ASN1 encoded signature. + sig_bytes = _helpers.to_bytes(signature) + if len(sig_bytes) != 64: + return False + r = utils.int_from_bytes(sig_bytes[:32], byteorder="big") + s = utils.int_from_bytes(sig_bytes[32:], byteorder="big") + asn1_sig = encode_dss_signature(r, s) + message = _helpers.to_bytes(message) try: - self._pubkey.verify(signature, message, ec.ECDSA(hashes.SHA256())) + self._pubkey.verify(asn1_sig, message, ec.ECDSA(hashes.SHA256())) return True except (ValueError, cryptography.exceptions.InvalidSignature): return False @@ -118,7 +129,11 @@ def key_id(self): @_helpers.copy_docstring(base.Signer) def sign(self, message): message = _helpers.to_bytes(message) - return self._key.sign(message, ec.ECDSA(hashes.SHA256())) + asn1_signature = self._key.sign(message, ec.ECDSA(hashes.SHA256())) + + # Convert ASN1 encoded signature to (r||s) raw signature. + (r, s) = decode_dss_signature(asn1_signature) + return utils.int_to_bytes(r, 32) + utils.int_to_bytes(s, 32) @classmethod def from_string(cls, key, key_id=None): diff --git a/packages/google-auth/tests/crypt/test_es256.py b/packages/google-auth/tests/crypt/test_es256.py index 087ce6e2324f..5bb9050cd8d7 100644 --- a/packages/google-auth/tests/crypt/test_es256.py +++ b/packages/google-auth/tests/crypt/test_es256.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import base64 import json import os @@ -72,6 +73,17 @@ def test_verify_failure(self): bad_signature2 = b"a" assert not verifier.verify(b"foo", bad_signature2) + def test_verify_failure_with_wrong_raw_signature(self): + to_sign = b"foo" + + # This signature has a wrong "r" value in the "(r,s)" raw signature. + wrong_signature = base64.urlsafe_b64decode( + b"m7oaRxUDeYqjZ8qiMwo0PZLTMZWKJLFQREpqce1StMIa_yXQQ-C5WgeIRHW7OqlYSDL0XbUrj_uAw9i-QhfOJQ==" + ) + + verifier = es256.ES256Verifier.from_string(PUBLIC_KEY_BYTES) + assert not verifier.verify(to_sign, wrong_signature) + def test_from_string_pub_key(self): verifier = es256.ES256Verifier.from_string(PUBLIC_KEY_BYTES) assert isinstance(verifier, es256.ES256Verifier) From bce00678d4aad71abfcbecbed61c50ee9c918e31 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2020 14:50:57 -0700 Subject: [PATCH 289/966] chore: release 1.14.1 (#491) * updated CHANGELOG.md [ci skip] * updated setup.cfg [ci skip] * updated setup.py [ci skip] Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 268077fdff5f..a47c7e67e098 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +### [1.14.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.14.0...v1.14.1) (2020-04-21) + + +### Bug Fixes + +* support es256 raw format signature ([#490](https://www.github.com/googleapis/google-auth-library-python/issues/490)) ([cf2c0a9](https://www.github.com/googleapis/google-auth-library-python/commit/cf2c0a90701ce42f47df71281ae9cdf212c28e0e)) + ## [1.14.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.13.1...v1.14.0) (2020-04-13) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 2acef446b559..a0d279ffc4d8 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -30,7 +30,7 @@ with io.open("README.rst", "r") as fh: long_description = fh.read() -version = "1.14.0" +version = "1.14.1" setup( name="google-auth", From 4117674c2cba994d6c8d592f19afaa5500116e5e Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Tue, 5 May 2020 12:53:37 -0700 Subject: [PATCH 290/966] test: fix the usage so the tests can pass in g3 (#498) * fix: fix the usage so the tests can pass in g3 * use frozenset --- packages/google-auth/google/auth/jwt.py | 2 +- .../google-auth/tests/transport/test_requests.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/google-auth/google/auth/jwt.py b/packages/google-auth/google/auth/jwt.py index 9248eb27f133..24b92eb4fb07 100644 --- a/packages/google-auth/google/auth/jwt.py +++ b/packages/google-auth/google/auth/jwt.py @@ -67,7 +67,7 @@ _DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds _DEFAULT_MAX_CACHE_SIZE = 10 _ALGORITHM_TO_VERIFIER_CLASS = {"RS256": crypt.RSAVerifier} -_CRYPTOGRAPHY_BASED_ALGORITHMS = set(["ES256"]) +_CRYPTOGRAPHY_BASED_ALGORITHMS = frozenset(["ES256"]) if es256 is not None: # pragma: NO COVER _ALGORITHM_TO_VERIFIER_CLASS["ES256"] = es256.ES256Verifier diff --git a/packages/google-auth/tests/transport/test_requests.py b/packages/google-auth/tests/transport/test_requests.py index d6770de73549..d24b6f6445f0 100644 --- a/packages/google-auth/tests/transport/test_requests.py +++ b/packages/google-auth/tests/transport/test_requests.py @@ -55,12 +55,12 @@ def make_guard(self, *args, **kwargs): def test_tracks_elapsed_time_w_numeric_timeout(self, frozen_time): with self.make_guard(timeout=10) as guard: - frozen_time.tick(delta=3.8) + frozen_time.tick(delta=datetime.timedelta(seconds=3.8)) assert guard.remaining_timeout == 6.2 def test_tracks_elapsed_time_w_tuple_timeout(self, frozen_time): with self.make_guard(timeout=(16, 19)) as guard: - frozen_time.tick(delta=3.8) + frozen_time.tick(delta=datetime.timedelta(seconds=3.8)) assert guard.remaining_timeout == (12.2, 15.2) def test_noop_if_no_timeout(self, frozen_time): @@ -72,13 +72,13 @@ def test_noop_if_no_timeout(self, frozen_time): def test_timeout_error_w_numeric_timeout(self, frozen_time): with pytest.raises(requests.exceptions.Timeout): with self.make_guard(timeout=10) as guard: - frozen_time.tick(delta=10.001) + frozen_time.tick(delta=datetime.timedelta(seconds=10.001)) assert guard.remaining_timeout == pytest.approx(-0.001) def test_timeout_error_w_tuple_timeout(self, frozen_time): with pytest.raises(requests.exceptions.Timeout): with self.make_guard(timeout=(11, 10)) as guard: - frozen_time.tick(delta=10.001) + frozen_time.tick(delta=datetime.timedelta(seconds=10.001)) assert guard.remaining_timeout == pytest.approx((0.999, -0.001)) def test_custom_timeout_error_type(self, frozen_time): @@ -87,7 +87,7 @@ class FooError(Exception): with pytest.raises(FooError): with self.make_guard(timeout=1, timeout_error_type=FooError): - frozen_time.tick(2) + frozen_time.tick(delta=datetime.timedelta(seconds=2)) def test_lets_suite_errors_bubble_up(self, frozen_time): with pytest.raises(IndexError): @@ -222,7 +222,7 @@ def test_request_default_timeout(self): authed_session.request("GET", self.TEST_URL) expected_timeout = google.auth.transport.requests._DEFAULT_TIMEOUT - assert patched_request.call_args.kwargs.get("timeout") == expected_timeout + assert patched_request.call_args[1]["timeout"] == expected_timeout def test_request_no_refresh(self): credentials = mock.Mock(wraps=CredentialsStub()) From 069ac06d2c236b2eb22125a1e099c90fb60d7e2e Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Wed, 6 May 2020 16:00:17 -0700 Subject: [PATCH 291/966] chore: support string type response.data for gcloud (#503) --- .../google/auth/impersonated_credentials.py | 8 ++++++-- .../tests/test_impersonated_credentials.py | 18 ++++++++++++++---- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index 1bb6b826861d..c2351e7f71e9 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -88,13 +88,17 @@ def _make_iam_token_request(request, principal, headers, body): response = request(url=iam_endpoint, method="POST", headers=headers, body=body) - response_body = response.data.decode("utf-8") + response_body = ( + response.data.decode("utf-8") + if hasattr(response.data, "decode") + else response.data + ) if response.status != http_client.OK: exceptions.RefreshError(_REFRESH_ERROR, response_body) try: - token_response = json.loads(response.data.decode("utf-8")) + token_response = json.loads(response_body) token = token_response["accessToken"] expiry = datetime.strptime(token_response["expireTime"], "%Y-%m-%dT%H:%M:%SZ") diff --git a/packages/google-auth/tests/test_impersonated_credentials.py b/packages/google-auth/tests/test_impersonated_credentials.py index 31075ca84922..19e2f3421616 100644 --- a/packages/google-auth/tests/test_impersonated_credentials.py +++ b/packages/google-auth/tests/test_impersonated_credentials.py @@ -132,10 +132,17 @@ def test_default_state(self): assert not credentials.valid assert credentials.expired - def make_request(self, data, status=http_client.OK, headers=None, side_effect=None): + def make_request( + self, + data, + status=http_client.OK, + headers=None, + side_effect=None, + use_data_bytes=True, + ): response = mock.create_autospec(transport.Response, instance=False) response.status = status - response.data = _helpers.to_bytes(data) + response.data = _helpers.to_bytes(data) if use_data_bytes else data response.headers = headers or {} request = mock.create_autospec(transport.Request, instance=False) @@ -144,7 +151,8 @@ def make_request(self, data, status=http_client.OK, headers=None, side_effect=No return request - def test_refresh_success(self, mock_donor_credentials): + @pytest.mark.parametrize("use_data_bytes", [True, False]) + def test_refresh_success(self, use_data_bytes, mock_donor_credentials): credentials = self.make_credentials(lifetime=None) token = "token" @@ -154,7 +162,9 @@ def test_refresh_success(self, mock_donor_credentials): response_body = {"accessToken": token, "expireTime": expire_time} request = self.make_request( - data=json.dumps(response_body), status=http_client.OK + data=json.dumps(response_body), + status=http_client.OK, + use_data_bytes=use_data_bytes, ) credentials.refresh(request) From f859981b120ac213eef9f7c4553e42186bbb3453 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Wed, 6 May 2020 17:11:01 -0700 Subject: [PATCH 292/966] fix: support string type response.data (#504) --- packages/google-auth/google/auth/impersonated_credentials.py | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index c2351e7f71e9..84df484a4cea 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -88,6 +88,7 @@ def _make_iam_token_request(request, principal, headers, body): response = request(url=iam_endpoint, method="POST", headers=headers, body=body) + # support both string and bytes type response.data response_body = ( response.data.decode("utf-8") if hasattr(response.data, "decode") From 4babb895a4bde878de1e3ed6cef9ca2aed41b9ec Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 7 May 2020 00:24:11 +0000 Subject: [PATCH 293/966] chore: release 1.14.2 (#505) :robot: I have created a release \*beep\* \*boop\* --- ### [1.14.2](https://www.github.com/googleapis/google-auth-library-python/compare/v1.14.1...v1.14.2) (2020-05-07) ### Bug Fixes * support string type response.data ([#504](https://www.github.com/googleapis/google-auth-library-python/issues/504)) ([9b7228e](https://www.github.com/googleapis/google-auth-library-python/commit/9b7228ec849e311bcb4007ad3e23cf2f1e54a721)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index a47c7e67e098..84f25baba32e 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +### [1.14.2](https://www.github.com/googleapis/google-auth-library-python/compare/v1.14.1...v1.14.2) (2020-05-07) + + +### Bug Fixes + +* support string type response.data ([#504](https://www.github.com/googleapis/google-auth-library-python/issues/504)) ([9b7228e](https://www.github.com/googleapis/google-auth-library-python/commit/9b7228ec849e311bcb4007ad3e23cf2f1e54a721)) + ### [1.14.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.14.0...v1.14.1) (2020-04-21) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index a0d279ffc4d8..83075ecfa7bb 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -30,7 +30,7 @@ with io.open("README.rst", "r") as fh: long_description = fh.read() -version = "1.14.1" +version = "1.14.2" setup( name="google-auth", From 27bb2f173a2ff5b390d7fcdd4a5b3484423145b0 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Fri, 8 May 2020 09:58:04 -0700 Subject: [PATCH 294/966] test: fix usage of tick function so g3 tests can pass (#507) --- .../google-auth/tests/transport/test_requests.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/google-auth/tests/transport/test_requests.py b/packages/google-auth/tests/transport/test_requests.py index d24b6f6445f0..ed388d4790d5 100644 --- a/packages/google-auth/tests/transport/test_requests.py +++ b/packages/google-auth/tests/transport/test_requests.py @@ -268,7 +268,9 @@ def test_request_refresh(self): assert adapter.requests[1].headers["authorization"] == "token1" def test_request_max_allowed_time_timeout_error(self, frozen_time): - tick_one_second = functools.partial(frozen_time.tick, delta=1.0) + tick_one_second = functools.partial( + frozen_time.tick, delta=datetime.timedelta(seconds=1.0) + ) credentials = mock.Mock( wraps=TimeTickCredentialsStub(time_tick=tick_one_second) @@ -286,7 +288,9 @@ def test_request_max_allowed_time_timeout_error(self, frozen_time): authed_session.request("GET", self.TEST_URL, max_allowed_time=0.9) def test_request_max_allowed_time_w_transport_timeout_no_error(self, frozen_time): - tick_one_second = functools.partial(frozen_time.tick, delta=1.0) + tick_one_second = functools.partial( + frozen_time.tick, delta=datetime.timedelta(seconds=1.0) + ) credentials = mock.Mock( wraps=TimeTickCredentialsStub(time_tick=tick_one_second) @@ -308,7 +312,9 @@ def test_request_max_allowed_time_w_transport_timeout_no_error(self, frozen_time authed_session.request("GET", self.TEST_URL, timeout=0.5, max_allowed_time=3.1) def test_request_max_allowed_time_w_refresh_timeout_no_error(self, frozen_time): - tick_one_second = functools.partial(frozen_time.tick, delta=1.0) + tick_one_second = functools.partial( + frozen_time.tick, delta=datetime.timedelta(seconds=1.0) + ) credentials = mock.Mock( wraps=TimeTickCredentialsStub(time_tick=tick_one_second) @@ -333,7 +339,9 @@ def test_request_max_allowed_time_w_refresh_timeout_no_error(self, frozen_time): authed_session.request("GET", self.TEST_URL, timeout=60, max_allowed_time=3.1) def test_request_timeout_w_refresh_timeout_timeout_error(self, frozen_time): - tick_one_second = functools.partial(frozen_time.tick, delta=1.0) + tick_one_second = functools.partial( + frozen_time.tick, delta=datetime.timedelta(seconds=1.0) + ) credentials = mock.Mock( wraps=TimeTickCredentialsStub(time_tick=tick_one_second) From d4b9dafc35835bd955616e6a94c67eade24e0f9b Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Mon, 11 May 2020 14:22:32 -0700 Subject: [PATCH 295/966] fix: catch exceptions.RefreshError (#508) --- packages/google-auth/google/oauth2/id_token.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/google/oauth2/id_token.py b/packages/google-auth/google/oauth2/id_token.py index f559c6c39bbe..e78add417eb2 100644 --- a/packages/google-auth/google/oauth2/id_token.py +++ b/packages/google-auth/google/oauth2/id_token.py @@ -212,7 +212,7 @@ def fetch_id_token(request, audience): ) credentials.refresh(request) return credentials.token - except (ImportError, exceptions.TransportError): + except (ImportError, exceptions.TransportError, exceptions.RefreshError): pass # 2. Try to use service account credentials to get ID token. From 487c7b414831d47da30d5fd6e0fe3bab92ea556d Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 11 May 2020 14:34:48 -0700 Subject: [PATCH 296/966] chore: release 1.14.3 (#509) * updated CHANGELOG.md [ci skip] * updated setup.cfg [ci skip] * updated setup.py [ci skip] Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 84f25baba32e..b2ca9ac11167 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +### [1.14.3](https://www.github.com/googleapis/google-auth-library-python/compare/v1.14.2...v1.14.3) (2020-05-11) + + +### Bug Fixes + +* catch exceptions.RefreshError ([#508](https://www.github.com/googleapis/google-auth-library-python/issues/508)) ([3d672e9](https://www.github.com/googleapis/google-auth-library-python/commit/3d672e9cddd9e8c4946290ab9f90ca9009b8be69)) + ### [1.14.2](https://www.github.com/googleapis/google-auth-library-python/compare/v1.14.1...v1.14.2) (2020-05-07) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 83075ecfa7bb..2835f886b45b 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -30,7 +30,7 @@ with io.open("README.rst", "r") as fh: long_description = fh.read() -version = "1.14.2" +version = "1.14.3" setup( name="google-auth", From 4c55ce6321108abb65571e5322600e1d31f40612 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Wed, 13 May 2020 00:18:50 -0700 Subject: [PATCH 297/966] feat: encrypted mtls private key support (#496) (1) support encrypted private key decryption (2) support reading encrypted key and passphrase from cert provider command Co-authored-by: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> --- .../google-auth/google/auth/exceptions.py | 4 + .../google/auth/transport/_mtls_helper.py | 178 ++++++++++--- .../google-auth/google/auth/transport/grpc.py | 16 +- .../google/auth/transport/requests.py | 4 +- .../google/auth/transport/urllib3.py | 4 +- .../tests/transport/test__mtls_helper.py | 248 +++++++++++++++--- .../google-auth/tests/transport/test_grpc.py | 11 +- .../tests/transport/test_requests.py | 2 +- .../tests/transport/test_urllib3.py | 2 +- 9 files changed, 372 insertions(+), 97 deletions(-) diff --git a/packages/google-auth/google/auth/exceptions.py b/packages/google-auth/google/auth/exceptions.py index 5b614602be6d..da06d8696283 100644 --- a/packages/google-auth/google/auth/exceptions.py +++ b/packages/google-auth/google/auth/exceptions.py @@ -39,3 +39,7 @@ class DefaultCredentialsError(GoogleAuthError): class MutualTLSChannelError(GoogleAuthError): """Used to indicate that mutual TLS channel creation is failed, or mutual TLS channel credentials is missing or invalid.""" + + +class ClientCertError(GoogleAuthError): + """Used to indicate that client certificate is missing or invalid.""" diff --git a/packages/google-auth/google/auth/transport/_mtls_helper.py b/packages/google-auth/google/auth/transport/_mtls_helper.py index c518cc821311..388ae3c1586b 100644 --- a/packages/google-auth/google/auth/transport/_mtls_helper.py +++ b/packages/google-auth/google/auth/transport/_mtls_helper.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Helper functions for getting mTLS cert and key, for internal use only.""" +"""Helper functions for getting mTLS cert and key.""" import json import logging @@ -20,6 +20,10 @@ import re import subprocess +import six + +from google.auth import exceptions + CONTEXT_AWARE_METADATA_PATH = "~/.secureConnect/context_aware_metadata.json" _CERT_PROVIDER_COMMAND = "cert_provider_command" _CERT_REGEX = re.compile( @@ -30,6 +34,7 @@ # "-----BEGIN PRIVATE KEY-----...", # "-----BEGIN EC PRIVATE KEY-----...", # "-----BEGIN RSA PRIVATE KEY-----..." +# "-----BEGIN ENCRYPTED PRIVATE KEY-----" _KEY_REGEX = re.compile( b"-----BEGIN [A-Z ]*PRIVATE KEY-----.+-----END [A-Z ]*PRIVATE KEY-----\r?\n?", re.DOTALL, @@ -38,6 +43,11 @@ _LOGGER = logging.getLogger(__name__) +_PASSPHRASE_REGEX = re.compile( + b"-----BEGIN PASSPHRASE-----(.+)-----END PASSPHRASE-----", re.DOTALL +) + + def _check_dca_metadata_path(metadata_path): """Checks for context aware metadata. If it exists, returns the absolute path; otherwise returns None. @@ -65,57 +75,109 @@ def _read_dca_metadata_file(metadata_path): Dict[str, str]: The metadata. Raises: - ValueError: If failed to parse metadata as JSON. + google.auth.exceptions.ClientCertError: If failed to parse metadata as JSON. """ - with open(metadata_path) as f: - metadata = json.load(f) + try: + with open(metadata_path) as f: + metadata = json.load(f) + except ValueError as caught_exc: + new_exc = exceptions.ClientCertError(caught_exc) + six.raise_from(new_exc, caught_exc) return metadata -def get_client_ssl_credentials(metadata_json): - """Returns the client side mTLS cert and key. +def _run_cert_provider_command(command, expect_encrypted_key=False): + """Run the provided command, and return client side mTLS cert, key and + passphrase. Args: - metadata_json (Dict[str, str]): metadata JSON file which contains the cert - provider command. + command (List[str]): cert provider command. + expect_encrypted_key (bool): If encrypted private key is expected. Returns: - Tuple[bytes, bytes]: client certificate and key, both in PEM format. + Tuple[bytes, bytes, bytes]: client certificate bytes in PEM format, key + bytes in PEM format and passphrase bytes. Raises: - OSError: If the cert provider command failed to run. - RuntimeError: If the cert provider command has a runtime error. - ValueError: If the metadata json file doesn't contain the cert provider - command or if the command doesn't produce both the client certificate - and client key. + google.auth.exceptions.ClientCertError: if problems occurs when running + the cert provider command or generating cert, key and passphrase. """ - # TODO: implement an in-memory cache of cert and key so we don't have to - # run cert provider command every time. - - # Check the cert provider command existence in the metadata json file. - if _CERT_PROVIDER_COMMAND not in metadata_json: - raise ValueError("Cert provider command is not found") - - # Execute the command. It throws OsError in case of system failure. - command = metadata_json[_CERT_PROVIDER_COMMAND] - process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - stdout, stderr = process.communicate() + try: + process = subprocess.Popen( + command, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + stdout, stderr = process.communicate() + except OSError as caught_exc: + new_exc = exceptions.ClientCertError(caught_exc) + six.raise_from(new_exc, caught_exc) # Check cert provider command execution error. if process.returncode != 0: - raise RuntimeError( + raise exceptions.ClientCertError( "Cert provider command returns non-zero status code %s" % process.returncode ) - # Extract certificate (chain) and key. + # Extract certificate (chain), key and passphrase. cert_match = re.findall(_CERT_REGEX, stdout) if len(cert_match) != 1: - raise ValueError("Client SSL certificate is missing or invalid") + raise exceptions.ClientCertError("Client SSL certificate is missing or invalid") key_match = re.findall(_KEY_REGEX, stdout) if len(key_match) != 1: - raise ValueError("Client SSL key is missing or invalid") - return cert_match[0], key_match[0] + raise exceptions.ClientCertError("Client SSL key is missing or invalid") + passphrase_match = re.findall(_PASSPHRASE_REGEX, stdout) + + if expect_encrypted_key: + if len(passphrase_match) != 1: + raise exceptions.ClientCertError("Passphrase is missing or invalid") + if b"ENCRYPTED" not in key_match[0]: + raise exceptions.ClientCertError("Encrypted private key is expected") + return cert_match[0], key_match[0], passphrase_match[0].strip() + + if b"ENCRYPTED" in key_match[0]: + raise exceptions.ClientCertError("Encrypted private key is not expected") + if len(passphrase_match) > 0: + raise exceptions.ClientCertError("Passphrase is not expected") + return cert_match[0], key_match[0], None + + +def get_client_ssl_credentials(generate_encrypted_key=False): + """Returns the client side certificate, private key and passphrase. + + Args: + generate_encrypted_key (bool): If set to True, encrypted private key + and passphrase will be generated; otherwise, unencrypted private key + will be generated and passphrase will be None. + + Returns: + Tuple[bool, bytes, bytes, bytes]: + A boolean indicating if cert, key and passphrase are obtained, the + cert bytes and key bytes both in PEM format, and passphrase bytes. + + Raises: + google.auth.exceptions.ClientCertError: if problems occurs when getting + the cert, key and passphrase. + """ + metadata_path = _check_dca_metadata_path(CONTEXT_AWARE_METADATA_PATH) + + if metadata_path: + metadata_json = _read_dca_metadata_file(metadata_path) + + if _CERT_PROVIDER_COMMAND not in metadata_json: + raise exceptions.ClientCertError("Cert provider command is not found") + + command = metadata_json[_CERT_PROVIDER_COMMAND] + + if generate_encrypted_key and "--with_passphrase" not in command: + command.append("--with_passphrase") + + # Execute the command. + cert, key, passphrase = _run_cert_provider_command( + command, expect_encrypted_key=generate_encrypted_key + ) + return True, cert, key, passphrase + + return False, None, None, None def get_client_cert_and_key(client_cert_callback=None): @@ -135,20 +197,54 @@ def get_client_cert_and_key(client_cert_callback=None): and key bytes both in PEM format. Raises: - OSError: If the cert provider command failed to run. - RuntimeError: If the cert provider command has a runtime error. - ValueError: If the metadata json file doesn't contain the cert provider - command or if the command doesn't produce both the client certificate - and client key. + google.auth.exceptions.ClientCertError: if problems occurs when getting + the cert and key. """ if client_cert_callback: cert, key = client_cert_callback() return True, cert, key - metadata_path = _check_dca_metadata_path(CONTEXT_AWARE_METADATA_PATH) - if metadata_path: - metadata = _read_dca_metadata_file(metadata_path) - cert, key = get_client_ssl_credentials(metadata) - return True, cert, key + has_cert, cert, key, _ = get_client_ssl_credentials(generate_encrypted_key=False) + return has_cert, cert, key + + +def decrypt_private_key(key, passphrase): + """A helper function to decrypt the private key with the given passphrase. + google-auth library doesn't support passphrase protected private key for + mutual TLS channel. This helper function can be used to decrypt the + passphrase protected private key in order to estalish mutual TLS channel. + + For example, if you have a function which produces client cert, passphrase + protected private key and passphrase, you can convert it to a client cert + callback function accepted by google-auth:: + + from google.auth.transport import _mtls_helper + + def your_client_cert_function(): + return cert, encrypted_key, passphrase + + # callback accepted by google-auth for mutual TLS channel. + def client_cert_callback(): + cert, encrypted_key, passphrase = your_client_cert_function() + decrypted_key = _mtls_helper.decrypt_private_key(encrypted_key, + passphrase) + return cert, decrypted_key + + Args: + key (bytes): The private key bytes in PEM format. + passphrase (bytes): The passphrase bytes. + + Returns: + bytes: The decrypted private key in PEM format. + + Raises: + ImportError: If pyOpenSSL is not installed. + OpenSSL.crypto.Error: If there is any problem decrypting the private key. + """ + from OpenSSL import crypto + + # First convert encrypted_key_bytes to PKey object + pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, key, passphrase=passphrase) - return False, None, None + # Then dump the decrypted key bytes + return crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey) diff --git a/packages/google-auth/google/auth/transport/grpc.py b/packages/google-auth/google/auth/transport/grpc.py index d62c41502ca6..13234a331e9c 100644 --- a/packages/google-auth/google/auth/transport/grpc.py +++ b/packages/google-auth/google/auth/transport/grpc.py @@ -264,13 +264,10 @@ class SslCredentials: def __init__(self): # Load client SSL credentials. - self._context_aware_metadata_path = _mtls_helper._check_dca_metadata_path( + metadata_path = _mtls_helper._check_dca_metadata_path( _mtls_helper.CONTEXT_AWARE_METADATA_PATH ) - if self._context_aware_metadata_path: - self._is_mtls = True - else: - self._is_mtls = False + self._is_mtls = metadata_path is not None @property def ssl_credentials(self): @@ -288,16 +285,13 @@ def ssl_credentials(self): google.auth.exceptions.MutualTLSChannelError: If mutual TLS channel creation failed for any reason. """ - if self._context_aware_metadata_path: + if self._is_mtls: try: - metadata = _mtls_helper._read_dca_metadata_file( - self._context_aware_metadata_path - ) - cert, key = _mtls_helper.get_client_ssl_credentials(metadata) + _, cert, key, _ = _mtls_helper.get_client_ssl_credentials() self._ssl_credentials = grpc.ssl_channel_credentials( certificate_chain=cert, private_key=key ) - except (OSError, RuntimeError, ValueError) as caught_exc: + except exceptions.ClientCertError as caught_exc: new_exc = exceptions.MutualTLSChannelError(caught_exc) six.raise_from(new_exc, caught_exc) else: diff --git a/packages/google-auth/google/auth/transport/requests.py b/packages/google-auth/google/auth/transport/requests.py index cc0e93b6fc35..9f55bead6bf2 100644 --- a/packages/google-auth/google/auth/transport/requests.py +++ b/packages/google-auth/google/auth/transport/requests.py @@ -373,11 +373,9 @@ def configure_mtls_channel(self, client_cert_callback=None): mtls_adapter = _MutualTlsAdapter(cert, key) self.mount("https://", mtls_adapter) except ( + exceptions.ClientCertError, ImportError, OpenSSL.crypto.Error, - OSError, - RuntimeError, - ValueError, ) as caught_exc: new_exc = exceptions.MutualTLSChannelError(caught_exc) six.raise_from(new_exc, caught_exc) diff --git a/packages/google-auth/google/auth/transport/urllib3.py b/packages/google-auth/google/auth/transport/urllib3.py index 3771d84c43cb..3742f1a419e7 100644 --- a/packages/google-auth/google/auth/transport/urllib3.py +++ b/packages/google-auth/google/auth/transport/urllib3.py @@ -316,11 +316,9 @@ def configure_mtls_channel(self, client_cert_callback=None): else: self.http = _make_default_http() except ( + exceptions.ClientCertError, ImportError, OpenSSL.crypto.Error, - OSError, - RuntimeError, - ValueError, ) as caught_exc: new_exc = exceptions.MutualTLSChannelError(caught_exc) six.raise_from(new_exc, caught_exc) diff --git a/packages/google-auth/tests/transport/test__mtls_helper.py b/packages/google-auth/tests/transport/test__mtls_helper.py index 5bf196797894..04d0b56d7c03 100644 --- a/packages/google-auth/tests/transport/test__mtls_helper.py +++ b/packages/google-auth/tests/transport/test__mtls_helper.py @@ -16,14 +16,34 @@ import re import mock +from OpenSSL import crypto import pytest +from google.auth import exceptions from google.auth.transport import _mtls_helper CONTEXT_AWARE_METADATA = {"cert_provider_command": ["some command"]} CONTEXT_AWARE_METADATA_NO_CERT_PROVIDER_COMMAND = {} +ENCRYPTED_EC_PRIVATE_KEY = b"""-----BEGIN ENCRYPTED PRIVATE KEY----- +MIHkME8GCSqGSIb3DQEFDTBCMCkGCSqGSIb3DQEFDDAcBAgl2/yVgs1h3QICCAAw +DAYIKoZIhvcNAgkFADAVBgkrBgEEAZdVAQIECJk2GRrvxOaJBIGQXIBnMU4wmciT +uA6yD8q0FxuIzjG7E2S6tc5VRgSbhRB00eBO3jWmO2pBybeQW+zVioDcn50zp2ts +wYErWC+LCm1Zg3r+EGnT1E1GgNoODbVQ3AEHlKh1CGCYhEovxtn3G+Fjh7xOBrNB +saVVeDb4tHD4tMkiVVUBrUcTZPndP73CtgyGHYEphasYPzEz3+AU +-----END ENCRYPTED PRIVATE KEY-----""" + +EC_PUBLIC_KEY = b"""-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEvCNi1NoDY1oMqPHIgXI8RBbTYGi/ +brEjbre1nSiQW11xRTJbVeETdsuP0EAu2tG3PcRhhwDfeJ8zXREgTBurNw== +-----END PUBLIC KEY-----""" + +PASSPHRASE = b"""-----BEGIN PASSPHRASE----- +password +-----END PASSPHRASE-----""" +PASSPHRASE_VALUE = b"password" + def check_cert_and_key(content, expected_cert, expected_key): success = True @@ -115,11 +135,11 @@ def test_success(self): def test_file_not_json(self): # read a file which is not json format. metadata_path = os.path.join(pytest.data_dir, "privatekey.pem") - with pytest.raises(ValueError): + with pytest.raises(exceptions.ClientCertError): _mtls_helper._read_dca_metadata_file(metadata_path) -class TestGetClientSslCredentials(object): +class TestRunCertProviderCommand(object): def create_mock_process(self, output, error): # There are two steps to execute a script with subprocess.Popen. # (1) process = subprocess.Popen([comannds]) @@ -137,9 +157,20 @@ def test_success(self, mock_popen): mock_popen.return_value = self.create_mock_process( pytest.public_cert_bytes + pytest.private_key_bytes, b"" ) - cert, key = _mtls_helper.get_client_ssl_credentials(CONTEXT_AWARE_METADATA) + cert, key, passphrase = _mtls_helper._run_cert_provider_command(["command"]) assert cert == pytest.public_cert_bytes assert key == pytest.private_key_bytes + assert passphrase is None + + mock_popen.return_value = self.create_mock_process( + pytest.public_cert_bytes + ENCRYPTED_EC_PRIVATE_KEY + PASSPHRASE, b"" + ) + cert, key, passphrase = _mtls_helper._run_cert_provider_command( + ["command"], expect_encrypted_key=True + ) + assert cert == pytest.public_cert_bytes + assert key == ENCRYPTED_EC_PRIVATE_KEY + assert passphrase == PASSPHRASE_VALUE @mock.patch("subprocess.Popen", autospec=True) def test_success_with_cert_chain(self, mock_popen): @@ -147,44 +178,185 @@ def test_success_with_cert_chain(self, mock_popen): mock_popen.return_value = self.create_mock_process( PUBLIC_CERT_CHAIN_BYTES + pytest.private_key_bytes, b"" ) - cert, key = _mtls_helper.get_client_ssl_credentials(CONTEXT_AWARE_METADATA) + cert, key, passphrase = _mtls_helper._run_cert_provider_command(["command"]) assert cert == PUBLIC_CERT_CHAIN_BYTES assert key == pytest.private_key_bytes + assert passphrase is None - def test_missing_cert_provider_command(self): - with pytest.raises(ValueError): - assert _mtls_helper.get_client_ssl_credentials( - CONTEXT_AWARE_METADATA_NO_CERT_PROVIDER_COMMAND - ) + mock_popen.return_value = self.create_mock_process( + PUBLIC_CERT_CHAIN_BYTES + ENCRYPTED_EC_PRIVATE_KEY + PASSPHRASE, b"" + ) + cert, key, passphrase = _mtls_helper._run_cert_provider_command( + ["command"], expect_encrypted_key=True + ) + assert cert == PUBLIC_CERT_CHAIN_BYTES + assert key == ENCRYPTED_EC_PRIVATE_KEY + assert passphrase == PASSPHRASE_VALUE @mock.patch("subprocess.Popen", autospec=True) def test_missing_cert(self, mock_popen): mock_popen.return_value = self.create_mock_process( pytest.private_key_bytes, b"" ) - with pytest.raises(ValueError): - assert _mtls_helper.get_client_ssl_credentials(CONTEXT_AWARE_METADATA) + with pytest.raises(exceptions.ClientCertError): + _mtls_helper._run_cert_provider_command(["command"]) + + mock_popen.return_value = self.create_mock_process( + ENCRYPTED_EC_PRIVATE_KEY + PASSPHRASE, b"" + ) + with pytest.raises(exceptions.ClientCertError): + _mtls_helper._run_cert_provider_command( + ["command"], expect_encrypted_key=True + ) @mock.patch("subprocess.Popen", autospec=True) def test_missing_key(self, mock_popen): mock_popen.return_value = self.create_mock_process( pytest.public_cert_bytes, b"" ) - with pytest.raises(ValueError): - assert _mtls_helper.get_client_ssl_credentials(CONTEXT_AWARE_METADATA) + with pytest.raises(exceptions.ClientCertError): + _mtls_helper._run_cert_provider_command(["command"]) + + mock_popen.return_value = self.create_mock_process( + pytest.public_cert_bytes + PASSPHRASE, b"" + ) + with pytest.raises(exceptions.ClientCertError): + _mtls_helper._run_cert_provider_command( + ["command"], expect_encrypted_key=True + ) + + @mock.patch("subprocess.Popen", autospec=True) + def test_missing_passphrase(self, mock_popen): + mock_popen.return_value = self.create_mock_process( + pytest.public_cert_bytes + ENCRYPTED_EC_PRIVATE_KEY, b"" + ) + with pytest.raises(exceptions.ClientCertError): + _mtls_helper._run_cert_provider_command( + ["command"], expect_encrypted_key=True + ) + + @mock.patch("subprocess.Popen", autospec=True) + def test_passphrase_not_expected(self, mock_popen): + mock_popen.return_value = self.create_mock_process( + pytest.public_cert_bytes + pytest.private_key_bytes + PASSPHRASE, b"" + ) + with pytest.raises(exceptions.ClientCertError): + _mtls_helper._run_cert_provider_command(["command"]) + + @mock.patch("subprocess.Popen", autospec=True) + def test_encrypted_key_expected(self, mock_popen): + mock_popen.return_value = self.create_mock_process( + pytest.public_cert_bytes + pytest.private_key_bytes + PASSPHRASE, b"" + ) + with pytest.raises(exceptions.ClientCertError): + _mtls_helper._run_cert_provider_command( + ["command"], expect_encrypted_key=True + ) + + @mock.patch("subprocess.Popen", autospec=True) + def test_unencrypted_key_expected(self, mock_popen): + mock_popen.return_value = self.create_mock_process( + pytest.public_cert_bytes + ENCRYPTED_EC_PRIVATE_KEY, b"" + ) + with pytest.raises(exceptions.ClientCertError): + _mtls_helper._run_cert_provider_command(["command"]) @mock.patch("subprocess.Popen", autospec=True) def test_cert_provider_returns_error(self, mock_popen): mock_popen.return_value = self.create_mock_process(b"", b"some error") mock_popen.return_value.returncode = 1 - with pytest.raises(RuntimeError): - assert _mtls_helper.get_client_ssl_credentials(CONTEXT_AWARE_METADATA) + with pytest.raises(exceptions.ClientCertError): + _mtls_helper._run_cert_provider_command(["command"]) @mock.patch("subprocess.Popen", autospec=True) def test_popen_raise_exception(self, mock_popen): mock_popen.side_effect = OSError() - with pytest.raises(OSError): - assert _mtls_helper.get_client_ssl_credentials(CONTEXT_AWARE_METADATA) + with pytest.raises(exceptions.ClientCertError): + _mtls_helper._run_cert_provider_command(["command"]) + + +class TestGetClientSslCredentials(object): + @mock.patch( + "google.auth.transport._mtls_helper._run_cert_provider_command", autospec=True + ) + @mock.patch( + "google.auth.transport._mtls_helper._read_dca_metadata_file", autospec=True + ) + @mock.patch( + "google.auth.transport._mtls_helper._check_dca_metadata_path", autospec=True + ) + def test_success( + self, + mock_check_dca_metadata_path, + mock_read_dca_metadata_file, + mock_run_cert_provider_command, + ): + mock_check_dca_metadata_path.return_value = True + mock_read_dca_metadata_file.return_value = { + "cert_provider_command": ["command"] + } + mock_run_cert_provider_command.return_value = (b"cert", b"key", None) + has_cert, cert, key, passphrase = _mtls_helper.get_client_ssl_credentials() + assert has_cert + assert cert == b"cert" + assert key == b"key" + assert passphrase is None + + @mock.patch( + "google.auth.transport._mtls_helper._check_dca_metadata_path", autospec=True + ) + def test_success_without_metadata(self, mock_check_dca_metadata_path): + mock_check_dca_metadata_path.return_value = False + has_cert, cert, key, passphrase = _mtls_helper.get_client_ssl_credentials() + assert not has_cert + assert cert is None + assert key is None + assert passphrase is None + + @mock.patch( + "google.auth.transport._mtls_helper._run_cert_provider_command", autospec=True + ) + @mock.patch( + "google.auth.transport._mtls_helper._read_dca_metadata_file", autospec=True + ) + @mock.patch( + "google.auth.transport._mtls_helper._check_dca_metadata_path", autospec=True + ) + def test_success_with_encrypted_key( + self, + mock_check_dca_metadata_path, + mock_read_dca_metadata_file, + mock_run_cert_provider_command, + ): + mock_check_dca_metadata_path.return_value = True + mock_read_dca_metadata_file.return_value = { + "cert_provider_command": ["command"] + } + mock_run_cert_provider_command.return_value = (b"cert", b"key", b"passphrase") + has_cert, cert, key, passphrase = _mtls_helper.get_client_ssl_credentials( + generate_encrypted_key=True + ) + assert has_cert + assert cert == b"cert" + assert key == b"key" + assert passphrase == b"passphrase" + mock_run_cert_provider_command.assert_called_once_with( + ["command", "--with_passphrase"], expect_encrypted_key=True + ) + + @mock.patch( + "google.auth.transport._mtls_helper._read_dca_metadata_file", autospec=True + ) + @mock.patch( + "google.auth.transport._mtls_helper._check_dca_metadata_path", autospec=True + ) + def test_missing_cert_command( + self, mock_check_dca_metadata_path, mock_read_dca_metadata_file + ): + mock_check_dca_metadata_path.return_value = True + mock_read_dca_metadata_file.return_value = {} + with pytest.raises(exceptions.ClientCertError): + _mtls_helper.get_client_ssl_credentials() class TestGetClientCertAndKey(object): @@ -197,33 +369,39 @@ def test_callback_success(self): assert cert == pytest.public_cert_bytes assert key == pytest.private_key_bytes - @mock.patch( - "google.auth.transport._mtls_helper._check_dca_metadata_path", autospec=True - ) - def test_no_metadata(self, mock_check_dca_metadata_path): - mock_check_dca_metadata_path.return_value = None - - found_cert_key, cert, key = _mtls_helper.get_client_cert_and_key() - assert not found_cert_key - @mock.patch( "google.auth.transport._mtls_helper.get_client_ssl_credentials", autospec=True ) - @mock.patch( - "google.auth.transport._mtls_helper._check_dca_metadata_path", autospec=True - ) - def test_use_metadata( - self, mock_check_dca_metadata_path, mock_get_client_ssl_credentials - ): - mock_check_dca_metadata_path.return_value = os.path.join( - pytest.data_dir, "context_aware_metadata.json" - ) + def test_use_metadata(self, mock_get_client_ssl_credentials): mock_get_client_ssl_credentials.return_value = ( + True, pytest.public_cert_bytes, pytest.private_key_bytes, + None, ) found_cert_key, cert, key = _mtls_helper.get_client_cert_and_key() assert found_cert_key assert cert == pytest.public_cert_bytes assert key == pytest.private_key_bytes + + +class TestDecryptPrivateKey(object): + def test_success(self): + decrypted_key = _mtls_helper.decrypt_private_key( + ENCRYPTED_EC_PRIVATE_KEY, PASSPHRASE_VALUE + ) + private_key = crypto.load_privatekey(crypto.FILETYPE_PEM, decrypted_key) + public_key = crypto.load_publickey(crypto.FILETYPE_PEM, EC_PUBLIC_KEY) + x509 = crypto.X509() + x509.set_pubkey(public_key) + + # Test the decrypted key works by signing and verification. + signature = crypto.sign(private_key, b"data", "sha256") + crypto.verify(x509, signature, b"data", "sha256") + + def test_crypto_error(self): + with pytest.raises(crypto.Error): + _mtls_helper.decrypt_private_key( + ENCRYPTED_EC_PRIVATE_KEY, b"wrong_password" + ) diff --git a/packages/google-auth/tests/transport/test_grpc.py b/packages/google-auth/tests/transport/test_grpc.py index 5c61f96e151c..c3da76df12b9 100644 --- a/packages/google-auth/tests/transport/test_grpc.py +++ b/packages/google-auth/tests/transport/test_grpc.py @@ -129,7 +129,12 @@ def test_secure_authorized_channel_adc( read_dca_metadata_file.return_value = { "cert_provider_command": ["some command"] } - get_client_ssl_credentials.return_value = (PUBLIC_CERT_BYTES, PRIVATE_KEY_BYTES) + get_client_ssl_credentials.return_value = ( + True, + PUBLIC_CERT_BYTES, + PRIVATE_KEY_BYTES, + None, + ) channel = google.auth.transport.grpc.secure_authorized_channel( credentials, request, target, options=mock.sentinel.options @@ -314,7 +319,7 @@ def test_get_client_ssl_credentials_failure( } # Mock that client cert and key are not loaded and exception is raised. - mock_get_client_ssl_credentials.side_effect = ValueError() + mock_get_client_ssl_credentials.side_effect = exceptions.ClientCertError() with pytest.raises(exceptions.MutualTLSChannelError): assert google.auth.transport.grpc.SslCredentials().ssl_credentials @@ -331,8 +336,10 @@ def test_get_client_ssl_credentials_success( "cert_provider_command": ["some command"] } mock_get_client_ssl_credentials.return_value = ( + True, PUBLIC_CERT_BYTES, PRIVATE_KEY_BYTES, + None, ) ssl_credentials = google.auth.transport.grpc.SslCredentials() diff --git a/packages/google-auth/tests/transport/test_requests.py b/packages/google-auth/tests/transport/test_requests.py index ed388d4790d5..77e1527a9bea 100644 --- a/packages/google-auth/tests/transport/test_requests.py +++ b/packages/google-auth/tests/transport/test_requests.py @@ -429,7 +429,7 @@ def test_configure_mtls_channel_non_mtls( "google.auth.transport._mtls_helper.get_client_cert_and_key", autospec=True ) def test_configure_mtls_channel_exceptions(self, mock_get_client_cert_and_key): - mock_get_client_cert_and_key.side_effect = ValueError() + mock_get_client_cert_and_key.side_effect = exceptions.ClientCertError() auth_session = google.auth.transport.requests.AuthorizedSession( credentials=mock.Mock() diff --git a/packages/google-auth/tests/transport/test_urllib3.py b/packages/google-auth/tests/transport/test_urllib3.py index a25fcd7d97af..1a1c0a1e6801 100644 --- a/packages/google-auth/tests/transport/test_urllib3.py +++ b/packages/google-auth/tests/transport/test_urllib3.py @@ -233,7 +233,7 @@ def test_configure_mtls_channel_exceptions(self, mock_get_client_cert_and_key): credentials=mock.Mock() ) - mock_get_client_cert_and_key.side_effect = ValueError() + mock_get_client_cert_and_key.side_effect = exceptions.ClientCertError() with pytest.raises(exceptions.MutualTLSChannelError): authed_http.configure_mtls_channel() From dc5aa6dd1bc418984eb3999da486d16648822797 Mon Sep 17 00:00:00 2001 From: Aniruddha Maru Date: Fri, 15 May 2020 14:52:50 -0700 Subject: [PATCH 298/966] fix: signBytes for impersonated credentials (#506) * fix: signBytes doesn't work for impersonated credentials Fixes #338 * black --- packages/google-auth/google/auth/impersonated_credentials.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index 84df484a4cea..42998021063b 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -264,7 +264,10 @@ def sign_bytes(self, message): iam_sign_endpoint = _IAM_SIGN_ENDPOINT.format(self._target_principal) - body = {"payload": base64.b64encode(message), "delegates": self._delegates} + body = { + "payload": base64.b64encode(message).decode("utf-8"), + "delegates": self._delegates, + } headers = {"Content-Type": "application/json"} From 4878627a07c7fee22a29bcb0f6b08119c96f351e Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 18 May 2020 10:11:09 -0700 Subject: [PATCH 299/966] chore: release 1.15.0 (#510) * updated CHANGELOG.md [ci skip] * updated setup.cfg [ci skip] * updated setup.py [ci skip] Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 12 ++++++++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index b2ca9ac11167..215ddae9a982 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,18 @@ [1]: https://pypi.org/project/google-auth/#history +## [1.15.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.14.3...v1.15.0) (2020-05-15) + + +### Features + +* encrypted mtls private key support ([#496](https://www.github.com/googleapis/google-auth-library-python/issues/496)) ([9dc9e9f](https://www.github.com/googleapis/google-auth-library-python/commit/9dc9e9f4ca65780b4d7f24e2c36021d2300b4006)) + + +### Bug Fixes + +* signBytes for impersonated credentials ([#506](https://www.github.com/googleapis/google-auth-library-python/issues/506)) ([ca8d98a](https://www.github.com/googleapis/google-auth-library-python/commit/ca8d98ab2e5277e53ab8df78beb1e75cdf5321e3)), closes [#338](https://www.github.com/googleapis/google-auth-library-python/issues/338) + ### [1.14.3](https://www.github.com/googleapis/google-auth-library-python/compare/v1.14.2...v1.14.3) (2020-05-11) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 2835f886b45b..09bbcef2bc49 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -30,7 +30,7 @@ with io.open("README.rst", "r") as fh: long_description = fh.read() -version = "1.14.3" +version = "1.15.0" setup( name="google-auth", From 0e1ab395b9eaf2117f91fd3c31ddb4ccd41a7973 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Thu, 28 May 2020 11:01:24 -0700 Subject: [PATCH 300/966] fix: fix impersonated cred for gcloud (#516) * fix: fix impersonated cred for gcloud (1) For IDTokenCredentials.refresh(self.request), use the provided request instead of creating a new one (2) For Credentials, only refresh the source credentials if it is not valid * update expired func --- .../google/auth/impersonated_credentials.py | 13 +++---- .../tests/test_impersonated_credentials.py | 38 +++++++++++++++++++ 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index 42998021063b..c6822776b84f 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -226,10 +226,6 @@ def __init__( def refresh(self, request): self._update_token(request) - @property - def expired(self): - return _helpers.utcnow() >= self.expiry - def _update_token(self, request): """Updates credentials with a new access_token representing the impersonated account. @@ -239,8 +235,9 @@ def _update_token(self, request): to use for refreshing credentials. """ - # Refresh our source credentials. - self._source_credentials.refresh(request) + # Refresh our source credentials if it is not valid. + if not self._source_credentials.valid: + self._source_credentials.refresh(request) body = { "delegates": self._delegates, @@ -347,7 +344,9 @@ def refresh(self, request): headers = {"Content-Type": "application/json"} - authed_session = AuthorizedSession(self._target_credentials._source_credentials) + authed_session = AuthorizedSession( + self._target_credentials._source_credentials, auth_request=request + ) response = authed_session.post( url=iam_sign_endpoint, diff --git a/packages/google-auth/tests/test_impersonated_credentials.py b/packages/google-auth/tests/test_impersonated_credentials.py index 19e2f3421616..e0b5b1179e9e 100644 --- a/packages/google-auth/tests/test_impersonated_credentials.py +++ b/packages/google-auth/tests/test_impersonated_credentials.py @@ -172,6 +172,44 @@ def test_refresh_success(self, use_data_bytes, mock_donor_credentials): assert credentials.valid assert not credentials.expired + @pytest.mark.parametrize("time_skew", [100, -100]) + def test_refresh_source_credentials(self, time_skew): + credentials = self.make_credentials(lifetime=None) + + # Source credentials is refreshed only if it is expired within + # _helpers.CLOCK_SKEW from now. We add a time_skew to the expiry, so + # source credentials is refreshed only if time_skew <= 0. + credentials._source_credentials.expiry = ( + _helpers.utcnow() + + _helpers.CLOCK_SKEW + + datetime.timedelta(seconds=time_skew) + ) + credentials._source_credentials.token = "Token" + + with mock.patch( + "google.oauth2.service_account.Credentials.refresh", autospec=True + ) as source_cred_refresh: + expire_time = ( + _helpers.utcnow().replace(microsecond=0) + + datetime.timedelta(seconds=500) + ).isoformat("T") + "Z" + response_body = {"accessToken": "token", "expireTime": expire_time} + request = self.make_request( + data=json.dumps(response_body), status=http_client.OK + ) + + credentials.refresh(request) + + assert credentials.valid + assert not credentials.expired + + # Source credentials is refreshed only if it is expired within + # _helpers.CLOCK_SKEW + if time_skew > 0: + source_cred_refresh.assert_not_called() + else: + source_cred_refresh.assert_called_once() + def test_refresh_failure_malformed_expire_time(self, mock_donor_credentials): credentials = self.make_credentials(lifetime=None) token = "token" From e303c23b6056f39b7ab40c0d061bfe1bae640d04 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Thu, 28 May 2020 11:59:47 -0700 Subject: [PATCH 301/966] feat: add helper func to for default encrypted cert (#514) * feat: helper func to for default encrpted cert --- .../google-auth/google/auth/transport/mtls.py | 42 +++++++++++++++++++ .../google-auth/tests/transport/test_mtls.py | 28 +++++++++++++ 2 files changed, 70 insertions(+) diff --git a/packages/google-auth/google/auth/transport/mtls.py b/packages/google-auth/google/auth/transport/mtls.py index 063b26504f8f..5b742306b54d 100644 --- a/packages/google-auth/google/auth/transport/mtls.py +++ b/packages/google-auth/google/auth/transport/mtls.py @@ -58,3 +58,45 @@ def callback(): return cert_bytes, key_bytes return callback + + +def default_client_encrypted_cert_source(cert_path, key_path): + """Get a callback which returns the default encrpyted client SSL credentials. + + Args: + cert_path (str): The cert file path. The default client certificate will + be written to this file when the returned callback is called. + key_path (str): The key file path. The default encrypted client key will + be written to this file when the returned callback is called. + + Returns: + Callable[[], [str, str, bytes]]: A callback which generates the default + client certificate, encrpyted private key and passphrase. It writes + the certificate and private key into the cert_path and key_path, and + returns the cert_path, key_path and passphrase bytes. + + Raises: + google.auth.exceptions.DefaultClientCertSourceError: If any problem + occurs when loading or saving the client certificate and key. + """ + if not has_default_client_cert_source(): + raise exceptions.MutualTLSChannelError( + "Default client encrypted cert source doesn't exist" + ) + + def callback(): + try: + _, cert_bytes, key_bytes, passphrase_bytes = _mtls_helper.get_client_ssl_credentials( + generate_encrypted_key=True + ) + with open(cert_path, "wb") as cert_file: + cert_file.write(cert_bytes) + with open(key_path, "wb") as key_file: + key_file.write(key_bytes) + except (exceptions.ClientCertError, OSError) as caught_exc: + new_exc = exceptions.MutualTLSChannelError(caught_exc) + six.raise_from(new_exc, caught_exc) + + return cert_path, key_path, passphrase_bytes + + return callback diff --git a/packages/google-auth/tests/transport/test_mtls.py b/packages/google-auth/tests/transport/test_mtls.py index d3bc3915a291..ff70bb3c2290 100644 --- a/packages/google-auth/tests/transport/test_mtls.py +++ b/packages/google-auth/tests/transport/test_mtls.py @@ -53,3 +53,31 @@ def test_default_client_cert_source( callback = mtls.default_client_cert_source() with pytest.raises(exceptions.MutualTLSChannelError): callback() + + +@mock.patch( + "google.auth.transport._mtls_helper.get_client_ssl_credentials", autospec=True +) +@mock.patch("google.auth.transport.mtls.has_default_client_cert_source", autospec=True) +def test_default_client_encrypted_cert_source( + has_default_client_cert_source, get_client_ssl_credentials +): + # Test default client cert source doesn't exist. + has_default_client_cert_source.return_value = False + with pytest.raises(exceptions.MutualTLSChannelError): + mtls.default_client_encrypted_cert_source("cert_path", "key_path") + + # The following tests will assume default client cert source exists. + has_default_client_cert_source.return_value = True + + # Test good callback. + get_client_ssl_credentials.return_value = (True, b"cert", b"key", b"passphrase") + callback = mtls.default_client_encrypted_cert_source("cert_path", "key_path") + with mock.patch("{}.open".format(__name__), return_value=mock.MagicMock()): + assert callback() == ("cert_path", "key_path", b"passphrase") + + # Test bad callback which throws exception. + get_client_ssl_credentials.side_effect = exceptions.ClientCertError() + callback = mtls.default_client_encrypted_cert_source("cert_path", "key_path") + with pytest.raises(exceptions.MutualTLSChannelError): + callback() From 6d8a2573cd4e27c7f667c8910b4a64b41b425c5f Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 28 May 2020 12:44:34 -0700 Subject: [PATCH 302/966] chore: release 1.16.0 (#518) * updated CHANGELOG.md [ci skip] * updated setup.cfg [ci skip] * updated setup.py Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 12 ++++++++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 215ddae9a982..11f1a4d0af25 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,18 @@ [1]: https://pypi.org/project/google-auth/#history +## [1.16.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.15.0...v1.16.0) (2020-05-28) + + +### Features + +* add helper func to for default encrypted cert ([#514](https://www.github.com/googleapis/google-auth-library-python/issues/514)) ([f282aa4](https://www.github.com/googleapis/google-auth-library-python/commit/f282aa4acc73d5b56aa7d4bb745d286c3cf1fc39)) + + +### Bug Fixes + +* fix impersonated cred for gcloud ([#516](https://www.github.com/googleapis/google-auth-library-python/issues/516)) ([eb7be3f](https://www.github.com/googleapis/google-auth-library-python/commit/eb7be3fa98ace42b3e949a8af90bbb978ae7e455)) + ## [1.15.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.14.3...v1.15.0) (2020-05-15) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 09bbcef2bc49..212228d12595 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -30,7 +30,7 @@ with io.open("README.rst", "r") as fh: long_description = fh.read() -version = "1.15.0" +version = "1.16.0" setup( name="google-auth", From a46a7a11eaf5ba903bdb9958eac48b34601575ae Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Wed, 3 Jun 2020 10:47:36 -0700 Subject: [PATCH 303/966] fix: fix impersonated cred exception doc (#521) --- .../google/auth/impersonated_credentials.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index c6822776b84f..58e1bab06970 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -75,12 +75,12 @@ def _make_iam_token_request(request, principal, headers, body): API call. Raises: - TransportError: Raised if there is an underlying HTTP connection - Error - DefaultCredentialsError: Raised if the impersonated credentials - are not available. Common reasons are - `iamcredentials.googleapis.com` is not enabled or the - `Service Account Token Creator` is not assigned + google.auth.exceptions.TransportError: Raised if there is an underlying + HTTP connection error + google.auth.exceptions.RefreshError: Raised if the impersonated + credentials are not available. Common reasons are + `iamcredentials.googleapis.com` is not enabled or the + `Service Account Token Creator` is not assigned """ iam_endpoint = _IAM_ENDPOINT.format(principal) From eb42840549f08c2caa944454a21adf760bd3ac03 Mon Sep 17 00:00:00 2001 From: chenyumic Date: Thu, 4 Jun 2020 10:14:05 -0700 Subject: [PATCH 304/966] fix: replace environment variable GCE_METADATA_ROOT with GCE_METADATA_HOST (#433) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …_HOST * keeps consistent naming across auth packages of all languages. The package will now check GCE_METADATA_HOST (the new name) first; if not present, it falls back to GCE_METADATA_ROOT (the old name), then the default value. closes [#339](https://github.com/googleapis/google-auth-library-python/issues/339). --- .../google/auth/compute_engine/_metadata.py | 13 ++++++++--- .../google/auth/environment_vars.py | 8 ++++++- .../tests/compute_engine/test__metadata.py | 22 ++++++++++++++++++- 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/packages/google-auth/google/auth/compute_engine/_metadata.py b/packages/google-auth/google/auth/compute_engine/_metadata.py index 2861192d0849..cba426fb228a 100644 --- a/packages/google-auth/google/auth/compute_engine/_metadata.py +++ b/packages/google-auth/google/auth/compute_engine/_metadata.py @@ -32,9 +32,16 @@ _LOGGER = logging.getLogger(__name__) -_METADATA_ROOT = "http://{}/computeMetadata/v1/".format( - os.getenv(environment_vars.GCE_METADATA_ROOT, "metadata.google.internal") -) +# Environment variable GCE_METADATA_HOST is originally named +# GCE_METADATA_ROOT. For compatiblity reasons, here it checks +# the new variable first; if not set, the system falls back +# to the old variable. +_GCE_METADATA_HOST = os.getenv(environment_vars.GCE_METADATA_HOST, None) +if not _GCE_METADATA_HOST: + _GCE_METADATA_HOST = os.getenv( + environment_vars.GCE_METADATA_ROOT, "metadata.google.internal" + ) +_METADATA_ROOT = "http://{}/computeMetadata/v1/".format(_GCE_METADATA_HOST) # This is used to ping the metadata server, it avoids the cost of a DNS # lookup. diff --git a/packages/google-auth/google/auth/environment_vars.py b/packages/google-auth/google/auth/environment_vars.py index 6a596f22da90..9c1367f5221e 100644 --- a/packages/google-auth/google/auth/environment_vars.py +++ b/packages/google-auth/google/auth/environment_vars.py @@ -40,9 +40,15 @@ # These two variables allow for customization of the addresses used when # contacting the GCE metadata service. +GCE_METADATA_HOST = "GCE_METADATA_HOST" GCE_METADATA_ROOT = "GCE_METADATA_ROOT" """Environment variable providing an alternate hostname or host:port to be -used for GCE metadata requests.""" +used for GCE metadata requests. + +This environment variable is originally named GCE_METADATA_ROOT. System will +check the new variable first; should there be no value present, +the system falls back to the old variable. +""" GCE_METADATA_IP = "GCE_METADATA_IP" """Environment variable providing an alternate ip:port to be used for ip-only diff --git a/packages/google-auth/tests/compute_engine/test__metadata.py b/packages/google-auth/tests/compute_engine/test__metadata.py index 8b5eece7a80e..d9b039a32751 100644 --- a/packages/google-auth/tests/compute_engine/test__metadata.py +++ b/packages/google-auth/tests/compute_engine/test__metadata.py @@ -155,7 +155,27 @@ def test_get_success_text(): assert result == data -def test_get_success_custom_root(): +def test_get_success_custom_root_new_variable(): + request = make_request("{}", headers={"content-type": "application/json"}) + + fake_root = "another.metadata.service" + os.environ[environment_vars.GCE_METADATA_HOST] = fake_root + reload_module(_metadata) + + try: + _metadata.get(request, PATH) + finally: + del os.environ[environment_vars.GCE_METADATA_HOST] + reload_module(_metadata) + + request.assert_called_once_with( + method="GET", + url="http://{}/computeMetadata/v1/{}".format(fake_root, PATH), + headers=_metadata._METADATA_HEADERS, + ) + + +def test_get_success_custom_root_old_variable(): request = make_request("{}", headers={"content-type": "application/json"}) fake_root = "another.metadata.service" From 5a3c757e98bd13b3bfa704be90590ab369bd289c Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 4 Jun 2020 17:30:05 +0000 Subject: [PATCH 305/966] chore: release 1.16.1 (#522) :robot: I have created a release \*beep\* \*boop\* --- ### [1.16.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.16.0...v1.16.1) (2020-06-04) ### Bug Fixes * fix impersonated cred exception doc ([#521](https://www.github.com/googleapis/google-auth-library-python/issues/521)) ([9d5a9a9](https://www.github.com/googleapis/google-auth-library-python/commit/9d5a9a9884fecbd698a602d2a9fd9bec6b987de7)) * replace environment variable GCE_METADATA_ROOT with GCE_METADATA_HOST ([#433](https://www.github.com/googleapis/google-auth-library-python/issues/433)) ([8ffb4d3](https://www.github.com/googleapis/google-auth-library-python/commit/8ffb4d3e832607869026444e5a071c5f3e225fd2)), closes [#339](https://www.github.com/googleapis/google-auth-library-python/issues/339) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). --- packages/google-auth/CHANGELOG.md | 8 ++++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 11f1a4d0af25..945f8ea9ec9a 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,14 @@ [1]: https://pypi.org/project/google-auth/#history +### [1.16.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.16.0...v1.16.1) (2020-06-04) + + +### Bug Fixes + +* fix impersonated cred exception doc ([#521](https://www.github.com/googleapis/google-auth-library-python/issues/521)) ([9d5a9a9](https://www.github.com/googleapis/google-auth-library-python/commit/9d5a9a9884fecbd698a602d2a9fd9bec6b987de7)) +* replace environment variable GCE_METADATA_ROOT with GCE_METADATA_HOST ([#433](https://www.github.com/googleapis/google-auth-library-python/issues/433)) ([8ffb4d3](https://www.github.com/googleapis/google-auth-library-python/commit/8ffb4d3e832607869026444e5a071c5f3e225fd2)), closes [#339](https://www.github.com/googleapis/google-auth-library-python/issues/339) + ## [1.16.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.15.0...v1.16.0) (2020-05-28) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 212228d12595..e02e9f9c5686 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -30,7 +30,7 @@ with io.open("README.rst", "r") as fh: long_description = fh.read() -version = "1.16.0" +version = "1.16.1" setup( name="google-auth", From ee60ded94bf9ca78e6589b1d83405e0fd40904ff Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Wed, 10 Jun 2020 13:44:07 -0700 Subject: [PATCH 306/966] feat: add quota_project_id to service accounts; add with_quota_project methods (#519) Adds quota_project_id to service account credentials, making it possible to set quota_project_id on OAuth2 credentials and service account credentials. This PR also adds the method with_quota_project to both classes. --- .../google-auth/google/oauth2/credentials.py | 29 +++++++++++- .../google/oauth2/service_account.py | 44 +++++++++++++++++++ .../tests/oauth2/test_credentials.py | 16 +++++++ .../tests/oauth2/test_service_account.py | 8 ++++ 4 files changed, 96 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/google/oauth2/credentials.py b/packages/google-auth/google/oauth2/credentials.py index baf3cf7f48cc..d39848b41908 100644 --- a/packages/google-auth/google/oauth2/credentials.py +++ b/packages/google-auth/google/oauth2/credentials.py @@ -48,7 +48,13 @@ class Credentials(credentials.ReadOnlyScoped, credentials.Credentials): - """Credentials using OAuth 2.0 access and refresh tokens.""" + """Credentials using OAuth 2.0 access and refresh tokens. + + The credentials are considered immutable. If you want to modify the + quota project, use :meth:`with_quota_project` or :: + + credentials = credentials.with_quota_project('myproject-123) + """ def __init__( self, @@ -160,6 +166,27 @@ def requires_scopes(self): the initial token is requested and can not be changed.""" return False + def with_quota_project(self, quota_project_id): + """Returns a copy of these credentials with a modified quota project + + Args: + quota_project_id (str): The project to use for quota and + billing purposes + + Returns: + google.oauth2.credentials.Credentials: A new credentials instance. + """ + return self.__class__( + self.token, + refresh_token=self.refresh_token, + id_token=self.id_token, + token_uri=self.token_uri, + client_id=self.client_id, + client_secret=self.client_secret, + scopes=self.scopes, + quota_project_id=quota_project_id, + ) + @_helpers.copy_docstring(credentials.Credentials) def refresh(self, request): if ( diff --git a/packages/google-auth/google/oauth2/service_account.py b/packages/google-auth/google/oauth2/service_account.py index af86588d532a..54630d34ba98 100644 --- a/packages/google-auth/google/oauth2/service_account.py +++ b/packages/google-auth/google/oauth2/service_account.py @@ -112,6 +112,10 @@ class Credentials(credentials.Signing, credentials.Scoped, credentials.Credentia scoped_credentials = credentials.with_scopes(['email']) delegated_credentials = credentials.with_subject(subject) + + To add a quota project, use :meth:`with_quota_project`:: + + credentials = credentials.with_quota_project('myproject-123') """ def __init__( @@ -122,6 +126,7 @@ def __init__( scopes=None, subject=None, project_id=None, + quota_project_id=None, additional_claims=None, ): """ @@ -135,6 +140,8 @@ def __init__( user to for which to request delegated access. project_id (str): Project ID associated with the service account credential. + quota_project_id (Optional[str]): The project ID used for quota and + billing. additional_claims (Mapping[str, str]): Any additional claims for the JWT assertion used in the authorization grant. @@ -150,6 +157,7 @@ def __init__( self._service_account_email = service_account_email self._subject = subject self._project_id = project_id + self._quota_project_id = quota_project_id self._token_uri = token_uri if additional_claims is not None: @@ -229,6 +237,11 @@ def project_id(self): """Project ID associated with this credential.""" return self._project_id + @property + def quota_project_id(self): + """Project ID to use for quota and billing purposes.""" + return self._quota_project_id + @property def requires_scopes(self): """Checks if the credentials requires scopes. @@ -247,6 +260,7 @@ def with_scopes(self, scopes): token_uri=self._token_uri, subject=self._subject, project_id=self._project_id, + quota_project_id=self._quota_project_id, additional_claims=self._additional_claims.copy(), ) @@ -267,6 +281,7 @@ def with_subject(self, subject): token_uri=self._token_uri, subject=subject, project_id=self._project_id, + quota_project_id=self._quota_project_id, additional_claims=self._additional_claims.copy(), ) @@ -292,9 +307,32 @@ def with_claims(self, additional_claims): token_uri=self._token_uri, subject=self._subject, project_id=self._project_id, + quota_project_id=self._quota_project_id, additional_claims=new_additional_claims, ) + def with_quota_project(self, quota_project_id): + """Returns a copy of these credentials with a modified quota project. + + Args: + quota_project_id (str): The project to use for quota and + billing purposes + + Returns: + google.auth.service_account.Credentials: A new credentials + instance. + """ + return self.__class__( + self._signer, + service_account_email=self._service_account_email, + scopes=self._scopes, + token_uri=self._token_uri, + subject=self._subject, + project_id=self._project_id, + quota_project_id=quota_project_id, + additional_claims=self._additional_claims.copy(), + ) + def _make_authorization_grant_assertion(self): """Create the OAuth 2.0 assertion. @@ -335,6 +373,12 @@ def refresh(self, request): self.token = access_token self.expiry = expiry + @_helpers.copy_docstring(credentials.Credentials) + def apply(self, headers, token=None): + super(Credentials, self).apply(headers, token=token) + if self.quota_project_id is not None: + headers["x-goog-user-project"] = self.quota_project_id + @_helpers.copy_docstring(credentials.Signing) def sign_bytes(self, message): return self._signer.sign(message) diff --git a/packages/google-auth/tests/oauth2/test_credentials.py b/packages/google-auth/tests/oauth2/test_credentials.py index 76aa463cbfd1..78b10125215b 100644 --- a/packages/google-auth/tests/oauth2/test_credentials.py +++ b/packages/google-auth/tests/oauth2/test_credentials.py @@ -323,6 +323,22 @@ def test_apply_with_no_quota_project_id(self): creds.apply(headers) assert "x-goog-user-project" not in headers + def test_with_quota_project(self): + creds = credentials.Credentials( + token="token", + refresh_token=self.REFRESH_TOKEN, + token_uri=self.TOKEN_URI, + client_id=self.CLIENT_ID, + client_secret=self.CLIENT_SECRET, + quota_project_id="quota-project-123", + ) + + new_creds = creds.with_quota_project("new-project-456") + assert new_creds.quota_project_id == "new-project-456" + headers = {} + creds.apply(headers) + assert "x-goog-user-project" in headers + def test_from_authorized_user_info(self): info = AUTH_USER_INFO.copy() diff --git a/packages/google-auth/tests/oauth2/test_service_account.py b/packages/google-auth/tests/oauth2/test_service_account.py index 897374a6fa56..457d472d7b07 100644 --- a/packages/google-auth/tests/oauth2/test_service_account.py +++ b/packages/google-auth/tests/oauth2/test_service_account.py @@ -147,6 +147,14 @@ def test_with_claims(self): new_credentials = credentials.with_claims({"meep": "moop"}) assert new_credentials._additional_claims == {"meep": "moop"} + def test_with_quota_project(self): + credentials = self.make_credentials() + new_credentials = credentials.with_quota_project("new-project-456") + assert new_credentials.quota_project_id == "new-project-456" + hdrs = {} + new_credentials.apply(hdrs, token="tok") + assert "x-goog-user-project" in hdrs + def test__make_authorization_grant_assertion(self): credentials = self.make_credentials() token = credentials._make_authorization_grant_assertion() From df0abadf3dfe584c264aed3734f1398d83da80d2 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Thu, 11 Jun 2020 00:58:18 +0200 Subject: [PATCH 307/966] chore(deps): update dependency rsa to >=3.1.4,<4.2 (#524) This PR contains the following updates: | Package | Update | Change | |---|---|---| | [rsa](https://stuvel.eu/rsa) ([source](https://togithub.com/sybrenstuvel/python-rsa)) | minor | `>=3.1.4,<4.1` -> `>=3.1.4,<4.2` | --- ### Release Notes
sybrenstuvel/python-rsa ### [`v4.1`](https://togithub.com/sybrenstuvel/python-rsa/blob/master/CHANGELOG.md#Version-41---released-2020-06-10) - Added support for Python 3.8. - Dropped support for Python 2 and 3.4. - Added type annotations to the source code. This will make Python-RSA easier to use in your IDE, and allows better type checking. - Added static type checking via [MyPy](http://mypy-lang.org/). - Fix [#​129](https://togithub.com/sybrenstuvel/python-rsa/issues/129) Installing from source gives UnicodeDecodeError. - Switched to using [Poetry](https://poetry.eustace.io/) for package management. - Added support for SHA3 hashing: SHA3-256, SHA3-384, SHA3-512. This is natively supported by Python 3.6+ and supported via a third-party library on Python 3.5. - Choose blinding factor relatively prime to N. Thanks Christian Heimes for pointing this out. - Reject cyphertexts (when decrypting) and signatures (when verifying) that have been modified by prepending zero bytes. This resolves CVE-2020-13757. Thanks Adelapie for pointing this out.
--- ### Renovate configuration :date: **Schedule**: At any time (no schedule defined). :vertical_traffic_light: **Automerge**: Disabled by config. Please merge this manually once you are satisfied. :recycle: **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. :no_bell: **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [WhiteSource Renovate](https://renovate.whitesourcesoftware.com). View repository job log [here](https://app.renovatebot.com/dashboard#googleapis/google-auth-library-python). --- packages/google-auth/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index e02e9f9c5686..2506d99864d1 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -21,7 +21,7 @@ DEPENDENCIES = ( "cachetools>=2.0.0,<5.0", "pyasn1-modules>=0.2.1", - "rsa>=3.1.4,<4.1", + "rsa>=3.1.4,<5.0", "setuptools>=40.3.0", "six>=1.9.0", ) From f7fe7d3c6e224a416f0bbd80dadb40139ccc733c Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Wed, 10 Jun 2020 19:01:13 -0700 Subject: [PATCH 308/966] chore: release 1.17.0 (#525) * updated CHANGELOG.md [ci skip] * updated setup.cfg [ci skip] * updated setup.py Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> Co-authored-by: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 945f8ea9ec9a..a84ed94b4d15 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +## [1.17.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.16.1...v1.17.0) (2020-06-10) + + +### Features + +* add quota_project_id to service accounts; add with_quota_project methods ([#519](https://www.github.com/googleapis/google-auth-library-python/issues/519)) ([b12488c](https://www.github.com/googleapis/google-auth-library-python/commit/b12488cf552888299425c8009ea075511627cf08)) + ### [1.16.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.16.0...v1.16.1) (2020-06-04) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 2506d99864d1..f518ec10d24e 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -30,7 +30,7 @@ with io.open("README.rst", "r") as fh: long_description = fh.read() -version = "1.16.1" +version = "1.17.0" setup( name="google-auth", From ac9fe7e4abbfd3d611458c6b07c060b3e8b973f4 Mon Sep 17 00:00:00 2001 From: alvyjudy <53921639+alvyjudy@users.noreply.github.com> Date: Wed, 10 Jun 2020 22:23:31 -0400 Subject: [PATCH 309/966] docs: update docstring of to_json() method (#511) from #494, this PR updated the docstring of ``to_json`` method in the credential so that it points user to ``from_authorized_user_info`` instead of the non-existent ``from_json`` --- packages/google-auth/google/oauth2/credentials.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/google/oauth2/credentials.py b/packages/google-auth/google/oauth2/credentials.py index d39848b41908..757219671894 100644 --- a/packages/google-auth/google/oauth2/credentials.py +++ b/packages/google-auth/google/oauth2/credentials.py @@ -300,8 +300,9 @@ def to_json(self, strip=None): generated JSON. Returns: - str: A JSON representation of this instance, suitable to pass to - from_json(). + str: A JSON representation of this instance. When converted into + a dictionary, it can be passed to from_authorized_user_info() + to create a new credential instance. """ prep = { "token": self.token, From 960d149a710eb61b2c8441ba7f695b656855382c Mon Sep 17 00:00:00 2001 From: Christopher Wilcox Date: Thu, 11 Jun 2020 16:05:28 -0700 Subject: [PATCH 310/966] fix: narrow acceptable RSA versions to maintain Python 2 compatability (#528) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Narrow acceptable RSA versions to maintain Python 2 compat * Update setup.py Co-authored-by: Kamil Breguła * Use a more specific pin to support new versions that may support python 2. * Update setup.py Co-authored-by: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> * how many commits to get the format correct? Co-authored-by: Kamil Breguła Co-authored-by: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> --- packages/google-auth/setup.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index f518ec10d24e..0762da867657 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -21,7 +21,9 @@ DEPENDENCIES = ( "cachetools>=2.0.0,<5.0", "pyasn1-modules>=0.2.1", - "rsa>=3.1.4,<5.0", + # rsa 4.1, 4.1.1, 4.2 are broken on Py2: https://github.com/sybrenstuvel/python-rsa/issues/152 + 'rsa>=3.1.4,!=4.1,!=4.1.1,!=4.2,<5; python_version < "3"', + 'rsa>=3.1.4,<5; python_version >= "3"', "setuptools>=40.3.0", "six>=1.9.0", ) From f8c6d52084be5e5d6035ada55e5e5b7d65c7ce02 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 11 Jun 2020 23:22:10 +0000 Subject: [PATCH 311/966] chore: release 1.17.1 (#529) :robot: I have created a release \*beep\* \*boop\* --- ### [1.17.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.17.0...v1.17.1) (2020-06-11) ### Bug Fixes * narrow acceptable RSA versions to maintain Python 2 compatability ([#528](https://www.github.com/googleapis/google-auth-library-python/issues/528)) ([9434868](https://www.github.com/googleapis/google-auth-library-python/commit/9434868a6789464549af1d4562f62d8a899b6809)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index a84ed94b4d15..34018f4a4898 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +### [1.17.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.17.0...v1.17.1) (2020-06-11) + + +### Bug Fixes + +* narrow acceptable RSA versions to maintain Python 2 compatability ([#528](https://www.github.com/googleapis/google-auth-library-python/issues/528)) ([9434868](https://www.github.com/googleapis/google-auth-library-python/commit/9434868a6789464549af1d4562f62d8a899b6809)) + ## [1.17.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.16.1...v1.17.0) (2020-06-10) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 0762da867657..a278ac273ea9 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -32,7 +32,7 @@ with io.open("README.rst", "r") as fh: long_description = fh.read() -version = "1.17.0" +version = "1.17.1" setup( name="google-auth", From bdb8882d3a28a393fb809090f03ccfc6b86a0f60 Mon Sep 17 00:00:00 2001 From: Christopher Wilcox Date: Fri, 12 Jun 2020 11:28:08 -0700 Subject: [PATCH 312/966] fix(dependencies): Further restrict RSA versions (#532) Related to #528. RSA seems to have released another version without `python_requires` being enforced. This will guard against that for our package. --- packages/google-auth/setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index a278ac273ea9..e12be43ae4b9 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -21,8 +21,8 @@ DEPENDENCIES = ( "cachetools>=2.0.0,<5.0", "pyasn1-modules>=0.2.1", - # rsa 4.1, 4.1.1, 4.2 are broken on Py2: https://github.com/sybrenstuvel/python-rsa/issues/152 - 'rsa>=3.1.4,!=4.1,!=4.1.1,!=4.2,<5; python_version < "3"', + # rsa >= 4.1 no longer supports python 2 https://github.com/sybrenstuvel/python-rsa/issues/152 + 'rsa<4.1; python_version < "3"', 'rsa>=3.1.4,<5; python_version >= "3"', "setuptools>=40.3.0", "six>=1.9.0", From 40b3ade9967cd8f6670fa58f2be01e492f8c35fa Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Fri, 12 Jun 2020 12:15:24 -0700 Subject: [PATCH 313/966] chore: release 1.17.2 (#533) * updated CHANGELOG.md [ci skip] * updated setup.cfg [ci skip] * updated setup.py Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 34018f4a4898..bf5e6e574c5f 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +### [1.17.2](https://www.github.com/googleapis/google-auth-library-python/compare/v1.17.1...v1.17.2) (2020-06-12) + + +### Bug Fixes + +* **dependencies:** Further restrict RSA versions ([#532](https://www.github.com/googleapis/google-auth-library-python/issues/532)) ([46677a0](https://www.github.com/googleapis/google-auth-library-python/commit/46677a0cb3bde6622be10061bc61daaff7a0aaca)), closes [#528](https://www.github.com/googleapis/google-auth-library-python/issues/528) + ### [1.17.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.17.0...v1.17.1) (2020-06-11) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index e12be43ae4b9..22ac79cc21e1 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -32,7 +32,7 @@ with io.open("README.rst", "r") as fh: long_description = fh.read() -version = "1.17.1" +version = "1.17.2" setup( name="google-auth", From 18c7339bea4172c06d154070dcb10c105c589072 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Wed, 17 Jun 2020 23:36:04 -0700 Subject: [PATCH 314/966] fix: no warning if quota_project_id is given (#537) If user account cred has 'quota_project_id', ignore the warning. Implementing http://shortn/_YUlAgzL40H --- packages/google-auth/google/auth/_default.py | 14 +++++++------- ...rized_user_cloud_sdk_with_quota_project_id.json | 7 +++++++ packages/google-auth/tests/test__default.py | 11 +++++++++++ 3 files changed, 25 insertions(+), 7 deletions(-) create mode 100644 packages/google-auth/tests/data/authorized_user_cloud_sdk_with_quota_project_id.json diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index d7110a10dfc2..559695ecc897 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -49,11 +49,11 @@ # Warning when using Cloud SDK user credentials _CLOUD_SDK_CREDENTIALS_WARNING = """\ Your application has authenticated using end user credentials from Google \ -Cloud SDK. We recommend that most server applications use service accounts \ -instead. If your application continues to use end user credentials from Cloud \ -SDK, you might receive a "quota exceeded" or "API not enabled" error. For \ -more information about service accounts, see \ -https://cloud.google.com/docs/authentication/""" +Cloud SDK without a quota project. You might receive a "quota exceeded" \ +or "API not enabled" error. We recommend you rerun \ +`gcloud auth application-default login` and make sure a quota project is \ +added. Or you can use service accounts instead. For more information \ +about service accounts, see https://cloud.google.com/docs/authentication/""" def _warn_about_problematic_credentials(credentials): @@ -114,8 +114,8 @@ def _load_credentials_from_file(filename): msg = "Failed to load authorized user credentials from {}".format(filename) new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) six.raise_from(new_exc, caught_exc) - # Authorized user credentials do not contain the project ID. - _warn_about_problematic_credentials(credentials) + if not credentials.quota_project_id: + _warn_about_problematic_credentials(credentials) return credentials, None elif credential_type == _SERVICE_ACCOUNT_TYPE: diff --git a/packages/google-auth/tests/data/authorized_user_cloud_sdk_with_quota_project_id.json b/packages/google-auth/tests/data/authorized_user_cloud_sdk_with_quota_project_id.json new file mode 100644 index 000000000000..53a8ff88aa28 --- /dev/null +++ b/packages/google-auth/tests/data/authorized_user_cloud_sdk_with_quota_project_id.json @@ -0,0 +1,7 @@ +{ + "client_id": "764086051850-6qr4p6gpi6hn506pt8ejuq83di341hur.apps.googleusercontent.com", + "client_secret": "secret", + "refresh_token": "alabalaportocala", + "type": "authorized_user", + "quota_project_id": "quota_project_id" +} diff --git a/packages/google-auth/tests/test__default.py b/packages/google-auth/tests/test__default.py index 35000b04d743..b769fc78460b 100644 --- a/packages/google-auth/tests/test__default.py +++ b/packages/google-auth/tests/test__default.py @@ -37,6 +37,10 @@ DATA_DIR, "authorized_user_cloud_sdk.json" ) +AUTHORIZED_USER_CLOUD_SDK_WITH_QUOTA_PROJECT_ID_FILE = os.path.join( + DATA_DIR, "authorized_user_cloud_sdk_with_quota_project_id.json" +) + SERVICE_ACCOUNT_FILE = os.path.join(DATA_DIR, "service_account.json") with open(SERVICE_ACCOUNT_FILE) as fh: @@ -101,6 +105,13 @@ def test__load_credentials_from_file_authorized_user_cloud_sdk(): assert isinstance(credentials, google.oauth2.credentials.Credentials) assert project_id is None + # No warning if the json file has quota project id. + credentials, project_id = _default._load_credentials_from_file( + AUTHORIZED_USER_CLOUD_SDK_WITH_QUOTA_PROJECT_ID_FILE + ) + assert isinstance(credentials, google.oauth2.credentials.Credentials) + assert project_id is None + def test__load_credentials_from_file_service_account(): credentials, project_id = _default._load_credentials_from_file(SERVICE_ACCOUNT_FILE) From 20567611b03af3378556c95ee84805b5ab386a00 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Thu, 18 Jun 2020 14:05:40 -0700 Subject: [PATCH 315/966] feat: make ``load_credentials_from_file`` a public method (#530) * feat: make load_credentials_from_file public and alllow scopes * test: update tests * fix: raise error for json with no type * test: fix test names * refactor: simplify control flow * fix: raise coverage * test: update test Co-authored-by: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Co-authored-by: Sijun Liu --- packages/google-auth/google/auth/__init__.py | 4 +- packages/google-auth/google/auth/_default.py | 19 ++++-- packages/google-auth/tests/test__default.py | 68 ++++++++++++++------ 3 files changed, 65 insertions(+), 26 deletions(-) diff --git a/packages/google-auth/google/auth/__init__.py b/packages/google-auth/google/auth/__init__.py index 6b4b78b29d91..5ca20a36297d 100644 --- a/packages/google-auth/google/auth/__init__.py +++ b/packages/google-auth/google/auth/__init__.py @@ -16,10 +16,10 @@ import logging -from google.auth._default import default +from google.auth._default import default, load_credentials_from_file -__all__ = ["default"] +__all__ = ["default", "load_credentials_from_file"] # Set default logging handler to avoid "No handler found" warnings. diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index 559695ecc897..d3bbbdace33b 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -69,14 +69,17 @@ def _warn_about_problematic_credentials(credentials): warnings.warn(_CLOUD_SDK_CREDENTIALS_WARNING) -def _load_credentials_from_file(filename): - """Loads credentials from a file. +def load_credentials_from_file(filename, scopes=None): + """Loads Google credentials from a file. The credentials file must be a service account key or stored authorized user credentials. Args: filename (str): The full path to the credentials file. + scopes (Optional[Sequence[str]]): The list of scopes for the credentials. If + specified, the credentials will automatically be scoped if + necessary. Returns: Tuple[google.auth.credentials.Credentials, Optional[str]]: Loaded @@ -109,7 +112,9 @@ def _load_credentials_from_file(filename): from google.oauth2 import credentials try: - credentials = credentials.Credentials.from_authorized_user_info(info) + credentials = credentials.Credentials.from_authorized_user_info( + info, scopes=scopes + ) except ValueError as caught_exc: msg = "Failed to load authorized user credentials from {}".format(filename) new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) @@ -122,7 +127,9 @@ def _load_credentials_from_file(filename): from google.oauth2 import service_account try: - credentials = service_account.Credentials.from_service_account_info(info) + credentials = service_account.Credentials.from_service_account_info( + info, scopes=scopes + ) except ValueError as caught_exc: msg = "Failed to load service account credentials from {}".format(filename) new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) @@ -148,7 +155,7 @@ def _get_gcloud_sdk_credentials(): if not os.path.isfile(credentials_filename): return None, None - credentials, project_id = _load_credentials_from_file(credentials_filename) + credentials, project_id = load_credentials_from_file(credentials_filename) if not project_id: project_id = _cloud_sdk.get_project_id() @@ -162,7 +169,7 @@ def _get_explicit_environ_credentials(): explicit_file = os.environ.get(environment_vars.CREDENTIALS) if explicit_file is not None: - credentials, project_id = _load_credentials_from_file( + credentials, project_id = load_credentials_from_file( os.environ[environment_vars.CREDENTIALS] ) diff --git a/packages/google-auth/tests/test__default.py b/packages/google-auth/tests/test__default.py index b769fc78460b..3c87b35ebeef 100644 --- a/packages/google-auth/tests/test__default.py +++ b/packages/google-auth/tests/test__default.py @@ -43,88 +43,120 @@ SERVICE_ACCOUNT_FILE = os.path.join(DATA_DIR, "service_account.json") +CLIENT_SECRETS_FILE = os.path.join(DATA_DIR, "client_secrets.json") + with open(SERVICE_ACCOUNT_FILE) as fh: SERVICE_ACCOUNT_FILE_DATA = json.load(fh) LOAD_FILE_PATCH = mock.patch( - "google.auth._default._load_credentials_from_file", + "google.auth._default.load_credentials_from_file", return_value=(mock.sentinel.credentials, mock.sentinel.project_id), autospec=True, ) -def test__load_credentials_from_missing_file(): +def test_load_credentials_from_missing_file(): with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: - _default._load_credentials_from_file("") + _default.load_credentials_from_file("") assert excinfo.match(r"not found") -def test__load_credentials_from_file_invalid_json(tmpdir): +def test_load_credentials_from_file_invalid_json(tmpdir): jsonfile = tmpdir.join("invalid.json") jsonfile.write("{") with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: - _default._load_credentials_from_file(str(jsonfile)) + _default.load_credentials_from_file(str(jsonfile)) assert excinfo.match(r"not a valid json file") -def test__load_credentials_from_file_invalid_type(tmpdir): +def test_load_credentials_from_file_invalid_type(tmpdir): jsonfile = tmpdir.join("invalid.json") jsonfile.write(json.dumps({"type": "not-a-real-type"})) with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: - _default._load_credentials_from_file(str(jsonfile)) + _default.load_credentials_from_file(str(jsonfile)) assert excinfo.match(r"does not have a valid type") -def test__load_credentials_from_file_authorized_user(): - credentials, project_id = _default._load_credentials_from_file(AUTHORIZED_USER_FILE) +def test_load_credentials_from_file_authorized_user(): + credentials, project_id = _default.load_credentials_from_file(AUTHORIZED_USER_FILE) assert isinstance(credentials, google.oauth2.credentials.Credentials) assert project_id is None -def test__load_credentials_from_file_authorized_user_bad_format(tmpdir): +def test_load_credentials_from_file_no_type(tmpdir): + # use the client_secrets.json, which is valid json but not a + # loadable credentials type + with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: + _default.load_credentials_from_file(CLIENT_SECRETS_FILE) + + assert excinfo.match(r"does not have a valid type") + assert excinfo.match(r"Type is None") + + +def test_load_credentials_from_file_authorized_user_bad_format(tmpdir): filename = tmpdir.join("authorized_user_bad.json") filename.write(json.dumps({"type": "authorized_user"})) with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: - _default._load_credentials_from_file(str(filename)) + _default.load_credentials_from_file(str(filename)) assert excinfo.match(r"Failed to load authorized user") assert excinfo.match(r"missing fields") -def test__load_credentials_from_file_authorized_user_cloud_sdk(): +def test_load_credentials_from_file_authorized_user_cloud_sdk(): with pytest.warns(UserWarning, match="Cloud SDK"): - credentials, project_id = _default._load_credentials_from_file( + credentials, project_id = _default.load_credentials_from_file( AUTHORIZED_USER_CLOUD_SDK_FILE ) assert isinstance(credentials, google.oauth2.credentials.Credentials) assert project_id is None # No warning if the json file has quota project id. - credentials, project_id = _default._load_credentials_from_file( + credentials, project_id = _default.load_credentials_from_file( AUTHORIZED_USER_CLOUD_SDK_WITH_QUOTA_PROJECT_ID_FILE ) assert isinstance(credentials, google.oauth2.credentials.Credentials) assert project_id is None -def test__load_credentials_from_file_service_account(): - credentials, project_id = _default._load_credentials_from_file(SERVICE_ACCOUNT_FILE) +def test_load_credentials_from_file_authorized_user_cloud_sdk_with_scopes(): + with pytest.warns(UserWarning, match="Cloud SDK"): + credentials, project_id = _default.load_credentials_from_file( + AUTHORIZED_USER_CLOUD_SDK_FILE, + scopes=["https://www.google.com/calendar/feeds"], + ) + assert isinstance(credentials, google.oauth2.credentials.Credentials) + assert project_id is None + assert credentials.scopes == ["https://www.google.com/calendar/feeds"] + + +def test_load_credentials_from_file_service_account(): + credentials, project_id = _default.load_credentials_from_file(SERVICE_ACCOUNT_FILE) + assert isinstance(credentials, service_account.Credentials) + assert project_id == SERVICE_ACCOUNT_FILE_DATA["project_id"] + + +def test_load_credentials_from_file_service_account_with_scopes(): + credentials, project_id = _default.load_credentials_from_file( + SERVICE_ACCOUNT_FILE, scopes=["https://www.google.com/calendar/feeds"] + ) assert isinstance(credentials, service_account.Credentials) assert project_id == SERVICE_ACCOUNT_FILE_DATA["project_id"] + assert credentials.scopes == ["https://www.google.com/calendar/feeds"] -def test__load_credentials_from_file_service_account_bad_format(tmpdir): +def test_load_credentials_from_file_service_account_bad_format(tmpdir): filename = tmpdir.join("serivce_account_bad.json") filename.write(json.dumps({"type": "service_account"})) with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: - _default._load_credentials_from_file(str(filename)) + _default.load_credentials_from_file(str(filename)) assert excinfo.match(r"Failed to load service account") assert excinfo.match(r"missing fields") From cd9fbee511c1115a2913827ed8c71a7d1bef126d Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 18 Jun 2020 14:20:05 -0700 Subject: [PATCH 316/966] chore: release 1.18.0 (#539) * updated CHANGELOG.md [ci skip] * updated setup.cfg [ci skip] * updated setup.py Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 12 ++++++++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index bf5e6e574c5f..18df080d6319 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,18 @@ [1]: https://pypi.org/project/google-auth/#history +## [1.18.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.17.2...v1.18.0) (2020-06-18) + + +### Features + +* make ``load_credentials_from_file`` a public method ([#530](https://www.github.com/googleapis/google-auth-library-python/issues/530)) ([15d5fa9](https://www.github.com/googleapis/google-auth-library-python/commit/15d5fa946177581b52a5a9eb3ca285c088f5c45d)) + + +### Bug Fixes + +* no warning if quota_project_id is given ([#537](https://www.github.com/googleapis/google-auth-library-python/issues/537)) ([f30b45a](https://www.github.com/googleapis/google-auth-library-python/commit/f30b45a9b2f824c494724548732c5ce838218c30)) + ### [1.17.2](https://www.github.com/googleapis/google-auth-library-python/compare/v1.17.1...v1.17.2) (2020-06-12) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 22ac79cc21e1..6c68a75d4b45 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -32,7 +32,7 @@ with io.open("README.rst", "r") as fh: long_description = fh.read() -version = "1.17.2" +version = "1.18.0" setup( name="google-auth", From e9b5f7ceef2b600dd2b26913676dbcb6f501c470 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Mon, 29 Jun 2020 16:27:30 -0700 Subject: [PATCH 317/966] feat: check 'iss' in `verify_oauth2_token` (#500) Co-authored-by: Tianzi Cai --- .../google/auth/transport/requests.py | 6 +++++- packages/google-auth/google/oauth2/id_token.py | 16 +++++++++++++++- .../google-auth/tests/oauth2/test_id_token.py | 11 +++++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/google/auth/transport/requests.py b/packages/google-auth/google/auth/transport/requests.py index 9f55bead6bf2..4f5af7dea29e 100644 --- a/packages/google-auth/google/auth/transport/requests.py +++ b/packages/google-auth/google/auth/transport/requests.py @@ -365,7 +365,11 @@ def configure_mtls_channel(self, client_cert_callback=None): six.raise_from(new_exc, caught_exc) try: - self._is_mtls, cert, key = google.auth.transport._mtls_helper.get_client_cert_and_key( + ( + self._is_mtls, + cert, + key, + ) = google.auth.transport._mtls_helper.get_client_cert_and_key( client_cert_callback ) diff --git a/packages/google-auth/google/oauth2/id_token.py b/packages/google-auth/google/oauth2/id_token.py index e78add417eb2..bf6bf2c70181 100644 --- a/packages/google-auth/google/oauth2/id_token.py +++ b/packages/google-auth/google/oauth2/id_token.py @@ -80,6 +80,8 @@ "/securetoken@system.gserviceaccount.com" ) +_GOOGLE_ISSUERS = ["accounts.google.com", "https://accounts.google.com"] + def _fetch_certs(request, certs_url): """Fetches certificates. @@ -140,11 +142,23 @@ def verify_oauth2_token(id_token, request, audience=None): Returns: Mapping[str, Any]: The decoded token. + + Raises: + exceptions.GoogleAuthError: If the issuer is invalid. """ - return verify_token( + idinfo = verify_token( id_token, request, audience=audience, certs_url=_GOOGLE_OAUTH2_CERTS_URL ) + if idinfo["iss"] not in _GOOGLE_ISSUERS: + raise exceptions.GoogleAuthError( + "Wrong issuer. 'iss' should be one of the following: {}".format( + _GOOGLE_ISSUERS + ) + ) + + return idinfo + def verify_firebase_token(id_token, request, audience=None): """Verifies an ID Token issued by Firebase Authentication. diff --git a/packages/google-auth/tests/oauth2/test_id_token.py b/packages/google-auth/tests/oauth2/test_id_token.py index ff858078a5e4..0c70d6891f52 100644 --- a/packages/google-auth/tests/oauth2/test_id_token.py +++ b/packages/google-auth/tests/oauth2/test_id_token.py @@ -95,6 +95,7 @@ def test_verify_token_args(_fetch_certs, decode): @mock.patch("google.oauth2.id_token.verify_token", autospec=True) def test_verify_oauth2_token(verify_token): + verify_token.return_value = {"iss": "accounts.google.com"} result = id_token.verify_oauth2_token( mock.sentinel.token, mock.sentinel.request, audience=mock.sentinel.audience ) @@ -108,6 +109,16 @@ def test_verify_oauth2_token(verify_token): ) +@mock.patch("google.oauth2.id_token.verify_token", autospec=True) +def test_verify_oauth2_token_invalid_iss(verify_token): + verify_token.return_value = {"iss": "invalid_issuer"} + + with pytest.raises(exceptions.GoogleAuthError): + id_token.verify_oauth2_token( + mock.sentinel.token, mock.sentinel.request, audience=mock.sentinel.audience + ) + + @mock.patch("google.oauth2.id_token.verify_token", autospec=True) def test_verify_firebase_token(verify_token): result = id_token.verify_firebase_token( From be5be8998dfea10d3be2ff3a7a6ca8ab7eeac3d4 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Tue, 30 Jun 2020 14:10:03 -0700 Subject: [PATCH 318/966] docs: remove 3.4 from supported versions list (#549) Remove 3.4 from the supported versions list. We no longer actively test it and it has been EOL since March. https://www.python.org/downloads/release/python-3410/ --- packages/google-auth/README.rst | 2 +- packages/google-auth/setup.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/google-auth/README.rst b/packages/google-auth/README.rst index c9c411f08ff1..10de0ac06f75 100644 --- a/packages/google-auth/README.rst +++ b/packages/google-auth/README.rst @@ -24,7 +24,7 @@ For more information on setting up your Python development environment, please r Supported Python Versions ^^^^^^^^^^^^^^^^^^^^^^^^^ -Python >= 3.4 +Python >= 3.5 Deprecated Python Versions ^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 6c68a75d4b45..82259d54e55c 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -52,7 +52,6 @@ "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", From 617e3ca861aa55727964f5f4132ed04d3b99cad8 Mon Sep 17 00:00:00 2001 From: bojeil-google Date: Tue, 30 Jun 2020 16:01:20 -0700 Subject: [PATCH 319/966] fix: migrate signBlob to iamcredentials.googleapis.com (#553) * Migrate signBlob from iam.googleapis.com to iamcredentials.googleapis.com. This API is deprecated and will be shutdown in one year. This is used google.auth.iam.Signer. Added a system_test to sanity check the implementation. --- packages/google-auth/.gitignore | 3 +++ packages/google-auth/google/auth/iam.py | 6 +++--- .../system_tests/test_service_account.py | 17 +++++++++++++++++ .../tests/compute_engine/test_credentials.py | 6 +++--- packages/google-auth/tests/test_iam.py | 2 +- 5 files changed, 27 insertions(+), 7 deletions(-) diff --git a/packages/google-auth/.gitignore b/packages/google-auth/.gitignore index 598752fa60c0..8274565140db 100644 --- a/packages/google-auth/.gitignore +++ b/packages/google-auth/.gitignore @@ -39,3 +39,6 @@ pylintrc.test pytype_output/ .python-version +.DS_Store +cert_path +key_path \ No newline at end of file diff --git a/packages/google-auth/google/auth/iam.py b/packages/google-auth/google/auth/iam.py index bd0500457819..9e3887909a88 100644 --- a/packages/google-auth/google/auth/iam.py +++ b/packages/google-auth/google/auth/iam.py @@ -28,7 +28,7 @@ from google.auth import crypt from google.auth import exceptions -_IAM_API_ROOT_URI = "https://iam.googleapis.com/v1" +_IAM_API_ROOT_URI = "https://iamcredentials.googleapis.com/v1" _SIGN_BLOB_URI = _IAM_API_ROOT_URI + "/projects/-/serviceAccounts/{}:signBlob?alt=json" @@ -71,7 +71,7 @@ def _make_signing_request(self, message): url = _SIGN_BLOB_URI.format(self._service_account_email) headers = {} body = json.dumps( - {"bytesToSign": base64.b64encode(message).decode("utf-8")} + {"payload": base64.b64encode(message).decode("utf-8")} ).encode("utf-8") self._credentials.before_request(self._request, method, url, headers) @@ -97,4 +97,4 @@ def key_id(self): @_helpers.copy_docstring(crypt.Signer) def sign(self, message): response = self._make_signing_request(message) - return base64.b64decode(response["signature"]) + return base64.b64decode(response["signedBlob"]) diff --git a/packages/google-auth/system_tests/test_service_account.py b/packages/google-auth/system_tests/test_service_account.py index 262ce84f5387..498b75b2233c 100644 --- a/packages/google-auth/system_tests/test_service_account.py +++ b/packages/google-auth/system_tests/test_service_account.py @@ -16,6 +16,7 @@ from google.auth import _helpers from google.auth import exceptions +from google.auth import iam from google.oauth2 import service_account @@ -46,3 +47,19 @@ def test_refresh_success(http_request, credentials, token_info): "https://www.googleapis.com/auth/userinfo.profile", ] ) + +def test_iam_signer(http_request, credentials): + credentials = credentials.with_scopes( + ["https://www.googleapis.com/auth/iam"] + ) + + # Verify iamcredentials signer. + signer = iam.Signer( + http_request, + credentials, + credentials.service_account_email + ) + + signed_blob = signer.sign("message") + + assert isinstance(signed_blob, bytes) diff --git a/packages/google-auth/tests/compute_engine/test_credentials.py b/packages/google-auth/tests/compute_engine/test_credentials.py index 98def0fc5f7b..363dc9dbb274 100644 --- a/packages/google-auth/tests/compute_engine/test_credentials.py +++ b/packages/google-auth/tests/compute_engine/test_credentials.py @@ -354,11 +354,11 @@ def test_with_target_audience_integration(self): signature = base64.b64encode(b"some-signature").decode("utf-8") responses.add( responses.POST, - "https://iam.googleapis.com/v1/projects/-/serviceAccounts/" - "service-account@example.com:signBlob?alt=json", + "https://iamcredentials.googleapis.com/v1/projects/-/" + "serviceAccounts/service-account@example.com:signBlob?alt=json", status=200, content_type="application/json", - json={"keyId": "some-key-id", "signature": signature}, + json={"keyId": "some-key-id", "signedBlob": signature}, ) id_token = "{}.{}.{}".format( diff --git a/packages/google-auth/tests/test_iam.py b/packages/google-auth/tests/test_iam.py index c98a138f9e42..ea7d08a4075e 100644 --- a/packages/google-auth/tests/test_iam.py +++ b/packages/google-auth/tests/test_iam.py @@ -78,7 +78,7 @@ def test_key_id(self): def test_sign_bytes(self): signature = b"DEADBEEF" encoded_signature = base64.b64encode(signature).decode("utf-8") - request = make_request(http_client.OK, data={"signature": encoded_signature}) + request = make_request(http_client.OK, data={"signedBlob": encoded_signature}) credentials = make_credentials() signer = iam.Signer(request, credentials, mock.sentinel.service_account_email) From c8d9afb40a5b4e0259a8e8834cbbaf71eefb8fd3 Mon Sep 17 00:00:00 2001 From: Yoshi Automation Bot Date: Mon, 6 Jul 2020 13:59:22 -0700 Subject: [PATCH 320/966] chore: update file headers (#515) * changes without context autosynth cannot find the source of changes triggered by earlier changes in this repository, or by version upgrades to tools such as linters. * chore: modify synth excludes Co-authored-by: Bu Sun Kim Co-authored-by: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> --- packages/google-auth/.kokoro/publish-docs.sh | 13 +++++++++++++ packages/google-auth/.kokoro/release.sh | 13 +++++++++++++ packages/google-auth/.kokoro/trampoline.sh | 2 +- packages/google-auth/synth.metadata | 16 +++++++++++----- packages/google-auth/synth.py | 9 ++++++++- 5 files changed, 46 insertions(+), 7 deletions(-) diff --git a/packages/google-auth/.kokoro/publish-docs.sh b/packages/google-auth/.kokoro/publish-docs.sh index 0d97db8f1553..0e5d97867f53 100755 --- a/packages/google-auth/.kokoro/publish-docs.sh +++ b/packages/google-auth/.kokoro/publish-docs.sh @@ -1,4 +1,17 @@ #!/bin/bash +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. set -eo pipefail diff --git a/packages/google-auth/.kokoro/release.sh b/packages/google-auth/.kokoro/release.sh index cf855559a2c5..ded1baeda26e 100755 --- a/packages/google-auth/.kokoro/release.sh +++ b/packages/google-auth/.kokoro/release.sh @@ -1,4 +1,17 @@ #!/bin/bash +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. set -eo pipefail diff --git a/packages/google-auth/.kokoro/trampoline.sh b/packages/google-auth/.kokoro/trampoline.sh index 4b4ba9f81a0a..e8c4251f3ed4 100755 --- a/packages/google-auth/.kokoro/trampoline.sh +++ b/packages/google-auth/.kokoro/trampoline.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright 2017 Google LLC +# Copyright 2017 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/synth.metadata b/packages/google-auth/synth.metadata index ebfeb7f1211d..0efbe1e90416 100644 --- a/packages/google-auth/synth.metadata +++ b/packages/google-auth/synth.metadata @@ -1,11 +1,17 @@ { - "updateTime": "2019-09-12T22:40:28.148018Z", "sources": [ { - "template": { - "name": "python_library", - "origin": "synthtool.gcp", - "version": "2019.5.2" + "git": { + "name": ".", + "remote": "git@github.com:googleapis/google-auth-library-python", + "sha": "aab4f2fdb2cfa598397026865ccb270a05c38cc4" + } + }, + { + "git": { + "name": "synthtool", + "remote": "https://github.com/googleapis/synthtool.git", + "sha": "71b8a272549c06b5768d00fa48d3ae990e871bec" } } ] diff --git a/packages/google-auth/synth.py b/packages/google-auth/synth.py index 069b21472d31..49bf2dda6236 100644 --- a/packages/google-auth/synth.py +++ b/packages/google-auth/synth.py @@ -7,4 +7,11 @@ # Add templated files # ---------------------------------------------------------------------------- templated_files = common.py_library(unit_cov_level=100, cov_level=100) -s.move(templated_files / '.kokoro') # just move kokoro configs \ No newline at end of file +s.move( + templated_files / ".kokoro", + excludes=[ + ".kokoro/continuous/common.cfg", + ".kokoro/presubmit/common.cfg", + ".kokoro/build.sh", + ], +) # just move kokoro configs From 04c565e6a645c18e5c2e82c3872078ebd55b6462 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Thu, 9 Jul 2020 10:39:39 -0700 Subject: [PATCH 321/966] feat: add quota project to base credentials class (#546) --- packages/google-auth/.gitignore | 2 + packages/google-auth/google/auth/_default.py | 19 ++- .../google-auth/google/auth/app_engine.py | 17 ++- .../google/auth/compute_engine/credentials.py | 42 +++++- .../google-auth/google/auth/credentials.py | 24 +++ .../google/auth/impersonated_credentials.py | 45 +++++- packages/google-auth/google/auth/jwt.py | 34 +++++ .../google-auth/google/oauth2/credentials.py | 32 ++-- .../google/oauth2/service_account.py | 37 ++--- .../tests/compute_engine/test_credentials.py | 141 ++++++++++++++++++ .../tests/oauth2/test_credentials.py | 7 + .../tests/oauth2/test_service_account.py | 5 + packages/google-auth/tests/test__default.py | 71 +++++---- packages/google-auth/tests/test_app_engine.py | 11 ++ .../google-auth/tests/test_credentials.py | 9 ++ packages/google-auth/tests/test_iam.py | 3 + .../tests/test_impersonated_credentials.py | 35 +++++ packages/google-auth/tests/test_jwt.py | 22 +++ .../google-auth/tests/transport/test_grpc.py | 3 + .../tests/transport/test_requests.py | 3 + .../tests/transport/test_urllib3.py | 3 + 21 files changed, 482 insertions(+), 83 deletions(-) diff --git a/packages/google-auth/.gitignore b/packages/google-auth/.gitignore index 8274565140db..f01e60ec0b0f 100644 --- a/packages/google-auth/.gitignore +++ b/packages/google-auth/.gitignore @@ -13,6 +13,8 @@ docs/_build .tox/ .cache/ .pytest_cache/ +cert_path +key_path # Django test database db.sqlite3 diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index d3bbbdace33b..f3e498bb57ed 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -69,7 +69,7 @@ def _warn_about_problematic_credentials(credentials): warnings.warn(_CLOUD_SDK_CREDENTIALS_WARNING) -def load_credentials_from_file(filename, scopes=None): +def load_credentials_from_file(filename, scopes=None, quota_project_id=None): """Loads Google credentials from a file. The credentials file must be a service account key or stored authorized @@ -79,7 +79,9 @@ def load_credentials_from_file(filename, scopes=None): filename (str): The full path to the credentials file. scopes (Optional[Sequence[str]]): The list of scopes for the credentials. If specified, the credentials will automatically be scoped if - necessary. + necessary + quota_project_id (Optional[str]): The project ID used for + quota and billing. Returns: Tuple[google.auth.credentials.Credentials, Optional[str]]: Loaded @@ -114,7 +116,7 @@ def load_credentials_from_file(filename, scopes=None): try: credentials = credentials.Credentials.from_authorized_user_info( info, scopes=scopes - ) + ).with_quota_project(quota_project_id) except ValueError as caught_exc: msg = "Failed to load authorized user credentials from {}".format(filename) new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) @@ -129,7 +131,7 @@ def load_credentials_from_file(filename, scopes=None): try: credentials = service_account.Credentials.from_service_account_info( info, scopes=scopes - ) + ).with_quota_project(quota_project_id) except ValueError as caught_exc: msg = "Failed to load service account credentials from {}".format(filename) new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) @@ -226,7 +228,7 @@ def _get_gce_credentials(request=None): return None, None -def default(scopes=None, request=None): +def default(scopes=None, request=None, quota_project_id=None): """Gets the default credentials for the current environment. `Application Default Credentials`_ provides an easy way to obtain @@ -286,7 +288,8 @@ def default(scopes=None, request=None): HTTP requests. This is used to detect whether the application is running on Compute Engine. If not specified, then it will use the standard library http client to make requests. - + quota_project_id (Optional[str]): The project ID used for + quota and billing. Returns: Tuple[~google.auth.credentials.Credentials, Optional[str]]: the current environment's credentials and project ID. Project ID @@ -314,7 +317,9 @@ def default(scopes=None, request=None): for checker in checkers: credentials, project_id = checker() if credentials is not None: - credentials = with_scopes_if_required(credentials, scopes) + credentials = with_scopes_if_required( + credentials, scopes + ).with_quota_project(quota_project_id) effective_project_id = explicit_project_id or project_id if not effective_project_id: _LOGGER.warning( diff --git a/packages/google-auth/google/auth/app_engine.py b/packages/google-auth/google/auth/app_engine.py index ab69951039d0..fae00d0b8745 100644 --- a/packages/google-auth/google/auth/app_engine.py +++ b/packages/google-auth/google/auth/app_engine.py @@ -84,7 +84,7 @@ class Credentials(credentials.Scoped, credentials.Signing, credentials.Credentia tokens. """ - def __init__(self, scopes=None, service_account_id=None): + def __init__(self, scopes=None, service_account_id=None, quota_project_id=None): """ Args: scopes (Sequence[str]): Scopes to request from the App Identity @@ -93,6 +93,8 @@ def __init__(self, scopes=None, service_account_id=None): :func:`google.appengine.api.app_identity.get_access_token`. If not specified, the default application service account ID will be used. + quota_project_id (Optional[str]): The project ID used for quota + and billing. Raises: EnvironmentError: If the App Engine APIs are unavailable. @@ -107,6 +109,7 @@ def __init__(self, scopes=None, service_account_id=None): self._scopes = scopes self._service_account_id = service_account_id self._signer = Signer() + self._quota_project_id = quota_project_id @_helpers.copy_docstring(credentials.Credentials) def refresh(self, request): @@ -137,7 +140,17 @@ def requires_scopes(self): @_helpers.copy_docstring(credentials.Scoped) def with_scopes(self, scopes): return self.__class__( - scopes=scopes, service_account_id=self._service_account_id + scopes=scopes, + service_account_id=self._service_account_id, + quota_project_id=self.quota_project_id, + ) + + @_helpers.copy_docstring(credentials.Credentials) + def with_quota_project(self, quota_project_id): + return self.__class__( + scopes=self._scopes, + service_account_id=self._service_account_id, + quota_project_id=quota_project_id, ) @_helpers.copy_docstring(credentials.Signing) diff --git a/packages/google-auth/google/auth/compute_engine/credentials.py b/packages/google-auth/google/auth/compute_engine/credentials.py index 155046596dd0..e6da238a04db 100644 --- a/packages/google-auth/google/auth/compute_engine/credentials.py +++ b/packages/google-auth/google/auth/compute_engine/credentials.py @@ -54,15 +54,18 @@ class Credentials(credentials.ReadOnlyScoped, credentials.Credentials): https://cloud.google.com/compute/docs/authentication#using """ - def __init__(self, service_account_email="default"): + def __init__(self, service_account_email="default", quota_project_id=None): """ Args: service_account_email (str): The service account email to use, or 'default'. A Compute Engine instance may have multiple service accounts. + quota_project_id (Optional[str]): The project ID used for quota and + billing. """ super(Credentials, self).__init__() self._service_account_email = service_account_email + self._quota_project_id = quota_project_id def _retrieve_info(self, request): """Retrieve information about the service account. @@ -115,6 +118,13 @@ def requires_scopes(self): """False: Compute Engine credentials can not be scoped.""" return False + @_helpers.copy_docstring(credentials.Credentials) + def with_quota_project(self, quota_project_id): + return self.__class__( + service_account_email=self._service_account_email, + quota_project_id=quota_project_id, + ) + _DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds _DEFAULT_TOKEN_URI = "https://www.googleapis.com/oauth2/v4/token" @@ -143,6 +153,7 @@ def __init__( service_account_email=None, signer=None, use_metadata_identity_endpoint=False, + quota_project_id=None, ): """ Args: @@ -165,6 +176,8 @@ def __init__( is False. If set to True, ``token_uri``, ``additional_claims``, ``service_account_email``, ``signer`` argument should not be set; otherwise ValueError will be raised. + quota_project_id (Optional[str]): The project ID used for quota and + billing. Raises: ValueError: @@ -174,6 +187,7 @@ def __init__( """ super(IDTokenCredentials, self).__init__() + self._quota_project_id = quota_project_id self._use_metadata_identity_endpoint = use_metadata_identity_endpoint self._target_audience = target_audience @@ -226,6 +240,7 @@ def with_target_audience(self, target_audience): None, target_audience=target_audience, use_metadata_identity_endpoint=True, + quota_project_id=self._quota_project_id, ) else: return self.__class__( @@ -236,6 +251,31 @@ def with_target_audience(self, target_audience): additional_claims=self._additional_claims.copy(), signer=self.signer, use_metadata_identity_endpoint=False, + quota_project_id=self._quota_project_id, + ) + + @_helpers.copy_docstring(credentials.Credentials) + def with_quota_project(self, quota_project_id): + + # since the signer is already instantiated, + # the request is not needed + if self._use_metadata_identity_endpoint: + return self.__class__( + None, + target_audience=self._target_audience, + use_metadata_identity_endpoint=True, + quota_project_id=quota_project_id, + ) + else: + return self.__class__( + None, + service_account_email=self._service_account_email, + token_uri=self._token_uri, + target_audience=self._target_audience, + additional_claims=self._additional_claims.copy(), + signer=self.signer, + use_metadata_identity_endpoint=False, + quota_project_id=quota_project_id, ) def _make_authorization_grant_assertion(self): diff --git a/packages/google-auth/google/auth/credentials.py b/packages/google-auth/google/auth/credentials.py index 3cc976b525da..3f389b1713ff 100644 --- a/packages/google-auth/google/auth/credentials.py +++ b/packages/google-auth/google/auth/credentials.py @@ -49,6 +49,8 @@ def __init__(self): self.expiry = None """Optional[datetime]: When the token expires and is no longer valid. If this is None, the token is assumed to never expire.""" + self._quota_project_id = None + """Optional[str]: Project to use for quota and billing purposes.""" @property def expired(self): @@ -75,6 +77,11 @@ def valid(self): """ return self.token is not None and not self.expired + @property + def quota_project_id(self): + """Project to use for quota and billing purposes.""" + return self._quota_project_id + @abc.abstractmethod def refresh(self, request): """Refreshes the access token. @@ -102,6 +109,8 @@ def apply(self, headers, token=None): headers["authorization"] = "Bearer {}".format( _helpers.from_bytes(token or self.token) ) + if self.quota_project_id: + headers["x-goog-user-project"] = self.quota_project_id def before_request(self, request, method, url, headers): """Performs credential-specific before request logic. @@ -124,6 +133,18 @@ def before_request(self, request, method, url, headers): self.refresh(request) self.apply(headers) + def with_quota_project(self, quota_project_id): + """Returns a copy of these credentials with a modified quota project + + Args: + quota_project_id (str): The project to use for quota and + billing purposes + + Returns: + google.oauth2.credentials.Credentials: A new credentials instance. + """ + raise NotImplementedError("This class does not support quota project.") + class AnonymousCredentials(Credentials): """Credentials that do not provide any authentication information. @@ -161,6 +182,9 @@ def apply(self, headers, token=None): def before_request(self, request, method, url, headers): """Anonymous credentials do nothing to the request.""" + def with_quota_project(self, quota_project_id): + raise ValueError("Anonymous credentials don't support quota project.") + @six.add_metaclass(abc.ABCMeta) class ReadOnlyScoped(object): diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index 58e1bab06970..dbcb2914eece 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -184,6 +184,7 @@ def __init__( target_scopes, delegates=None, lifetime=_DEFAULT_TOKEN_LIFETIME_SECS, + quota_project_id=None, ): """ Args: @@ -205,6 +206,9 @@ def __init__( target_principal. lifetime (int): Number of seconds the delegated credential should be valid for (upto 3600). + quota_project_id (Optional[str]): The project ID used for quota and billing. + This project may be different from the project used to + create the credentials. """ super(Credentials, self).__init__() @@ -221,6 +225,7 @@ def __init__( self._lifetime = lifetime self.token = None self.expiry = _helpers.utcnow() + self._quota_project_id = quota_project_id @_helpers.copy_docstring(credentials.Credentials) def refresh(self, request): @@ -288,19 +293,38 @@ def service_account_email(self): def signer(self): return self + @_helpers.copy_docstring(credentials.Credentials) + def with_quota_project(self, quota_project_id): + return self.__class__( + self._source_credentials, + target_principal=self._target_principal, + target_scopes=self._target_scopes, + delegates=self._delegates, + lifetime=self._lifetime, + quota_project_id=quota_project_id, + ) + class IDTokenCredentials(credentials.Credentials): """Open ID Connect ID Token-based service account credentials. """ - def __init__(self, target_credentials, target_audience=None, include_email=False): + def __init__( + self, + target_credentials, + target_audience=None, + include_email=False, + quota_project_id=None, + ): """ Args: target_credentials (google.auth.Credentials): The target credential used as to acquire the id tokens for. target_audience (string): Audience to issue the token for. include_email (bool): Include email in IdToken + quota_project_id (Optional[str]): The project ID used for + quota and billing. """ super(IDTokenCredentials, self).__init__() @@ -311,15 +335,20 @@ def __init__(self, target_credentials, target_audience=None, include_email=False self._target_credentials = target_credentials self._target_audience = target_audience self._include_email = include_email + self._quota_project_id = quota_project_id def from_credentials(self, target_credentials, target_audience=None): return self.__class__( - target_credentials=self._target_credentials, target_audience=target_audience + target_credentials=self._target_credentials, + target_audience=target_audience, + quota_project_id=self._quota_project_id, ) def with_target_audience(self, target_audience): return self.__class__( - target_credentials=self._target_credentials, target_audience=target_audience + target_credentials=self._target_credentials, + target_audience=target_audience, + quota_project_id=self._quota_project_id, ) def with_include_email(self, include_email): @@ -327,6 +356,16 @@ def with_include_email(self, include_email): target_credentials=self._target_credentials, target_audience=self._target_audience, include_email=include_email, + quota_project_id=self._quota_project_id, + ) + + @_helpers.copy_docstring(credentials.Credentials) + def with_quota_project(self, quota_project_id): + return self.__class__( + target_credentials=self._target_credentials, + target_audience=self._target_audience, + include_email=self._include_email, + quota_project_id=quota_project_id, ) @_helpers.copy_docstring(credentials.Credentials) diff --git a/packages/google-auth/google/auth/jwt.py b/packages/google-auth/google/auth/jwt.py index 24b92eb4fb07..35ae034325c7 100644 --- a/packages/google-auth/google/auth/jwt.py +++ b/packages/google-auth/google/auth/jwt.py @@ -346,6 +346,7 @@ def __init__( audience, additional_claims=None, token_lifetime=_DEFAULT_TOKEN_LIFETIME_SECS, + quota_project_id=None, ): """ Args: @@ -358,6 +359,8 @@ def __init__( the JWT payload. token_lifetime (int): The amount of time in seconds for which the token is valid. Defaults to 1 hour. + quota_project_id (Optional[str]): The project ID used for quota + and billing. """ super(Credentials, self).__init__() self._signer = signer @@ -365,6 +368,7 @@ def __init__( self._subject = subject self._audience = audience self._token_lifetime = token_lifetime + self._quota_project_id = quota_project_id if additional_claims is None: additional_claims = {} @@ -486,6 +490,18 @@ def with_claims( subject=subject if subject is not None else self._subject, audience=audience if audience is not None else self._audience, additional_claims=new_additional_claims, + quota_project_id=self._quota_project_id, + ) + + @_helpers.copy_docstring(google.auth.credentials.Credentials) + def with_quota_project(self, quota_project_id): + return self.__class__( + self._signer, + issuer=self._issuer, + subject=self._subject, + audience=self._audience, + additional_claims=self._additional_claims, + quota_project_id=quota_project_id, ) def _make_jwt(self): @@ -565,6 +581,7 @@ def __init__( additional_claims=None, token_lifetime=_DEFAULT_TOKEN_LIFETIME_SECS, max_cache_size=_DEFAULT_MAX_CACHE_SIZE, + quota_project_id=None, ): """ Args: @@ -577,12 +594,16 @@ def __init__( which the token is valid. Defaults to 1 hour. max_cache_size (int): The maximum number of JWT tokens to keep in cache. Tokens are cached using :class:`cachetools.LRUCache`. + quota_project_id (Optional[str]): The project ID used for quota + and billing. + """ super(OnDemandCredentials, self).__init__() self._signer = signer self._issuer = issuer self._subject = subject self._token_lifetime = token_lifetime + self._quota_project_id = quota_project_id if additional_claims is None: additional_claims = {} @@ -697,6 +718,19 @@ def with_claims(self, issuer=None, subject=None, additional_claims=None): subject=subject if subject is not None else self._subject, additional_claims=new_additional_claims, max_cache_size=self._cache.maxsize, + quota_project_id=self._quota_project_id, + ) + + @_helpers.copy_docstring(google.auth.credentials.Credentials) + def with_quota_project(self, quota_project_id): + + return self.__class__( + self._signer, + issuer=self._issuer, + subject=self._subject, + additional_claims=self._additional_claims, + max_cache_size=self._cache.maxsize, + quota_project_id=quota_project_id, ) @property diff --git a/packages/google-auth/google/oauth2/credentials.py b/packages/google-auth/google/oauth2/credentials.py index 757219671894..6f9627572d5e 100644 --- a/packages/google-auth/google/oauth2/credentials.py +++ b/packages/google-auth/google/oauth2/credentials.py @@ -155,27 +155,15 @@ def client_secret(self): """Optional[str]: The OAuth 2.0 client secret.""" return self._client_secret - @property - def quota_project_id(self): - """Optional[str]: The project to use for quota and billing purposes.""" - return self._quota_project_id - @property def requires_scopes(self): """False: OAuth 2.0 credentials have their scopes set when the initial token is requested and can not be changed.""" return False + @_helpers.copy_docstring(credentials.Credentials) def with_quota_project(self, quota_project_id): - """Returns a copy of these credentials with a modified quota project - Args: - quota_project_id (str): The project to use for quota and - billing purposes - - Returns: - google.oauth2.credentials.Credentials: A new credentials instance. - """ return self.__class__( self.token, refresh_token=self.refresh_token, @@ -227,12 +215,6 @@ def refresh(self, request): ) ) - @_helpers.copy_docstring(credentials.Credentials) - def apply(self, headers, token=None): - super(Credentials, self).apply(headers, token=token) - if self.quota_project_id is not None: - headers["x-goog-user-project"] = self.quota_project_id - @classmethod def from_authorized_user_info(cls, info, scopes=None): """Creates a Credentials instance from parsed authorized user info. @@ -332,11 +314,15 @@ class UserAccessTokenCredentials(credentials.Credentials): Args: account (Optional[str]): Account to get the access token for. If not specified, the current active account will be used. + quota_project_id (Optional[str]): The project ID used for quota + and billing. + """ - def __init__(self, account=None): + def __init__(self, account=None, quota_project_id=None): super(UserAccessTokenCredentials, self).__init__() self._account = account + self._quota_project_id = quota_project_id def with_account(self, account): """Create a new instance with the given account. @@ -348,7 +334,11 @@ def with_account(self, account): google.oauth2.credentials.UserAccessTokenCredentials: The created credentials with the given account. """ - return self.__class__(account=account) + return self.__class__(account=account, quota_project_id=self._quota_project_id) + + @_helpers.copy_docstring(credentials.Credentials) + def with_quota_project(self, quota_project_id): + return self.__class__(account=self._account, quota_project_id=quota_project_id) def refresh(self, request): """Refreshes the access token. diff --git a/packages/google-auth/google/oauth2/service_account.py b/packages/google-auth/google/oauth2/service_account.py index 54630d34ba98..2240631e9af9 100644 --- a/packages/google-auth/google/oauth2/service_account.py +++ b/packages/google-auth/google/oauth2/service_account.py @@ -237,11 +237,6 @@ def project_id(self): """Project ID associated with this credential.""" return self._project_id - @property - def quota_project_id(self): - """Project ID to use for quota and billing purposes.""" - return self._quota_project_id - @property def requires_scopes(self): """Checks if the credentials requires scopes. @@ -311,17 +306,9 @@ def with_claims(self, additional_claims): additional_claims=new_additional_claims, ) + @_helpers.copy_docstring(credentials.Credentials) def with_quota_project(self, quota_project_id): - """Returns a copy of these credentials with a modified quota project. - Args: - quota_project_id (str): The project to use for quota and - billing purposes - - Returns: - google.auth.service_account.Credentials: A new credentials - instance. - """ return self.__class__( self._signer, service_account_email=self._service_account_email, @@ -373,12 +360,6 @@ def refresh(self, request): self.token = access_token self.expiry = expiry - @_helpers.copy_docstring(credentials.Credentials) - def apply(self, headers, token=None): - super(Credentials, self).apply(headers, token=token) - if self.quota_project_id is not None: - headers["x-goog-user-project"] = self.quota_project_id - @_helpers.copy_docstring(credentials.Signing) def sign_bytes(self, message): return self._signer.sign(message) @@ -443,6 +424,7 @@ def __init__( token_uri, target_audience, additional_claims=None, + quota_project_id=None, ): """ Args: @@ -454,7 +436,7 @@ def __init__( will be set to this string. additional_claims (Mapping[str, str]): Any additional claims for the JWT assertion used in the authorization grant. - + quota_project_id (Optional[str]): The project ID used for quota and billing. .. note:: Typically one of the helper constructors :meth:`from_service_account_file` or :meth:`from_service_account_info` are used instead of calling the @@ -465,6 +447,7 @@ def __init__( self._service_account_email = service_account_email self._token_uri = token_uri self._target_audience = target_audience + self._quota_project_id = quota_project_id if additional_claims is not None: self._additional_claims = additional_claims @@ -547,6 +530,18 @@ def with_target_audience(self, target_audience): token_uri=self._token_uri, target_audience=target_audience, additional_claims=self._additional_claims.copy(), + quota_project_id=self.quota_project_id, + ) + + @_helpers.copy_docstring(credentials.Credentials) + def with_quota_project(self, quota_project_id): + return self.__class__( + self._signer, + service_account_email=self._service_account_email, + token_uri=self._token_uri, + target_audience=self._target_audience, + additional_claims=self._additional_claims.copy(), + quota_project_id=quota_project_id, ) def _make_authorization_grant_assertion(self): diff --git a/packages/google-auth/tests/compute_engine/test_credentials.py b/packages/google-auth/tests/compute_engine/test_credentials.py index 363dc9dbb274..4ee6536762b0 100644 --- a/packages/google-auth/tests/compute_engine/test_credentials.py +++ b/packages/google-auth/tests/compute_engine/test_credentials.py @@ -59,6 +59,8 @@ def test_default_state(self): assert not self.credentials.requires_scopes # Service account email hasn't been populated assert self.credentials.service_account_email == "default" + # No quota project + assert not self.credentials._quota_project_id @mock.patch( "google.auth._helpers.utcnow", @@ -131,6 +133,11 @@ def test_before_request_refreshes(self, get): # Credentials should now be valid. assert self.credentials.valid + def test_with_quota_project(self): + quota_project_creds = self.credentials.with_quota_project("project-foo") + + assert quota_project_creds._quota_project_id == "project-foo" + class TestIDTokenCredentials(object): credentials = None @@ -154,6 +161,8 @@ def test_default_state(self, get): # Signer is initialized assert self.credentials.signer assert self.credentials.signer_email == "service-account@example.com" + # No quota project + assert not self.credentials._quota_project_id @mock.patch( "google.auth._helpers.utcnow", @@ -388,6 +397,121 @@ def test_with_target_audience_integration(self): assert self.credentials.token is not None + @mock.patch( + "google.auth._helpers.utcnow", + return_value=datetime.datetime.utcfromtimestamp(0), + ) + @mock.patch("google.auth.compute_engine._metadata.get", autospec=True) + @mock.patch("google.auth.iam.Signer.sign", autospec=True) + def test_with_quota_project(self, sign, get, utcnow): + get.side_effect = [ + {"email": "service-account@example.com", "scopes": ["one", "two"]} + ] + sign.side_effect = [b"signature"] + + request = mock.create_autospec(transport.Request, instance=True) + self.credentials = credentials.IDTokenCredentials( + request=request, target_audience="https://audience.com" + ) + self.credentials = self.credentials.with_quota_project("project-foo") + + assert self.credentials._quota_project_id == "project-foo" + + # Generate authorization grant: + token = self.credentials._make_authorization_grant_assertion() + payload = jwt.decode(token, verify=False) + + # The JWT token signature is 'signature' encoded in base 64: + assert token.endswith(b".c2lnbmF0dXJl") + + # Check that the credentials have the token and proper expiration + assert payload == { + "aud": "https://www.googleapis.com/oauth2/v4/token", + "exp": 3600, + "iat": 0, + "iss": "service-account@example.com", + "target_audience": "https://audience.com", + } + + # Check that the signer have been initialized with a Request object + assert isinstance(self.credentials._signer._request, transport.Request) + + @responses.activate + def test_with_quota_project_integration(self): + """ Test that it is possible to refresh credentials + generated from `with_quota_project`. + + Instead of mocking the methods, the HTTP responses + have been mocked. + """ + + # mock information about credentials + responses.add( + responses.GET, + "http://metadata.google.internal/computeMetadata/v1/instance/" + "service-accounts/default/?recursive=true", + status=200, + content_type="application/json", + json={ + "scopes": "email", + "email": "service-account@example.com", + "aliases": ["default"], + }, + ) + + # mock token for credentials + responses.add( + responses.GET, + "http://metadata.google.internal/computeMetadata/v1/instance/" + "service-accounts/service-account@example.com/token", + status=200, + content_type="application/json", + json={ + "access_token": "some-token", + "expires_in": 3210, + "token_type": "Bearer", + }, + ) + + # mock sign blob endpoint + signature = base64.b64encode(b"some-signature").decode("utf-8") + responses.add( + responses.POST, + "https://iamcredentials.googleapis.com/v1/projects/-/" + "serviceAccounts/service-account@example.com:signBlob?alt=json", + status=200, + content_type="application/json", + json={"keyId": "some-key-id", "signedBlob": signature}, + ) + + id_token = "{}.{}.{}".format( + base64.b64encode(b'{"some":"some"}').decode("utf-8"), + base64.b64encode(b'{"exp": 3210}').decode("utf-8"), + base64.b64encode(b"token").decode("utf-8"), + ) + + # mock id token endpoint + responses.add( + responses.POST, + "https://www.googleapis.com/oauth2/v4/token", + status=200, + content_type="application/json", + json={"id_token": id_token, "expiry": 3210}, + ) + + self.credentials = credentials.IDTokenCredentials( + request=requests.Request(), + service_account_email="service-account@example.com", + target_audience="https://audience.com", + ) + + self.credentials = self.credentials.with_quota_project("project-foo") + + self.credentials.refresh(requests.Request()) + + assert self.credentials.token is not None + assert self.credentials._quota_project_id == "project-foo" + @mock.patch( "google.auth._helpers.utcnow", return_value=datetime.datetime.utcfromtimestamp(0), @@ -548,6 +672,23 @@ def test_with_target_audience_for_metadata(self, get_service_account_info): assert cred._token_uri is None assert cred._service_account_email == "foo@example.com" + @mock.patch( + "google.auth.compute_engine._metadata.get_service_account_info", autospec=True + ) + def test_id_token_with_quota_project(self, get_service_account_info): + get_service_account_info.return_value = {"email": "foo@example.com"} + + cred = credentials.IDTokenCredentials( + mock.Mock(), "audience", use_metadata_identity_endpoint=True + ) + cred = cred.with_quota_project("project-foo") + + assert cred._quota_project_id == "project-foo" + assert cred._use_metadata_identity_endpoint + assert cred._signer is None + assert cred._token_uri is None + assert cred._service_account_email == "foo@example.com" + @mock.patch( "google.auth.compute_engine._metadata.get_service_account_info", autospec=True ) diff --git a/packages/google-auth/tests/oauth2/test_credentials.py b/packages/google-auth/tests/oauth2/test_credentials.py index 78b10125215b..69d9fbcea745 100644 --- a/packages/google-auth/tests/oauth2/test_credentials.py +++ b/packages/google-auth/tests/oauth2/test_credentials.py @@ -454,6 +454,13 @@ def test_refresh(self, get_auth_access_token): cred.refresh(None) assert cred.token == "access_token" + def test_with_quota_project(self): + cred = credentials.UserAccessTokenCredentials() + quota_project_cred = cred.with_quota_project("project-foo") + + assert quota_project_cred._quota_project_id == "project-foo" + assert quota_project_cred._account == cred._account + @mock.patch( "google.oauth2.credentials.UserAccessTokenCredentials.apply", autospec=True ) diff --git a/packages/google-auth/tests/oauth2/test_service_account.py b/packages/google-auth/tests/oauth2/test_service_account.py index 457d472d7b07..7f27dad3cf4e 100644 --- a/packages/google-auth/tests/oauth2/test_service_account.py +++ b/packages/google-auth/tests/oauth2/test_service_account.py @@ -291,6 +291,11 @@ def test_with_target_audience(self): new_credentials = credentials.with_target_audience("https://new.example.com") assert new_credentials._target_audience == "https://new.example.com" + def test_with_quota_project(self): + credentials = self.make_credentials() + new_credentials = credentials.with_quota_project("project-foo") + assert new_credentials._quota_project_id == "project-foo" + def test__make_authorization_grant_assertion(self): credentials = self.make_credentials() token = credentials._make_authorization_grant_assertion() diff --git a/packages/google-auth/tests/test__default.py b/packages/google-auth/tests/test__default.py index 3c87b35ebeef..0665efab2944 100644 --- a/packages/google-auth/tests/test__default.py +++ b/packages/google-auth/tests/test__default.py @@ -21,6 +21,7 @@ from google.auth import _default from google.auth import app_engine from google.auth import compute_engine +from google.auth import credentials from google.auth import environment_vars from google.auth import exceptions from google.oauth2 import service_account @@ -48,9 +49,12 @@ with open(SERVICE_ACCOUNT_FILE) as fh: SERVICE_ACCOUNT_FILE_DATA = json.load(fh) +MOCK_CREDENTIALS = mock.Mock(spec=credentials.Credentials) +MOCK_CREDENTIALS.with_quota_project.return_value = MOCK_CREDENTIALS + LOAD_FILE_PATCH = mock.patch( "google.auth._default.load_credentials_from_file", - return_value=(mock.sentinel.credentials, mock.sentinel.project_id), + return_value=(MOCK_CREDENTIALS, mock.sentinel.project_id), autospec=True, ) @@ -136,6 +140,16 @@ def test_load_credentials_from_file_authorized_user_cloud_sdk_with_scopes(): assert credentials.scopes == ["https://www.google.com/calendar/feeds"] +def test_load_credentials_from_file_authorized_user_cloud_sdk_with_quota_project(): + credentials, project_id = _default.load_credentials_from_file( + AUTHORIZED_USER_CLOUD_SDK_FILE, quota_project_id="project-foo" + ) + + assert isinstance(credentials, google.oauth2.credentials.Credentials) + assert project_id is None + assert credentials.quota_project_id == "project-foo" + + def test_load_credentials_from_file_service_account(): credentials, project_id = _default.load_credentials_from_file(SERVICE_ACCOUNT_FILE) assert isinstance(credentials, service_account.Credentials) @@ -173,19 +187,19 @@ def test__get_explicit_environ_credentials(load, monkeypatch): credentials, project_id = _default._get_explicit_environ_credentials() - assert credentials is mock.sentinel.credentials + assert credentials is MOCK_CREDENTIALS assert project_id is mock.sentinel.project_id load.assert_called_with("filename") @LOAD_FILE_PATCH def test__get_explicit_environ_credentials_no_project_id(load, monkeypatch): - load.return_value = mock.sentinel.credentials, None + load.return_value = MOCK_CREDENTIALS, None monkeypatch.setenv(environment_vars.CREDENTIALS, "filename") credentials, project_id = _default._get_explicit_environ_credentials() - assert credentials is mock.sentinel.credentials + assert credentials is MOCK_CREDENTIALS assert project_id is None @@ -198,7 +212,7 @@ def test__get_gcloud_sdk_credentials(get_adc_path, load): credentials, project_id = _default._get_gcloud_sdk_credentials() - assert credentials is mock.sentinel.credentials + assert credentials is MOCK_CREDENTIALS assert project_id is mock.sentinel.project_id load.assert_called_with(SERVICE_ACCOUNT_FILE) @@ -226,11 +240,11 @@ def test__get_gcloud_sdk_credentials_non_existent(get_adc_path, tmpdir): def test__get_gcloud_sdk_credentials_project_id(load, unused_isfile, get_project_id): # Don't return a project ID from load file, make the function check # the Cloud SDK project. - load.return_value = mock.sentinel.credentials, None + load.return_value = MOCK_CREDENTIALS, None credentials, project_id = _default._get_gcloud_sdk_credentials() - assert credentials == mock.sentinel.credentials + assert credentials == MOCK_CREDENTIALS assert project_id == mock.sentinel.project_id assert get_project_id.called @@ -241,11 +255,11 @@ def test__get_gcloud_sdk_credentials_project_id(load, unused_isfile, get_project def test__get_gcloud_sdk_credentials_no_project_id(load, unused_isfile, get_project_id): # Don't return a project ID from load file, make the function check # the Cloud SDK project. - load.return_value = mock.sentinel.credentials, None + load.return_value = MOCK_CREDENTIALS, None credentials, project_id = _default._get_gcloud_sdk_credentials() - assert credentials == mock.sentinel.credentials + assert credentials == MOCK_CREDENTIALS assert project_id is None assert get_project_id.called @@ -351,58 +365,58 @@ def test__get_gce_credentials_explicit_request(ping): @mock.patch( "google.auth._default._get_explicit_environ_credentials", - return_value=(mock.sentinel.credentials, mock.sentinel.project_id), + return_value=(MOCK_CREDENTIALS, mock.sentinel.project_id), autospec=True, ) def test_default_early_out(unused_get): - assert _default.default() == (mock.sentinel.credentials, mock.sentinel.project_id) + assert _default.default() == (MOCK_CREDENTIALS, mock.sentinel.project_id) @mock.patch( "google.auth._default._get_explicit_environ_credentials", - return_value=(mock.sentinel.credentials, mock.sentinel.project_id), + return_value=(MOCK_CREDENTIALS, mock.sentinel.project_id), autospec=True, ) def test_default_explict_project_id(unused_get, monkeypatch): monkeypatch.setenv(environment_vars.PROJECT, "explicit-env") - assert _default.default() == (mock.sentinel.credentials, "explicit-env") + assert _default.default() == (MOCK_CREDENTIALS, "explicit-env") @mock.patch( "google.auth._default._get_explicit_environ_credentials", - return_value=(mock.sentinel.credentials, mock.sentinel.project_id), + return_value=(MOCK_CREDENTIALS, mock.sentinel.project_id), autospec=True, ) def test_default_explict_legacy_project_id(unused_get, monkeypatch): monkeypatch.setenv(environment_vars.LEGACY_PROJECT, "explicit-env") - assert _default.default() == (mock.sentinel.credentials, "explicit-env") + assert _default.default() == (MOCK_CREDENTIALS, "explicit-env") @mock.patch("logging.Logger.warning", autospec=True) @mock.patch( "google.auth._default._get_explicit_environ_credentials", - return_value=(mock.sentinel.credentials, None), + return_value=(MOCK_CREDENTIALS, None), autospec=True, ) @mock.patch( "google.auth._default._get_gcloud_sdk_credentials", - return_value=(mock.sentinel.credentials, None), + return_value=(MOCK_CREDENTIALS, None), autospec=True, ) @mock.patch( "google.auth._default._get_gae_credentials", - return_value=(mock.sentinel.credentials, None), + return_value=(MOCK_CREDENTIALS, None), autospec=True, ) @mock.patch( "google.auth._default._get_gce_credentials", - return_value=(mock.sentinel.credentials, None), + return_value=(MOCK_CREDENTIALS, None), autospec=True, ) def test_default_without_project_id( unused_gce, unused_gae, unused_sdk, unused_explicit, logger_warning ): - assert _default.default() == (mock.sentinel.credentials, None) + assert _default.default() == (MOCK_CREDENTIALS, None) logger_warning.assert_called_with(mock.ANY, mock.ANY, mock.ANY) @@ -433,10 +447,14 @@ def test_default_fail(unused_gce, unused_gae, unused_sdk, unused_explicit): @mock.patch( "google.auth._default._get_explicit_environ_credentials", - return_value=(mock.sentinel.credentials, mock.sentinel.project_id), + return_value=(MOCK_CREDENTIALS, mock.sentinel.project_id), + autospec=True, +) +@mock.patch( + "google.auth.credentials.with_scopes_if_required", + return_value=MOCK_CREDENTIALS, autospec=True, ) -@mock.patch("google.auth.credentials.with_scopes_if_required", autospec=True) def test_default_scoped(with_scopes, unused_get): scopes = ["one", "two"] @@ -444,12 +462,12 @@ def test_default_scoped(with_scopes, unused_get): assert credentials == with_scopes.return_value assert project_id == mock.sentinel.project_id - with_scopes.assert_called_once_with(mock.sentinel.credentials, scopes) + with_scopes.assert_called_once_with(MOCK_CREDENTIALS, scopes) @mock.patch( "google.auth._default._get_explicit_environ_credentials", - return_value=(mock.sentinel.credentials, mock.sentinel.project_id), + return_value=(MOCK_CREDENTIALS, mock.sentinel.project_id), autospec=True, ) def test_default_no_app_engine_compute_engine_module(unused_get): @@ -463,7 +481,4 @@ def test_default_no_app_engine_compute_engine_module(unused_get): with mock.patch.dict("sys.modules"): sys.modules["google.auth.compute_engine"] = None sys.modules["google.auth.app_engine"] = None - assert _default.default() == ( - mock.sentinel.credentials, - mock.sentinel.project_id, - ) + assert _default.default() == (MOCK_CREDENTIALS, mock.sentinel.project_id) diff --git a/packages/google-auth/tests/test_app_engine.py b/packages/google-auth/tests/test_app_engine.py index 9dfdfa65be14..846d31477462 100644 --- a/packages/google-auth/tests/test_app_engine.py +++ b/packages/google-auth/tests/test_app_engine.py @@ -102,6 +102,7 @@ def test_default_state(self, app_identity): # Scopes are required assert not credentials.scopes assert credentials.requires_scopes + assert not credentials.quota_project_id def test_with_scopes(self, app_identity): credentials = app_engine.Credentials() @@ -114,6 +115,16 @@ def test_with_scopes(self, app_identity): assert scoped_credentials.has_scopes(["email"]) assert not scoped_credentials.requires_scopes + def test_with_quota_project(self, app_identity): + credentials = app_engine.Credentials() + + assert not credentials.scopes + assert not credentials.quota_project_id + + quota_project_creds = credentials.with_quota_project("project-foo") + + assert quota_project_creds.quota_project_id == "project-foo" + def test_service_account_email_implicit(self, app_identity): app_identity.get_service_account_name.return_value = ( mock.sentinel.service_account_email diff --git a/packages/google-auth/tests/test_credentials.py b/packages/google-auth/tests/test_credentials.py index 16ddd9b44120..2023fac1bc9d 100644 --- a/packages/google-auth/tests/test_credentials.py +++ b/packages/google-auth/tests/test_credentials.py @@ -24,6 +24,9 @@ class CredentialsImpl(credentials.Credentials): def refresh(self, request): self.token = request + def with_quota_project(self, quota_project_id): + raise NotImplementedError() + def test_credentials_constructor(): credentials = CredentialsImpl() @@ -112,6 +115,12 @@ def test_anonymous_credentials_before_request(): assert headers == {} +def test_anonymous_credentials_with_quota_project(): + with pytest.raises(ValueError): + anon = credentials.AnonymousCredentials() + anon.with_quota_project("project-foo") + + class ReadOnlyScopedCredentialsImpl(credentials.ReadOnlyScoped, CredentialsImpl): @property def requires_scopes(self): diff --git a/packages/google-auth/tests/test_iam.py b/packages/google-auth/tests/test_iam.py index ea7d08a4075e..e20eebaa7612 100644 --- a/packages/google-auth/tests/test_iam.py +++ b/packages/google-auth/tests/test_iam.py @@ -50,6 +50,9 @@ def __init__(self): def refresh(self, request): pass + def with_quota_project(self, quota_project_id): + raise NotImplementedError() + return CredentialsImpl() diff --git a/packages/google-auth/tests/test_impersonated_credentials.py b/packages/google-auth/tests/test_impersonated_credentials.py index e0b5b1179e9e..46850a0d9a17 100644 --- a/packages/google-auth/tests/test_impersonated_credentials.py +++ b/packages/google-auth/tests/test_impersonated_credentials.py @@ -311,6 +311,12 @@ def test_sign_bytes(self, mock_donor_credentials, mock_authorizedsession_sign): signature = credentials.sign_bytes(b"signed bytes") assert signature == b"signature" + def test_with_quota_project(self): + credentials = self.make_credentials() + + quota_project_creds = credentials.with_quota_project("project-foo") + assert quota_project_creds._quota_project_id == "project-foo" + def test_id_token_success( self, mock_donor_credentials, mock_authorizedsession_idtoken ): @@ -435,3 +441,32 @@ def test_id_token_with_include_email( id_creds.refresh(request) assert id_creds.token == ID_TOKEN_DATA + + def test_id_token_with_quota_project( + self, mock_donor_credentials, mock_authorizedsession_idtoken + ): + credentials = self.make_credentials(lifetime=None) + token = "token" + target_audience = "https://foo.bar" + + expire_time = ( + _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=500) + ).isoformat("T") + "Z" + response_body = {"accessToken": token, "expireTime": expire_time} + + request = self.make_request( + data=json.dumps(response_body), status=http_client.OK + ) + + credentials.refresh(request) + + assert credentials.valid + assert not credentials.expired + + id_creds = impersonated_credentials.IDTokenCredentials( + credentials, target_audience=target_audience + ) + id_creds = id_creds.with_quota_project("project-foo") + id_creds.refresh(request) + + assert id_creds.quota_project_id == "project-foo" diff --git a/packages/google-auth/tests/test_jwt.py b/packages/google-auth/tests/test_jwt.py index 488aee4671df..7aa031ec533d 100644 --- a/packages/google-auth/tests/test_jwt.py +++ b/packages/google-auth/tests/test_jwt.py @@ -363,6 +363,18 @@ def test_with_claims(self): assert new_credentials._subject == self.credentials._subject assert new_credentials._audience == new_audience assert new_credentials._additional_claims == self.credentials._additional_claims + assert new_credentials._quota_project_id == self.credentials._quota_project_id + + def test_with_quota_project(self): + quota_project_id = "project-foo" + + new_credentials = self.credentials.with_quota_project(quota_project_id) + assert new_credentials._signer == self.credentials._signer + assert new_credentials._issuer == self.credentials._issuer + assert new_credentials._subject == self.credentials._subject + assert new_credentials._audience == self.credentials._audience + assert new_credentials._additional_claims == self.credentials._additional_claims + assert new_credentials._quota_project_id == quota_project_id def test_sign_bytes(self): to_sign = b"123" @@ -507,6 +519,16 @@ def test_with_claims(self): assert new_credentials._subject == self.credentials._subject assert new_credentials._additional_claims == new_claims + def test_with_quota_project(self): + quota_project_id = "project-foo" + new_credentials = self.credentials.with_quota_project(quota_project_id) + + assert new_credentials._signer == self.credentials._signer + assert new_credentials._issuer == self.credentials._issuer + assert new_credentials._subject == self.credentials._subject + assert new_credentials._additional_claims == self.credentials._additional_claims + assert new_credentials._quota_project_id == quota_project_id + def test_sign_bytes(self): to_sign = b"123" signature = self.credentials.sign_bytes(to_sign) diff --git a/packages/google-auth/tests/transport/test_grpc.py b/packages/google-auth/tests/transport/test_grpc.py index c3da76df12b9..ef2e2e24f8a2 100644 --- a/packages/google-auth/tests/transport/test_grpc.py +++ b/packages/google-auth/tests/transport/test_grpc.py @@ -52,6 +52,9 @@ def __init__(self, token="token"): def refresh(self, request): self.token += "1" + def with_quota_project(self, quota_project_id): + raise NotImplementedError() + class TestAuthMetadataPlugin(object): def test_call_no_refresh(self): diff --git a/packages/google-auth/tests/transport/test_requests.py b/packages/google-auth/tests/transport/test_requests.py index 77e1527a9bea..7ac55cebbff9 100644 --- a/packages/google-auth/tests/transport/test_requests.py +++ b/packages/google-auth/tests/transport/test_requests.py @@ -109,6 +109,9 @@ def before_request(self, request, method, url, headers): def refresh(self, request): self.token += "1" + def with_quota_project(self, quota_project_id): + raise NotImplementedError() + class TimeTickCredentialsStub(CredentialsStub): """Credentials that spend some (mocked) time when refreshing a token.""" diff --git a/packages/google-auth/tests/transport/test_urllib3.py b/packages/google-auth/tests/transport/test_urllib3.py index 1a1c0a1e6801..3158b92c613d 100644 --- a/packages/google-auth/tests/transport/test_urllib3.py +++ b/packages/google-auth/tests/transport/test_urllib3.py @@ -65,6 +65,9 @@ def before_request(self, request, method, url, headers): def refresh(self, request): self.token += "1" + def with_quota_project(self, quota_project_id): + raise NotImplementedError() + class HttpStub(object): def __init__(self, responses, headers=None): From eae26de86d69dd6d198e79da66ffc4a0bf23c2e2 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 13 Jul 2020 11:34:51 -0700 Subject: [PATCH 322/966] chore: release 1.19.0 (#552) * updated CHANGELOG.md [ci skip] * updated setup.cfg [ci skip] * updated setup.py Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 18 ++++++++++++++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 18df080d6319..cef09d5ef0fa 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,24 @@ [1]: https://pypi.org/project/google-auth/#history +## [1.19.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.18.0...v1.19.0) (2020-07-09) + + +### Features + +* add quota project to base credentials class ([#546](https://www.github.com/googleapis/google-auth-library-python/issues/546)) ([3dda7b2](https://www.github.com/googleapis/google-auth-library-python/commit/3dda7b2ab88aba7941b8b5281b4acbc7db74169b)) +* check 'iss' in `verify_oauth2_token` ([#500](https://www.github.com/googleapis/google-auth-library-python/issues/500)) ([c05b8b5](https://www.github.com/googleapis/google-auth-library-python/commit/c05b8b52e3bbc096cf32e2d4bb5bd45986d3cd04)) + + +### Bug Fixes + +* migrate signBlob to iamcredentials.googleapis.com ([#553](https://www.github.com/googleapis/google-auth-library-python/issues/553)) ([038ae1b](https://www.github.com/googleapis/google-auth-library-python/commit/038ae1b78dc83e44ad39ef7ba15c607f62232087)) + + +### Documentation + +* remove 3.4 from supported versions list ([#549](https://www.github.com/googleapis/google-auth-library-python/issues/549)) ([8c84d0f](https://www.github.com/googleapis/google-auth-library-python/commit/8c84d0fb36d9eba6b319964ca0a22501efca805b)) + ## [1.18.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.17.2...v1.18.0) (2020-06-18) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 82259d54e55c..9327cf6d17e8 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -32,7 +32,7 @@ with io.open("README.rst", "r") as fh: long_description = fh.read() -version = "1.18.0" +version = "1.19.0" setup( name="google-auth", From ed42fa99f45e1dbd10de8563b3c549ff1a52ee1f Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Wed, 15 Jul 2020 16:49:27 -0700 Subject: [PATCH 323/966] fix: don't add empty quota project (#560) --- packages/google-auth/google/auth/_default.py | 15 +++++++++----- packages/google-auth/tests/test__default.py | 21 ++++++++++++++++++++ 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index f3e498bb57ed..694033f3c42c 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -116,11 +116,13 @@ def load_credentials_from_file(filename, scopes=None, quota_project_id=None): try: credentials = credentials.Credentials.from_authorized_user_info( info, scopes=scopes - ).with_quota_project(quota_project_id) + ) except ValueError as caught_exc: msg = "Failed to load authorized user credentials from {}".format(filename) new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) six.raise_from(new_exc, caught_exc) + if quota_project_id: + credentials = credentials.with_quota_project(quota_project_id) if not credentials.quota_project_id: _warn_about_problematic_credentials(credentials) return credentials, None @@ -131,11 +133,13 @@ def load_credentials_from_file(filename, scopes=None, quota_project_id=None): try: credentials = service_account.Credentials.from_service_account_info( info, scopes=scopes - ).with_quota_project(quota_project_id) + ) except ValueError as caught_exc: msg = "Failed to load service account credentials from {}".format(filename) new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) six.raise_from(new_exc, caught_exc) + if quota_project_id: + credentials = credentials.with_quota_project(quota_project_id) return credentials, info.get("project_id") else: @@ -317,9 +321,10 @@ def default(scopes=None, request=None, quota_project_id=None): for checker in checkers: credentials, project_id = checker() if credentials is not None: - credentials = with_scopes_if_required( - credentials, scopes - ).with_quota_project(quota_project_id) + credentials = with_scopes_if_required(credentials, scopes) + if quota_project_id: + credentials = credentials.with_quota_project(quota_project_id) + effective_project_id = explicit_project_id or project_id if not effective_project_id: _LOGGER.warning( diff --git a/packages/google-auth/tests/test__default.py b/packages/google-auth/tests/test__default.py index 0665efab2944..55a14c207fb8 100644 --- a/packages/google-auth/tests/test__default.py +++ b/packages/google-auth/tests/test__default.py @@ -165,6 +165,15 @@ def test_load_credentials_from_file_service_account_with_scopes(): assert credentials.scopes == ["https://www.google.com/calendar/feeds"] +def test_load_credentials_from_file_service_account_with_quota_project(): + credentials, project_id = _default.load_credentials_from_file( + SERVICE_ACCOUNT_FILE, quota_project_id="project-foo" + ) + assert isinstance(credentials, service_account.Credentials) + assert project_id == SERVICE_ACCOUNT_FILE_DATA["project_id"] + assert credentials.quota_project_id == "project-foo" + + def test_load_credentials_from_file_service_account_bad_format(tmpdir): filename = tmpdir.join("serivce_account_bad.json") filename.write(json.dumps({"type": "service_account"})) @@ -465,6 +474,18 @@ def test_default_scoped(with_scopes, unused_get): with_scopes.assert_called_once_with(MOCK_CREDENTIALS, scopes) +@mock.patch( + "google.auth._default._get_explicit_environ_credentials", + return_value=(MOCK_CREDENTIALS, mock.sentinel.project_id), + autospec=True, +) +def test_default_quota_project(with_quota_project): + credentials, project_id = _default.default(quota_project_id="project-foo") + + MOCK_CREDENTIALS.with_quota_project.assert_called_once_with("project-foo") + assert project_id == mock.sentinel.project_id + + @mock.patch( "google.auth._default._get_explicit_environ_credentials", return_value=(MOCK_CREDENTIALS, mock.sentinel.project_id), From 493562abef012e3ce702cc111f2111d43e6d6ba1 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Wed, 15 Jul 2020 17:55:49 -0700 Subject: [PATCH 324/966] chore: release 1.19.1 (#562) --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index cef09d5ef0fa..593596e0c9ec 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +### [1.19.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.19.0...v1.19.1) (2020-07-15) + + +### Bug Fixes + +* don't add empty quota project ([#560](https://www.github.com/googleapis/google-auth-library-python/issues/560)) ([ab2be5d](https://www.github.com/googleapis/google-auth-library-python/commit/ab2be5de829e830979514683582c11f98fa943c7)) + ## [1.19.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.18.0...v1.19.0) (2020-07-09) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 9327cf6d17e8..52033def6e15 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -32,7 +32,7 @@ with io.open("README.rst", "r") as fh: long_description = fh.read() -version = "1.19.0" +version = "1.19.1" setup( name="google-auth", From b863c96c850a3ba8e933133488cf1e1ce73476b2 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Fri, 17 Jul 2020 10:24:02 -0700 Subject: [PATCH 325/966] Revert "fix: migrate signBlob to iamcredentials.googleapis.com" (#563) Reverts googleapis/google-auth-library-python#553 We have received reports that this is breaking users. See internal issue 161506225. --- packages/google-auth/.gitignore | 3 --- packages/google-auth/google/auth/iam.py | 6 +++--- .../system_tests/test_service_account.py | 17 ----------------- .../tests/compute_engine/test_credentials.py | 12 ++++++------ packages/google-auth/tests/test_iam.py | 2 +- 5 files changed, 10 insertions(+), 30 deletions(-) diff --git a/packages/google-auth/.gitignore b/packages/google-auth/.gitignore index f01e60ec0b0f..6d86d1f7a7c2 100644 --- a/packages/google-auth/.gitignore +++ b/packages/google-auth/.gitignore @@ -41,6 +41,3 @@ pylintrc.test pytype_output/ .python-version -.DS_Store -cert_path -key_path \ No newline at end of file diff --git a/packages/google-auth/google/auth/iam.py b/packages/google-auth/google/auth/iam.py index 9e3887909a88..bd0500457819 100644 --- a/packages/google-auth/google/auth/iam.py +++ b/packages/google-auth/google/auth/iam.py @@ -28,7 +28,7 @@ from google.auth import crypt from google.auth import exceptions -_IAM_API_ROOT_URI = "https://iamcredentials.googleapis.com/v1" +_IAM_API_ROOT_URI = "https://iam.googleapis.com/v1" _SIGN_BLOB_URI = _IAM_API_ROOT_URI + "/projects/-/serviceAccounts/{}:signBlob?alt=json" @@ -71,7 +71,7 @@ def _make_signing_request(self, message): url = _SIGN_BLOB_URI.format(self._service_account_email) headers = {} body = json.dumps( - {"payload": base64.b64encode(message).decode("utf-8")} + {"bytesToSign": base64.b64encode(message).decode("utf-8")} ).encode("utf-8") self._credentials.before_request(self._request, method, url, headers) @@ -97,4 +97,4 @@ def key_id(self): @_helpers.copy_docstring(crypt.Signer) def sign(self, message): response = self._make_signing_request(message) - return base64.b64decode(response["signedBlob"]) + return base64.b64decode(response["signature"]) diff --git a/packages/google-auth/system_tests/test_service_account.py b/packages/google-auth/system_tests/test_service_account.py index 498b75b2233c..262ce84f5387 100644 --- a/packages/google-auth/system_tests/test_service_account.py +++ b/packages/google-auth/system_tests/test_service_account.py @@ -16,7 +16,6 @@ from google.auth import _helpers from google.auth import exceptions -from google.auth import iam from google.oauth2 import service_account @@ -47,19 +46,3 @@ def test_refresh_success(http_request, credentials, token_info): "https://www.googleapis.com/auth/userinfo.profile", ] ) - -def test_iam_signer(http_request, credentials): - credentials = credentials.with_scopes( - ["https://www.googleapis.com/auth/iam"] - ) - - # Verify iamcredentials signer. - signer = iam.Signer( - http_request, - credentials, - credentials.service_account_email - ) - - signed_blob = signer.sign("message") - - assert isinstance(signed_blob, bytes) diff --git a/packages/google-auth/tests/compute_engine/test_credentials.py b/packages/google-auth/tests/compute_engine/test_credentials.py index 4ee6536762b0..8c95e2437782 100644 --- a/packages/google-auth/tests/compute_engine/test_credentials.py +++ b/packages/google-auth/tests/compute_engine/test_credentials.py @@ -363,11 +363,11 @@ def test_with_target_audience_integration(self): signature = base64.b64encode(b"some-signature").decode("utf-8") responses.add( responses.POST, - "https://iamcredentials.googleapis.com/v1/projects/-/" - "serviceAccounts/service-account@example.com:signBlob?alt=json", + "https://iam.googleapis.com/v1/projects/-/serviceAccounts/" + "service-account@example.com:signBlob?alt=json", status=200, content_type="application/json", - json={"keyId": "some-key-id", "signedBlob": signature}, + json={"keyId": "some-key-id", "signature": signature}, ) id_token = "{}.{}.{}".format( @@ -477,11 +477,11 @@ def test_with_quota_project_integration(self): signature = base64.b64encode(b"some-signature").decode("utf-8") responses.add( responses.POST, - "https://iamcredentials.googleapis.com/v1/projects/-/" - "serviceAccounts/service-account@example.com:signBlob?alt=json", + "https://iam.googleapis.com/v1/projects/-/serviceAccounts/" + "service-account@example.com:signBlob?alt=json", status=200, content_type="application/json", - json={"keyId": "some-key-id", "signedBlob": signature}, + json={"keyId": "some-key-id", "signature": signature}, ) id_token = "{}.{}.{}".format( diff --git a/packages/google-auth/tests/test_iam.py b/packages/google-auth/tests/test_iam.py index e20eebaa7612..4367fe7a813b 100644 --- a/packages/google-auth/tests/test_iam.py +++ b/packages/google-auth/tests/test_iam.py @@ -81,7 +81,7 @@ def test_key_id(self): def test_sign_bytes(self): signature = b"DEADBEEF" encoded_signature = base64.b64encode(signature).decode("utf-8") - request = make_request(http_client.OK, data={"signedBlob": encoded_signature}) + request = make_request(http_client.OK, data={"signature": encoded_signature}) credentials = make_credentials() signer = iam.Signer(request, credentials, mock.sentinel.service_account_email) From 0ec28a1893048b6369fa06d73ecf83281f5eb2d8 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Fri, 17 Jul 2020 10:34:46 -0700 Subject: [PATCH 326/966] docs: add 3.8 support to contributing (#564) Release-As: 1.19.2 --- packages/google-auth/CONTRIBUTING.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/CONTRIBUTING.rst b/packages/google-auth/CONTRIBUTING.rst index c04a6c63e5de..ac65343ee12d 100644 --- a/packages/google-auth/CONTRIBUTING.rst +++ b/packages/google-auth/CONTRIBUTING.rst @@ -19,7 +19,7 @@ A few notes on making changes to ``google-auth-library-python``. using ``nox -s docgen``. - The change must work fully on the following CPython versions: 2.7, - 3.5, 3.6, 3.7 across macOS, Linux, and Windows. + 3.5, 3.6, 3.7, 3.8 across macOS, Linux, and Windows. - The codebase *must* have 100% test statement coverage after each commit. You can test coverage via ``nox -e cover``. From a0660133f9e7955225e918049641567e63166b4f Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Fri, 17 Jul 2020 10:51:59 -0700 Subject: [PATCH 327/966] chore: release 1.19.2 (#565) --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 593596e0c9ec..532d7d9259e3 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +### [1.19.2](https://www.github.com/googleapis/google-auth-library-python/compare/v1.19.1...v1.19.2) (2020-07-17) + + +### Bug fixes + +* Revert "fix: migrate signBlob to iamcredentials.googleapis.com" ([#563](https://www.github.com/googleapis/google-auth-library-python/issues/563)) ([a48b5b](https://www.github.com/googleapis/google-auth-library-python/commit/a48b5b9135b30ff06f1fe18dd9dbe92ffcf3a272)) + ### [1.19.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.19.0...v1.19.1) (2020-07-15) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 52033def6e15..629e059df37f 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -32,7 +32,7 @@ with io.open("README.rst", "r") as fh: long_description = fh.read() -version = "1.19.1" +version = "1.19.2" setup( name="google-auth", From 57cf50015130697dbe089089b4cf45441b99d730 Mon Sep 17 00:00:00 2001 From: Vaughan Hilts Date: Tue, 21 Jul 2020 16:25:51 -0400 Subject: [PATCH 328/966] feat: Add debug logging that can help with diagnosing auth lib. path (#473) Co-authored-by: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Co-authored-by: Tres Seaver --- packages/google-auth/google/auth/_default.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index 694033f3c42c..de81c5b2c6c2 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -155,10 +155,13 @@ def _get_gcloud_sdk_credentials(): """Gets the credentials and project ID from the Cloud SDK.""" from google.auth import _cloud_sdk + _LOGGER.debug("Checking Cloud SDK credentials as part of auth process...") + # Check if application default credentials exist. credentials_filename = _cloud_sdk.get_application_default_credentials_path() if not os.path.isfile(credentials_filename): + _LOGGER.debug("Cloud SDK credentials not found on disk; not using them") return None, None credentials, project_id = load_credentials_from_file(credentials_filename) @@ -174,6 +177,10 @@ def _get_explicit_environ_credentials(): variable.""" explicit_file = os.environ.get(environment_vars.CREDENTIALS) + _LOGGER.debug( + "Checking %s for explicit credentials as part of auth process...", explicit_file + ) + if explicit_file is not None: credentials, project_id = load_credentials_from_file( os.environ[environment_vars.CREDENTIALS] @@ -190,8 +197,10 @@ def _get_gae_credentials(): # While this library is normally bundled with app_engine, there are # some cases where it's not available, so we tolerate ImportError. try: + _LOGGER.debug("Checking for App Engine runtime as part of auth process...") import google.auth.app_engine as app_engine except ImportError: + _LOGGER.warning("Import of App Engine auth library failed.") return None, None try: @@ -199,6 +208,9 @@ def _get_gae_credentials(): project_id = app_engine.get_project_id() return credentials, project_id except EnvironmentError: + _LOGGER.debug( + "No App Engine library was found so cannot authentication via App Engine Identity Credentials." + ) return None, None @@ -215,6 +227,7 @@ def _get_gce_credentials(request=None): from google.auth import compute_engine from google.auth.compute_engine import _metadata except ImportError: + _LOGGER.warning("Import of Compute Engine auth library failed.") return None, None if request is None: @@ -229,6 +242,9 @@ def _get_gce_credentials(request=None): return compute_engine.Credentials(), project_id else: + _LOGGER.warning( + "Authentication failed using Compute Engine authentication due to unavailable metadata server." + ) return None, None From c8a7e30a66fad5cc72bc0abeb1bad7c22aa9a3e0 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Tue, 21 Jul 2020 13:44:03 -0700 Subject: [PATCH 329/966] test: add more tests for service account quota projects (#527) Follow up to #519 --- .../tests/oauth2/test_credentials.py | 2 ++ .../tests/oauth2/test_service_account.py | 25 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/packages/google-auth/tests/oauth2/test_credentials.py b/packages/google-auth/tests/oauth2/test_credentials.py index 69d9fbcea745..ceb8cdfd5ef1 100644 --- a/packages/google-auth/tests/oauth2/test_credentials.py +++ b/packages/google-auth/tests/oauth2/test_credentials.py @@ -309,6 +309,7 @@ def test_apply_with_quota_project_id(self): headers = {} creds.apply(headers) assert headers["x-goog-user-project"] == "quota-project-123" + assert "token" in headers["authorization"] def test_apply_with_no_quota_project_id(self): creds = credentials.Credentials( @@ -322,6 +323,7 @@ def test_apply_with_no_quota_project_id(self): headers = {} creds.apply(headers) assert "x-goog-user-project" not in headers + assert "token" in headers["authorization"] def test_with_quota_project(self): creds = credentials.Credentials( diff --git a/packages/google-auth/tests/oauth2/test_service_account.py b/packages/google-auth/tests/oauth2/test_service_account.py index 7f27dad3cf4e..4c75e371bd31 100644 --- a/packages/google-auth/tests/oauth2/test_service_account.py +++ b/packages/google-auth/tests/oauth2/test_service_account.py @@ -178,6 +178,31 @@ def test__make_authorization_grant_assertion_subject(self): payload = jwt.decode(token, PUBLIC_CERT_BYTES) assert payload["sub"] == subject + def test_apply_with_quota_project_id(self): + credentials = service_account.Credentials( + SIGNER, + self.SERVICE_ACCOUNT_EMAIL, + self.TOKEN_URI, + quota_project_id="quota-project-123", + ) + + headers = {} + credentials.apply(headers, token="token") + + assert headers["x-goog-user-project"] == "quota-project-123" + assert "token" in headers["authorization"] + + def test_apply_with_no_quota_project_id(self): + credentials = service_account.Credentials( + SIGNER, self.SERVICE_ACCOUNT_EMAIL, self.TOKEN_URI + ) + + headers = {} + credentials.apply(headers, token="token") + + assert "x-goog-user-project" not in headers + assert "token" in headers["authorization"] + @mock.patch("google.oauth2._client.jwt_grant", autospec=True) def test_refresh_success(self, jwt_grant): credentials = self.make_credentials() From b7aaf225fde2e2733880e1934dd118b4c34678a0 Mon Sep 17 00:00:00 2001 From: Vaughan Hilts Date: Tue, 21 Jul 2020 17:12:06 -0400 Subject: [PATCH 330/966] feat: Show the transport exception that happened for GCE Metadata (#474) Two primary motivators here: 1. Why obscure the exception? Sometimes it has some really useful information, such as the case when its being rate limited or throwing some internal error. Some of those responses have valuable information; and it's a public API so the contents of it are not a secret. 2. The metadata server should not be spewing failures that often; I think it would be good to know with `WARNING` when it happens since `WARNING` is the default and what a lot of people run with. When Metadata servers have issues overnight, we have no logs for those who left it as default. A good example of this is when the Metadata server is being rate limited; it would be good to know that's happening. Logs will help make that faster, though ideally of course maybe the library would have a result or something indicating the status of all checks (or something else so faults can be diagnosed faster?) but logs are cheap, easy and make diagnosing it a lot faster. It should be rare enough (correct me if I'm wrong?) that it should not be failing often enough except in the "resolution lag" case. --- .../google/auth/compute_engine/_metadata.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/google-auth/google/auth/compute_engine/_metadata.py b/packages/google-auth/google/auth/compute_engine/_metadata.py index cba426fb228a..fe821418e092 100644 --- a/packages/google-auth/google/auth/compute_engine/_metadata.py +++ b/packages/google-auth/google/auth/compute_engine/_metadata.py @@ -95,11 +95,13 @@ def ping(request, timeout=_METADATA_DEFAULT_TIMEOUT, retry_count=3): and metadata_flavor == _METADATA_FLAVOR_VALUE ) - except exceptions.TransportError: - _LOGGER.info( - "Compute Engine Metadata server unavailable on" "attempt %s of %s", + except exceptions.TransportError as e: + _LOGGER.warning( + "Compute Engine Metadata server unavailable on" + "attempt %s of %s. Reason: %s", retries + 1, retry_count, + e, ) retries += 1 @@ -144,11 +146,13 @@ def get(request, path, root=_METADATA_ROOT, recursive=False, retry_count=5): response = request(url=url, method="GET", headers=_METADATA_HEADERS) break - except exceptions.TransportError: - _LOGGER.info( - "Compute Engine Metadata server unavailable on" "attempt %s of %s", + except exceptions.TransportError as e: + _LOGGER.warning( + "Compute Engine Metadata server unavailable on" + "attempt %s of %s. Reason: %s", retries + 1, retry_count, + e, ) retries += 1 else: From 048a907adc57ade092df81091e4164a4d775c6c2 Mon Sep 17 00:00:00 2001 From: Yoshi Automation Bot Date: Tue, 21 Jul 2020 14:24:03 -0700 Subject: [PATCH 331/966] chore: update kokoro config (via synth) (#556) This PR was generated using Autosynth. :rainbow: Synth log will be available here: https://source.cloud.google.com/results/invocations/abd11f9f-00ea-4883-9679-79e339a91948/targets - [ ] To automatically regenerate this PR, check this box. Source-Link: https://github.com/googleapis/synthtool/commit/ffe10407ee2f261c799fb0d01bf32a8abc67ed1e --- packages/google-auth/.kokoro/build.sh | 13 +-- .../google-auth/.kokoro/continuous/common.cfg | 2 +- .../google-auth/.kokoro/presubmit/common.cfg | 6 +- .../.kokoro/samples/lint/common.cfg | 34 ++++++ .../.kokoro/samples/lint/continuous.cfg | 6 + .../.kokoro/samples/lint/periodic.cfg | 6 + .../.kokoro/samples/lint/presubmit.cfg | 6 + .../.kokoro/samples/python3.6/common.cfg | 34 ++++++ .../.kokoro/samples/python3.6/continuous.cfg | 7 ++ .../.kokoro/samples/python3.6/periodic.cfg | 6 + .../.kokoro/samples/python3.6/presubmit.cfg | 6 + .../.kokoro/samples/python3.7/common.cfg | 34 ++++++ .../.kokoro/samples/python3.7/continuous.cfg | 6 + .../.kokoro/samples/python3.7/periodic.cfg | 6 + .../.kokoro/samples/python3.7/presubmit.cfg | 6 + .../.kokoro/samples/python3.8/common.cfg | 34 ++++++ .../.kokoro/samples/python3.8/continuous.cfg | 6 + .../.kokoro/samples/python3.8/periodic.cfg | 6 + .../.kokoro/samples/python3.8/presubmit.cfg | 6 + packages/google-auth/.kokoro/test-samples.sh | 104 ++++++++++++++++++ packages/google-auth/synth.metadata | 6 +- 21 files changed, 321 insertions(+), 19 deletions(-) create mode 100644 packages/google-auth/.kokoro/samples/lint/common.cfg create mode 100644 packages/google-auth/.kokoro/samples/lint/continuous.cfg create mode 100644 packages/google-auth/.kokoro/samples/lint/periodic.cfg create mode 100644 packages/google-auth/.kokoro/samples/lint/presubmit.cfg create mode 100644 packages/google-auth/.kokoro/samples/python3.6/common.cfg create mode 100644 packages/google-auth/.kokoro/samples/python3.6/continuous.cfg create mode 100644 packages/google-auth/.kokoro/samples/python3.6/periodic.cfg create mode 100644 packages/google-auth/.kokoro/samples/python3.6/presubmit.cfg create mode 100644 packages/google-auth/.kokoro/samples/python3.7/common.cfg create mode 100644 packages/google-auth/.kokoro/samples/python3.7/continuous.cfg create mode 100644 packages/google-auth/.kokoro/samples/python3.7/periodic.cfg create mode 100644 packages/google-auth/.kokoro/samples/python3.7/presubmit.cfg create mode 100644 packages/google-auth/.kokoro/samples/python3.8/common.cfg create mode 100644 packages/google-auth/.kokoro/samples/python3.8/continuous.cfg create mode 100644 packages/google-auth/.kokoro/samples/python3.8/periodic.cfg create mode 100644 packages/google-auth/.kokoro/samples/python3.8/presubmit.cfg create mode 100755 packages/google-auth/.kokoro/test-samples.sh diff --git a/packages/google-auth/.kokoro/build.sh b/packages/google-auth/.kokoro/build.sh index e80311313e4b..3ce87f39d037 100755 --- a/packages/google-auth/.kokoro/build.sh +++ b/packages/google-auth/.kokoro/build.sh @@ -1,5 +1,4 @@ #!/bin/bash - # Copyright 2018 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,19 +24,10 @@ export PYTHONUNBUFFERED=1 env | grep KOKORO # Setup service account credentials. - -# add creds to gfile dir export GOOGLE_APPLICATION_CREDENTIALS=${KOKORO_GFILE_DIR}/service-account.json # Setup project id. -export PROJECT_ID=$(cat "${KOKORO_GFILE_DIR}/project-id.txt") - -# Activate gcloud with service account credentials -gcloud auth activate-service-account --key-file=$GOOGLE_APPLICATION_CREDENTIALS -gcloud config set project $PROJECT_ID - -# Decrypt system test secrets -./scripts/decrypt-secrets.sh +export PROJECT_ID=$(cat "${KOKORO_GFILE_DIR}/project-id.json") # Remove old nox python3.6 -m pip uninstall --yes --quiet nox-automation @@ -47,4 +37,3 @@ python3.6 -m pip install --upgrade --quiet nox python3.6 -m nox --version python3.6 -m nox -python3.6 -m nox -f system_tests/noxfile.py \ No newline at end of file diff --git a/packages/google-auth/.kokoro/continuous/common.cfg b/packages/google-auth/.kokoro/continuous/common.cfg index 10910e357558..c587b41047d0 100644 --- a/packages/google-auth/.kokoro/continuous/common.cfg +++ b/packages/google-auth/.kokoro/continuous/common.cfg @@ -11,7 +11,7 @@ action { gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" # Download resources for system tests (service account key, etc.) -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-auth-library-python" +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-cloud-python" # Use the trampoline script to run in docker. build_file: "google-auth-library-python/.kokoro/trampoline.sh" diff --git a/packages/google-auth/.kokoro/presubmit/common.cfg b/packages/google-auth/.kokoro/presubmit/common.cfg index 7dbee1cfeabf..c587b41047d0 100644 --- a/packages/google-auth/.kokoro/presubmit/common.cfg +++ b/packages/google-auth/.kokoro/presubmit/common.cfg @@ -7,11 +7,11 @@ action { } } -# Download trampoline resources. These will be in ${KOKORO_GFILE_DIR} +# Download trampoline resources. gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" -# Download resources for tests -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-auth-library-python" +# Download resources for system tests (service account key, etc.) +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-cloud-python" # Use the trampoline script to run in docker. build_file: "google-auth-library-python/.kokoro/trampoline.sh" diff --git a/packages/google-auth/.kokoro/samples/lint/common.cfg b/packages/google-auth/.kokoro/samples/lint/common.cfg new file mode 100644 index 000000000000..61fa5217bb9d --- /dev/null +++ b/packages/google-auth/.kokoro/samples/lint/common.cfg @@ -0,0 +1,34 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Build logs will be here +action { + define_artifacts { + regex: "**/*sponge_log.xml" + } +} + +# Specify which tests to run +env_vars: { + key: "RUN_TESTS_SESSION" + value: "lint" +} + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/google-auth-library-python/.kokoro/test-samples.sh" +} + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/python-samples-testing-docker" +} + +# Download secrets for samples +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/python-docs-samples" + +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" + +# Use the trampoline script to run in docker. +build_file: "google-auth-library-python/.kokoro/trampoline.sh" \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/lint/continuous.cfg b/packages/google-auth/.kokoro/samples/lint/continuous.cfg new file mode 100644 index 000000000000..a1c8d9759c88 --- /dev/null +++ b/packages/google-auth/.kokoro/samples/lint/continuous.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/lint/periodic.cfg b/packages/google-auth/.kokoro/samples/lint/periodic.cfg new file mode 100644 index 000000000000..50fec9649732 --- /dev/null +++ b/packages/google-auth/.kokoro/samples/lint/periodic.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "False" +} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/lint/presubmit.cfg b/packages/google-auth/.kokoro/samples/lint/presubmit.cfg new file mode 100644 index 000000000000..a1c8d9759c88 --- /dev/null +++ b/packages/google-auth/.kokoro/samples/lint/presubmit.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.6/common.cfg b/packages/google-auth/.kokoro/samples/python3.6/common.cfg new file mode 100644 index 000000000000..792bc4bbe3ae --- /dev/null +++ b/packages/google-auth/.kokoro/samples/python3.6/common.cfg @@ -0,0 +1,34 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Build logs will be here +action { + define_artifacts { + regex: "**/*sponge_log.xml" + } +} + +# Specify which tests to run +env_vars: { + key: "RUN_TESTS_SESSION" + value: "py-3.6" +} + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/google-auth-library-python/.kokoro/test-samples.sh" +} + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/python-samples-testing-docker" +} + +# Download secrets for samples +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/python-docs-samples" + +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" + +# Use the trampoline script to run in docker. +build_file: "google-auth-library-python/.kokoro/trampoline.sh" \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.6/continuous.cfg b/packages/google-auth/.kokoro/samples/python3.6/continuous.cfg new file mode 100644 index 000000000000..7218af1499e5 --- /dev/null +++ b/packages/google-auth/.kokoro/samples/python3.6/continuous.cfg @@ -0,0 +1,7 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} + diff --git a/packages/google-auth/.kokoro/samples/python3.6/periodic.cfg b/packages/google-auth/.kokoro/samples/python3.6/periodic.cfg new file mode 100644 index 000000000000..50fec9649732 --- /dev/null +++ b/packages/google-auth/.kokoro/samples/python3.6/periodic.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "False" +} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.6/presubmit.cfg b/packages/google-auth/.kokoro/samples/python3.6/presubmit.cfg new file mode 100644 index 000000000000..a1c8d9759c88 --- /dev/null +++ b/packages/google-auth/.kokoro/samples/python3.6/presubmit.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.7/common.cfg b/packages/google-auth/.kokoro/samples/python3.7/common.cfg new file mode 100644 index 000000000000..209f6cef97db --- /dev/null +++ b/packages/google-auth/.kokoro/samples/python3.7/common.cfg @@ -0,0 +1,34 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Build logs will be here +action { + define_artifacts { + regex: "**/*sponge_log.xml" + } +} + +# Specify which tests to run +env_vars: { + key: "RUN_TESTS_SESSION" + value: "py-3.7" +} + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/google-auth-library-python/.kokoro/test-samples.sh" +} + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/python-samples-testing-docker" +} + +# Download secrets for samples +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/python-docs-samples" + +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" + +# Use the trampoline script to run in docker. +build_file: "google-auth-library-python/.kokoro/trampoline.sh" \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.7/continuous.cfg b/packages/google-auth/.kokoro/samples/python3.7/continuous.cfg new file mode 100644 index 000000000000..a1c8d9759c88 --- /dev/null +++ b/packages/google-auth/.kokoro/samples/python3.7/continuous.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.7/periodic.cfg b/packages/google-auth/.kokoro/samples/python3.7/periodic.cfg new file mode 100644 index 000000000000..50fec9649732 --- /dev/null +++ b/packages/google-auth/.kokoro/samples/python3.7/periodic.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "False" +} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.7/presubmit.cfg b/packages/google-auth/.kokoro/samples/python3.7/presubmit.cfg new file mode 100644 index 000000000000..a1c8d9759c88 --- /dev/null +++ b/packages/google-auth/.kokoro/samples/python3.7/presubmit.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.8/common.cfg b/packages/google-auth/.kokoro/samples/python3.8/common.cfg new file mode 100644 index 000000000000..b0095dabd152 --- /dev/null +++ b/packages/google-auth/.kokoro/samples/python3.8/common.cfg @@ -0,0 +1,34 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Build logs will be here +action { + define_artifacts { + regex: "**/*sponge_log.xml" + } +} + +# Specify which tests to run +env_vars: { + key: "RUN_TESTS_SESSION" + value: "py-3.8" +} + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/google-auth-library-python/.kokoro/test-samples.sh" +} + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/python-samples-testing-docker" +} + +# Download secrets for samples +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/python-docs-samples" + +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" + +# Use the trampoline script to run in docker. +build_file: "google-auth-library-python/.kokoro/trampoline.sh" \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.8/continuous.cfg b/packages/google-auth/.kokoro/samples/python3.8/continuous.cfg new file mode 100644 index 000000000000..a1c8d9759c88 --- /dev/null +++ b/packages/google-auth/.kokoro/samples/python3.8/continuous.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.8/periodic.cfg b/packages/google-auth/.kokoro/samples/python3.8/periodic.cfg new file mode 100644 index 000000000000..50fec9649732 --- /dev/null +++ b/packages/google-auth/.kokoro/samples/python3.8/periodic.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "False" +} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.8/presubmit.cfg b/packages/google-auth/.kokoro/samples/python3.8/presubmit.cfg new file mode 100644 index 000000000000..a1c8d9759c88 --- /dev/null +++ b/packages/google-auth/.kokoro/samples/python3.8/presubmit.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/test-samples.sh b/packages/google-auth/.kokoro/test-samples.sh new file mode 100755 index 000000000000..f4426f67a99d --- /dev/null +++ b/packages/google-auth/.kokoro/test-samples.sh @@ -0,0 +1,104 @@ +#!/bin/bash +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +# `-e` enables the script to automatically fail when a command fails +# `-o pipefail` sets the exit code to the rightmost comment to exit with a non-zero +set -eo pipefail +# Enables `**` to include files nested inside sub-folders +shopt -s globstar + +cd github/google-auth-library-python + +# Run periodic samples tests at latest release +if [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"periodic"* ]]; then + LATEST_RELEASE=$(git describe --abbrev=0 --tags) + git checkout $LATEST_RELEASE +fi + +# Disable buffering, so that the logs stream through. +export PYTHONUNBUFFERED=1 + +# Debug: show build environment +env | grep KOKORO + +# Install nox +python3.6 -m pip install --upgrade --quiet nox + +# Use secrets acessor service account to get secrets +if [[ -f "${KOKORO_GFILE_DIR}/secrets_viewer_service_account.json" ]]; then + gcloud auth activate-service-account \ + --key-file="${KOKORO_GFILE_DIR}/secrets_viewer_service_account.json" \ + --project="cloud-devrel-kokoro-resources" +fi + +# This script will create 3 files: +# - testing/test-env.sh +# - testing/service-account.json +# - testing/client-secrets.json +./scripts/decrypt-secrets.sh + +source ./testing/test-env.sh +export GOOGLE_APPLICATION_CREDENTIALS=$(pwd)/testing/service-account.json + +# For cloud-run session, we activate the service account for gcloud sdk. +gcloud auth activate-service-account \ + --key-file "${GOOGLE_APPLICATION_CREDENTIALS}" + +export GOOGLE_CLIENT_SECRETS=$(pwd)/testing/client-secrets.json + +echo -e "\n******************** TESTING PROJECTS ********************" + +# Switch to 'fail at end' to allow all tests to complete before exiting. +set +e +# Use RTN to return a non-zero value if the test fails. +RTN=0 +ROOT=$(pwd) +# Find all requirements.txt in the samples directory (may break on whitespace). +for file in samples/**/requirements.txt; do + cd "$ROOT" + # Navigate to the project folder. + file=$(dirname "$file") + cd "$file" + + echo "------------------------------------------------------------" + echo "- testing $file" + echo "------------------------------------------------------------" + + # Use nox to execute the tests for the project. + python3.6 -m nox -s "$RUN_TESTS_SESSION" + EXIT=$? + + # If this is a periodic build, send the test log to the Build Cop Bot. + # See https://github.com/googleapis/repo-automation-bots/tree/master/packages/buildcop. + if [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"periodic"* ]]; then + chmod +x $KOKORO_GFILE_DIR/linux_amd64/buildcop + $KOKORO_GFILE_DIR/linux_amd64/buildcop + fi + + if [[ $EXIT -ne 0 ]]; then + RTN=1 + echo -e "\n Testing failed: Nox returned a non-zero exit code. \n" + else + echo -e "\n Testing completed.\n" + fi + +done +cd "$ROOT" + +# Workaround for Kokoro permissions issue: delete secrets +rm testing/{test-env.sh,client-secrets.json,service-account.json} + +exit "$RTN" \ No newline at end of file diff --git a/packages/google-auth/synth.metadata b/packages/google-auth/synth.metadata index 0efbe1e90416..901a2cb9b728 100644 --- a/packages/google-auth/synth.metadata +++ b/packages/google-auth/synth.metadata @@ -3,15 +3,15 @@ { "git": { "name": ".", - "remote": "git@github.com:googleapis/google-auth-library-python", - "sha": "aab4f2fdb2cfa598397026865ccb270a05c38cc4" + "remote": "https://github.com/googleapis/google-auth-library-python.git", + "sha": "218a159f646c81021c890b92f9cff003aed949a8" } }, { "git": { "name": "synthtool", "remote": "https://github.com/googleapis/synthtool.git", - "sha": "71b8a272549c06b5768d00fa48d3ae990e871bec" + "sha": "ffe10407ee2f261c799fb0d01bf32a8abc67ed1e" } } ] From db4a0fb2f39fc7bfd3b06e17dfc11c429cb91a3c Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Tue, 21 Jul 2020 23:42:03 +0200 Subject: [PATCH 332/966] chore(deps): update dependency rsa to <4.6 (#544) This PR contains the following updates: | Package | Update | Change | |---|---|---| | [rsa](https://stuvel.eu/rsa) | minor | `<4.1` -> `<4.7` | --- ### Renovate configuration :date: **Schedule**: At any time (no schedule defined). :vertical_traffic_light: **Automerge**: Disabled by config. Please merge this manually once you are satisfied. :recycle: **Rebasing**: Renovate will not automatically rebase this PR, because other commits have been found. :no_bell: **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [WhiteSource Renovate](https://renovate.whitesourcesoftware.com). View repository job log [here](https://app.renovatebot.com/dashboard#googleapis/google-auth-library-python). --- packages/google-auth/setup.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 629e059df37f..1f5c6861cad2 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -21,9 +21,10 @@ DEPENDENCIES = ( "cachetools>=2.0.0,<5.0", "pyasn1-modules>=0.2.1", - # rsa >= 4.1 no longer supports python 2 https://github.com/sybrenstuvel/python-rsa/issues/152 - 'rsa<4.1; python_version < "3"', - 'rsa>=3.1.4,<5; python_version >= "3"', + # rsa==4.5 is the last version to support 2.7 + # https://github.com/sybrenstuvel/python-rsa/issues/152#issuecomment-643470233 + 'rsa<4.6; python_version < "3.5"', + 'rsa>=3.1.4,<5; python_version >= "3.5"', "setuptools>=40.3.0", "six>=1.9.0", ) From b6edb1a710da8d2a1e2eb89b102d4d54e008ab0e Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Wed, 22 Jul 2020 20:17:28 -0400 Subject: [PATCH 333/966] feat(packaging): add support for Python 3.8 (#569) Release-As: 1.20.0 Closes #568 --- .../google-auth/docs/reference/google.auth.compute_engine.rst | 1 + packages/google-auth/docs/reference/google.auth.crypt.rst | 1 + packages/google-auth/docs/reference/google.auth.rst | 2 ++ packages/google-auth/docs/reference/google.auth.transport.rst | 2 ++ packages/google-auth/docs/reference/google.oauth2.rst | 1 + packages/google-auth/docs/reference/google.rst | 1 + packages/google-auth/noxfile.py | 2 +- packages/google-auth/setup.py | 1 + 8 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/docs/reference/google.auth.compute_engine.rst b/packages/google-auth/docs/reference/google.auth.compute_engine.rst index 38785c803227..819248cb5fab 100644 --- a/packages/google-auth/docs/reference/google.auth.compute_engine.rst +++ b/packages/google-auth/docs/reference/google.auth.compute_engine.rst @@ -10,5 +10,6 @@ Submodules ---------- .. toctree:: + :maxdepth: 4 google.auth.compute_engine.credentials diff --git a/packages/google-auth/docs/reference/google.auth.crypt.rst b/packages/google-auth/docs/reference/google.auth.crypt.rst index be142f428912..ff38fa34ea84 100644 --- a/packages/google-auth/docs/reference/google.auth.crypt.rst +++ b/packages/google-auth/docs/reference/google.auth.crypt.rst @@ -10,6 +10,7 @@ Submodules ---------- .. toctree:: + :maxdepth: 4 google.auth.crypt.base google.auth.crypt.es256 diff --git a/packages/google-auth/docs/reference/google.auth.rst b/packages/google-auth/docs/reference/google.auth.rst index f6ea073c55e0..cfcf70357e30 100644 --- a/packages/google-auth/docs/reference/google.auth.rst +++ b/packages/google-auth/docs/reference/google.auth.rst @@ -10,6 +10,7 @@ Subpackages ----------- .. toctree:: + :maxdepth: 4 google.auth.compute_engine google.auth.crypt @@ -19,6 +20,7 @@ Submodules ---------- .. toctree:: + :maxdepth: 4 google.auth.app_engine google.auth.credentials diff --git a/packages/google-auth/docs/reference/google.auth.transport.rst b/packages/google-auth/docs/reference/google.auth.transport.rst index 48e2e0551478..89218632becd 100644 --- a/packages/google-auth/docs/reference/google.auth.transport.rst +++ b/packages/google-auth/docs/reference/google.auth.transport.rst @@ -10,7 +10,9 @@ Submodules ---------- .. toctree:: + :maxdepth: 4 google.auth.transport.grpc + google.auth.transport.mtls google.auth.transport.requests google.auth.transport.urllib3 diff --git a/packages/google-auth/docs/reference/google.oauth2.rst b/packages/google-auth/docs/reference/google.oauth2.rst index 4f1df071f5d5..1ac9c7320567 100644 --- a/packages/google-auth/docs/reference/google.oauth2.rst +++ b/packages/google-auth/docs/reference/google.oauth2.rst @@ -10,6 +10,7 @@ Submodules ---------- .. toctree:: + :maxdepth: 4 google.oauth2.credentials google.oauth2.id_token diff --git a/packages/google-auth/docs/reference/google.rst b/packages/google-auth/docs/reference/google.rst index 4b1e0853787d..f122ca13c85b 100644 --- a/packages/google-auth/docs/reference/google.rst +++ b/packages/google-auth/docs/reference/google.rst @@ -10,6 +10,7 @@ Subpackages ----------- .. toctree:: + :maxdepth: 4 google.auth google.oauth2 diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index bcea1fbc8cd2..c39f27c478f6 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -64,7 +64,7 @@ def blacken(session): session.run("black", *BLACK_PATHS) -@nox.session(python=["2.7", "3.5", "3.6", "3.7"]) +@nox.session(python=["2.7", "3.5", "3.6", "3.7", "3.8"]) def unit(session): session.install(*TEST_DEPENDENCIES) session.install(".") diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 1f5c6861cad2..6a70fca180b6 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -56,6 +56,7 @@ "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", From d582f63b532dbacfd8a84505cd4f606f394f4774 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 28 Jul 2020 11:53:42 -0700 Subject: [PATCH 334/966] chore: release 1.20.0 (#567) --- packages/google-auth/CHANGELOG.md | 9 +++++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 532d7d9259e3..a7e71f265c98 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,15 @@ [1]: https://pypi.org/project/google-auth/#history +## [1.20.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.19.2...v1.20.0) (2020-07-23) + + +### Features + +* Add debug logging that can help with diagnosing auth lib. path ([#473](https://www.github.com/googleapis/google-auth-library-python/issues/473)) ([ecd88d4](https://www.github.com/googleapis/google-auth-library-python/commit/ecd88d4f0efc5c619ebd3e3fa7e2472f11c63452)) +* Show the transport exception that happened for GCE Metadata ([#474](https://www.github.com/googleapis/google-auth-library-python/issues/474)) ([23919bb](https://www.github.com/googleapis/google-auth-library-python/commit/23919bb60e5f9d9b73644e9a2e127d4d1dd68e8c)) +* **packaging:** add support for Python 3.8 ([#569](https://www.github.com/googleapis/google-auth-library-python/issues/569)) ([1aad54a](https://www.github.com/googleapis/google-auth-library-python/commit/1aad54af6b1d5da73d7471cdbfaf0d0b37c5fde6)), closes [#568](https://www.github.com/googleapis/google-auth-library-python/issues/568) + ### [1.19.2](https://www.github.com/googleapis/google-auth-library-python/compare/v1.19.1...v1.19.2) (2020-07-17) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 6a70fca180b6..5b192b10af95 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -33,7 +33,7 @@ with io.open("README.rst", "r") as fh: long_description = fh.read() -version = "1.19.2" +version = "1.20.0" setup( name="google-auth", From 9b1f56a091bd8a579b67fdb0d0b124ee588a3221 Mon Sep 17 00:00:00 2001 From: Kenji Imamula Date: Fri, 31 Jul 2020 05:40:02 +0900 Subject: [PATCH 335/966] fix: set Content-Type header in the request to signBlob API to avoid Invalid JSON payload error (#439) `auth.create_custom_token()` results in an error: ``` Failed to sign custom token. Error calling the IAM signBytes API:{ (...) "error": { "code": 400, "message": "Invalid JSON payload received. Unknown name \"{\"bytesToSign\": \"...\"}\": Cannot bind query parameter. Field '{\"bytesToSign\": \"...\"}' could not be found in request message.", "status": "INVALID_ARGUMENT", "details": [ { "@type": "type.googleapis.com/google.rpc.BadRequest", "fieldViolations": [ { "description": "Invalid JSON payload received. Unknown name \"{\"bytesToSign\": \"...\"}\": Cannot bind query parameter. Field '{\"bytesToSign\": \"...\"}' could not be found in request message." } ] } ] } } ``` I have confirmed setting `'Content-Type': 'application/json'` header resolves the problem. --- packages/google-auth/google/auth/iam.py | 2 +- packages/google-auth/tests/test_iam.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/iam.py b/packages/google-auth/google/auth/iam.py index bd0500457819..d83b25180afc 100644 --- a/packages/google-auth/google/auth/iam.py +++ b/packages/google-auth/google/auth/iam.py @@ -69,7 +69,7 @@ def _make_signing_request(self, message): method = "POST" url = _SIGN_BLOB_URI.format(self._service_account_email) - headers = {} + headers = {"Content-Type": "application/json"} body = json.dumps( {"bytesToSign": base64.b64encode(message).decode("utf-8")} ).encode("utf-8") diff --git a/packages/google-auth/tests/test_iam.py b/packages/google-auth/tests/test_iam.py index 4367fe7a813b..cb2c26f7335d 100644 --- a/packages/google-auth/tests/test_iam.py +++ b/packages/google-auth/tests/test_iam.py @@ -89,6 +89,8 @@ def test_sign_bytes(self): returned_signature = signer.sign("123") assert returned_signature == signature + kwargs = request.call_args.kwargs + assert kwargs["headers"]["Content-Type"] == "application/json" def test_sign_bytes_failure(self): request = make_request(http_client.UNAUTHORIZED) From 81b0fbcd62de71e0437bf43c7eef2f2b1b191ca3 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Thu, 6 Aug 2020 16:50:06 -0700 Subject: [PATCH 336/966] fix: reduce refresh clock skew to 10 seconds (#581) --- packages/google-auth/google/auth/_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/_helpers.py b/packages/google-auth/google/auth/_helpers.py index ecb88ffda455..21c987a73277 100644 --- a/packages/google-auth/google/auth/_helpers.py +++ b/packages/google-auth/google/auth/_helpers.py @@ -22,7 +22,7 @@ from six.moves import urllib -CLOCK_SKEW_SECS = 300 # 5 minutes in seconds +CLOCK_SKEW_SECS = 10 # 10 seconds CLOCK_SKEW = datetime.timedelta(seconds=CLOCK_SKEW_SECS) From 6e89f29a263fcb4e20cd08770a93cd84b47ae2a5 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 6 Aug 2020 16:59:19 -0700 Subject: [PATCH 337/966] chore: release 1.20.1 (#582) * chore: updated CHANGELOG.md [ci skip] * chore: updated setup.cfg [ci skip] * chore: updated setup.py Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 8 ++++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index a7e71f265c98..56faf8efbd67 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,14 @@ [1]: https://pypi.org/project/google-auth/#history +### [1.20.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.20.0...v1.20.1) (2020-08-06) + + +### Bug Fixes + +* reduce refresh clock skew to 10 seconds ([#581](https://www.github.com/googleapis/google-auth-library-python/issues/581)) ([42321ba](https://www.github.com/googleapis/google-auth-library-python/commit/42321bafd38a8bd806f4d01bfa0eda3b5a961667)) +* set Content-Type header in the request to signBlob API to avoid Invalid JSON payload error ([#439](https://www.github.com/googleapis/google-auth-library-python/issues/439)) ([20f82e2](https://www.github.com/googleapis/google-auth-library-python/commit/20f82e22b7e8c6c7fdd29e08eaf7b4cf2abdcf37)) + ## [1.20.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.19.2...v1.20.0) (2020-07-23) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 5b192b10af95..cfa7b0a8522c 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -33,7 +33,7 @@ with io.open("README.rst", "r") as fh: long_description = fh.read() -version = "1.20.0" +version = "1.20.1" setup( name="google-auth", From 04448ee6e187f977fb93a12df4a6741c5daf5a66 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Thu, 27 Aug 2020 14:05:30 -0700 Subject: [PATCH 338/966] feat: add GOOGLE_API_USE_CLIENT_CERTIFICATE support (#592) --- .../reference/google.auth.transport.mtls.rst | 7 + .../google/auth/environment_vars.py | 6 + .../google-auth/google/auth/transport/grpc.py | 57 ++++++-- .../google/auth/transport/requests.py | 26 +++- .../google/auth/transport/urllib3.py | 27 +++- .../system_tests/test_mtls_http.py | 21 ++- .../google-auth/tests/transport/test_grpc.py | 137 ++++++++++++++++-- .../tests/transport/test_requests.py | 49 ++++++- .../tests/transport/test_urllib3.py | 50 ++++++- 9 files changed, 326 insertions(+), 54 deletions(-) create mode 100644 packages/google-auth/docs/reference/google.auth.transport.mtls.rst diff --git a/packages/google-auth/docs/reference/google.auth.transport.mtls.rst b/packages/google-auth/docs/reference/google.auth.transport.mtls.rst new file mode 100644 index 000000000000..11b50e23cb78 --- /dev/null +++ b/packages/google-auth/docs/reference/google.auth.transport.mtls.rst @@ -0,0 +1,7 @@ +google.auth.transport.mtls module +================================= + +.. automodule:: google.auth.transport.mtls + :members: + :inherited-members: + :show-inheritance: diff --git a/packages/google-auth/google/auth/environment_vars.py b/packages/google-auth/google/auth/environment_vars.py index 9c1367f5221e..46a8926646b9 100644 --- a/packages/google-auth/google/auth/environment_vars.py +++ b/packages/google-auth/google/auth/environment_vars.py @@ -53,3 +53,9 @@ GCE_METADATA_IP = "GCE_METADATA_IP" """Environment variable providing an alternate ip:port to be used for ip-only GCE metadata requests.""" + +GOOGLE_API_USE_CLIENT_CERTIFICATE = "GOOGLE_API_USE_CLIENT_CERTIFICATE" +"""Environment variable controlling whether to use client certificate or not. + +The default value is false. Users have to explicitly set this value to true +in order to use client certificate to establish a mutual TLS channel.""" diff --git a/packages/google-auth/google/auth/transport/grpc.py b/packages/google-auth/google/auth/transport/grpc.py index 13234a331e9c..ab7d0dbf8fc3 100644 --- a/packages/google-auth/google/auth/transport/grpc.py +++ b/packages/google-auth/google/auth/transport/grpc.py @@ -17,9 +17,11 @@ from __future__ import absolute_import import logging +import os import six +from google.auth import environment_vars from google.auth import exceptions from google.auth.transport import _mtls_helper @@ -96,6 +98,9 @@ def secure_authorized_channel( This creates a channel with SSL and :class:`AuthMetadataPlugin`. This channel can be used to create a stub that can make authorized requests. + Users can configure client certificate or rely on device certificates to + establish a mutual TLS channel, if the `GOOGLE_API_USE_CLIENT_CERTIFICATE` + variable is explicitly set to `true`. Example:: @@ -138,7 +143,9 @@ def secure_authorized_channel( ssl_credentials=regular_ssl_credentials) Option 2: create a mutual TLS channel by calling a callback which returns - the client side certificate and the key:: + the client side certificate and the key (Note that + `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable must be explicitly + set to `true`):: def my_client_cert_callback(): code_to_load_client_cert_and_key() @@ -155,7 +162,9 @@ def my_client_cert_callback(): Option 3: use application default SSL credentials. It searches and uses the command in a context aware metadata file, which is available on devices - with endpoint verification support. + with endpoint verification support (Note that + `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable must be explicitly + set to `true`). See https://cloud.google.com/endpoint-verification/docs/overview:: try: @@ -174,7 +183,8 @@ def my_client_cert_callback(): ssl_credentials=default_ssl_credentials) Option 4: not setting ssl_credentials and client_cert_callback. For devices - without endpoint verification support, a regular TLS channel is created; + without endpoint verification support or `GOOGLE_API_USE_CLIENT_CERTIFICATE` + environment variable is not `true`, a regular TLS channel is created; otherwise, a mutual TLS channel is created, however, the call should be wrapped in a try/except block in case of malformed context aware metadata. @@ -205,13 +215,15 @@ def my_client_cert_callback(): This argument is mutually exclusive with client_cert_callback; providing both will raise an exception. If ssl_credentials and client_cert_callback are None, application - default SSL credentials will be used. + default SSL credentials are used if `GOOGLE_API_USE_CLIENT_CERTIFICATE` + environment variable is explicitly set to `true`, otherwise one way TLS + SSL credentials are used. client_cert_callback (Callable[[], (bytes, bytes)]): Optional callback function to obtain client certicate and key for mutual TLS connection. This argument is mutually exclusive with ssl_credentials; providing both will raise an exception. - If ssl_credentials and client_cert_callback are None, application - default SSL credentials will be used. + This argument does nothing unless `GOOGLE_API_USE_CLIENT_CERTIFICATE` + environment variable is explicitly set to `true`. kwargs: Additional arguments to pass to :func:`grpc.secure_channel`. Returns: @@ -235,16 +247,21 @@ def my_client_cert_callback(): # If SSL credentials are not explicitly set, try client_cert_callback and ADC. if not ssl_credentials: - if client_cert_callback: + use_client_cert = os.getenv( + environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE, "false" + ) + if use_client_cert == "true" and client_cert_callback: # Use the callback if provided. cert, key = client_cert_callback() ssl_credentials = grpc.ssl_channel_credentials( certificate_chain=cert, private_key=key ) - else: + elif use_client_cert == "true": # Use application default SSL credentials. adc_ssl_credentils = SslCredentials() ssl_credentials = adc_ssl_credentils.ssl_credentials + else: + ssl_credentials = grpc.ssl_channel_credentials() # Combine the ssl credentials and the authorization credentials. composite_credentials = grpc.composite_channel_credentials( @@ -257,17 +274,29 @@ def my_client_cert_callback(): class SslCredentials: """Class for application default SSL credentials. - For devices with endpoint verification support, a device certificate will be - automatically loaded and mutual TLS will be established. + The behavior is controlled by `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment + variable whose default value is `false`. Client certificate will not be used + unless the environment variable is explicitly set to `true`. See + https://google.aip.dev/auth/4114 + + If the environment variable is `true`, then for devices with endpoint verification + support, a device certificate will be automatically loaded and mutual TLS will + be established. See https://cloud.google.com/endpoint-verification/docs/overview. """ def __init__(self): - # Load client SSL credentials. - metadata_path = _mtls_helper._check_dca_metadata_path( - _mtls_helper.CONTEXT_AWARE_METADATA_PATH + use_client_cert = os.getenv( + environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE, "false" ) - self._is_mtls = metadata_path is not None + if use_client_cert != "true": + self._is_mtls = False + else: + # Load client SSL credentials. + metadata_path = _mtls_helper._check_dca_metadata_path( + _mtls_helper.CONTEXT_AWARE_METADATA_PATH + ) + self._is_mtls = metadata_path is not None @property def ssl_credentials(self): diff --git a/packages/google-auth/google/auth/transport/requests.py b/packages/google-auth/google/auth/transport/requests.py index 4f5af7dea29e..9a2f3afc7966 100644 --- a/packages/google-auth/google/auth/transport/requests.py +++ b/packages/google-auth/google/auth/transport/requests.py @@ -19,6 +19,7 @@ import functools import logging import numbers +import os import time try: @@ -40,6 +41,7 @@ ) # pylint: disable=ungrouped-imports import six # pylint: disable=ungrouped-imports +from google.auth import environment_vars from google.auth import exceptions from google.auth import transport import google.auth.transport._mtls_helper @@ -249,13 +251,18 @@ class AuthorizedSession(requests.Session): credentials' headers to the request and refreshing credentials as needed. This class also supports mutual TLS via :meth:`configure_mtls_channel` - method. If client_cert_callback is provided, client certificate and private + method. In order to use this method, the `GOOGLE_API_USE_CLIENT_CERTIFICATE` + environment variable must be explicitly set to `true`, otherwise it does + nothing. Assume the environment is set to `true`, the method behaves in the + following manner: + If client_cert_callback is provided, client certificate and private key are loaded using the callback; if client_cert_callback is None, application default SSL credentials will be used. Exceptions are raised if there are problems with the certificate, private key, or the loading process, so it should be called within a try/except block. - First we create an :class:`AuthorizedSession` instance and specify the endpoints:: + First we set the environment variable to `true`, then create an :class:`AuthorizedSession` + instance and specify the endpoints:: regular_endpoint = 'https://pubsub.googleapis.com/v1/projects/{my_project_id}/topics' mtls_endpoint = 'https://pubsub.mtls.googleapis.com/v1/projects/{my_project_id}/topics' @@ -343,9 +350,11 @@ def __init__( def configure_mtls_channel(self, client_cert_callback=None): """Configure the client certificate and key for SSL connection. - If client certificate and key are successfully obtained (from the given - client_cert_callback or from application default SSL credentials), a - :class:`_MutualTlsAdapter` instance will be mounted to "https://" prefix. + The function does nothing unless `GOOGLE_API_USE_CLIENT_CERTIFICATE` is + explicitly set to `true`. In this case if client certificate and key are + successfully obtained (from the given client_cert_callback or from application + default SSL credentials), a :class:`_MutualTlsAdapter` instance will be mounted + to "https://" prefix. Args: client_cert_callback (Optional[Callable[[], (bytes, bytes)]]): @@ -358,6 +367,13 @@ def configure_mtls_channel(self, client_cert_callback=None): google.auth.exceptions.MutualTLSChannelError: If mutual TLS channel creation failed for any reason. """ + use_client_cert = os.getenv( + environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE, "false" + ) + if use_client_cert != "true": + self._is_mtls = False + return + try: import OpenSSL except ImportError as caught_exc: diff --git a/packages/google-auth/google/auth/transport/urllib3.py b/packages/google-auth/google/auth/transport/urllib3.py index 3742f1a419e7..209fc51bc549 100644 --- a/packages/google-auth/google/auth/transport/urllib3.py +++ b/packages/google-auth/google/auth/transport/urllib3.py @@ -17,6 +17,7 @@ from __future__ import absolute_import import logging +import os import warnings # Certifi is Mozilla's certificate bundle. Urllib3 needs a certificate bundle @@ -45,6 +46,7 @@ import six import urllib3.exceptions # pylint: disable=ungrouped-imports +from google.auth import environment_vars from google.auth import exceptions from google.auth import transport @@ -202,13 +204,18 @@ class AuthorizedHttp(urllib3.request.RequestMethods): credentials' headers to the request and refreshing credentials as needed. This class also supports mutual TLS via :meth:`configure_mtls_channel` - method. If client_cert_callback is provided, client certificate and private + method. In order to use this method, the `GOOGLE_API_USE_CLIENT_CERTIFICATE` + environment variable must be explicitly set to `true`, otherwise it does + nothing. Assume the environment is set to `true`, the method behaves in the + following manner: + If client_cert_callback is provided, client certificate and private key are loaded using the callback; if client_cert_callback is None, application default SSL credentials will be used. Exceptions are raised if there are problems with the certificate, private key, or the loading process, so it should be called within a try/except block. - First we create an :class:`AuthorizedHttp` instance and specify the endpoints:: + First we set the environment variable to `true`, then create an :class:`AuthorizedHttp` + instance and specify the endpoints:: regular_endpoint = 'https://pubsub.googleapis.com/v1/projects/{my_project_id}/topics' mtls_endpoint = 'https://pubsub.mtls.googleapis.com/v1/projects/{my_project_id}/topics' @@ -282,9 +289,13 @@ def __init__( def configure_mtls_channel(self, client_cert_callback=None): """Configures mutual TLS channel using the given client_cert_callback or - application default SSL credentials. Returns True if the channel is - mutual TLS and False otherwise. Note that the `http` provided in the - constructor will be overwritten. + application default SSL credentials. The behavior is controlled by + `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable. + (1) If the environment variable value is `true`, the function returns True + if the channel is mutual TLS and False otherwise. The `http` provided + in the constructor will be overwritten. + (2) If the environment variable is not set or `false`, the function does + nothing and it always return False. Args: client_cert_callback (Optional[Callable[[], (bytes, bytes)]]): @@ -300,6 +311,12 @@ def configure_mtls_channel(self, client_cert_callback=None): google.auth.exceptions.MutualTLSChannelError: If mutual TLS channel creation failed for any reason. """ + use_client_cert = os.getenv( + environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE, "false" + ) + if use_client_cert != "true": + return False + try: import OpenSSL except ImportError as caught_exc: diff --git a/packages/google-auth/system_tests/test_mtls_http.py b/packages/google-auth/system_tests/test_mtls_http.py index 4a6a9c4bc401..7c564968503b 100644 --- a/packages/google-auth/system_tests/test_mtls_http.py +++ b/packages/google-auth/system_tests/test_mtls_http.py @@ -18,6 +18,7 @@ import google.auth import google.auth.credentials +from google.auth import environment_vars from google.auth.transport import mtls import google.auth.transport.requests import google.auth.transport.urllib3 @@ -33,7 +34,8 @@ def test_requests(): ) authed_session = google.auth.transport.requests.AuthorizedSession(credentials) - authed_session.configure_mtls_channel() + with mock.patch.dict(os.environ, {environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true"}): + authed_session.configure_mtls_channel() # If the devices has default client cert source, then a mutual TLS channel # is supposed to be created. @@ -57,7 +59,8 @@ def test_urllib3(): ) authed_http = google.auth.transport.urllib3.AuthorizedHttp(credentials) - is_mtls = authed_http.configure_mtls_channel() + with mock.patch.dict(os.environ, {environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true"}): + is_mtls = authed_http.configure_mtls_channel() # If the devices has default client cert source, then a mutual TLS channel # is supposed to be created. @@ -83,9 +86,10 @@ def test_requests_with_default_client_cert_source(): authed_session = google.auth.transport.requests.AuthorizedSession(credentials) if mtls.has_default_client_cert_source(): - authed_session.configure_mtls_channel( - client_cert_callback=mtls.default_client_cert_source() - ) + with mock.patch.dict(os.environ, {environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true"}): + authed_session.configure_mtls_channel( + client_cert_callback=mtls.default_client_cert_source() + ) assert authed_session.is_mtls @@ -105,9 +109,10 @@ def test_urllib3_with_default_client_cert_source(): authed_http = google.auth.transport.urllib3.AuthorizedHttp(credentials) if mtls.has_default_client_cert_source(): - assert authed_http.configure_mtls_channel( - client_cert_callback=mtls.default_client_cert_source() - ) + with mock.patch.dict(os.environ, {environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true"}): + assert authed_http.configure_mtls_channel( + client_cert_callback=mtls.default_client_cert_source() + ) # Sleep 1 second to avoid 503 error. time.sleep(1) diff --git a/packages/google-auth/tests/transport/test_grpc.py b/packages/google-auth/tests/transport/test_grpc.py index ef2e2e24f8a2..39f8b11c8a13 100644 --- a/packages/google-auth/tests/transport/test_grpc.py +++ b/packages/google-auth/tests/transport/test_grpc.py @@ -21,6 +21,7 @@ from google.auth import _helpers from google.auth import credentials +from google.auth import environment_vars from google.auth import exceptions from google.auth import transport @@ -139,9 +140,13 @@ def test_secure_authorized_channel_adc( None, ) - channel = google.auth.transport.grpc.secure_authorized_channel( - credentials, request, target, options=mock.sentinel.options - ) + channel = None + with mock.patch.dict( + os.environ, {environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true"} + ): + channel = google.auth.transport.grpc.secure_authorized_channel( + credentials, request, target, options=mock.sentinel.options + ) # Check the auth plugin construction. auth_plugin = metadata_call_credentials.call_args[0][0] @@ -167,6 +172,49 @@ def test_secure_authorized_channel_adc( ) assert channel == secure_channel.return_value + @mock.patch("google.auth.transport.grpc.SslCredentials", autospec=True) + def test_secure_authorized_channel_adc_without_client_cert_env( + self, + ssl_credentials_adc_method, + secure_channel, + ssl_channel_credentials, + metadata_call_credentials, + composite_channel_credentials, + get_client_ssl_credentials, + ): + # Test client cert won't be used if GOOGLE_API_USE_CLIENT_CERTIFICATE + # environment variable is not set. + credentials = CredentialsStub() + request = mock.create_autospec(transport.Request) + target = "example.com:80" + + channel = google.auth.transport.grpc.secure_authorized_channel( + credentials, request, target, options=mock.sentinel.options + ) + + # Check the auth plugin construction. + auth_plugin = metadata_call_credentials.call_args[0][0] + assert isinstance(auth_plugin, google.auth.transport.grpc.AuthMetadataPlugin) + assert auth_plugin._credentials == credentials + assert auth_plugin._request == request + + # Check the ssl channel call. + ssl_channel_credentials.assert_called_once() + ssl_credentials_adc_method.assert_not_called() + + # Check the composite credentials call. + composite_channel_credentials.assert_called_once_with( + ssl_channel_credentials.return_value, metadata_call_credentials.return_value + ) + + # Check the channel call. + secure_channel.assert_called_once_with( + target, + composite_channel_credentials.return_value, + options=mock.sentinel.options, + ) + assert channel == secure_channel.return_value + def test_secure_authorized_channel_explicit_ssl( self, secure_channel, @@ -233,9 +281,12 @@ def test_secure_authorized_channel_with_client_cert_callback_success( client_cert_callback = mock.Mock() client_cert_callback.return_value = (PUBLIC_CERT_BYTES, PRIVATE_KEY_BYTES) - google.auth.transport.grpc.secure_authorized_channel( - credentials, request, target, client_cert_callback=client_cert_callback - ) + with mock.patch.dict( + os.environ, {environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true"} + ): + google.auth.transport.grpc.secure_authorized_channel( + credentials, request, target, client_cert_callback=client_cert_callback + ) client_cert_callback.assert_called_once() @@ -273,12 +324,48 @@ def test_secure_authorized_channel_with_client_cert_callback_failure( client_cert_callback.side_effect = Exception("callback exception") with pytest.raises(Exception) as excinfo: - google.auth.transport.grpc.secure_authorized_channel( - credentials, request, target, client_cert_callback=client_cert_callback - ) + with mock.patch.dict( + os.environ, {environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true"} + ): + google.auth.transport.grpc.secure_authorized_channel( + credentials, + request, + target, + client_cert_callback=client_cert_callback, + ) assert str(excinfo.value) == "callback exception" + def test_secure_authorized_channel_cert_callback_without_client_cert_env( + self, + secure_channel, + ssl_channel_credentials, + metadata_call_credentials, + composite_channel_credentials, + get_client_ssl_credentials, + ): + # Test client cert won't be used if GOOGLE_API_USE_CLIENT_CERTIFICATE + # environment variable is not set. + credentials = mock.Mock() + request = mock.Mock() + target = "example.com:80" + client_cert_callback = mock.Mock() + + google.auth.transport.grpc.secure_authorized_channel( + credentials, request, target, client_cert_callback=client_cert_callback + ) + + # Check client_cert_callback is not called because GOOGLE_API_USE_CLIENT_CERTIFICATE + # is not set. + client_cert_callback.assert_not_called() + + ssl_channel_credentials.assert_called_once() + + # Check the composite credentials call. + composite_channel_credentials.assert_called_once_with( + ssl_channel_credentials.return_value, metadata_call_credentials.return_value + ) + @mock.patch("grpc.ssl_channel_credentials", autospec=True) @mock.patch( @@ -299,7 +386,10 @@ def test_no_context_aware_metadata( # Mock that the metadata file doesn't exist. mock_check_dca_metadata_path.return_value = None - ssl_credentials = google.auth.transport.grpc.SslCredentials() + with mock.patch.dict( + os.environ, {environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true"} + ): + ssl_credentials = google.auth.transport.grpc.SslCredentials() # Since no context aware metadata is found, we wouldn't call # get_client_ssl_credentials, and the SSL channel credentials created is @@ -325,7 +415,10 @@ def test_get_client_ssl_credentials_failure( mock_get_client_ssl_credentials.side_effect = exceptions.ClientCertError() with pytest.raises(exceptions.MutualTLSChannelError): - assert google.auth.transport.grpc.SslCredentials().ssl_credentials + with mock.patch.dict( + os.environ, {environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true"} + ): + assert google.auth.transport.grpc.SslCredentials().ssl_credentials def test_get_client_ssl_credentials_success( self, @@ -345,7 +438,10 @@ def test_get_client_ssl_credentials_success( None, ) - ssl_credentials = google.auth.transport.grpc.SslCredentials() + with mock.patch.dict( + os.environ, {environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true"} + ): + ssl_credentials = google.auth.transport.grpc.SslCredentials() assert ssl_credentials.ssl_credentials is not None assert ssl_credentials.is_mtls @@ -353,3 +449,20 @@ def test_get_client_ssl_credentials_success( mock_ssl_channel_credentials.assert_called_once_with( certificate_chain=PUBLIC_CERT_BYTES, private_key=PRIVATE_KEY_BYTES ) + + def test_get_client_ssl_credentials_without_client_cert_env( + self, + mock_check_dca_metadata_path, + mock_read_dca_metadata_file, + mock_get_client_ssl_credentials, + mock_ssl_channel_credentials, + ): + # Test client cert won't be used if GOOGLE_API_USE_CLIENT_CERTIFICATE is not set. + ssl_credentials = google.auth.transport.grpc.SslCredentials() + + assert ssl_credentials.ssl_credentials is not None + assert not ssl_credentials.is_mtls + mock_check_dca_metadata_path.assert_not_called() + mock_read_dca_metadata_file.assert_not_called() + mock_get_client_ssl_credentials.assert_not_called() + mock_ssl_channel_credentials.assert_called_once() diff --git a/packages/google-auth/tests/transport/test_requests.py b/packages/google-auth/tests/transport/test_requests.py index 7ac55cebbff9..d56c2be5599c 100644 --- a/packages/google-auth/tests/transport/test_requests.py +++ b/packages/google-auth/tests/transport/test_requests.py @@ -14,6 +14,7 @@ import datetime import functools +import os import sys import freezegun @@ -24,6 +25,7 @@ import requests.adapters from six.moves import http_client +from google.auth import environment_vars from google.auth import exceptions import google.auth.credentials import google.auth.transport._mtls_helper @@ -380,7 +382,10 @@ def test_configure_mtls_channel_with_callback(self): auth_session = google.auth.transport.requests.AuthorizedSession( credentials=mock.Mock() ) - auth_session.configure_mtls_channel(mock_callback) + with mock.patch.dict( + os.environ, {environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true"} + ): + auth_session.configure_mtls_channel(mock_callback) assert auth_session.is_mtls assert isinstance( @@ -401,7 +406,10 @@ def test_configure_mtls_channel_with_metadata(self, mock_get_client_cert_and_key auth_session = google.auth.transport.requests.AuthorizedSession( credentials=mock.Mock() ) - auth_session.configure_mtls_channel() + with mock.patch.dict( + os.environ, {environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true"} + ): + auth_session.configure_mtls_channel() assert auth_session.is_mtls assert isinstance( @@ -421,7 +429,10 @@ def test_configure_mtls_channel_non_mtls( auth_session = google.auth.transport.requests.AuthorizedSession( credentials=mock.Mock() ) - auth_session.configure_mtls_channel() + with mock.patch.dict( + os.environ, {environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true"} + ): + auth_session.configure_mtls_channel() assert not auth_session.is_mtls @@ -438,10 +449,38 @@ def test_configure_mtls_channel_exceptions(self, mock_get_client_cert_and_key): credentials=mock.Mock() ) with pytest.raises(exceptions.MutualTLSChannelError): - auth_session.configure_mtls_channel() + with mock.patch.dict( + os.environ, {environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true"} + ): + auth_session.configure_mtls_channel() mock_get_client_cert_and_key.return_value = (False, None, None) with mock.patch.dict("sys.modules"): sys.modules["OpenSSL"] = None with pytest.raises(exceptions.MutualTLSChannelError): - auth_session.configure_mtls_channel() + with mock.patch.dict( + os.environ, + {environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true"}, + ): + auth_session.configure_mtls_channel() + + @mock.patch( + "google.auth.transport._mtls_helper.get_client_cert_and_key", autospec=True + ) + def test_configure_mtls_channel_without_client_cert_env( + self, get_client_cert_and_key + ): + # Test client cert won't be used if GOOGLE_API_USE_CLIENT_CERTIFICATE + # environment variable is not set. + auth_session = google.auth.transport.requests.AuthorizedSession( + credentials=mock.Mock() + ) + + auth_session.configure_mtls_channel() + assert not auth_session.is_mtls + get_client_cert_and_key.assert_not_called() + + mock_callback = mock.Mock() + auth_session.configure_mtls_channel(mock_callback) + assert not auth_session.is_mtls + mock_callback.assert_not_called() diff --git a/packages/google-auth/tests/transport/test_urllib3.py b/packages/google-auth/tests/transport/test_urllib3.py index 3158b92c613d..29561f6d6571 100644 --- a/packages/google-auth/tests/transport/test_urllib3.py +++ b/packages/google-auth/tests/transport/test_urllib3.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import os import sys import mock @@ -20,6 +21,7 @@ from six.moves import http_client import urllib3 +from google.auth import environment_vars from google.auth import exceptions import google.auth.credentials import google.auth.transport._mtls_helper @@ -179,7 +181,10 @@ def test_configure_mtls_channel_with_callback(self, mock_make_mutual_tls_http): ) with pytest.warns(UserWarning): - is_mtls = authed_http.configure_mtls_channel(callback) + with mock.patch.dict( + os.environ, {environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true"} + ): + is_mtls = authed_http.configure_mtls_channel(callback) assert is_mtls mock_make_mutual_tls_http.assert_called_once_with( @@ -202,7 +207,10 @@ def test_configure_mtls_channel_with_metadata( pytest.public_cert_bytes, pytest.private_key_bytes, ) - is_mtls = authed_http.configure_mtls_channel() + with mock.patch.dict( + os.environ, {environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true"} + ): + is_mtls = authed_http.configure_mtls_channel() assert is_mtls mock_get_client_cert_and_key.assert_called_once() @@ -222,7 +230,10 @@ def test_configure_mtls_channel_non_mtls( ) mock_get_client_cert_and_key.return_value = (False, None, None) - is_mtls = authed_http.configure_mtls_channel() + with mock.patch.dict( + os.environ, {environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true"} + ): + is_mtls = authed_http.configure_mtls_channel() assert not is_mtls mock_get_client_cert_and_key.assert_called_once() @@ -238,10 +249,39 @@ def test_configure_mtls_channel_exceptions(self, mock_get_client_cert_and_key): mock_get_client_cert_and_key.side_effect = exceptions.ClientCertError() with pytest.raises(exceptions.MutualTLSChannelError): - authed_http.configure_mtls_channel() + with mock.patch.dict( + os.environ, {environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true"} + ): + authed_http.configure_mtls_channel() mock_get_client_cert_and_key.return_value = (False, None, None) with mock.patch.dict("sys.modules"): sys.modules["OpenSSL"] = None with pytest.raises(exceptions.MutualTLSChannelError): - authed_http.configure_mtls_channel() + with mock.patch.dict( + os.environ, + {environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true"}, + ): + authed_http.configure_mtls_channel() + + @mock.patch( + "google.auth.transport._mtls_helper.get_client_cert_and_key", autospec=True + ) + def test_configure_mtls_channel_without_client_cert_env( + self, get_client_cert_and_key + ): + callback = mock.Mock() + + authed_http = google.auth.transport.urllib3.AuthorizedHttp( + credentials=mock.Mock(), http=mock.Mock() + ) + + # Test the callback is not called if GOOGLE_API_USE_CLIENT_CERTIFICATE is not set. + is_mtls = authed_http.configure_mtls_channel(callback) + assert not is_mtls + callback.assert_not_called() + + # Test ADC client cert is not used if GOOGLE_API_USE_CLIENT_CERTIFICATE is not set. + is_mtls = authed_http.configure_mtls_channel(callback) + assert not is_mtls + get_client_cert_and_key.assert_not_called() From 7d799d70aaafe3b5f3b61e8c3d960545aecc8e34 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 27 Aug 2020 14:15:09 -0700 Subject: [PATCH 339/966] chore: release 1.21.0 (#593) * chore: updated CHANGELOG.md [ci skip] * chore: updated setup.cfg [ci skip] * chore: updated setup.py Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 56faf8efbd67..21519f2de7e6 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +## [1.21.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.20.1...v1.21.0) (2020-08-27) + + +### Features + +* add GOOGLE_API_USE_CLIENT_CERTIFICATE support ([#592](https://www.github.com/googleapis/google-auth-library-python/issues/592)) ([c0c995f](https://www.github.com/googleapis/google-auth-library-python/commit/c0c995f3de237a2346b59797ee7c4d44ff2a197c)) + ### [1.20.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.20.0...v1.20.1) (2020-08-06) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index cfa7b0a8522c..c0365391e98b 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -33,7 +33,7 @@ with io.open("README.rst", "r") as fh: long_description = fh.read() -version = "1.20.1" +version = "1.21.0" setup( name="google-auth", From 17eb67fabc320bdc12ca193c3720b640ec835266 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Wed, 2 Sep 2020 12:55:42 -0600 Subject: [PATCH 340/966] refactor: split 'with_quota_project' into separate base class (#561) Co-authored-by: Tres Seaver --- packages/google-auth/google/auth/app_engine.py | 6 ++++-- .../google/auth/compute_engine/credentials.py | 8 ++++---- packages/google-auth/google/auth/credentials.py | 9 +++++---- .../google/auth/impersonated_credentials.py | 8 ++++---- packages/google-auth/google/auth/jwt.py | 10 ++++++---- packages/google-auth/google/oauth2/credentials.py | 8 ++++---- packages/google-auth/google/oauth2/service_account.py | 10 ++++++---- packages/google-auth/tests/test__default.py | 2 +- packages/google-auth/tests/test_credentials.py | 6 ------ 9 files changed, 34 insertions(+), 33 deletions(-) diff --git a/packages/google-auth/google/auth/app_engine.py b/packages/google-auth/google/auth/app_engine.py index fae00d0b8745..f1d21280eca8 100644 --- a/packages/google-auth/google/auth/app_engine.py +++ b/packages/google-auth/google/auth/app_engine.py @@ -77,7 +77,9 @@ def get_project_id(): return app_identity.get_application_id() -class Credentials(credentials.Scoped, credentials.Signing, credentials.Credentials): +class Credentials( + credentials.Scoped, credentials.Signing, credentials.CredentialsWithQuotaProject +): """App Engine standard environment credentials. These credentials use the App Engine App Identity API to obtain access @@ -145,7 +147,7 @@ def with_scopes(self, scopes): quota_project_id=self.quota_project_id, ) - @_helpers.copy_docstring(credentials.Credentials) + @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) def with_quota_project(self, quota_project_id): return self.__class__( scopes=self._scopes, diff --git a/packages/google-auth/google/auth/compute_engine/credentials.py b/packages/google-auth/google/auth/compute_engine/credentials.py index e6da238a04db..b7fca18326f2 100644 --- a/packages/google-auth/google/auth/compute_engine/credentials.py +++ b/packages/google-auth/google/auth/compute_engine/credentials.py @@ -32,7 +32,7 @@ from google.oauth2 import _client -class Credentials(credentials.ReadOnlyScoped, credentials.Credentials): +class Credentials(credentials.ReadOnlyScoped, credentials.CredentialsWithQuotaProject): """Compute Engine Credentials. These credentials use the Google Compute Engine metadata server to obtain @@ -118,7 +118,7 @@ def requires_scopes(self): """False: Compute Engine credentials can not be scoped.""" return False - @_helpers.copy_docstring(credentials.Credentials) + @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) def with_quota_project(self, quota_project_id): return self.__class__( service_account_email=self._service_account_email, @@ -130,7 +130,7 @@ def with_quota_project(self, quota_project_id): _DEFAULT_TOKEN_URI = "https://www.googleapis.com/oauth2/v4/token" -class IDTokenCredentials(credentials.Credentials, credentials.Signing): +class IDTokenCredentials(credentials.CredentialsWithQuotaProject, credentials.Signing): """Open ID Connect ID Token-based service account credentials. These credentials relies on the default service account of a GCE instance. @@ -254,7 +254,7 @@ def with_target_audience(self, target_audience): quota_project_id=self._quota_project_id, ) - @_helpers.copy_docstring(credentials.Credentials) + @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) def with_quota_project(self, quota_project_id): # since the signer is already instantiated, diff --git a/packages/google-auth/google/auth/credentials.py b/packages/google-auth/google/auth/credentials.py index 3f389b1713ff..5ea36a0bae9c 100644 --- a/packages/google-auth/google/auth/credentials.py +++ b/packages/google-auth/google/auth/credentials.py @@ -133,6 +133,10 @@ def before_request(self, request, method, url, headers): self.refresh(request) self.apply(headers) + +class CredentialsWithQuotaProject(Credentials): + """Abstract base for credentials supporting ``with_quota_project`` factory""" + def with_quota_project(self, quota_project_id): """Returns a copy of these credentials with a modified quota project @@ -143,7 +147,7 @@ def with_quota_project(self, quota_project_id): Returns: google.oauth2.credentials.Credentials: A new credentials instance. """ - raise NotImplementedError("This class does not support quota project.") + raise NotImplementedError("This credential does not support quota project.") class AnonymousCredentials(Credentials): @@ -182,9 +186,6 @@ def apply(self, headers, token=None): def before_request(self, request, method, url, headers): """Anonymous credentials do nothing to the request.""" - def with_quota_project(self, quota_project_id): - raise ValueError("Anonymous credentials don't support quota project.") - @six.add_metaclass(abc.ABCMeta) class ReadOnlyScoped(object): diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index dbcb2914eece..d2c5ded1ce28 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -115,7 +115,7 @@ def _make_iam_token_request(request, principal, headers, body): six.raise_from(new_exc, caught_exc) -class Credentials(credentials.Credentials, credentials.Signing): +class Credentials(credentials.CredentialsWithQuotaProject, credentials.Signing): """This module defines impersonated credentials which are essentially impersonated identities. @@ -293,7 +293,7 @@ def service_account_email(self): def signer(self): return self - @_helpers.copy_docstring(credentials.Credentials) + @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) def with_quota_project(self, quota_project_id): return self.__class__( self._source_credentials, @@ -305,7 +305,7 @@ def with_quota_project(self, quota_project_id): ) -class IDTokenCredentials(credentials.Credentials): +class IDTokenCredentials(credentials.CredentialsWithQuotaProject): """Open ID Connect ID Token-based service account credentials. """ @@ -359,7 +359,7 @@ def with_include_email(self, include_email): quota_project_id=self._quota_project_id, ) - @_helpers.copy_docstring(credentials.Credentials) + @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) def with_quota_project(self, quota_project_id): return self.__class__( target_credentials=self._target_credentials, diff --git a/packages/google-auth/google/auth/jwt.py b/packages/google-auth/google/auth/jwt.py index 35ae034325c7..a4f04f529e03 100644 --- a/packages/google-auth/google/auth/jwt.py +++ b/packages/google-auth/google/auth/jwt.py @@ -288,7 +288,9 @@ def decode(token, certs=None, verify=True, audience=None): return payload -class Credentials(google.auth.credentials.Signing, google.auth.credentials.Credentials): +class Credentials( + google.auth.credentials.Signing, google.auth.credentials.CredentialsWithQuotaProject +): """Credentials that use a JWT as the bearer token. These credentials require an "audience" claim. This claim identifies the @@ -493,7 +495,7 @@ def with_claims( quota_project_id=self._quota_project_id, ) - @_helpers.copy_docstring(google.auth.credentials.Credentials) + @_helpers.copy_docstring(google.auth.credentials.CredentialsWithQuotaProject) def with_quota_project(self, quota_project_id): return self.__class__( self._signer, @@ -554,7 +556,7 @@ def signer(self): class OnDemandCredentials( - google.auth.credentials.Signing, google.auth.credentials.Credentials + google.auth.credentials.Signing, google.auth.credentials.CredentialsWithQuotaProject ): """On-demand JWT credentials. @@ -721,7 +723,7 @@ def with_claims(self, issuer=None, subject=None, additional_claims=None): quota_project_id=self._quota_project_id, ) - @_helpers.copy_docstring(google.auth.credentials.Credentials) + @_helpers.copy_docstring(google.auth.credentials.CredentialsWithQuotaProject) def with_quota_project(self, quota_project_id): return self.__class__( diff --git a/packages/google-auth/google/oauth2/credentials.py b/packages/google-auth/google/oauth2/credentials.py index 6f9627572d5e..6e58f630d36b 100644 --- a/packages/google-auth/google/oauth2/credentials.py +++ b/packages/google-auth/google/oauth2/credentials.py @@ -47,7 +47,7 @@ _GOOGLE_OAUTH2_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token" -class Credentials(credentials.ReadOnlyScoped, credentials.Credentials): +class Credentials(credentials.ReadOnlyScoped, credentials.CredentialsWithQuotaProject): """Credentials using OAuth 2.0 access and refresh tokens. The credentials are considered immutable. If you want to modify the @@ -161,7 +161,7 @@ def requires_scopes(self): the initial token is requested and can not be changed.""" return False - @_helpers.copy_docstring(credentials.Credentials) + @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) def with_quota_project(self, quota_project_id): return self.__class__( @@ -305,7 +305,7 @@ def to_json(self, strip=None): return json.dumps(prep) -class UserAccessTokenCredentials(credentials.Credentials): +class UserAccessTokenCredentials(credentials.CredentialsWithQuotaProject): """Access token credentials for user account. Obtain the access token for a given user account or the current active @@ -336,7 +336,7 @@ def with_account(self, account): """ return self.__class__(account=account, quota_project_id=self._quota_project_id) - @_helpers.copy_docstring(credentials.Credentials) + @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) def with_quota_project(self, quota_project_id): return self.__class__(account=self._account, quota_project_id=quota_project_id) diff --git a/packages/google-auth/google/oauth2/service_account.py b/packages/google-auth/google/oauth2/service_account.py index 2240631e9af9..c4898a247ba4 100644 --- a/packages/google-auth/google/oauth2/service_account.py +++ b/packages/google-auth/google/oauth2/service_account.py @@ -82,7 +82,9 @@ _DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds -class Credentials(credentials.Signing, credentials.Scoped, credentials.Credentials): +class Credentials( + credentials.Signing, credentials.Scoped, credentials.CredentialsWithQuotaProject +): """Service account credentials Usually, you'll create these credentials with one of the helper @@ -306,7 +308,7 @@ def with_claims(self, additional_claims): additional_claims=new_additional_claims, ) - @_helpers.copy_docstring(credentials.Credentials) + @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) def with_quota_project(self, quota_project_id): return self.__class__( @@ -375,7 +377,7 @@ def signer_email(self): return self._service_account_email -class IDTokenCredentials(credentials.Signing, credentials.Credentials): +class IDTokenCredentials(credentials.Signing, credentials.CredentialsWithQuotaProject): """Open ID Connect ID Token-based service account credentials. These credentials are largely similar to :class:`.Credentials`, but instead @@ -533,7 +535,7 @@ def with_target_audience(self, target_audience): quota_project_id=self.quota_project_id, ) - @_helpers.copy_docstring(credentials.Credentials) + @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) def with_quota_project(self, quota_project_id): return self.__class__( self._signer, diff --git a/packages/google-auth/tests/test__default.py b/packages/google-auth/tests/test__default.py index 55a14c207fb8..2738e22bc82a 100644 --- a/packages/google-auth/tests/test__default.py +++ b/packages/google-auth/tests/test__default.py @@ -49,7 +49,7 @@ with open(SERVICE_ACCOUNT_FILE) as fh: SERVICE_ACCOUNT_FILE_DATA = json.load(fh) -MOCK_CREDENTIALS = mock.Mock(spec=credentials.Credentials) +MOCK_CREDENTIALS = mock.Mock(spec=credentials.CredentialsWithQuotaProject) MOCK_CREDENTIALS.with_quota_project.return_value = MOCK_CREDENTIALS LOAD_FILE_PATCH = mock.patch( diff --git a/packages/google-auth/tests/test_credentials.py b/packages/google-auth/tests/test_credentials.py index 2023fac1bc9d..0637b01e42e2 100644 --- a/packages/google-auth/tests/test_credentials.py +++ b/packages/google-auth/tests/test_credentials.py @@ -115,12 +115,6 @@ def test_anonymous_credentials_before_request(): assert headers == {} -def test_anonymous_credentials_with_quota_project(): - with pytest.raises(ValueError): - anon = credentials.AnonymousCredentials() - anon.with_quota_project("project-foo") - - class ReadOnlyScopedCredentialsImpl(credentials.ReadOnlyScoped, CredentialsImpl): @property def requires_scopes(self): From 14545ca98898d9b95c83b3215a5b21cd5d03193d Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Thu, 3 Sep 2020 09:55:20 -0700 Subject: [PATCH 341/966] fix: dummy commit to trigger a auto release (#597) --- packages/google-auth/google/auth/credentials.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/credentials.py b/packages/google-auth/google/auth/credentials.py index 5ea36a0bae9c..bc42546b9a06 100644 --- a/packages/google-auth/google/auth/credentials.py +++ b/packages/google-auth/google/auth/credentials.py @@ -138,7 +138,7 @@ class CredentialsWithQuotaProject(Credentials): """Abstract base for credentials supporting ``with_quota_project`` factory""" def with_quota_project(self, quota_project_id): - """Returns a copy of these credentials with a modified quota project + """Returns a copy of these credentials with a modified quota project. Args: quota_project_id (str): The project to use for quota and From 817e2f5c18179d99bf0b82c9e0659f488b7d5cb6 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 3 Sep 2020 10:08:05 -0700 Subject: [PATCH 342/966] chore: release 1.21.1 (#599) * chore: updated CHANGELOG.md [ci skip] * chore: updated setup.cfg [ci skip] * chore: updated setup.py Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 21519f2de7e6..0b4c774eb2c0 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +### [1.21.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.21.0...v1.21.1) (2020-09-03) + + +### Bug Fixes + +* dummy commit to trigger a auto release ([#597](https://www.github.com/googleapis/google-auth-library-python/issues/597)) ([d32f7df](https://www.github.com/googleapis/google-auth-library-python/commit/d32f7df4895122ef23b664672d7db3f58d9b7d36)) + ## [1.21.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.20.1...v1.21.0) (2020-08-27) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index c0365391e98b..c1218dbf7580 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -33,7 +33,7 @@ with io.open("README.rst", "r") as fh: long_description = fh.read() -version = "1.21.0" +version = "1.21.1" setup( name="google-auth", From ea9d07d1ca053fd03f41f38c6f57f6343135b216 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Tue, 8 Sep 2020 15:28:08 -0600 Subject: [PATCH 343/966] fix: migrate signBlob to iamcredentials.googleapis.com (#600) Migrate signBlob from iam.googleapis.com to iamcredentials.googleapis.com. This API is deprecated and will be shutdown in one year. This is used google.auth.iam.Signer. Added a system_test to sanity check the implementation. --- packages/google-auth/.gitignore | 3 +++ packages/google-auth/google/auth/iam.py | 6 +++--- .../system_tests/test_service_account.py | 17 +++++++++++++++++ .../tests/compute_engine/test_credentials.py | 12 ++++++------ packages/google-auth/tests/test_iam.py | 2 +- 5 files changed, 30 insertions(+), 10 deletions(-) diff --git a/packages/google-auth/.gitignore b/packages/google-auth/.gitignore index 6d86d1f7a7c2..f01e60ec0b0f 100644 --- a/packages/google-auth/.gitignore +++ b/packages/google-auth/.gitignore @@ -41,3 +41,6 @@ pylintrc.test pytype_output/ .python-version +.DS_Store +cert_path +key_path \ No newline at end of file diff --git a/packages/google-auth/google/auth/iam.py b/packages/google-auth/google/auth/iam.py index d83b25180afc..5e88a0435c50 100644 --- a/packages/google-auth/google/auth/iam.py +++ b/packages/google-auth/google/auth/iam.py @@ -28,7 +28,7 @@ from google.auth import crypt from google.auth import exceptions -_IAM_API_ROOT_URI = "https://iam.googleapis.com/v1" +_IAM_API_ROOT_URI = "https://iamcredentials.googleapis.com/v1" _SIGN_BLOB_URI = _IAM_API_ROOT_URI + "/projects/-/serviceAccounts/{}:signBlob?alt=json" @@ -71,7 +71,7 @@ def _make_signing_request(self, message): url = _SIGN_BLOB_URI.format(self._service_account_email) headers = {"Content-Type": "application/json"} body = json.dumps( - {"bytesToSign": base64.b64encode(message).decode("utf-8")} + {"payload": base64.b64encode(message).decode("utf-8")} ).encode("utf-8") self._credentials.before_request(self._request, method, url, headers) @@ -97,4 +97,4 @@ def key_id(self): @_helpers.copy_docstring(crypt.Signer) def sign(self, message): response = self._make_signing_request(message) - return base64.b64decode(response["signature"]) + return base64.b64decode(response["signedBlob"]) diff --git a/packages/google-auth/system_tests/test_service_account.py b/packages/google-auth/system_tests/test_service_account.py index 262ce84f5387..498b75b2233c 100644 --- a/packages/google-auth/system_tests/test_service_account.py +++ b/packages/google-auth/system_tests/test_service_account.py @@ -16,6 +16,7 @@ from google.auth import _helpers from google.auth import exceptions +from google.auth import iam from google.oauth2 import service_account @@ -46,3 +47,19 @@ def test_refresh_success(http_request, credentials, token_info): "https://www.googleapis.com/auth/userinfo.profile", ] ) + +def test_iam_signer(http_request, credentials): + credentials = credentials.with_scopes( + ["https://www.googleapis.com/auth/iam"] + ) + + # Verify iamcredentials signer. + signer = iam.Signer( + http_request, + credentials, + credentials.service_account_email + ) + + signed_blob = signer.sign("message") + + assert isinstance(signed_blob, bytes) diff --git a/packages/google-auth/tests/compute_engine/test_credentials.py b/packages/google-auth/tests/compute_engine/test_credentials.py index 8c95e2437782..4ee6536762b0 100644 --- a/packages/google-auth/tests/compute_engine/test_credentials.py +++ b/packages/google-auth/tests/compute_engine/test_credentials.py @@ -363,11 +363,11 @@ def test_with_target_audience_integration(self): signature = base64.b64encode(b"some-signature").decode("utf-8") responses.add( responses.POST, - "https://iam.googleapis.com/v1/projects/-/serviceAccounts/" - "service-account@example.com:signBlob?alt=json", + "https://iamcredentials.googleapis.com/v1/projects/-/" + "serviceAccounts/service-account@example.com:signBlob?alt=json", status=200, content_type="application/json", - json={"keyId": "some-key-id", "signature": signature}, + json={"keyId": "some-key-id", "signedBlob": signature}, ) id_token = "{}.{}.{}".format( @@ -477,11 +477,11 @@ def test_with_quota_project_integration(self): signature = base64.b64encode(b"some-signature").decode("utf-8") responses.add( responses.POST, - "https://iam.googleapis.com/v1/projects/-/serviceAccounts/" - "service-account@example.com:signBlob?alt=json", + "https://iamcredentials.googleapis.com/v1/projects/-/" + "serviceAccounts/service-account@example.com:signBlob?alt=json", status=200, content_type="application/json", - json={"keyId": "some-key-id", "signature": signature}, + json={"keyId": "some-key-id", "signedBlob": signature}, ) id_token = "{}.{}.{}".format( diff --git a/packages/google-auth/tests/test_iam.py b/packages/google-auth/tests/test_iam.py index cb2c26f7335d..fbd3e418dac5 100644 --- a/packages/google-auth/tests/test_iam.py +++ b/packages/google-auth/tests/test_iam.py @@ -81,7 +81,7 @@ def test_key_id(self): def test_sign_bytes(self): signature = b"DEADBEEF" encoded_signature = base64.b64encode(signature).decode("utf-8") - request = make_request(http_client.OK, data={"signature": encoded_signature}) + request = make_request(http_client.OK, data={"signedBlob": encoded_signature}) credentials = make_credentials() signer = iam.Signer(request, credentials, mock.sentinel.service_account_email) From ca3b882ebb1ab04d90498bc31d14835e810a75e3 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 15 Sep 2020 18:32:41 -0600 Subject: [PATCH 344/966] chore: release 1.21.2 (#601) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 0b4c774eb2c0..78863ffed01c 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +### [1.21.2](https://www.github.com/googleapis/google-auth-library-python/compare/v1.21.1...v1.21.2) (2020-09-08) + + +### Bug Fixes + +* migrate signBlob to iamcredentials.googleapis.com ([#600](https://www.github.com/googleapis/google-auth-library-python/issues/600)) ([694d83f](https://www.github.com/googleapis/google-auth-library-python/commit/694d83fd23c0e8c2fde27136d1b3f8f6db6338a6)) + ### [1.21.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.21.0...v1.21.1) (2020-09-03) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index c1218dbf7580..ea43594c0c18 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -33,7 +33,7 @@ with io.open("README.rst", "r") as fh: long_description = fh.read() -version = "1.21.1" +version = "1.21.2" setup( name="google-auth", From 5210996f8ef8a391a40a4c441be479291d9031ca Mon Sep 17 00:00:00 2001 From: wesley chun Date: Thu, 17 Sep 2020 09:18:55 -0700 Subject: [PATCH 345/966] fix: fix expiry for `to_json()` (#589) * This patch for includes the following fixes: - The access token is always set to `None`, so the fix involves using (the access) `token` from the saved JSON credentials file. - For refresh needs, `expiry` also needs to be saved via `to_json()`. - DUMP: As `expiry` is a `datetime.datetime` object, serialize to `datetime.isoformat()` in the same [`oauth2client` format](https://github.com/googleapis/oauth2client/blob/master/oauth2client/client.py#L55) for consistency. - LOAD: Add code to restore `expiry` back to `datetime.datetime` object when imported. - LOAD: If `expiry` was unsaved, automatically set it as expired so refresh takes place. - Minor `scopes` updates - DUMP: Add property for `scopes` so `to_json()` can grab it - LOAD: `scopes` may be saved as a string instead of a JSON array (Python list), so ensure it is Sequence[str] when imported. --- .../google-auth/google/oauth2/credentials.py | 43 ++++++++++++++----- .../tests/oauth2/test_credentials.py | 24 +++++++++++ 2 files changed, 57 insertions(+), 10 deletions(-) diff --git a/packages/google-auth/google/oauth2/credentials.py b/packages/google-auth/google/oauth2/credentials.py index 6e58f630d36b..36b8f0cb7624 100644 --- a/packages/google-auth/google/oauth2/credentials.py +++ b/packages/google-auth/google/oauth2/credentials.py @@ -31,6 +31,7 @@ .. _rfc6749 section 4.1: https://tools.ietf.org/html/rfc6749#section-4.1 """ +from datetime import datetime import io import json @@ -66,6 +67,7 @@ def __init__( client_secret=None, scopes=None, quota_project_id=None, + expiry=None, ): """ Args: @@ -95,6 +97,7 @@ def __init__( """ super(Credentials, self).__init__() self.token = token + self.expiry = expiry self._refresh_token = refresh_token self._id_token = id_token self._scopes = scopes @@ -128,6 +131,11 @@ def refresh_token(self): """Optional[str]: The OAuth 2.0 refresh token.""" return self._refresh_token + @property + def scopes(self): + """Optional[str]: The OAuth 2.0 permission scopes.""" + return self._scopes + @property def token_uri(self): """Optional[str]: The OAuth 2.0 authorization server's token endpoint @@ -241,16 +249,30 @@ def from_authorized_user_info(cls, info, scopes=None): "fields {}.".format(", ".join(missing)) ) + # access token expiry (datetime obj); auto-expire if not saved + expiry = info.get("expiry") + if expiry: + expiry = datetime.strptime( + expiry.rstrip("Z").split(".")[0], "%Y-%m-%dT%H:%M:%S" + ) + else: + expiry = _helpers.utcnow() - _helpers.CLOCK_SKEW + + # process scopes, which needs to be a seq + if scopes is None and "scopes" in info: + scopes = info.get("scopes") + if isinstance(scopes, str): + scopes = scopes.split(" ") + return cls( - None, # No access token, must be refreshed. - refresh_token=info["refresh_token"], - token_uri=_GOOGLE_OAUTH2_TOKEN_ENDPOINT, + token=info.get("token"), + refresh_token=info.get("refresh_token"), + token_uri=_GOOGLE_OAUTH2_TOKEN_ENDPOINT, # always overrides scopes=scopes, - client_id=info["client_id"], - client_secret=info["client_secret"], - quota_project_id=info.get( - "quota_project_id" - ), # quota project may not exist + client_id=info.get("client_id"), + client_secret=info.get("client_secret"), + quota_project_id=info.get("quota_project_id"), # may not exist + expiry=expiry, ) @classmethod @@ -294,8 +316,10 @@ def to_json(self, strip=None): "client_secret": self.client_secret, "scopes": self.scopes, } + if self.expiry: # flatten expiry timestamp + prep["expiry"] = self.expiry.isoformat() + "Z" - # Remove empty entries + # Remove empty entries (those which are None) prep = {k: v for k, v in prep.items() if v is not None} # Remove entries that explicitely need to be removed @@ -316,7 +340,6 @@ class UserAccessTokenCredentials(credentials.CredentialsWithQuotaProject): specified, the current active account will be used. quota_project_id (Optional[str]): The project ID used for quota and billing. - """ def __init__(self, account=None, quota_project_id=None): diff --git a/packages/google-auth/tests/oauth2/test_credentials.py b/packages/google-auth/tests/oauth2/test_credentials.py index ceb8cdfd5ef1..ee8b8a2112f1 100644 --- a/packages/google-auth/tests/oauth2/test_credentials.py +++ b/packages/google-auth/tests/oauth2/test_credentials.py @@ -359,6 +359,20 @@ def test_from_authorized_user_info(self): assert creds.token_uri == credentials._GOOGLE_OAUTH2_TOKEN_ENDPOINT assert creds.scopes == scopes + info["scopes"] = "email" # single non-array scope from file + creds = credentials.Credentials.from_authorized_user_info(info) + assert creds.scopes == [info["scopes"]] + + info["scopes"] = ["email", "profile"] # array scope from file + creds = credentials.Credentials.from_authorized_user_info(info) + assert creds.scopes == info["scopes"] + + expiry = datetime.datetime(2020, 8, 14, 15, 54, 1) + info["expiry"] = expiry.isoformat() + "Z" + creds = credentials.Credentials.from_authorized_user_info(info) + assert creds.expiry == expiry + assert creds.expired + def test_from_authorized_user_file(self): info = AUTH_USER_INFO.copy() @@ -381,7 +395,10 @@ def test_from_authorized_user_file(self): def test_to_json(self): info = AUTH_USER_INFO.copy() + expiry = datetime.datetime(2020, 8, 14, 15, 54, 1) + info["expiry"] = expiry.isoformat() + "Z" creds = credentials.Credentials.from_authorized_user_info(info) + assert creds.expiry == expiry # Test with no `strip` arg json_output = creds.to_json() @@ -392,6 +409,7 @@ def test_to_json(self): assert json_asdict.get("client_id") == creds.client_id assert json_asdict.get("scopes") == creds.scopes assert json_asdict.get("client_secret") == creds.client_secret + assert json_asdict.get("expiry") == info["expiry"] # Test with a `strip` arg json_output = creds.to_json(strip=["client_secret"]) @@ -403,6 +421,12 @@ def test_to_json(self): assert json_asdict.get("scopes") == creds.scopes assert json_asdict.get("client_secret") is None + # Test with no expiry + creds.expiry = None + json_output = creds.to_json() + json_asdict = json.loads(json_output) + assert json_asdict.get("expiry") is None + def test_pickle_and_unpickle(self): creds = self.make_credentials() unpickled = pickle.loads(pickle.dumps(creds)) From c96c23ad5b11f41663d62ab3580549731c85af4e Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Tue, 22 Sep 2020 16:11:44 -0600 Subject: [PATCH 346/966] chore: add default CODEOWNERS (#609) --- packages/google-auth/.github/CODEOWNERS | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 packages/google-auth/.github/CODEOWNERS diff --git a/packages/google-auth/.github/CODEOWNERS b/packages/google-auth/.github/CODEOWNERS new file mode 100644 index 000000000000..30c3973aa372 --- /dev/null +++ b/packages/google-auth/.github/CODEOWNERS @@ -0,0 +1,11 @@ +# Code owners file. +# This file controls who is tagged for review for any given pull request. +# +# For syntax help see: +# https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners#codeowners-syntax + +# The @googleapis/yoshi-python is the default owner for changes in this repo +* @googleapis/yoshi-python + +# The python-samples-reviewers team is the default owner for samples changes +/samples/ @googleapis/python-samples-owners \ No newline at end of file From d81ab89abe8f9cec26693bfa2b06c1c4dea85c57 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Wed, 23 Sep 2020 12:31:17 -0600 Subject: [PATCH 347/966] chore: release 1.21.3 (#607) --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 78863ffed01c..d57047990bfb 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +### [1.21.3](https://www.github.com/googleapis/google-auth-library-python/compare/v1.21.2...v1.21.3) (2020-09-22) + + +### Bug Fixes + +* fix expiry for `to_json()` ([#589](https://www.github.com/googleapis/google-auth-library-python/issues/589)) ([d0e0aba](https://www.github.com/googleapis/google-auth-library-python/commit/d0e0aba0a9f665268ffa1b22d44f4bd7e9b449d6)), closes [/github.com/googleapis/oauth2client/blob/master/oauth2client/client.py#L55](https://www.github.com/googleapis//github.com/googleapis/oauth2client/blob/master/oauth2client/client.py/issues/L55) + ### [1.21.2](https://www.github.com/googleapis/google-auth-library-python/compare/v1.21.1...v1.21.2) (2020-09-08) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index ea43594c0c18..1c3578f60c8b 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -33,7 +33,7 @@ with io.open("README.rst", "r") as fh: long_description = fh.read() -version = "1.21.2" +version = "1.21.3" setup( name="google-auth", From c95afc48eb57ba8115c4c0aefa48ffddb9c9ec09 Mon Sep 17 00:00:00 2001 From: Christopher Wilcox Date: Mon, 28 Sep 2020 15:27:20 -0700 Subject: [PATCH 348/966] feat: add asyncio based auth flow (#612) * feat: asyncio http request logic and asynchronous credentials logic (#572) Co-authored-by: Anirudh Baddepudi <43104821+anibadde@users.noreply.github.com> --- .../google.auth.credentials_async.rst | 7 + .../docs/reference/google.auth.jwt_async.rst | 7 + .../docs/reference/google.auth.rst | 2 + ...google.auth.transport.aiohttp_requests.rst | 7 + .../docs/reference/google.auth.transport.rst | 1 + .../google.oauth2.credentials_async.rst | 7 + .../docs/reference/google.oauth2.rst | 2 + .../google.oauth2.service_account_async.rst | 7 + packages/google-auth/google/auth/__init__.py | 2 - .../google/auth/_credentials_async.py | 176 +++++++ .../google-auth/google/auth/_default_async.py | 266 ++++++++++ .../google-auth/google/auth/_jwt_async.py | 168 ++++++ .../auth/transport/_aiohttp_requests.py | 384 ++++++++++++++ .../google-auth/google/auth/transport/mtls.py | 9 +- .../google/oauth2/_client_async.py | 264 ++++++++++ .../google/oauth2/_credentials_async.py | 108 ++++ .../google/oauth2/_id_token_async.py | 267 ++++++++++ .../google/oauth2/_service_account_async.py | 132 +++++ packages/google-auth/noxfile.py | 40 +- packages/google-auth/setup.py | 1 + packages/google-auth/system_tests/noxfile.py | 154 ++++-- .../system_tests_async/conftest.py | 108 ++++ .../system_tests_async/test_default.py | 30 ++ .../system_tests_async/test_id_token.py | 25 + .../test_service_account.py | 53 ++ .../{ => system_tests_sync}/.gitignore | 0 .../{ => system_tests_sync}/__init__.py | 0 .../app_engine_test_app/.gitignore | 0 .../app_engine_test_app/app.yaml | 0 .../app_engine_test_app/appengine_config.py | 0 .../app_engine_test_app/main.py | 0 .../app_engine_test_app/requirements.txt | 0 .../{ => system_tests_sync}/conftest.py | 3 +- .../{ => system_tests_sync}/secrets.tar.enc | Bin .../test_app_engine.py | 0 .../test_compute_engine.py | 0 .../{ => system_tests_sync}/test_default.py | 0 .../{ => system_tests_sync}/test_grpc.py | 0 .../{ => system_tests_sync}/test_id_token.py | 0 .../test_impersonated_credentials.py | 0 .../{ => system_tests_sync}/test_mtls_http.py | 0 .../test_oauth2_credentials.py | 0 .../test_service_account.py | 0 packages/google-auth/tests_async/__init__.py | 0 packages/google-auth/tests_async/conftest.py | 51 ++ .../tests_async/oauth2/test__client_async.py | 297 +++++++++++ .../oauth2/test_credentials_async.py | 478 ++++++++++++++++++ .../tests_async/oauth2/test_id_token.py | 205 ++++++++ .../oauth2/test_service_account_async.py | 372 ++++++++++++++ .../tests_async/test__default_async.py | 468 +++++++++++++++++ .../tests_async/test_credentials_async.py | 177 +++++++ .../google-auth/tests_async/test_jwt_async.py | 356 +++++++++++++ .../tests_async/transport/__init__.py | 0 .../tests_async/transport/async_compliance.py | 133 +++++ .../transport/test_aiohttp_requests.py | 245 +++++++++ 55 files changed, 4949 insertions(+), 63 deletions(-) create mode 100644 packages/google-auth/docs/reference/google.auth.credentials_async.rst create mode 100644 packages/google-auth/docs/reference/google.auth.jwt_async.rst create mode 100644 packages/google-auth/docs/reference/google.auth.transport.aiohttp_requests.rst create mode 100644 packages/google-auth/docs/reference/google.oauth2.credentials_async.rst create mode 100644 packages/google-auth/docs/reference/google.oauth2.service_account_async.rst create mode 100644 packages/google-auth/google/auth/_credentials_async.py create mode 100644 packages/google-auth/google/auth/_default_async.py create mode 100644 packages/google-auth/google/auth/_jwt_async.py create mode 100644 packages/google-auth/google/auth/transport/_aiohttp_requests.py create mode 100644 packages/google-auth/google/oauth2/_client_async.py create mode 100644 packages/google-auth/google/oauth2/_credentials_async.py create mode 100644 packages/google-auth/google/oauth2/_id_token_async.py create mode 100644 packages/google-auth/google/oauth2/_service_account_async.py create mode 100644 packages/google-auth/system_tests/system_tests_async/conftest.py create mode 100644 packages/google-auth/system_tests/system_tests_async/test_default.py create mode 100644 packages/google-auth/system_tests/system_tests_async/test_id_token.py create mode 100644 packages/google-auth/system_tests/system_tests_async/test_service_account.py rename packages/google-auth/system_tests/{ => system_tests_sync}/.gitignore (100%) rename packages/google-auth/system_tests/{ => system_tests_sync}/__init__.py (100%) rename packages/google-auth/system_tests/{ => system_tests_sync}/app_engine_test_app/.gitignore (100%) rename packages/google-auth/system_tests/{ => system_tests_sync}/app_engine_test_app/app.yaml (100%) rename packages/google-auth/system_tests/{ => system_tests_sync}/app_engine_test_app/appengine_config.py (100%) rename packages/google-auth/system_tests/{ => system_tests_sync}/app_engine_test_app/main.py (100%) rename packages/google-auth/system_tests/{ => system_tests_sync}/app_engine_test_app/requirements.txt (100%) rename packages/google-auth/system_tests/{ => system_tests_sync}/conftest.py (96%) rename packages/google-auth/system_tests/{ => system_tests_sync}/secrets.tar.enc (100%) rename packages/google-auth/system_tests/{ => system_tests_sync}/test_app_engine.py (100%) rename packages/google-auth/system_tests/{ => system_tests_sync}/test_compute_engine.py (100%) rename packages/google-auth/system_tests/{ => system_tests_sync}/test_default.py (100%) rename packages/google-auth/system_tests/{ => system_tests_sync}/test_grpc.py (100%) rename packages/google-auth/system_tests/{ => system_tests_sync}/test_id_token.py (100%) rename packages/google-auth/system_tests/{ => system_tests_sync}/test_impersonated_credentials.py (100%) rename packages/google-auth/system_tests/{ => system_tests_sync}/test_mtls_http.py (100%) rename packages/google-auth/system_tests/{ => system_tests_sync}/test_oauth2_credentials.py (100%) rename packages/google-auth/system_tests/{ => system_tests_sync}/test_service_account.py (100%) create mode 100644 packages/google-auth/tests_async/__init__.py create mode 100644 packages/google-auth/tests_async/conftest.py create mode 100644 packages/google-auth/tests_async/oauth2/test__client_async.py create mode 100644 packages/google-auth/tests_async/oauth2/test_credentials_async.py create mode 100644 packages/google-auth/tests_async/oauth2/test_id_token.py create mode 100644 packages/google-auth/tests_async/oauth2/test_service_account_async.py create mode 100644 packages/google-auth/tests_async/test__default_async.py create mode 100644 packages/google-auth/tests_async/test_credentials_async.py create mode 100644 packages/google-auth/tests_async/test_jwt_async.py create mode 100644 packages/google-auth/tests_async/transport/__init__.py create mode 100644 packages/google-auth/tests_async/transport/async_compliance.py create mode 100644 packages/google-auth/tests_async/transport/test_aiohttp_requests.py diff --git a/packages/google-auth/docs/reference/google.auth.credentials_async.rst b/packages/google-auth/docs/reference/google.auth.credentials_async.rst new file mode 100644 index 000000000000..683139aa533e --- /dev/null +++ b/packages/google-auth/docs/reference/google.auth.credentials_async.rst @@ -0,0 +1,7 @@ +google.auth.credentials\_async module +===================================== + +.. automodule:: google.auth._credentials_async + :members: + :inherited-members: + :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.auth.jwt_async.rst b/packages/google-auth/docs/reference/google.auth.jwt_async.rst new file mode 100644 index 000000000000..4e56a6ea3539 --- /dev/null +++ b/packages/google-auth/docs/reference/google.auth.jwt_async.rst @@ -0,0 +1,7 @@ +google.auth.jwt\_async module +============================= + +.. automodule:: google.auth.jwt_async + :members: + :inherited-members: + :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.auth.rst b/packages/google-auth/docs/reference/google.auth.rst index cfcf70357e30..3acf7dfb8d99 100644 --- a/packages/google-auth/docs/reference/google.auth.rst +++ b/packages/google-auth/docs/reference/google.auth.rst @@ -24,8 +24,10 @@ Submodules google.auth.app_engine google.auth.credentials + google.auth._credentials_async google.auth.environment_vars google.auth.exceptions google.auth.iam google.auth.impersonated_credentials google.auth.jwt + google.auth.jwt_async diff --git a/packages/google-auth/docs/reference/google.auth.transport.aiohttp_requests.rst b/packages/google-auth/docs/reference/google.auth.transport.aiohttp_requests.rst new file mode 100644 index 000000000000..44fc4e577514 --- /dev/null +++ b/packages/google-auth/docs/reference/google.auth.transport.aiohttp_requests.rst @@ -0,0 +1,7 @@ +google.auth.transport.aiohttp\_requests module +============================================== + +.. automodule:: google.auth.transport._aiohttp_requests + :members: + :inherited-members: + :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.auth.transport.rst b/packages/google-auth/docs/reference/google.auth.transport.rst index 89218632becd..f1d198855d50 100644 --- a/packages/google-auth/docs/reference/google.auth.transport.rst +++ b/packages/google-auth/docs/reference/google.auth.transport.rst @@ -12,6 +12,7 @@ Submodules .. toctree:: :maxdepth: 4 + google.auth.transport._aiohttp_requests google.auth.transport.grpc google.auth.transport.mtls google.auth.transport.requests diff --git a/packages/google-auth/docs/reference/google.oauth2.credentials_async.rst b/packages/google-auth/docs/reference/google.oauth2.credentials_async.rst new file mode 100644 index 000000000000..d0df1e8a38e1 --- /dev/null +++ b/packages/google-auth/docs/reference/google.oauth2.credentials_async.rst @@ -0,0 +1,7 @@ +google.oauth2.credentials\_async module +======================================= + +.. automodule:: google.oauth2._credentials_async + :members: + :inherited-members: + :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.oauth2.rst b/packages/google-auth/docs/reference/google.oauth2.rst index 1ac9c7320567..6f3ba50c217c 100644 --- a/packages/google-auth/docs/reference/google.oauth2.rst +++ b/packages/google-auth/docs/reference/google.oauth2.rst @@ -13,5 +13,7 @@ Submodules :maxdepth: 4 google.oauth2.credentials + google.oauth2._credentials_async google.oauth2.id_token google.oauth2.service_account + google.oauth2._service_account_async diff --git a/packages/google-auth/docs/reference/google.oauth2.service_account_async.rst b/packages/google-auth/docs/reference/google.oauth2.service_account_async.rst new file mode 100644 index 000000000000..8aba0d8516f1 --- /dev/null +++ b/packages/google-auth/docs/reference/google.oauth2.service_account_async.rst @@ -0,0 +1,7 @@ +google.oauth2.service\_account\_async module +============================================ + +.. automodule:: google.oauth2._service_account_async + :members: + :inherited-members: + :show-inheritance: diff --git a/packages/google-auth/google/auth/__init__.py b/packages/google-auth/google/auth/__init__.py index 5ca20a36297d..22d61c66fe0c 100644 --- a/packages/google-auth/google/auth/__init__.py +++ b/packages/google-auth/google/auth/__init__.py @@ -18,9 +18,7 @@ from google.auth._default import default, load_credentials_from_file - __all__ = ["default", "load_credentials_from_file"] - # Set default logging handler to avoid "No handler found" warnings. logging.getLogger(__name__).addHandler(logging.NullHandler()) diff --git a/packages/google-auth/google/auth/_credentials_async.py b/packages/google-auth/google/auth/_credentials_async.py new file mode 100644 index 000000000000..d4d4e2c0e45f --- /dev/null +++ b/packages/google-auth/google/auth/_credentials_async.py @@ -0,0 +1,176 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +"""Interfaces for credentials.""" + +import abc +import inspect + +import six + +from google.auth import credentials + + +@six.add_metaclass(abc.ABCMeta) +class Credentials(credentials.Credentials): + """Async inherited credentials class from google.auth.credentials. + The added functionality is the before_request call which requires + async/await syntax. + All credentials have a :attr:`token` that is used for authentication and + may also optionally set an :attr:`expiry` to indicate when the token will + no longer be valid. + + Most credentials will be :attr:`invalid` until :meth:`refresh` is called. + Credentials can do this automatically before the first HTTP request in + :meth:`before_request`. + + Although the token and expiration will change as the credentials are + :meth:`refreshed ` and used, credentials should be considered + immutable. Various credentials will accept configuration such as private + keys, scopes, and other options. These options are not changeable after + construction. Some classes will provide mechanisms to copy the credentials + with modifications such as :meth:`ScopedCredentials.with_scopes`. + """ + + async def before_request(self, request, method, url, headers): + """Performs credential-specific before request logic. + + Refreshes the credentials if necessary, then calls :meth:`apply` to + apply the token to the authentication header. + + Args: + request (google.auth.transport.Request): The object used to make + HTTP requests. + method (str): The request's HTTP method or the RPC method being + invoked. + url (str): The request's URI or the RPC service's URI. + headers (Mapping): The request's headers. + """ + # pylint: disable=unused-argument + # (Subclasses may use these arguments to ascertain information about + # the http request.) + + if not self.valid: + if inspect.iscoroutinefunction(self.refresh): + await self.refresh(request) + else: + self.refresh(request) + self.apply(headers) + + +class CredentialsWithQuotaProject(credentials.CredentialsWithQuotaProject): + """Abstract base for credentials supporting ``with_quota_project`` factory""" + + +class AnonymousCredentials(credentials.AnonymousCredentials, Credentials): + """Credentials that do not provide any authentication information. + + These are useful in the case of services that support anonymous access or + local service emulators that do not use credentials. This class inherits + from the sync anonymous credentials file, but is kept if async credentials + is initialized and we would like anonymous credentials. + """ + + +@six.add_metaclass(abc.ABCMeta) +class ReadOnlyScoped(credentials.ReadOnlyScoped): + """Interface for credentials whose scopes can be queried. + + OAuth 2.0-based credentials allow limiting access using scopes as described + in `RFC6749 Section 3.3`_. + If a credential class implements this interface then the credentials either + use scopes in their implementation. + + Some credentials require scopes in order to obtain a token. You can check + if scoping is necessary with :attr:`requires_scopes`:: + + if credentials.requires_scopes: + # Scoping is required. + credentials = _credentials_async.with_scopes(scopes=['one', 'two']) + + Credentials that require scopes must either be constructed with scopes:: + + credentials = SomeScopedCredentials(scopes=['one', 'two']) + + Or must copy an existing instance using :meth:`with_scopes`:: + + scoped_credentials = _credentials_async.with_scopes(scopes=['one', 'two']) + + Some credentials have scopes but do not allow or require scopes to be set, + these credentials can be used as-is. + + .. _RFC6749 Section 3.3: https://tools.ietf.org/html/rfc6749#section-3.3 + """ + + +class Scoped(credentials.Scoped): + """Interface for credentials whose scopes can be replaced while copying. + + OAuth 2.0-based credentials allow limiting access using scopes as described + in `RFC6749 Section 3.3`_. + If a credential class implements this interface then the credentials either + use scopes in their implementation. + + Some credentials require scopes in order to obtain a token. You can check + if scoping is necessary with :attr:`requires_scopes`:: + + if credentials.requires_scopes: + # Scoping is required. + credentials = _credentials_async.create_scoped(['one', 'two']) + + Credentials that require scopes must either be constructed with scopes:: + + credentials = SomeScopedCredentials(scopes=['one', 'two']) + + Or must copy an existing instance using :meth:`with_scopes`:: + + scoped_credentials = credentials.with_scopes(scopes=['one', 'two']) + + Some credentials have scopes but do not allow or require scopes to be set, + these credentials can be used as-is. + + .. _RFC6749 Section 3.3: https://tools.ietf.org/html/rfc6749#section-3.3 + """ + + +def with_scopes_if_required(credentials, scopes): + """Creates a copy of the credentials with scopes if scoping is required. + + This helper function is useful when you do not know (or care to know) the + specific type of credentials you are using (such as when you use + :func:`google.auth.default`). This function will call + :meth:`Scoped.with_scopes` if the credentials are scoped credentials and if + the credentials require scoping. Otherwise, it will return the credentials + as-is. + + Args: + credentials (google.auth.credentials.Credentials): The credentials to + scope if necessary. + scopes (Sequence[str]): The list of scopes to use. + + Returns: + google.auth._credentials_async.Credentials: Either a new set of scoped + credentials, or the passed in credentials instance if no scoping + was required. + """ + if isinstance(credentials, Scoped) and credentials.requires_scopes: + return credentials.with_scopes(scopes) + else: + return credentials + + +@six.add_metaclass(abc.ABCMeta) +class Signing(credentials.Signing): + """Interface for credentials that can cryptographically sign messages.""" diff --git a/packages/google-auth/google/auth/_default_async.py b/packages/google-auth/google/auth/_default_async.py new file mode 100644 index 000000000000..3347fbfdc37f --- /dev/null +++ b/packages/google-auth/google/auth/_default_async.py @@ -0,0 +1,266 @@ +# Copyright 2020 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Application default credentials. + +Implements application default credentials and project ID detection. +""" + +import io +import json +import os + +import six + +from google.auth import _default +from google.auth import environment_vars +from google.auth import exceptions + + +def load_credentials_from_file(filename, scopes=None, quota_project_id=None): + """Loads Google credentials from a file. + + The credentials file must be a service account key or stored authorized + user credentials. + + Args: + filename (str): The full path to the credentials file. + scopes (Optional[Sequence[str]]): The list of scopes for the credentials. If + specified, the credentials will automatically be scoped if + necessary + quota_project_id (Optional[str]): The project ID used for + quota and billing. + + Returns: + Tuple[google.auth.credentials.Credentials, Optional[str]]: Loaded + credentials and the project ID. Authorized user credentials do not + have the project ID information. + + Raises: + google.auth.exceptions.DefaultCredentialsError: if the file is in the + wrong format or is missing. + """ + if not os.path.exists(filename): + raise exceptions.DefaultCredentialsError( + "File {} was not found.".format(filename) + ) + + with io.open(filename, "r") as file_obj: + try: + info = json.load(file_obj) + except ValueError as caught_exc: + new_exc = exceptions.DefaultCredentialsError( + "File {} is not a valid json file.".format(filename), caught_exc + ) + six.raise_from(new_exc, caught_exc) + + # The type key should indicate that the file is either a service account + # credentials file or an authorized user credentials file. + credential_type = info.get("type") + + if credential_type == _default._AUTHORIZED_USER_TYPE: + from google.oauth2 import _credentials_async as credentials + + try: + credentials = credentials.Credentials.from_authorized_user_info( + info, scopes=scopes + ).with_quota_project(quota_project_id) + except ValueError as caught_exc: + msg = "Failed to load authorized user credentials from {}".format(filename) + new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) + six.raise_from(new_exc, caught_exc) + if not credentials.quota_project_id: + _default._warn_about_problematic_credentials(credentials) + return credentials, None + + elif credential_type == _default._SERVICE_ACCOUNT_TYPE: + from google.oauth2 import _service_account_async as service_account + + try: + credentials = service_account.Credentials.from_service_account_info( + info, scopes=scopes + ).with_quota_project(quota_project_id) + except ValueError as caught_exc: + msg = "Failed to load service account credentials from {}".format(filename) + new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) + six.raise_from(new_exc, caught_exc) + return credentials, info.get("project_id") + + else: + raise exceptions.DefaultCredentialsError( + "The file {file} does not have a valid type. " + "Type is {type}, expected one of {valid_types}.".format( + file=filename, type=credential_type, valid_types=_default._VALID_TYPES + ) + ) + + +def _get_gcloud_sdk_credentials(): + """Gets the credentials and project ID from the Cloud SDK.""" + from google.auth import _cloud_sdk + + # Check if application default credentials exist. + credentials_filename = _cloud_sdk.get_application_default_credentials_path() + + if not os.path.isfile(credentials_filename): + return None, None + + credentials, project_id = load_credentials_from_file(credentials_filename) + + if not project_id: + project_id = _cloud_sdk.get_project_id() + + return credentials, project_id + + +def _get_explicit_environ_credentials(): + """Gets credentials from the GOOGLE_APPLICATION_CREDENTIALS environment + variable.""" + explicit_file = os.environ.get(environment_vars.CREDENTIALS) + + if explicit_file is not None: + credentials, project_id = load_credentials_from_file( + os.environ[environment_vars.CREDENTIALS] + ) + + return credentials, project_id + + else: + return None, None + + +def _get_gae_credentials(): + """Gets Google App Engine App Identity credentials and project ID.""" + # While this library is normally bundled with app_engine, there are + # some cases where it's not available, so we tolerate ImportError. + + return _default._get_gae_credentials() + + +def _get_gce_credentials(request=None): + """Gets credentials and project ID from the GCE Metadata Service.""" + # Ping requires a transport, but we want application default credentials + # to require no arguments. So, we'll use the _http_client transport which + # uses http.client. This is only acceptable because the metadata server + # doesn't do SSL and never requires proxies. + + # While this library is normally bundled with compute_engine, there are + # some cases where it's not available, so we tolerate ImportError. + + return _default._get_gce_credentials(request) + + +def default_async(scopes=None, request=None, quota_project_id=None): + """Gets the default credentials for the current environment. + + `Application Default Credentials`_ provides an easy way to obtain + credentials to call Google APIs for server-to-server or local applications. + This function acquires credentials from the environment in the following + order: + + 1. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set + to the path of a valid service account JSON private key file, then it is + loaded and returned. The project ID returned is the project ID defined + in the service account file if available (some older files do not + contain project ID information). + 2. If the `Google Cloud SDK`_ is installed and has application default + credentials set they are loaded and returned. + + To enable application default credentials with the Cloud SDK run:: + + gcloud auth application-default login + + If the Cloud SDK has an active project, the project ID is returned. The + active project can be set using:: + + gcloud config set project + + 3. If the application is running in the `App Engine standard environment`_ + then the credentials and project ID from the `App Identity Service`_ + are used. + 4. If the application is running in `Compute Engine`_ or the + `App Engine flexible environment`_ then the credentials and project ID + are obtained from the `Metadata Service`_. + 5. If no credentials are found, + :class:`~google.auth.exceptions.DefaultCredentialsError` will be raised. + + .. _Application Default Credentials: https://developers.google.com\ + /identity/protocols/application-default-credentials + .. _Google Cloud SDK: https://cloud.google.com/sdk + .. _App Engine standard environment: https://cloud.google.com/appengine + .. _App Identity Service: https://cloud.google.com/appengine/docs/python\ + /appidentity/ + .. _Compute Engine: https://cloud.google.com/compute + .. _App Engine flexible environment: https://cloud.google.com\ + /appengine/flexible + .. _Metadata Service: https://cloud.google.com/compute/docs\ + /storing-retrieving-metadata + + Example:: + + import google.auth + + credentials, project_id = google.auth.default() + + Args: + scopes (Sequence[str]): The list of scopes for the credentials. If + specified, the credentials will automatically be scoped if + necessary. + request (google.auth.transport.Request): An object used to make + HTTP requests. This is used to detect whether the application + is running on Compute Engine. If not specified, then it will + use the standard library http client to make requests. + quota_project_id (Optional[str]): The project ID used for + quota and billing. + Returns: + Tuple[~google.auth.credentials.Credentials, Optional[str]]: + the current environment's credentials and project ID. Project ID + may be None, which indicates that the Project ID could not be + ascertained from the environment. + + Raises: + ~google.auth.exceptions.DefaultCredentialsError: + If no credentials were found, or if the credentials found were + invalid. + """ + from google.auth._credentials_async import with_scopes_if_required + + explicit_project_id = os.environ.get( + environment_vars.PROJECT, os.environ.get(environment_vars.LEGACY_PROJECT) + ) + + checkers = ( + _get_explicit_environ_credentials, + _get_gcloud_sdk_credentials, + _get_gae_credentials, + lambda: _get_gce_credentials(request), + ) + + for checker in checkers: + credentials, project_id = checker() + if credentials is not None: + credentials = with_scopes_if_required( + credentials, scopes + ).with_quota_project(quota_project_id) + effective_project_id = explicit_project_id or project_id + if not effective_project_id: + _default._LOGGER.warning( + "No project ID could be determined. Consider running " + "`gcloud config set project` or setting the %s " + "environment variable", + environment_vars.PROJECT, + ) + return credentials, effective_project_id + + raise exceptions.DefaultCredentialsError(_default._HELP_MESSAGE) diff --git a/packages/google-auth/google/auth/_jwt_async.py b/packages/google-auth/google/auth/_jwt_async.py new file mode 100644 index 000000000000..49e3026e5315 --- /dev/null +++ b/packages/google-auth/google/auth/_jwt_async.py @@ -0,0 +1,168 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""JSON Web Tokens + +Provides support for creating (encoding) and verifying (decoding) JWTs, +especially JWTs generated and consumed by Google infrastructure. + +See `rfc7519`_ for more details on JWTs. + +To encode a JWT use :func:`encode`:: + + from google.auth import crypt + from google.auth import jwt_async + + signer = crypt.Signer(private_key) + payload = {'some': 'payload'} + encoded = jwt_async.encode(signer, payload) + +To decode a JWT and verify claims use :func:`decode`:: + + claims = jwt_async.decode(encoded, certs=public_certs) + +You can also skip verification:: + + claims = jwt_async.decode(encoded, verify=False) + +.. _rfc7519: https://tools.ietf.org/html/rfc7519 + + +NOTE: This async support is experimental and marked internal. This surface may +change in minor releases. +""" + +import google.auth +from google.auth import jwt + + +def encode(signer, payload, header=None, key_id=None): + """Make a signed JWT. + + Args: + signer (google.auth.crypt.Signer): The signer used to sign the JWT. + payload (Mapping[str, str]): The JWT payload. + header (Mapping[str, str]): Additional JWT header payload. + key_id (str): The key id to add to the JWT header. If the + signer has a key id it will be used as the default. If this is + specified it will override the signer's key id. + + Returns: + bytes: The encoded JWT. + """ + return jwt.encode(signer, payload, header, key_id) + + +def decode(token, certs=None, verify=True, audience=None): + """Decode and verify a JWT. + + Args: + token (str): The encoded JWT. + certs (Union[str, bytes, Mapping[str, Union[str, bytes]]]): The + certificate used to validate the JWT signature. If bytes or string, + it must the the public key certificate in PEM format. If a mapping, + it must be a mapping of key IDs to public key certificates in PEM + format. The mapping must contain the same key ID that's specified + in the token's header. + verify (bool): Whether to perform signature and claim validation. + Verification is done by default. + audience (str): The audience claim, 'aud', that this JWT should + contain. If None then the JWT's 'aud' parameter is not verified. + + Returns: + Mapping[str, str]: The deserialized JSON payload in the JWT. + + Raises: + ValueError: if any verification checks failed. + """ + + return jwt.decode(token, certs, verify, audience) + + +class Credentials( + jwt.Credentials, + google.auth._credentials_async.Signing, + google.auth._credentials_async.Credentials, +): + """Credentials that use a JWT as the bearer token. + + These credentials require an "audience" claim. This claim identifies the + intended recipient of the bearer token. + + The constructor arguments determine the claims for the JWT that is + sent with requests. Usually, you'll construct these credentials with + one of the helper constructors as shown in the next section. + + To create JWT credentials using a Google service account private key + JSON file:: + + audience = 'https://pubsub.googleapis.com/google.pubsub.v1.Publisher' + credentials = jwt_async.Credentials.from_service_account_file( + 'service-account.json', + audience=audience) + + If you already have the service account file loaded and parsed:: + + service_account_info = json.load(open('service_account.json')) + credentials = jwt_async.Credentials.from_service_account_info( + service_account_info, + audience=audience) + + Both helper methods pass on arguments to the constructor, so you can + specify the JWT claims:: + + credentials = jwt_async.Credentials.from_service_account_file( + 'service-account.json', + audience=audience, + additional_claims={'meta': 'data'}) + + You can also construct the credentials directly if you have a + :class:`~google.auth.crypt.Signer` instance:: + + credentials = jwt_async.Credentials( + signer, + issuer='your-issuer', + subject='your-subject', + audience=audience) + + The claims are considered immutable. If you want to modify the claims, + you can easily create another instance using :meth:`with_claims`:: + + new_audience = ( + 'https://pubsub.googleapis.com/google.pubsub.v1.Subscriber') + new_credentials = credentials.with_claims(audience=new_audience) + """ + + +class OnDemandCredentials( + jwt.OnDemandCredentials, + google.auth._credentials_async.Signing, + google.auth._credentials_async.Credentials, +): + """On-demand JWT credentials. + + Like :class:`Credentials`, this class uses a JWT as the bearer token for + authentication. However, this class does not require the audience at + construction time. Instead, it will generate a new token on-demand for + each request using the request URI as the audience. It caches tokens + so that multiple requests to the same URI do not incur the overhead + of generating a new token every time. + + This behavior is especially useful for `gRPC`_ clients. A gRPC service may + have multiple audience and gRPC clients may not know all of the audiences + required for accessing a particular service. With these credentials, + no knowledge of the audiences is required ahead of time. + + .. _grpc: http://www.grpc.io/ + """ diff --git a/packages/google-auth/google/auth/transport/_aiohttp_requests.py b/packages/google-auth/google/auth/transport/_aiohttp_requests.py new file mode 100644 index 000000000000..aaf4e2c0b23e --- /dev/null +++ b/packages/google-auth/google/auth/transport/_aiohttp_requests.py @@ -0,0 +1,384 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Transport adapter for Async HTTP (aiohttp). + +NOTE: This async support is experimental and marked internal. This surface may +change in minor releases. +""" + +from __future__ import absolute_import + +import asyncio +import functools + +import aiohttp +import six +import urllib3 + +from google.auth import exceptions +from google.auth import transport +from google.auth.transport import requests + +# Timeout can be re-defined depending on async requirement. Currently made 60s more than +# sync timeout. +_DEFAULT_TIMEOUT = 180 # in seconds + + +class _CombinedResponse(transport.Response): + """ + In order to more closely resemble the `requests` interface, where a raw + and deflated content could be accessed at once, this class lazily reads the + stream in `transport.Response` so both return forms can be used. + + The gzip and deflate transfer-encodings are automatically decoded for you + because the default parameter for autodecompress into the ClientSession is set + to False, and therefore we add this class to act as a wrapper for a user to be + able to access both the raw and decoded response bodies - mirroring the sync + implementation. + """ + + def __init__(self, response): + self._response = response + self._raw_content = None + + def _is_compressed(self): + headers = self._response.headers + return "Content-Encoding" in headers and ( + headers["Content-Encoding"] == "gzip" + or headers["Content-Encoding"] == "deflate" + ) + + @property + def status(self): + return self._response.status + + @property + def headers(self): + return self._response.headers + + @property + def data(self): + return self._response.content + + async def raw_content(self): + if self._raw_content is None: + self._raw_content = await self._response.content.read() + return self._raw_content + + async def content(self): + # Load raw_content if necessary + await self.raw_content() + if self._is_compressed(): + decoder = urllib3.response.MultiDecoder( + self._response.headers["Content-Encoding"] + ) + decompressed = decoder.decompress(self._raw_content) + return decompressed + + return self._raw_content + + +class _Response(transport.Response): + """ + Requests transport response adapter. + + Args: + response (requests.Response): The raw Requests response. + """ + + def __init__(self, response): + self._response = response + + @property + def status(self): + return self._response.status + + @property + def headers(self): + return self._response.headers + + @property + def data(self): + return self._response.content + + +class Request(transport.Request): + """Requests request adapter. + + This class is used internally for making requests using asyncio transports + in a consistent way. If you use :class:`AuthorizedSession` you do not need + to construct or use this class directly. + + This class can be useful if you want to manually refresh a + :class:`~google.auth.credentials.Credentials` instance:: + + import google.auth.transport.aiohttp_requests + + request = google.auth.transport.aiohttp_requests.Request() + + credentials.refresh(request) + + Args: + session (aiohttp.ClientSession): An instance :class: aiohttp.ClientSession used + to make HTTP requests. If not specified, a session will be created. + + .. automethod:: __call__ + """ + + def __init__(self, session=None): + self.session = None + + async def __call__( + self, + url, + method="GET", + body=None, + headers=None, + timeout=_DEFAULT_TIMEOUT, + **kwargs, + ): + """ + Make an HTTP request using aiohttp. + + Args: + url (str): The URL to be requested. + method (str): The HTTP method to use for the request. Defaults + to 'GET'. + body (bytes): The payload / body in HTTP request. + headers (Mapping[str, str]): Request headers. + timeout (Optional[int]): The number of seconds to wait for a + response from the server. If not specified or if None, the + requests default timeout will be used. + kwargs: Additional arguments passed through to the underlying + requests :meth:`~requests.Session.request` method. + + Returns: + google.auth.transport.Response: The HTTP response. + + Raises: + google.auth.exceptions.TransportError: If any exception occurred. + """ + + try: + if self.session is None: # pragma: NO COVER + self.session = aiohttp.ClientSession( + auto_decompress=False + ) # pragma: NO COVER + requests._LOGGER.debug("Making request: %s %s", method, url) + response = await self.session.request( + method, url, data=body, headers=headers, timeout=timeout, **kwargs + ) + return _CombinedResponse(response) + + except aiohttp.ClientError as caught_exc: + new_exc = exceptions.TransportError(caught_exc) + six.raise_from(new_exc, caught_exc) + + except asyncio.TimeoutError as caught_exc: + new_exc = exceptions.TransportError(caught_exc) + six.raise_from(new_exc, caught_exc) + + +class AuthorizedSession(aiohttp.ClientSession): + """This is an async implementation of the Authorized Session class. We utilize an + aiohttp transport instance, and the interface mirrors the google.auth.transport.requests + Authorized Session class, except for the change in the transport used in the async use case. + + A Requests Session class with credentials. + + This class is used to perform requests to API endpoints that require + authorization:: + + from google.auth.transport import aiohttp_requests + + async with aiohttp_requests.AuthorizedSession(credentials) as authed_session: + response = await authed_session.request( + 'GET', 'https://www.googleapis.com/storage/v1/b') + + The underlying :meth:`request` implementation handles adding the + credentials' headers to the request and refreshing credentials as needed. + + Args: + credentials (google.auth._credentials_async.Credentials): The credentials to + add to the request. + refresh_status_codes (Sequence[int]): Which HTTP status codes indicate + that credentials should be refreshed and the request should be + retried. + max_refresh_attempts (int): The maximum number of times to attempt to + refresh the credentials and retry the request. + refresh_timeout (Optional[int]): The timeout value in seconds for + credential refresh HTTP requests. + auth_request (google.auth.transport.aiohttp_requests.Request): + (Optional) An instance of + :class:`~google.auth.transport.aiohttp_requests.Request` used when + refreshing credentials. If not passed, + an instance of :class:`~google.auth.transport.aiohttp_requests.Request` + is created. + """ + + def __init__( + self, + credentials, + refresh_status_codes=transport.DEFAULT_REFRESH_STATUS_CODES, + max_refresh_attempts=transport.DEFAULT_MAX_REFRESH_ATTEMPTS, + refresh_timeout=None, + auth_request=None, + auto_decompress=False, + ): + super(AuthorizedSession, self).__init__() + self.credentials = credentials + self._refresh_status_codes = refresh_status_codes + self._max_refresh_attempts = max_refresh_attempts + self._refresh_timeout = refresh_timeout + self._is_mtls = False + self._auth_request = auth_request + self._auth_request_session = None + self._loop = asyncio.get_event_loop() + self._refresh_lock = asyncio.Lock() + self._auto_decompress = auto_decompress + + async def request( + self, + method, + url, + data=None, + headers=None, + max_allowed_time=None, + timeout=_DEFAULT_TIMEOUT, + auto_decompress=False, + **kwargs, + ): + + """Implementation of Authorized Session aiohttp request. + + Args: + method: The http request method used (e.g. GET, PUT, DELETE) + + url: The url at which the http request is sent. + + data, headers: These fields parallel the associated data and headers + fields of a regular http request. Using the aiohttp client session to + send the http request allows us to use this parallel corresponding structure + in our Authorized Session class. + + timeout (Optional[Union[float, aiohttp.ClientTimeout]]): + The amount of time in seconds to wait for the server response + with each individual request. + + Can also be passed as an `aiohttp.ClientTimeout` object. + + max_allowed_time (Optional[float]): + If the method runs longer than this, a ``Timeout`` exception is + automatically raised. Unlike the ``timeout` parameter, this + value applies to the total method execution time, even if + multiple requests are made under the hood. + + Mind that it is not guaranteed that the timeout error is raised + at ``max_allowed_time`. It might take longer, for example, if + an underlying request takes a lot of time, but the request + itself does not timeout, e.g. if a large file is being + transmitted. The timout error will be raised after such + request completes. + """ + # Headers come in as bytes which isn't expected behavior, the resumable + # media libraries in some cases expect a str type for the header values, + # but sometimes the operations return these in bytes types. + if headers: + for key in headers.keys(): + if type(headers[key]) is bytes: + headers[key] = headers[key].decode("utf-8") + + async with aiohttp.ClientSession( + auto_decompress=self._auto_decompress + ) as self._auth_request_session: + auth_request = Request(self._auth_request_session) + self._auth_request = auth_request + + # Use a kwarg for this instead of an attribute to maintain + # thread-safety. + _credential_refresh_attempt = kwargs.pop("_credential_refresh_attempt", 0) + # Make a copy of the headers. They will be modified by the credentials + # and we want to pass the original headers if we recurse. + request_headers = headers.copy() if headers is not None else {} + + # Do not apply the timeout unconditionally in order to not override the + # _auth_request's default timeout. + auth_request = ( + self._auth_request + if timeout is None + else functools.partial(self._auth_request, timeout=timeout) + ) + + remaining_time = max_allowed_time + + with requests.TimeoutGuard(remaining_time, asyncio.TimeoutError) as guard: + await self.credentials.before_request( + auth_request, method, url, request_headers + ) + + with requests.TimeoutGuard(remaining_time, asyncio.TimeoutError) as guard: + response = await super(AuthorizedSession, self).request( + method, + url, + data=data, + headers=request_headers, + timeout=timeout, + **kwargs, + ) + + remaining_time = guard.remaining_timeout + + if ( + response.status in self._refresh_status_codes + and _credential_refresh_attempt < self._max_refresh_attempts + ): + + requests._LOGGER.info( + "Refreshing credentials due to a %s response. Attempt %s/%s.", + response.status, + _credential_refresh_attempt + 1, + self._max_refresh_attempts, + ) + + # Do not apply the timeout unconditionally in order to not override the + # _auth_request's default timeout. + auth_request = ( + self._auth_request + if timeout is None + else functools.partial(self._auth_request, timeout=timeout) + ) + + with requests.TimeoutGuard( + remaining_time, asyncio.TimeoutError + ) as guard: + async with self._refresh_lock: + await self._loop.run_in_executor( + None, self.credentials.refresh, auth_request + ) + + remaining_time = guard.remaining_timeout + + return await self.request( + method, + url, + data=data, + headers=headers, + max_allowed_time=remaining_time, + timeout=timeout, + _credential_refresh_attempt=_credential_refresh_attempt + 1, + **kwargs, + ) + + return response diff --git a/packages/google-auth/google/auth/transport/mtls.py b/packages/google-auth/google/auth/transport/mtls.py index 5b742306b54d..b40bfbedf97d 100644 --- a/packages/google-auth/google/auth/transport/mtls.py +++ b/packages/google-auth/google/auth/transport/mtls.py @@ -86,9 +86,12 @@ def default_client_encrypted_cert_source(cert_path, key_path): def callback(): try: - _, cert_bytes, key_bytes, passphrase_bytes = _mtls_helper.get_client_ssl_credentials( - generate_encrypted_key=True - ) + ( + _, + cert_bytes, + key_bytes, + passphrase_bytes, + ) = _mtls_helper.get_client_ssl_credentials(generate_encrypted_key=True) with open(cert_path, "wb") as cert_file: cert_file.write(cert_bytes) with open(key_path, "wb") as key_file: diff --git a/packages/google-auth/google/oauth2/_client_async.py b/packages/google-auth/google/oauth2/_client_async.py new file mode 100644 index 000000000000..4817ea40e559 --- /dev/null +++ b/packages/google-auth/google/oauth2/_client_async.py @@ -0,0 +1,264 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""OAuth 2.0 async client. + +This is a client for interacting with an OAuth 2.0 authorization server's +token endpoint. + +For more information about the token endpoint, see +`Section 3.1 of rfc6749`_ + +.. _Section 3.1 of rfc6749: https://tools.ietf.org/html/rfc6749#section-3.2 +""" + +import datetime +import json + +import six +from six.moves import http_client +from six.moves import urllib + +from google.auth import _helpers +from google.auth import exceptions +from google.auth import jwt +from google.oauth2 import _client as client + + +def _handle_error_response(response_body): + """"Translates an error response into an exception. + + Args: + response_body (str): The decoded response data. + + Raises: + google.auth.exceptions.RefreshError + """ + try: + error_data = json.loads(response_body) + error_details = "{}: {}".format( + error_data["error"], error_data.get("error_description") + ) + # If no details could be extracted, use the response data. + except (KeyError, ValueError): + error_details = response_body + + raise exceptions.RefreshError(error_details, response_body) + + +def _parse_expiry(response_data): + """Parses the expiry field from a response into a datetime. + + Args: + response_data (Mapping): The JSON-parsed response data. + + Returns: + Optional[datetime]: The expiration or ``None`` if no expiration was + specified. + """ + expires_in = response_data.get("expires_in", None) + + if expires_in is not None: + return _helpers.utcnow() + datetime.timedelta(seconds=expires_in) + else: + return None + + +async def _token_endpoint_request(request, token_uri, body): + """Makes a request to the OAuth 2.0 authorization server's token endpoint. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + token_uri (str): The OAuth 2.0 authorizations server's token endpoint + URI. + body (Mapping[str, str]): The parameters to send in the request body. + + Returns: + Mapping[str, str]: The JSON-decoded response data. + + Raises: + google.auth.exceptions.RefreshError: If the token endpoint returned + an error. + """ + body = urllib.parse.urlencode(body).encode("utf-8") + headers = {"content-type": client._URLENCODED_CONTENT_TYPE} + + retry = 0 + # retry to fetch token for maximum of two times if any internal failure + # occurs. + while True: + + response = await request( + method="POST", url=token_uri, headers=headers, body=body + ) + + # Using data.read() resulted in zlib decompression errors. This may require future investigation. + response_body1 = await response.content() + + response_body = ( + response_body1.decode("utf-8") + if hasattr(response_body1, "decode") + else response_body1 + ) + + response_data = json.loads(response_body) + + if response.status == http_client.OK: + break + else: + error_desc = response_data.get("error_description") or "" + error_code = response_data.get("error") or "" + if ( + any(e == "internal_failure" for e in (error_code, error_desc)) + and retry < 1 + ): + retry += 1 + continue + _handle_error_response(response_body) + + return response_data + + +async def jwt_grant(request, token_uri, assertion): + """Implements the JWT Profile for OAuth 2.0 Authorization Grants. + + For more details, see `rfc7523 section 4`_. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + token_uri (str): The OAuth 2.0 authorizations server's token endpoint + URI. + assertion (str): The OAuth 2.0 assertion. + + Returns: + Tuple[str, Optional[datetime], Mapping[str, str]]: The access token, + expiration, and additional data returned by the token endpoint. + + Raises: + google.auth.exceptions.RefreshError: If the token endpoint returned + an error. + + .. _rfc7523 section 4: https://tools.ietf.org/html/rfc7523#section-4 + """ + body = {"assertion": assertion, "grant_type": client._JWT_GRANT_TYPE} + + response_data = await _token_endpoint_request(request, token_uri, body) + + try: + access_token = response_data["access_token"] + except KeyError as caught_exc: + new_exc = exceptions.RefreshError("No access token in response.", response_data) + six.raise_from(new_exc, caught_exc) + + expiry = _parse_expiry(response_data) + + return access_token, expiry, response_data + + +async def id_token_jwt_grant(request, token_uri, assertion): + """Implements the JWT Profile for OAuth 2.0 Authorization Grants, but + requests an OpenID Connect ID Token instead of an access token. + + This is a variant on the standard JWT Profile that is currently unique + to Google. This was added for the benefit of authenticating to services + that require ID Tokens instead of access tokens or JWT bearer tokens. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + token_uri (str): The OAuth 2.0 authorization server's token endpoint + URI. + assertion (str): JWT token signed by a service account. The token's + payload must include a ``target_audience`` claim. + + Returns: + Tuple[str, Optional[datetime], Mapping[str, str]]: + The (encoded) Open ID Connect ID Token, expiration, and additional + data returned by the endpoint. + + Raises: + google.auth.exceptions.RefreshError: If the token endpoint returned + an error. + """ + body = {"assertion": assertion, "grant_type": client._JWT_GRANT_TYPE} + + response_data = await _token_endpoint_request(request, token_uri, body) + + try: + id_token = response_data["id_token"] + except KeyError as caught_exc: + new_exc = exceptions.RefreshError("No ID token in response.", response_data) + six.raise_from(new_exc, caught_exc) + + payload = jwt.decode(id_token, verify=False) + expiry = datetime.datetime.utcfromtimestamp(payload["exp"]) + + return id_token, expiry, response_data + + +async def refresh_grant( + request, token_uri, refresh_token, client_id, client_secret, scopes=None +): + """Implements the OAuth 2.0 refresh token grant. + + For more details, see `rfc678 section 6`_. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + token_uri (str): The OAuth 2.0 authorizations server's token endpoint + URI. + refresh_token (str): The refresh token to use to get a new access + token. + client_id (str): The OAuth 2.0 application's client ID. + client_secret (str): The Oauth 2.0 appliaction's client secret. + scopes (Optional(Sequence[str])): Scopes to request. If present, all + scopes must be authorized for the refresh token. Useful if refresh + token has a wild card scope (e.g. + 'https://www.googleapis.com/auth/any-api'). + + Returns: + Tuple[str, Optional[str], Optional[datetime], Mapping[str, str]]: The + access token, new refresh token, expiration, and additional data + returned by the token endpoint. + + Raises: + google.auth.exceptions.RefreshError: If the token endpoint returned + an error. + + .. _rfc6748 section 6: https://tools.ietf.org/html/rfc6749#section-6 + """ + body = { + "grant_type": client._REFRESH_GRANT_TYPE, + "client_id": client_id, + "client_secret": client_secret, + "refresh_token": refresh_token, + } + if scopes: + body["scope"] = " ".join(scopes) + + response_data = await _token_endpoint_request(request, token_uri, body) + + try: + access_token = response_data["access_token"] + except KeyError as caught_exc: + new_exc = exceptions.RefreshError("No access token in response.", response_data) + six.raise_from(new_exc, caught_exc) + + refresh_token = response_data.get("refresh_token", refresh_token) + expiry = _parse_expiry(response_data) + + return access_token, refresh_token, expiry, response_data diff --git a/packages/google-auth/google/oauth2/_credentials_async.py b/packages/google-auth/google/oauth2/_credentials_async.py new file mode 100644 index 000000000000..eb3e97c08066 --- /dev/null +++ b/packages/google-auth/google/oauth2/_credentials_async.py @@ -0,0 +1,108 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""OAuth 2.0 Async Credentials. + +This module provides credentials based on OAuth 2.0 access and refresh tokens. +These credentials usually access resources on behalf of a user (resource +owner). + +Specifically, this is intended to use access tokens acquired using the +`Authorization Code grant`_ and can refresh those tokens using a +optional `refresh token`_. + +Obtaining the initial access and refresh token is outside of the scope of this +module. Consult `rfc6749 section 4.1`_ for complete details on the +Authorization Code grant flow. + +.. _Authorization Code grant: https://tools.ietf.org/html/rfc6749#section-1.3.1 +.. _refresh token: https://tools.ietf.org/html/rfc6749#section-6 +.. _rfc6749 section 4.1: https://tools.ietf.org/html/rfc6749#section-4.1 +""" + +from google.auth import _credentials_async as credentials +from google.auth import _helpers +from google.auth import exceptions +from google.oauth2 import _client_async as _client +from google.oauth2 import credentials as oauth2_credentials + + +class Credentials(oauth2_credentials.Credentials): + """Credentials using OAuth 2.0 access and refresh tokens. + + The credentials are considered immutable. If you want to modify the + quota project, use :meth:`with_quota_project` or :: + + credentials = credentials.with_quota_project('myproject-123) + """ + + @_helpers.copy_docstring(credentials.Credentials) + async def refresh(self, request): + if ( + self._refresh_token is None + or self._token_uri is None + or self._client_id is None + or self._client_secret is None + ): + raise exceptions.RefreshError( + "The credentials do not contain the necessary fields need to " + "refresh the access token. You must specify refresh_token, " + "token_uri, client_id, and client_secret." + ) + + ( + access_token, + refresh_token, + expiry, + grant_response, + ) = await _client.refresh_grant( + request, + self._token_uri, + self._refresh_token, + self._client_id, + self._client_secret, + self._scopes, + ) + + self.token = access_token + self.expiry = expiry + self._refresh_token = refresh_token + self._id_token = grant_response.get("id_token") + + if self._scopes and "scopes" in grant_response: + requested_scopes = frozenset(self._scopes) + granted_scopes = frozenset(grant_response["scopes"].split()) + scopes_requested_but_not_granted = requested_scopes - granted_scopes + if scopes_requested_but_not_granted: + raise exceptions.RefreshError( + "Not all requested scopes were granted by the " + "authorization server, missing scopes {}.".format( + ", ".join(scopes_requested_but_not_granted) + ) + ) + + +class UserAccessTokenCredentials(oauth2_credentials.UserAccessTokenCredentials): + """Access token credentials for user account. + + Obtain the access token for a given user account or the current active + user account with the ``gcloud auth print-access-token`` command. + + Args: + account (Optional[str]): Account to get the access token for. If not + specified, the current active account will be used. + quota_project_id (Optional[str]): The project ID used for quota + and billing. + + """ diff --git a/packages/google-auth/google/oauth2/_id_token_async.py b/packages/google-auth/google/oauth2/_id_token_async.py new file mode 100644 index 000000000000..f5ef8baff8a7 --- /dev/null +++ b/packages/google-auth/google/oauth2/_id_token_async.py @@ -0,0 +1,267 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google ID Token helpers. + +Provides support for verifying `OpenID Connect ID Tokens`_, especially ones +generated by Google infrastructure. + +To parse and verify an ID Token issued by Google's OAuth 2.0 authorization +server use :func:`verify_oauth2_token`. To verify an ID Token issued by +Firebase, use :func:`verify_firebase_token`. + +A general purpose ID Token verifier is available as :func:`verify_token`. + +Example:: + + from google.oauth2 import _id_token_async + from google.auth.transport import aiohttp_requests + + request = aiohttp_requests.Request() + + id_info = await _id_token_async.verify_oauth2_token( + token, request, 'my-client-id.example.com') + + if id_info['iss'] != 'https://accounts.google.com': + raise ValueError('Wrong issuer.') + + userid = id_info['sub'] + +By default, this will re-fetch certificates for each verification. Because +Google's public keys are only changed infrequently (on the order of once per +day), you may wish to take advantage of caching to reduce latency and the +potential for network errors. This can be accomplished using an external +library like `CacheControl`_ to create a cache-aware +:class:`google.auth.transport.Request`:: + + import cachecontrol + import google.auth.transport.requests + import requests + + session = requests.session() + cached_session = cachecontrol.CacheControl(session) + request = google.auth.transport.requests.Request(session=cached_session) + +.. _OpenID Connect ID Token: + http://openid.net/specs/openid-connect-core-1_0.html#IDToken +.. _CacheControl: https://cachecontrol.readthedocs.io +""" + +import json +import os + +import six +from six.moves import http_client + +from google.auth import environment_vars +from google.auth import exceptions +from google.auth import jwt +from google.auth.transport import requests +from google.oauth2 import id_token as sync_id_token + + +async def _fetch_certs(request, certs_url): + """Fetches certificates. + + Google-style cerificate endpoints return JSON in the format of + ``{'key id': 'x509 certificate'}``. + + Args: + request (google.auth.transport.Request): The object used to make + HTTP requests. This must be an aiohttp request. + certs_url (str): The certificate endpoint URL. + + Returns: + Mapping[str, str]: A mapping of public key ID to x.509 certificate + data. + """ + response = await request(certs_url, method="GET") + + if response.status != http_client.OK: + raise exceptions.TransportError( + "Could not fetch certificates at {}".format(certs_url) + ) + + data = await response.data.read() + + return json.loads(json.dumps(data)) + + +async def verify_token( + id_token, request, audience=None, certs_url=sync_id_token._GOOGLE_OAUTH2_CERTS_URL +): + """Verifies an ID token and returns the decoded token. + + Args: + id_token (Union[str, bytes]): The encoded token. + request (google.auth.transport.Request): The object used to make + HTTP requests. This must be an aiohttp request. + audience (str): The audience that this token is intended for. If None + then the audience is not verified. + certs_url (str): The URL that specifies the certificates to use to + verify the token. This URL should return JSON in the format of + ``{'key id': 'x509 certificate'}``. + + Returns: + Mapping[str, Any]: The decoded token. + """ + certs = await _fetch_certs(request, certs_url) + + return jwt.decode(id_token, certs=certs, audience=audience) + + +async def verify_oauth2_token(id_token, request, audience=None): + """Verifies an ID Token issued by Google's OAuth 2.0 authorization server. + + Args: + id_token (Union[str, bytes]): The encoded token. + request (google.auth.transport.Request): The object used to make + HTTP requests. This must be an aiohttp request. + audience (str): The audience that this token is intended for. This is + typically your application's OAuth 2.0 client ID. If None then the + audience is not verified. + + Returns: + Mapping[str, Any]: The decoded token. + + Raises: + exceptions.GoogleAuthError: If the issuer is invalid. + """ + idinfo = await verify_token( + id_token, + request, + audience=audience, + certs_url=sync_id_token._GOOGLE_OAUTH2_CERTS_URL, + ) + + if idinfo["iss"] not in sync_id_token._GOOGLE_ISSUERS: + raise exceptions.GoogleAuthError( + "Wrong issuer. 'iss' should be one of the following: {}".format( + sync_id_token._GOOGLE_ISSUERS + ) + ) + + return idinfo + + +async def verify_firebase_token(id_token, request, audience=None): + """Verifies an ID Token issued by Firebase Authentication. + + Args: + id_token (Union[str, bytes]): The encoded token. + request (google.auth.transport.Request): The object used to make + HTTP requests. This must be an aiohttp request. + audience (str): The audience that this token is intended for. This is + typically your Firebase application ID. If None then the audience + is not verified. + + Returns: + Mapping[str, Any]: The decoded token. + """ + return await verify_token( + id_token, + request, + audience=audience, + certs_url=sync_id_token._GOOGLE_APIS_CERTS_URL, + ) + + +async def fetch_id_token(request, audience): + """Fetch the ID Token from the current environment. + + This function acquires ID token from the environment in the following order: + + 1. If the application is running in Compute Engine, App Engine or Cloud Run, + then the ID token are obtained from the metadata server. + 2. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set + to the path of a valid service account JSON file, then ID token is + acquired using this service account credentials. + 3. If metadata server doesn't exist and no valid service account credentials + are found, :class:`~google.auth.exceptions.DefaultCredentialsError` will + be raised. + + Example:: + + import google.oauth2._id_token_async + import google.auth.transport.aiohttp_requests + + request = google.auth.transport.aiohttp_requests.Request() + target_audience = "https://pubsub.googleapis.com" + + id_token = await google.oauth2._id_token_async.fetch_id_token(request, target_audience) + + Args: + request (google.auth.transport.aiohttp_requests.Request): A callable used to make + HTTP requests. + audience (str): The audience that this ID token is intended for. + + Returns: + str: The ID token. + + Raises: + ~google.auth.exceptions.DefaultCredentialsError: + If metadata server doesn't exist and no valid service account + credentials are found. + """ + # 1. First try to fetch ID token from metadata server if it exists. The code + # works for GAE and Cloud Run metadata server as well. + try: + from google.auth import compute_engine + + request_new = requests.Request() + credentials = compute_engine.IDTokenCredentials( + request_new, audience, use_metadata_identity_endpoint=True + ) + credentials.refresh(request_new) + + return credentials.token + + except (ImportError, exceptions.TransportError, exceptions.RefreshError): + pass + + # 2. Try to use service account credentials to get ID token. + + # Try to get credentials from the GOOGLE_APPLICATION_CREDENTIALS environment + # variable. + credentials_filename = os.environ.get(environment_vars.CREDENTIALS) + if not ( + credentials_filename + and os.path.exists(credentials_filename) + and os.path.isfile(credentials_filename) + ): + raise exceptions.DefaultCredentialsError( + "Neither metadata server or valid service account credentials are found." + ) + + try: + with open(credentials_filename, "r") as f: + info = json.load(f) + credentials_content = ( + (info.get("type") == "service_account") and info or None + ) + + from google.oauth2 import _service_account_async as service_account + + credentials = service_account.IDTokenCredentials.from_service_account_info( + credentials_content, target_audience=audience + ) + except ValueError as caught_exc: + new_exc = exceptions.DefaultCredentialsError( + "Neither metadata server or valid service account credentials are found.", + caught_exc, + ) + six.raise_from(new_exc, caught_exc) + + await credentials.refresh(request) + return credentials.token diff --git a/packages/google-auth/google/oauth2/_service_account_async.py b/packages/google-auth/google/oauth2/_service_account_async.py new file mode 100644 index 000000000000..0a4e724a4f9b --- /dev/null +++ b/packages/google-auth/google/oauth2/_service_account_async.py @@ -0,0 +1,132 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Service Accounts: JSON Web Token (JWT) Profile for OAuth 2.0 + +NOTE: This file adds asynchronous refresh methods to both credentials +classes, and therefore async/await syntax is required when calling this +method when using service account credentials with asynchronous functionality. +Otherwise, all other methods are inherited from the regular service account +credentials file google.oauth2.service_account + +""" + +from google.auth import _credentials_async as credentials_async +from google.auth import _helpers +from google.oauth2 import _client_async +from google.oauth2 import service_account + + +class Credentials( + service_account.Credentials, credentials_async.Scoped, credentials_async.Credentials +): + """Service account credentials + + Usually, you'll create these credentials with one of the helper + constructors. To create credentials using a Google service account + private key JSON file:: + + credentials = _service_account_async.Credentials.from_service_account_file( + 'service-account.json') + + Or if you already have the service account file loaded:: + + service_account_info = json.load(open('service_account.json')) + credentials = _service_account_async.Credentials.from_service_account_info( + service_account_info) + + Both helper methods pass on arguments to the constructor, so you can + specify additional scopes and a subject if necessary:: + + credentials = _service_account_async.Credentials.from_service_account_file( + 'service-account.json', + scopes=['email'], + subject='user@example.com') + + The credentials are considered immutable. If you want to modify the scopes + or the subject used for delegation, use :meth:`with_scopes` or + :meth:`with_subject`:: + + scoped_credentials = credentials.with_scopes(['email']) + delegated_credentials = credentials.with_subject(subject) + + To add a quota project, use :meth:`with_quota_project`:: + + credentials = credentials.with_quota_project('myproject-123') + """ + + @_helpers.copy_docstring(credentials_async.Credentials) + async def refresh(self, request): + assertion = self._make_authorization_grant_assertion() + access_token, expiry, _ = await _client_async.jwt_grant( + request, self._token_uri, assertion + ) + self.token = access_token + self.expiry = expiry + + +class IDTokenCredentials( + service_account.IDTokenCredentials, + credentials_async.Signing, + credentials_async.Credentials, +): + """Open ID Connect ID Token-based service account credentials. + + These credentials are largely similar to :class:`.Credentials`, but instead + of using an OAuth 2.0 Access Token as the bearer token, they use an Open + ID Connect ID Token as the bearer token. These credentials are useful when + communicating to services that require ID Tokens and can not accept access + tokens. + + Usually, you'll create these credentials with one of the helper + constructors. To create credentials using a Google service account + private key JSON file:: + + credentials = ( + _service_account_async.IDTokenCredentials.from_service_account_file( + 'service-account.json')) + + Or if you already have the service account file loaded:: + + service_account_info = json.load(open('service_account.json')) + credentials = ( + _service_account_async.IDTokenCredentials.from_service_account_info( + service_account_info)) + + Both helper methods pass on arguments to the constructor, so you can + specify additional scopes and a subject if necessary:: + + credentials = ( + _service_account_async.IDTokenCredentials.from_service_account_file( + 'service-account.json', + scopes=['email'], + subject='user@example.com')) +` + The credentials are considered immutable. If you want to modify the scopes + or the subject used for delegation, use :meth:`with_scopes` or + :meth:`with_subject`:: + + scoped_credentials = credentials.with_scopes(['email']) + delegated_credentials = credentials.with_subject(subject) + + """ + + @_helpers.copy_docstring(credentials_async.Credentials) + async def refresh(self, request): + assertion = self._make_authorization_grant_assertion() + access_token, expiry, _ = await _client_async.id_token_jwt_grant( + request, self._token_uri, assertion + ) + self.token = access_token + self.expiry = expiry diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index c39f27c478f6..d497f53057b1 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -29,8 +29,18 @@ "responses", "grpcio", ] + +ASYNC_DEPENDENCIES = ["pytest-asyncio", "aioresponses"] + BLACK_VERSION = "black==19.3b0" -BLACK_PATHS = ["google", "tests", "noxfile.py", "setup.py", "docs/conf.py"] +BLACK_PATHS = [ + "google", + "tests", + "tests_async", + "noxfile.py", + "setup.py", + "docs/conf.py", +] @nox.session(python="3.7") @@ -44,6 +54,7 @@ def lint(session): "--application-import-names=google,tests,system_tests", "google", "tests", + "tests_async", ) session.run( "python", "setup.py", "check", "--metadata", "--restructuredtext", "--strict" @@ -64,8 +75,23 @@ def blacken(session): session.run("black", *BLACK_PATHS) -@nox.session(python=["2.7", "3.5", "3.6", "3.7", "3.8"]) +@nox.session(python=["3.6", "3.7", "3.8"]) def unit(session): + session.install(*TEST_DEPENDENCIES) + session.install(*(ASYNC_DEPENDENCIES)) + session.install(".") + session.run( + "pytest", + "--cov=google.auth", + "--cov=google.oauth2", + "--cov=tests", + "tests", + "tests_async", + ) + + +@nox.session(python=["2.7", "3.5"]) +def unit_prev_versions(session): session.install(*TEST_DEPENDENCIES) session.install(".") session.run( @@ -76,14 +102,17 @@ def unit(session): @nox.session(python="3.7") def cover(session): session.install(*TEST_DEPENDENCIES) + session.install(*(ASYNC_DEPENDENCIES)) session.install(".") session.run( "pytest", "--cov=google.auth", "--cov=google.oauth2", "--cov=tests", + "--cov=tests_async", "--cov-report=", "tests", + "tests_async", ) session.run("coverage", "report", "--show-missing", "--fail-under=100") @@ -117,5 +146,10 @@ def pypy(session): session.install(*TEST_DEPENDENCIES) session.install(".") session.run( - "pytest", "--cov=google.auth", "--cov=google.oauth2", "--cov=tests", "tests" + "pytest", + "--cov=google.auth", + "--cov=google.oauth2", + "--cov=tests", + "tests", + "tests_async", ) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 1c3578f60c8b..dd58f30f2d46 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -27,6 +27,7 @@ 'rsa>=3.1.4,<5; python_version >= "3.5"', "setuptools>=40.3.0", "six>=1.9.0", + 'aiohttp >= 3.6.2, < 4.0.0dev; python_version>="3.6"', ) diff --git a/packages/google-auth/system_tests/noxfile.py b/packages/google-auth/system_tests/noxfile.py index 14cd3db8e321..a039228d929e 100644 --- a/packages/google-auth/system_tests/noxfile.py +++ b/packages/google-auth/system_tests/noxfile.py @@ -1,4 +1,4 @@ -# Copyright 2016 Google LLC +# Copyright 2020 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -29,7 +29,6 @@ import nox import py.path - HERE = os.path.abspath(os.path.dirname(__file__)) LIBRARY_DIR = os.path.join(HERE, "..") DATA_DIR = os.path.join(HERE, "data") @@ -169,92 +168,79 @@ def configure_cloud_sdk(session, application_default_credentials, project=False) # Test sesssions -TEST_DEPENDENCIES = ["pytest", "requests"] -PYTHON_VERSIONS = ["2.7", "3.7"] - - -@nox.session(python=PYTHON_VERSIONS) -def service_account(session): - session.install(*TEST_DEPENDENCIES) - session.install(LIBRARY_DIR) - session.run("pytest", "test_service_account.py") - - -@nox.session(python=PYTHON_VERSIONS) -def oauth2_credentials(session): - session.install(*TEST_DEPENDENCIES) - session.install(LIBRARY_DIR) - session.run("pytest", "test_oauth2_credentials.py") +TEST_DEPENDENCIES_ASYNC = ["aiohttp", "pytest-asyncio", "nest-asyncio"] +TEST_DEPENDENCIES_SYNC = ["pytest", "requests"] +PYTHON_VERSIONS_ASYNC = ["3.7"] +PYTHON_VERSIONS_SYNC = ["2.7", "3.7"] -@nox.session(python=PYTHON_VERSIONS) -def impersonated_credentials(session): - session.install(*TEST_DEPENDENCIES) +@nox.session(python=PYTHON_VERSIONS_SYNC) +def service_account_sync(session): + session.install(*TEST_DEPENDENCIES_SYNC) session.install(LIBRARY_DIR) - session.run("pytest", "test_impersonated_credentials.py") + session.run("pytest", "system_tests_sync/test_service_account.py") -@nox.session(python=PYTHON_VERSIONS) +@nox.session(python=PYTHON_VERSIONS_SYNC) def default_explicit_service_account(session): session.env[EXPLICIT_CREDENTIALS_ENV] = SERVICE_ACCOUNT_FILE session.env[EXPECT_PROJECT_ENV] = "1" - session.install(*TEST_DEPENDENCIES) + session.install(*TEST_DEPENDENCIES_SYNC) session.install(LIBRARY_DIR) - session.run("pytest", "test_default.py", "test_id_token.py") + session.run("pytest", "system_tests_sync/test_default.py", "system_tests_sync/test_id_token.py") -@nox.session(python=PYTHON_VERSIONS) +@nox.session(python=PYTHON_VERSIONS_SYNC) def default_explicit_authorized_user(session): session.env[EXPLICIT_CREDENTIALS_ENV] = AUTHORIZED_USER_FILE - session.install(*TEST_DEPENDENCIES) + session.install(*TEST_DEPENDENCIES_SYNC) session.install(LIBRARY_DIR) - session.run("pytest", "test_default.py") + session.run("pytest", "system_tests_sync/test_default.py") -@nox.session(python=PYTHON_VERSIONS) +@nox.session(python=PYTHON_VERSIONS_SYNC) def default_explicit_authorized_user_explicit_project(session): session.env[EXPLICIT_CREDENTIALS_ENV] = AUTHORIZED_USER_FILE session.env[EXPLICIT_PROJECT_ENV] = "example-project" session.env[EXPECT_PROJECT_ENV] = "1" - session.install(*TEST_DEPENDENCIES) + session.install(*TEST_DEPENDENCIES_SYNC) session.install(LIBRARY_DIR) - session.run("pytest", "test_default.py") + session.run("pytest", "system_tests_sync/test_default.py") -@nox.session(python=PYTHON_VERSIONS) +@nox.session(python=PYTHON_VERSIONS_SYNC) def default_cloud_sdk_service_account(session): configure_cloud_sdk(session, SERVICE_ACCOUNT_FILE) session.env[EXPECT_PROJECT_ENV] = "1" - session.install(*TEST_DEPENDENCIES) + session.install(*TEST_DEPENDENCIES_SYNC) session.install(LIBRARY_DIR) - session.run("pytest", "test_default.py") + session.run("pytest", "system_tests_sync/test_default.py") -@nox.session(python=PYTHON_VERSIONS) +@nox.session(python=PYTHON_VERSIONS_SYNC) def default_cloud_sdk_authorized_user(session): configure_cloud_sdk(session, AUTHORIZED_USER_FILE) - session.install(*TEST_DEPENDENCIES) + session.install(*TEST_DEPENDENCIES_SYNC) session.install(LIBRARY_DIR) - session.run("pytest", "test_default.py") + session.run("pytest", "system_tests_sync/test_default.py") -@nox.session(python=PYTHON_VERSIONS) +@nox.session(python=PYTHON_VERSIONS_SYNC) def default_cloud_sdk_authorized_user_configured_project(session): configure_cloud_sdk(session, AUTHORIZED_USER_FILE, project=True) session.env[EXPECT_PROJECT_ENV] = "1" - session.install(*TEST_DEPENDENCIES) + session.install(*TEST_DEPENDENCIES_SYNC) session.install(LIBRARY_DIR) - session.run("pytest", "test_default.py") - + session.run("pytest", "system_tests_sync/test_default.py") -@nox.session(python=PYTHON_VERSIONS) +@nox.session(python=PYTHON_VERSIONS_SYNC) def compute_engine(session): - session.install(*TEST_DEPENDENCIES) + session.install(*TEST_DEPENDENCIES_SYNC) # unset Application Default Credentials so # credentials are detected from environment del session.virtualenv.env["GOOGLE_APPLICATION_CREDENTIALS"] session.install(LIBRARY_DIR) - session.run("pytest", "test_compute_engine.py") + session.run("pytest", "system_tests_sync/test_compute_engine.py") @nox.session(python=["2.7"]) @@ -283,8 +269,8 @@ def app_engine(session): application_url = GAE_APP_URL_TMPL.format(GAE_TEST_APP_SERVICE, project_id) # Vendor in the test application's dependencies - session.chdir(os.path.join(HERE, "app_engine_test_app")) - session.install(*TEST_DEPENDENCIES) + session.chdir(os.path.join(HERE, "../app_engine_test_app")) + session.install(*TEST_DEPENDENCIES_SYNC) session.install(LIBRARY_DIR) session.run( "pip", "install", "--target", "lib", "-r", "requirements.txt", silent=True @@ -296,20 +282,82 @@ def app_engine(session): # Run the tests session.env["TEST_APP_URL"] = application_url session.chdir(HERE) - session.run("pytest", "test_app_engine.py") + session.run("pytest", "system_tests_sync/test_app_engine.py") -@nox.session(python=PYTHON_VERSIONS) +@nox.session(python=PYTHON_VERSIONS_SYNC) def grpc(session): session.install(LIBRARY_DIR) - session.install(*TEST_DEPENDENCIES, "google-cloud-pubsub==1.0.0") + session.install(*TEST_DEPENDENCIES_SYNC, "google-cloud-pubsub==1.0.0") session.env[EXPLICIT_CREDENTIALS_ENV] = SERVICE_ACCOUNT_FILE - session.run("pytest", "test_grpc.py") + session.run("pytest", "system_tests_sync/test_grpc.py") -@nox.session(python=PYTHON_VERSIONS) +@nox.session(python=PYTHON_VERSIONS_SYNC) def mtls_http(session): session.install(LIBRARY_DIR) - session.install(*TEST_DEPENDENCIES, "pyopenssl") + session.install(*TEST_DEPENDENCIES_SYNC, "pyopenssl") + session.env[EXPLICIT_CREDENTIALS_ENV] = SERVICE_ACCOUNT_FILE + session.run("pytest", "system_tests_sync/test_mtls_http.py") + +#ASYNC SYSTEM TESTS + +@nox.session(python=PYTHON_VERSIONS_ASYNC) +def service_account_async(session): + session.install(*(TEST_DEPENDENCIES_SYNC+TEST_DEPENDENCIES_ASYNC)) + session.install(LIBRARY_DIR) + session.run("pytest", "system_tests_async/test_service_account.py") + + +@nox.session(python=PYTHON_VERSIONS_ASYNC) +def default_explicit_service_account_async(session): session.env[EXPLICIT_CREDENTIALS_ENV] = SERVICE_ACCOUNT_FILE - session.run("pytest", "test_mtls_http.py") + session.env[EXPECT_PROJECT_ENV] = "1" + session.install(*(TEST_DEPENDENCIES_SYNC + TEST_DEPENDENCIES_ASYNC)) + session.install(LIBRARY_DIR) + session.run("pytest", "system_tests_async/test_default.py", + "system_tests_async/test_id_token.py") + + +@nox.session(python=PYTHON_VERSIONS_ASYNC) +def default_explicit_authorized_user_async(session): + session.env[EXPLICIT_CREDENTIALS_ENV] = AUTHORIZED_USER_FILE + session.install(*(TEST_DEPENDENCIES_SYNC + TEST_DEPENDENCIES_ASYNC)) + session.install(LIBRARY_DIR) + session.run("pytest", "system_tests_async/test_default.py") + + +@nox.session(python=PYTHON_VERSIONS_ASYNC) +def default_explicit_authorized_user_explicit_project_async(session): + session.env[EXPLICIT_CREDENTIALS_ENV] = AUTHORIZED_USER_FILE + session.env[EXPLICIT_PROJECT_ENV] = "example-project" + session.env[EXPECT_PROJECT_ENV] = "1" + session.install(*(TEST_DEPENDENCIES_SYNC + TEST_DEPENDENCIES_ASYNC)) + session.install(LIBRARY_DIR) + session.run("pytest", "system_tests_async/test_default.py") + + +@nox.session(python=PYTHON_VERSIONS_ASYNC) +def default_cloud_sdk_service_account_async(session): + configure_cloud_sdk(session, SERVICE_ACCOUNT_FILE) + session.env[EXPECT_PROJECT_ENV] = "1" + session.install(*(TEST_DEPENDENCIES_SYNC + TEST_DEPENDENCIES_ASYNC)) + session.install(LIBRARY_DIR) + session.run("pytest", "system_tests_async/test_default.py") + + +@nox.session(python=PYTHON_VERSIONS_ASYNC) +def default_cloud_sdk_authorized_user_async(session): + configure_cloud_sdk(session, AUTHORIZED_USER_FILE) + session.install(*(TEST_DEPENDENCIES_SYNC + TEST_DEPENDENCIES_ASYNC)) + session.install(LIBRARY_DIR) + session.run("pytest", "system_tests_async/test_default.py") + + +@nox.session(python=PYTHON_VERSIONS_ASYNC) +def default_cloud_sdk_authorized_user_configured_project_async(session): + configure_cloud_sdk(session, AUTHORIZED_USER_FILE, project=True) + session.env[EXPECT_PROJECT_ENV] = "1" + session.install(*(TEST_DEPENDENCIES_SYNC + TEST_DEPENDENCIES_ASYNC)) + session.install(LIBRARY_DIR) + session.run("pytest", "system_tests_async/test_default.py") diff --git a/packages/google-auth/system_tests/system_tests_async/conftest.py b/packages/google-auth/system_tests/system_tests_async/conftest.py new file mode 100644 index 000000000000..ecff74c96b50 --- /dev/null +++ b/packages/google-auth/system_tests/system_tests_async/conftest.py @@ -0,0 +1,108 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import os + +from google.auth import _helpers +import google.auth.transport.requests +import google.auth.transport.urllib3 +import pytest +import requests +import urllib3 + +import aiohttp +from google.auth.transport import _aiohttp_requests as aiohttp_requests +from system_tests.system_tests_sync import conftest as sync_conftest + +ASYNC_REQUESTS_SESSION = aiohttp.ClientSession() + +ASYNC_REQUESTS_SESSION.verify = False +TOKEN_INFO_URL = "https://www.googleapis.com/oauth2/v3/tokeninfo" + + +@pytest.fixture +def service_account_file(): + """The full path to a valid service account key file.""" + yield sync_conftest.SERVICE_ACCOUNT_FILE + + +@pytest.fixture +def impersonated_service_account_file(): + """The full path to a valid service account key file.""" + yield sync_conftest.IMPERSONATED_SERVICE_ACCOUNT_FILE + + +@pytest.fixture +def authorized_user_file(): + """The full path to a valid authorized user file.""" + yield sync_conftest.AUTHORIZED_USER_FILE + +@pytest.fixture(params=["aiohttp"]) +async def http_request(request): + """A transport.request object.""" + yield aiohttp_requests.Request(ASYNC_REQUESTS_SESSION) + +@pytest.fixture +async def token_info(http_request): + """Returns a function that obtains OAuth2 token info.""" + + async def _token_info(access_token=None, id_token=None): + query_params = {} + + if access_token is not None: + query_params["access_token"] = access_token + elif id_token is not None: + query_params["id_token"] = id_token + else: + raise ValueError("No token specified.") + + url = _helpers.update_query(sync_conftest.TOKEN_INFO_URL, query_params) + + response = await http_request(url=url, method="GET") + data = await response.data.read() + + return json.loads(data.decode("utf-8")) + + yield _token_info + + +@pytest.fixture +async def verify_refresh(http_request): + """Returns a function that verifies that credentials can be refreshed.""" + + async def _verify_refresh(credentials): + if credentials.requires_scopes: + credentials = credentials.with_scopes(["email", "profile"]) + + await credentials.refresh(http_request) + + assert credentials.token + assert credentials.valid + + yield _verify_refresh + + +def verify_environment(): + """Checks to make sure that requisite data files are available.""" + if not os.path.isdir(sync_conftest.DATA_DIR): + raise EnvironmentError( + "In order to run system tests, test data must exist in " + "system_tests/data. See CONTRIBUTING.rst for details." + ) + + +def pytest_configure(config): + """Pytest hook that runs before Pytest collects any tests.""" + verify_environment() diff --git a/packages/google-auth/system_tests/system_tests_async/test_default.py b/packages/google-auth/system_tests/system_tests_async/test_default.py new file mode 100644 index 000000000000..383cbff01ac1 --- /dev/null +++ b/packages/google-auth/system_tests/system_tests_async/test_default.py @@ -0,0 +1,30 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import pytest + +import google.auth + +EXPECT_PROJECT_ID = os.environ.get("EXPECT_PROJECT_ID") + +@pytest.mark.asyncio +async def test_application_default_credentials(verify_refresh): + credentials, project_id = google.auth.default_async() + #breakpoint() + + if EXPECT_PROJECT_ID is not None: + assert project_id is not None + + await verify_refresh(credentials) diff --git a/packages/google-auth/system_tests/system_tests_async/test_id_token.py b/packages/google-auth/system_tests/system_tests_async/test_id_token.py new file mode 100644 index 000000000000..a21b137b65b0 --- /dev/null +++ b/packages/google-auth/system_tests/system_tests_async/test_id_token.py @@ -0,0 +1,25 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import pytest + +from google.auth import jwt +import google.oauth2._id_token_async + +@pytest.mark.asyncio +async def test_fetch_id_token(http_request): + audience = "https://pubsub.googleapis.com" + token = await google.oauth2._id_token_async.fetch_id_token(http_request, audience) + + _, payload, _, _ = jwt._unverified_decode(token) + assert payload["aud"] == audience diff --git a/packages/google-auth/system_tests/system_tests_async/test_service_account.py b/packages/google-auth/system_tests/system_tests_async/test_service_account.py new file mode 100644 index 000000000000..c1c16ccd7309 --- /dev/null +++ b/packages/google-auth/system_tests/system_tests_async/test_service_account.py @@ -0,0 +1,53 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from google.auth import _helpers +from google.auth import exceptions +from google.auth import iam +from google.oauth2 import _service_account_async + + +@pytest.fixture +def credentials(service_account_file): + yield _service_account_async.Credentials.from_service_account_file(service_account_file) + + +@pytest.mark.asyncio +async def test_refresh_no_scopes(http_request, credentials): + """ + We expect the http request to refresh credentials + without scopes provided to throw an error. + """ + with pytest.raises(exceptions.RefreshError): + await credentials.refresh(http_request) + +@pytest.mark.asyncio +async def test_refresh_success(http_request, credentials, token_info): + credentials = credentials.with_scopes(["email", "profile"]) + await credentials.refresh(http_request) + + assert credentials.token + + info = await token_info(credentials.token) + + assert info["email"] == credentials.service_account_email + info_scopes = _helpers.string_to_scopes(info["scope"]) + assert set(info_scopes) == set( + [ + "https://www.googleapis.com/auth/userinfo.email", + "https://www.googleapis.com/auth/userinfo.profile", + ] + ) diff --git a/packages/google-auth/system_tests/.gitignore b/packages/google-auth/system_tests/system_tests_sync/.gitignore similarity index 100% rename from packages/google-auth/system_tests/.gitignore rename to packages/google-auth/system_tests/system_tests_sync/.gitignore diff --git a/packages/google-auth/system_tests/__init__.py b/packages/google-auth/system_tests/system_tests_sync/__init__.py similarity index 100% rename from packages/google-auth/system_tests/__init__.py rename to packages/google-auth/system_tests/system_tests_sync/__init__.py diff --git a/packages/google-auth/system_tests/app_engine_test_app/.gitignore b/packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/.gitignore similarity index 100% rename from packages/google-auth/system_tests/app_engine_test_app/.gitignore rename to packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/.gitignore diff --git a/packages/google-auth/system_tests/app_engine_test_app/app.yaml b/packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/app.yaml similarity index 100% rename from packages/google-auth/system_tests/app_engine_test_app/app.yaml rename to packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/app.yaml diff --git a/packages/google-auth/system_tests/app_engine_test_app/appengine_config.py b/packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/appengine_config.py similarity index 100% rename from packages/google-auth/system_tests/app_engine_test_app/appengine_config.py rename to packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/appengine_config.py diff --git a/packages/google-auth/system_tests/app_engine_test_app/main.py b/packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/main.py similarity index 100% rename from packages/google-auth/system_tests/app_engine_test_app/main.py rename to packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/main.py diff --git a/packages/google-auth/system_tests/app_engine_test_app/requirements.txt b/packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/requirements.txt similarity index 100% rename from packages/google-auth/system_tests/app_engine_test_app/requirements.txt rename to packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/requirements.txt diff --git a/packages/google-auth/system_tests/conftest.py b/packages/google-auth/system_tests/system_tests_sync/conftest.py similarity index 96% rename from packages/google-auth/system_tests/conftest.py rename to packages/google-auth/system_tests/system_tests_sync/conftest.py index 02de846642af..37a6fd346bf9 100644 --- a/packages/google-auth/system_tests/conftest.py +++ b/packages/google-auth/system_tests/system_tests_sync/conftest.py @@ -24,12 +24,11 @@ HERE = os.path.dirname(__file__) -DATA_DIR = os.path.join(HERE, "data") +DATA_DIR = os.path.join(HERE, "../data") IMPERSONATED_SERVICE_ACCOUNT_FILE = os.path.join( DATA_DIR, "impersonated_service_account.json" ) SERVICE_ACCOUNT_FILE = os.path.join(DATA_DIR, "service_account.json") -AUTHORIZED_USER_FILE = os.path.join(DATA_DIR, "authorized_user.json") URLLIB3_HTTP = urllib3.PoolManager(retries=False) REQUESTS_SESSION = requests.Session() REQUESTS_SESSION.verify = False diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/system_tests_sync/secrets.tar.enc similarity index 100% rename from packages/google-auth/system_tests/secrets.tar.enc rename to packages/google-auth/system_tests/system_tests_sync/secrets.tar.enc diff --git a/packages/google-auth/system_tests/test_app_engine.py b/packages/google-auth/system_tests/system_tests_sync/test_app_engine.py similarity index 100% rename from packages/google-auth/system_tests/test_app_engine.py rename to packages/google-auth/system_tests/system_tests_sync/test_app_engine.py diff --git a/packages/google-auth/system_tests/test_compute_engine.py b/packages/google-auth/system_tests/system_tests_sync/test_compute_engine.py similarity index 100% rename from packages/google-auth/system_tests/test_compute_engine.py rename to packages/google-auth/system_tests/system_tests_sync/test_compute_engine.py diff --git a/packages/google-auth/system_tests/test_default.py b/packages/google-auth/system_tests/system_tests_sync/test_default.py similarity index 100% rename from packages/google-auth/system_tests/test_default.py rename to packages/google-auth/system_tests/system_tests_sync/test_default.py diff --git a/packages/google-auth/system_tests/test_grpc.py b/packages/google-auth/system_tests/system_tests_sync/test_grpc.py similarity index 100% rename from packages/google-auth/system_tests/test_grpc.py rename to packages/google-auth/system_tests/system_tests_sync/test_grpc.py diff --git a/packages/google-auth/system_tests/test_id_token.py b/packages/google-auth/system_tests/system_tests_sync/test_id_token.py similarity index 100% rename from packages/google-auth/system_tests/test_id_token.py rename to packages/google-auth/system_tests/system_tests_sync/test_id_token.py diff --git a/packages/google-auth/system_tests/test_impersonated_credentials.py b/packages/google-auth/system_tests/system_tests_sync/test_impersonated_credentials.py similarity index 100% rename from packages/google-auth/system_tests/test_impersonated_credentials.py rename to packages/google-auth/system_tests/system_tests_sync/test_impersonated_credentials.py diff --git a/packages/google-auth/system_tests/test_mtls_http.py b/packages/google-auth/system_tests/system_tests_sync/test_mtls_http.py similarity index 100% rename from packages/google-auth/system_tests/test_mtls_http.py rename to packages/google-auth/system_tests/system_tests_sync/test_mtls_http.py diff --git a/packages/google-auth/system_tests/test_oauth2_credentials.py b/packages/google-auth/system_tests/system_tests_sync/test_oauth2_credentials.py similarity index 100% rename from packages/google-auth/system_tests/test_oauth2_credentials.py rename to packages/google-auth/system_tests/system_tests_sync/test_oauth2_credentials.py diff --git a/packages/google-auth/system_tests/test_service_account.py b/packages/google-auth/system_tests/system_tests_sync/test_service_account.py similarity index 100% rename from packages/google-auth/system_tests/test_service_account.py rename to packages/google-auth/system_tests/system_tests_sync/test_service_account.py diff --git a/packages/google-auth/tests_async/__init__.py b/packages/google-auth/tests_async/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/google-auth/tests_async/conftest.py b/packages/google-auth/tests_async/conftest.py new file mode 100644 index 000000000000..b4e90f0e8c34 --- /dev/null +++ b/packages/google-auth/tests_async/conftest.py @@ -0,0 +1,51 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import sys + +import mock +import pytest + + +def pytest_configure(): + """Load public certificate and private key.""" + pytest.data_dir = os.path.join( + os.path.abspath(os.path.join(__file__, "../..")), "tests/data" + ) + + with open(os.path.join(pytest.data_dir, "privatekey.pem"), "rb") as fh: + pytest.private_key_bytes = fh.read() + + with open(os.path.join(pytest.data_dir, "public_cert.pem"), "rb") as fh: + pytest.public_cert_bytes = fh.read() + + +@pytest.fixture +def mock_non_existent_module(monkeypatch): + """Mocks a non-existing module in sys.modules. + + Additionally mocks any non-existing modules specified in the dotted path. + """ + + def _mock_non_existent_module(path): + parts = path.split(".") + partial = [] + for part in parts: + partial.append(part) + current_module = ".".join(partial) + if current_module not in sys.modules: + monkeypatch.setitem(sys.modules, current_module, mock.MagicMock()) + + return _mock_non_existent_module diff --git a/packages/google-auth/tests_async/oauth2/test__client_async.py b/packages/google-auth/tests_async/oauth2/test__client_async.py new file mode 100644 index 000000000000..458937ac1f5f --- /dev/null +++ b/packages/google-auth/tests_async/oauth2/test__client_async.py @@ -0,0 +1,297 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import datetime +import json + +import mock +import pytest +import six +from six.moves import http_client +from six.moves import urllib + +from google.auth import _helpers +from google.auth import _jwt_async as jwt +from google.auth import exceptions +from google.oauth2 import _client as sync_client +from google.oauth2 import _client_async as _client +from tests.oauth2 import test__client as test_client + + +def test__handle_error_response(): + response_data = json.dumps({"error": "help", "error_description": "I'm alive"}) + + with pytest.raises(exceptions.RefreshError) as excinfo: + _client._handle_error_response(response_data) + + assert excinfo.match(r"help: I\'m alive") + + +def test__handle_error_response_non_json(): + response_data = "Help, I'm alive" + + with pytest.raises(exceptions.RefreshError) as excinfo: + _client._handle_error_response(response_data) + + assert excinfo.match(r"Help, I\'m alive") + + +@mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) +def test__parse_expiry(unused_utcnow): + result = _client._parse_expiry({"expires_in": 500}) + assert result == datetime.datetime.min + datetime.timedelta(seconds=500) + + +def test__parse_expiry_none(): + assert _client._parse_expiry({}) is None + + +def make_request(response_data, status=http_client.OK): + response = mock.AsyncMock(spec=["transport.Response"]) + response.status = status + data = json.dumps(response_data).encode("utf-8") + response.data = mock.AsyncMock(spec=["__call__", "read"]) + response.data.read = mock.AsyncMock(spec=["__call__"], return_value=data) + response.content = mock.AsyncMock(spec=["__call__"], return_value=data) + request = mock.AsyncMock(spec=["transport.Request"]) + request.return_value = response + return request + + +@pytest.mark.asyncio +async def test__token_endpoint_request(): + + request = make_request({"test": "response"}) + + result = await _client._token_endpoint_request( + request, "http://example.com", {"test": "params"} + ) + + # Check request call + request.assert_called_with( + method="POST", + url="http://example.com", + headers={"content-type": "application/x-www-form-urlencoded"}, + body="test=params".encode("utf-8"), + ) + + # Check result + assert result == {"test": "response"} + + +@pytest.mark.asyncio +async def test__token_endpoint_request_error(): + request = make_request({}, status=http_client.BAD_REQUEST) + + with pytest.raises(exceptions.RefreshError): + await _client._token_endpoint_request(request, "http://example.com", {}) + + +@pytest.mark.asyncio +async def test__token_endpoint_request_internal_failure_error(): + request = make_request( + {"error_description": "internal_failure"}, status=http_client.BAD_REQUEST + ) + + with pytest.raises(exceptions.RefreshError): + await _client._token_endpoint_request( + request, "http://example.com", {"error_description": "internal_failure"} + ) + + request = make_request( + {"error": "internal_failure"}, status=http_client.BAD_REQUEST + ) + + with pytest.raises(exceptions.RefreshError): + await _client._token_endpoint_request( + request, "http://example.com", {"error": "internal_failure"} + ) + + +def verify_request_params(request, params): + request_body = request.call_args[1]["body"].decode("utf-8") + request_params = urllib.parse.parse_qs(request_body) + + for key, value in six.iteritems(params): + assert request_params[key][0] == value + + +@mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) +@pytest.mark.asyncio +async def test_jwt_grant(utcnow): + request = make_request( + {"access_token": "token", "expires_in": 500, "extra": "data"} + ) + + token, expiry, extra_data = await _client.jwt_grant( + request, "http://example.com", "assertion_value" + ) + + # Check request call + verify_request_params( + request, + {"grant_type": sync_client._JWT_GRANT_TYPE, "assertion": "assertion_value"}, + ) + + # Check result + assert token == "token" + assert expiry == utcnow() + datetime.timedelta(seconds=500) + assert extra_data["extra"] == "data" + + +@pytest.mark.asyncio +async def test_jwt_grant_no_access_token(): + request = make_request( + { + # No access token. + "expires_in": 500, + "extra": "data", + } + ) + + with pytest.raises(exceptions.RefreshError): + await _client.jwt_grant(request, "http://example.com", "assertion_value") + + +@pytest.mark.asyncio +async def test_id_token_jwt_grant(): + now = _helpers.utcnow() + id_token_expiry = _helpers.datetime_to_secs(now) + id_token = jwt.encode(test_client.SIGNER, {"exp": id_token_expiry}).decode("utf-8") + request = make_request({"id_token": id_token, "extra": "data"}) + + token, expiry, extra_data = await _client.id_token_jwt_grant( + request, "http://example.com", "assertion_value" + ) + + # Check request call + verify_request_params( + request, + {"grant_type": sync_client._JWT_GRANT_TYPE, "assertion": "assertion_value"}, + ) + + # Check result + assert token == id_token + # JWT does not store microseconds + now = now.replace(microsecond=0) + assert expiry == now + assert extra_data["extra"] == "data" + + +@pytest.mark.asyncio +async def test_id_token_jwt_grant_no_access_token(): + request = make_request( + { + # No access token. + "expires_in": 500, + "extra": "data", + } + ) + + with pytest.raises(exceptions.RefreshError): + await _client.id_token_jwt_grant( + request, "http://example.com", "assertion_value" + ) + + +@mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) +@pytest.mark.asyncio +async def test_refresh_grant(unused_utcnow): + request = make_request( + { + "access_token": "token", + "refresh_token": "new_refresh_token", + "expires_in": 500, + "extra": "data", + } + ) + + token, refresh_token, expiry, extra_data = await _client.refresh_grant( + request, "http://example.com", "refresh_token", "client_id", "client_secret" + ) + + # Check request call + verify_request_params( + request, + { + "grant_type": sync_client._REFRESH_GRANT_TYPE, + "refresh_token": "refresh_token", + "client_id": "client_id", + "client_secret": "client_secret", + }, + ) + + # Check result + assert token == "token" + assert refresh_token == "new_refresh_token" + assert expiry == datetime.datetime.min + datetime.timedelta(seconds=500) + assert extra_data["extra"] == "data" + + +@mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) +@pytest.mark.asyncio +async def test_refresh_grant_with_scopes(unused_utcnow): + request = make_request( + { + "access_token": "token", + "refresh_token": "new_refresh_token", + "expires_in": 500, + "extra": "data", + "scope": test_client.SCOPES_AS_STRING, + } + ) + + token, refresh_token, expiry, extra_data = await _client.refresh_grant( + request, + "http://example.com", + "refresh_token", + "client_id", + "client_secret", + test_client.SCOPES_AS_LIST, + ) + + # Check request call. + verify_request_params( + request, + { + "grant_type": sync_client._REFRESH_GRANT_TYPE, + "refresh_token": "refresh_token", + "client_id": "client_id", + "client_secret": "client_secret", + "scope": test_client.SCOPES_AS_STRING, + }, + ) + + # Check result. + assert token == "token" + assert refresh_token == "new_refresh_token" + assert expiry == datetime.datetime.min + datetime.timedelta(seconds=500) + assert extra_data["extra"] == "data" + + +@pytest.mark.asyncio +async def test_refresh_grant_no_access_token(): + request = make_request( + { + # No access token. + "refresh_token": "new_refresh_token", + "expires_in": 500, + "extra": "data", + } + ) + + with pytest.raises(exceptions.RefreshError): + await _client.refresh_grant( + request, "http://example.com", "refresh_token", "client_id", "client_secret" + ) diff --git a/packages/google-auth/tests_async/oauth2/test_credentials_async.py b/packages/google-auth/tests_async/oauth2/test_credentials_async.py new file mode 100644 index 000000000000..5c883d614768 --- /dev/null +++ b/packages/google-auth/tests_async/oauth2/test_credentials_async.py @@ -0,0 +1,478 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import datetime +import json +import os +import pickle +import sys + +import mock +import pytest + +from google.auth import _helpers +from google.auth import exceptions +from google.oauth2 import _credentials_async as _credentials_async +from google.oauth2 import credentials +from tests.oauth2 import test_credentials + + +class TestCredentials: + + TOKEN_URI = "https://example.com/oauth2/token" + REFRESH_TOKEN = "refresh_token" + CLIENT_ID = "client_id" + CLIENT_SECRET = "client_secret" + + @classmethod + def make_credentials(cls): + return _credentials_async.Credentials( + token=None, + refresh_token=cls.REFRESH_TOKEN, + token_uri=cls.TOKEN_URI, + client_id=cls.CLIENT_ID, + client_secret=cls.CLIENT_SECRET, + ) + + def test_default_state(self): + credentials = self.make_credentials() + assert not credentials.valid + # Expiration hasn't been set yet + assert not credentials.expired + # Scopes aren't required for these credentials + assert not credentials.requires_scopes + # Test properties + assert credentials.refresh_token == self.REFRESH_TOKEN + assert credentials.token_uri == self.TOKEN_URI + assert credentials.client_id == self.CLIENT_ID + assert credentials.client_secret == self.CLIENT_SECRET + + @mock.patch("google.oauth2._client_async.refresh_grant", autospec=True) + @mock.patch( + "google.auth._helpers.utcnow", + return_value=datetime.datetime.min + _helpers.CLOCK_SKEW, + ) + @pytest.mark.asyncio + async def test_refresh_success(self, unused_utcnow, refresh_grant): + token = "token" + expiry = _helpers.utcnow() + datetime.timedelta(seconds=500) + grant_response = {"id_token": mock.sentinel.id_token} + refresh_grant.return_value = ( + # Access token + token, + # New refresh token + None, + # Expiry, + expiry, + # Extra data + grant_response, + ) + + request = mock.AsyncMock(spec=["transport.Request"]) + creds = self.make_credentials() + + # Refresh credentials + await creds.refresh(request) + + # Check jwt grant call. + refresh_grant.assert_called_with( + request, + self.TOKEN_URI, + self.REFRESH_TOKEN, + self.CLIENT_ID, + self.CLIENT_SECRET, + None, + ) + + # Check that the credentials have the token and expiry + assert creds.token == token + assert creds.expiry == expiry + assert creds.id_token == mock.sentinel.id_token + + # Check that the credentials are valid (have a token and are not + # expired) + assert creds.valid + + @pytest.mark.asyncio + async def test_refresh_no_refresh_token(self): + request = mock.AsyncMock(spec=["transport.Request"]) + credentials_ = _credentials_async.Credentials(token=None, refresh_token=None) + + with pytest.raises(exceptions.RefreshError, match="necessary fields"): + await credentials_.refresh(request) + + request.assert_not_called() + + @mock.patch("google.oauth2._client_async.refresh_grant", autospec=True) + @mock.patch( + "google.auth._helpers.utcnow", + return_value=datetime.datetime.min + _helpers.CLOCK_SKEW, + ) + @pytest.mark.asyncio + async def test_credentials_with_scopes_requested_refresh_success( + self, unused_utcnow, refresh_grant + ): + scopes = ["email", "profile"] + token = "token" + expiry = _helpers.utcnow() + datetime.timedelta(seconds=500) + grant_response = {"id_token": mock.sentinel.id_token} + refresh_grant.return_value = ( + # Access token + token, + # New refresh token + None, + # Expiry, + expiry, + # Extra data + grant_response, + ) + + request = mock.AsyncMock(spec=["transport.Request"]) + creds = _credentials_async.Credentials( + token=None, + refresh_token=self.REFRESH_TOKEN, + token_uri=self.TOKEN_URI, + client_id=self.CLIENT_ID, + client_secret=self.CLIENT_SECRET, + scopes=scopes, + ) + + # Refresh credentials + await creds.refresh(request) + + # Check jwt grant call. + refresh_grant.assert_called_with( + request, + self.TOKEN_URI, + self.REFRESH_TOKEN, + self.CLIENT_ID, + self.CLIENT_SECRET, + scopes, + ) + + # Check that the credentials have the token and expiry + assert creds.token == token + assert creds.expiry == expiry + assert creds.id_token == mock.sentinel.id_token + assert creds.has_scopes(scopes) + + # Check that the credentials are valid (have a token and are not + # expired.) + assert creds.valid + + @mock.patch("google.oauth2._client_async.refresh_grant", autospec=True) + @mock.patch( + "google.auth._helpers.utcnow", + return_value=datetime.datetime.min + _helpers.CLOCK_SKEW, + ) + @pytest.mark.asyncio + async def test_credentials_with_scopes_returned_refresh_success( + self, unused_utcnow, refresh_grant + ): + scopes = ["email", "profile"] + token = "token" + expiry = _helpers.utcnow() + datetime.timedelta(seconds=500) + grant_response = { + "id_token": mock.sentinel.id_token, + "scopes": " ".join(scopes), + } + refresh_grant.return_value = ( + # Access token + token, + # New refresh token + None, + # Expiry, + expiry, + # Extra data + grant_response, + ) + + request = mock.AsyncMock(spec=["transport.Request"]) + creds = _credentials_async.Credentials( + token=None, + refresh_token=self.REFRESH_TOKEN, + token_uri=self.TOKEN_URI, + client_id=self.CLIENT_ID, + client_secret=self.CLIENT_SECRET, + scopes=scopes, + ) + + # Refresh credentials + await creds.refresh(request) + + # Check jwt grant call. + refresh_grant.assert_called_with( + request, + self.TOKEN_URI, + self.REFRESH_TOKEN, + self.CLIENT_ID, + self.CLIENT_SECRET, + scopes, + ) + + # Check that the credentials have the token and expiry + assert creds.token == token + assert creds.expiry == expiry + assert creds.id_token == mock.sentinel.id_token + assert creds.has_scopes(scopes) + + # Check that the credentials are valid (have a token and are not + # expired.) + assert creds.valid + + @mock.patch("google.oauth2._client_async.refresh_grant", autospec=True) + @mock.patch( + "google.auth._helpers.utcnow", + return_value=datetime.datetime.min + _helpers.CLOCK_SKEW, + ) + @pytest.mark.asyncio + async def test_credentials_with_scopes_refresh_failure_raises_refresh_error( + self, unused_utcnow, refresh_grant + ): + scopes = ["email", "profile"] + scopes_returned = ["email"] + token = "token" + expiry = _helpers.utcnow() + datetime.timedelta(seconds=500) + grant_response = { + "id_token": mock.sentinel.id_token, + "scopes": " ".join(scopes_returned), + } + refresh_grant.return_value = ( + # Access token + token, + # New refresh token + None, + # Expiry, + expiry, + # Extra data + grant_response, + ) + + request = mock.AsyncMock(spec=["transport.Request"]) + creds = _credentials_async.Credentials( + token=None, + refresh_token=self.REFRESH_TOKEN, + token_uri=self.TOKEN_URI, + client_id=self.CLIENT_ID, + client_secret=self.CLIENT_SECRET, + scopes=scopes, + ) + + # Refresh credentials + with pytest.raises( + exceptions.RefreshError, match="Not all requested scopes were granted" + ): + await creds.refresh(request) + + # Check jwt grant call. + refresh_grant.assert_called_with( + request, + self.TOKEN_URI, + self.REFRESH_TOKEN, + self.CLIENT_ID, + self.CLIENT_SECRET, + scopes, + ) + + # Check that the credentials have the token and expiry + assert creds.token == token + assert creds.expiry == expiry + assert creds.id_token == mock.sentinel.id_token + assert creds.has_scopes(scopes) + + # Check that the credentials are valid (have a token and are not + # expired.) + assert creds.valid + + def test_apply_with_quota_project_id(self): + creds = _credentials_async.Credentials( + token="token", + refresh_token=self.REFRESH_TOKEN, + token_uri=self.TOKEN_URI, + client_id=self.CLIENT_ID, + client_secret=self.CLIENT_SECRET, + quota_project_id="quota-project-123", + ) + + headers = {} + creds.apply(headers) + assert headers["x-goog-user-project"] == "quota-project-123" + + def test_apply_with_no_quota_project_id(self): + creds = _credentials_async.Credentials( + token="token", + refresh_token=self.REFRESH_TOKEN, + token_uri=self.TOKEN_URI, + client_id=self.CLIENT_ID, + client_secret=self.CLIENT_SECRET, + ) + + headers = {} + creds.apply(headers) + assert "x-goog-user-project" not in headers + + def test_with_quota_project(self): + creds = _credentials_async.Credentials( + token="token", + refresh_token=self.REFRESH_TOKEN, + token_uri=self.TOKEN_URI, + client_id=self.CLIENT_ID, + client_secret=self.CLIENT_SECRET, + quota_project_id="quota-project-123", + ) + + new_creds = creds.with_quota_project("new-project-456") + assert new_creds.quota_project_id == "new-project-456" + headers = {} + creds.apply(headers) + assert "x-goog-user-project" in headers + + def test_from_authorized_user_info(self): + info = test_credentials.AUTH_USER_INFO.copy() + + creds = _credentials_async.Credentials.from_authorized_user_info(info) + assert creds.client_secret == info["client_secret"] + assert creds.client_id == info["client_id"] + assert creds.refresh_token == info["refresh_token"] + assert creds.token_uri == credentials._GOOGLE_OAUTH2_TOKEN_ENDPOINT + assert creds.scopes is None + + scopes = ["email", "profile"] + creds = _credentials_async.Credentials.from_authorized_user_info(info, scopes) + assert creds.client_secret == info["client_secret"] + assert creds.client_id == info["client_id"] + assert creds.refresh_token == info["refresh_token"] + assert creds.token_uri == credentials._GOOGLE_OAUTH2_TOKEN_ENDPOINT + assert creds.scopes == scopes + + def test_from_authorized_user_file(self): + info = test_credentials.AUTH_USER_INFO.copy() + + creds = _credentials_async.Credentials.from_authorized_user_file( + test_credentials.AUTH_USER_JSON_FILE + ) + assert creds.client_secret == info["client_secret"] + assert creds.client_id == info["client_id"] + assert creds.refresh_token == info["refresh_token"] + assert creds.token_uri == credentials._GOOGLE_OAUTH2_TOKEN_ENDPOINT + assert creds.scopes is None + + scopes = ["email", "profile"] + creds = _credentials_async.Credentials.from_authorized_user_file( + test_credentials.AUTH_USER_JSON_FILE, scopes + ) + assert creds.client_secret == info["client_secret"] + assert creds.client_id == info["client_id"] + assert creds.refresh_token == info["refresh_token"] + assert creds.token_uri == credentials._GOOGLE_OAUTH2_TOKEN_ENDPOINT + assert creds.scopes == scopes + + def test_to_json(self): + info = test_credentials.AUTH_USER_INFO.copy() + creds = _credentials_async.Credentials.from_authorized_user_info(info) + + # Test with no `strip` arg + json_output = creds.to_json() + json_asdict = json.loads(json_output) + assert json_asdict.get("token") == creds.token + assert json_asdict.get("refresh_token") == creds.refresh_token + assert json_asdict.get("token_uri") == creds.token_uri + assert json_asdict.get("client_id") == creds.client_id + assert json_asdict.get("scopes") == creds.scopes + assert json_asdict.get("client_secret") == creds.client_secret + + # Test with a `strip` arg + json_output = creds.to_json(strip=["client_secret"]) + json_asdict = json.loads(json_output) + assert json_asdict.get("token") == creds.token + assert json_asdict.get("refresh_token") == creds.refresh_token + assert json_asdict.get("token_uri") == creds.token_uri + assert json_asdict.get("client_id") == creds.client_id + assert json_asdict.get("scopes") == creds.scopes + assert json_asdict.get("client_secret") is None + + def test_pickle_and_unpickle(self): + creds = self.make_credentials() + unpickled = pickle.loads(pickle.dumps(creds)) + + # make sure attributes aren't lost during pickling + assert list(creds.__dict__).sort() == list(unpickled.__dict__).sort() + + for attr in list(creds.__dict__): + assert getattr(creds, attr) == getattr(unpickled, attr) + + def test_pickle_with_missing_attribute(self): + creds = self.make_credentials() + + # remove an optional attribute before pickling + # this mimics a pickle created with a previous class definition with + # fewer attributes + del creds.__dict__["_quota_project_id"] + + unpickled = pickle.loads(pickle.dumps(creds)) + + # Attribute should be initialized by `__setstate__` + assert unpickled.quota_project_id is None + + # pickles are not compatible across versions + @pytest.mark.skipif( + sys.version_info < (3, 5), + reason="pickle file can only be loaded with Python >= 3.5", + ) + def test_unpickle_old_credentials_pickle(self): + # make sure a credentials file pickled with an older + # library version (google-auth==1.5.1) can be unpickled + with open( + os.path.join(test_credentials.DATA_DIR, "old_oauth_credentials_py3.pickle"), + "rb", + ) as f: + credentials = pickle.load(f) + assert credentials.quota_project_id is None + + +class TestUserAccessTokenCredentials(object): + def test_instance(self): + cred = _credentials_async.UserAccessTokenCredentials() + assert cred._account is None + + cred = cred.with_account("account") + assert cred._account == "account" + + @mock.patch("google.auth._cloud_sdk.get_auth_access_token", autospec=True) + def test_refresh(self, get_auth_access_token): + get_auth_access_token.return_value = "access_token" + cred = _credentials_async.UserAccessTokenCredentials() + cred.refresh(None) + assert cred.token == "access_token" + + def test_with_quota_project(self): + cred = _credentials_async.UserAccessTokenCredentials() + quota_project_cred = cred.with_quota_project("project-foo") + + assert quota_project_cred._quota_project_id == "project-foo" + assert quota_project_cred._account == cred._account + + @mock.patch( + "google.oauth2._credentials_async.UserAccessTokenCredentials.apply", + autospec=True, + ) + @mock.patch( + "google.oauth2._credentials_async.UserAccessTokenCredentials.refresh", + autospec=True, + ) + def test_before_request(self, refresh, apply): + cred = _credentials_async.UserAccessTokenCredentials() + cred.before_request(mock.Mock(), "GET", "https://example.com", {}) + refresh.assert_called() + apply.assert_called() diff --git a/packages/google-auth/tests_async/oauth2/test_id_token.py b/packages/google-auth/tests_async/oauth2/test_id_token.py new file mode 100644 index 000000000000..a46bd615ecaf --- /dev/null +++ b/packages/google-auth/tests_async/oauth2/test_id_token.py @@ -0,0 +1,205 @@ +# Copyright 2020 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os + +import mock +import pytest + +from google.auth import environment_vars +from google.auth import exceptions +import google.auth.compute_engine._metadata +from google.oauth2 import _id_token_async as id_token +from google.oauth2 import id_token as sync_id_token +from tests.oauth2 import test_id_token + + +def make_request(status, data=None): + response = mock.AsyncMock(spec=["transport.Response"]) + response.status = status + + if data is not None: + response.data = mock.AsyncMock(spec=["__call__", "read"]) + response.data.read = mock.AsyncMock(spec=["__call__"], return_value=data) + + request = mock.AsyncMock(spec=["transport.Request"]) + request.return_value = response + return request + + +@pytest.mark.asyncio +async def test__fetch_certs_success(): + certs = {"1": "cert"} + request = make_request(200, certs) + + returned_certs = await id_token._fetch_certs(request, mock.sentinel.cert_url) + + request.assert_called_once_with(mock.sentinel.cert_url, method="GET") + assert returned_certs == certs + + +@pytest.mark.asyncio +async def test__fetch_certs_failure(): + request = make_request(404) + + with pytest.raises(exceptions.TransportError): + await id_token._fetch_certs(request, mock.sentinel.cert_url) + + request.assert_called_once_with(mock.sentinel.cert_url, method="GET") + + +@mock.patch("google.auth.jwt.decode", autospec=True) +@mock.patch("google.oauth2._id_token_async._fetch_certs", autospec=True) +@pytest.mark.asyncio +async def test_verify_token(_fetch_certs, decode): + result = await id_token.verify_token(mock.sentinel.token, mock.sentinel.request) + + assert result == decode.return_value + _fetch_certs.assert_called_once_with( + mock.sentinel.request, sync_id_token._GOOGLE_OAUTH2_CERTS_URL + ) + decode.assert_called_once_with( + mock.sentinel.token, certs=_fetch_certs.return_value, audience=None + ) + + +@mock.patch("google.auth.jwt.decode", autospec=True) +@mock.patch("google.oauth2._id_token_async._fetch_certs", autospec=True) +@pytest.mark.asyncio +async def test_verify_token_args(_fetch_certs, decode): + result = await id_token.verify_token( + mock.sentinel.token, + mock.sentinel.request, + audience=mock.sentinel.audience, + certs_url=mock.sentinel.certs_url, + ) + + assert result == decode.return_value + _fetch_certs.assert_called_once_with(mock.sentinel.request, mock.sentinel.certs_url) + decode.assert_called_once_with( + mock.sentinel.token, + certs=_fetch_certs.return_value, + audience=mock.sentinel.audience, + ) + + +@mock.patch("google.oauth2._id_token_async.verify_token", autospec=True) +@pytest.mark.asyncio +async def test_verify_oauth2_token(verify_token): + verify_token.return_value = {"iss": "accounts.google.com"} + result = await id_token.verify_oauth2_token( + mock.sentinel.token, mock.sentinel.request, audience=mock.sentinel.audience + ) + + assert result == verify_token.return_value + verify_token.assert_called_once_with( + mock.sentinel.token, + mock.sentinel.request, + audience=mock.sentinel.audience, + certs_url=sync_id_token._GOOGLE_OAUTH2_CERTS_URL, + ) + + +@mock.patch("google.oauth2._id_token_async.verify_token", autospec=True) +@pytest.mark.asyncio +async def test_verify_oauth2_token_invalid_iss(verify_token): + verify_token.return_value = {"iss": "invalid_issuer"} + + with pytest.raises(exceptions.GoogleAuthError): + await id_token.verify_oauth2_token( + mock.sentinel.token, mock.sentinel.request, audience=mock.sentinel.audience + ) + + +@mock.patch("google.oauth2._id_token_async.verify_token", autospec=True) +@pytest.mark.asyncio +async def test_verify_firebase_token(verify_token): + result = await id_token.verify_firebase_token( + mock.sentinel.token, mock.sentinel.request, audience=mock.sentinel.audience + ) + + assert result == verify_token.return_value + verify_token.assert_called_once_with( + mock.sentinel.token, + mock.sentinel.request, + audience=mock.sentinel.audience, + certs_url=sync_id_token._GOOGLE_APIS_CERTS_URL, + ) + + +@pytest.mark.asyncio +async def test_fetch_id_token_from_metadata_server(): + def mock_init(self, request, audience, use_metadata_identity_endpoint): + assert use_metadata_identity_endpoint + self.token = "id_token" + + with mock.patch.multiple( + google.auth.compute_engine.IDTokenCredentials, + __init__=mock_init, + refresh=mock.Mock(), + ): + request = mock.AsyncMock() + token = await id_token.fetch_id_token(request, "https://pubsub.googleapis.com") + assert token == "id_token" + + +@mock.patch.object( + google.auth.compute_engine.IDTokenCredentials, + "__init__", + side_effect=exceptions.TransportError(), +) +@pytest.mark.asyncio +async def test_fetch_id_token_from_explicit_cred_json_file(mock_init, monkeypatch): + monkeypatch.setenv(environment_vars.CREDENTIALS, test_id_token.SERVICE_ACCOUNT_FILE) + + async def mock_refresh(self, request): + self.token = "id_token" + + with mock.patch.object( + google.oauth2._service_account_async.IDTokenCredentials, "refresh", mock_refresh + ): + request = mock.AsyncMock() + token = await id_token.fetch_id_token(request, "https://pubsub.googleapis.com") + assert token == "id_token" + + +@mock.patch.object( + google.auth.compute_engine.IDTokenCredentials, + "__init__", + side_effect=exceptions.TransportError(), +) +@pytest.mark.asyncio +async def test_fetch_id_token_no_cred_json_file(mock_init, monkeypatch): + monkeypatch.delenv(environment_vars.CREDENTIALS, raising=False) + + with pytest.raises(exceptions.DefaultCredentialsError): + request = mock.AsyncMock() + await id_token.fetch_id_token(request, "https://pubsub.googleapis.com") + + +@mock.patch.object( + google.auth.compute_engine.IDTokenCredentials, + "__init__", + side_effect=exceptions.TransportError(), +) +@pytest.mark.asyncio +async def test_fetch_id_token_invalid_cred_file(mock_init, monkeypatch): + not_json_file = os.path.join( + os.path.dirname(__file__), "../../tests/data/public_cert.pem" + ) + monkeypatch.setenv(environment_vars.CREDENTIALS, not_json_file) + + with pytest.raises(exceptions.DefaultCredentialsError): + request = mock.AsyncMock() + await id_token.fetch_id_token(request, "https://pubsub.googleapis.com") diff --git a/packages/google-auth/tests_async/oauth2/test_service_account_async.py b/packages/google-auth/tests_async/oauth2/test_service_account_async.py new file mode 100644 index 000000000000..40794536cee9 --- /dev/null +++ b/packages/google-auth/tests_async/oauth2/test_service_account_async.py @@ -0,0 +1,372 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import datetime + +import mock +import pytest + +from google.auth import _helpers +from google.auth import crypt +from google.auth import jwt +from google.auth import transport +from google.oauth2 import _service_account_async as service_account +from tests.oauth2 import test_service_account + + +class TestCredentials(object): + SERVICE_ACCOUNT_EMAIL = "service-account@example.com" + TOKEN_URI = "https://example.com/oauth2/token" + + @classmethod + def make_credentials(cls): + return service_account.Credentials( + test_service_account.SIGNER, cls.SERVICE_ACCOUNT_EMAIL, cls.TOKEN_URI + ) + + def test_from_service_account_info(self): + credentials = service_account.Credentials.from_service_account_info( + test_service_account.SERVICE_ACCOUNT_INFO + ) + + assert ( + credentials._signer.key_id + == test_service_account.SERVICE_ACCOUNT_INFO["private_key_id"] + ) + assert ( + credentials.service_account_email + == test_service_account.SERVICE_ACCOUNT_INFO["client_email"] + ) + assert ( + credentials._token_uri + == test_service_account.SERVICE_ACCOUNT_INFO["token_uri"] + ) + + def test_from_service_account_info_args(self): + info = test_service_account.SERVICE_ACCOUNT_INFO.copy() + scopes = ["email", "profile"] + subject = "subject" + additional_claims = {"meta": "data"} + + credentials = service_account.Credentials.from_service_account_info( + info, scopes=scopes, subject=subject, additional_claims=additional_claims + ) + + assert credentials.service_account_email == info["client_email"] + assert credentials.project_id == info["project_id"] + assert credentials._signer.key_id == info["private_key_id"] + assert credentials._token_uri == info["token_uri"] + assert credentials._scopes == scopes + assert credentials._subject == subject + assert credentials._additional_claims == additional_claims + + def test_from_service_account_file(self): + info = test_service_account.SERVICE_ACCOUNT_INFO.copy() + + credentials = service_account.Credentials.from_service_account_file( + test_service_account.SERVICE_ACCOUNT_JSON_FILE + ) + + assert credentials.service_account_email == info["client_email"] + assert credentials.project_id == info["project_id"] + assert credentials._signer.key_id == info["private_key_id"] + assert credentials._token_uri == info["token_uri"] + + def test_from_service_account_file_args(self): + info = test_service_account.SERVICE_ACCOUNT_INFO.copy() + scopes = ["email", "profile"] + subject = "subject" + additional_claims = {"meta": "data"} + + credentials = service_account.Credentials.from_service_account_file( + test_service_account.SERVICE_ACCOUNT_JSON_FILE, + subject=subject, + scopes=scopes, + additional_claims=additional_claims, + ) + + assert credentials.service_account_email == info["client_email"] + assert credentials.project_id == info["project_id"] + assert credentials._signer.key_id == info["private_key_id"] + assert credentials._token_uri == info["token_uri"] + assert credentials._scopes == scopes + assert credentials._subject == subject + assert credentials._additional_claims == additional_claims + + def test_default_state(self): + credentials = self.make_credentials() + assert not credentials.valid + # Expiration hasn't been set yet + assert not credentials.expired + # Scopes haven't been specified yet + assert credentials.requires_scopes + + def test_sign_bytes(self): + credentials = self.make_credentials() + to_sign = b"123" + signature = credentials.sign_bytes(to_sign) + assert crypt.verify_signature( + to_sign, signature, test_service_account.PUBLIC_CERT_BYTES + ) + + def test_signer(self): + credentials = self.make_credentials() + assert isinstance(credentials.signer, crypt.Signer) + + def test_signer_email(self): + credentials = self.make_credentials() + assert credentials.signer_email == self.SERVICE_ACCOUNT_EMAIL + + def test_create_scoped(self): + credentials = self.make_credentials() + scopes = ["email", "profile"] + credentials = credentials.with_scopes(scopes) + assert credentials._scopes == scopes + + def test_with_claims(self): + credentials = self.make_credentials() + new_credentials = credentials.with_claims({"meep": "moop"}) + assert new_credentials._additional_claims == {"meep": "moop"} + + def test_with_quota_project(self): + credentials = self.make_credentials() + new_credentials = credentials.with_quota_project("new-project-456") + assert new_credentials.quota_project_id == "new-project-456" + hdrs = {} + new_credentials.apply(hdrs, token="tok") + assert "x-goog-user-project" in hdrs + + def test__make_authorization_grant_assertion(self): + credentials = self.make_credentials() + token = credentials._make_authorization_grant_assertion() + payload = jwt.decode(token, test_service_account.PUBLIC_CERT_BYTES) + assert payload["iss"] == self.SERVICE_ACCOUNT_EMAIL + assert payload["aud"] == self.TOKEN_URI + + def test__make_authorization_grant_assertion_scoped(self): + credentials = self.make_credentials() + scopes = ["email", "profile"] + credentials = credentials.with_scopes(scopes) + token = credentials._make_authorization_grant_assertion() + payload = jwt.decode(token, test_service_account.PUBLIC_CERT_BYTES) + assert payload["scope"] == "email profile" + + def test__make_authorization_grant_assertion_subject(self): + credentials = self.make_credentials() + subject = "user@example.com" + credentials = credentials.with_subject(subject) + token = credentials._make_authorization_grant_assertion() + payload = jwt.decode(token, test_service_account.PUBLIC_CERT_BYTES) + assert payload["sub"] == subject + + @mock.patch("google.oauth2._client_async.jwt_grant", autospec=True) + @pytest.mark.asyncio + async def test_refresh_success(self, jwt_grant): + credentials = self.make_credentials() + token = "token" + jwt_grant.return_value = ( + token, + _helpers.utcnow() + datetime.timedelta(seconds=500), + {}, + ) + request = mock.create_autospec(transport.Request, instance=True) + + # Refresh credentials + await credentials.refresh(request) + + # Check jwt grant call. + assert jwt_grant.called + + called_request, token_uri, assertion = jwt_grant.call_args[0] + assert called_request == request + assert token_uri == credentials._token_uri + assert jwt.decode(assertion, test_service_account.PUBLIC_CERT_BYTES) + # No further assertion done on the token, as there are separate tests + # for checking the authorization grant assertion. + + # Check that the credentials have the token. + assert credentials.token == token + + # Check that the credentials are valid (have a token and are not + # expired) + assert credentials.valid + + @mock.patch("google.oauth2._client_async.jwt_grant", autospec=True) + @pytest.mark.asyncio + async def test_before_request_refreshes(self, jwt_grant): + credentials = self.make_credentials() + token = "token" + jwt_grant.return_value = ( + token, + _helpers.utcnow() + datetime.timedelta(seconds=500), + None, + ) + request = mock.create_autospec(transport.Request, instance=True) + + # Credentials should start as invalid + assert not credentials.valid + + # before_request should cause a refresh + await credentials.before_request(request, "GET", "http://example.com?a=1#3", {}) + + # The refresh endpoint should've been called. + assert jwt_grant.called + + # Credentials should now be valid. + assert credentials.valid + + +class TestIDTokenCredentials(object): + SERVICE_ACCOUNT_EMAIL = "service-account@example.com" + TOKEN_URI = "https://example.com/oauth2/token" + TARGET_AUDIENCE = "https://example.com" + + @classmethod + def make_credentials(cls): + return service_account.IDTokenCredentials( + test_service_account.SIGNER, + cls.SERVICE_ACCOUNT_EMAIL, + cls.TOKEN_URI, + cls.TARGET_AUDIENCE, + ) + + def test_from_service_account_info(self): + credentials = service_account.IDTokenCredentials.from_service_account_info( + test_service_account.SERVICE_ACCOUNT_INFO, + target_audience=self.TARGET_AUDIENCE, + ) + + assert ( + credentials._signer.key_id + == test_service_account.SERVICE_ACCOUNT_INFO["private_key_id"] + ) + assert ( + credentials.service_account_email + == test_service_account.SERVICE_ACCOUNT_INFO["client_email"] + ) + assert ( + credentials._token_uri + == test_service_account.SERVICE_ACCOUNT_INFO["token_uri"] + ) + assert credentials._target_audience == self.TARGET_AUDIENCE + + def test_from_service_account_file(self): + info = test_service_account.SERVICE_ACCOUNT_INFO.copy() + + credentials = service_account.IDTokenCredentials.from_service_account_file( + test_service_account.SERVICE_ACCOUNT_JSON_FILE, + target_audience=self.TARGET_AUDIENCE, + ) + + assert credentials.service_account_email == info["client_email"] + assert credentials._signer.key_id == info["private_key_id"] + assert credentials._token_uri == info["token_uri"] + assert credentials._target_audience == self.TARGET_AUDIENCE + + def test_default_state(self): + credentials = self.make_credentials() + assert not credentials.valid + # Expiration hasn't been set yet + assert not credentials.expired + + def test_sign_bytes(self): + credentials = self.make_credentials() + to_sign = b"123" + signature = credentials.sign_bytes(to_sign) + assert crypt.verify_signature( + to_sign, signature, test_service_account.PUBLIC_CERT_BYTES + ) + + def test_signer(self): + credentials = self.make_credentials() + assert isinstance(credentials.signer, crypt.Signer) + + def test_signer_email(self): + credentials = self.make_credentials() + assert credentials.signer_email == self.SERVICE_ACCOUNT_EMAIL + + def test_with_target_audience(self): + credentials = self.make_credentials() + new_credentials = credentials.with_target_audience("https://new.example.com") + assert new_credentials._target_audience == "https://new.example.com" + + def test_with_quota_project(self): + credentials = self.make_credentials() + new_credentials = credentials.with_quota_project("project-foo") + assert new_credentials._quota_project_id == "project-foo" + + def test__make_authorization_grant_assertion(self): + credentials = self.make_credentials() + token = credentials._make_authorization_grant_assertion() + payload = jwt.decode(token, test_service_account.PUBLIC_CERT_BYTES) + assert payload["iss"] == self.SERVICE_ACCOUNT_EMAIL + assert payload["aud"] == self.TOKEN_URI + assert payload["target_audience"] == self.TARGET_AUDIENCE + + @mock.patch("google.oauth2._client_async.id_token_jwt_grant", autospec=True) + @pytest.mark.asyncio + async def test_refresh_success(self, id_token_jwt_grant): + credentials = self.make_credentials() + token = "token" + id_token_jwt_grant.return_value = ( + token, + _helpers.utcnow() + datetime.timedelta(seconds=500), + {}, + ) + + request = mock.AsyncMock(spec=["transport.Request"]) + + # Refresh credentials + await credentials.refresh(request) + + # Check jwt grant call. + assert id_token_jwt_grant.called + + called_request, token_uri, assertion = id_token_jwt_grant.call_args[0] + assert called_request == request + assert token_uri == credentials._token_uri + assert jwt.decode(assertion, test_service_account.PUBLIC_CERT_BYTES) + # No further assertion done on the token, as there are separate tests + # for checking the authorization grant assertion. + + # Check that the credentials have the token. + assert credentials.token == token + + # Check that the credentials are valid (have a token and are not + # expired) + assert credentials.valid + + @mock.patch("google.oauth2._client_async.id_token_jwt_grant", autospec=True) + @pytest.mark.asyncio + async def test_before_request_refreshes(self, id_token_jwt_grant): + credentials = self.make_credentials() + token = "token" + id_token_jwt_grant.return_value = ( + token, + _helpers.utcnow() + datetime.timedelta(seconds=500), + None, + ) + request = mock.AsyncMock(spec=["transport.Request"]) + + # Credentials should start as invalid + assert not credentials.valid + + # before_request should cause a refresh + await credentials.before_request(request, "GET", "http://example.com?a=1#3", {}) + + # The refresh endpoint should've been called. + assert id_token_jwt_grant.called + + # Credentials should now be valid. + assert credentials.valid diff --git a/packages/google-auth/tests_async/test__default_async.py b/packages/google-auth/tests_async/test__default_async.py new file mode 100644 index 000000000000..bca396aeef54 --- /dev/null +++ b/packages/google-auth/tests_async/test__default_async.py @@ -0,0 +1,468 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import os + +import mock +import pytest + +from google.auth import _credentials_async as credentials +from google.auth import _default_async as _default +from google.auth import app_engine +from google.auth import compute_engine +from google.auth import environment_vars +from google.auth import exceptions +from google.oauth2 import _service_account_async as service_account +import google.oauth2.credentials +from tests import test__default as test_default + +MOCK_CREDENTIALS = mock.Mock(spec=credentials.CredentialsWithQuotaProject) +MOCK_CREDENTIALS.with_quota_project.return_value = MOCK_CREDENTIALS + +LOAD_FILE_PATCH = mock.patch( + "google.auth._default_async.load_credentials_from_file", + return_value=(MOCK_CREDENTIALS, mock.sentinel.project_id), + autospec=True, +) + + +def test_load_credentials_from_missing_file(): + with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: + _default.load_credentials_from_file("") + + assert excinfo.match(r"not found") + + +def test_load_credentials_from_file_invalid_json(tmpdir): + jsonfile = tmpdir.join("invalid.json") + jsonfile.write("{") + + with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: + _default.load_credentials_from_file(str(jsonfile)) + + assert excinfo.match(r"not a valid json file") + + +def test_load_credentials_from_file_invalid_type(tmpdir): + jsonfile = tmpdir.join("invalid.json") + jsonfile.write(json.dumps({"type": "not-a-real-type"})) + + with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: + _default.load_credentials_from_file(str(jsonfile)) + + assert excinfo.match(r"does not have a valid type") + + +def test_load_credentials_from_file_authorized_user(): + credentials, project_id = _default.load_credentials_from_file( + test_default.AUTHORIZED_USER_FILE + ) + assert isinstance(credentials, google.oauth2._credentials_async.Credentials) + assert project_id is None + + +def test_load_credentials_from_file_no_type(tmpdir): + # use the client_secrets.json, which is valid json but not a + # loadable credentials type + with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: + _default.load_credentials_from_file(test_default.CLIENT_SECRETS_FILE) + + assert excinfo.match(r"does not have a valid type") + assert excinfo.match(r"Type is None") + + +def test_load_credentials_from_file_authorized_user_bad_format(tmpdir): + filename = tmpdir.join("authorized_user_bad.json") + filename.write(json.dumps({"type": "authorized_user"})) + + with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: + _default.load_credentials_from_file(str(filename)) + + assert excinfo.match(r"Failed to load authorized user") + assert excinfo.match(r"missing fields") + + +def test_load_credentials_from_file_authorized_user_cloud_sdk(): + with pytest.warns(UserWarning, match="Cloud SDK"): + credentials, project_id = _default.load_credentials_from_file( + test_default.AUTHORIZED_USER_CLOUD_SDK_FILE + ) + assert isinstance(credentials, google.oauth2._credentials_async.Credentials) + assert project_id is None + + # No warning if the json file has quota project id. + credentials, project_id = _default.load_credentials_from_file( + test_default.AUTHORIZED_USER_CLOUD_SDK_WITH_QUOTA_PROJECT_ID_FILE + ) + assert isinstance(credentials, google.oauth2._credentials_async.Credentials) + assert project_id is None + + +def test_load_credentials_from_file_authorized_user_cloud_sdk_with_scopes(): + with pytest.warns(UserWarning, match="Cloud SDK"): + credentials, project_id = _default.load_credentials_from_file( + test_default.AUTHORIZED_USER_CLOUD_SDK_FILE, + scopes=["https://www.google.com/calendar/feeds"], + ) + assert isinstance(credentials, google.oauth2._credentials_async.Credentials) + assert project_id is None + assert credentials.scopes == ["https://www.google.com/calendar/feeds"] + + +def test_load_credentials_from_file_authorized_user_cloud_sdk_with_quota_project(): + credentials, project_id = _default.load_credentials_from_file( + test_default.AUTHORIZED_USER_CLOUD_SDK_FILE, quota_project_id="project-foo" + ) + + assert isinstance(credentials, google.oauth2._credentials_async.Credentials) + assert project_id is None + assert credentials.quota_project_id == "project-foo" + + +def test_load_credentials_from_file_service_account(): + credentials, project_id = _default.load_credentials_from_file( + test_default.SERVICE_ACCOUNT_FILE + ) + assert isinstance(credentials, service_account.Credentials) + assert project_id == test_default.SERVICE_ACCOUNT_FILE_DATA["project_id"] + + +def test_load_credentials_from_file_service_account_with_scopes(): + credentials, project_id = _default.load_credentials_from_file( + test_default.SERVICE_ACCOUNT_FILE, + scopes=["https://www.google.com/calendar/feeds"], + ) + assert isinstance(credentials, service_account.Credentials) + assert project_id == test_default.SERVICE_ACCOUNT_FILE_DATA["project_id"] + assert credentials.scopes == ["https://www.google.com/calendar/feeds"] + + +def test_load_credentials_from_file_service_account_bad_format(tmpdir): + filename = tmpdir.join("serivce_account_bad.json") + filename.write(json.dumps({"type": "service_account"})) + + with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: + _default.load_credentials_from_file(str(filename)) + + assert excinfo.match(r"Failed to load service account") + assert excinfo.match(r"missing fields") + + +@mock.patch.dict(os.environ, {}, clear=True) +def test__get_explicit_environ_credentials_no_env(): + assert _default._get_explicit_environ_credentials() == (None, None) + + +@LOAD_FILE_PATCH +def test__get_explicit_environ_credentials(load, monkeypatch): + monkeypatch.setenv(environment_vars.CREDENTIALS, "filename") + + credentials, project_id = _default._get_explicit_environ_credentials() + + assert credentials is MOCK_CREDENTIALS + assert project_id is mock.sentinel.project_id + load.assert_called_with("filename") + + +@LOAD_FILE_PATCH +def test__get_explicit_environ_credentials_no_project_id(load, monkeypatch): + load.return_value = MOCK_CREDENTIALS, None + monkeypatch.setenv(environment_vars.CREDENTIALS, "filename") + + credentials, project_id = _default._get_explicit_environ_credentials() + + assert credentials is MOCK_CREDENTIALS + assert project_id is None + + +@LOAD_FILE_PATCH +@mock.patch( + "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True +) +def test__get_gcloud_sdk_credentials(get_adc_path, load): + get_adc_path.return_value = test_default.SERVICE_ACCOUNT_FILE + + credentials, project_id = _default._get_gcloud_sdk_credentials() + + assert credentials is MOCK_CREDENTIALS + assert project_id is mock.sentinel.project_id + load.assert_called_with(test_default.SERVICE_ACCOUNT_FILE) + + +@mock.patch( + "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True +) +def test__get_gcloud_sdk_credentials_non_existent(get_adc_path, tmpdir): + non_existent = tmpdir.join("non-existent") + get_adc_path.return_value = str(non_existent) + + credentials, project_id = _default._get_gcloud_sdk_credentials() + + assert credentials is None + assert project_id is None + + +@mock.patch( + "google.auth._cloud_sdk.get_project_id", + return_value=mock.sentinel.project_id, + autospec=True, +) +@mock.patch("os.path.isfile", return_value=True, autospec=True) +@LOAD_FILE_PATCH +def test__get_gcloud_sdk_credentials_project_id(load, unused_isfile, get_project_id): + # Don't return a project ID from load file, make the function check + # the Cloud SDK project. + load.return_value = MOCK_CREDENTIALS, None + + credentials, project_id = _default._get_gcloud_sdk_credentials() + + assert credentials == MOCK_CREDENTIALS + assert project_id == mock.sentinel.project_id + assert get_project_id.called + + +@mock.patch("google.auth._cloud_sdk.get_project_id", return_value=None, autospec=True) +@mock.patch("os.path.isfile", return_value=True) +@LOAD_FILE_PATCH +def test__get_gcloud_sdk_credentials_no_project_id(load, unused_isfile, get_project_id): + # Don't return a project ID from load file, make the function check + # the Cloud SDK project. + load.return_value = MOCK_CREDENTIALS, None + + credentials, project_id = _default._get_gcloud_sdk_credentials() + + assert credentials == MOCK_CREDENTIALS + assert project_id is None + assert get_project_id.called + + +class _AppIdentityModule(object): + """The interface of the App Idenity app engine module. + See https://cloud.google.com/appengine/docs/standard/python/refdocs\ + /google.appengine.api.app_identity.app_identity + """ + + def get_application_id(self): + raise NotImplementedError() + + +@pytest.fixture +def app_identity(monkeypatch): + """Mocks the app_identity module for google.auth.app_engine.""" + app_identity_module = mock.create_autospec(_AppIdentityModule, instance=True) + monkeypatch.setattr(app_engine, "app_identity", app_identity_module) + yield app_identity_module + + +def test__get_gae_credentials(app_identity): + app_identity.get_application_id.return_value = mock.sentinel.project + + credentials, project_id = _default._get_gae_credentials() + + assert isinstance(credentials, app_engine.Credentials) + assert project_id == mock.sentinel.project + + +def test__get_gae_credentials_no_app_engine(): + import sys + + with mock.patch.dict("sys.modules"): + sys.modules["google.auth.app_engine"] = None + credentials, project_id = _default._get_gae_credentials() + assert credentials is None + assert project_id is None + + +def test__get_gae_credentials_no_apis(): + assert _default._get_gae_credentials() == (None, None) + + +@mock.patch( + "google.auth.compute_engine._metadata.ping", return_value=True, autospec=True +) +@mock.patch( + "google.auth.compute_engine._metadata.get_project_id", + return_value="example-project", + autospec=True, +) +def test__get_gce_credentials(unused_get, unused_ping): + credentials, project_id = _default._get_gce_credentials() + + assert isinstance(credentials, compute_engine.Credentials) + assert project_id == "example-project" + + +@mock.patch( + "google.auth.compute_engine._metadata.ping", return_value=False, autospec=True +) +def test__get_gce_credentials_no_ping(unused_ping): + credentials, project_id = _default._get_gce_credentials() + + assert credentials is None + assert project_id is None + + +@mock.patch( + "google.auth.compute_engine._metadata.ping", return_value=True, autospec=True +) +@mock.patch( + "google.auth.compute_engine._metadata.get_project_id", + side_effect=exceptions.TransportError(), + autospec=True, +) +def test__get_gce_credentials_no_project_id(unused_get, unused_ping): + credentials, project_id = _default._get_gce_credentials() + + assert isinstance(credentials, compute_engine.Credentials) + assert project_id is None + + +def test__get_gce_credentials_no_compute_engine(): + import sys + + with mock.patch.dict("sys.modules"): + sys.modules["google.auth.compute_engine"] = None + credentials, project_id = _default._get_gce_credentials() + assert credentials is None + assert project_id is None + + +@mock.patch( + "google.auth.compute_engine._metadata.ping", return_value=False, autospec=True +) +def test__get_gce_credentials_explicit_request(ping): + _default._get_gce_credentials(mock.sentinel.request) + ping.assert_called_with(request=mock.sentinel.request) + + +@mock.patch( + "google.auth._default_async._get_explicit_environ_credentials", + return_value=(MOCK_CREDENTIALS, mock.sentinel.project_id), + autospec=True, +) +def test_default_early_out(unused_get): + assert _default.default_async() == (MOCK_CREDENTIALS, mock.sentinel.project_id) + + +@mock.patch( + "google.auth._default_async._get_explicit_environ_credentials", + return_value=(MOCK_CREDENTIALS, mock.sentinel.project_id), + autospec=True, +) +def test_default_explict_project_id(unused_get, monkeypatch): + monkeypatch.setenv(environment_vars.PROJECT, "explicit-env") + assert _default.default_async() == (MOCK_CREDENTIALS, "explicit-env") + + +@mock.patch( + "google.auth._default_async._get_explicit_environ_credentials", + return_value=(MOCK_CREDENTIALS, mock.sentinel.project_id), + autospec=True, +) +def test_default_explict_legacy_project_id(unused_get, monkeypatch): + monkeypatch.setenv(environment_vars.LEGACY_PROJECT, "explicit-env") + assert _default.default_async() == (MOCK_CREDENTIALS, "explicit-env") + + +@mock.patch("logging.Logger.warning", autospec=True) +@mock.patch( + "google.auth._default_async._get_explicit_environ_credentials", + return_value=(MOCK_CREDENTIALS, None), + autospec=True, +) +@mock.patch( + "google.auth._default_async._get_gcloud_sdk_credentials", + return_value=(MOCK_CREDENTIALS, None), + autospec=True, +) +@mock.patch( + "google.auth._default_async._get_gae_credentials", + return_value=(MOCK_CREDENTIALS, None), + autospec=True, +) +@mock.patch( + "google.auth._default_async._get_gce_credentials", + return_value=(MOCK_CREDENTIALS, None), + autospec=True, +) +def test_default_without_project_id( + unused_gce, unused_gae, unused_sdk, unused_explicit, logger_warning +): + assert _default.default_async() == (MOCK_CREDENTIALS, None) + logger_warning.assert_called_with(mock.ANY, mock.ANY, mock.ANY) + + +@mock.patch( + "google.auth._default_async._get_explicit_environ_credentials", + return_value=(None, None), + autospec=True, +) +@mock.patch( + "google.auth._default_async._get_gcloud_sdk_credentials", + return_value=(None, None), + autospec=True, +) +@mock.patch( + "google.auth._default_async._get_gae_credentials", + return_value=(None, None), + autospec=True, +) +@mock.patch( + "google.auth._default_async._get_gce_credentials", + return_value=(None, None), + autospec=True, +) +def test_default_fail(unused_gce, unused_gae, unused_sdk, unused_explicit): + with pytest.raises(exceptions.DefaultCredentialsError): + assert _default.default_async() + + +@mock.patch( + "google.auth._default_async._get_explicit_environ_credentials", + return_value=(MOCK_CREDENTIALS, mock.sentinel.project_id), + autospec=True, +) +@mock.patch( + "google.auth._credentials_async.with_scopes_if_required", + return_value=MOCK_CREDENTIALS, + autospec=True, +) +def test_default_scoped(with_scopes, unused_get): + scopes = ["one", "two"] + + credentials, project_id = _default.default_async(scopes=scopes) + + assert credentials == with_scopes.return_value + assert project_id == mock.sentinel.project_id + with_scopes.assert_called_once_with(MOCK_CREDENTIALS, scopes) + + +@mock.patch( + "google.auth._default_async._get_explicit_environ_credentials", + return_value=(MOCK_CREDENTIALS, mock.sentinel.project_id), + autospec=True, +) +def test_default_no_app_engine_compute_engine_module(unused_get): + """ + google.auth.compute_engine and google.auth.app_engine are both optional + to allow not including them when using this package. This verifies + that default fails gracefully if these modules are absent + """ + import sys + + with mock.patch.dict("sys.modules"): + sys.modules["google.auth.compute_engine"] = None + sys.modules["google.auth.app_engine"] = None + assert _default.default_async() == (MOCK_CREDENTIALS, mock.sentinel.project_id) diff --git a/packages/google-auth/tests_async/test_credentials_async.py b/packages/google-auth/tests_async/test_credentials_async.py new file mode 100644 index 000000000000..0a4890825f23 --- /dev/null +++ b/packages/google-auth/tests_async/test_credentials_async.py @@ -0,0 +1,177 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import datetime + +import pytest + +from google.auth import _credentials_async as credentials +from google.auth import _helpers + + +class CredentialsImpl(credentials.Credentials): + def refresh(self, request): + self.token = request + + def with_quota_project(self, quota_project_id): + raise NotImplementedError() + + +def test_credentials_constructor(): + credentials = CredentialsImpl() + assert not credentials.token + assert not credentials.expiry + assert not credentials.expired + assert not credentials.valid + + +def test_expired_and_valid(): + credentials = CredentialsImpl() + credentials.token = "token" + + assert credentials.valid + assert not credentials.expired + + # Set the expiration to one second more than now plus the clock skew + # accomodation. These credentials should be valid. + credentials.expiry = ( + datetime.datetime.utcnow() + _helpers.CLOCK_SKEW + datetime.timedelta(seconds=1) + ) + + assert credentials.valid + assert not credentials.expired + + # Set the credentials expiration to now. Because of the clock skew + # accomodation, these credentials should report as expired. + credentials.expiry = datetime.datetime.utcnow() + + assert not credentials.valid + assert credentials.expired + + +@pytest.mark.asyncio +async def test_before_request(): + credentials = CredentialsImpl() + request = "token" + headers = {} + + # First call should call refresh, setting the token. + await credentials.before_request(request, "http://example.com", "GET", headers) + assert credentials.valid + assert credentials.token == "token" + assert headers["authorization"] == "Bearer token" + + request = "token2" + headers = {} + + # Second call shouldn't call refresh. + credentials.before_request(request, "http://example.com", "GET", headers) + + assert credentials.valid + assert credentials.token == "token" + + +def test_anonymous_credentials_ctor(): + anon = credentials.AnonymousCredentials() + + assert anon.token is None + assert anon.expiry is None + assert not anon.expired + assert anon.valid + + +def test_anonymous_credentials_refresh(): + anon = credentials.AnonymousCredentials() + + request = object() + with pytest.raises(ValueError): + anon.refresh(request) + + +def test_anonymous_credentials_apply_default(): + anon = credentials.AnonymousCredentials() + headers = {} + anon.apply(headers) + assert headers == {} + with pytest.raises(ValueError): + anon.apply(headers, token="TOKEN") + + +def test_anonymous_credentials_before_request(): + anon = credentials.AnonymousCredentials() + request = object() + method = "GET" + url = "https://example.com/api/endpoint" + headers = {} + anon.before_request(request, method, url, headers) + assert headers == {} + + +class ReadOnlyScopedCredentialsImpl(credentials.ReadOnlyScoped, CredentialsImpl): + @property + def requires_scopes(self): + return super(ReadOnlyScopedCredentialsImpl, self).requires_scopes + + +def test_readonly_scoped_credentials_constructor(): + credentials = ReadOnlyScopedCredentialsImpl() + assert credentials._scopes is None + + +def test_readonly_scoped_credentials_scopes(): + credentials = ReadOnlyScopedCredentialsImpl() + credentials._scopes = ["one", "two"] + assert credentials.scopes == ["one", "two"] + assert credentials.has_scopes(["one"]) + assert credentials.has_scopes(["two"]) + assert credentials.has_scopes(["one", "two"]) + assert not credentials.has_scopes(["three"]) + + +def test_readonly_scoped_credentials_requires_scopes(): + credentials = ReadOnlyScopedCredentialsImpl() + assert not credentials.requires_scopes + + +class RequiresScopedCredentialsImpl(credentials.Scoped, CredentialsImpl): + def __init__(self, scopes=None): + super(RequiresScopedCredentialsImpl, self).__init__() + self._scopes = scopes + + @property + def requires_scopes(self): + return not self.scopes + + def with_scopes(self, scopes): + return RequiresScopedCredentialsImpl(scopes=scopes) + + +def test_create_scoped_if_required_scoped(): + unscoped_credentials = RequiresScopedCredentialsImpl() + scoped_credentials = credentials.with_scopes_if_required( + unscoped_credentials, ["one", "two"] + ) + + assert scoped_credentials is not unscoped_credentials + assert not scoped_credentials.requires_scopes + assert scoped_credentials.has_scopes(["one", "two"]) + + +def test_create_scoped_if_required_not_scopes(): + unscoped_credentials = CredentialsImpl() + scoped_credentials = credentials.with_scopes_if_required( + unscoped_credentials, ["one", "two"] + ) + + assert scoped_credentials is unscoped_credentials diff --git a/packages/google-auth/tests_async/test_jwt_async.py b/packages/google-auth/tests_async/test_jwt_async.py new file mode 100644 index 000000000000..a35b837b7f4f --- /dev/null +++ b/packages/google-auth/tests_async/test_jwt_async.py @@ -0,0 +1,356 @@ +# Copyright 2020 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import datetime +import json + +import mock +import pytest + +from google.auth import _jwt_async as jwt_async +from google.auth import crypt +from google.auth import exceptions +from tests import test_jwt + + +@pytest.fixture +def signer(): + return crypt.RSASigner.from_string(test_jwt.PRIVATE_KEY_BYTES, "1") + + +class TestCredentials(object): + SERVICE_ACCOUNT_EMAIL = "service-account@example.com" + SUBJECT = "subject" + AUDIENCE = "audience" + ADDITIONAL_CLAIMS = {"meta": "data"} + credentials = None + + @pytest.fixture(autouse=True) + def credentials_fixture(self, signer): + self.credentials = jwt_async.Credentials( + signer, + self.SERVICE_ACCOUNT_EMAIL, + self.SERVICE_ACCOUNT_EMAIL, + self.AUDIENCE, + ) + + def test_from_service_account_info(self): + with open(test_jwt.SERVICE_ACCOUNT_JSON_FILE, "r") as fh: + info = json.load(fh) + + credentials = jwt_async.Credentials.from_service_account_info( + info, audience=self.AUDIENCE + ) + + assert credentials._signer.key_id == info["private_key_id"] + assert credentials._issuer == info["client_email"] + assert credentials._subject == info["client_email"] + assert credentials._audience == self.AUDIENCE + + def test_from_service_account_info_args(self): + info = test_jwt.SERVICE_ACCOUNT_INFO.copy() + + credentials = jwt_async.Credentials.from_service_account_info( + info, + subject=self.SUBJECT, + audience=self.AUDIENCE, + additional_claims=self.ADDITIONAL_CLAIMS, + ) + + assert credentials._signer.key_id == info["private_key_id"] + assert credentials._issuer == info["client_email"] + assert credentials._subject == self.SUBJECT + assert credentials._audience == self.AUDIENCE + assert credentials._additional_claims == self.ADDITIONAL_CLAIMS + + def test_from_service_account_file(self): + info = test_jwt.SERVICE_ACCOUNT_INFO.copy() + + credentials = jwt_async.Credentials.from_service_account_file( + test_jwt.SERVICE_ACCOUNT_JSON_FILE, audience=self.AUDIENCE + ) + + assert credentials._signer.key_id == info["private_key_id"] + assert credentials._issuer == info["client_email"] + assert credentials._subject == info["client_email"] + assert credentials._audience == self.AUDIENCE + + def test_from_service_account_file_args(self): + info = test_jwt.SERVICE_ACCOUNT_INFO.copy() + + credentials = jwt_async.Credentials.from_service_account_file( + test_jwt.SERVICE_ACCOUNT_JSON_FILE, + subject=self.SUBJECT, + audience=self.AUDIENCE, + additional_claims=self.ADDITIONAL_CLAIMS, + ) + + assert credentials._signer.key_id == info["private_key_id"] + assert credentials._issuer == info["client_email"] + assert credentials._subject == self.SUBJECT + assert credentials._audience == self.AUDIENCE + assert credentials._additional_claims == self.ADDITIONAL_CLAIMS + + def test_from_signing_credentials(self): + jwt_from_signing = self.credentials.from_signing_credentials( + self.credentials, audience=mock.sentinel.new_audience + ) + jwt_from_info = jwt_async.Credentials.from_service_account_info( + test_jwt.SERVICE_ACCOUNT_INFO, audience=mock.sentinel.new_audience + ) + + assert isinstance(jwt_from_signing, jwt_async.Credentials) + assert jwt_from_signing._signer.key_id == jwt_from_info._signer.key_id + assert jwt_from_signing._issuer == jwt_from_info._issuer + assert jwt_from_signing._subject == jwt_from_info._subject + assert jwt_from_signing._audience == jwt_from_info._audience + + def test_default_state(self): + assert not self.credentials.valid + # Expiration hasn't been set yet + assert not self.credentials.expired + + def test_with_claims(self): + new_audience = "new_audience" + new_credentials = self.credentials.with_claims(audience=new_audience) + + assert new_credentials._signer == self.credentials._signer + assert new_credentials._issuer == self.credentials._issuer + assert new_credentials._subject == self.credentials._subject + assert new_credentials._audience == new_audience + assert new_credentials._additional_claims == self.credentials._additional_claims + assert new_credentials._quota_project_id == self.credentials._quota_project_id + + def test_with_quota_project(self): + quota_project_id = "project-foo" + + new_credentials = self.credentials.with_quota_project(quota_project_id) + assert new_credentials._signer == self.credentials._signer + assert new_credentials._issuer == self.credentials._issuer + assert new_credentials._subject == self.credentials._subject + assert new_credentials._audience == self.credentials._audience + assert new_credentials._additional_claims == self.credentials._additional_claims + assert new_credentials._quota_project_id == quota_project_id + + def test_sign_bytes(self): + to_sign = b"123" + signature = self.credentials.sign_bytes(to_sign) + assert crypt.verify_signature(to_sign, signature, test_jwt.PUBLIC_CERT_BYTES) + + def test_signer(self): + assert isinstance(self.credentials.signer, crypt.RSASigner) + + def test_signer_email(self): + assert ( + self.credentials.signer_email + == test_jwt.SERVICE_ACCOUNT_INFO["client_email"] + ) + + def _verify_token(self, token): + payload = jwt_async.decode(token, test_jwt.PUBLIC_CERT_BYTES) + assert payload["iss"] == self.SERVICE_ACCOUNT_EMAIL + return payload + + def test_refresh(self): + self.credentials.refresh(None) + assert self.credentials.valid + assert not self.credentials.expired + + def test_expired(self): + assert not self.credentials.expired + + self.credentials.refresh(None) + assert not self.credentials.expired + + with mock.patch("google.auth._helpers.utcnow") as now: + one_day = datetime.timedelta(days=1) + now.return_value = self.credentials.expiry + one_day + assert self.credentials.expired + + @pytest.mark.asyncio + async def test_before_request(self): + headers = {} + + self.credentials.refresh(None) + await self.credentials.before_request( + None, "GET", "http://example.com?a=1#3", headers + ) + + header_value = headers["authorization"] + _, token = header_value.split(" ") + + # Since the audience is set, it should use the existing token. + assert token.encode("utf-8") == self.credentials.token + + payload = self._verify_token(token) + assert payload["aud"] == self.AUDIENCE + + @pytest.mark.asyncio + async def test_before_request_refreshes(self): + assert not self.credentials.valid + await self.credentials.before_request( + None, "GET", "http://example.com?a=1#3", {} + ) + assert self.credentials.valid + + +class TestOnDemandCredentials(object): + SERVICE_ACCOUNT_EMAIL = "service-account@example.com" + SUBJECT = "subject" + ADDITIONAL_CLAIMS = {"meta": "data"} + credentials = None + + @pytest.fixture(autouse=True) + def credentials_fixture(self, signer): + self.credentials = jwt_async.OnDemandCredentials( + signer, + self.SERVICE_ACCOUNT_EMAIL, + self.SERVICE_ACCOUNT_EMAIL, + max_cache_size=2, + ) + + def test_from_service_account_info(self): + with open(test_jwt.SERVICE_ACCOUNT_JSON_FILE, "r") as fh: + info = json.load(fh) + + credentials = jwt_async.OnDemandCredentials.from_service_account_info(info) + + assert credentials._signer.key_id == info["private_key_id"] + assert credentials._issuer == info["client_email"] + assert credentials._subject == info["client_email"] + + def test_from_service_account_info_args(self): + info = test_jwt.SERVICE_ACCOUNT_INFO.copy() + + credentials = jwt_async.OnDemandCredentials.from_service_account_info( + info, subject=self.SUBJECT, additional_claims=self.ADDITIONAL_CLAIMS + ) + + assert credentials._signer.key_id == info["private_key_id"] + assert credentials._issuer == info["client_email"] + assert credentials._subject == self.SUBJECT + assert credentials._additional_claims == self.ADDITIONAL_CLAIMS + + def test_from_service_account_file(self): + info = test_jwt.SERVICE_ACCOUNT_INFO.copy() + + credentials = jwt_async.OnDemandCredentials.from_service_account_file( + test_jwt.SERVICE_ACCOUNT_JSON_FILE + ) + + assert credentials._signer.key_id == info["private_key_id"] + assert credentials._issuer == info["client_email"] + assert credentials._subject == info["client_email"] + + def test_from_service_account_file_args(self): + info = test_jwt.SERVICE_ACCOUNT_INFO.copy() + + credentials = jwt_async.OnDemandCredentials.from_service_account_file( + test_jwt.SERVICE_ACCOUNT_JSON_FILE, + subject=self.SUBJECT, + additional_claims=self.ADDITIONAL_CLAIMS, + ) + + assert credentials._signer.key_id == info["private_key_id"] + assert credentials._issuer == info["client_email"] + assert credentials._subject == self.SUBJECT + assert credentials._additional_claims == self.ADDITIONAL_CLAIMS + + def test_from_signing_credentials(self): + jwt_from_signing = self.credentials.from_signing_credentials(self.credentials) + jwt_from_info = jwt_async.OnDemandCredentials.from_service_account_info( + test_jwt.SERVICE_ACCOUNT_INFO + ) + + assert isinstance(jwt_from_signing, jwt_async.OnDemandCredentials) + assert jwt_from_signing._signer.key_id == jwt_from_info._signer.key_id + assert jwt_from_signing._issuer == jwt_from_info._issuer + assert jwt_from_signing._subject == jwt_from_info._subject + + def test_default_state(self): + # Credentials are *always* valid. + assert self.credentials.valid + # Credentials *never* expire. + assert not self.credentials.expired + + def test_with_claims(self): + new_claims = {"meep": "moop"} + new_credentials = self.credentials.with_claims(additional_claims=new_claims) + + assert new_credentials._signer == self.credentials._signer + assert new_credentials._issuer == self.credentials._issuer + assert new_credentials._subject == self.credentials._subject + assert new_credentials._additional_claims == new_claims + + def test_with_quota_project(self): + quota_project_id = "project-foo" + new_credentials = self.credentials.with_quota_project(quota_project_id) + + assert new_credentials._signer == self.credentials._signer + assert new_credentials._issuer == self.credentials._issuer + assert new_credentials._subject == self.credentials._subject + assert new_credentials._additional_claims == self.credentials._additional_claims + assert new_credentials._quota_project_id == quota_project_id + + def test_sign_bytes(self): + to_sign = b"123" + signature = self.credentials.sign_bytes(to_sign) + assert crypt.verify_signature(to_sign, signature, test_jwt.PUBLIC_CERT_BYTES) + + def test_signer(self): + assert isinstance(self.credentials.signer, crypt.RSASigner) + + def test_signer_email(self): + assert ( + self.credentials.signer_email + == test_jwt.SERVICE_ACCOUNT_INFO["client_email"] + ) + + def _verify_token(self, token): + payload = jwt_async.decode(token, test_jwt.PUBLIC_CERT_BYTES) + assert payload["iss"] == self.SERVICE_ACCOUNT_EMAIL + return payload + + def test_refresh(self): + with pytest.raises(exceptions.RefreshError): + self.credentials.refresh(None) + + def test_before_request(self): + headers = {} + + self.credentials.before_request( + None, "GET", "http://example.com?a=1#3", headers + ) + + _, token = headers["authorization"].split(" ") + payload = self._verify_token(token) + + assert payload["aud"] == "http://example.com" + + # Making another request should re-use the same token. + self.credentials.before_request(None, "GET", "http://example.com?b=2", headers) + + _, new_token = headers["authorization"].split(" ") + + assert new_token == token + + def test_expired_token(self): + self.credentials._cache["audience"] = ( + mock.sentinel.token, + datetime.datetime.min, + ) + + token = self.credentials._get_jwt_for_audience("audience") + + assert token != mock.sentinel.token diff --git a/packages/google-auth/tests_async/transport/__init__.py b/packages/google-auth/tests_async/transport/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/google-auth/tests_async/transport/async_compliance.py b/packages/google-auth/tests_async/transport/async_compliance.py new file mode 100644 index 000000000000..9c4b173c2341 --- /dev/null +++ b/packages/google-auth/tests_async/transport/async_compliance.py @@ -0,0 +1,133 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import time + +import flask +import pytest +from pytest_localserver.http import WSGIServer +from six.moves import http_client + +from google.auth import exceptions +from tests.transport import compliance + + +class RequestResponseTests(object): + @pytest.fixture(scope="module") + def server(self): + """Provides a test HTTP server. + + The test server is automatically created before + a test and destroyed at the end. The server is serving a test + application that can be used to verify requests. + """ + app = flask.Flask(__name__) + app.debug = True + + # pylint: disable=unused-variable + # (pylint thinks the flask routes are unusued.) + @app.route("/basic") + def index(): + header_value = flask.request.headers.get("x-test-header", "value") + headers = {"X-Test-Header": header_value} + return "Basic Content", http_client.OK, headers + + @app.route("/server_error") + def server_error(): + return "Error", http_client.INTERNAL_SERVER_ERROR + + @app.route("/wait") + def wait(): + time.sleep(3) + return "Waited" + + # pylint: enable=unused-variable + + server = WSGIServer(application=app.wsgi_app) + server.start() + yield server + server.stop() + + @pytest.mark.asyncio + async def test_request_basic(self, server): + request = self.make_request() + response = await request(url=server.url + "/basic", method="GET") + assert response.status == http_client.OK + assert response.headers["x-test-header"] == "value" + + # Use 13 as this is the length of the data written into the stream. + + data = await response.data.read(13) + assert data == b"Basic Content" + + @pytest.mark.asyncio + async def test_request_basic_with_http(self, server): + request = self.make_with_parameter_request() + response = await request(url=server.url + "/basic", method="GET") + assert response.status == http_client.OK + assert response.headers["x-test-header"] == "value" + + # Use 13 as this is the length of the data written into the stream. + + data = await response.data.read(13) + assert data == b"Basic Content" + + @pytest.mark.asyncio + async def test_request_with_timeout_success(self, server): + request = self.make_request() + response = await request(url=server.url + "/basic", method="GET", timeout=2) + + assert response.status == http_client.OK + assert response.headers["x-test-header"] == "value" + + data = await response.data.read(13) + assert data == b"Basic Content" + + @pytest.mark.asyncio + async def test_request_with_timeout_failure(self, server): + request = self.make_request() + + with pytest.raises(exceptions.TransportError): + await request(url=server.url + "/wait", method="GET", timeout=1) + + @pytest.mark.asyncio + async def test_request_headers(self, server): + request = self.make_request() + response = await request( + url=server.url + "/basic", + method="GET", + headers={"x-test-header": "hello world"}, + ) + + assert response.status == http_client.OK + assert response.headers["x-test-header"] == "hello world" + + data = await response.data.read(13) + assert data == b"Basic Content" + + @pytest.mark.asyncio + async def test_request_error(self, server): + request = self.make_request() + + response = await request(url=server.url + "/server_error", method="GET") + assert response.status == http_client.INTERNAL_SERVER_ERROR + data = await response.data.read(5) + assert data == b"Error" + + @pytest.mark.asyncio + async def test_connection_error(self): + request = self.make_request() + + with pytest.raises(exceptions.TransportError): + await request(url="http://{}".format(compliance.NXDOMAIN), method="GET") diff --git a/packages/google-auth/tests_async/transport/test_aiohttp_requests.py b/packages/google-auth/tests_async/transport/test_aiohttp_requests.py new file mode 100644 index 000000000000..10c31db8ff65 --- /dev/null +++ b/packages/google-auth/tests_async/transport/test_aiohttp_requests.py @@ -0,0 +1,245 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import aiohttp +from aioresponses import aioresponses, core +import mock +import pytest +from tests_async.transport import async_compliance + +import google.auth._credentials_async +from google.auth.transport import _aiohttp_requests as aiohttp_requests +import google.auth.transport._mtls_helper + + +class TestCombinedResponse: + @pytest.mark.asyncio + async def test__is_compressed(self): + response = core.CallbackResult(headers={"Content-Encoding": "gzip"}) + combined_response = aiohttp_requests._CombinedResponse(response) + compressed = combined_response._is_compressed() + assert compressed + + def test__is_compressed_not(self): + response = core.CallbackResult(headers={"Content-Encoding": "not"}) + combined_response = aiohttp_requests._CombinedResponse(response) + compressed = combined_response._is_compressed() + assert not compressed + + @pytest.mark.asyncio + async def test_raw_content(self): + + mock_response = mock.AsyncMock() + mock_response.content.read.return_value = mock.sentinel.read + combined_response = aiohttp_requests._CombinedResponse(response=mock_response) + raw_content = await combined_response.raw_content() + assert raw_content == mock.sentinel.read + + # Second call to validate the preconfigured path. + combined_response._raw_content = mock.sentinel.stored_raw + raw_content = await combined_response.raw_content() + assert raw_content == mock.sentinel.stored_raw + + @pytest.mark.asyncio + async def test_content(self): + mock_response = mock.AsyncMock() + mock_response.content.read.return_value = mock.sentinel.read + combined_response = aiohttp_requests._CombinedResponse(response=mock_response) + content = await combined_response.content() + assert content == mock.sentinel.read + + @mock.patch( + "google.auth.transport._aiohttp_requests.urllib3.response.MultiDecoder.decompress", + return_value="decompressed", + autospec=True, + ) + @pytest.mark.asyncio + async def test_content_compressed(self, urllib3_mock): + rm = core.RequestMatch( + "url", headers={"Content-Encoding": "gzip"}, payload="compressed" + ) + response = await rm.build_response(core.URL("url")) + + combined_response = aiohttp_requests._CombinedResponse(response=response) + content = await combined_response.content() + + urllib3_mock.assert_called_once() + assert content == "decompressed" + + +class TestResponse: + def test_ctor(self): + response = aiohttp_requests._Response(mock.sentinel.response) + assert response._response == mock.sentinel.response + + @pytest.mark.asyncio + async def test_headers_prop(self): + rm = core.RequestMatch("url", headers={"Content-Encoding": "header prop"}) + mock_response = await rm.build_response(core.URL("url")) + + response = aiohttp_requests._Response(mock_response) + assert response.headers["Content-Encoding"] == "header prop" + + @pytest.mark.asyncio + async def test_status_prop(self): + rm = core.RequestMatch("url", status=123) + mock_response = await rm.build_response(core.URL("url")) + response = aiohttp_requests._Response(mock_response) + assert response.status == 123 + + @pytest.mark.asyncio + async def test_data_prop(self): + mock_response = mock.AsyncMock() + mock_response.content.read.return_value = mock.sentinel.read + response = aiohttp_requests._Response(mock_response) + data = await response.data.read() + assert data == mock.sentinel.read + + +class TestRequestResponse(async_compliance.RequestResponseTests): + def make_request(self): + return aiohttp_requests.Request() + + def make_with_parameter_request(self): + http = mock.create_autospec(aiohttp.ClientSession, instance=True) + return aiohttp_requests.Request(http) + + def test_timeout(self): + http = mock.create_autospec(aiohttp.ClientSession, instance=True) + request = aiohttp_requests.Request(http) + request(url="http://example.com", method="GET", timeout=5) + + +class CredentialsStub(google.auth._credentials_async.Credentials): + def __init__(self, token="token"): + super(CredentialsStub, self).__init__() + self.token = token + + def apply(self, headers, token=None): + headers["authorization"] = self.token + + def refresh(self, request): + self.token += "1" + + +class TestAuthorizedSession(object): + TEST_URL = "http://example.com/" + method = "GET" + + def test_constructor(self): + authed_session = aiohttp_requests.AuthorizedSession(mock.sentinel.credentials) + assert authed_session.credentials == mock.sentinel.credentials + + def test_constructor_with_auth_request(self): + http = mock.create_autospec(aiohttp.ClientSession) + auth_request = aiohttp_requests.Request(http) + + authed_session = aiohttp_requests.AuthorizedSession( + mock.sentinel.credentials, auth_request=auth_request + ) + + assert authed_session._auth_request == auth_request + + @pytest.mark.asyncio + async def test_request(self): + with aioresponses() as mocked: + credentials = mock.Mock(wraps=CredentialsStub()) + + mocked.get(self.TEST_URL, status=200, body="test") + session = aiohttp_requests.AuthorizedSession(credentials) + resp = await session.request( + "GET", + "http://example.com/", + headers={"Keep-Alive": "timeout=5, max=1000", "fake": b"bytes"}, + ) + + assert resp.status == 200 + assert "test" == await resp.text() + + await session.close() + + @pytest.mark.asyncio + async def test_ctx(self): + with aioresponses() as mocked: + credentials = mock.Mock(wraps=CredentialsStub()) + mocked.get("http://test.example.com", payload=dict(foo="bar")) + session = aiohttp_requests.AuthorizedSession(credentials) + resp = await session.request("GET", "http://test.example.com") + data = await resp.json() + + assert dict(foo="bar") == data + + await session.close() + + @pytest.mark.asyncio + async def test_http_headers(self): + with aioresponses() as mocked: + credentials = mock.Mock(wraps=CredentialsStub()) + mocked.post( + "http://example.com", + payload=dict(), + headers=dict(connection="keep-alive"), + ) + + session = aiohttp_requests.AuthorizedSession(credentials) + resp = await session.request("POST", "http://example.com") + + assert resp.headers["Connection"] == "keep-alive" + + await session.close() + + @pytest.mark.asyncio + async def test_regexp_example(self): + with aioresponses() as mocked: + credentials = mock.Mock(wraps=CredentialsStub()) + mocked.get("http://example.com", status=500) + mocked.get("http://example.com", status=200) + + session1 = aiohttp_requests.AuthorizedSession(credentials) + + resp1 = await session1.request("GET", "http://example.com") + session2 = aiohttp_requests.AuthorizedSession(credentials) + resp2 = await session2.request("GET", "http://example.com") + + assert resp1.status == 500 + assert resp2.status == 200 + + await session1.close() + await session2.close() + + @pytest.mark.asyncio + async def test_request_no_refresh(self): + credentials = mock.Mock(wraps=CredentialsStub()) + with aioresponses() as mocked: + mocked.get("http://example.com", status=200) + authed_session = aiohttp_requests.AuthorizedSession(credentials) + response = await authed_session.request("GET", "http://example.com") + assert response.status == 200 + assert credentials.before_request.called + assert not credentials.refresh.called + + await authed_session.close() + + @pytest.mark.asyncio + async def test_request_refresh(self): + credentials = mock.Mock(wraps=CredentialsStub()) + with aioresponses() as mocked: + mocked.get("http://example.com", status=401) + mocked.get("http://example.com", status=200) + authed_session = aiohttp_requests.AuthorizedSession(credentials) + response = await authed_session.request("GET", "http://example.com") + assert credentials.refresh.called + assert response.status == 200 + + await authed_session.close() From b18d06b4e415bb0140547c6f6733696bd217c6ab Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 28 Sep 2020 15:41:41 -0700 Subject: [PATCH 349/966] chore: release 1.22.0 (#615) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index d57047990bfb..94dd9259407a 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +## [1.22.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.21.3...v1.22.0) (2020-09-28) + + +### Features + +* add asyncio based auth flow ([#612](https://www.github.com/googleapis/google-auth-library-python/issues/612)) ([7e15258](https://www.github.com/googleapis/google-auth-library-python/commit/7e1525822d51bd9ce7dffca42d71313e6e776fcd)), closes [#572](https://www.github.com/googleapis/google-auth-library-python/issues/572) + ### [1.21.3](https://www.github.com/googleapis/google-auth-library-python/compare/v1.21.2...v1.21.3) (2020-09-22) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index dd58f30f2d46..bbc892312705 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -34,7 +34,7 @@ with io.open("README.rst", "r") as fh: long_description = fh.read() -version = "1.21.3" +version = "1.22.0" setup( name="google-auth", From e29e7f0f735cd37cf77cb984e64055ff8219d163 Mon Sep 17 00:00:00 2001 From: Christopher Wilcox Date: Mon, 5 Oct 2020 10:08:02 -0700 Subject: [PATCH 350/966] fix: move aiohttp to extra as it is currently internal surface (#619) Fix #618. Removes aiohttp from required dependencies to lessen dependency tree for google-auth. This will need to be looked at again as more folks use aiohttp and once the surfaces goes to public visibility. --- packages/google-auth/setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index bbc892312705..ee2c8466b73c 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -27,9 +27,9 @@ 'rsa>=3.1.4,<5; python_version >= "3.5"', "setuptools>=40.3.0", "six>=1.9.0", - 'aiohttp >= 3.6.2, < 4.0.0dev; python_version>="3.6"', ) +extras = {"aiohttp": "aiohttp >= 3.6.2, < 4.0.0dev; python_version>='3.6'"} with io.open("README.rst", "r") as fh: long_description = fh.read() @@ -47,6 +47,7 @@ packages=find_packages(exclude=("tests*", "system_tests*")), namespace_packages=("google",), install_requires=DEPENDENCIES, + extras_require=extras, python_requires=">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*", license="Apache 2.0", keywords="google auth oauth client", From 75eb474ea2967ee9ff5ff93e33c8c61d3553a06d Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2020 10:53:00 -0700 Subject: [PATCH 351/966] chore: release 1.22.1 (#620) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 94dd9259407a..d05e4e7d5b08 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +### [1.22.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.22.0...v1.22.1) (2020-10-05) + + +### Bug Fixes + +* move aiohttp to extra as it is currently internal surface ([#619](https://www.github.com/googleapis/google-auth-library-python/issues/619)) ([a924011](https://www.github.com/googleapis/google-auth-library-python/commit/a9240111e7af29338624d98ee10aed31462f4d19)), closes [#618](https://www.github.com/googleapis/google-auth-library-python/issues/618) + ## [1.22.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.21.3...v1.22.0) (2020-09-28) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index ee2c8466b73c..522b9810306f 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -34,7 +34,7 @@ with io.open("README.rst", "r") as fh: long_description = fh.read() -version = "1.22.0" +version = "1.22.1" setup( name="google-auth", From e4e5a8adf5c1984d80255ea21995c8e8064a9133 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Fri, 9 Oct 2020 00:58:16 +0300 Subject: [PATCH 352/966] fix: remove checks for ancient versions of Cryptography (#596) Refs https://github.com/googleapis/google-auth-library-python/issues/595#issuecomment-683903062 I see no point in checking whether someone is running a version of https://github.com/pyca/cryptography/ from 2014 that doesn't even compile against modern versions of OpenSSL anymore. --- .../google/auth/crypt/_cryptography_rsa.py | 13 ------------- packages/google-auth/google/auth/crypt/es256.py | 12 ------------ 2 files changed, 25 deletions(-) diff --git a/packages/google-auth/google/auth/crypt/_cryptography_rsa.py b/packages/google-auth/google/auth/crypt/_cryptography_rsa.py index e94bc681ecbd..916c9d80a8ed 100644 --- a/packages/google-auth/google/auth/crypt/_cryptography_rsa.py +++ b/packages/google-auth/google/auth/crypt/_cryptography_rsa.py @@ -25,23 +25,10 @@ from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import padding import cryptography.x509 -import pkg_resources from google.auth import _helpers from google.auth.crypt import base -_IMPORT_ERROR_MSG = ( - "cryptography>=1.4.0 is required to use cryptography-based RSA " "implementation." -) - -try: # pragma: NO COVER - release = pkg_resources.get_distribution("cryptography").parsed_version - if release < pkg_resources.parse_version("1.4.0"): - raise ImportError(_IMPORT_ERROR_MSG) -except pkg_resources.DistributionNotFound: # pragma: NO COVER - raise ImportError(_IMPORT_ERROR_MSG) - - _CERTIFICATE_MARKER = b"-----BEGIN CERTIFICATE-----" _BACKEND = backends.default_backend() _PADDING = padding.PKCS1v15() diff --git a/packages/google-auth/google/auth/crypt/es256.py b/packages/google-auth/google/auth/crypt/es256.py index 6955efcc588f..c6d617606728 100644 --- a/packages/google-auth/google/auth/crypt/es256.py +++ b/packages/google-auth/google/auth/crypt/es256.py @@ -25,22 +25,10 @@ from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature from cryptography.hazmat.primitives.asymmetric.utils import encode_dss_signature import cryptography.x509 -import pkg_resources from google.auth import _helpers from google.auth.crypt import base -_IMPORT_ERROR_MSG = ( - "cryptography>=1.4.0 is required to use cryptography-based ECDSA " "algorithms" -) - -try: # pragma: NO COVER - release = pkg_resources.get_distribution("cryptography").parsed_version - if release < pkg_resources.parse_version("1.4.0"): - raise ImportError(_IMPORT_ERROR_MSG) -except pkg_resources.DistributionNotFound: # pragma: NO COVER - raise ImportError(_IMPORT_ERROR_MSG) - _CERTIFICATE_MARKER = b"-----BEGIN CERTIFICATE-----" _BACKEND = backends.default_backend() From 2737a3586fb9213ad031c34012affd189a284542 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Thu, 22 Oct 2020 16:05:20 -0400 Subject: [PATCH 353/966] tests: fix unit tests on python 3.6 / 3.7 (#630) --- packages/google-auth/noxfile.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index d497f53057b1..b92f4939deb8 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -30,7 +30,7 @@ "grpcio", ] -ASYNC_DEPENDENCIES = ["pytest-asyncio", "aioresponses"] +ASYNC_DEPENDENCIES = ["pytest-asyncio", "aioresponses", "asynctest"] BLACK_VERSION = "black==19.3b0" BLACK_PATHS = [ @@ -144,6 +144,7 @@ def docs(session): @nox.session(python="pypy") def pypy(session): session.install(*TEST_DEPENDENCIES) + session.install(*ASYNC_DEPENDENCIES) session.install(".") session.run( "pytest", From 3442568f158ea101d01d5d7d86ba139220f0490b Mon Sep 17 00:00:00 2001 From: David Buxton Date: Fri, 23 Oct 2020 03:06:04 +0100 Subject: [PATCH 354/966] Change metadata service helper to work with any query parameters (#588) Part of #579 This helper is used with '?recursive=true' in one place, and can now be used by IDTokenCredentials for requests with query parameters to the metadata identity end-point. This change will allow making requests to the token end-point with '?scopes=..' query parameters. --- .../google/auth/compute_engine/_metadata.py | 17 +++++--- .../google/auth/compute_engine/credentials.py | 9 ++-- .../tests/compute_engine/test__metadata.py | 43 +++++++++++++++++++ 3 files changed, 56 insertions(+), 13 deletions(-) diff --git a/packages/google-auth/google/auth/compute_engine/_metadata.py b/packages/google-auth/google/auth/compute_engine/_metadata.py index fe821418e092..94e4ffbf084a 100644 --- a/packages/google-auth/google/auth/compute_engine/_metadata.py +++ b/packages/google-auth/google/auth/compute_engine/_metadata.py @@ -108,7 +108,9 @@ def ping(request, timeout=_METADATA_DEFAULT_TIMEOUT, retry_count=3): return False -def get(request, path, root=_METADATA_ROOT, recursive=False, retry_count=5): +def get( + request, path, root=_METADATA_ROOT, params=None, recursive=False, retry_count=5 +): """Fetch a resource from the metadata server. Args: @@ -117,6 +119,8 @@ def get(request, path, root=_METADATA_ROOT, recursive=False, retry_count=5): path (str): The resource to retrieve. For example, ``'instance/service-accounts/default'``. root (str): The full path to the metadata server root. + params (Optional[Mapping[str, str]]): A mapping of query parameter + keys to values. recursive (bool): Whether to do a recursive query of metadata. See https://cloud.google.com/compute/docs/metadata#aggcontents for more details. @@ -133,7 +137,7 @@ def get(request, path, root=_METADATA_ROOT, recursive=False, retry_count=5): retrieving metadata. """ base_url = urlparse.urljoin(root, path) - query_params = {} + query_params = {} if params is None else params if recursive: query_params["recursive"] = "true" @@ -224,11 +228,10 @@ def get_service_account_info(request, service_account="default"): google.auth.exceptions.TransportError: if an error occurred while retrieving metadata. """ - return get( - request, - "instance/service-accounts/{0}/".format(service_account), - recursive=True, - ) + path = "instance/service-accounts/{0}/".format(service_account) + # See https://cloud.google.com/compute/docs/metadata#aggcontents + # for more on the use of 'recursive'. + return get(request, path, params={"recursive": "true"}) def get_service_account_token(request, service_account="default"): diff --git a/packages/google-auth/google/auth/compute_engine/credentials.py b/packages/google-auth/google/auth/compute_engine/credentials.py index b7fca18326f2..8a41ffcc08ca 100644 --- a/packages/google-auth/google/auth/compute_engine/credentials.py +++ b/packages/google-auth/google/auth/compute_engine/credentials.py @@ -323,12 +323,9 @@ def _call_metadata_identity_endpoint(self, request): ValueError: If extracting expiry from the obtained ID token fails. """ try: - id_token = _metadata.get( - request, - "instance/service-accounts/default/identity?audience={}&format=full".format( - self._target_audience - ), - ) + path = "instance/service-accounts/default/identity" + params = {"audience": self._target_audience, "format": "full"} + id_token = _metadata.get(request, path, params=params) except exceptions.TransportError as caught_exc: new_exc = exceptions.RefreshError(caught_exc) six.raise_from(new_exc, caught_exc) diff --git a/packages/google-auth/tests/compute_engine/test__metadata.py b/packages/google-auth/tests/compute_engine/test__metadata.py index d9b039a32751..d05337263b5e 100644 --- a/packages/google-auth/tests/compute_engine/test__metadata.py +++ b/packages/google-auth/tests/compute_engine/test__metadata.py @@ -155,6 +155,49 @@ def test_get_success_text(): assert result == data +def test_get_success_params(): + data = "foobar" + request = make_request(data, headers={"content-type": "text/plain"}) + params = {"recursive": "true"} + + result = _metadata.get(request, PATH, params=params) + + request.assert_called_once_with( + method="GET", + url=_metadata._METADATA_ROOT + PATH + "?recursive=true", + headers=_metadata._METADATA_HEADERS, + ) + assert result == data + + +def test_get_success_recursive_and_params(): + data = "foobar" + request = make_request(data, headers={"content-type": "text/plain"}) + params = {"recursive": "false"} + result = _metadata.get(request, PATH, recursive=True, params=params) + + request.assert_called_once_with( + method="GET", + url=_metadata._METADATA_ROOT + PATH + "?recursive=true", + headers=_metadata._METADATA_HEADERS, + ) + assert result == data + + +def test_get_success_recursive(): + data = "foobar" + request = make_request(data, headers={"content-type": "text/plain"}) + + result = _metadata.get(request, PATH, recursive=True) + + request.assert_called_once_with( + method="GET", + url=_metadata._METADATA_ROOT + PATH + "?recursive=true", + headers=_metadata._METADATA_HEADERS, + ) + assert result == data + + def test_get_success_custom_root_new_variable(): request = make_request("{}", headers={"content-type": "application/json"}) From 50f7df5db3eab4b072703245fce38f5dbdf369d9 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Mon, 26 Oct 2020 20:52:11 -0400 Subject: [PATCH 355/966] fix: pin 'aoihttp < 3.7.0dev' (#634) Working around breaking change in 3.7.0. See: https://github.com/pnuckowski/aioresponses/issues/173 --- packages/google-auth/noxfile.py | 7 ++++++- packages/google-auth/setup.py | 2 +- packages/google-auth/system_tests/noxfile.py | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index b92f4939deb8..388814491b74 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -30,7 +30,12 @@ "grpcio", ] -ASYNC_DEPENDENCIES = ["pytest-asyncio", "aioresponses", "asynctest"] +ASYNC_DEPENDENCIES = [ + "pytest-asyncio", + "aiohttp < 3.7.0dev", + "aioresponses", + "asynctest", +] BLACK_VERSION = "black==19.3b0" BLACK_PATHS = [ diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 522b9810306f..16c277950a9b 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -29,7 +29,7 @@ "six>=1.9.0", ) -extras = {"aiohttp": "aiohttp >= 3.6.2, < 4.0.0dev; python_version>='3.6'"} +extras = {"aiohttp": "aiohttp >= 3.6.2, < 3.7.0dev; python_version>='3.6'"} with io.open("README.rst", "r") as fh: long_description = fh.read() diff --git a/packages/google-auth/system_tests/noxfile.py b/packages/google-auth/system_tests/noxfile.py index a039228d929e..0f852ea27bf5 100644 --- a/packages/google-auth/system_tests/noxfile.py +++ b/packages/google-auth/system_tests/noxfile.py @@ -168,7 +168,7 @@ def configure_cloud_sdk(session, application_default_credentials, project=False) # Test sesssions -TEST_DEPENDENCIES_ASYNC = ["aiohttp", "pytest-asyncio", "nest-asyncio"] +TEST_DEPENDENCIES_ASYNC = ["aiohttp < 3.7.0dev", "pytest-asyncio", "nest-asyncio"] TEST_DEPENDENCIES_SYNC = ["pytest", "requests"] PYTHON_VERSIONS_ASYNC = ["3.7"] PYTHON_VERSIONS_SYNC = ["2.7", "3.7"] From 87365ef13489ea39ed6b995f996ec63828f3081e Mon Sep 17 00:00:00 2001 From: Yoshi Automation Bot Date: Wed, 28 Oct 2020 12:47:31 -0700 Subject: [PATCH 356/966] chore: add infrastructure to support `docs-presubmit` build (via synth) (#578) * feat(python-library): changes to docs job * feat(python-library): changes to docs job * migrate to Trampoline V2 * add docs-presubmit job * create docfx yaml files and upload them to another bucket * remove redundant envvars * add a failing test first * fix TemplateSyntaxError: Missing end of comment tag * serving_path is not needed any more * use `raw` to make jinja happy Source-Author: Takashi Matsuo Source-Date: Thu Jul 30 12:44:02 2020 -0700 Source-Repo: googleapis/synthtool Source-Sha: 5dfda5621df45b71b6e88544ebbb53b1a8c90214 Source-Link: https://github.com/googleapis/synthtool/commit/5dfda5621df45b71b6e88544ebbb53b1a8c90214 * fix(python-library): add missing changes Source-Author: Takashi Matsuo Source-Date: Thu Jul 30 18:26:35 2020 -0700 Source-Repo: googleapis/synthtool Source-Sha: 39b527a39f5cd56d4882b3874fc08eed4756cebe Source-Link: https://github.com/googleapis/synthtool/commit/39b527a39f5cd56d4882b3874fc08eed4756cebe Co-authored-by: Tres Seaver --- .../.kokoro/docker/docs/Dockerfile | 98 ++++ .../.kokoro/docker/docs/fetch_gpg_keys.sh | 45 ++ packages/google-auth/.kokoro/docs/common.cfg | 21 +- .../.kokoro/docs/docs-presubmit.cfg | 17 + packages/google-auth/.kokoro/publish-docs.sh | 39 +- packages/google-auth/.kokoro/trampoline_v2.sh | 487 ++++++++++++++++++ packages/google-auth/synth.metadata | 4 +- 7 files changed, 692 insertions(+), 19 deletions(-) create mode 100644 packages/google-auth/.kokoro/docker/docs/Dockerfile create mode 100755 packages/google-auth/.kokoro/docker/docs/fetch_gpg_keys.sh create mode 100644 packages/google-auth/.kokoro/docs/docs-presubmit.cfg create mode 100755 packages/google-auth/.kokoro/trampoline_v2.sh diff --git a/packages/google-auth/.kokoro/docker/docs/Dockerfile b/packages/google-auth/.kokoro/docker/docs/Dockerfile new file mode 100644 index 000000000000..412b0b56a921 --- /dev/null +++ b/packages/google-auth/.kokoro/docker/docs/Dockerfile @@ -0,0 +1,98 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ubuntu:20.04 + +ENV DEBIAN_FRONTEND noninteractive + +# Ensure local Python is preferred over distribution Python. +ENV PATH /usr/local/bin:$PATH + +# Install dependencies. +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + apt-transport-https \ + build-essential \ + ca-certificates \ + curl \ + dirmngr \ + git \ + gpg-agent \ + graphviz \ + libbz2-dev \ + libdb5.3-dev \ + libexpat1-dev \ + libffi-dev \ + liblzma-dev \ + libreadline-dev \ + libsnappy-dev \ + libssl-dev \ + libsqlite3-dev \ + portaudio19-dev \ + redis-server \ + software-properties-common \ + ssh \ + sudo \ + tcl \ + tcl-dev \ + tk \ + tk-dev \ + uuid-dev \ + wget \ + zlib1g-dev \ + && add-apt-repository universe \ + && apt-get update \ + && apt-get -y install jq \ + && apt-get clean autoclean \ + && apt-get autoremove -y \ + && rm -rf /var/lib/apt/lists/* \ + && rm -f /var/cache/apt/archives/*.deb + + +COPY fetch_gpg_keys.sh /tmp +# Install the desired versions of Python. +RUN set -ex \ + && export GNUPGHOME="$(mktemp -d)" \ + && echo "disable-ipv6" >> "${GNUPGHOME}/dirmngr.conf" \ + && /tmp/fetch_gpg_keys.sh \ + && for PYTHON_VERSION in 3.7.8 3.8.5; do \ + wget --no-check-certificate -O python-${PYTHON_VERSION}.tar.xz "https://www.python.org/ftp/python/${PYTHON_VERSION%%[a-z]*}/Python-$PYTHON_VERSION.tar.xz" \ + && wget --no-check-certificate -O python-${PYTHON_VERSION}.tar.xz.asc "https://www.python.org/ftp/python/${PYTHON_VERSION%%[a-z]*}/Python-$PYTHON_VERSION.tar.xz.asc" \ + && gpg --batch --verify python-${PYTHON_VERSION}.tar.xz.asc python-${PYTHON_VERSION}.tar.xz \ + && rm -r python-${PYTHON_VERSION}.tar.xz.asc \ + && mkdir -p /usr/src/python-${PYTHON_VERSION} \ + && tar -xJC /usr/src/python-${PYTHON_VERSION} --strip-components=1 -f python-${PYTHON_VERSION}.tar.xz \ + && rm python-${PYTHON_VERSION}.tar.xz \ + && cd /usr/src/python-${PYTHON_VERSION} \ + && ./configure \ + --enable-shared \ + # This works only on Python 2.7 and throws a warning on every other + # version, but seems otherwise harmless. + --enable-unicode=ucs4 \ + --with-system-ffi \ + --without-ensurepip \ + && make -j$(nproc) \ + && make install \ + && ldconfig \ + ; done \ + && rm -rf "${GNUPGHOME}" \ + && rm -rf /usr/src/python* \ + && rm -rf ~/.cache/ + +RUN wget -O /tmp/get-pip.py 'https://bootstrap.pypa.io/get-pip.py' \ + && python3.7 /tmp/get-pip.py \ + && python3.8 /tmp/get-pip.py \ + && rm /tmp/get-pip.py + +CMD ["python3.7"] diff --git a/packages/google-auth/.kokoro/docker/docs/fetch_gpg_keys.sh b/packages/google-auth/.kokoro/docker/docs/fetch_gpg_keys.sh new file mode 100755 index 000000000000..d653dd868e4b --- /dev/null +++ b/packages/google-auth/.kokoro/docker/docs/fetch_gpg_keys.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# A script to fetch gpg keys with retry. +# Avoid jinja parsing the file. +# + +function retry { + if [[ "${#}" -le 1 ]]; then + echo "Usage: ${0} retry_count commands.." + exit 1 + fi + local retries=${1} + local command="${@:2}" + until [[ "${retries}" -le 0 ]]; do + $command && return 0 + if [[ $? -ne 0 ]]; then + echo "command failed, retrying" + ((retries--)) + fi + done + return 1 +} + +# 3.6.9, 3.7.5 (Ned Deily) +retry 3 gpg --keyserver ha.pool.sks-keyservers.net --recv-keys \ + 0D96DF4D4110E5C43FBFB17F2D347EA6AA65421D + +# 3.8.0 (Łukasz Langa) +retry 3 gpg --keyserver ha.pool.sks-keyservers.net --recv-keys \ + E3FF2839C048B25C084DEBE9B26995E310250568 + +# diff --git a/packages/google-auth/.kokoro/docs/common.cfg b/packages/google-auth/.kokoro/docs/common.cfg index e49c23215db8..d6b496716e34 100644 --- a/packages/google-auth/.kokoro/docs/common.cfg +++ b/packages/google-auth/.kokoro/docs/common.cfg @@ -11,12 +11,12 @@ action { gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" # Use the trampoline script to run in docker. -build_file: "google-auth-library-python/.kokoro/trampoline.sh" +build_file: "google-auth-library-python/.kokoro/trampoline_v2.sh" # Configure the docker image for kokoro-trampoline. env_vars: { key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-kokoro-resources/python-multi" + value: "gcr.io/cloud-devrel-kokoro-resources/python-lib-docs" } env_vars: { key: "TRAMPOLINE_BUILD_FILE" @@ -28,6 +28,23 @@ env_vars: { value: "docs-staging" } +env_vars: { + key: "V2_STAGING_BUCKET" + value: "docs-staging-v2-staging" +} + +# It will upload the docker image after successful builds. +env_vars: { + key: "TRAMPOLINE_IMAGE_UPLOAD" + value: "true" +} + +# It will always build the docker image. +env_vars: { + key: "TRAMPOLINE_DOCKERFILE" + value: ".kokoro/docker/docs/Dockerfile" +} + # Fetch the token needed for reporting release status to GitHub before_action { fetch_keystore { diff --git a/packages/google-auth/.kokoro/docs/docs-presubmit.cfg b/packages/google-auth/.kokoro/docs/docs-presubmit.cfg new file mode 100644 index 000000000000..1118107829b7 --- /dev/null +++ b/packages/google-auth/.kokoro/docs/docs-presubmit.cfg @@ -0,0 +1,17 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "STAGING_BUCKET" + value: "gcloud-python-test" +} + +env_vars: { + key: "V2_STAGING_BUCKET" + value: "gcloud-python-test" +} + +# We only upload the image in the main `docs` build. +env_vars: { + key: "TRAMPOLINE_IMAGE_UPLOAD" + value: "false" +} diff --git a/packages/google-auth/.kokoro/publish-docs.sh b/packages/google-auth/.kokoro/publish-docs.sh index 0e5d97867f53..8acb14e802b0 100755 --- a/packages/google-auth/.kokoro/publish-docs.sh +++ b/packages/google-auth/.kokoro/publish-docs.sh @@ -18,26 +18,16 @@ set -eo pipefail # Disable buffering, so that the logs stream through. export PYTHONUNBUFFERED=1 -cd github/google-auth-library-python - -# Remove old nox -python3.6 -m pip uninstall --yes --quiet nox-automation +export PATH="${HOME}/.local/bin:${PATH}" # Install nox -python3.6 -m pip install --upgrade --quiet nox -python3.6 -m nox --version +python3 -m pip install --user --upgrade --quiet nox +python3 -m nox --version # build docs nox -s docs -python3 -m pip install gcp-docuploader - -# install a json parser -sudo apt-get update -sudo apt-get -y install software-properties-common -sudo add-apt-repository universe -sudo apt-get update -sudo apt-get -y install jq +python3 -m pip install --user gcp-docuploader # create metadata python3 -m docuploader create-metadata \ @@ -52,4 +42,23 @@ python3 -m docuploader create-metadata \ cat docs.metadata # upload docs -python3 -m docuploader upload docs/_build/html --metadata-file docs.metadata --staging-bucket docs-staging +python3 -m docuploader upload docs/_build/html --metadata-file docs.metadata --staging-bucket "${STAGING_BUCKET}" + + +# docfx yaml files +nox -s docfx + +# create metadata. +python3 -m docuploader create-metadata \ + --name=$(jq --raw-output '.name // empty' .repo-metadata.json) \ + --version=$(python3 setup.py --version) \ + --language=$(jq --raw-output '.language // empty' .repo-metadata.json) \ + --distribution-name=$(python3 setup.py --name) \ + --product-page=$(jq --raw-output '.product_documentation // empty' .repo-metadata.json) \ + --github-repository=$(jq --raw-output '.repo // empty' .repo-metadata.json) \ + --issue-tracker=$(jq --raw-output '.issue_tracker // empty' .repo-metadata.json) + +cat docs.metadata + +# upload docs +python3 -m docuploader upload docs/_build/html/docfx_yaml --metadata-file docs.metadata --destination-prefix docfx --staging-bucket "${V2_STAGING_BUCKET}" diff --git a/packages/google-auth/.kokoro/trampoline_v2.sh b/packages/google-auth/.kokoro/trampoline_v2.sh new file mode 100755 index 000000000000..719bcd5ba84d --- /dev/null +++ b/packages/google-auth/.kokoro/trampoline_v2.sh @@ -0,0 +1,487 @@ +#!/usr/bin/env bash +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# trampoline_v2.sh +# +# This script does 3 things. +# +# 1. Prepare the Docker image for the test +# 2. Run the Docker with appropriate flags to run the test +# 3. Upload the newly built Docker image +# +# in a way that is somewhat compatible with trampoline_v1. +# +# To run this script, first download few files from gcs to /dev/shm. +# (/dev/shm is passed into the container as KOKORO_GFILE_DIR). +# +# gsutil cp gs://cloud-devrel-kokoro-resources/python-docs-samples/secrets_viewer_service_account.json /dev/shm +# gsutil cp gs://cloud-devrel-kokoro-resources/python-docs-samples/automl_secrets.txt /dev/shm +# +# Then run the script. +# .kokoro/trampoline_v2.sh +# +# These environment variables are required: +# TRAMPOLINE_IMAGE: The docker image to use. +# TRAMPOLINE_DOCKERFILE: The location of the Dockerfile. +# +# You can optionally change these environment variables: +# TRAMPOLINE_IMAGE_UPLOAD: +# (true|false): Whether to upload the Docker image after the +# successful builds. +# TRAMPOLINE_BUILD_FILE: The script to run in the docker container. +# TRAMPOLINE_WORKSPACE: The workspace path in the docker container. +# Defaults to /workspace. +# Potentially there are some repo specific envvars in .trampolinerc in +# the project root. + + +set -euo pipefail + +TRAMPOLINE_VERSION="2.0.5" + +if command -v tput >/dev/null && [[ -n "${TERM:-}" ]]; then + readonly IO_COLOR_RED="$(tput setaf 1)" + readonly IO_COLOR_GREEN="$(tput setaf 2)" + readonly IO_COLOR_YELLOW="$(tput setaf 3)" + readonly IO_COLOR_RESET="$(tput sgr0)" +else + readonly IO_COLOR_RED="" + readonly IO_COLOR_GREEN="" + readonly IO_COLOR_YELLOW="" + readonly IO_COLOR_RESET="" +fi + +function function_exists { + [ $(LC_ALL=C type -t $1)"" == "function" ] +} + +# Logs a message using the given color. The first argument must be one +# of the IO_COLOR_* variables defined above, such as +# "${IO_COLOR_YELLOW}". The remaining arguments will be logged in the +# given color. The log message will also have an RFC-3339 timestamp +# prepended (in UTC). You can disable the color output by setting +# TERM=vt100. +function log_impl() { + local color="$1" + shift + local timestamp="$(date -u "+%Y-%m-%dT%H:%M:%SZ")" + echo "================================================================" + echo "${color}${timestamp}:" "$@" "${IO_COLOR_RESET}" + echo "================================================================" +} + +# Logs the given message with normal coloring and a timestamp. +function log() { + log_impl "${IO_COLOR_RESET}" "$@" +} + +# Logs the given message in green with a timestamp. +function log_green() { + log_impl "${IO_COLOR_GREEN}" "$@" +} + +# Logs the given message in yellow with a timestamp. +function log_yellow() { + log_impl "${IO_COLOR_YELLOW}" "$@" +} + +# Logs the given message in red with a timestamp. +function log_red() { + log_impl "${IO_COLOR_RED}" "$@" +} + +readonly tmpdir=$(mktemp -d -t ci-XXXXXXXX) +readonly tmphome="${tmpdir}/h" +mkdir -p "${tmphome}" + +function cleanup() { + rm -rf "${tmpdir}" +} +trap cleanup EXIT + +RUNNING_IN_CI="${RUNNING_IN_CI:-false}" + +# The workspace in the container, defaults to /workspace. +TRAMPOLINE_WORKSPACE="${TRAMPOLINE_WORKSPACE:-/workspace}" + +pass_down_envvars=( + # TRAMPOLINE_V2 variables. + # Tells scripts whether they are running as part of CI or not. + "RUNNING_IN_CI" + # Indicates which CI system we're in. + "TRAMPOLINE_CI" + # Indicates the version of the script. + "TRAMPOLINE_VERSION" +) + +log_yellow "Building with Trampoline ${TRAMPOLINE_VERSION}" + +# Detect which CI systems we're in. If we're in any of the CI systems +# we support, `RUNNING_IN_CI` will be true and `TRAMPOLINE_CI` will be +# the name of the CI system. Both envvars will be passing down to the +# container for telling which CI system we're in. +if [[ -n "${KOKORO_BUILD_ID:-}" ]]; then + # descriptive env var for indicating it's on CI. + RUNNING_IN_CI="true" + TRAMPOLINE_CI="kokoro" + if [[ "${TRAMPOLINE_USE_LEGACY_SERVICE_ACCOUNT:-}" == "true" ]]; then + if [[ ! -f "${KOKORO_GFILE_DIR}/kokoro-trampoline.service-account.json" ]]; then + log_red "${KOKORO_GFILE_DIR}/kokoro-trampoline.service-account.json does not exist. Did you forget to mount cloud-devrel-kokoro-resources/trampoline? Aborting." + exit 1 + fi + # This service account will be activated later. + TRAMPOLINE_SERVICE_ACCOUNT="${KOKORO_GFILE_DIR}/kokoro-trampoline.service-account.json" + else + if [[ "${TRAMPOLINE_VERBOSE:-}" == "true" ]]; then + gcloud auth list + fi + log_yellow "Configuring Container Registry access" + gcloud auth configure-docker --quiet + fi + pass_down_envvars+=( + # KOKORO dynamic variables. + "KOKORO_BUILD_NUMBER" + "KOKORO_BUILD_ID" + "KOKORO_JOB_NAME" + "KOKORO_GIT_COMMIT" + "KOKORO_GITHUB_COMMIT" + "KOKORO_GITHUB_PULL_REQUEST_NUMBER" + "KOKORO_GITHUB_PULL_REQUEST_COMMIT" + # For Build Cop Bot + "KOKORO_GITHUB_COMMIT_URL" + "KOKORO_GITHUB_PULL_REQUEST_URL" + ) +elif [[ "${TRAVIS:-}" == "true" ]]; then + RUNNING_IN_CI="true" + TRAMPOLINE_CI="travis" + pass_down_envvars+=( + "TRAVIS_BRANCH" + "TRAVIS_BUILD_ID" + "TRAVIS_BUILD_NUMBER" + "TRAVIS_BUILD_WEB_URL" + "TRAVIS_COMMIT" + "TRAVIS_COMMIT_MESSAGE" + "TRAVIS_COMMIT_RANGE" + "TRAVIS_JOB_NAME" + "TRAVIS_JOB_NUMBER" + "TRAVIS_JOB_WEB_URL" + "TRAVIS_PULL_REQUEST" + "TRAVIS_PULL_REQUEST_BRANCH" + "TRAVIS_PULL_REQUEST_SHA" + "TRAVIS_PULL_REQUEST_SLUG" + "TRAVIS_REPO_SLUG" + "TRAVIS_SECURE_ENV_VARS" + "TRAVIS_TAG" + ) +elif [[ -n "${GITHUB_RUN_ID:-}" ]]; then + RUNNING_IN_CI="true" + TRAMPOLINE_CI="github-workflow" + pass_down_envvars+=( + "GITHUB_WORKFLOW" + "GITHUB_RUN_ID" + "GITHUB_RUN_NUMBER" + "GITHUB_ACTION" + "GITHUB_ACTIONS" + "GITHUB_ACTOR" + "GITHUB_REPOSITORY" + "GITHUB_EVENT_NAME" + "GITHUB_EVENT_PATH" + "GITHUB_SHA" + "GITHUB_REF" + "GITHUB_HEAD_REF" + "GITHUB_BASE_REF" + ) +elif [[ "${CIRCLECI:-}" == "true" ]]; then + RUNNING_IN_CI="true" + TRAMPOLINE_CI="circleci" + pass_down_envvars+=( + "CIRCLE_BRANCH" + "CIRCLE_BUILD_NUM" + "CIRCLE_BUILD_URL" + "CIRCLE_COMPARE_URL" + "CIRCLE_JOB" + "CIRCLE_NODE_INDEX" + "CIRCLE_NODE_TOTAL" + "CIRCLE_PREVIOUS_BUILD_NUM" + "CIRCLE_PROJECT_REPONAME" + "CIRCLE_PROJECT_USERNAME" + "CIRCLE_REPOSITORY_URL" + "CIRCLE_SHA1" + "CIRCLE_STAGE" + "CIRCLE_USERNAME" + "CIRCLE_WORKFLOW_ID" + "CIRCLE_WORKFLOW_JOB_ID" + "CIRCLE_WORKFLOW_UPSTREAM_JOB_IDS" + "CIRCLE_WORKFLOW_WORKSPACE_ID" + ) +fi + +# Configure the service account for pulling the docker image. +function repo_root() { + local dir="$1" + while [[ ! -d "${dir}/.git" ]]; do + dir="$(dirname "$dir")" + done + echo "${dir}" +} + +# Detect the project root. In CI builds, we assume the script is in +# the git tree and traverse from there, otherwise, traverse from `pwd` +# to find `.git` directory. +if [[ "${RUNNING_IN_CI:-}" == "true" ]]; then + PROGRAM_PATH="$(realpath "$0")" + PROGRAM_DIR="$(dirname "${PROGRAM_PATH}")" + PROJECT_ROOT="$(repo_root "${PROGRAM_DIR}")" +else + PROJECT_ROOT="$(repo_root $(pwd))" +fi + +log_yellow "Changing to the project root: ${PROJECT_ROOT}." +cd "${PROJECT_ROOT}" + +# To support relative path for `TRAMPOLINE_SERVICE_ACCOUNT`, we need +# to use this environment variable in `PROJECT_ROOT`. +if [[ -n "${TRAMPOLINE_SERVICE_ACCOUNT:-}" ]]; then + + mkdir -p "${tmpdir}/gcloud" + gcloud_config_dir="${tmpdir}/gcloud" + + log_yellow "Using isolated gcloud config: ${gcloud_config_dir}." + export CLOUDSDK_CONFIG="${gcloud_config_dir}" + + log_yellow "Using ${TRAMPOLINE_SERVICE_ACCOUNT} for authentication." + gcloud auth activate-service-account \ + --key-file "${TRAMPOLINE_SERVICE_ACCOUNT}" + log_yellow "Configuring Container Registry access" + gcloud auth configure-docker --quiet +fi + +required_envvars=( + # The basic trampoline configurations. + "TRAMPOLINE_IMAGE" + "TRAMPOLINE_BUILD_FILE" +) + +if [[ -f "${PROJECT_ROOT}/.trampolinerc" ]]; then + source "${PROJECT_ROOT}/.trampolinerc" +fi + +log_yellow "Checking environment variables." +for e in "${required_envvars[@]}" +do + if [[ -z "${!e:-}" ]]; then + log "Missing ${e} env var. Aborting." + exit 1 + fi +done + +# We want to support legacy style TRAMPOLINE_BUILD_FILE used with V1 +# script: e.g. "github/repo-name/.kokoro/run_tests.sh" +TRAMPOLINE_BUILD_FILE="${TRAMPOLINE_BUILD_FILE#github/*/}" +log_yellow "Using TRAMPOLINE_BUILD_FILE: ${TRAMPOLINE_BUILD_FILE}" + +# ignore error on docker operations and test execution +set +e + +log_yellow "Preparing Docker image." +# We only download the docker image in CI builds. +if [[ "${RUNNING_IN_CI:-}" == "true" ]]; then + # Download the docker image specified by `TRAMPOLINE_IMAGE` + + # We may want to add --max-concurrent-downloads flag. + + log_yellow "Start pulling the Docker image: ${TRAMPOLINE_IMAGE}." + if docker pull "${TRAMPOLINE_IMAGE}"; then + log_green "Finished pulling the Docker image: ${TRAMPOLINE_IMAGE}." + has_image="true" + else + log_red "Failed pulling the Docker image: ${TRAMPOLINE_IMAGE}." + has_image="false" + fi +else + # For local run, check if we have the image. + if docker images "${TRAMPOLINE_IMAGE}:latest" | grep "${TRAMPOLINE_IMAGE}"; then + has_image="true" + else + has_image="false" + fi +fi + + +# The default user for a Docker container has uid 0 (root). To avoid +# creating root-owned files in the build directory we tell docker to +# use the current user ID. +user_uid="$(id -u)" +user_gid="$(id -g)" +user_name="$(id -un)" + +# To allow docker in docker, we add the user to the docker group in +# the host os. +docker_gid=$(cut -d: -f3 < <(getent group docker)) + +update_cache="false" +if [[ "${TRAMPOLINE_DOCKERFILE:-none}" != "none" ]]; then + # Build the Docker image from the source. + context_dir=$(dirname "${TRAMPOLINE_DOCKERFILE}") + docker_build_flags=( + "-f" "${TRAMPOLINE_DOCKERFILE}" + "-t" "${TRAMPOLINE_IMAGE}" + "--build-arg" "UID=${user_uid}" + "--build-arg" "USERNAME=${user_name}" + ) + if [[ "${has_image}" == "true" ]]; then + docker_build_flags+=("--cache-from" "${TRAMPOLINE_IMAGE}") + fi + + log_yellow "Start building the docker image." + if [[ "${TRAMPOLINE_VERBOSE:-false}" == "true" ]]; then + echo "docker build" "${docker_build_flags[@]}" "${context_dir}" + fi + + # ON CI systems, we want to suppress docker build logs, only + # output the logs when it fails. + if [[ "${RUNNING_IN_CI:-}" == "true" ]]; then + if docker build "${docker_build_flags[@]}" "${context_dir}" \ + > "${tmpdir}/docker_build.log" 2>&1; then + if [[ "${TRAMPOLINE_VERBOSE:-}" == "true" ]]; then + cat "${tmpdir}/docker_build.log" + fi + + log_green "Finished building the docker image." + update_cache="true" + else + log_red "Failed to build the Docker image, aborting." + log_yellow "Dumping the build logs:" + cat "${tmpdir}/docker_build.log" + exit 1 + fi + else + if docker build "${docker_build_flags[@]}" "${context_dir}"; then + log_green "Finished building the docker image." + update_cache="true" + else + log_red "Failed to build the Docker image, aborting." + exit 1 + fi + fi +else + if [[ "${has_image}" != "true" ]]; then + log_red "We do not have ${TRAMPOLINE_IMAGE} locally, aborting." + exit 1 + fi +fi + +# We use an array for the flags so they are easier to document. +docker_flags=( + # Remove the container after it exists. + "--rm" + + # Use the host network. + "--network=host" + + # Run in priviledged mode. We are not using docker for sandboxing or + # isolation, just for packaging our dev tools. + "--privileged" + + # Run the docker script with the user id. Because the docker image gets to + # write in ${PWD} you typically want this to be your user id. + # To allow docker in docker, we need to use docker gid on the host. + "--user" "${user_uid}:${docker_gid}" + + # Pass down the USER. + "--env" "USER=${user_name}" + + # Mount the project directory inside the Docker container. + "--volume" "${PROJECT_ROOT}:${TRAMPOLINE_WORKSPACE}" + "--workdir" "${TRAMPOLINE_WORKSPACE}" + "--env" "PROJECT_ROOT=${TRAMPOLINE_WORKSPACE}" + + # Mount the temporary home directory. + "--volume" "${tmphome}:/h" + "--env" "HOME=/h" + + # Allow docker in docker. + "--volume" "/var/run/docker.sock:/var/run/docker.sock" + + # Mount the /tmp so that docker in docker can mount the files + # there correctly. + "--volume" "/tmp:/tmp" + # Pass down the KOKORO_GFILE_DIR and KOKORO_KEYSTORE_DIR + # TODO(tmatsuo): This part is not portable. + "--env" "TRAMPOLINE_SECRET_DIR=/secrets" + "--volume" "${KOKORO_GFILE_DIR:-/dev/shm}:/secrets/gfile" + "--env" "KOKORO_GFILE_DIR=/secrets/gfile" + "--volume" "${KOKORO_KEYSTORE_DIR:-/dev/shm}:/secrets/keystore" + "--env" "KOKORO_KEYSTORE_DIR=/secrets/keystore" +) + +# Add an option for nicer output if the build gets a tty. +if [[ -t 0 ]]; then + docker_flags+=("-it") +fi + +# Passing down env vars +for e in "${pass_down_envvars[@]}" +do + if [[ -n "${!e:-}" ]]; then + docker_flags+=("--env" "${e}=${!e}") + fi +done + +# If arguments are given, all arguments will become the commands run +# in the container, otherwise run TRAMPOLINE_BUILD_FILE. +if [[ $# -ge 1 ]]; then + log_yellow "Running the given commands '" "${@:1}" "' in the container." + readonly commands=("${@:1}") + if [[ "${TRAMPOLINE_VERBOSE:-}" == "true" ]]; then + echo docker run "${docker_flags[@]}" "${TRAMPOLINE_IMAGE}" "${commands[@]}" + fi + docker run "${docker_flags[@]}" "${TRAMPOLINE_IMAGE}" "${commands[@]}" +else + log_yellow "Running the tests in a Docker container." + docker_flags+=("--entrypoint=${TRAMPOLINE_BUILD_FILE}") + if [[ "${TRAMPOLINE_VERBOSE:-}" == "true" ]]; then + echo docker run "${docker_flags[@]}" "${TRAMPOLINE_IMAGE}" + fi + docker run "${docker_flags[@]}" "${TRAMPOLINE_IMAGE}" +fi + + +test_retval=$? + +if [[ ${test_retval} -eq 0 ]]; then + log_green "Build finished with ${test_retval}" +else + log_red "Build finished with ${test_retval}" +fi + +# Only upload it when the test passes. +if [[ "${update_cache}" == "true" ]] && \ + [[ $test_retval == 0 ]] && \ + [[ "${TRAMPOLINE_IMAGE_UPLOAD:-false}" == "true" ]]; then + log_yellow "Uploading the Docker image." + if docker push "${TRAMPOLINE_IMAGE}"; then + log_green "Finished uploading the Docker image." + else + log_red "Failed uploading the Docker image." + fi + # Call trampoline_after_upload_hook if it's defined. + if function_exists trampoline_after_upload_hook; then + trampoline_after_upload_hook + fi + +fi + +exit "${test_retval}" diff --git a/packages/google-auth/synth.metadata b/packages/google-auth/synth.metadata index 901a2cb9b728..2563871fced8 100644 --- a/packages/google-auth/synth.metadata +++ b/packages/google-auth/synth.metadata @@ -4,14 +4,14 @@ "git": { "name": ".", "remote": "https://github.com/googleapis/google-auth-library-python.git", - "sha": "218a159f646c81021c890b92f9cff003aed949a8" + "sha": "20f82e22b7e8c6c7fdd29e08eaf7b4cf2abdcf37" } }, { "git": { "name": "synthtool", "remote": "https://github.com/googleapis/synthtool.git", - "sha": "ffe10407ee2f261c799fb0d01bf32a8abc67ed1e" + "sha": "39b527a39f5cd56d4882b3874fc08eed4756cebe" } } ] From 0af14fbbcae61d4925dd1136b0a4361f5f931f2c Mon Sep 17 00:00:00 2001 From: matthewhughes934 <34972397+matthewhughes934@users.noreply.github.com> Date: Wed, 28 Oct 2020 20:00:02 +0000 Subject: [PATCH 357/966] Update example in oauth2.id_token docs (#624) Since c05b8b5 oauth2.id_token.verify_oauth2_token handles the issuer check itself, so remove this redundant check from the docs. --- packages/google-auth/google/oauth2/id_token.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/google-auth/google/oauth2/id_token.py b/packages/google-auth/google/oauth2/id_token.py index bf6bf2c70181..d70782b51413 100644 --- a/packages/google-auth/google/oauth2/id_token.py +++ b/packages/google-auth/google/oauth2/id_token.py @@ -33,9 +33,6 @@ id_info = id_token.verify_oauth2_token( token, request, 'my-client-id.example.com') - if id_info['iss'] != 'https://accounts.google.com': - raise ValueError('Wrong issuer.') - userid = id_info['sub'] By default, this will re-fetch certificates for each verification. Because From f81ce4ad53debb32fcdfe24a5890212278b04cc3 Mon Sep 17 00:00:00 2001 From: Yoshi Automation Bot Date: Thu, 29 Oct 2020 07:58:21 -0700 Subject: [PATCH 358/966] build: use pypi secret from secret manager (#639) --- packages/google-auth/.kokoro/build.sh | 8 ++- packages/google-auth/.kokoro/docs/common.cfg | 2 +- .../google-auth/.kokoro/populate-secrets.sh | 43 ++++++++++++++++ .../google-auth/.kokoro/release/common.cfg | 50 +++++-------------- .../.kokoro/samples/python3.6/common.cfg | 6 +++ .../.kokoro/samples/python3.7/common.cfg | 6 +++ .../.kokoro/samples/python3.8/common.cfg | 6 +++ packages/google-auth/.kokoro/test-samples.sh | 8 ++- packages/google-auth/.kokoro/trampoline.sh | 15 ++++-- packages/google-auth/synth.metadata | 40 ++++++++++++++- 10 files changed, 137 insertions(+), 47 deletions(-) create mode 100755 packages/google-auth/.kokoro/populate-secrets.sh diff --git a/packages/google-auth/.kokoro/build.sh b/packages/google-auth/.kokoro/build.sh index 3ce87f39d037..3a63e98c6ca9 100755 --- a/packages/google-auth/.kokoro/build.sh +++ b/packages/google-auth/.kokoro/build.sh @@ -36,4 +36,10 @@ python3.6 -m pip uninstall --yes --quiet nox-automation python3.6 -m pip install --upgrade --quiet nox python3.6 -m nox --version -python3.6 -m nox +# If NOX_SESSION is set, it only runs the specified session, +# otherwise run all the sessions. +if [[ -n "${NOX_SESSION:-}" ]]; then + python3.6 -m nox -s "${NOX_SESSION:-}" +else + python3.6 -m nox +fi diff --git a/packages/google-auth/.kokoro/docs/common.cfg b/packages/google-auth/.kokoro/docs/common.cfg index d6b496716e34..24c8c89dd4d8 100644 --- a/packages/google-auth/.kokoro/docs/common.cfg +++ b/packages/google-auth/.kokoro/docs/common.cfg @@ -30,7 +30,7 @@ env_vars: { env_vars: { key: "V2_STAGING_BUCKET" - value: "docs-staging-v2-staging" + value: "docs-staging-v2" } # It will upload the docker image after successful builds. diff --git a/packages/google-auth/.kokoro/populate-secrets.sh b/packages/google-auth/.kokoro/populate-secrets.sh new file mode 100755 index 000000000000..f52514257ef0 --- /dev/null +++ b/packages/google-auth/.kokoro/populate-secrets.sh @@ -0,0 +1,43 @@ +#!/bin/bash +# Copyright 2020 Google LLC. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -eo pipefail + +function now { date +"%Y-%m-%d %H:%M:%S" | tr -d '\n' ;} +function msg { println "$*" >&2 ;} +function println { printf '%s\n' "$(now) $*" ;} + + +# Populates requested secrets set in SECRET_MANAGER_KEYS from service account: +# kokoro-trampoline@cloud-devrel-kokoro-resources.iam.gserviceaccount.com +SECRET_LOCATION="${KOKORO_GFILE_DIR}/secret_manager" +msg "Creating folder on disk for secrets: ${SECRET_LOCATION}" +mkdir -p ${SECRET_LOCATION} +for key in $(echo ${SECRET_MANAGER_KEYS} | sed "s/,/ /g") +do + msg "Retrieving secret ${key}" + docker run --entrypoint=gcloud \ + --volume=${KOKORO_GFILE_DIR}:${KOKORO_GFILE_DIR} \ + gcr.io/google.com/cloudsdktool/cloud-sdk \ + secrets versions access latest \ + --project cloud-devrel-kokoro-resources \ + --secret ${key} > \ + "${SECRET_LOCATION}/${key}" + if [[ $? == 0 ]]; then + msg "Secret written to ${SECRET_LOCATION}/${key}" + else + msg "Error retrieving secret ${key}" + fi +done diff --git a/packages/google-auth/.kokoro/release/common.cfg b/packages/google-auth/.kokoro/release/common.cfg index b2088d009fdf..b56ca902dff7 100644 --- a/packages/google-auth/.kokoro/release/common.cfg +++ b/packages/google-auth/.kokoro/release/common.cfg @@ -23,42 +23,18 @@ env_vars: { value: "github/google-auth-library-python/.kokoro/release.sh" } -# Fetch the token needed for reporting release status to GitHub -before_action { - fetch_keystore { - keystore_resource { - keystore_config_id: 73713 - keyname: "yoshi-automation-github-key" - } - } -} - -# Fetch PyPI password -before_action { - fetch_keystore { - keystore_resource { - keystore_config_id: 73713 - keyname: "google_cloud_pypi_password" - } - } -} - -# Fetch magictoken to use with Magic Github Proxy -before_action { - fetch_keystore { - keystore_resource { - keystore_config_id: 73713 - keyname: "releasetool-magictoken" - } - } +# Fetch PyPI password +before_action { + fetch_keystore { + keystore_resource { + keystore_config_id: 73713 + keyname: "google_cloud_pypi_password" + } + } } -# Fetch api key to use with Magic Github Proxy -before_action { - fetch_keystore { - keystore_resource { - keystore_config_id: 73713 - keyname: "magic-github-proxy-api-key" - } - } -} +# Tokens needed to report release status back to GitHub +env_vars: { + key: "SECRET_MANAGER_KEYS" + value: "releasetool-publish-reporter-app,releasetool-publish-reporter-googleapis-installation,releasetool-publish-reporter-pem" +} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.6/common.cfg b/packages/google-auth/.kokoro/samples/python3.6/common.cfg index 792bc4bbe3ae..4895c2bcf824 100644 --- a/packages/google-auth/.kokoro/samples/python3.6/common.cfg +++ b/packages/google-auth/.kokoro/samples/python3.6/common.cfg @@ -13,6 +13,12 @@ env_vars: { value: "py-3.6" } +# Declare build specific Cloud project. +env_vars: { + key: "BUILD_SPECIFIC_GCLOUD_PROJECT" + value: "python-docs-samples-tests-py36" +} + env_vars: { key: "TRAMPOLINE_BUILD_FILE" value: "github/google-auth-library-python/.kokoro/test-samples.sh" diff --git a/packages/google-auth/.kokoro/samples/python3.7/common.cfg b/packages/google-auth/.kokoro/samples/python3.7/common.cfg index 209f6cef97db..90aaef1b43f9 100644 --- a/packages/google-auth/.kokoro/samples/python3.7/common.cfg +++ b/packages/google-auth/.kokoro/samples/python3.7/common.cfg @@ -13,6 +13,12 @@ env_vars: { value: "py-3.7" } +# Declare build specific Cloud project. +env_vars: { + key: "BUILD_SPECIFIC_GCLOUD_PROJECT" + value: "python-docs-samples-tests-py37" +} + env_vars: { key: "TRAMPOLINE_BUILD_FILE" value: "github/google-auth-library-python/.kokoro/test-samples.sh" diff --git a/packages/google-auth/.kokoro/samples/python3.8/common.cfg b/packages/google-auth/.kokoro/samples/python3.8/common.cfg index b0095dabd152..78fd8c749639 100644 --- a/packages/google-auth/.kokoro/samples/python3.8/common.cfg +++ b/packages/google-auth/.kokoro/samples/python3.8/common.cfg @@ -13,6 +13,12 @@ env_vars: { value: "py-3.8" } +# Declare build specific Cloud project. +env_vars: { + key: "BUILD_SPECIFIC_GCLOUD_PROJECT" + value: "python-docs-samples-tests-py38" +} + env_vars: { key: "TRAMPOLINE_BUILD_FILE" value: "github/google-auth-library-python/.kokoro/test-samples.sh" diff --git a/packages/google-auth/.kokoro/test-samples.sh b/packages/google-auth/.kokoro/test-samples.sh index f4426f67a99d..9a9de2086c49 100755 --- a/packages/google-auth/.kokoro/test-samples.sh +++ b/packages/google-auth/.kokoro/test-samples.sh @@ -28,6 +28,12 @@ if [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"periodic"* ]]; then git checkout $LATEST_RELEASE fi +# Exit early if samples directory doesn't exist +if [ ! -d "./samples" ]; then + echo "No tests run. `./samples` not found" + exit 0 +fi + # Disable buffering, so that the logs stream through. export PYTHONUNBUFFERED=1 @@ -101,4 +107,4 @@ cd "$ROOT" # Workaround for Kokoro permissions issue: delete secrets rm testing/{test-env.sh,client-secrets.json,service-account.json} -exit "$RTN" \ No newline at end of file +exit "$RTN" diff --git a/packages/google-auth/.kokoro/trampoline.sh b/packages/google-auth/.kokoro/trampoline.sh index e8c4251f3ed4..f39236e943a8 100755 --- a/packages/google-auth/.kokoro/trampoline.sh +++ b/packages/google-auth/.kokoro/trampoline.sh @@ -15,9 +15,14 @@ set -eo pipefail -python3 "${KOKORO_GFILE_DIR}/trampoline_v1.py" || ret_code=$? +# Always run the cleanup script, regardless of the success of bouncing into +# the container. +function cleanup() { + chmod +x ${KOKORO_GFILE_DIR}/trampoline_cleanup.sh + ${KOKORO_GFILE_DIR}/trampoline_cleanup.sh + echo "cleanup"; +} +trap cleanup EXIT -chmod +x ${KOKORO_GFILE_DIR}/trampoline_cleanup.sh -${KOKORO_GFILE_DIR}/trampoline_cleanup.sh || true - -exit ${ret_code} +$(dirname $0)/populate-secrets.sh # Secret Manager secrets. +python3 "${KOKORO_GFILE_DIR}/trampoline_v1.py" \ No newline at end of file diff --git a/packages/google-auth/synth.metadata b/packages/google-auth/synth.metadata index 2563871fced8..5e1ef9a55daf 100644 --- a/packages/google-auth/synth.metadata +++ b/packages/google-auth/synth.metadata @@ -4,15 +4,51 @@ "git": { "name": ".", "remote": "https://github.com/googleapis/google-auth-library-python.git", - "sha": "20f82e22b7e8c6c7fdd29e08eaf7b4cf2abdcf37" + "sha": "9c4200dff31986b7ff300126e9aa35d14aa84dba" } }, { "git": { "name": "synthtool", "remote": "https://github.com/googleapis/synthtool.git", - "sha": "39b527a39f5cd56d4882b3874fc08eed4756cebe" + "sha": "da5c6050d13b4950c82666a81d8acd25157664ae" } } + ], + "generatedFiles": [ + ".kokoro/build.sh", + ".kokoro/continuous/common.cfg", + ".kokoro/continuous/continuous.cfg", + ".kokoro/docker/docs/Dockerfile", + ".kokoro/docker/docs/fetch_gpg_keys.sh", + ".kokoro/docs/common.cfg", + ".kokoro/docs/docs-presubmit.cfg", + ".kokoro/docs/docs.cfg", + ".kokoro/populate-secrets.sh", + ".kokoro/presubmit/common.cfg", + ".kokoro/presubmit/presubmit.cfg", + ".kokoro/publish-docs.sh", + ".kokoro/release.sh", + ".kokoro/release/common.cfg", + ".kokoro/release/release.cfg", + ".kokoro/samples/lint/common.cfg", + ".kokoro/samples/lint/continuous.cfg", + ".kokoro/samples/lint/periodic.cfg", + ".kokoro/samples/lint/presubmit.cfg", + ".kokoro/samples/python3.6/common.cfg", + ".kokoro/samples/python3.6/continuous.cfg", + ".kokoro/samples/python3.6/periodic.cfg", + ".kokoro/samples/python3.6/presubmit.cfg", + ".kokoro/samples/python3.7/common.cfg", + ".kokoro/samples/python3.7/continuous.cfg", + ".kokoro/samples/python3.7/periodic.cfg", + ".kokoro/samples/python3.7/presubmit.cfg", + ".kokoro/samples/python3.8/common.cfg", + ".kokoro/samples/python3.8/continuous.cfg", + ".kokoro/samples/python3.8/periodic.cfg", + ".kokoro/samples/python3.8/presubmit.cfg", + ".kokoro/test-samples.sh", + ".kokoro/trampoline.sh", + ".kokoro/trampoline_v2.sh" ] } \ No newline at end of file From 65141709416ae69429f8eeb259354591853382b6 Mon Sep 17 00:00:00 2001 From: David Buxton Date: Thu, 29 Oct 2020 21:26:11 +0000 Subject: [PATCH 359/966] feat: Add custom scopes for access tokens from the metadata service (#633) This works for App Engine, Cloud Run and Flex. On Compute Engine you can request custom scopes, but they are ignored. Co-authored-by: Tres Seaver Co-authored-by: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> --- packages/google-auth/google/auth/_default.py | 10 ++-- .../google-auth/google/auth/_default_async.py | 10 ++-- .../google/auth/compute_engine/_metadata.py | 17 +++++-- .../google/auth/compute_engine/credentials.py | 44 +++++++++++----- packages/google-auth/system_tests/noxfile.py | 2 +- .../tests/compute_engine/test_credentials.py | 51 ++++++++++++++++++- 6 files changed, 104 insertions(+), 30 deletions(-) diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index de81c5b2c6c2..43778931a389 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -274,10 +274,11 @@ def default(scopes=None, request=None, quota_project_id=None): gcloud config set project 3. If the application is running in the `App Engine standard environment`_ - then the credentials and project ID from the `App Identity Service`_ - are used. - 4. If the application is running in `Compute Engine`_ or the - `App Engine flexible environment`_ then the credentials and project ID + (first generation) then the credentials and project ID from the + `App Identity Service`_ are used. + 4. If the application is running in `Compute Engine`_ or `Cloud Run`_ or + the `App Engine flexible environment`_ or the `App Engine standard + environment`_ (second generation) then the credentials and project ID are obtained from the `Metadata Service`_. 5. If no credentials are found, :class:`~google.auth.exceptions.DefaultCredentialsError` will be raised. @@ -293,6 +294,7 @@ def default(scopes=None, request=None, quota_project_id=None): /appengine/flexible .. _Metadata Service: https://cloud.google.com/compute/docs\ /storing-retrieving-metadata + .. _Cloud Run: https://cloud.google.com/run Example:: diff --git a/packages/google-auth/google/auth/_default_async.py b/packages/google-auth/google/auth/_default_async.py index 3347fbfdc37f..1a725afba414 100644 --- a/packages/google-auth/google/auth/_default_async.py +++ b/packages/google-auth/google/auth/_default_async.py @@ -187,10 +187,11 @@ def default_async(scopes=None, request=None, quota_project_id=None): gcloud config set project 3. If the application is running in the `App Engine standard environment`_ - then the credentials and project ID from the `App Identity Service`_ - are used. - 4. If the application is running in `Compute Engine`_ or the - `App Engine flexible environment`_ then the credentials and project ID + (first generation) then the credentials and project ID from the + `App Identity Service`_ are used. + 4. If the application is running in `Compute Engine`_ or `Cloud Run`_ or + the `App Engine flexible environment`_ or the `App Engine standard + environment`_ (second generation) then the credentials and project ID are obtained from the `Metadata Service`_. 5. If no credentials are found, :class:`~google.auth.exceptions.DefaultCredentialsError` will be raised. @@ -206,6 +207,7 @@ def default_async(scopes=None, request=None, quota_project_id=None): /appengine/flexible .. _Metadata Service: https://cloud.google.com/compute/docs\ /storing-retrieving-metadata + .. _Cloud Run: https://cloud.google.com/run Example:: diff --git a/packages/google-auth/google/auth/compute_engine/_metadata.py b/packages/google-auth/google/auth/compute_engine/_metadata.py index 94e4ffbf084a..5687a42f965e 100644 --- a/packages/google-auth/google/auth/compute_engine/_metadata.py +++ b/packages/google-auth/google/auth/compute_engine/_metadata.py @@ -234,7 +234,7 @@ def get_service_account_info(request, service_account="default"): return get(request, path, params={"recursive": "true"}) -def get_service_account_token(request, service_account="default"): +def get_service_account_token(request, service_account="default", scopes=None): """Get the OAuth 2.0 access token for a service account. Args: @@ -243,7 +243,8 @@ def get_service_account_token(request, service_account="default"): service_account (str): The string 'default' or a service account email address. The determines which service account for which to acquire an access token. - + scopes (Optional[Union[str, List[str]]]): Optional string or list of + strings with auth scopes. Returns: Union[str, datetime]: The access token and its expiration. @@ -251,9 +252,15 @@ def get_service_account_token(request, service_account="default"): google.auth.exceptions.TransportError: if an error occurred while retrieving metadata. """ - token_json = get( - request, "instance/service-accounts/{0}/token".format(service_account) - ) + if scopes: + if not isinstance(scopes, str): + scopes = ",".join(scopes) + params = {"scopes": scopes} + else: + params = None + + path = "instance/service-accounts/{0}/token".format(service_account) + token_json = get(request, path, params=params) token_expiry = _helpers.utcnow() + datetime.timedelta( seconds=token_json["expires_in"] ) diff --git a/packages/google-auth/google/auth/compute_engine/credentials.py b/packages/google-auth/google/auth/compute_engine/credentials.py index 8a41ffcc08ca..4ac6c8c2c11b 100644 --- a/packages/google-auth/google/auth/compute_engine/credentials.py +++ b/packages/google-auth/google/auth/compute_engine/credentials.py @@ -32,29 +32,28 @@ from google.oauth2 import _client -class Credentials(credentials.ReadOnlyScoped, credentials.CredentialsWithQuotaProject): +class Credentials(credentials.Scoped, credentials.CredentialsWithQuotaProject): """Compute Engine Credentials. These credentials use the Google Compute Engine metadata server to obtain - OAuth 2.0 access tokens associated with the instance's service account. + OAuth 2.0 access tokens associated with the instance's service account, + and are also used for Cloud Run, Flex and App Engine (except for the Python + 2.7 runtime). For more information about Compute Engine authentication, including how to configure scopes, see the `Compute Engine authentication documentation`_. - .. note:: Compute Engine instances can be created with scopes and therefore - these credentials are considered to be 'scoped'. However, you can - not use :meth:`~google.auth.credentials.ScopedCredentials.with_scopes` - because it is not possible to change the scopes that the instance - has. Also note that - :meth:`~google.auth.credentials.ScopedCredentials.has_scopes` will not - work until the credentials have been refreshed. + .. note:: On Compute Engine the metadata server ignores requested scopes. + On Cloud Run, Flex and App Engine the server honours requested scopes. .. _Compute Engine authentication documentation: https://cloud.google.com/compute/docs/authentication#using """ - def __init__(self, service_account_email="default", quota_project_id=None): + def __init__( + self, service_account_email="default", quota_project_id=None, scopes=None + ): """ Args: service_account_email (str): The service account email to use, or @@ -66,6 +65,7 @@ def __init__(self, service_account_email="default", quota_project_id=None): super(Credentials, self).__init__() self._service_account_email = service_account_email self._quota_project_id = quota_project_id + self._scopes = scopes def _retrieve_info(self, request): """Retrieve information about the service account. @@ -81,7 +81,10 @@ def _retrieve_info(self, request): ) self._service_account_email = info["email"] - self._scopes = info["scopes"] + + # Don't override scopes requested by the user. + if self._scopes is None: + self._scopes = info["scopes"] def refresh(self, request): """Refresh the access token and scopes. @@ -98,7 +101,9 @@ def refresh(self, request): try: self._retrieve_info(request) self.token, self.expiry = _metadata.get_service_account_token( - request, service_account=self._service_account_email + request, + service_account=self._service_account_email, + scopes=self._scopes, ) except exceptions.TransportError as caught_exc: new_exc = exceptions.RefreshError(caught_exc) @@ -115,14 +120,25 @@ def service_account_email(self): @property def requires_scopes(self): - """False: Compute Engine credentials can not be scoped.""" - return False + return not self._scopes @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) def with_quota_project(self, quota_project_id): return self.__class__( service_account_email=self._service_account_email, quota_project_id=quota_project_id, + scopes=self._scopes, + ) + + @_helpers.copy_docstring(credentials.Scoped) + def with_scopes(self, scopes): + # Compute Engine credentials can not be scoped (the metadata service + # ignores the scopes parameter). App Engine, Cloud Run and Flex support + # requesting scopes. + return self.__class__( + scopes=scopes, + service_account_email=self._service_account_email, + quota_project_id=self._quota_project_id, ) diff --git a/packages/google-auth/system_tests/noxfile.py b/packages/google-auth/system_tests/noxfile.py index 0f852ea27bf5..699a1b3afca5 100644 --- a/packages/google-auth/system_tests/noxfile.py +++ b/packages/google-auth/system_tests/noxfile.py @@ -315,7 +315,7 @@ def default_explicit_service_account_async(session): session.env[EXPECT_PROJECT_ENV] = "1" session.install(*(TEST_DEPENDENCIES_SYNC + TEST_DEPENDENCIES_ASYNC)) session.install(LIBRARY_DIR) - session.run("pytest", "system_tests_async/test_default.py", + session.run("pytest", "system_tests_async/test_default.py", "system_tests_async/test_id_token.py") diff --git a/packages/google-auth/tests/compute_engine/test_credentials.py b/packages/google-auth/tests/compute_engine/test_credentials.py index 4ee6536762b0..ebe9aa5ba300 100644 --- a/packages/google-auth/tests/compute_engine/test_credentials.py +++ b/packages/google-auth/tests/compute_engine/test_credentials.py @@ -55,8 +55,8 @@ def test_default_state(self): assert not self.credentials.valid # Expiration hasn't been set yet assert not self.credentials.expired - # Scopes aren't needed - assert not self.credentials.requires_scopes + # Scopes are needed + assert self.credentials.requires_scopes # Service account email hasn't been populated assert self.credentials.service_account_email == "default" # No quota project @@ -96,6 +96,45 @@ def test_refresh_success(self, get, utcnow): # expired) assert self.credentials.valid + @mock.patch( + "google.auth._helpers.utcnow", + return_value=datetime.datetime.min + _helpers.CLOCK_SKEW, + ) + @mock.patch("google.auth.compute_engine._metadata.get", autospec=True) + def test_refresh_success_with_scopes(self, get, utcnow): + get.side_effect = [ + { + # First request is for sevice account info. + "email": "service-account@example.com", + "scopes": ["one", "two"], + }, + { + # Second request is for the token. + "access_token": "token", + "expires_in": 500, + }, + ] + + # Refresh credentials + scopes = ["three", "four"] + self.credentials = self.credentials.with_scopes(scopes) + self.credentials.refresh(None) + + # Check that the credentials have the token and proper expiration + assert self.credentials.token == "token" + assert self.credentials.expiry == (utcnow() + datetime.timedelta(seconds=500)) + + # Check the credential info + assert self.credentials.service_account_email == "service-account@example.com" + assert self.credentials._scopes == scopes + + # Check that the credentials are valid (have a token and are not + # expired) + assert self.credentials.valid + + kwargs = get.call_args[1] + assert kwargs == {"params": {"scopes": "three,four"}} + @mock.patch("google.auth.compute_engine._metadata.get", autospec=True) def test_refresh_error(self, get): get.side_effect = exceptions.TransportError("http error") @@ -138,6 +177,14 @@ def test_with_quota_project(self): assert quota_project_creds._quota_project_id == "project-foo" + def test_with_scopes(self): + assert self.credentials._scopes is None + + scopes = ["one", "two"] + self.credentials = self.credentials.with_scopes(scopes) + + assert self.credentials._scopes == scopes + class TestIDTokenCredentials(object): credentials = None From 311dd6f06050f4153237cc3552cd709380d1c80a Mon Sep 17 00:00:00 2001 From: David Buxton Date: Thu, 29 Oct 2020 21:38:01 +0000 Subject: [PATCH 360/966] fix(deps): Revert "fix: pin 'aoihttp < 3.7.0dev' (#634)" (#632) (#640) This reverts commit 05f95246fab928fe2f445781117eeac8088497fb. The compatibility bug was fixed in the aioresponses package version 0.7.1 - https://pypi.org/project/aioresponses/ Fixes #632 --- packages/google-auth/noxfile.py | 7 +------ packages/google-auth/setup.py | 2 +- packages/google-auth/system_tests/noxfile.py | 2 +- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index 388814491b74..b92f4939deb8 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -30,12 +30,7 @@ "grpcio", ] -ASYNC_DEPENDENCIES = [ - "pytest-asyncio", - "aiohttp < 3.7.0dev", - "aioresponses", - "asynctest", -] +ASYNC_DEPENDENCIES = ["pytest-asyncio", "aioresponses", "asynctest"] BLACK_VERSION = "black==19.3b0" BLACK_PATHS = [ diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 16c277950a9b..522b9810306f 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -29,7 +29,7 @@ "six>=1.9.0", ) -extras = {"aiohttp": "aiohttp >= 3.6.2, < 3.7.0dev; python_version>='3.6'"} +extras = {"aiohttp": "aiohttp >= 3.6.2, < 4.0.0dev; python_version>='3.6'"} with io.open("README.rst", "r") as fh: long_description = fh.read() diff --git a/packages/google-auth/system_tests/noxfile.py b/packages/google-auth/system_tests/noxfile.py index 699a1b3afca5..dcfe8ee81664 100644 --- a/packages/google-auth/system_tests/noxfile.py +++ b/packages/google-auth/system_tests/noxfile.py @@ -168,7 +168,7 @@ def configure_cloud_sdk(session, application_default_credentials, project=False) # Test sesssions -TEST_DEPENDENCIES_ASYNC = ["aiohttp < 3.7.0dev", "pytest-asyncio", "nest-asyncio"] +TEST_DEPENDENCIES_ASYNC = ["aiohttp", "pytest-asyncio", "nest-asyncio"] TEST_DEPENDENCIES_SYNC = ["pytest", "requests"] PYTHON_VERSIONS_ASYNC = ["3.7"] PYTHON_VERSIONS_SYNC = ["2.7", "3.7"] From 305fa93328fce6a97aabc413af10ef9205e5ca69 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 29 Oct 2020 21:48:02 +0000 Subject: [PATCH 361/966] chore: release 1.23.0 (#641) :robot: I have created a release \*beep\* \*boop\* --- ## [1.23.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.22.1...v1.23.0) (2020-10-29) ### Features * Add custom scopes for access tokens from the metadata service ([#633](https://www.github.com/googleapis/google-auth-library-python/issues/633)) ([0323cf3](https://www.github.com/googleapis/google-auth-library-python/commit/0323cf390b16e8483660ac88775e8ea4e7f7702d)) ### Bug Fixes * **deps:** Revert "fix: pin 'aoihttp < 3.7.0dev' ([#634](https://www.github.com/googleapis/google-auth-library-python/issues/634))" ([#632](https://www.github.com/googleapis/google-auth-library-python/issues/632)) ([#640](https://www.github.com/googleapis/google-auth-library-python/issues/640)) ([b790e65](https://www.github.com/googleapis/google-auth-library-python/commit/b790e6535cc37591b23866027a426cde312e07c1)) * pin 'aoihttp < 3.7.0dev' ([#634](https://www.github.com/googleapis/google-auth-library-python/issues/634)) ([05f9524](https://www.github.com/googleapis/google-auth-library-python/commit/05f95246fab928fe2f445781117eeac8088497fb)) * remove checks for ancient versions of Cryptography ([#596](https://www.github.com/googleapis/google-auth-library-python/issues/596)) ([6407258](https://www.github.com/googleapis/google-auth-library-python/commit/6407258956ec42e3b722418cb7f366e5ae9272ec)), closes [/github.com/googleapis/google-auth-library-python/issues/595#issuecomment-683903062](https://www.github.com/googleapis//github.com/googleapis/google-auth-library-python/issues/595/issues/issuecomment-683903062) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). --- packages/google-auth/CHANGELOG.md | 14 ++++++++++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index d05e4e7d5b08..1e5950234f92 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,20 @@ [1]: https://pypi.org/project/google-auth/#history +## [1.23.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.22.1...v1.23.0) (2020-10-29) + + +### Features + +* Add custom scopes for access tokens from the metadata service ([#633](https://www.github.com/googleapis/google-auth-library-python/issues/633)) ([0323cf3](https://www.github.com/googleapis/google-auth-library-python/commit/0323cf390b16e8483660ac88775e8ea4e7f7702d)) + + +### Bug Fixes + +* **deps:** Revert "fix: pin 'aoihttp < 3.7.0dev' ([#634](https://www.github.com/googleapis/google-auth-library-python/issues/634))" ([#632](https://www.github.com/googleapis/google-auth-library-python/issues/632)) ([#640](https://www.github.com/googleapis/google-auth-library-python/issues/640)) ([b790e65](https://www.github.com/googleapis/google-auth-library-python/commit/b790e6535cc37591b23866027a426cde312e07c1)) +* pin 'aoihttp < 3.7.0dev' ([#634](https://www.github.com/googleapis/google-auth-library-python/issues/634)) ([05f9524](https://www.github.com/googleapis/google-auth-library-python/commit/05f95246fab928fe2f445781117eeac8088497fb)) +* remove checks for ancient versions of Cryptography ([#596](https://www.github.com/googleapis/google-auth-library-python/issues/596)) ([6407258](https://www.github.com/googleapis/google-auth-library-python/commit/6407258956ec42e3b722418cb7f366e5ae9272ec)), closes [/github.com/googleapis/google-auth-library-python/issues/595#issuecomment-683903062](https://www.github.com/googleapis//github.com/googleapis/google-auth-library-python/issues/595/issues/issuecomment-683903062) + ### [1.22.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.22.0...v1.22.1) (2020-10-05) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 522b9810306f..d599ecc171f0 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -34,7 +34,7 @@ with io.open("README.rst", "r") as fh: long_description = fh.read() -version = "1.22.1" +version = "1.23.0" setup( name="google-auth", From c83e8aa47acef5adf726bcf2ca2b4e1e7ab798b6 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Mon, 7 Dec 2020 11:00:03 -0700 Subject: [PATCH 362/966] docs: fix typo in import (#651) `service_acccount` -> `service_account`. Closes #650 --- packages/google-auth/google/auth/impersonated_credentials.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index d2c5ded1ce28..d96c05bbfefe 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -148,7 +148,7 @@ class Credentials(credentials.CredentialsWithQuotaProject, credentials.Signing): Initialize a source credential which does not have access to list bucket:: - from google.oauth2 import service_acccount + from google.oauth2 import service_account target_scopes = [ 'https://www.googleapis.com/auth/devstorage.read_only'] From 51b9145b7a21a74e10a725d8b51c10a7a6b2837c Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Tue, 8 Dec 2020 14:31:41 -0700 Subject: [PATCH 363/966] chore: fix comment about clock_skew (#653) Clock skew was changed in #581 --- packages/google-auth/google/auth/credentials.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/credentials.py b/packages/google-auth/google/auth/credentials.py index bc42546b9a06..02082cad9342 100644 --- a/packages/google-auth/google/auth/credentials.py +++ b/packages/google-auth/google/auth/credentials.py @@ -63,7 +63,7 @@ def expired(self): if not self.expiry: return False - # Remove 5 minutes from expiry to err on the side of reporting + # Remove 10 seconds from expiry to err on the side of reporting # expiration early so that we avoid the 401-refresh-retry loop. skewed_expiry = self.expiry - _helpers.CLOCK_SKEW return _helpers.utcnow() >= skewed_expiry From 65b79d4d2f8f888f1e03125dd47d10bad3b47432 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Fri, 11 Dec 2020 13:20:39 -0500 Subject: [PATCH 364/966] feat: add Python 3.9 support, drop Python 3.5 support (#655) Closes #654. --- packages/google-auth/noxfile.py | 4 ++-- packages/google-auth/setup.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index b92f4939deb8..adce2527c13d 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -75,7 +75,7 @@ def blacken(session): session.run("black", *BLACK_PATHS) -@nox.session(python=["3.6", "3.7", "3.8"]) +@nox.session(python=["3.6", "3.7", "3.8", "3.9"]) def unit(session): session.install(*TEST_DEPENDENCIES) session.install(*(ASYNC_DEPENDENCIES)) @@ -90,7 +90,7 @@ def unit(session): ) -@nox.session(python=["2.7", "3.5"]) +@nox.session(python=["2.7"]) def unit_prev_versions(session): session.install(*TEST_DEPENDENCIES) session.install(".") diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index d599ecc171f0..66e74ee4374e 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -23,8 +23,8 @@ "pyasn1-modules>=0.2.1", # rsa==4.5 is the last version to support 2.7 # https://github.com/sybrenstuvel/python-rsa/issues/152#issuecomment-643470233 - 'rsa<4.6; python_version < "3.5"', - 'rsa>=3.1.4,<5; python_version >= "3.5"', + 'rsa<4.6; python_version < "3.6"', + 'rsa>=3.1.4,<5; python_version >= "3.6"', "setuptools>=40.3.0", "six>=1.9.0", ) @@ -48,17 +48,17 @@ namespace_packages=("google",), install_requires=DEPENDENCIES, extras_require=extras, - python_requires=">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*", + python_requires=">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*", license="Apache 2.0", keywords="google auth oauth client", classifiers=[ "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", From d27821676ac77657456684d7beba120575214a13 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Fri, 11 Dec 2020 11:34:04 -0700 Subject: [PATCH 365/966] chore: add constraints file (#649) Add constraints file to test lower bounds --- packages/google-auth/testing/constraints-3.10.txt | 0 packages/google-auth/testing/constraints-3.11.txt | 0 packages/google-auth/testing/constraints-3.6.txt | 14 ++++++++++++++ packages/google-auth/testing/constraints-3.7.txt | 0 packages/google-auth/testing/constraints-3.8.txt | 0 packages/google-auth/testing/constraints-3.9.txt | 0 6 files changed, 14 insertions(+) create mode 100644 packages/google-auth/testing/constraints-3.10.txt create mode 100644 packages/google-auth/testing/constraints-3.11.txt create mode 100644 packages/google-auth/testing/constraints-3.6.txt create mode 100644 packages/google-auth/testing/constraints-3.7.txt create mode 100644 packages/google-auth/testing/constraints-3.8.txt create mode 100644 packages/google-auth/testing/constraints-3.9.txt diff --git a/packages/google-auth/testing/constraints-3.10.txt b/packages/google-auth/testing/constraints-3.10.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/google-auth/testing/constraints-3.11.txt b/packages/google-auth/testing/constraints-3.11.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/google-auth/testing/constraints-3.6.txt b/packages/google-auth/testing/constraints-3.6.txt new file mode 100644 index 000000000000..ff7f099d4eae --- /dev/null +++ b/packages/google-auth/testing/constraints-3.6.txt @@ -0,0 +1,14 @@ +# This constraints file is used to check that lower bounds +# are correct in setup.py +# List *all* library dependencies and extras in this file. +# Pin the version to the lower bound. +# +# e.g., if setup.py has "foo >= 1.14.0, < 2.0.0dev", +# Then this file should have foo==1.14.0 +cachetools==2.0.0 +pyasn1-modules==0.2.1 +setuptools==40.3.0 +six==1.9.0 +rsa==4.6 +rsa==3.1.4 +aiohttp==3.6.2 \ No newline at end of file diff --git a/packages/google-auth/testing/constraints-3.7.txt b/packages/google-auth/testing/constraints-3.7.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/google-auth/testing/constraints-3.8.txt b/packages/google-auth/testing/constraints-3.8.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/google-auth/testing/constraints-3.9.txt b/packages/google-auth/testing/constraints-3.9.txt new file mode 100644 index 000000000000..e69de29bb2d1 From cddfdf91fcb0b67ca456013e4b4ade1f6b244b5a Mon Sep 17 00:00:00 2001 From: Daniel Gorelik Date: Fri, 11 Dec 2020 13:46:06 -0500 Subject: [PATCH 366/966] chore: fix typo (#647) --- .../google-auth/google/auth/compute_engine/credentials.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/google/auth/compute_engine/credentials.py b/packages/google-auth/google/auth/compute_engine/credentials.py index 4ac6c8c2c11b..29063103a3a9 100644 --- a/packages/google-auth/google/auth/compute_engine/credentials.py +++ b/packages/google-auth/google/auth/compute_engine/credentials.py @@ -14,8 +14,8 @@ """Google Compute Engine credentials. -This module provides authentication for application running on Google Compute -Engine using the Compute Engine metadata server. +This module provides authentication for an application running on Google +Compute Engine using the Compute Engine metadata server. """ From b49b868e78b576a7a65e89a8d40d8aa88ec908f1 Mon Sep 17 00:00:00 2001 From: Pietro De Nicolao Date: Fri, 11 Dec 2020 20:00:30 +0100 Subject: [PATCH 367/966] fix: avoid losing the original '_include_email' parameter in impersonated credentials (#626) Co-authored-by: Tres Seaver --- .../google-auth/google/auth/impersonated_credentials.py | 2 ++ .../google-auth/tests/test_impersonated_credentials.py | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index d96c05bbfefe..4d158373a7e8 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -341,6 +341,7 @@ def from_credentials(self, target_credentials, target_audience=None): return self.__class__( target_credentials=self._target_credentials, target_audience=target_audience, + include_email=self._include_email, quota_project_id=self._quota_project_id, ) @@ -348,6 +349,7 @@ def with_target_audience(self, target_audience): return self.__class__( target_credentials=self._target_credentials, target_audience=target_audience, + include_email=self._include_email, quota_project_id=self._quota_project_id, ) diff --git a/packages/google-auth/tests/test_impersonated_credentials.py b/packages/google-auth/tests/test_impersonated_credentials.py index 46850a0d9a17..305f9392667a 100644 --- a/packages/google-auth/tests/test_impersonated_credentials.py +++ b/packages/google-auth/tests/test_impersonated_credentials.py @@ -368,12 +368,13 @@ def test_id_token_from_credential( assert not credentials.expired id_creds = impersonated_credentials.IDTokenCredentials( - credentials, target_audience=target_audience + credentials, target_audience=target_audience, include_email=True ) id_creds = id_creds.from_credentials(target_credentials=credentials) id_creds.refresh(request) assert id_creds.token == ID_TOKEN_DATA + assert id_creds._include_email is True def test_id_token_with_target_audience( self, mock_donor_credentials, mock_authorizedsession_idtoken @@ -396,12 +397,15 @@ def test_id_token_with_target_audience( assert credentials.valid assert not credentials.expired - id_creds = impersonated_credentials.IDTokenCredentials(credentials) + id_creds = impersonated_credentials.IDTokenCredentials( + credentials, include_email=True + ) id_creds = id_creds.with_target_audience(target_audience=target_audience) id_creds.refresh(request) assert id_creds.token == ID_TOKEN_DATA assert id_creds.expiry == datetime.datetime.fromtimestamp(ID_TOKEN_EXPIRY) + assert id_creds._include_email is True def test_id_token_invalid_cred( self, mock_donor_credentials, mock_authorizedsession_idtoken From a4c7d605a3ff57af6971a19f43c8a616c99a5ace Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Fri, 11 Dec 2020 19:12:02 +0000 Subject: [PATCH 368/966] chore: release 1.24.0 (#656) :robot: I have created a release \*beep\* \*boop\* --- ## [1.24.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.23.0...v1.24.0) (2020-12-11) ### Features * add Python 3.9 support, drop Python 3.5 support ([#655](https://www.github.com/googleapis/google-auth-library-python/issues/655)) ([6de753d](https://www.github.com/googleapis/google-auth-library-python/commit/6de753d585254c813b3e6cbde27bf5466261ba10)), closes [#654](https://www.github.com/googleapis/google-auth-library-python/issues/654) ### Bug Fixes * avoid losing the original '_include_email' parameter in impersonated credentials ([#626](https://www.github.com/googleapis/google-auth-library-python/issues/626)) ([fd9b5b1](https://www.github.com/googleapis/google-auth-library-python/commit/fd9b5b10c80950784bd37ee56e32c505acb5078d)) ### Documentation * fix typo in import ([#651](https://www.github.com/googleapis/google-auth-library-python/issues/651)) ([3319ea8](https://www.github.com/googleapis/google-auth-library-python/commit/3319ea8ae876c73a94f51237b3bbb3f5df2aef89)), closes [#650](https://www.github.com/googleapis/google-auth-library-python/issues/650) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). --- packages/google-auth/CHANGELOG.md | 17 +++++++++++++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 1e5950234f92..3a06300cf0dc 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,23 @@ [1]: https://pypi.org/project/google-auth/#history +## [1.24.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.23.0...v1.24.0) (2020-12-11) + + +### Features + +* add Python 3.9 support, drop Python 3.5 support ([#655](https://www.github.com/googleapis/google-auth-library-python/issues/655)) ([6de753d](https://www.github.com/googleapis/google-auth-library-python/commit/6de753d585254c813b3e6cbde27bf5466261ba10)), closes [#654](https://www.github.com/googleapis/google-auth-library-python/issues/654) + + +### Bug Fixes + +* avoid losing the original '_include_email' parameter in impersonated credentials ([#626](https://www.github.com/googleapis/google-auth-library-python/issues/626)) ([fd9b5b1](https://www.github.com/googleapis/google-auth-library-python/commit/fd9b5b10c80950784bd37ee56e32c505acb5078d)) + + +### Documentation + +* fix typo in import ([#651](https://www.github.com/googleapis/google-auth-library-python/issues/651)) ([3319ea8](https://www.github.com/googleapis/google-auth-library-python/commit/3319ea8ae876c73a94f51237b3bbb3f5df2aef89)), closes [#650](https://www.github.com/googleapis/google-auth-library-python/issues/650) + ## [1.23.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.22.1...v1.23.0) (2020-10-29) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 66e74ee4374e..3006d9ace3eb 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -34,7 +34,7 @@ with io.open("README.rst", "r") as fh: long_description = fh.read() -version = "1.23.0" +version = "1.24.0" setup( name="google-auth", From 07d69320afbdd969e6b63c6c39952a4c4d9d3964 Mon Sep 17 00:00:00 2001 From: Yoshi Automation Bot Date: Mon, 11 Jan 2021 08:56:03 -0800 Subject: [PATCH 369/966] chore(python): skip docfx in main presubmit (#661) * chore(python): skip docfx in main presubmit * fix: properly template the repo name Source-Author: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Source-Date: Fri Jan 8 10:32:13 2021 -0700 Source-Repo: googleapis/synthtool Source-Sha: fb53b6fb373b7c3edf4e55f3e8036bc6d73fa483 Source-Link: https://github.com/googleapis/synthtool/commit/fb53b6fb373b7c3edf4e55f3e8036bc6d73fa483 --- packages/google-auth/.kokoro/build.sh | 16 ++++++++++------ .../google-auth/.kokoro/docs/docs-presubmit.cfg | 11 +++++++++++ packages/google-auth/synth.metadata | 4 ++-- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/packages/google-auth/.kokoro/build.sh b/packages/google-auth/.kokoro/build.sh index 3a63e98c6ca9..8739d4072ee2 100755 --- a/packages/google-auth/.kokoro/build.sh +++ b/packages/google-auth/.kokoro/build.sh @@ -15,7 +15,11 @@ set -eo pipefail -cd github/google-auth-library-python +if [[ -z "${PROJECT_ROOT:-}" ]]; then + PROJECT_ROOT="github/google-auth-library-python" +fi + +cd "${PROJECT_ROOT}" # Disable buffering, so that the logs stream through. export PYTHONUNBUFFERED=1 @@ -30,16 +34,16 @@ export GOOGLE_APPLICATION_CREDENTIALS=${KOKORO_GFILE_DIR}/service-account.json export PROJECT_ID=$(cat "${KOKORO_GFILE_DIR}/project-id.json") # Remove old nox -python3.6 -m pip uninstall --yes --quiet nox-automation +python3 -m pip uninstall --yes --quiet nox-automation # Install nox -python3.6 -m pip install --upgrade --quiet nox -python3.6 -m nox --version +python3 -m pip install --upgrade --quiet nox +python3 -m nox --version # If NOX_SESSION is set, it only runs the specified session, # otherwise run all the sessions. if [[ -n "${NOX_SESSION:-}" ]]; then - python3.6 -m nox -s "${NOX_SESSION:-}" + python3 -m nox -s ${NOX_SESSION:-} else - python3.6 -m nox + python3 -m nox fi diff --git a/packages/google-auth/.kokoro/docs/docs-presubmit.cfg b/packages/google-auth/.kokoro/docs/docs-presubmit.cfg index 1118107829b7..93c606f51087 100644 --- a/packages/google-auth/.kokoro/docs/docs-presubmit.cfg +++ b/packages/google-auth/.kokoro/docs/docs-presubmit.cfg @@ -15,3 +15,14 @@ env_vars: { key: "TRAMPOLINE_IMAGE_UPLOAD" value: "false" } + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: github/google-auth-library-python/.kokoro/build.sh" +} + +# Only run this nox session. +env_vars: { + key: "NOX_SESSION" + value: "docs docfx" +} diff --git a/packages/google-auth/synth.metadata b/packages/google-auth/synth.metadata index 5e1ef9a55daf..db9feffdd529 100644 --- a/packages/google-auth/synth.metadata +++ b/packages/google-auth/synth.metadata @@ -4,14 +4,14 @@ "git": { "name": ".", "remote": "https://github.com/googleapis/google-auth-library-python.git", - "sha": "9c4200dff31986b7ff300126e9aa35d14aa84dba" + "sha": "647290a2dfc797067f7966c1dae512359e6bb7e7" } }, { "git": { "name": "synthtool", "remote": "https://github.com/googleapis/synthtool.git", - "sha": "da5c6050d13b4950c82666a81d8acd25157664ae" + "sha": "fb53b6fb373b7c3edf4e55f3e8036bc6d73fa483" } } ], From 596e9d8e8c4b781a0b38ba50ebc6826d7487c442 Mon Sep 17 00:00:00 2001 From: Yoshi Automation Bot Date: Tue, 12 Jan 2021 10:08:13 -0800 Subject: [PATCH 370/966] chore: add missing quotation mark (#664) Source-Author: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Source-Date: Mon Jan 11 09:43:06 2021 -0700 Source-Repo: googleapis/synthtool Source-Sha: 16ec872dd898d7de6e1822badfac32484b5d9031 Source-Link: https://github.com/googleapis/synthtool/commit/16ec872dd898d7de6e1822badfac32484b5d9031 --- packages/google-auth/.kokoro/docs/docs-presubmit.cfg | 2 +- packages/google-auth/synth.metadata | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/google-auth/.kokoro/docs/docs-presubmit.cfg b/packages/google-auth/.kokoro/docs/docs-presubmit.cfg index 93c606f51087..d0f5783d5345 100644 --- a/packages/google-auth/.kokoro/docs/docs-presubmit.cfg +++ b/packages/google-auth/.kokoro/docs/docs-presubmit.cfg @@ -18,7 +18,7 @@ env_vars: { env_vars: { key: "TRAMPOLINE_BUILD_FILE" - value: github/google-auth-library-python/.kokoro/build.sh" + value: "github/google-auth-library-python/.kokoro/build.sh" } # Only run this nox session. diff --git a/packages/google-auth/synth.metadata b/packages/google-auth/synth.metadata index db9feffdd529..0de642bf785c 100644 --- a/packages/google-auth/synth.metadata +++ b/packages/google-auth/synth.metadata @@ -4,14 +4,14 @@ "git": { "name": ".", "remote": "https://github.com/googleapis/google-auth-library-python.git", - "sha": "647290a2dfc797067f7966c1dae512359e6bb7e7" + "sha": "f062da8392c32fb3306cdc6e4dbae78212aa0dc7" } }, { "git": { "name": "synthtool", "remote": "https://github.com/googleapis/synthtool.git", - "sha": "fb53b6fb373b7c3edf4e55f3e8036bc6d73fa483" + "sha": "16ec872dd898d7de6e1822badfac32484b5d9031" } } ], From e408f8e251f254a9bdfbb223ec1a652f24fd0f8f Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Fri, 22 Jan 2021 13:43:41 -0700 Subject: [PATCH 371/966] test: re-enable system tests (#670) --- packages/google-auth/.gitignore | 1 + packages/google-auth/.kokoro/build.sh | 16 +++++++++++- .../google-auth/.kokoro/continuous/common.cfg | 2 +- .../google-auth/.kokoro/presubmit/common.cfg | 2 +- packages/google-auth/synth.py | 6 ++--- packages/google-auth/system_tests/__init__.py | 0 packages/google-auth/system_tests/noxfile.py | 10 ++++---- .../google-auth/system_tests/secrets.tar.enc | Bin 0 -> 10323 bytes .../system_tests_async/__init__.py | 0 .../system_tests_async/conftest.py | 3 ++- .../system_tests_async/test_default.py | 5 ++-- .../app_engine_test_app/requirements.txt | 2 +- .../system_tests_sync/secrets.tar.enc | Bin 10323 -> 0 bytes .../system_tests_sync/test_grpc.py | 23 +++--------------- .../system_tests_sync/test_mtls_http.py | 5 +++- 15 files changed, 38 insertions(+), 37 deletions(-) create mode 100644 packages/google-auth/system_tests/__init__.py create mode 100644 packages/google-auth/system_tests/secrets.tar.enc create mode 100644 packages/google-auth/system_tests/system_tests_async/__init__.py delete mode 100644 packages/google-auth/system_tests/system_tests_sync/secrets.tar.enc diff --git a/packages/google-auth/.gitignore b/packages/google-auth/.gitignore index f01e60ec0b0f..1f0b7e3c7836 100644 --- a/packages/google-auth/.gitignore +++ b/packages/google-auth/.gitignore @@ -30,6 +30,7 @@ scripts/local_test_setup tests/data/key.json tests/data/key.p12 tests/data/user-key.json +system_tests/data/ # PyCharm configuration: .idea diff --git a/packages/google-auth/.kokoro/build.sh b/packages/google-auth/.kokoro/build.sh index 8739d4072ee2..1f96e21d7834 100755 --- a/packages/google-auth/.kokoro/build.sh +++ b/packages/google-auth/.kokoro/build.sh @@ -31,7 +31,14 @@ env | grep KOKORO export GOOGLE_APPLICATION_CREDENTIALS=${KOKORO_GFILE_DIR}/service-account.json # Setup project id. -export PROJECT_ID=$(cat "${KOKORO_GFILE_DIR}/project-id.json") +export PROJECT_ID=$(cat "${KOKORO_GFILE_DIR}/project-id.txt") + +# Activate gcloud with service account credentials +gcloud auth activate-service-account --key-file=$GOOGLE_APPLICATION_CREDENTIALS +gcloud config set project ${PROJECT_ID} + +# Decrypt system test secrets +./scripts/decrypt-secrets.sh # Remove old nox python3 -m pip uninstall --yes --quiet nox-automation @@ -47,3 +54,10 @@ if [[ -n "${NOX_SESSION:-}" ]]; then else python3 -m nox fi + + +# Decrypt system test secrets +./scripts/decrypt-secrets.sh + +# Run system tests which use a different noxfile +python3 -m nox -f system_tests/noxfile.py \ No newline at end of file diff --git a/packages/google-auth/.kokoro/continuous/common.cfg b/packages/google-auth/.kokoro/continuous/common.cfg index c587b41047d0..10910e357558 100644 --- a/packages/google-auth/.kokoro/continuous/common.cfg +++ b/packages/google-auth/.kokoro/continuous/common.cfg @@ -11,7 +11,7 @@ action { gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" # Download resources for system tests (service account key, etc.) -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-cloud-python" +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-auth-library-python" # Use the trampoline script to run in docker. build_file: "google-auth-library-python/.kokoro/trampoline.sh" diff --git a/packages/google-auth/.kokoro/presubmit/common.cfg b/packages/google-auth/.kokoro/presubmit/common.cfg index c587b41047d0..10910e357558 100644 --- a/packages/google-auth/.kokoro/presubmit/common.cfg +++ b/packages/google-auth/.kokoro/presubmit/common.cfg @@ -11,7 +11,7 @@ action { gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" # Download resources for system tests (service account key, etc.) -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-cloud-python" +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-auth-library-python" # Use the trampoline script to run in docker. build_file: "google-auth-library-python/.kokoro/trampoline.sh" diff --git a/packages/google-auth/synth.py b/packages/google-auth/synth.py index 49bf2dda6236..f692f7010623 100644 --- a/packages/google-auth/synth.py +++ b/packages/google-auth/synth.py @@ -10,8 +10,8 @@ s.move( templated_files / ".kokoro", excludes=[ - ".kokoro/continuous/common.cfg", - ".kokoro/presubmit/common.cfg", - ".kokoro/build.sh", + "continuous/common.cfg", + "presubmit/common.cfg", + "build.sh", ], ) # just move kokoro configs diff --git a/packages/google-auth/system_tests/__init__.py b/packages/google-auth/system_tests/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/google-auth/system_tests/noxfile.py b/packages/google-auth/system_tests/noxfile.py index dcfe8ee81664..5d0014bc80d9 100644 --- a/packages/google-auth/system_tests/noxfile.py +++ b/packages/google-auth/system_tests/noxfile.py @@ -30,7 +30,7 @@ import py.path HERE = os.path.abspath(os.path.dirname(__file__)) -LIBRARY_DIR = os.path.join(HERE, "..") +LIBRARY_DIR = os.path.abspath(os.path.dirname(HERE)) DATA_DIR = os.path.join(HERE, "data") SERVICE_ACCOUNT_FILE = os.path.join(DATA_DIR, "service_account.json") AUTHORIZED_USER_FILE = os.path.join(DATA_DIR, "authorized_user.json") @@ -169,7 +169,7 @@ def configure_cloud_sdk(session, application_default_credentials, project=False) # Test sesssions TEST_DEPENDENCIES_ASYNC = ["aiohttp", "pytest-asyncio", "nest-asyncio"] -TEST_DEPENDENCIES_SYNC = ["pytest", "requests"] +TEST_DEPENDENCIES_SYNC = ["pytest", "requests", "mock"] PYTHON_VERSIONS_ASYNC = ["3.7"] PYTHON_VERSIONS_SYNC = ["2.7", "3.7"] @@ -249,6 +249,7 @@ def app_engine(session): session.log("Skipping App Engine tests.") return + session.install(LIBRARY_DIR) # Unlike the default tests above, the App Engine system test require a # 'real' gcloud sdk installation that is configured to deploy to an # app engine project. @@ -269,9 +270,8 @@ def app_engine(session): application_url = GAE_APP_URL_TMPL.format(GAE_TEST_APP_SERVICE, project_id) # Vendor in the test application's dependencies - session.chdir(os.path.join(HERE, "../app_engine_test_app")) + session.chdir(os.path.join(HERE, "system_tests_sync/app_engine_test_app")) session.install(*TEST_DEPENDENCIES_SYNC) - session.install(LIBRARY_DIR) session.run( "pip", "install", "--target", "lib", "-r", "requirements.txt", silent=True ) @@ -288,7 +288,7 @@ def app_engine(session): @nox.session(python=PYTHON_VERSIONS_SYNC) def grpc(session): session.install(LIBRARY_DIR) - session.install(*TEST_DEPENDENCIES_SYNC, "google-cloud-pubsub==1.0.0") + session.install(*TEST_DEPENDENCIES_SYNC, "google-cloud-pubsub==1.7.0") session.env[EXPLICIT_CREDENTIALS_ENV] = SERVICE_ACCOUNT_FILE session.run("pytest", "system_tests_sync/test_grpc.py") diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc new file mode 100644 index 0000000000000000000000000000000000000000..29e06923f0f028b54d1b571dc218cdd92f751bd8 GIT binary patch literal 10323 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 diff --git a/packages/google-auth/system_tests/system_tests_sync/test_grpc.py b/packages/google-auth/system_tests/system_tests_sync/test_grpc.py index 650fa96a40a3..7dcbd4c43335 100644 --- a/packages/google-auth/system_tests/system_tests_sync/test_grpc.py +++ b/packages/google-auth/system_tests/system_tests_sync/test_grpc.py @@ -17,8 +17,6 @@ import google.auth.jwt import google.auth.transport.grpc from google.cloud import pubsub_v1 -from google.cloud.pubsub_v1.gapic import publisher_client -from google.cloud.pubsub_v1.gapic.transports import publisher_grpc_transport def test_grpc_request_with_regular_credentials(http_request): @@ -27,13 +25,8 @@ def test_grpc_request_with_regular_credentials(http_request): credentials, ["https://www.googleapis.com/auth/pubsub"] ) - transport = publisher_grpc_transport.PublisherGrpcTransport( - address=publisher_client.PublisherClient.SERVICE_ADDRESS, - credentials=credentials, - ) - # Create a pub/sub client. - client = pubsub_v1.PublisherClient(transport=transport) + client = pubsub_v1.PublisherClient(credentials=credentials) # list the topics and drain the iterator to test that an authorized API # call works. @@ -48,13 +41,8 @@ def test_grpc_request_with_jwt_credentials(): credentials, audience=audience ) - transport = publisher_grpc_transport.PublisherGrpcTransport( - address=publisher_client.PublisherClient.SERVICE_ADDRESS, - credentials=credentials, - ) - # Create a pub/sub client. - client = pubsub_v1.PublisherClient(transport=transport) + client = pubsub_v1.PublisherClient(credentials=credentials) # list the topics and drain the iterator to test that an authorized API # call works. @@ -68,13 +56,8 @@ def test_grpc_request_with_on_demand_jwt_credentials(): credentials ) - transport = publisher_grpc_transport.PublisherGrpcTransport( - address=publisher_client.PublisherClient.SERVICE_ADDRESS, - credentials=credentials, - ) - # Create a pub/sub client. - client = pubsub_v1.PublisherClient(transport=transport) + client = pubsub_v1.PublisherClient(credentials=credentials) # list the topics and drain the iterator to test that an authorized API # call works. diff --git a/packages/google-auth/system_tests/system_tests_sync/test_mtls_http.py b/packages/google-auth/system_tests/system_tests_sync/test_mtls_http.py index 7c564968503b..bcf2a59da880 100644 --- a/packages/google-auth/system_tests/system_tests_sync/test_mtls_http.py +++ b/packages/google-auth/system_tests/system_tests_sync/test_mtls_http.py @@ -13,8 +13,11 @@ # limitations under the License. import json -from os import path +import mock +import os import time +from os import path + import google.auth import google.auth.credentials From 1728328cbd36d11890eca92bea390020bcee4110 Mon Sep 17 00:00:00 2001 From: Justin Beckwith Date: Fri, 29 Jan 2021 08:08:28 -0800 Subject: [PATCH 372/966] build: migrate to flakybot (#675) --- packages/google-auth/.kokoro/test-samples.sh | 8 ++++---- packages/google-auth/.kokoro/trampoline_v2.sh | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/google-auth/.kokoro/test-samples.sh b/packages/google-auth/.kokoro/test-samples.sh index 9a9de2086c49..dbfe9fbb028b 100755 --- a/packages/google-auth/.kokoro/test-samples.sh +++ b/packages/google-auth/.kokoro/test-samples.sh @@ -87,11 +87,11 @@ for file in samples/**/requirements.txt; do python3.6 -m nox -s "$RUN_TESTS_SESSION" EXIT=$? - # If this is a periodic build, send the test log to the Build Cop Bot. - # See https://github.com/googleapis/repo-automation-bots/tree/master/packages/buildcop. + # If this is a periodic build, send the test log to the FlakyBot. + # See https://github.com/googleapis/repo-automation-bots/tree/master/packages/flakybot. if [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"periodic"* ]]; then - chmod +x $KOKORO_GFILE_DIR/linux_amd64/buildcop - $KOKORO_GFILE_DIR/linux_amd64/buildcop + chmod +x $KOKORO_GFILE_DIR/linux_amd64/flakybot + $KOKORO_GFILE_DIR/linux_amd64/flakybot fi if [[ $EXIT -ne 0 ]]; then diff --git a/packages/google-auth/.kokoro/trampoline_v2.sh b/packages/google-auth/.kokoro/trampoline_v2.sh index 719bcd5ba84d..4af6cdc26dbc 100755 --- a/packages/google-auth/.kokoro/trampoline_v2.sh +++ b/packages/google-auth/.kokoro/trampoline_v2.sh @@ -159,7 +159,7 @@ if [[ -n "${KOKORO_BUILD_ID:-}" ]]; then "KOKORO_GITHUB_COMMIT" "KOKORO_GITHUB_PULL_REQUEST_NUMBER" "KOKORO_GITHUB_PULL_REQUEST_COMMIT" - # For Build Cop Bot + # For FlakyBot "KOKORO_GITHUB_COMMIT_URL" "KOKORO_GITHUB_PULL_REQUEST_URL" ) From 9d72beb541235e65059c59033c74634a64b51302 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Mon, 1 Feb 2021 15:17:49 -0700 Subject: [PATCH 373/966] feat: use self-signed jwt for service account (#665) --- packages/google-auth/google/auth/_default.py | 16 +++-- .../google-auth/google/auth/app_engine.py | 21 +++++-- .../google/auth/compute_engine/credentials.py | 18 ++++-- .../google-auth/google/auth/credentials.py | 19 ++++-- .../google-auth/google/auth/transport/grpc.py | 20 ++++++- .../google-auth/google/oauth2/credentials.py | 14 ++++- .../google/oauth2/service_account.py | 44 +++++++++++--- .../system_tests_sync/test_grpc.py | 29 +++++++++- .../tests/compute_engine/test__metadata.py | 38 ++++++++++++ .../tests/oauth2/test_credentials.py | 58 +++++++++++++++++++ .../tests/oauth2/test_service_account.py | 48 +++++++++++++++ packages/google-auth/tests/test__default.py | 2 +- packages/google-auth/tests/test_app_engine.py | 36 +++++++++++- .../google-auth/tests/test_credentials.py | 9 ++- .../google-auth/tests/transport/test_grpc.py | 39 ++++++++++++- 15 files changed, 373 insertions(+), 38 deletions(-) diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index 43778931a389..3b8c281e72b6 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -69,7 +69,9 @@ def _warn_about_problematic_credentials(credentials): warnings.warn(_CLOUD_SDK_CREDENTIALS_WARNING) -def load_credentials_from_file(filename, scopes=None, quota_project_id=None): +def load_credentials_from_file( + filename, scopes=None, default_scopes=None, quota_project_id=None +): """Loads Google credentials from a file. The credentials file must be a service account key or stored authorized @@ -80,6 +82,8 @@ def load_credentials_from_file(filename, scopes=None, quota_project_id=None): scopes (Optional[Sequence[str]]): The list of scopes for the credentials. If specified, the credentials will automatically be scoped if necessary + default_scopes (Optional[Sequence[str]]): Default scopes passed by a + Google client library. Use 'scopes' for user-defined scopes. quota_project_id (Optional[str]): The project ID used for quota and billing. @@ -132,7 +136,7 @@ def load_credentials_from_file(filename, scopes=None, quota_project_id=None): try: credentials = service_account.Credentials.from_service_account_info( - info, scopes=scopes + info, scopes=scopes, default_scopes=default_scopes ) except ValueError as caught_exc: msg = "Failed to load service account credentials from {}".format(filename) @@ -248,7 +252,7 @@ def _get_gce_credentials(request=None): return None, None -def default(scopes=None, request=None, quota_project_id=None): +def default(scopes=None, request=None, quota_project_id=None, default_scopes=None): """Gets the default credentials for the current environment. `Application Default Credentials`_ provides an easy way to obtain @@ -312,6 +316,8 @@ def default(scopes=None, request=None, quota_project_id=None): use the standard library http client to make requests. quota_project_id (Optional[str]): The project ID used for quota and billing. + default_scopes (Optional[Sequence[str]]): Default scopes passed by a + Google client library. Use 'scopes' for user-defined scopes. Returns: Tuple[~google.auth.credentials.Credentials, Optional[str]]: the current environment's credentials and project ID. Project ID @@ -339,7 +345,9 @@ def default(scopes=None, request=None, quota_project_id=None): for checker in checkers: credentials, project_id = checker() if credentials is not None: - credentials = with_scopes_if_required(credentials, scopes) + credentials = with_scopes_if_required( + credentials, scopes, default_scopes=default_scopes + ) if quota_project_id: credentials = credentials.with_quota_project(quota_project_id) diff --git a/packages/google-auth/google/auth/app_engine.py b/packages/google-auth/google/auth/app_engine.py index f1d21280eca8..81aef73b4506 100644 --- a/packages/google-auth/google/auth/app_engine.py +++ b/packages/google-auth/google/auth/app_engine.py @@ -86,11 +86,19 @@ class Credentials( tokens. """ - def __init__(self, scopes=None, service_account_id=None, quota_project_id=None): + def __init__( + self, + scopes=None, + default_scopes=None, + service_account_id=None, + quota_project_id=None, + ): """ Args: scopes (Sequence[str]): Scopes to request from the App Identity API. + default_scopes (Sequence[str]): Default scopes passed by a + Google client library. Use 'scopes' for user-defined scopes. service_account_id (str): The service account ID passed into :func:`google.appengine.api.app_identity.get_access_token`. If not specified, the default application service account @@ -109,16 +117,16 @@ def __init__(self, scopes=None, service_account_id=None, quota_project_id=None): super(Credentials, self).__init__() self._scopes = scopes + self._default_scopes = default_scopes self._service_account_id = service_account_id self._signer = Signer() self._quota_project_id = quota_project_id @_helpers.copy_docstring(credentials.Credentials) def refresh(self, request): + scopes = self._scopes if self._scopes is not None else self._default_scopes # pylint: disable=unused-argument - token, ttl = app_identity.get_access_token( - self._scopes, self._service_account_id - ) + token, ttl = app_identity.get_access_token(scopes, self._service_account_id) expiry = datetime.datetime.utcfromtimestamp(ttl) self.token, self.expiry = token, expiry @@ -137,12 +145,13 @@ def requires_scopes(self): Returns: bool: True if there are no scopes set otherwise False. """ - return not self._scopes + return not self._scopes and not self._default_scopes @_helpers.copy_docstring(credentials.Scoped) - def with_scopes(self, scopes): + def with_scopes(self, scopes, default_scopes=None): return self.__class__( scopes=scopes, + default_scopes=default_scopes, service_account_id=self._service_account_id, quota_project_id=self.quota_project_id, ) diff --git a/packages/google-auth/google/auth/compute_engine/credentials.py b/packages/google-auth/google/auth/compute_engine/credentials.py index 29063103a3a9..167165620068 100644 --- a/packages/google-auth/google/auth/compute_engine/credentials.py +++ b/packages/google-auth/google/auth/compute_engine/credentials.py @@ -52,7 +52,11 @@ class Credentials(credentials.Scoped, credentials.CredentialsWithQuotaProject): """ def __init__( - self, service_account_email="default", quota_project_id=None, scopes=None + self, + service_account_email="default", + quota_project_id=None, + scopes=None, + default_scopes=None, ): """ Args: @@ -61,11 +65,15 @@ def __init__( accounts. quota_project_id (Optional[str]): The project ID used for quota and billing. + scopes (Optional[Sequence[str]]): The list of scopes for the credentials. + default_scopes (Optional[Sequence[str]]): Default scopes passed by a + Google client library. Use 'scopes' for user-defined scopes. """ super(Credentials, self).__init__() self._service_account_email = service_account_email self._quota_project_id = quota_project_id self._scopes = scopes + self._default_scopes = default_scopes def _retrieve_info(self, request): """Retrieve information about the service account. @@ -98,12 +106,11 @@ def refresh(self, request): service can't be reached if if the instance has not credentials. """ + scopes = self._scopes if self._scopes is not None else self._default_scopes try: self._retrieve_info(request) self.token, self.expiry = _metadata.get_service_account_token( - request, - service_account=self._service_account_email, - scopes=self._scopes, + request, service_account=self._service_account_email, scopes=scopes ) except exceptions.TransportError as caught_exc: new_exc = exceptions.RefreshError(caught_exc) @@ -131,12 +138,13 @@ def with_quota_project(self, quota_project_id): ) @_helpers.copy_docstring(credentials.Scoped) - def with_scopes(self, scopes): + def with_scopes(self, scopes, default_scopes=None): # Compute Engine credentials can not be scoped (the metadata service # ignores the scopes parameter). App Engine, Cloud Run and Flex support # requesting scopes. return self.__class__( scopes=scopes, + default_scopes=default_scopes, service_account_email=self._service_account_email, quota_project_id=self._quota_project_id, ) diff --git a/packages/google-auth/google/auth/credentials.py b/packages/google-auth/google/auth/credentials.py index 02082cad9342..7d3c798b139d 100644 --- a/packages/google-auth/google/auth/credentials.py +++ b/packages/google-auth/google/auth/credentials.py @@ -220,12 +220,18 @@ class ReadOnlyScoped(object): def __init__(self): super(ReadOnlyScoped, self).__init__() self._scopes = None + self._default_scopes = None @property def scopes(self): """Sequence[str]: the credentials' current set of scopes.""" return self._scopes + @property + def default_scopes(self): + """Sequence[str]: the credentials' current set of default scopes.""" + return self._default_scopes + @abc.abstractproperty def requires_scopes(self): """True if these credentials require scopes to obtain an access token. @@ -244,7 +250,10 @@ def has_scopes(self, scopes): Returns: bool: True if the credentials have the given scopes. """ - return set(scopes).issubset(set(self._scopes or [])) + credential_scopes = ( + self._scopes if self._scopes is not None else self._default_scopes + ) + return set(scopes).issubset(set(credential_scopes or [])) class Scoped(ReadOnlyScoped): @@ -277,7 +286,7 @@ class Scoped(ReadOnlyScoped): """ @abc.abstractmethod - def with_scopes(self, scopes): + def with_scopes(self, scopes, default_scopes=None): """Create a copy of these credentials with the specified scopes. Args: @@ -292,7 +301,7 @@ def with_scopes(self, scopes): raise NotImplementedError("This class does not require scoping.") -def with_scopes_if_required(credentials, scopes): +def with_scopes_if_required(credentials, scopes, default_scopes=None): """Creates a copy of the credentials with scopes if scoping is required. This helper function is useful when you do not know (or care to know) the @@ -306,6 +315,8 @@ def with_scopes_if_required(credentials, scopes): credentials (google.auth.credentials.Credentials): The credentials to scope if necessary. scopes (Sequence[str]): The list of scopes to use. + default_scopes (Sequence[str]): Default scopes passed by a + Google client library. Use 'scopes' for user-defined scopes. Returns: google.auth.credentials.Credentials: Either a new set of scoped @@ -313,7 +324,7 @@ def with_scopes_if_required(credentials, scopes): was required. """ if isinstance(credentials, Scoped) and credentials.requires_scopes: - return credentials.with_scopes(scopes) + return credentials.with_scopes(scopes, default_scopes=default_scopes) else: return credentials diff --git a/packages/google-auth/google/auth/transport/grpc.py b/packages/google-auth/google/auth/transport/grpc.py index ab7d0dbf8fc3..04c0f4f55fee 100644 --- a/packages/google-auth/google/auth/transport/grpc.py +++ b/packages/google-auth/google/auth/transport/grpc.py @@ -24,6 +24,7 @@ from google.auth import environment_vars from google.auth import exceptions from google.auth.transport import _mtls_helper +from google.oauth2 import service_account try: import grpc @@ -51,15 +52,19 @@ class AuthMetadataPlugin(grpc.AuthMetadataPlugin): add to requests. request (google.auth.transport.Request): A HTTP transport request object used to refresh credentials as needed. + default_host (Optional[str]): A host like "pubsub.googleapis.com". + This is used when a self-signed JWT is created from service + account credentials. """ - def __init__(self, credentials, request): + def __init__(self, credentials, request, default_host=None): # pylint: disable=no-value-for-parameter # pylint doesn't realize that the super method takes no arguments # because this class is the same name as the superclass. super(AuthMetadataPlugin, self).__init__() self._credentials = credentials self._request = request + self._default_host = default_host def _get_authorization_headers(self, context): """Gets the authorization headers for a request. @@ -69,6 +74,19 @@ def _get_authorization_headers(self, context): to add to the request. """ headers = {} + + # https://google.aip.dev/auth/4111 + # Attempt to use self-signed JWTs when a service account is used. + # A default host must be explicitly provided since it cannot always + # be determined from the context.service_url. + if ( + isinstance(self._credentials, service_account.Credentials) + and self._default_host + ): + self._credentials._create_self_signed_jwt( + "https://{}/".format(self._default_host) + ) + self._credentials.before_request( self._request, context.method_name, context.service_url, headers ) diff --git a/packages/google-auth/google/oauth2/credentials.py b/packages/google-auth/google/oauth2/credentials.py index 36b8f0cb7624..464cc4878ca6 100644 --- a/packages/google-auth/google/oauth2/credentials.py +++ b/packages/google-auth/google/oauth2/credentials.py @@ -66,6 +66,7 @@ def __init__( client_id=None, client_secret=None, scopes=None, + default_scopes=None, quota_project_id=None, expiry=None, ): @@ -91,6 +92,8 @@ def __init__( token if refresh information is provided (e.g. The refresh token scopes are a superset of this or contain a wild card scope like 'https://www.googleapis.com/auth/any-api'). + default_scopes (Sequence[str]): Default scopes passed by a + Google client library. Use 'scopes' for user-defined scopes. quota_project_id (Optional[str]): The project ID used for quota and billing. This project may be different from the project used to create the credentials. @@ -101,6 +104,7 @@ def __init__( self._refresh_token = refresh_token self._id_token = id_token self._scopes = scopes + self._default_scopes = default_scopes self._token_uri = token_uri self._client_id = client_id self._client_secret = client_secret @@ -121,6 +125,7 @@ def __setstate__(self, d): self._refresh_token = d.get("_refresh_token") self._id_token = d.get("_id_token") self._scopes = d.get("_scopes") + self._default_scopes = d.get("_default_scopes") self._token_uri = d.get("_token_uri") self._client_id = d.get("_client_id") self._client_secret = d.get("_client_secret") @@ -180,6 +185,7 @@ def with_quota_project(self, quota_project_id): client_id=self.client_id, client_secret=self.client_secret, scopes=self.scopes, + default_scopes=self.default_scopes, quota_project_id=quota_project_id, ) @@ -197,13 +203,15 @@ def refresh(self, request): "token_uri, client_id, and client_secret." ) + scopes = self._scopes if self._scopes is not None else self._default_scopes + access_token, refresh_token, expiry, grant_response = _client.refresh_grant( request, self._token_uri, self._refresh_token, self._client_id, self._client_secret, - self._scopes, + scopes, ) self.token = access_token @@ -211,8 +219,8 @@ def refresh(self, request): self._refresh_token = refresh_token self._id_token = grant_response.get("id_token") - if self._scopes and "scopes" in grant_response: - requested_scopes = frozenset(self._scopes) + if scopes and "scopes" in grant_response: + requested_scopes = frozenset(scopes) granted_scopes = frozenset(grant_response["scopes"].split()) scopes_requested_but_not_granted = requested_scopes - granted_scopes if scopes_requested_but_not_granted: diff --git a/packages/google-auth/google/oauth2/service_account.py b/packages/google-auth/google/oauth2/service_account.py index c4898a247ba4..ed91011428b0 100644 --- a/packages/google-auth/google/oauth2/service_account.py +++ b/packages/google-auth/google/oauth2/service_account.py @@ -126,6 +126,7 @@ def __init__( service_account_email, token_uri, scopes=None, + default_scopes=None, subject=None, project_id=None, quota_project_id=None, @@ -135,8 +136,10 @@ def __init__( Args: signer (google.auth.crypt.Signer): The signer used to sign JWTs. service_account_email (str): The service account's email. - scopes (Sequence[str]): Scopes to request during the authorization - grant. + scopes (Sequence[str]): User-defined scopes to request during the + authorization grant. + default_scopes (Sequence[str]): Default scopes passed by a + Google client library. Use 'scopes' for user-defined scopes. token_uri (str): The OAuth 2.0 Token URI. subject (str): For domain-wide delegation, the email address of the user to for which to request delegated access. @@ -155,6 +158,7 @@ def __init__( super(Credentials, self).__init__() self._scopes = scopes + self._default_scopes = default_scopes self._signer = signer self._service_account_email = service_account_email self._subject = subject @@ -162,6 +166,8 @@ def __init__( self._quota_project_id = quota_project_id self._token_uri = token_uri + self._jwt_credentials = None + if additional_claims is not None: self._additional_claims = additional_claims else: @@ -249,11 +255,12 @@ def requires_scopes(self): return True if not self._scopes else False @_helpers.copy_docstring(credentials.Scoped) - def with_scopes(self, scopes): + def with_scopes(self, scopes, default_scopes=None): return self.__class__( self._signer, service_account_email=self._service_account_email, scopes=scopes, + default_scopes=default_scopes, token_uri=self._token_uri, subject=self._subject, project_id=self._project_id, @@ -275,6 +282,7 @@ def with_subject(self, subject): self._signer, service_account_email=self._service_account_email, scopes=self._scopes, + default_scopes=self._default_scopes, token_uri=self._token_uri, subject=subject, project_id=self._project_id, @@ -301,6 +309,7 @@ def with_claims(self, additional_claims): self._signer, service_account_email=self._service_account_email, scopes=self._scopes, + default_scopes=self._default_scopes, token_uri=self._token_uri, subject=self._subject, project_id=self._project_id, @@ -314,6 +323,7 @@ def with_quota_project(self, quota_project_id): return self.__class__( self._signer, service_account_email=self._service_account_email, + default_scopes=self._default_scopes, scopes=self._scopes, token_uri=self._token_uri, subject=self._subject, @@ -357,10 +367,30 @@ def _make_authorization_grant_assertion(self): @_helpers.copy_docstring(credentials.Credentials) def refresh(self, request): - assertion = self._make_authorization_grant_assertion() - access_token, expiry, _ = _client.jwt_grant(request, self._token_uri, assertion) - self.token = access_token - self.expiry = expiry + if self._jwt_credentials is not None: + self._jwt_credentials.refresh(request) + self.token = self._jwt_credentials.token + self.expiry = self._jwt_credentials.expiry + else: + assertion = self._make_authorization_grant_assertion() + access_token, expiry, _ = _client.jwt_grant( + request, self._token_uri, assertion + ) + self.token = access_token + self.expiry = expiry + + def _create_self_signed_jwt(self, audience): + """Create a self-signed JWT from the credentials if requirements are met. + + Args: + audience (str): The service URL. ``https://[API_ENDPOINT]/`` + """ + # https://google.aip.dev/auth/4111 + # If the user has not defined scopes, create a self-signed jwt + if not self.scopes: + self._jwt_credentials = jwt.Credentials.from_signing_credentials( + self, audience + ) @_helpers.copy_docstring(credentials.Signing) def sign_bytes(self, message): diff --git a/packages/google-auth/system_tests/system_tests_sync/test_grpc.py b/packages/google-auth/system_tests/system_tests_sync/test_grpc.py index 7dcbd4c43335..da2eb71fb449 100644 --- a/packages/google-auth/system_tests/system_tests_sync/test_grpc.py +++ b/packages/google-auth/system_tests/system_tests_sync/test_grpc.py @@ -16,14 +16,38 @@ import google.auth.credentials import google.auth.jwt import google.auth.transport.grpc +from google.oauth2 import service_account + from google.cloud import pubsub_v1 def test_grpc_request_with_regular_credentials(http_request): credentials, project_id = google.auth.default() credentials = google.auth.credentials.with_scopes_if_required( - credentials, ["https://www.googleapis.com/auth/pubsub"] + credentials, scopes=["https://www.googleapis.com/auth/pubsub"] + ) + + + # Create a pub/sub client. + client = pubsub_v1.PublisherClient(credentials=credentials) + + # list the topics and drain the iterator to test that an authorized API + # call works. + list_topics_iter = client.list_topics(project="projects/{}".format(project_id)) + list(list_topics_iter) + + +def test_grpc_request_with_regular_credentials_and_self_signed_jwt(http_request): + credentials, project_id = google.auth.default() + + # At the time this test is being written, there are no GAPIC libraries + # that will trigger the self-signed JWT flow. Manually create the self-signed + # jwt on the service account credential to check that the request + # succeeds. + credentials = credentials.with_scopes( + scopes=[], default_scopes=["https://www.googleapis.com/auth/pubsub"] ) + credentials._create_self_signed_jwt(audience="https://pubsub.googleapis.com/") # Create a pub/sub client. client = pubsub_v1.PublisherClient(credentials=credentials) @@ -32,6 +56,9 @@ def test_grpc_request_with_regular_credentials(http_request): # call works. list_topics_iter = client.list_topics(project="projects/{}".format(project_id)) list(list_topics_iter) + + # Check that self-signed JWT was created + assert credentials._jwt_credentials is not None def test_grpc_request_with_jwt_credentials(): diff --git a/packages/google-auth/tests/compute_engine/test__metadata.py b/packages/google-auth/tests/compute_engine/test__metadata.py index d05337263b5e..852822dc0c44 100644 --- a/packages/google-auth/tests/compute_engine/test__metadata.py +++ b/packages/google-auth/tests/compute_engine/test__metadata.py @@ -318,6 +318,44 @@ def test_get_service_account_token(utcnow): assert expiry == utcnow() + datetime.timedelta(seconds=ttl) +@mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) +def test_get_service_account_token_with_scopes_list(utcnow): + ttl = 500 + request = make_request( + json.dumps({"access_token": "token", "expires_in": ttl}), + headers={"content-type": "application/json"}, + ) + + token, expiry = _metadata.get_service_account_token(request, scopes=["foo", "bar"]) + + request.assert_called_once_with( + method="GET", + url=_metadata._METADATA_ROOT + PATH + "/token" + "?scopes=foo%2Cbar", + headers=_metadata._METADATA_HEADERS, + ) + assert token == "token" + assert expiry == utcnow() + datetime.timedelta(seconds=ttl) + + +@mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) +def test_get_service_account_token_with_scopes_string(utcnow): + ttl = 500 + request = make_request( + json.dumps({"access_token": "token", "expires_in": ttl}), + headers={"content-type": "application/json"}, + ) + + token, expiry = _metadata.get_service_account_token(request, scopes="foo,bar") + + request.assert_called_once_with( + method="GET", + url=_metadata._METADATA_ROOT + PATH + "/token" + "?scopes=foo%2Cbar", + headers=_metadata._METADATA_HEADERS, + ) + assert token == "token" + assert expiry == utcnow() + datetime.timedelta(seconds=ttl) + + def test_get_service_account_info(): key, value = "foo", "bar" request = make_request( diff --git a/packages/google-auth/tests/oauth2/test_credentials.py b/packages/google-auth/tests/oauth2/test_credentials.py index ee8b8a2112f1..b885d2973db9 100644 --- a/packages/google-auth/tests/oauth2/test_credentials.py +++ b/packages/google-auth/tests/oauth2/test_credentials.py @@ -127,6 +127,7 @@ def test_credentials_with_scopes_requested_refresh_success( self, unused_utcnow, refresh_grant ): scopes = ["email", "profile"] + default_scopes = ["https://www.googleapis.com/auth/cloud-platform"] token = "token" expiry = _helpers.utcnow() + datetime.timedelta(seconds=500) grant_response = {"id_token": mock.sentinel.id_token} @@ -149,6 +150,7 @@ def test_credentials_with_scopes_requested_refresh_success( client_id=self.CLIENT_ID, client_secret=self.CLIENT_SECRET, scopes=scopes, + default_scopes=default_scopes, ) # Refresh credentials @@ -174,6 +176,62 @@ def test_credentials_with_scopes_requested_refresh_success( # expired.) assert creds.valid + @mock.patch("google.oauth2._client.refresh_grant", autospec=True) + @mock.patch( + "google.auth._helpers.utcnow", + return_value=datetime.datetime.min + _helpers.CLOCK_SKEW, + ) + def test_credentials_with_only_default_scopes_requested( + self, unused_utcnow, refresh_grant + ): + default_scopes = ["email", "profile"] + token = "token" + expiry = _helpers.utcnow() + datetime.timedelta(seconds=500) + grant_response = {"id_token": mock.sentinel.id_token} + refresh_grant.return_value = ( + # Access token + token, + # New refresh token + None, + # Expiry, + expiry, + # Extra data + grant_response, + ) + + request = mock.create_autospec(transport.Request) + creds = credentials.Credentials( + token=None, + refresh_token=self.REFRESH_TOKEN, + token_uri=self.TOKEN_URI, + client_id=self.CLIENT_ID, + client_secret=self.CLIENT_SECRET, + default_scopes=default_scopes, + ) + + # Refresh credentials + creds.refresh(request) + + # Check jwt grant call. + refresh_grant.assert_called_with( + request, + self.TOKEN_URI, + self.REFRESH_TOKEN, + self.CLIENT_ID, + self.CLIENT_SECRET, + default_scopes, + ) + + # Check that the credentials have the token and expiry + assert creds.token == token + assert creds.expiry == expiry + assert creds.id_token == mock.sentinel.id_token + assert creds.has_scopes(default_scopes) + + # Check that the credentials are valid (have a token and are not + # expired.) + assert creds.valid + @mock.patch("google.oauth2._client.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", diff --git a/packages/google-auth/tests/oauth2/test_service_account.py b/packages/google-auth/tests/oauth2/test_service_account.py index 4c75e371bd31..40a4ca219760 100644 --- a/packages/google-auth/tests/oauth2/test_service_account.py +++ b/packages/google-auth/tests/oauth2/test_service_account.py @@ -203,6 +203,28 @@ def test_apply_with_no_quota_project_id(self): assert "x-goog-user-project" not in headers assert "token" in headers["authorization"] + @mock.patch("google.auth.jwt.Credentials.from_signing_credentials", autospec=True) + def test__create_self_signed_jwt(self, from_signing_credentials): + credentials = service_account.Credentials( + SIGNER, self.SERVICE_ACCOUNT_EMAIL, self.TOKEN_URI + ) + + audience = "https://pubsub.googleapis.com" + credentials._create_self_signed_jwt(audience) + from_signing_credentials.assert_called_once_with(credentials, audience) + + @mock.patch("google.auth.jwt.Credentials.from_signing_credentials", autospec=True) + def test__create_self_signed_jwt_with_user_scopes(self, from_signing_credentials): + credentials = service_account.Credentials( + SIGNER, self.SERVICE_ACCOUNT_EMAIL, self.TOKEN_URI, scopes=["foo"] + ) + + audience = "https://pubsub.googleapis.com" + credentials._create_self_signed_jwt(audience) + + # JWT should not be created if there are user-defined scopes + from_signing_credentials.assert_not_called() + @mock.patch("google.oauth2._client.jwt_grant", autospec=True) def test_refresh_success(self, jwt_grant): credentials = self.make_credentials() @@ -257,6 +279,32 @@ def test_before_request_refreshes(self, jwt_grant): # Credentials should now be valid. assert credentials.valid + @mock.patch("google.auth.jwt.Credentials._make_jwt") + def test_refresh_with_jwt_credentials(self, make_jwt): + credentials = self.make_credentials() + credentials._create_self_signed_jwt("https://pubsub.googleapis.com") + + request = mock.create_autospec(transport.Request, instance=True) + + token = "token" + expiry = _helpers.utcnow() + datetime.timedelta(seconds=500) + make_jwt.return_value = (token, expiry) + + # Credentials should start as invalid + assert not credentials.valid + + # before_request should cause a refresh + credentials.before_request(request, "GET", "http://example.com?a=1#3", {}) + + # Credentials should now be valid. + assert credentials.valid + + # Assert make_jwt was called + assert make_jwt.called_once() + + assert credentials.token == token + assert credentials.expiry == expiry + class TestIDTokenCredentials(object): SERVICE_ACCOUNT_EMAIL = "service-account@example.com" diff --git a/packages/google-auth/tests/test__default.py b/packages/google-auth/tests/test__default.py index 2738e22bc82a..74511f9e51c9 100644 --- a/packages/google-auth/tests/test__default.py +++ b/packages/google-auth/tests/test__default.py @@ -471,7 +471,7 @@ def test_default_scoped(with_scopes, unused_get): assert credentials == with_scopes.return_value assert project_id == mock.sentinel.project_id - with_scopes.assert_called_once_with(MOCK_CREDENTIALS, scopes) + with_scopes.assert_called_once_with(MOCK_CREDENTIALS, scopes, default_scopes=None) @mock.patch( diff --git a/packages/google-auth/tests/test_app_engine.py b/packages/google-auth/tests/test_app_engine.py index 846d31477462..e335ff7ed2d8 100644 --- a/packages/google-auth/tests/test_app_engine.py +++ b/packages/google-auth/tests/test_app_engine.py @@ -101,6 +101,7 @@ def test_default_state(self, app_identity): assert not credentials.expired # Scopes are required assert not credentials.scopes + assert not credentials.default_scopes assert credentials.requires_scopes assert not credentials.quota_project_id @@ -115,6 +116,20 @@ def test_with_scopes(self, app_identity): assert scoped_credentials.has_scopes(["email"]) assert not scoped_credentials.requires_scopes + def test_with_default_scopes(self, app_identity): + credentials = app_engine.Credentials() + + assert not credentials.scopes + assert not credentials.default_scopes + assert credentials.requires_scopes + + scoped_credentials = credentials.with_scopes( + scopes=None, default_scopes=["email"] + ) + + assert scoped_credentials.has_scopes(["email"]) + assert not scoped_credentials.requires_scopes + def test_with_quota_project(self, app_identity): credentials = app_engine.Credentials() @@ -147,7 +162,9 @@ def test_refresh(self, utcnow, app_identity): token = "token" ttl = 643942923 app_identity.get_access_token.return_value = token, ttl - credentials = app_engine.Credentials(scopes=["email"]) + credentials = app_engine.Credentials( + scopes=["email"], default_scopes=["profile"] + ) credentials.refresh(None) @@ -159,6 +176,23 @@ def test_refresh(self, utcnow, app_identity): assert credentials.valid assert not credentials.expired + @mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) + def test_refresh_with_default_scopes(self, utcnow, app_identity): + token = "token" + ttl = 643942923 + app_identity.get_access_token.return_value = token, ttl + credentials = app_engine.Credentials(default_scopes=["email"]) + + credentials.refresh(None) + + app_identity.get_access_token.assert_called_with( + credentials.default_scopes, credentials._service_account_id + ) + assert credentials.token == token + assert credentials.expiry == datetime.datetime(1990, 5, 29, 1, 2, 3) + assert credentials.valid + assert not credentials.expired + def test_sign_bytes(self, app_identity): app_identity.sign_blob.return_value = ( mock.sentinel.key_id, diff --git a/packages/google-auth/tests/test_credentials.py b/packages/google-auth/tests/test_credentials.py index 0637b01e42e2..0633b38c07ca 100644 --- a/packages/google-auth/tests/test_credentials.py +++ b/packages/google-auth/tests/test_credentials.py @@ -142,16 +142,19 @@ def test_readonly_scoped_credentials_requires_scopes(): class RequiresScopedCredentialsImpl(credentials.Scoped, CredentialsImpl): - def __init__(self, scopes=None): + def __init__(self, scopes=None, default_scopes=None): super(RequiresScopedCredentialsImpl, self).__init__() self._scopes = scopes + self._default_scopes = default_scopes @property def requires_scopes(self): return not self.scopes - def with_scopes(self, scopes): - return RequiresScopedCredentialsImpl(scopes=scopes) + def with_scopes(self, scopes, default_scopes=None): + return RequiresScopedCredentialsImpl( + scopes=scopes, default_scopes=default_scopes + ) def test_create_scoped_if_required_scoped(): diff --git a/packages/google-auth/tests/transport/test_grpc.py b/packages/google-auth/tests/transport/test_grpc.py index 39f8b11c8a13..1602f4c0f071 100644 --- a/packages/google-auth/tests/transport/test_grpc.py +++ b/packages/google-auth/tests/transport/test_grpc.py @@ -24,6 +24,7 @@ from google.auth import environment_vars from google.auth import exceptions from google.auth import transport +from google.oauth2 import service_account try: # pylint: disable=ungrouped-imports @@ -74,7 +75,7 @@ def test_call_no_refresh(self): time.sleep(2) callback.assert_called_once_with( - [(u"authorization", u"Bearer {}".format(credentials.token))], None + [("authorization", "Bearer {}".format(credentials.token))], None ) def test_call_refresh(self): @@ -95,7 +96,41 @@ def test_call_refresh(self): assert credentials.token == "token1" callback.assert_called_once_with( - [(u"authorization", u"Bearer {}".format(credentials.token))], None + [("authorization", "Bearer {}".format(credentials.token))], None + ) + + def test__get_authorization_headers_with_service_account(self): + credentials = mock.create_autospec(service_account.Credentials) + request = mock.create_autospec(transport.Request) + + plugin = google.auth.transport.grpc.AuthMetadataPlugin(credentials, request) + + context = mock.create_autospec(grpc.AuthMetadataContext, instance=True) + context.method_name = "methodName" + context.service_url = "https://pubsub.googleapis.com/methodName" + + plugin._get_authorization_headers(context) + + # self-signed JWT should not be created when default_host is not set + credentials._create_self_signed_jwt.assert_not_called() + + def test__get_authorization_headers_with_service_account_and_default_host(self): + credentials = mock.create_autospec(service_account.Credentials) + request = mock.create_autospec(transport.Request) + + default_host = "pubsub.googleapis.com" + plugin = google.auth.transport.grpc.AuthMetadataPlugin( + credentials, request, default_host=default_host + ) + + context = mock.create_autospec(grpc.AuthMetadataContext, instance=True) + context.method_name = "methodName" + context.service_url = "https://pubsub.googleapis.com/methodName" + + plugin._get_authorization_headers(context) + + credentials._create_self_signed_jwt.assert_called_once_with( + "https://{}/".format(default_host) ) From 4e57878ff9de2f41d786a8174c5a933639d2f509 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Wed, 3 Feb 2021 16:57:08 -0700 Subject: [PATCH 374/966] feat: support self-signed jwt in requests and urllib3 transports (#679) --- .../google/auth/transport/requests.py | 17 +++++++ .../google/auth/transport/urllib3.py | 17 +++++++ packages/google-auth/system_tests/noxfile.py | 17 +++++++ .../system_tests_sync/test_grpc.py | 3 +- .../system_tests_sync/test_requests.py | 40 +++++++++++++++++ .../system_tests_sync/test_urllib3.py | 44 +++++++++++++++++++ .../tests/transport/test_requests.py | 20 +++++++++ .../tests/transport/test_urllib3.py | 20 +++++++++ 8 files changed, 177 insertions(+), 1 deletion(-) create mode 100644 packages/google-auth/system_tests/system_tests_sync/test_requests.py create mode 100644 packages/google-auth/system_tests/system_tests_sync/test_urllib3.py diff --git a/packages/google-auth/google/auth/transport/requests.py b/packages/google-auth/google/auth/transport/requests.py index 9a2f3afc7966..ef973fce4433 100644 --- a/packages/google-auth/google/auth/transport/requests.py +++ b/packages/google-auth/google/auth/transport/requests.py @@ -45,6 +45,7 @@ from google.auth import exceptions from google.auth import transport import google.auth.transport._mtls_helper +from google.oauth2 import service_account _LOGGER = logging.getLogger(__name__) @@ -313,6 +314,9 @@ def my_cert_callback(): refreshing credentials. If not passed, an instance of :class:`~google.auth.transport.requests.Request` is created. + default_host (Optional[str]): A host like "pubsub.googleapis.com". + This is used when a self-signed JWT is created from service + account credentials. """ def __init__( @@ -322,6 +326,7 @@ def __init__( max_refresh_attempts=transport.DEFAULT_MAX_REFRESH_ATTEMPTS, refresh_timeout=None, auth_request=None, + default_host=None, ): super(AuthorizedSession, self).__init__() self.credentials = credentials @@ -329,6 +334,7 @@ def __init__( self._max_refresh_attempts = max_refresh_attempts self._refresh_timeout = refresh_timeout self._is_mtls = False + self._default_host = default_host if auth_request is None: auth_request_session = requests.Session() @@ -347,6 +353,17 @@ def __init__( # credentials.refresh). self._auth_request = auth_request + # https://google.aip.dev/auth/4111 + # Attempt to use self-signed JWTs when a service account is used. + # A default host must be explicitly provided. + if ( + isinstance(self.credentials, service_account.Credentials) + and self._default_host + ): + self.credentials._create_self_signed_jwt( + "https://{}/".format(self._default_host) + ) + def configure_mtls_channel(self, client_cert_callback=None): """Configure the client certificate and key for SSL connection. diff --git a/packages/google-auth/google/auth/transport/urllib3.py b/packages/google-auth/google/auth/transport/urllib3.py index 209fc51bc549..aadd116e84a0 100644 --- a/packages/google-auth/google/auth/transport/urllib3.py +++ b/packages/google-auth/google/auth/transport/urllib3.py @@ -49,6 +49,7 @@ from google.auth import environment_vars from google.auth import exceptions from google.auth import transport +from google.oauth2 import service_account _LOGGER = logging.getLogger(__name__) @@ -262,6 +263,9 @@ def my_cert_callback(): retried. max_refresh_attempts (int): The maximum number of times to attempt to refresh the credentials and retry the request. + default_host (Optional[str]): A host like "pubsub.googleapis.com". + This is used when a self-signed JWT is created from service + account credentials. """ def __init__( @@ -270,6 +274,7 @@ def __init__( http=None, refresh_status_codes=transport.DEFAULT_REFRESH_STATUS_CODES, max_refresh_attempts=transport.DEFAULT_MAX_REFRESH_ATTEMPTS, + default_host=None, ): if http is None: self.http = _make_default_http() @@ -281,10 +286,22 @@ def __init__( self.credentials = credentials self._refresh_status_codes = refresh_status_codes self._max_refresh_attempts = max_refresh_attempts + self._default_host = default_host # Request instance used by internal methods (for example, # credentials.refresh). self._request = Request(self.http) + # https://google.aip.dev/auth/4111 + # Attempt to use self-signed JWTs when a service account is used. + # A default host must be explicitly provided. + if ( + isinstance(self.credentials, service_account.Credentials) + and self._default_host + ): + self.credentials._create_self_signed_jwt( + "https://{}/".format(self._default_host) + ) + super(AuthorizedHttp, self).__init__() def configure_mtls_channel(self, client_cert_callback=None): diff --git a/packages/google-auth/system_tests/noxfile.py b/packages/google-auth/system_tests/noxfile.py index 5d0014bc80d9..4ba7cc3efedc 100644 --- a/packages/google-auth/system_tests/noxfile.py +++ b/packages/google-auth/system_tests/noxfile.py @@ -293,6 +293,22 @@ def grpc(session): session.run("pytest", "system_tests_sync/test_grpc.py") +@nox.session(python=PYTHON_VERSIONS_SYNC) +def requests(session): + session.install(LIBRARY_DIR) + session.install(*TEST_DEPENDENCIES_SYNC) + session.env[EXPLICIT_CREDENTIALS_ENV] = SERVICE_ACCOUNT_FILE + session.run("pytest", "system_tests_sync/test_requests.py") + + +@nox.session(python=PYTHON_VERSIONS_SYNC) +def urllib3(session): + session.install(LIBRARY_DIR) + session.install(*TEST_DEPENDENCIES_SYNC) + session.env[EXPLICIT_CREDENTIALS_ENV] = SERVICE_ACCOUNT_FILE + session.run("pytest", "system_tests_sync/test_urllib3.py") + + @nox.session(python=PYTHON_VERSIONS_SYNC) def mtls_http(session): session.install(LIBRARY_DIR) @@ -300,6 +316,7 @@ def mtls_http(session): session.env[EXPLICIT_CREDENTIALS_ENV] = SERVICE_ACCOUNT_FILE session.run("pytest", "system_tests_sync/test_mtls_http.py") + #ASYNC SYSTEM TESTS @nox.session(python=PYTHON_VERSIONS_ASYNC) diff --git a/packages/google-auth/system_tests/system_tests_sync/test_grpc.py b/packages/google-auth/system_tests/system_tests_sync/test_grpc.py index da2eb71fb449..7f548ec0e9b3 100644 --- a/packages/google-auth/system_tests/system_tests_sync/test_grpc.py +++ b/packages/google-auth/system_tests/system_tests_sync/test_grpc.py @@ -57,8 +57,9 @@ def test_grpc_request_with_regular_credentials_and_self_signed_jwt(http_request) list_topics_iter = client.list_topics(project="projects/{}".format(project_id)) list(list_topics_iter) - # Check that self-signed JWT was created + # Check that self-signed JWT was created and is being used assert credentials._jwt_credentials is not None + assert credentials._jwt_credentials.token == credentials.token def test_grpc_request_with_jwt_credentials(): diff --git a/packages/google-auth/system_tests/system_tests_sync/test_requests.py b/packages/google-auth/system_tests/system_tests_sync/test_requests.py new file mode 100644 index 000000000000..3ac9179b5072 --- /dev/null +++ b/packages/google-auth/system_tests/system_tests_sync/test_requests.py @@ -0,0 +1,40 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import google.auth +import google.auth.credentials +import google.auth.transport.requests +from google.oauth2 import service_account + + +def test_authorized_session_with_service_account_and_self_signed_jwt(): + credentials, project_id = google.auth.default() + + credentials = credentials.with_scopes( + scopes=[], + default_scopes=["https://www.googleapis.com/auth/pubsub"], + ) + + session = google.auth.transport.requests.AuthorizedSession( + credentials=credentials, default_host="pubsub.googleapis.com" + ) + + # List Pub/Sub Topics through the REST API + # https://cloud.google.com/pubsub/docs/reference/rest/v1/projects.topics/list + response = session.get("https://pubsub.googleapis.com/v1/projects/{}/topics".format(project_id)) + response.raise_for_status() + + # Check that self-signed JWT was created and is being used + assert credentials._jwt_credentials is not None + assert credentials._jwt_credentials.token == credentials.token diff --git a/packages/google-auth/system_tests/system_tests_sync/test_urllib3.py b/packages/google-auth/system_tests/system_tests_sync/test_urllib3.py new file mode 100644 index 000000000000..1932e1913339 --- /dev/null +++ b/packages/google-auth/system_tests/system_tests_sync/test_urllib3.py @@ -0,0 +1,44 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import google.auth +import google.auth.credentials +import google.auth.transport.requests +from google.oauth2 import service_account + + +def test_authorized_session_with_service_account_and_self_signed_jwt(): + credentials, project_id = google.auth.default() + + credentials = credentials.with_scopes( + scopes=[], + default_scopes=["https://www.googleapis.com/auth/pubsub"], + ) + + http = google.auth.transport.urllib3.AuthorizedHttp( + credentials=credentials, default_host="pubsub.googleapis.com" + ) + + # List Pub/Sub Topics through the REST API + # https://cloud.google.com/pubsub/docs/reference/rest/v1/projects.topics/list + response = http.urlopen( + method="GET", + url="https://pubsub.googleapis.com/v1/projects/{}/topics".format(project_id) + ) + + assert response.status == 200 + + # Check that self-signed JWT was created and is being used + assert credentials._jwt_credentials is not None + assert credentials._jwt_credentials.token == credentials.token diff --git a/packages/google-auth/tests/transport/test_requests.py b/packages/google-auth/tests/transport/test_requests.py index d56c2be5599c..3fdd17c3e480 100644 --- a/packages/google-auth/tests/transport/test_requests.py +++ b/packages/google-auth/tests/transport/test_requests.py @@ -30,6 +30,7 @@ import google.auth.credentials import google.auth.transport._mtls_helper import google.auth.transport.requests +from google.oauth2 import service_account from tests.transport import compliance @@ -372,6 +373,25 @@ def test_request_timeout_w_refresh_timeout_timeout_error(self, frozen_time): "GET", self.TEST_URL, timeout=60, max_allowed_time=2.9 ) + def test_authorized_session_without_default_host(self): + credentials = mock.create_autospec(service_account.Credentials) + + authed_session = google.auth.transport.requests.AuthorizedSession(credentials) + + authed_session.credentials._create_self_signed_jwt.assert_not_called() + + def test_authorized_session_with_default_host(self): + default_host = "pubsub.googleapis.com" + credentials = mock.create_autospec(service_account.Credentials) + + authed_session = google.auth.transport.requests.AuthorizedSession( + credentials, default_host=default_host + ) + + authed_session.credentials._create_self_signed_jwt.assert_called_once_with( + "https://{}/".format(default_host) + ) + def test_configure_mtls_channel_with_callback(self): mock_callback = mock.Mock() mock_callback.return_value = ( diff --git a/packages/google-auth/tests/transport/test_urllib3.py b/packages/google-auth/tests/transport/test_urllib3.py index 29561f6d6571..7c0693476074 100644 --- a/packages/google-auth/tests/transport/test_urllib3.py +++ b/packages/google-auth/tests/transport/test_urllib3.py @@ -26,6 +26,7 @@ import google.auth.credentials import google.auth.transport._mtls_helper import google.auth.transport.urllib3 +from google.oauth2 import service_account from tests.transport import compliance @@ -158,6 +159,25 @@ def test_urlopen_refresh(self): ("GET", self.TEST_URL, None, {"authorization": "token1"}, {}), ] + def test_urlopen_no_default_host(self): + credentials = mock.create_autospec(service_account.Credentials) + + authed_http = google.auth.transport.urllib3.AuthorizedHttp(credentials) + + authed_http.credentials._create_self_signed_jwt.assert_not_called() + + def test_urlopen_with_default_host(self): + default_host = "pubsub.googleapis.com" + credentials = mock.create_autospec(service_account.Credentials) + + authed_http = google.auth.transport.urllib3.AuthorizedHttp( + credentials, default_host=default_host + ) + + authed_http.credentials._create_self_signed_jwt.assert_called_once_with( + "https://{}/".format(default_host) + ) + def test_proxies(self): http = mock.create_autospec(urllib3.PoolManager) authed_http = google.auth.transport.urllib3.AuthorizedHttp(None, http=http) From 369934f7bf7a9b48023fd03d4e4143c880c683de Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Wed, 3 Feb 2021 17:17:14 -0700 Subject: [PATCH 375/966] chore: release 1.25.0 (#678) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 8 ++++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 3a06300cf0dc..88bb81b247d3 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,14 @@ [1]: https://pypi.org/project/google-auth/#history +## [1.25.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.24.0...v1.25.0) (2021-02-03) + + +### Features + +* support self-signed jwt in requests and urllib3 transports ([#679](https://www.github.com/googleapis/google-auth-library-python/issues/679)) ([7a94acb](https://www.github.com/googleapis/google-auth-library-python/commit/7a94acb50e75fe0a51688e0f968bca3fa9bd9082)) +* use self-signed jwt for service account ([#665](https://www.github.com/googleapis/google-auth-library-python/issues/665)) ([bf5ce0c](https://www.github.com/googleapis/google-auth-library-python/commit/bf5ce0c56c10f655ced6630653f0f2ad47fcceeb)) + ## [1.24.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.23.0...v1.24.0) (2020-12-11) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 3006d9ace3eb..ba72c3ccf782 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -34,7 +34,7 @@ with io.open("README.rst", "r") as fh: long_description = fh.read() -version = "1.24.0" +version = "1.25.0" setup( name="google-auth", From 5dc66b48ddc4d0a2606b8518bcf0a2ba9dbe6223 Mon Sep 17 00:00:00 2001 From: bojeil-google Date: Tue, 9 Feb 2021 11:05:00 -0800 Subject: [PATCH 376/966] feat: workload identity federation support (#686) Using workload identity federation, applications can access Google Cloud resources from Amazon Web Services (AWS), Microsoft Azure or any identity provider that supports OpenID Connect (OIDC). Workload identity federation is recommended for non-Google Cloud environments as it avoids the need to download, manage and store service account private keys locally. --- packages/google-auth/docs/index.rst | 2 + .../docs/reference/google.auth.aws.rst | 7 + .../google.auth.external_account.rst | 7 + .../reference/google.auth.identity_pool.rst | 7 + .../docs/reference/google.auth.rst | 3 + .../docs/reference/google.oauth2.rst | 2 + .../docs/reference/google.oauth2.sts.rst | 7 + .../docs/reference/google.oauth2.utils.rst | 7 + packages/google-auth/docs/user-guide.rst | 173 +- packages/google-auth/google/auth/_default.py | 146 +- packages/google-auth/google/auth/aws.py | 714 ++++++++ .../google/auth/environment_vars.py | 10 + .../google-auth/google/auth/exceptions.py | 5 + .../google/auth/external_account.py | 305 ++++ .../google-auth/google/auth/identity_pool.py | 279 ++++ .../google/auth/impersonated_credentials.py | 16 +- packages/google-auth/google/oauth2/sts.py | 155 ++ packages/google-auth/google/oauth2/utils.py | 171 ++ packages/google-auth/noxfile.py | 2 - .../system_tests_sync/secrets.tar.enc | Bin 0 -> 10323 bytes .../tests/data/external_subject_token.json | 3 + .../tests/data/external_subject_token.txt | 1 + packages/google-auth/tests/oauth2/test_sts.py | 395 +++++ .../google-auth/tests/oauth2/test_utils.py | 264 +++ packages/google-auth/tests/test__default.py | 219 ++- packages/google-auth/tests/test_aws.py | 1434 +++++++++++++++++ .../tests/test_external_account.py | 1095 +++++++++++++ .../google-auth/tests/test_identity_pool.py | 873 ++++++++++ .../tests/test_impersonated_credentials.py | 64 + 29 files changed, 6344 insertions(+), 22 deletions(-) create mode 100644 packages/google-auth/docs/reference/google.auth.aws.rst create mode 100644 packages/google-auth/docs/reference/google.auth.external_account.rst create mode 100644 packages/google-auth/docs/reference/google.auth.identity_pool.rst create mode 100644 packages/google-auth/docs/reference/google.oauth2.sts.rst create mode 100644 packages/google-auth/docs/reference/google.oauth2.utils.rst create mode 100644 packages/google-auth/google/auth/aws.py create mode 100644 packages/google-auth/google/auth/external_account.py create mode 100644 packages/google-auth/google/auth/identity_pool.py create mode 100644 packages/google-auth/google/oauth2/sts.py create mode 100644 packages/google-auth/google/oauth2/utils.py create mode 100644 packages/google-auth/system_tests/system_tests_sync/secrets.tar.enc create mode 100644 packages/google-auth/tests/data/external_subject_token.json create mode 100644 packages/google-auth/tests/data/external_subject_token.txt create mode 100644 packages/google-auth/tests/oauth2/test_sts.py create mode 100644 packages/google-auth/tests/oauth2/test_utils.py create mode 100644 packages/google-auth/tests/test_aws.py create mode 100644 packages/google-auth/tests/test_external_account.py create mode 100644 packages/google-auth/tests/test_identity_pool.py diff --git a/packages/google-auth/docs/index.rst b/packages/google-auth/docs/index.rst index 4287c3db3eb5..17169109a4df 100644 --- a/packages/google-auth/docs/index.rst +++ b/packages/google-auth/docs/index.rst @@ -20,6 +20,8 @@ also provides integration with several HTTP libraries. - Support for Google :mod:`Impersonated Credentials `. - Support for :mod:`Google Compute Engine credentials `. - Support for :mod:`Google App Engine standard credentials `. +- Support for :mod:`Identity Pool credentials `. +- Support for :mod:`AWS credentials `. - Support for various transports, including :mod:`Requests `, :mod:`urllib3 `, and diff --git a/packages/google-auth/docs/reference/google.auth.aws.rst b/packages/google-auth/docs/reference/google.auth.aws.rst new file mode 100644 index 000000000000..9c3966bbad73 --- /dev/null +++ b/packages/google-auth/docs/reference/google.auth.aws.rst @@ -0,0 +1,7 @@ +google.auth.aws module +====================== + +.. automodule:: google.auth.aws + :members: + :inherited-members: + :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.auth.external_account.rst b/packages/google-auth/docs/reference/google.auth.external_account.rst new file mode 100644 index 000000000000..0681eaa277a2 --- /dev/null +++ b/packages/google-auth/docs/reference/google.auth.external_account.rst @@ -0,0 +1,7 @@ +google.auth.external\_account module +==================================== + +.. automodule:: google.auth.external_account + :members: + :inherited-members: + :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.auth.identity_pool.rst b/packages/google-auth/docs/reference/google.auth.identity_pool.rst new file mode 100644 index 000000000000..48d9902236dd --- /dev/null +++ b/packages/google-auth/docs/reference/google.auth.identity_pool.rst @@ -0,0 +1,7 @@ +google.auth.identity\_pool module +================================= + +.. automodule:: google.auth.identity_pool + :members: + :inherited-members: + :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.auth.rst b/packages/google-auth/docs/reference/google.auth.rst index 3acf7dfb8d99..e21eaf9e31df 100644 --- a/packages/google-auth/docs/reference/google.auth.rst +++ b/packages/google-auth/docs/reference/google.auth.rst @@ -23,11 +23,14 @@ Submodules :maxdepth: 4 google.auth.app_engine + google.auth.aws google.auth.credentials google.auth._credentials_async google.auth.environment_vars google.auth.exceptions + google.auth.external_account google.auth.iam + google.auth.identity_pool google.auth.impersonated_credentials google.auth.jwt google.auth.jwt_async diff --git a/packages/google-auth/docs/reference/google.oauth2.rst b/packages/google-auth/docs/reference/google.oauth2.rst index 6f3ba50c217c..2a8a7a5885d2 100644 --- a/packages/google-auth/docs/reference/google.oauth2.rst +++ b/packages/google-auth/docs/reference/google.oauth2.rst @@ -17,3 +17,5 @@ Submodules google.oauth2.id_token google.oauth2.service_account google.oauth2._service_account_async + google.oauth2.sts + google.oauth2.utils diff --git a/packages/google-auth/docs/reference/google.oauth2.sts.rst b/packages/google-auth/docs/reference/google.oauth2.sts.rst new file mode 100644 index 000000000000..49d99dfe66f0 --- /dev/null +++ b/packages/google-auth/docs/reference/google.oauth2.sts.rst @@ -0,0 +1,7 @@ +google.oauth2.sts module +======================== + +.. automodule:: google.oauth2.sts + :members: + :inherited-members: + :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.oauth2.utils.rst b/packages/google-auth/docs/reference/google.oauth2.utils.rst new file mode 100644 index 000000000000..5b039eac825c --- /dev/null +++ b/packages/google-auth/docs/reference/google.oauth2.utils.rst @@ -0,0 +1,7 @@ +google.oauth2.utils module +========================== + +.. automodule:: google.oauth2.utils + :members: + :inherited-members: + :show-inheritance: diff --git a/packages/google-auth/docs/user-guide.rst b/packages/google-auth/docs/user-guide.rst index 08e7167dfe53..7332bd48e0b1 100644 --- a/packages/google-auth/docs/user-guide.rst +++ b/packages/google-auth/docs/user-guide.rst @@ -7,8 +7,8 @@ Credentials and account types ----------------------------- :class:`~credentials.Credentials` are the means of identifying an application or -user to a service or API. Credentials can be obtained with two different types -of accounts: *service accounts* and *user accounts*. +user to a service or API. Credentials can be obtained with three different types +of accounts: *service accounts*, *user accounts* and *external accounts*. Credentials from service accounts identify a particular application. These types of credentials are used in server-to-server use cases, such as accessing a @@ -21,6 +21,11 @@ a user's documents in Google Drive. This library provides no support for obtaining user credentials, but does provide limited support for using user credentials. +Credentials from external accounts (workload identity federation) are used to +identify a particular application from an on-prem or non-Google Cloud platform +including Amazon Web Services (AWS), Microsoft Azure or any identity provider +that supports OpenID Connect (OIDC). + Obtaining credentials --------------------- @@ -44,6 +49,13 @@ If your application requires specific scopes:: credentials, project = google.auth.default( scopes=['https://www.googleapis.com/auth/cloud-platform']) +Application Default Credentials also support workload identity federation to +access Google Cloud resources from non-Google Cloud platforms including Amazon +Web Services (AWS), Microsoft Azure or any identity provider that supports +OpenID Connect (OIDC). Workload identity federation is recommended for +non-Google Cloud environments as it avoids the need to download, manage and +store service account private keys locally. + .. _Google Application Default Credentials: https://developers.google.com/identity/protocols/ application-default-credentials @@ -219,6 +231,163 @@ You can also use :class:`google_auth_oauthlib.flow.Flow` to perform the OAuth .. _requests-oauthlib: https://requests-oauthlib.readthedocs.io/en/latest/ +External credentials (Workload identity federation) ++++++++++++++++++++++++++++++++++++++++++++++++++++ + +Using workload identity federation, your application can access Google Cloud +resources from Amazon Web Services (AWS), Microsoft Azure or any identity +provider that supports OpenID Connect (OIDC). + +Traditionally, applications running outside Google Cloud have used service +account keys to access Google Cloud resources. Using identity federation, +you can allow your workload to impersonate a service account. +This lets you access Google Cloud resources directly, eliminating the +maintenance and security burden associated with service account keys. + +Accessing resources from AWS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In order to access Google Cloud resources from Amazon Web Services (AWS), the +following requirements are needed: + +- A workload identity pool needs to be created. +- AWS needs to be added as an identity provider in the workload identity pool + (The Google organization policy needs to allow federation from AWS). +- Permission to impersonate a service account needs to be granted to the + external identity. +- A credential configuration file needs to be generated. Unlike service account + credential files, the generated credential configuration file will only + contain non-sensitive metadata to instruct the library on how to retrieve + external subject tokens and exchange them for service account access tokens. + +Follow the detailed instructions on how to +`Configure Workload Identity Federation from AWS`_. + +.. _Configure Workload Identity Federation from AWS: + https://cloud.google.com/iam/docs/access-resources-aws + +Accessing resources from Microsoft Azure +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In order to access Google Cloud resources from Microsoft Azure, the following +requirements are needed: + +- A workload identity pool needs to be created. +- Azure needs to be added as an identity provider in the workload identity pool + (The Google organization policy needs to allow federation from Azure). +- The Azure tenant needs to be configured for identity federation. +- Permission to impersonate a service account needs to be granted to the + external identity. +- A credential configuration file needs to be generated. Unlike service account + credential files, the generated credential configuration file will only + contain non-sensitive metadata to instruct the library on how to retrieve + external subject tokens and exchange them for service account access tokens. + +Follow the detailed instructions on how to +`Configure Workload Identity Federation from Microsoft Azure`_. + +.. _Configure Workload Identity Federation from Microsoft Azure: + https://cloud.google.com/iam/docs/access-resources-azure + +Accessing resources from an OIDC identity provider +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In order to access Google Cloud resources from an identity provider that +supports `OpenID Connect (OIDC)`_, the following requirements are needed: + +- A workload identity pool needs to be created. +- An OIDC identity provider needs to be added in the workload identity pool + (The Google organization policy needs to allow federation from the identity + provider). +- Permission to impersonate a service account needs to be granted to the + external identity. +- A credential configuration file needs to be generated. Unlike service account + credential files, the generated credential configuration file will only + contain non-sensitive metadata to instruct the library on how to retrieve + external subject tokens and exchange them for service account access tokens. + +For OIDC providers, the Auth library can retrieve OIDC tokens either from a +local file location (file-sourced credentials) or from a local server +(URL-sourced credentials). + +- For file-sourced credentials, a background process needs to be continuously + refreshing the file location with a new OIDC token prior to expiration. + For tokens with one hour lifetimes, the token needs to be updated in the file + every hour. The token can be stored directly as plain text or in JSON format. +- For URL-sourced credentials, a local server needs to host a GET endpoint to + return the OIDC token. The response can be in plain text or JSON. + Additional required request headers can also be specified. + +Follow the detailed instructions on how to +`Configure Workload Identity Federation from an OIDC identity provider`_. + +.. _OpenID Connect (OIDC): + https://openid.net/connect/ +.. _Configure Workload Identity Federation from an OIDC identity provider: + https://cloud.google.com/iam/docs/access-resources-oidc + +Using External Identities +~~~~~~~~~~~~~~~~~~~~~~~~~ + +External identities (AWS, Azure and OIDC identity providers) can be used with +Application Default Credentials. +In order to use external identities with Application Default Credentials, you +need to generate the JSON credentials configuration file for your external +identity. +Once generated, store the path to this file in the +``GOOGLE_APPLICATION_CREDENTIALS`` environment variable. + +.. code-block:: bash + + $ export GOOGLE_APPLICATION_CREDENTIALS=/path/to/config.json + +The library can now automatically choose the right type of client and initialize +credentials from the context provided in the configuration file:: + + import google.auth + + credentials, project = google.auth.default() + +When using external identities with Application Default Credentials, +the ``roles/browser`` role needs to be granted to the service account. +The ``Cloud Resource Manager API`` should also be enabled on the project. +This is needed since :func:`default` will try to auto-discover the project ID +from the current environment using the impersonated credential. +Otherwise, the project ID will resolve to ``None``. You can override the project +detection by setting the ``GOOGLE_CLOUD_PROJECT`` environment variable. + +You can also explicitly initialize external account clients using the generated +configuration file. + +For Azure and OIDC providers, use :meth:`identity_pool.Credentials.from_info +` or +:meth:`identity_pool.Credentials.from_file +`:: + + import json + + from google.auth import identity_pool + + json_config_info = json.loads(function_to_get_json_config()) + credentials = identity_pool.Credentials.from_info(json_config_info) + scoped_credentials = credentials.with_scopes( + ['https://www.googleapis.com/auth/cloud-platform']) + +For AWS providers, use :meth:`aws.Credentials.from_info +` or +:meth:`aws.Credentials.from_file +`:: + + import json + + from google.auth import aws + + json_config_info = json.loads(function_to_get_json_config()) + credentials = aws.Credentials.from_info(json_config_info) + scoped_credentials = credentials.with_scopes( + ['https://www.googleapis.com/auth/cloud-platform']) + + Impersonated credentials ++++++++++++++++++++++++ diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index 3b8c281e72b6..836c33915bed 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -34,7 +34,8 @@ # Valid types accepted for file-based credentials. _AUTHORIZED_USER_TYPE = "authorized_user" _SERVICE_ACCOUNT_TYPE = "service_account" -_VALID_TYPES = (_AUTHORIZED_USER_TYPE, _SERVICE_ACCOUNT_TYPE) +_EXTERNAL_ACCOUNT_TYPE = "external_account" +_VALID_TYPES = (_AUTHORIZED_USER_TYPE, _SERVICE_ACCOUNT_TYPE, _EXTERNAL_ACCOUNT_TYPE) # Help message when no credentials can be found. _HELP_MESSAGE = """\ @@ -70,12 +71,12 @@ def _warn_about_problematic_credentials(credentials): def load_credentials_from_file( - filename, scopes=None, default_scopes=None, quota_project_id=None + filename, scopes=None, default_scopes=None, quota_project_id=None, request=None ): """Loads Google credentials from a file. - The credentials file must be a service account key or stored authorized - user credentials. + The credentials file must be a service account key, stored authorized + user credentials or external account credentials. Args: filename (str): The full path to the credentials file. @@ -85,12 +86,18 @@ def load_credentials_from_file( default_scopes (Optional[Sequence[str]]): Default scopes passed by a Google client library. Use 'scopes' for user-defined scopes. quota_project_id (Optional[str]): The project ID used for - quota and billing. + quota and billing. + request (Optional[google.auth.transport.Request]): An object used to make + HTTP requests. This is used to determine the associated project ID + for a workload identity pool resource (external account credentials). + If not specified, then it will use a + google.auth.transport.requests.Request client to make requests. Returns: Tuple[google.auth.credentials.Credentials, Optional[str]]: Loaded credentials and the project ID. Authorized user credentials do not - have the project ID information. + have the project ID information. External account credentials project + IDs may not always be determined. Raises: google.auth.exceptions.DefaultCredentialsError: if the file is in the @@ -146,6 +153,18 @@ def load_credentials_from_file( credentials = credentials.with_quota_project(quota_project_id) return credentials, info.get("project_id") + elif credential_type == _EXTERNAL_ACCOUNT_TYPE: + credentials, project_id = _get_external_account_credentials( + info, + filename, + scopes=scopes, + default_scopes=default_scopes, + request=request, + ) + if quota_project_id: + credentials = credentials.with_quota_project(quota_project_id) + return credentials, project_id + else: raise exceptions.DefaultCredentialsError( "The file {file} does not have a valid type. " @@ -176,9 +195,28 @@ def _get_gcloud_sdk_credentials(): return credentials, project_id -def _get_explicit_environ_credentials(): +def _get_explicit_environ_credentials(request=None, scopes=None, default_scopes=None): """Gets credentials from the GOOGLE_APPLICATION_CREDENTIALS environment - variable.""" + variable. + + Args: + request (Optional[google.auth.transport.Request]): An object used to make + HTTP requests. This is used to determine the associated project ID + for a workload identity pool resource (external account credentials). + If not specified, then it will use a + google.auth.transport.requests.Request client to make requests. + scopes (Optional[Sequence[str]]): The list of scopes for the credentials. If + specified, the credentials will automatically be scoped if + necessary. + default_scopes (Optional[Sequence[str]]): Default scopes passed by a + Google client library. Use 'scopes' for user-defined scopes. + + Returns: + Tuple[Optional[google.auth.credentials.Credentials], Optional[str]]: Loaded + credentials and the project ID. Authorized user credentials do not + have the project ID information. External account credentials project + IDs may not always be determined. + """ explicit_file = os.environ.get(environment_vars.CREDENTIALS) _LOGGER.debug( @@ -187,7 +225,11 @@ def _get_explicit_environ_credentials(): if explicit_file is not None: credentials, project_id = load_credentials_from_file( - os.environ[environment_vars.CREDENTIALS] + os.environ[environment_vars.CREDENTIALS], + scopes=scopes, + default_scopes=default_scopes, + quota_project_id=None, + request=request, ) return credentials, project_id @@ -252,6 +294,65 @@ def _get_gce_credentials(request=None): return None, None +def _get_external_account_credentials( + info, filename, scopes=None, default_scopes=None, request=None +): + """Loads external account Credentials from the parsed external account info. + + The credentials information must correspond to a supported external account + credentials. + + Args: + info (Mapping[str, str]): The external account info in Google format. + filename (str): The full path to the credentials file. + scopes (Optional[Sequence[str]]): The list of scopes for the credentials. If + specified, the credentials will automatically be scoped if + necessary. + default_scopes (Optional[Sequence[str]]): Default scopes passed by a + Google client library. Use 'scopes' for user-defined scopes. + request (Optional[google.auth.transport.Request]): An object used to make + HTTP requests. This is used to determine the associated project ID + for a workload identity pool resource (external account credentials). + If not specified, then it will use a + google.auth.transport.requests.Request client to make requests. + + Returns: + Tuple[google.auth.credentials.Credentials, Optional[str]]: Loaded + credentials and the project ID. External account credentials project + IDs may not always be determined. + + Raises: + google.auth.exceptions.DefaultCredentialsError: if the info dictionary + is in the wrong format or is missing required information. + """ + # There are currently 2 types of external_account credentials. + try: + # Check if configuration corresponds to an AWS credentials. + from google.auth import aws + + credentials = aws.Credentials.from_info( + info, scopes=scopes, default_scopes=default_scopes + ) + except ValueError: + try: + # Check if configuration corresponds to an Identity Pool credentials. + from google.auth import identity_pool + + credentials = identity_pool.Credentials.from_info( + info, scopes=scopes, default_scopes=default_scopes + ) + except ValueError: + # If the configuration is invalid or does not correspond to any + # supported external_account credentials, raise an error. + raise exceptions.DefaultCredentialsError( + "Failed to load external account credentials from {}".format(filename) + ) + if request is None: + request = google.auth.transport.requests.Request() + + return credentials, credentials.get_project_id(request=request) + + def default(scopes=None, request=None, quota_project_id=None, default_scopes=None): """Gets the default credentials for the current environment. @@ -265,6 +366,15 @@ def default(scopes=None, request=None, quota_project_id=None, default_scopes=Non loaded and returned. The project ID returned is the project ID defined in the service account file if available (some older files do not contain project ID information). + + If the environment variable is set to the path of a valid external + account JSON configuration file (workload identity federation), then the + configuration file is used to determine and retrieve the external + credentials from the current environment (AWS, Azure, etc). + These will then be exchanged for Google access tokens via the Google STS + endpoint. + The project ID returned in this case is the one corresponding to the + underlying workload identity pool resource if determinable. 2. If the `Google Cloud SDK`_ is installed and has application default credentials set they are loaded and returned. @@ -310,11 +420,15 @@ def default(scopes=None, request=None, quota_project_id=None, default_scopes=Non scopes (Sequence[str]): The list of scopes for the credentials. If specified, the credentials will automatically be scoped if necessary. - request (google.auth.transport.Request): An object used to make - HTTP requests. This is used to detect whether the application - is running on Compute Engine. If not specified, then it will - use the standard library http client to make requests. - quota_project_id (Optional[str]): The project ID used for + request (Optional[google.auth.transport.Request]): An object used to make + HTTP requests. This is used to either detect whether the application + is running on Compute Engine or to determine the associated project + ID for a workload identity pool resource (external account + credentials). If not specified, then it will either use the standard + library http client to make requests for Compute Engine credentials + or a google.auth.transport.requests.Request client for external + account credentials. + quota_project_id (Optional[str]): The project ID used for quota and billing. default_scopes (Optional[Sequence[str]]): Default scopes passed by a Google client library. Use 'scopes' for user-defined scopes. @@ -336,7 +450,9 @@ def default(scopes=None, request=None, quota_project_id=None, default_scopes=Non ) checkers = ( - _get_explicit_environ_credentials, + lambda: _get_explicit_environ_credentials( + request=request, scopes=scopes, default_scopes=default_scopes + ), _get_gcloud_sdk_credentials, _get_gae_credentials, lambda: _get_gce_credentials(request), diff --git a/packages/google-auth/google/auth/aws.py b/packages/google-auth/google/auth/aws.py new file mode 100644 index 000000000000..b362dd315299 --- /dev/null +++ b/packages/google-auth/google/auth/aws.py @@ -0,0 +1,714 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""AWS Credentials and AWS Signature V4 Request Signer. + +This module provides credentials to access Google Cloud resources from Amazon +Web Services (AWS) workloads. These credentials are recommended over the +use of service account credentials in AWS as they do not involve the management +of long-live service account private keys. + +AWS Credentials are initialized using external_account arguments which are +typically loaded from the external credentials JSON file. +Unlike other Credentials that can be initialized with a list of explicit +arguments, secrets or credentials, external account clients use the +environment and hints/guidelines provided by the external_account JSON +file to retrieve credentials and exchange them for Google access tokens. + +This module also provides a basic implementation of the +`AWS Signature Version 4`_ request signing algorithm. + +AWS Credentials use serialized signed requests to the +`AWS STS GetCallerIdentity`_ API that can be exchanged for Google access tokens +via the GCP STS endpoint. + +.. _AWS Signature Version 4: https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html +.. _AWS STS GetCallerIdentity: https://docs.aws.amazon.com/STS/latest/APIReference/API_GetCallerIdentity.html +""" + +import hashlib +import hmac +import io +import json +import os +import re + +from six.moves import http_client +from six.moves import urllib + +from google.auth import _helpers +from google.auth import environment_vars +from google.auth import exceptions +from google.auth import external_account + +# AWS Signature Version 4 signing algorithm identifier. +_AWS_ALGORITHM = "AWS4-HMAC-SHA256" +# The termination string for the AWS credential scope value as defined in +# https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html +_AWS_REQUEST_TYPE = "aws4_request" +# The AWS authorization header name for the security session token if available. +_AWS_SECURITY_TOKEN_HEADER = "x-amz-security-token" +# The AWS authorization header name for the auto-generated date. +_AWS_DATE_HEADER = "x-amz-date" + + +class RequestSigner(object): + """Implements an AWS request signer based on the AWS Signature Version 4 signing + process. + https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html + """ + + def __init__(self, region_name): + """Instantiates an AWS request signer used to compute authenticated signed + requests to AWS APIs based on the AWS Signature Version 4 signing process. + + Args: + region_name (str): The AWS region to use. + """ + + self._region_name = region_name + + def get_request_options( + self, + aws_security_credentials, + url, + method, + request_payload="", + additional_headers={}, + ): + """Generates the signed request for the provided HTTP request for calling + an AWS API. This follows the steps described at: + https://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html + + Args: + aws_security_credentials (Mapping[str, str]): A dictionary containing + the AWS security credentials. + url (str): The AWS service URL containing the canonical URI and + query string. + method (str): The HTTP method used to call this API. + request_payload (Optional[str]): The optional request payload if + available. + additional_headers (Optional[Mapping[str, str]]): The optional + additional headers needed for the requested AWS API. + + Returns: + Mapping[str, str]: The AWS signed request dictionary object. + """ + # Get AWS credentials. + access_key = aws_security_credentials.get("access_key_id") + secret_key = aws_security_credentials.get("secret_access_key") + security_token = aws_security_credentials.get("security_token") + + additional_headers = additional_headers or {} + + uri = urllib.parse.urlparse(url) + # Validate provided URL. + if not uri.hostname or uri.scheme != "https": + raise ValueError("Invalid AWS service URL") + + header_map = _generate_authentication_header_map( + host=uri.hostname, + canonical_uri=os.path.normpath(uri.path or "/"), + canonical_querystring=_get_canonical_querystring(uri.query), + method=method, + region=self._region_name, + access_key=access_key, + secret_key=secret_key, + security_token=security_token, + request_payload=request_payload, + additional_headers=additional_headers, + ) + headers = { + "Authorization": header_map.get("authorization_header"), + "host": uri.hostname, + } + # Add x-amz-date if available. + if "amz_date" in header_map: + headers[_AWS_DATE_HEADER] = header_map.get("amz_date") + # Append additional optional headers, eg. X-Amz-Target, Content-Type, etc. + for key in additional_headers: + headers[key] = additional_headers[key] + + # Add session token if available. + if security_token is not None: + headers[_AWS_SECURITY_TOKEN_HEADER] = security_token + + signed_request = {"url": url, "method": method, "headers": headers} + if request_payload: + signed_request["data"] = request_payload + return signed_request + + +def _get_canonical_querystring(query): + """Generates the canonical query string given a raw query string. + Logic is based on + https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html + + Args: + query (str): The raw query string. + + Returns: + str: The canonical query string. + """ + # Parse raw query string. + querystring = urllib.parse.parse_qs(query) + querystring_encoded_map = {} + for key in querystring: + quote_key = urllib.parse.quote(key, safe="-_.~") + # URI encode key. + querystring_encoded_map[quote_key] = [] + for item in querystring[key]: + # For each key, URI encode all values for that key. + querystring_encoded_map[quote_key].append( + urllib.parse.quote(item, safe="-_.~") + ) + # Sort values for each key. + querystring_encoded_map[quote_key].sort() + # Sort keys. + sorted_keys = list(querystring_encoded_map.keys()) + sorted_keys.sort() + # Reconstruct the query string. Preserve keys with multiple values. + querystring_encoded_pairs = [] + for key in sorted_keys: + for item in querystring_encoded_map[key]: + querystring_encoded_pairs.append("{}={}".format(key, item)) + return "&".join(querystring_encoded_pairs) + + +def _sign(key, msg): + """Creates the HMAC-SHA256 hash of the provided message using the provided + key. + + Args: + key (str): The HMAC-SHA256 key to use. + msg (str): The message to hash. + + Returns: + str: The computed hash bytes. + """ + return hmac.new(key, msg.encode("utf-8"), hashlib.sha256).digest() + + +def _get_signing_key(key, date_stamp, region_name, service_name): + """Calculates the signing key used to calculate the signature for + AWS Signature Version 4 based on: + https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html + + Args: + key (str): The AWS secret access key. + date_stamp (str): The '%Y%m%d' date format. + region_name (str): The AWS region. + service_name (str): The AWS service name, eg. sts. + + Returns: + str: The signing key bytes. + """ + k_date = _sign(("AWS4" + key).encode("utf-8"), date_stamp) + k_region = _sign(k_date, region_name) + k_service = _sign(k_region, service_name) + k_signing = _sign(k_service, "aws4_request") + return k_signing + + +def _generate_authentication_header_map( + host, + canonical_uri, + canonical_querystring, + method, + region, + access_key, + secret_key, + security_token, + request_payload="", + additional_headers={}, +): + """Generates the authentication header map needed for generating the AWS + Signature Version 4 signed request. + + Args: + host (str): The AWS service URL hostname. + canonical_uri (str): The AWS service URL path name. + canonical_querystring (str): The AWS service URL query string. + method (str): The HTTP method used to call this API. + region (str): The AWS region. + access_key (str): The AWS access key ID. + secret_key (str): The AWS secret access key. + security_token (Optional[str]): The AWS security session token. This is + available for temporary sessions. + request_payload (Optional[str]): The optional request payload if + available. + additional_headers (Optional[Mapping[str, str]]): The optional + additional headers needed for the requested AWS API. + + Returns: + Mapping[str, str]: The AWS authentication header dictionary object. + This contains the x-amz-date and authorization header information. + """ + # iam.amazonaws.com host => iam service. + # sts.us-east-2.amazonaws.com host => sts service. + service_name = host.split(".")[0] + + current_time = _helpers.utcnow() + amz_date = current_time.strftime("%Y%m%dT%H%M%SZ") + date_stamp = current_time.strftime("%Y%m%d") + + # Change all additional headers to be lower case. + full_headers = {} + for key in additional_headers: + full_headers[key.lower()] = additional_headers[key] + # Add AWS session token if available. + if security_token is not None: + full_headers[_AWS_SECURITY_TOKEN_HEADER] = security_token + + # Required headers + full_headers["host"] = host + # Do not use generated x-amz-date if the date header is provided. + # Previously the date was not fixed with x-amz- and could be provided + # manually. + # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/get-header-value-trim.req + if "date" not in full_headers: + full_headers[_AWS_DATE_HEADER] = amz_date + + # Header keys need to be sorted alphabetically. + canonical_headers = "" + header_keys = list(full_headers.keys()) + header_keys.sort() + for key in header_keys: + canonical_headers = "{}{}:{}\n".format( + canonical_headers, key, full_headers[key] + ) + signed_headers = ";".join(header_keys) + + payload_hash = hashlib.sha256((request_payload or "").encode("utf-8")).hexdigest() + + # https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html + canonical_request = "{}\n{}\n{}\n{}\n{}\n{}".format( + method, + canonical_uri, + canonical_querystring, + canonical_headers, + signed_headers, + payload_hash, + ) + + credential_scope = "{}/{}/{}/{}".format( + date_stamp, region, service_name, _AWS_REQUEST_TYPE + ) + + # https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html + string_to_sign = "{}\n{}\n{}\n{}".format( + _AWS_ALGORITHM, + amz_date, + credential_scope, + hashlib.sha256(canonical_request.encode("utf-8")).hexdigest(), + ) + + # https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html + signing_key = _get_signing_key(secret_key, date_stamp, region, service_name) + signature = hmac.new( + signing_key, string_to_sign.encode("utf-8"), hashlib.sha256 + ).hexdigest() + + # https://docs.aws.amazon.com/general/latest/gr/sigv4-add-signature-to-request.html + authorization_header = "{} Credential={}/{}, SignedHeaders={}, Signature={}".format( + _AWS_ALGORITHM, access_key, credential_scope, signed_headers, signature + ) + + authentication_header = {"authorization_header": authorization_header} + # Do not use generated x-amz-date if the date header is provided. + if "date" not in full_headers: + authentication_header["amz_date"] = amz_date + return authentication_header + + +class Credentials(external_account.Credentials): + """AWS external account credentials. + This is used to exchange serialized AWS signature v4 signed requests to + AWS STS GetCallerIdentity service for Google access tokens. + """ + + def __init__( + self, + audience, + subject_token_type, + token_url, + credential_source=None, + service_account_impersonation_url=None, + client_id=None, + client_secret=None, + quota_project_id=None, + scopes=None, + default_scopes=None, + ): + """Instantiates an AWS workload external account credentials object. + + Args: + audience (str): The STS audience field. + subject_token_type (str): The subject token type. + token_url (str): The STS endpoint URL. + credential_source (Mapping): The credential source dictionary used + to provide instructions on how to retrieve external credential + to be exchanged for Google access tokens. + service_account_impersonation_url (Optional[str]): The optional + service account impersonation getAccessToken URL. + client_id (Optional[str]): The optional client ID. + client_secret (Optional[str]): The optional client secret. + quota_project_id (Optional[str]): The optional quota project ID. + scopes (Optional[Sequence[str]]): Optional scopes to request during + the authorization grant. + default_scopes (Optional[Sequence[str]]): Default scopes passed by a + Google client library. Use 'scopes' for user-defined scopes. + + Raises: + google.auth.exceptions.RefreshError: If an error is encountered during + access token retrieval logic. + ValueError: For invalid parameters. + + .. note:: Typically one of the helper constructors + :meth:`from_file` or + :meth:`from_info` are used instead of calling the constructor directly. + """ + super(Credentials, self).__init__( + audience=audience, + subject_token_type=subject_token_type, + token_url=token_url, + credential_source=credential_source, + service_account_impersonation_url=service_account_impersonation_url, + client_id=client_id, + client_secret=client_secret, + quota_project_id=quota_project_id, + scopes=scopes, + default_scopes=default_scopes, + ) + credential_source = credential_source or {} + self._environment_id = credential_source.get("environment_id") or "" + self._region_url = credential_source.get("region_url") + self._security_credentials_url = credential_source.get("url") + self._cred_verification_url = credential_source.get( + "regional_cred_verification_url" + ) + self._region = None + self._request_signer = None + self._target_resource = audience + + # Get the environment ID. Currently, only one version supported (v1). + matches = re.match(r"^(aws)([\d]+)$", self._environment_id) + if matches: + env_id, env_version = matches.groups() + else: + env_id, env_version = (None, None) + + if env_id != "aws" or self._cred_verification_url is None: + raise ValueError("No valid AWS 'credential_source' provided") + elif int(env_version or "") != 1: + raise ValueError( + "aws version '{}' is not supported in the current build.".format( + env_version + ) + ) + + def retrieve_subject_token(self, request): + """Retrieves the subject token using the credential_source object. + The subject token is a serialized `AWS GetCallerIdentity signed request`_. + + The logic is summarized as: + + Retrieve the AWS region from the AWS_REGION environment variable or from + the AWS metadata server availability-zone if not found in the + environment variable. + + Check AWS credentials in environment variables. If not found, retrieve + from the AWS metadata server security-credentials endpoint. + + When retrieving AWS credentials from the metadata server + security-credentials endpoint, the AWS role needs to be determined by + calling the security-credentials endpoint without any argument. Then the + credentials can be retrieved via: security-credentials/role_name + + Generate the signed request to AWS STS GetCallerIdentity action. + + Inject x-goog-cloud-target-resource into header and serialize the + signed request. This will be the subject-token to pass to GCP STS. + + .. _AWS GetCallerIdentity signed request: + https://cloud.google.com/iam/docs/access-resources-aws#exchange-token + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + Returns: + str: The retrieved subject token. + """ + # Initialize the request signer if not yet initialized after determining + # the current AWS region. + if self._request_signer is None: + self._region = self._get_region(request, self._region_url) + self._request_signer = RequestSigner(self._region) + + # Retrieve the AWS security credentials needed to generate the signed + # request. + aws_security_credentials = self._get_security_credentials(request) + # Generate the signed request to AWS STS GetCallerIdentity API. + # Use the required regional endpoint. Otherwise, the request will fail. + request_options = self._request_signer.get_request_options( + aws_security_credentials, + self._cred_verification_url.replace("{region}", self._region), + "POST", + ) + # The GCP STS endpoint expects the headers to be formatted as: + # [ + # {key: 'x-amz-date', value: '...'}, + # {key: 'Authorization', value: '...'}, + # ... + # ] + # And then serialized as: + # quote(json.dumps({ + # url: '...', + # method: 'POST', + # headers: [{key: 'x-amz-date', value: '...'}, ...] + # })) + request_headers = request_options.get("headers") + # The full, canonical resource name of the workload identity pool + # provider, with or without the HTTPS prefix. + # Including this header as part of the signature is recommended to + # ensure data integrity. + request_headers["x-goog-cloud-target-resource"] = self._target_resource + + # Serialize AWS signed request. + # Keeping inner keys in sorted order makes testing easier for Python + # versions <=3.5 as the stringified JSON string would have a predictable + # key order. + aws_signed_req = {} + aws_signed_req["url"] = request_options.get("url") + aws_signed_req["method"] = request_options.get("method") + aws_signed_req["headers"] = [] + # Reformat header to GCP STS expected format. + for key in sorted(request_headers.keys()): + aws_signed_req["headers"].append( + {"key": key, "value": request_headers[key]} + ) + + return urllib.parse.quote( + json.dumps(aws_signed_req, separators=(",", ":"), sort_keys=True) + ) + + def _get_region(self, request, url): + """Retrieves the current AWS region from either the AWS_REGION + environment variable or from the AWS metadata server. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + url (str): The AWS metadata server region URL. + + Returns: + str: The current AWS region. + + Raises: + google.auth.exceptions.RefreshError: If an error occurs while + retrieving the AWS region. + """ + # The AWS metadata server is not available in some AWS environments + # such as AWS lambda. Instead, it is available via environment + # variable. + env_aws_region = os.environ.get(environment_vars.AWS_REGION) + if env_aws_region is not None: + return env_aws_region + + if not self._region_url: + raise exceptions.RefreshError("Unable to determine AWS region") + response = request(url=self._region_url, method="GET") + + # Support both string and bytes type response.data. + response_body = ( + response.data.decode("utf-8") + if hasattr(response.data, "decode") + else response.data + ) + + if response.status != 200: + raise exceptions.RefreshError( + "Unable to retrieve AWS region", response_body + ) + + # This endpoint will return the region in format: us-east-2b. + # Only the us-east-2 part should be used. + return response_body[:-1] + + def _get_security_credentials(self, request): + """Retrieves the AWS security credentials required for signing AWS + requests from either the AWS security credentials environment variables + or from the AWS metadata server. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + + Returns: + Mapping[str, str]: The AWS security credentials dictionary object. + + Raises: + google.auth.exceptions.RefreshError: If an error occurs while + retrieving the AWS security credentials. + """ + + # Check environment variables for permanent credentials first. + # https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html + env_aws_access_key_id = os.environ.get(environment_vars.AWS_ACCESS_KEY_ID) + env_aws_secret_access_key = os.environ.get( + environment_vars.AWS_SECRET_ACCESS_KEY + ) + # This is normally not available for permanent credentials. + env_aws_session_token = os.environ.get(environment_vars.AWS_SESSION_TOKEN) + if env_aws_access_key_id and env_aws_secret_access_key: + return { + "access_key_id": env_aws_access_key_id, + "secret_access_key": env_aws_secret_access_key, + "security_token": env_aws_session_token, + } + + # Get role name. + role_name = self._get_metadata_role_name(request) + + # Get security credentials. + credentials = self._get_metadata_security_credentials(request, role_name) + + return { + "access_key_id": credentials.get("AccessKeyId"), + "secret_access_key": credentials.get("SecretAccessKey"), + "security_token": credentials.get("Token"), + } + + def _get_metadata_security_credentials(self, request, role_name): + """Retrieves the AWS security credentials required for signing AWS + requests from the AWS metadata server. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + role_name (str): The AWS role name required by the AWS metadata + server security_credentials endpoint in order to return the + credentials. + + Returns: + Mapping[str, str]: The AWS metadata server security credentials + response. + + Raises: + google.auth.exceptions.RefreshError: If an error occurs while + retrieving the AWS security credentials. + """ + headers = {"Content-Type": "application/json"} + response = request( + url="{}/{}".format(self._security_credentials_url, role_name), + method="GET", + headers=headers, + ) + + # support both string and bytes type response.data + response_body = ( + response.data.decode("utf-8") + if hasattr(response.data, "decode") + else response.data + ) + + if response.status != http_client.OK: + raise exceptions.RefreshError( + "Unable to retrieve AWS security credentials", response_body + ) + + credentials_response = json.loads(response_body) + + return credentials_response + + def _get_metadata_role_name(self, request): + """Retrieves the AWS role currently attached to the current AWS + workload by querying the AWS metadata server. This is needed for the + AWS metadata server security credentials endpoint in order to retrieve + the AWS security credentials needed to sign requests to AWS APIs. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + + Returns: + str: The AWS role name. + + Raises: + google.auth.exceptions.RefreshError: If an error occurs while + retrieving the AWS role name. + """ + if self._security_credentials_url is None: + raise exceptions.RefreshError( + "Unable to determine the AWS metadata server security credentials endpoint" + ) + response = request(url=self._security_credentials_url, method="GET") + + # support both string and bytes type response.data + response_body = ( + response.data.decode("utf-8") + if hasattr(response.data, "decode") + else response.data + ) + + if response.status != http_client.OK: + raise exceptions.RefreshError( + "Unable to retrieve AWS role name", response_body + ) + + return response_body + + @classmethod + def from_info(cls, info, **kwargs): + """Creates an AWS Credentials instance from parsed external account info. + + Args: + info (Mapping[str, str]): The AWS external account info in Google + format. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.aws.Credentials: The constructed credentials. + + Raises: + ValueError: For invalid parameters. + """ + return cls( + audience=info.get("audience"), + subject_token_type=info.get("subject_token_type"), + token_url=info.get("token_url"), + service_account_impersonation_url=info.get( + "service_account_impersonation_url" + ), + client_id=info.get("client_id"), + client_secret=info.get("client_secret"), + credential_source=info.get("credential_source"), + quota_project_id=info.get("quota_project_id"), + **kwargs + ) + + @classmethod + def from_file(cls, filename, **kwargs): + """Creates an AWS Credentials instance from an external account json file. + + Args: + filename (str): The path to the AWS external account json file. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.aws.Credentials: The constructed credentials. + """ + with io.open(filename, "r", encoding="utf-8") as json_file: + data = json.load(json_file) + return cls.from_info(data, **kwargs) diff --git a/packages/google-auth/google/auth/environment_vars.py b/packages/google-auth/google/auth/environment_vars.py index 46a8926646b9..416bab0c01ea 100644 --- a/packages/google-auth/google/auth/environment_vars.py +++ b/packages/google-auth/google/auth/environment_vars.py @@ -59,3 +59,13 @@ The default value is false. Users have to explicitly set this value to true in order to use client certificate to establish a mutual TLS channel.""" + +# AWS environment variables used with AWS workload identity pools to retrieve +# AWS security credentials and the AWS region needed to create a serialized +# signed requests to the AWS STS GetCalledIdentity API that can be exchanged +# for a Google access tokens via the GCP STS endpoint. +# When not available the AWS metadata server is used to retrieve these values. +AWS_ACCESS_KEY_ID = "AWS_ACCESS_KEY_ID" +AWS_SECRET_ACCESS_KEY = "AWS_SECRET_ACCESS_KEY" +AWS_SESSION_TOKEN = "AWS_SESSION_TOKEN" +AWS_REGION = "AWS_REGION" diff --git a/packages/google-auth/google/auth/exceptions.py b/packages/google-auth/google/auth/exceptions.py index da06d8696283..b6f686bbb57c 100644 --- a/packages/google-auth/google/auth/exceptions.py +++ b/packages/google-auth/google/auth/exceptions.py @@ -43,3 +43,8 @@ class MutualTLSChannelError(GoogleAuthError): class ClientCertError(GoogleAuthError): """Used to indicate that client certificate is missing or invalid.""" + + +class OAuthError(GoogleAuthError): + """Used to indicate an error occurred during an OAuth related HTTP + request.""" diff --git a/packages/google-auth/google/auth/external_account.py b/packages/google-auth/google/auth/external_account.py new file mode 100644 index 000000000000..0429ee08f4ff --- /dev/null +++ b/packages/google-auth/google/auth/external_account.py @@ -0,0 +1,305 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""External Account Credentials. + +This module provides credentials that exchange workload identity pool external +credentials for Google access tokens. This facilitates accessing Google Cloud +Platform resources from on-prem and non-Google Cloud platforms (e.g. AWS, +Microsoft Azure, OIDC identity providers), using native credentials retrieved +from the current environment without the need to copy, save and manage +long-lived service account credentials. + +Specifically, this is intended to use access tokens acquired using the GCP STS +token exchange endpoint following the `OAuth 2.0 Token Exchange`_ spec. + +.. _OAuth 2.0 Token Exchange: https://tools.ietf.org/html/rfc8693 +""" + +import abc +import datetime +import json + +import six + +from google.auth import _helpers +from google.auth import credentials +from google.auth import exceptions +from google.auth import impersonated_credentials +from google.oauth2 import sts +from google.oauth2 import utils + +# The token exchange grant_type used for exchanging credentials. +_STS_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:token-exchange" +# The token exchange requested_token_type. This is always an access_token. +_STS_REQUESTED_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token" +# Cloud resource manager URL used to retrieve project information. +_CLOUD_RESOURCE_MANAGER = "https://cloudresourcemanager.googleapis.com/v1/projects/" + + +@six.add_metaclass(abc.ABCMeta) +class Credentials(credentials.Scoped, credentials.CredentialsWithQuotaProject): + """Base class for all external account credentials. + + This is used to instantiate Credentials for exchanging external account + credentials for Google access token and authorizing requests to Google APIs. + The base class implements the common logic for exchanging external account + credentials for Google access tokens. + """ + + def __init__( + self, + audience, + subject_token_type, + token_url, + credential_source, + service_account_impersonation_url=None, + client_id=None, + client_secret=None, + quota_project_id=None, + scopes=None, + default_scopes=None, + ): + """Instantiates an external account credentials object. + + Args: + audience (str): The STS audience field. + subject_token_type (str): The subject token type. + token_url (str): The STS endpoint URL. + credential_source (Mapping): The credential source dictionary. + service_account_impersonation_url (Optional[str]): The optional service account + impersonation generateAccessToken URL. + client_id (Optional[str]): The optional client ID. + client_secret (Optional[str]): The optional client secret. + quota_project_id (Optional[str]): The optional quota project ID. + scopes (Optional[Sequence[str]]): Optional scopes to request during the + authorization grant. + default_scopes (Optional[Sequence[str]]): Default scopes passed by a + Google client library. Use 'scopes' for user-defined scopes. + Raises: + google.auth.exceptions.RefreshError: If the generateAccessToken + endpoint returned an error. + """ + super(Credentials, self).__init__() + self._audience = audience + self._subject_token_type = subject_token_type + self._token_url = token_url + self._credential_source = credential_source + self._service_account_impersonation_url = service_account_impersonation_url + self._client_id = client_id + self._client_secret = client_secret + self._quota_project_id = quota_project_id + self._scopes = scopes + self._default_scopes = default_scopes + + if self._client_id: + self._client_auth = utils.ClientAuthentication( + utils.ClientAuthType.basic, self._client_id, self._client_secret + ) + else: + self._client_auth = None + self._sts_client = sts.Client(self._token_url, self._client_auth) + + if self._service_account_impersonation_url: + self._impersonated_credentials = self._initialize_impersonated_credentials() + else: + self._impersonated_credentials = None + self._project_id = None + + @property + def requires_scopes(self): + """Checks if the credentials requires scopes. + + Returns: + bool: True if there are no scopes set otherwise False. + """ + return not self._scopes and not self._default_scopes + + @property + def project_number(self): + """Optional[str]: The project number corresponding to the workload identity pool.""" + + # STS audience pattern: + # //iam.googleapis.com/projects/$PROJECT_NUMBER/locations/... + components = self._audience.split("/") + try: + project_index = components.index("projects") + if project_index + 1 < len(components): + return components[project_index + 1] or None + except ValueError: + return None + + @_helpers.copy_docstring(credentials.Scoped) + def with_scopes(self, scopes, default_scopes=None): + return self.__class__( + audience=self._audience, + subject_token_type=self._subject_token_type, + token_url=self._token_url, + credential_source=self._credential_source, + service_account_impersonation_url=self._service_account_impersonation_url, + client_id=self._client_id, + client_secret=self._client_secret, + quota_project_id=self._quota_project_id, + scopes=scopes, + default_scopes=default_scopes, + ) + + @abc.abstractmethod + def retrieve_subject_token(self, request): + """Retrieves the subject token using the credential_source object. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + Returns: + str: The retrieved subject token. + """ + # pylint: disable=missing-raises-doc + # (pylint doesn't recognize that this is abstract) + raise NotImplementedError("retrieve_subject_token must be implemented") + + def get_project_id(self, request): + """Retrieves the project ID corresponding to the workload identity pool. + + When not determinable, None is returned. + + This is introduced to support the current pattern of using the Auth library: + + credentials, project_id = google.auth.default() + + The resource may not have permission (resourcemanager.projects.get) to + call this API or the required scopes may not be selected: + https://cloud.google.com/resource-manager/reference/rest/v1/projects/get#authorization-scopes + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + Returns: + Optional[str]: The project ID corresponding to the workload identity pool + if determinable. + """ + if self._project_id: + # If already retrieved, return the cached project ID value. + return self._project_id + scopes = self._scopes if self._scopes is not None else self._default_scopes + # Scopes are required in order to retrieve a valid access token. + if self.project_number and scopes: + headers = {} + url = _CLOUD_RESOURCE_MANAGER + self.project_number + self.before_request(request, "GET", url, headers) + response = request(url=url, method="GET", headers=headers) + + response_body = ( + response.data.decode("utf-8") + if hasattr(response.data, "decode") + else response.data + ) + response_data = json.loads(response_body) + + if response.status == 200: + # Cache result as this field is immutable. + self._project_id = response_data.get("projectId") + return self._project_id + + return None + + @_helpers.copy_docstring(credentials.Credentials) + def refresh(self, request): + scopes = self._scopes if self._scopes is not None else self._default_scopes + if self._impersonated_credentials: + self._impersonated_credentials.refresh(request) + self.token = self._impersonated_credentials.token + self.expiry = self._impersonated_credentials.expiry + else: + now = _helpers.utcnow() + response_data = self._sts_client.exchange_token( + request=request, + grant_type=_STS_GRANT_TYPE, + subject_token=self.retrieve_subject_token(request), + subject_token_type=self._subject_token_type, + audience=self._audience, + scopes=scopes, + requested_token_type=_STS_REQUESTED_TOKEN_TYPE, + ) + self.token = response_data.get("access_token") + lifetime = datetime.timedelta(seconds=response_data.get("expires_in")) + self.expiry = now + lifetime + + @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) + def with_quota_project(self, quota_project_id): + # Return copy of instance with the provided quota project ID. + return self.__class__( + audience=self._audience, + subject_token_type=self._subject_token_type, + token_url=self._token_url, + credential_source=self._credential_source, + service_account_impersonation_url=self._service_account_impersonation_url, + client_id=self._client_id, + client_secret=self._client_secret, + quota_project_id=quota_project_id, + scopes=self._scopes, + default_scopes=self._default_scopes, + ) + + def _initialize_impersonated_credentials(self): + """Generates an impersonated credentials. + + For more details, see `projects.serviceAccounts.generateAccessToken`_. + + .. _projects.serviceAccounts.generateAccessToken: https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/generateAccessToken + + Returns: + impersonated_credentials.Credential: The impersonated credentials + object. + + Raises: + google.auth.exceptions.RefreshError: If the generateAccessToken + endpoint returned an error. + """ + # Return copy of instance with no service account impersonation. + source_credentials = self.__class__( + audience=self._audience, + subject_token_type=self._subject_token_type, + token_url=self._token_url, + credential_source=self._credential_source, + service_account_impersonation_url=None, + client_id=self._client_id, + client_secret=self._client_secret, + quota_project_id=self._quota_project_id, + scopes=self._scopes, + default_scopes=self._default_scopes, + ) + + # Determine target_principal. + start_index = self._service_account_impersonation_url.rfind("/") + end_index = self._service_account_impersonation_url.find(":generateAccessToken") + if start_index != -1 and end_index != -1 and start_index < end_index: + start_index = start_index + 1 + target_principal = self._service_account_impersonation_url[ + start_index:end_index + ] + else: + raise exceptions.RefreshError( + "Unable to determine target principal from service account impersonation URL." + ) + + scopes = self._scopes if self._scopes is not None else self._default_scopes + # Initialize and return impersonated credentials. + return impersonated_credentials.Credentials( + source_credentials=source_credentials, + target_principal=target_principal, + target_scopes=scopes, + quota_project_id=self._quota_project_id, + iam_endpoint_override=self._service_account_impersonation_url, + ) diff --git a/packages/google-auth/google/auth/identity_pool.py b/packages/google-auth/google/auth/identity_pool.py new file mode 100644 index 000000000000..53621995557f --- /dev/null +++ b/packages/google-auth/google/auth/identity_pool.py @@ -0,0 +1,279 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Identity Pool Credentials. + +This module provides credentials to access Google Cloud resources from on-prem +or non-Google Cloud platforms which support external credentials (e.g. OIDC ID +tokens) retrieved from local file locations or local servers. This includes +Microsoft Azure and OIDC identity providers (e.g. K8s workloads registered with +Hub with Hub workload identity enabled). + +These credentials are recommended over the use of service account credentials +in on-prem/non-Google Cloud platforms as they do not involve the management of +long-live service account private keys. + +Identity Pool Credentials are initialized using external_account +arguments which are typically loaded from an external credentials file or +an external credentials URL. Unlike other Credentials that can be initialized +with a list of explicit arguments, secrets or credentials, external account +clients use the environment and hints/guidelines provided by the +external_account JSON file to retrieve credentials and exchange them for Google +access tokens. +""" + +try: + from collections.abc import Mapping +# Python 2.7 compatibility +except ImportError: # pragma: NO COVER + from collections import Mapping +import io +import json +import os + +from google.auth import _helpers +from google.auth import exceptions +from google.auth import external_account + + +class Credentials(external_account.Credentials): + """External account credentials sourced from files and URLs.""" + + def __init__( + self, + audience, + subject_token_type, + token_url, + credential_source, + service_account_impersonation_url=None, + client_id=None, + client_secret=None, + quota_project_id=None, + scopes=None, + default_scopes=None, + ): + """Instantiates an external account credentials object from a file/URL. + + Args: + audience (str): The STS audience field. + subject_token_type (str): The subject token type. + token_url (str): The STS endpoint URL. + credential_source (Mapping): The credential source dictionary used to + provide instructions on how to retrieve external credential to be + exchanged for Google access tokens. + + Example credential_source for url-sourced credential:: + + { + "url": "http://www.example.com", + "format": { + "type": "json", + "subject_token_field_name": "access_token", + }, + "headers": {"foo": "bar"}, + } + + Example credential_source for file-sourced credential:: + + { + "file": "/path/to/token/file.txt" + } + + service_account_impersonation_url (Optional[str]): The optional service account + impersonation getAccessToken URL. + client_id (Optional[str]): The optional client ID. + client_secret (Optional[str]): The optional client secret. + quota_project_id (Optional[str]): The optional quota project ID. + scopes (Optional[Sequence[str]]): Optional scopes to request during the + authorization grant. + default_scopes (Optional[Sequence[str]]): Default scopes passed by a + Google client library. Use 'scopes' for user-defined scopes. + + Raises: + google.auth.exceptions.RefreshError: If an error is encountered during + access token retrieval logic. + ValueError: For invalid parameters. + + .. note:: Typically one of the helper constructors + :meth:`from_file` or + :meth:`from_info` are used instead of calling the constructor directly. + """ + + super(Credentials, self).__init__( + audience=audience, + subject_token_type=subject_token_type, + token_url=token_url, + credential_source=credential_source, + service_account_impersonation_url=service_account_impersonation_url, + client_id=client_id, + client_secret=client_secret, + quota_project_id=quota_project_id, + scopes=scopes, + default_scopes=default_scopes, + ) + if not isinstance(credential_source, Mapping): + self._credential_source_file = None + self._credential_source_url = None + else: + self._credential_source_file = credential_source.get("file") + self._credential_source_url = credential_source.get("url") + self._credential_source_headers = credential_source.get("headers") + credential_source_format = credential_source.get("format", {}) + # Get credential_source format type. When not provided, this + # defaults to text. + self._credential_source_format_type = ( + credential_source_format.get("type") or "text" + ) + # environment_id is only supported in AWS or dedicated future external + # account credentials. + if "environment_id" in credential_source: + raise ValueError( + "Invalid Identity Pool credential_source field 'environment_id'" + ) + if self._credential_source_format_type not in ["text", "json"]: + raise ValueError( + "Invalid credential_source format '{}'".format( + self._credential_source_format_type + ) + ) + # For JSON types, get the required subject_token field name. + if self._credential_source_format_type == "json": + self._credential_source_field_name = credential_source_format.get( + "subject_token_field_name" + ) + if self._credential_source_field_name is None: + raise ValueError( + "Missing subject_token_field_name for JSON credential_source format" + ) + else: + self._credential_source_field_name = None + + if self._credential_source_file and self._credential_source_url: + raise ValueError( + "Ambiguous credential_source. 'file' is mutually exclusive with 'url'." + ) + if not self._credential_source_file and not self._credential_source_url: + raise ValueError( + "Missing credential_source. A 'file' or 'url' must be provided." + ) + + @_helpers.copy_docstring(external_account.Credentials) + def retrieve_subject_token(self, request): + return self._parse_token_data( + self._get_token_data(request), + self._credential_source_format_type, + self._credential_source_field_name, + ) + + def _get_token_data(self, request): + if self._credential_source_file: + return self._get_file_data(self._credential_source_file) + else: + return self._get_url_data( + request, self._credential_source_url, self._credential_source_headers + ) + + def _get_file_data(self, filename): + if not os.path.exists(filename): + raise exceptions.RefreshError("File '{}' was not found.".format(filename)) + + with io.open(filename, "r", encoding="utf-8") as file_obj: + return file_obj.read(), filename + + def _get_url_data(self, request, url, headers): + response = request(url=url, method="GET", headers=headers) + + # support both string and bytes type response.data + response_body = ( + response.data.decode("utf-8") + if hasattr(response.data, "decode") + else response.data + ) + + if response.status != 200: + raise exceptions.RefreshError( + "Unable to retrieve Identity Pool subject token", response_body + ) + + return response_body, url + + def _parse_token_data( + self, token_content, format_type="text", subject_token_field_name=None + ): + content, filename = token_content + if format_type == "text": + token = content + else: + try: + # Parse file content as JSON. + response_data = json.loads(content) + # Get the subject_token. + token = response_data[subject_token_field_name] + except (KeyError, ValueError): + raise exceptions.RefreshError( + "Unable to parse subject_token from JSON file '{}' using key '{}'".format( + filename, subject_token_field_name + ) + ) + if not token: + raise exceptions.RefreshError( + "Missing subject_token in the credential_source file" + ) + return token + + @classmethod + def from_info(cls, info, **kwargs): + """Creates an Identity Pool Credentials instance from parsed external account info. + + Args: + info (Mapping[str, str]): The Identity Pool external account info in Google + format. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.identity_pool.Credentials: The constructed + credentials. + + Raises: + ValueError: For invalid parameters. + """ + return cls( + audience=info.get("audience"), + subject_token_type=info.get("subject_token_type"), + token_url=info.get("token_url"), + service_account_impersonation_url=info.get( + "service_account_impersonation_url" + ), + client_id=info.get("client_id"), + client_secret=info.get("client_secret"), + credential_source=info.get("credential_source"), + quota_project_id=info.get("quota_project_id"), + **kwargs + ) + + @classmethod + def from_file(cls, filename, **kwargs): + """Creates an IdentityPool Credentials instance from an external account json file. + + Args: + filename (str): The path to the IdentityPool external account json file. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.identity_pool.Credentials: The constructed + credentials. + """ + with io.open(filename, "r", encoding="utf-8") as json_file: + data = json.load(json_file) + return cls.from_info(data, **kwargs) diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index 4d158373a7e8..b8a6c49a1eba 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -65,7 +65,9 @@ _DEFAULT_TOKEN_URI = "https://oauth2.googleapis.com/token" -def _make_iam_token_request(request, principal, headers, body): +def _make_iam_token_request( + request, principal, headers, body, iam_endpoint_override=None +): """Makes a request to the Google Cloud IAM service for an access token. Args: request (Request): The Request object to use. @@ -73,6 +75,9 @@ def _make_iam_token_request(request, principal, headers, body): headers (Mapping[str, str]): Map of headers to transmit. body (Mapping[str, str]): JSON Payload body for the iamcredentials API call. + iam_endpoint_override (Optiona[str]): The full IAM endpoint override + with the target_principal embedded. This is useful when supporting + impersonation with regional endpoints. Raises: google.auth.exceptions.TransportError: Raised if there is an underlying @@ -82,7 +87,7 @@ def _make_iam_token_request(request, principal, headers, body): `iamcredentials.googleapis.com` is not enabled or the `Service Account Token Creator` is not assigned """ - iam_endpoint = _IAM_ENDPOINT.format(principal) + iam_endpoint = iam_endpoint_override or _IAM_ENDPOINT.format(principal) body = json.dumps(body).encode("utf-8") @@ -185,6 +190,7 @@ def __init__( delegates=None, lifetime=_DEFAULT_TOKEN_LIFETIME_SECS, quota_project_id=None, + iam_endpoint_override=None, ): """ Args: @@ -209,6 +215,9 @@ def __init__( quota_project_id (Optional[str]): The project ID used for quota and billing. This project may be different from the project used to create the credentials. + iam_endpoint_override (Optiona[str]): The full IAM endpoint override + with the target_principal embedded. This is useful when supporting + impersonation with regional endpoints. """ super(Credentials, self).__init__() @@ -226,6 +235,7 @@ def __init__( self.token = None self.expiry = _helpers.utcnow() self._quota_project_id = quota_project_id + self._iam_endpoint_override = iam_endpoint_override @_helpers.copy_docstring(credentials.Credentials) def refresh(self, request): @@ -260,6 +270,7 @@ def _update_token(self, request): principal=self._target_principal, headers=headers, body=body, + iam_endpoint_override=self._iam_endpoint_override, ) def sign_bytes(self, message): @@ -302,6 +313,7 @@ def with_quota_project(self, quota_project_id): delegates=self._delegates, lifetime=self._lifetime, quota_project_id=quota_project_id, + iam_endpoint_override=self._iam_endpoint_override, ) diff --git a/packages/google-auth/google/oauth2/sts.py b/packages/google-auth/google/oauth2/sts.py new file mode 100644 index 000000000000..ae3c0146b114 --- /dev/null +++ b/packages/google-auth/google/oauth2/sts.py @@ -0,0 +1,155 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""OAuth 2.0 Token Exchange Spec. + +This module defines a token exchange utility based on the `OAuth 2.0 Token +Exchange`_ spec. This will be mainly used to exchange external credentials +for GCP access tokens in workload identity pools to access Google APIs. + +The implementation will support various types of client authentication as +allowed in the spec. + +A deviation on the spec will be for additional Google specific options that +cannot be easily mapped to parameters defined in the RFC. + +The returned dictionary response will be based on the `rfc8693 section 2.2.1`_ +spec JSON response. + +.. _OAuth 2.0 Token Exchange: https://tools.ietf.org/html/rfc8693 +.. _rfc8693 section 2.2.1: https://tools.ietf.org/html/rfc8693#section-2.2.1 +""" + +import json + +from six.moves import http_client +from six.moves import urllib + +from google.oauth2 import utils + + +_URLENCODED_HEADERS = {"Content-Type": "application/x-www-form-urlencoded"} + + +class Client(utils.OAuthClientAuthHandler): + """Implements the OAuth 2.0 token exchange spec based on + https://tools.ietf.org/html/rfc8693. + """ + + def __init__(self, token_exchange_endpoint, client_authentication=None): + """Initializes an STS client instance. + + Args: + token_exchange_endpoint (str): The token exchange endpoint. + client_authentication (Optional(google.oauth2.oauth2_utils.ClientAuthentication)): + The optional OAuth client authentication credentials if available. + """ + super(Client, self).__init__(client_authentication) + self._token_exchange_endpoint = token_exchange_endpoint + + def exchange_token( + self, + request, + grant_type, + subject_token, + subject_token_type, + resource=None, + audience=None, + scopes=None, + requested_token_type=None, + actor_token=None, + actor_token_type=None, + additional_options=None, + additional_headers=None, + ): + """Exchanges the provided token for another type of token based on the + rfc8693 spec. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + grant_type (str): The OAuth 2.0 token exchange grant type. + subject_token (str): The OAuth 2.0 token exchange subject token. + subject_token_type (str): The OAuth 2.0 token exchange subject token type. + resource (Optional[str]): The optional OAuth 2.0 token exchange resource field. + audience (Optional[str]): The optional OAuth 2.0 token exchange audience field. + scopes (Optional[Sequence[str]]): The optional list of scopes to use. + requested_token_type (Optional[str]): The optional OAuth 2.0 token exchange requested + token type. + actor_token (Optional[str]): The optional OAuth 2.0 token exchange actor token. + actor_token_type (Optional[str]): The optional OAuth 2.0 token exchange actor token type. + additional_options (Optional[Mapping[str, str]]): The optional additional + non-standard Google specific options. + additional_headers (Optional[Mapping[str, str]]): The optional additional + headers to pass to the token exchange endpoint. + + Returns: + Mapping[str, str]: The token exchange JSON-decoded response data containing + the requested token and its expiration time. + + Raises: + google.auth.exceptions.OAuthError: If the token endpoint returned + an error. + """ + # Initialize request headers. + headers = _URLENCODED_HEADERS.copy() + # Inject additional headers. + if additional_headers: + for k, v in dict(additional_headers).items(): + headers[k] = v + # Initialize request body. + request_body = { + "grant_type": grant_type, + "resource": resource, + "audience": audience, + "scope": " ".join(scopes or []), + "requested_token_type": requested_token_type, + "subject_token": subject_token, + "subject_token_type": subject_token_type, + "actor_token": actor_token, + "actor_token_type": actor_token_type, + "options": None, + } + # Add additional non-standard options. + if additional_options: + request_body["options"] = urllib.parse.quote(json.dumps(additional_options)) + # Remove empty fields in request body. + for k, v in dict(request_body).items(): + if v is None or v == "": + del request_body[k] + # Apply OAuth client authentication. + self.apply_client_authentication_options(headers, request_body) + + # Execute request. + response = request( + url=self._token_exchange_endpoint, + method="POST", + headers=headers, + body=urllib.parse.urlencode(request_body).encode("utf-8"), + ) + + response_body = ( + response.data.decode("utf-8") + if hasattr(response.data, "decode") + else response.data + ) + + # If non-200 response received, translate to OAuthError exception. + if response.status != http_client.OK: + utils.handle_error_response(response_body) + + response_data = json.loads(response_body) + + # Return successful response. + return response_data diff --git a/packages/google-auth/google/oauth2/utils.py b/packages/google-auth/google/oauth2/utils.py new file mode 100644 index 000000000000..efda7968dbaa --- /dev/null +++ b/packages/google-auth/google/oauth2/utils.py @@ -0,0 +1,171 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""OAuth 2.0 Utilities. + +This module provides implementations for various OAuth 2.0 utilities. +This includes `OAuth error handling`_ and +`Client authentication for OAuth flows`_. + +OAuth error handling +-------------------- +This will define interfaces for handling OAuth related error responses as +stated in `RFC 6749 section 5.2`_. +This will include a common function to convert these HTTP error responses to a +:class:`google.auth.exceptions.OAuthError` exception. + + +Client authentication for OAuth flows +------------------------------------- +We introduce an interface for defining client authentication credentials based +on `RFC 6749 section 2.3.1`_. This will expose the following +capabilities: + + * Ability to support basic authentication via request header. + * Ability to support bearer token authentication via request header. + * Ability to support client ID / secret authentication via request body. + +.. _RFC 6749 section 2.3.1: https://tools.ietf.org/html/rfc6749#section-2.3.1 +.. _RFC 6749 section 5.2: https://tools.ietf.org/html/rfc6749#section-5.2 +""" + +import abc +import base64 +import enum +import json + +import six + +from google.auth import exceptions + + +# OAuth client authentication based on +# https://tools.ietf.org/html/rfc6749#section-2.3. +class ClientAuthType(enum.Enum): + basic = 1 + request_body = 2 + + +class ClientAuthentication(object): + """Defines the client authentication credentials for basic and request-body + types based on https://tools.ietf.org/html/rfc6749#section-2.3.1. + """ + + def __init__(self, client_auth_type, client_id, client_secret=None): + """Instantiates a client authentication object containing the client ID + and secret credentials for basic and response-body auth. + + Args: + client_auth_type (google.oauth2.oauth_utils.ClientAuthType): The + client authentication type. + client_id (str): The client ID. + client_secret (Optional[str]): The client secret. + """ + self.client_auth_type = client_auth_type + self.client_id = client_id + self.client_secret = client_secret + + +@six.add_metaclass(abc.ABCMeta) +class OAuthClientAuthHandler(object): + """Abstract class for handling client authentication in OAuth-based + operations. + """ + + def __init__(self, client_authentication=None): + """Instantiates an OAuth client authentication handler. + + Args: + client_authentication (Optional[google.oauth2.utils.ClientAuthentication]): + The OAuth client authentication credentials if available. + """ + super(OAuthClientAuthHandler, self).__init__() + self._client_authentication = client_authentication + + def apply_client_authentication_options( + self, headers, request_body=None, bearer_token=None + ): + """Applies client authentication on the OAuth request's headers or POST + body. + + Args: + headers (Mapping[str, str]): The HTTP request header. + request_body (Optional[Mapping[str, str]): The HTTP request body + dictionary. For requests that do not support request body, this + is None and will be ignored. + bearer_token (Optional[str]): The optional bearer token. + """ + # Inject authenticated header. + self._inject_authenticated_headers(headers, bearer_token) + # Inject authenticated request body. + if bearer_token is None: + self._inject_authenticated_request_body(request_body) + + def _inject_authenticated_headers(self, headers, bearer_token=None): + if bearer_token is not None: + headers["Authorization"] = "Bearer %s" % bearer_token + elif ( + self._client_authentication is not None + and self._client_authentication.client_auth_type is ClientAuthType.basic + ): + username = self._client_authentication.client_id + password = self._client_authentication.client_secret or "" + + credentials = base64.b64encode( + ("%s:%s" % (username, password)).encode() + ).decode() + headers["Authorization"] = "Basic %s" % credentials + + def _inject_authenticated_request_body(self, request_body): + if ( + self._client_authentication is not None + and self._client_authentication.client_auth_type + is ClientAuthType.request_body + ): + if request_body is None: + raise exceptions.OAuthError( + "HTTP request does not support request-body" + ) + else: + request_body["client_id"] = self._client_authentication.client_id + request_body["client_secret"] = ( + self._client_authentication.client_secret or "" + ) + + +def handle_error_response(response_body): + """Translates an error response from an OAuth operation into an + OAuthError exception. + + Args: + response_body (str): The decoded response data. + + Raises: + google.auth.exceptions.OAuthError + """ + try: + error_components = [] + error_data = json.loads(response_body) + + error_components.append("Error code {}".format(error_data["error"])) + if "error_description" in error_data: + error_components.append(": {}".format(error_data["error_description"])) + if "error_uri" in error_data: + error_components.append(" - {}".format(error_data["error_uri"])) + error_details = "".join(error_components) + # If no details could be extracted, use the response data. + except (KeyError, ValueError): + error_details = response_body + + raise exceptions.OAuthError(error_details, response_body) diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index adce2527c13d..fa88d24b2695 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -64,9 +64,7 @@ def lint(session): @nox.session(python="3.6") def blacken(session): """Run black. - Format code to uniform standard. - This currently uses Python 3.6 due to the automated Kokoro run of synthtool. That run uses an image that doesn't have 3.6 installed. Before updating this check the state of the `gcp_ubuntu_config` we use for that Kokoro run. diff --git a/packages/google-auth/system_tests/system_tests_sync/secrets.tar.enc b/packages/google-auth/system_tests/system_tests_sync/secrets.tar.enc new file mode 100644 index 0000000000000000000000000000000000000000..29e06923f0f028b54d1b571dc218cdd92f751bd8 GIT binary patch literal 10323 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 0: + # If service account impersonation is requested, mock the expected response. + status, data, extra_requests = ( + extra_requests[0], + extra_requests[1], + extra_requests[2:], + ) + responses.append(cls.make_mock_response(status, data)) + + request = mock.create_autospec(transport.Request) + request.side_effect = responses + + return request + + @classmethod + def assert_credential_request_kwargs( + cls, request_kwargs, headers, url=CREDENTIAL_URL + ): + assert request_kwargs["url"] == url + assert request_kwargs["method"] == "GET" + assert request_kwargs["headers"] == headers + assert request_kwargs.get("body", None) is None + + @classmethod + def assert_token_request_kwargs( + cls, request_kwargs, headers, request_data, token_url=TOKEN_URL + ): + assert request_kwargs["url"] == token_url + assert request_kwargs["method"] == "POST" + assert request_kwargs["headers"] == headers + assert request_kwargs["body"] is not None + body_tuples = urllib.parse.parse_qsl(request_kwargs["body"]) + assert len(body_tuples) == len(request_data.keys()) + for (k, v) in body_tuples: + assert v.decode("utf-8") == request_data[k.decode("utf-8")] + + @classmethod + def assert_impersonation_request_kwargs( + cls, + request_kwargs, + headers, + request_data, + service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + ): + assert request_kwargs["url"] == service_account_impersonation_url + assert request_kwargs["method"] == "POST" + assert request_kwargs["headers"] == headers + assert request_kwargs["body"] is not None + body_json = json.loads(request_kwargs["body"].decode("utf-8")) + assert body_json == request_data + + @classmethod + def assert_underlying_credentials_refresh( + cls, + credentials, + audience, + subject_token, + subject_token_type, + token_url, + service_account_impersonation_url=None, + basic_auth_encoding=None, + quota_project_id=None, + used_scopes=None, + credential_data=None, + scopes=None, + default_scopes=None, + ): + """Utility to assert that a credentials are initialized with the expected + attributes by calling refresh functionality and confirming response matches + expected one and that the underlying requests were populated with the + expected parameters. + """ + # STS token exchange request/response. + token_response = cls.SUCCESS_RESPONSE.copy() + token_headers = {"Content-Type": "application/x-www-form-urlencoded"} + if basic_auth_encoding: + token_headers["Authorization"] = "Basic " + basic_auth_encoding + + if service_account_impersonation_url: + token_scopes = "https://www.googleapis.com/auth/iam" + else: + token_scopes = " ".join(used_scopes or []) + + token_request_data = { + "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange", + "audience": audience, + "requested_token_type": "urn:ietf:params:oauth:token-type:access_token", + "scope": token_scopes, + "subject_token": subject_token, + "subject_token_type": subject_token_type, + } + + if service_account_impersonation_url: + # Service account impersonation request/response. + expire_time = ( + _helpers.utcnow().replace(microsecond=0) + + datetime.timedelta(seconds=3600) + ).isoformat("T") + "Z" + impersonation_response = { + "accessToken": "SA_ACCESS_TOKEN", + "expireTime": expire_time, + } + impersonation_headers = { + "Content-Type": "application/json", + "authorization": "Bearer {}".format(token_response["access_token"]), + } + impersonation_request_data = { + "delegates": None, + "scope": used_scopes, + "lifetime": "3600s", + } + + # Initialize mock request to handle token retrieval, token exchange and + # service account impersonation request. + requests = [] + if credential_data: + requests.append((http_client.OK, credential_data)) + + token_request_index = len(requests) + requests.append((http_client.OK, token_response)) + + if service_account_impersonation_url: + impersonation_request_index = len(requests) + requests.append((http_client.OK, impersonation_response)) + + request = cls.make_mock_request(*[el for req in requests for el in req]) + + credentials.refresh(request) + + assert len(request.call_args_list) == len(requests) + if credential_data: + cls.assert_credential_request_kwargs(request.call_args_list[0].kwargs, None) + # Verify token exchange request parameters. + cls.assert_token_request_kwargs( + request.call_args_list[token_request_index].kwargs, + token_headers, + token_request_data, + token_url, + ) + # Verify service account impersonation request parameters if the request + # is processed. + if service_account_impersonation_url: + cls.assert_impersonation_request_kwargs( + request.call_args_list[impersonation_request_index].kwargs, + impersonation_headers, + impersonation_request_data, + service_account_impersonation_url, + ) + assert credentials.token == impersonation_response["accessToken"] + else: + assert credentials.token == token_response["access_token"] + assert credentials.quota_project_id == quota_project_id + assert credentials.scopes == scopes + assert credentials.default_scopes == default_scopes + + @classmethod + def make_credentials( + cls, + client_id=None, + client_secret=None, + quota_project_id=None, + scopes=None, + default_scopes=None, + service_account_impersonation_url=None, + credential_source=None, + ): + return identity_pool.Credentials( + audience=AUDIENCE, + subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + service_account_impersonation_url=service_account_impersonation_url, + credential_source=credential_source, + client_id=client_id, + client_secret=client_secret, + quota_project_id=quota_project_id, + scopes=scopes, + default_scopes=default_scopes, + ) + + @mock.patch.object(identity_pool.Credentials, "__init__", return_value=None) + def test_from_info_full_options(self, mock_init): + credentials = identity_pool.Credentials.from_info( + { + "audience": AUDIENCE, + "subject_token_type": SUBJECT_TOKEN_TYPE, + "token_url": TOKEN_URL, + "service_account_impersonation_url": SERVICE_ACCOUNT_IMPERSONATION_URL, + "client_id": CLIENT_ID, + "client_secret": CLIENT_SECRET, + "quota_project_id": QUOTA_PROJECT_ID, + "credential_source": self.CREDENTIAL_SOURCE_TEXT, + } + ) + + # Confirm identity_pool.Credentials instantiated with expected attributes. + assert isinstance(credentials, identity_pool.Credentials) + mock_init.assert_called_once_with( + audience=AUDIENCE, + subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + client_id=CLIENT_ID, + client_secret=CLIENT_SECRET, + credential_source=self.CREDENTIAL_SOURCE_TEXT, + quota_project_id=QUOTA_PROJECT_ID, + ) + + @mock.patch.object(identity_pool.Credentials, "__init__", return_value=None) + def test_from_info_required_options_only(self, mock_init): + credentials = identity_pool.Credentials.from_info( + { + "audience": AUDIENCE, + "subject_token_type": SUBJECT_TOKEN_TYPE, + "token_url": TOKEN_URL, + "credential_source": self.CREDENTIAL_SOURCE_TEXT, + } + ) + + # Confirm identity_pool.Credentials instantiated with expected attributes. + assert isinstance(credentials, identity_pool.Credentials) + mock_init.assert_called_once_with( + audience=AUDIENCE, + subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + service_account_impersonation_url=None, + client_id=None, + client_secret=None, + credential_source=self.CREDENTIAL_SOURCE_TEXT, + quota_project_id=None, + ) + + @mock.patch.object(identity_pool.Credentials, "__init__", return_value=None) + def test_from_file_full_options(self, mock_init, tmpdir): + info = { + "audience": AUDIENCE, + "subject_token_type": SUBJECT_TOKEN_TYPE, + "token_url": TOKEN_URL, + "service_account_impersonation_url": SERVICE_ACCOUNT_IMPERSONATION_URL, + "client_id": CLIENT_ID, + "client_secret": CLIENT_SECRET, + "quota_project_id": QUOTA_PROJECT_ID, + "credential_source": self.CREDENTIAL_SOURCE_TEXT, + } + config_file = tmpdir.join("config.json") + config_file.write(json.dumps(info)) + credentials = identity_pool.Credentials.from_file(str(config_file)) + + # Confirm identity_pool.Credentials instantiated with expected attributes. + assert isinstance(credentials, identity_pool.Credentials) + mock_init.assert_called_once_with( + audience=AUDIENCE, + subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + client_id=CLIENT_ID, + client_secret=CLIENT_SECRET, + credential_source=self.CREDENTIAL_SOURCE_TEXT, + quota_project_id=QUOTA_PROJECT_ID, + ) + + @mock.patch.object(identity_pool.Credentials, "__init__", return_value=None) + def test_from_file_required_options_only(self, mock_init, tmpdir): + info = { + "audience": AUDIENCE, + "subject_token_type": SUBJECT_TOKEN_TYPE, + "token_url": TOKEN_URL, + "credential_source": self.CREDENTIAL_SOURCE_TEXT, + } + config_file = tmpdir.join("config.json") + config_file.write(json.dumps(info)) + credentials = identity_pool.Credentials.from_file(str(config_file)) + + # Confirm identity_pool.Credentials instantiated with expected attributes. + assert isinstance(credentials, identity_pool.Credentials) + mock_init.assert_called_once_with( + audience=AUDIENCE, + subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + service_account_impersonation_url=None, + client_id=None, + client_secret=None, + credential_source=self.CREDENTIAL_SOURCE_TEXT, + quota_project_id=None, + ) + + def test_constructor_invalid_options(self): + credential_source = {"unsupported": "value"} + + with pytest.raises(ValueError) as excinfo: + self.make_credentials(credential_source=credential_source) + + assert excinfo.match(r"Missing credential_source") + + def test_constructor_invalid_options_url_and_file(self): + credential_source = { + "url": self.CREDENTIAL_URL, + "file": SUBJECT_TOKEN_TEXT_FILE, + } + + with pytest.raises(ValueError) as excinfo: + self.make_credentials(credential_source=credential_source) + + assert excinfo.match(r"Ambiguous credential_source") + + def test_constructor_invalid_options_environment_id(self): + credential_source = {"url": self.CREDENTIAL_URL, "environment_id": "aws1"} + + with pytest.raises(ValueError) as excinfo: + self.make_credentials(credential_source=credential_source) + + assert excinfo.match( + r"Invalid Identity Pool credential_source field 'environment_id'" + ) + + def test_constructor_invalid_credential_source(self): + with pytest.raises(ValueError) as excinfo: + self.make_credentials(credential_source="non-dict") + + assert excinfo.match(r"Missing credential_source") + + def test_constructor_invalid_credential_source_format_type(self): + credential_source = {"format": {"type": "xml"}} + + with pytest.raises(ValueError) as excinfo: + self.make_credentials(credential_source=credential_source) + + assert excinfo.match(r"Invalid credential_source format 'xml'") + + def test_constructor_missing_subject_token_field_name(self): + credential_source = {"format": {"type": "json"}} + + with pytest.raises(ValueError) as excinfo: + self.make_credentials(credential_source=credential_source) + + assert excinfo.match( + r"Missing subject_token_field_name for JSON credential_source format" + ) + + def test_retrieve_subject_token_missing_subject_token(self, tmpdir): + # Provide empty text file. + empty_file = tmpdir.join("empty.txt") + empty_file.write("") + credential_source = {"file": str(empty_file)} + credentials = self.make_credentials(credential_source=credential_source) + + with pytest.raises(exceptions.RefreshError) as excinfo: + credentials.retrieve_subject_token(None) + + assert excinfo.match(r"Missing subject_token in the credential_source file") + + def test_retrieve_subject_token_text_file(self): + credentials = self.make_credentials( + credential_source=self.CREDENTIAL_SOURCE_TEXT + ) + + subject_token = credentials.retrieve_subject_token(None) + + assert subject_token == TEXT_FILE_SUBJECT_TOKEN + + def test_retrieve_subject_token_json_file(self): + credentials = self.make_credentials( + credential_source=self.CREDENTIAL_SOURCE_JSON + ) + + subject_token = credentials.retrieve_subject_token(None) + + assert subject_token == JSON_FILE_SUBJECT_TOKEN + + def test_retrieve_subject_token_json_file_invalid_field_name(self): + credential_source = { + "file": SUBJECT_TOKEN_JSON_FILE, + "format": {"type": "json", "subject_token_field_name": "not_found"}, + } + credentials = self.make_credentials(credential_source=credential_source) + + with pytest.raises(exceptions.RefreshError) as excinfo: + credentials.retrieve_subject_token(None) + + assert excinfo.match( + "Unable to parse subject_token from JSON file '{}' using key '{}'".format( + SUBJECT_TOKEN_JSON_FILE, "not_found" + ) + ) + + def test_retrieve_subject_token_invalid_json(self, tmpdir): + # Provide JSON file. This should result in JSON parsing error. + invalid_json_file = tmpdir.join("invalid.json") + invalid_json_file.write("{") + credential_source = { + "file": str(invalid_json_file), + "format": {"type": "json", "subject_token_field_name": "access_token"}, + } + credentials = self.make_credentials(credential_source=credential_source) + + with pytest.raises(exceptions.RefreshError) as excinfo: + credentials.retrieve_subject_token(None) + + assert excinfo.match( + "Unable to parse subject_token from JSON file '{}' using key '{}'".format( + str(invalid_json_file), "access_token" + ) + ) + + def test_retrieve_subject_token_file_not_found(self): + credential_source = {"file": "./not_found.txt"} + credentials = self.make_credentials(credential_source=credential_source) + + with pytest.raises(exceptions.RefreshError) as excinfo: + credentials.retrieve_subject_token(None) + + assert excinfo.match(r"File './not_found.txt' was not found") + + def test_refresh_text_file_success_without_impersonation_ignore_default_scopes( + self + ): + credentials = self.make_credentials( + client_id=CLIENT_ID, + client_secret=CLIENT_SECRET, + # Test with text format type. + credential_source=self.CREDENTIAL_SOURCE_TEXT, + scopes=SCOPES, + # Default scopes should be ignored. + default_scopes=["ignored"], + ) + + self.assert_underlying_credentials_refresh( + credentials=credentials, + audience=AUDIENCE, + subject_token=TEXT_FILE_SUBJECT_TOKEN, + subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + service_account_impersonation_url=None, + basic_auth_encoding=BASIC_AUTH_ENCODING, + quota_project_id=None, + used_scopes=SCOPES, + scopes=SCOPES, + default_scopes=["ignored"], + ) + + def test_refresh_text_file_success_without_impersonation_use_default_scopes(self): + credentials = self.make_credentials( + client_id=CLIENT_ID, + client_secret=CLIENT_SECRET, + # Test with text format type. + credential_source=self.CREDENTIAL_SOURCE_TEXT, + scopes=None, + # Default scopes should be used since user specified scopes are none. + default_scopes=SCOPES, + ) + + self.assert_underlying_credentials_refresh( + credentials=credentials, + audience=AUDIENCE, + subject_token=TEXT_FILE_SUBJECT_TOKEN, + subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + service_account_impersonation_url=None, + basic_auth_encoding=BASIC_AUTH_ENCODING, + quota_project_id=None, + used_scopes=SCOPES, + scopes=None, + default_scopes=SCOPES, + ) + + def test_refresh_text_file_success_with_impersonation_ignore_default_scopes(self): + # Initialize credentials with service account impersonation and basic auth. + credentials = self.make_credentials( + # Test with text format type. + credential_source=self.CREDENTIAL_SOURCE_TEXT, + service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + scopes=SCOPES, + # Default scopes should be ignored. + default_scopes=["ignored"], + ) + + self.assert_underlying_credentials_refresh( + credentials=credentials, + audience=AUDIENCE, + subject_token=TEXT_FILE_SUBJECT_TOKEN, + subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + basic_auth_encoding=None, + quota_project_id=None, + used_scopes=SCOPES, + scopes=SCOPES, + default_scopes=["ignored"], + ) + + def test_refresh_text_file_success_with_impersonation_use_default_scopes(self): + # Initialize credentials with service account impersonation, basic auth + # and default scopes (no user scopes). + credentials = self.make_credentials( + # Test with text format type. + credential_source=self.CREDENTIAL_SOURCE_TEXT, + service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + scopes=None, + # Default scopes should be used since user specified scopes are none. + default_scopes=SCOPES, + ) + + self.assert_underlying_credentials_refresh( + credentials=credentials, + audience=AUDIENCE, + subject_token=TEXT_FILE_SUBJECT_TOKEN, + subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + basic_auth_encoding=None, + quota_project_id=None, + used_scopes=SCOPES, + scopes=None, + default_scopes=SCOPES, + ) + + def test_refresh_json_file_success_without_impersonation(self): + credentials = self.make_credentials( + client_id=CLIENT_ID, + client_secret=CLIENT_SECRET, + # Test with JSON format type. + credential_source=self.CREDENTIAL_SOURCE_JSON, + scopes=SCOPES, + ) + + self.assert_underlying_credentials_refresh( + credentials=credentials, + audience=AUDIENCE, + subject_token=JSON_FILE_SUBJECT_TOKEN, + subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + service_account_impersonation_url=None, + basic_auth_encoding=BASIC_AUTH_ENCODING, + quota_project_id=None, + used_scopes=SCOPES, + scopes=SCOPES, + default_scopes=None, + ) + + def test_refresh_json_file_success_with_impersonation(self): + # Initialize credentials with service account impersonation and basic auth. + credentials = self.make_credentials( + # Test with JSON format type. + credential_source=self.CREDENTIAL_SOURCE_JSON, + service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + scopes=SCOPES, + ) + + self.assert_underlying_credentials_refresh( + credentials=credentials, + audience=AUDIENCE, + subject_token=JSON_FILE_SUBJECT_TOKEN, + subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + basic_auth_encoding=None, + quota_project_id=None, + used_scopes=SCOPES, + scopes=SCOPES, + default_scopes=None, + ) + + def test_refresh_with_retrieve_subject_token_error(self): + credential_source = { + "file": SUBJECT_TOKEN_JSON_FILE, + "format": {"type": "json", "subject_token_field_name": "not_found"}, + } + credentials = self.make_credentials(credential_source=credential_source) + + with pytest.raises(exceptions.RefreshError) as excinfo: + credentials.refresh(None) + + assert excinfo.match( + "Unable to parse subject_token from JSON file '{}' using key '{}'".format( + SUBJECT_TOKEN_JSON_FILE, "not_found" + ) + ) + + def test_retrieve_subject_token_from_url(self): + credentials = self.make_credentials( + credential_source=self.CREDENTIAL_SOURCE_TEXT_URL + ) + request = self.make_mock_request(token_data=TEXT_FILE_SUBJECT_TOKEN) + subject_token = credentials.retrieve_subject_token(request) + + assert subject_token == TEXT_FILE_SUBJECT_TOKEN + self.assert_credential_request_kwargs(request.call_args_list[0].kwargs, None) + + def test_retrieve_subject_token_from_url_with_headers(self): + credentials = self.make_credentials( + credential_source={"url": self.CREDENTIAL_URL, "headers": {"foo": "bar"}} + ) + request = self.make_mock_request(token_data=TEXT_FILE_SUBJECT_TOKEN) + subject_token = credentials.retrieve_subject_token(request) + + assert subject_token == TEXT_FILE_SUBJECT_TOKEN + self.assert_credential_request_kwargs( + request.call_args_list[0].kwargs, {"foo": "bar"} + ) + + def test_retrieve_subject_token_from_url_json(self): + credentials = self.make_credentials( + credential_source=self.CREDENTIAL_SOURCE_JSON_URL + ) + request = self.make_mock_request(token_data=JSON_FILE_CONTENT) + subject_token = credentials.retrieve_subject_token(request) + + assert subject_token == JSON_FILE_SUBJECT_TOKEN + self.assert_credential_request_kwargs(request.call_args_list[0].kwargs, None) + + def test_retrieve_subject_token_from_url_json_with_headers(self): + credentials = self.make_credentials( + credential_source={ + "url": self.CREDENTIAL_URL, + "format": {"type": "json", "subject_token_field_name": "access_token"}, + "headers": {"foo": "bar"}, + } + ) + request = self.make_mock_request(token_data=JSON_FILE_CONTENT) + subject_token = credentials.retrieve_subject_token(request) + + assert subject_token == JSON_FILE_SUBJECT_TOKEN + self.assert_credential_request_kwargs( + request.call_args_list[0].kwargs, {"foo": "bar"} + ) + + def test_retrieve_subject_token_from_url_not_found(self): + credentials = self.make_credentials( + credential_source=self.CREDENTIAL_SOURCE_TEXT_URL + ) + with pytest.raises(exceptions.RefreshError) as excinfo: + credentials.retrieve_subject_token( + self.make_mock_request(token_status=404, token_data=JSON_FILE_CONTENT) + ) + + assert excinfo.match("Unable to retrieve Identity Pool subject token") + + def test_retrieve_subject_token_from_url_json_invalid_field(self): + credential_source = { + "url": self.CREDENTIAL_URL, + "format": {"type": "json", "subject_token_field_name": "not_found"}, + } + credentials = self.make_credentials(credential_source=credential_source) + + with pytest.raises(exceptions.RefreshError) as excinfo: + credentials.retrieve_subject_token( + self.make_mock_request(token_data=JSON_FILE_CONTENT) + ) + + assert excinfo.match( + "Unable to parse subject_token from JSON file '{}' using key '{}'".format( + self.CREDENTIAL_URL, "not_found" + ) + ) + + def test_retrieve_subject_token_from_url_json_invalid_format(self): + credentials = self.make_credentials( + credential_source=self.CREDENTIAL_SOURCE_JSON_URL + ) + + with pytest.raises(exceptions.RefreshError) as excinfo: + credentials.retrieve_subject_token(self.make_mock_request(token_data="{")) + + assert excinfo.match( + "Unable to parse subject_token from JSON file '{}' using key '{}'".format( + self.CREDENTIAL_URL, "access_token" + ) + ) + + def test_refresh_text_file_success_without_impersonation_url(self): + credentials = self.make_credentials( + client_id=CLIENT_ID, + client_secret=CLIENT_SECRET, + # Test with text format type. + credential_source=self.CREDENTIAL_SOURCE_TEXT_URL, + scopes=SCOPES, + ) + + self.assert_underlying_credentials_refresh( + credentials=credentials, + audience=AUDIENCE, + subject_token=TEXT_FILE_SUBJECT_TOKEN, + subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + service_account_impersonation_url=None, + basic_auth_encoding=BASIC_AUTH_ENCODING, + quota_project_id=None, + used_scopes=SCOPES, + scopes=SCOPES, + default_scopes=None, + credential_data=TEXT_FILE_SUBJECT_TOKEN, + ) + + def test_refresh_text_file_success_with_impersonation_url(self): + # Initialize credentials with service account impersonation and basic auth. + credentials = self.make_credentials( + # Test with text format type. + credential_source=self.CREDENTIAL_SOURCE_TEXT_URL, + service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + scopes=SCOPES, + ) + + self.assert_underlying_credentials_refresh( + credentials=credentials, + audience=AUDIENCE, + subject_token=TEXT_FILE_SUBJECT_TOKEN, + subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + basic_auth_encoding=None, + quota_project_id=None, + used_scopes=SCOPES, + scopes=SCOPES, + default_scopes=None, + credential_data=TEXT_FILE_SUBJECT_TOKEN, + ) + + def test_refresh_json_file_success_without_impersonation_url(self): + credentials = self.make_credentials( + client_id=CLIENT_ID, + client_secret=CLIENT_SECRET, + # Test with JSON format type. + credential_source=self.CREDENTIAL_SOURCE_JSON_URL, + scopes=SCOPES, + ) + + self.assert_underlying_credentials_refresh( + credentials=credentials, + audience=AUDIENCE, + subject_token=JSON_FILE_SUBJECT_TOKEN, + subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + service_account_impersonation_url=None, + basic_auth_encoding=BASIC_AUTH_ENCODING, + quota_project_id=None, + used_scopes=SCOPES, + scopes=SCOPES, + default_scopes=None, + credential_data=JSON_FILE_CONTENT, + ) + + def test_refresh_json_file_success_with_impersonation_url(self): + # Initialize credentials with service account impersonation and basic auth. + credentials = self.make_credentials( + # Test with JSON format type. + credential_source=self.CREDENTIAL_SOURCE_JSON_URL, + service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + scopes=SCOPES, + ) + + self.assert_underlying_credentials_refresh( + credentials=credentials, + audience=AUDIENCE, + subject_token=JSON_FILE_SUBJECT_TOKEN, + subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + basic_auth_encoding=None, + quota_project_id=None, + used_scopes=SCOPES, + scopes=SCOPES, + default_scopes=None, + credential_data=JSON_FILE_CONTENT, + ) + + def test_refresh_with_retrieve_subject_token_error_url(self): + credential_source = { + "url": self.CREDENTIAL_URL, + "format": {"type": "json", "subject_token_field_name": "not_found"}, + } + credentials = self.make_credentials(credential_source=credential_source) + + with pytest.raises(exceptions.RefreshError) as excinfo: + credentials.refresh(self.make_mock_request(token_data=JSON_FILE_CONTENT)) + + assert excinfo.match( + "Unable to parse subject_token from JSON file '{}' using key '{}'".format( + self.CREDENTIAL_URL, "not_found" + ) + ) diff --git a/packages/google-auth/tests/test_impersonated_credentials.py b/packages/google-auth/tests/test_impersonated_credentials.py index 305f9392667a..430c770d3672 100644 --- a/packages/google-auth/tests/test_impersonated_credentials.py +++ b/packages/google-auth/tests/test_impersonated_credentials.py @@ -104,12 +104,17 @@ class TestImpersonatedCredentials(object): SIGNER, SERVICE_ACCOUNT_EMAIL, TOKEN_URI ) USER_SOURCE_CREDENTIALS = credentials.Credentials(token="ABCDE") + IAM_ENDPOINT_OVERRIDE = ( + "https://us-east1-iamcredentials.googleapis.com/v1/projects/-" + + "/serviceAccounts/{}:generateAccessToken".format(SERVICE_ACCOUNT_EMAIL) + ) def make_credentials( self, source_credentials=SOURCE_CREDENTIALS, lifetime=LIFETIME, target_principal=TARGET_PRINCIPAL, + iam_endpoint_override=None, ): return Credentials( @@ -118,6 +123,7 @@ def make_credentials( target_scopes=self.TARGET_SCOPES, delegates=self.DELEGATES, lifetime=lifetime, + iam_endpoint_override=iam_endpoint_override, ) def test_make_from_user_credentials(self): @@ -172,6 +178,34 @@ def test_refresh_success(self, use_data_bytes, mock_donor_credentials): assert credentials.valid assert not credentials.expired + @pytest.mark.parametrize("use_data_bytes", [True, False]) + def test_refresh_success_iam_endpoint_override( + self, use_data_bytes, mock_donor_credentials + ): + credentials = self.make_credentials( + lifetime=None, iam_endpoint_override=self.IAM_ENDPOINT_OVERRIDE + ) + token = "token" + + expire_time = ( + _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=500) + ).isoformat("T") + "Z" + response_body = {"accessToken": token, "expireTime": expire_time} + + request = self.make_request( + data=json.dumps(response_body), + status=http_client.OK, + use_data_bytes=use_data_bytes, + ) + + credentials.refresh(request) + + assert credentials.valid + assert not credentials.expired + # Confirm override endpoint used. + request_kwargs = request.call_args.kwargs + assert request_kwargs["url"] == self.IAM_ENDPOINT_OVERRIDE + @pytest.mark.parametrize("time_skew", [100, -100]) def test_refresh_source_credentials(self, time_skew): credentials = self.make_credentials(lifetime=None) @@ -317,6 +351,36 @@ def test_with_quota_project(self): quota_project_creds = credentials.with_quota_project("project-foo") assert quota_project_creds._quota_project_id == "project-foo" + @pytest.mark.parametrize("use_data_bytes", [True, False]) + def test_with_quota_project_iam_endpoint_override( + self, use_data_bytes, mock_donor_credentials + ): + credentials = self.make_credentials( + lifetime=None, iam_endpoint_override=self.IAM_ENDPOINT_OVERRIDE + ) + token = "token" + # iam_endpoint_override should be copied to created credentials. + quota_project_creds = credentials.with_quota_project("project-foo") + + expire_time = ( + _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=500) + ).isoformat("T") + "Z" + response_body = {"accessToken": token, "expireTime": expire_time} + + request = self.make_request( + data=json.dumps(response_body), + status=http_client.OK, + use_data_bytes=use_data_bytes, + ) + + quota_project_creds.refresh(request) + + assert quota_project_creds.valid + assert not quota_project_creds.expired + # Confirm override endpoint used. + request_kwargs = request.call_args.kwargs + assert request_kwargs["url"] == self.IAM_ENDPOINT_OVERRIDE + def test_id_token_success( self, mock_donor_credentials, mock_authorizedsession_idtoken ): From 0d8f5a8482b9b63656adc410be99c2c5727dd6c3 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Wed, 10 Feb 2021 11:21:32 -0700 Subject: [PATCH 377/966] chore: release 1.26.0 (#689) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 88bb81b247d3..21298a8c0fcf 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +## [1.26.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.25.0...v1.26.0) (2021-02-09) + + +### Features + +* workload identity federation support ([#686](https://www.github.com/googleapis/google-auth-library-python/issues/686)) ([5dcd2b1](https://www.github.com/googleapis/google-auth-library-python/commit/5dcd2b1bdd9d21522636d959cffc49ee29dda88f)) + ## [1.25.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.24.0...v1.25.0) (2021-02-03) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index ba72c3ccf782..2f16e44ff9ec 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -34,7 +34,7 @@ with io.open("README.rst", "r") as fh: long_description = fh.read() -version = "1.25.0" +version = "1.26.0" setup( name="google-auth", From d8783b3adc61ebcd7c4fb9bcd344f14714c0b8ef Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Thu, 11 Feb 2021 12:53:26 -0700 Subject: [PATCH 378/966] Revert "feat: workload identity federation support (#686)" (#691) This reverts commit 5dcd2b1bdd9d21522636d959cffc49ee29dda88f. --- packages/google-auth/docs/index.rst | 2 - .../docs/reference/google.auth.aws.rst | 7 - .../google.auth.external_account.rst | 7 - .../reference/google.auth.identity_pool.rst | 7 - .../docs/reference/google.auth.rst | 3 - .../docs/reference/google.oauth2.rst | 2 - .../docs/reference/google.oauth2.sts.rst | 7 - .../docs/reference/google.oauth2.utils.rst | 7 - packages/google-auth/docs/user-guide.rst | 173 +- packages/google-auth/google/auth/_default.py | 146 +- packages/google-auth/google/auth/aws.py | 714 -------- .../google/auth/environment_vars.py | 10 - .../google-auth/google/auth/exceptions.py | 5 - .../google/auth/external_account.py | 305 ---- .../google-auth/google/auth/identity_pool.py | 279 ---- .../google/auth/impersonated_credentials.py | 16 +- packages/google-auth/google/oauth2/sts.py | 155 -- packages/google-auth/google/oauth2/utils.py | 171 -- packages/google-auth/noxfile.py | 2 + .../system_tests_sync/secrets.tar.enc | Bin 10323 -> 0 bytes .../tests/data/external_subject_token.json | 3 - .../tests/data/external_subject_token.txt | 1 - packages/google-auth/tests/oauth2/test_sts.py | 395 ----- .../google-auth/tests/oauth2/test_utils.py | 264 --- packages/google-auth/tests/test__default.py | 219 +-- packages/google-auth/tests/test_aws.py | 1434 ----------------- .../tests/test_external_account.py | 1095 ------------- .../google-auth/tests/test_identity_pool.py | 873 ---------- .../tests/test_impersonated_credentials.py | 64 - 29 files changed, 22 insertions(+), 6344 deletions(-) delete mode 100644 packages/google-auth/docs/reference/google.auth.aws.rst delete mode 100644 packages/google-auth/docs/reference/google.auth.external_account.rst delete mode 100644 packages/google-auth/docs/reference/google.auth.identity_pool.rst delete mode 100644 packages/google-auth/docs/reference/google.oauth2.sts.rst delete mode 100644 packages/google-auth/docs/reference/google.oauth2.utils.rst delete mode 100644 packages/google-auth/google/auth/aws.py delete mode 100644 packages/google-auth/google/auth/external_account.py delete mode 100644 packages/google-auth/google/auth/identity_pool.py delete mode 100644 packages/google-auth/google/oauth2/sts.py delete mode 100644 packages/google-auth/google/oauth2/utils.py delete mode 100644 packages/google-auth/system_tests/system_tests_sync/secrets.tar.enc delete mode 100644 packages/google-auth/tests/data/external_subject_token.json delete mode 100644 packages/google-auth/tests/data/external_subject_token.txt delete mode 100644 packages/google-auth/tests/oauth2/test_sts.py delete mode 100644 packages/google-auth/tests/oauth2/test_utils.py delete mode 100644 packages/google-auth/tests/test_aws.py delete mode 100644 packages/google-auth/tests/test_external_account.py delete mode 100644 packages/google-auth/tests/test_identity_pool.py diff --git a/packages/google-auth/docs/index.rst b/packages/google-auth/docs/index.rst index 17169109a4df..4287c3db3eb5 100644 --- a/packages/google-auth/docs/index.rst +++ b/packages/google-auth/docs/index.rst @@ -20,8 +20,6 @@ also provides integration with several HTTP libraries. - Support for Google :mod:`Impersonated Credentials `. - Support for :mod:`Google Compute Engine credentials `. - Support for :mod:`Google App Engine standard credentials `. -- Support for :mod:`Identity Pool credentials `. -- Support for :mod:`AWS credentials `. - Support for various transports, including :mod:`Requests `, :mod:`urllib3 `, and diff --git a/packages/google-auth/docs/reference/google.auth.aws.rst b/packages/google-auth/docs/reference/google.auth.aws.rst deleted file mode 100644 index 9c3966bbad73..000000000000 --- a/packages/google-auth/docs/reference/google.auth.aws.rst +++ /dev/null @@ -1,7 +0,0 @@ -google.auth.aws module -====================== - -.. automodule:: google.auth.aws - :members: - :inherited-members: - :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.auth.external_account.rst b/packages/google-auth/docs/reference/google.auth.external_account.rst deleted file mode 100644 index 0681eaa277a2..000000000000 --- a/packages/google-auth/docs/reference/google.auth.external_account.rst +++ /dev/null @@ -1,7 +0,0 @@ -google.auth.external\_account module -==================================== - -.. automodule:: google.auth.external_account - :members: - :inherited-members: - :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.auth.identity_pool.rst b/packages/google-auth/docs/reference/google.auth.identity_pool.rst deleted file mode 100644 index 48d9902236dd..000000000000 --- a/packages/google-auth/docs/reference/google.auth.identity_pool.rst +++ /dev/null @@ -1,7 +0,0 @@ -google.auth.identity\_pool module -================================= - -.. automodule:: google.auth.identity_pool - :members: - :inherited-members: - :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.auth.rst b/packages/google-auth/docs/reference/google.auth.rst index e21eaf9e31df..3acf7dfb8d99 100644 --- a/packages/google-auth/docs/reference/google.auth.rst +++ b/packages/google-auth/docs/reference/google.auth.rst @@ -23,14 +23,11 @@ Submodules :maxdepth: 4 google.auth.app_engine - google.auth.aws google.auth.credentials google.auth._credentials_async google.auth.environment_vars google.auth.exceptions - google.auth.external_account google.auth.iam - google.auth.identity_pool google.auth.impersonated_credentials google.auth.jwt google.auth.jwt_async diff --git a/packages/google-auth/docs/reference/google.oauth2.rst b/packages/google-auth/docs/reference/google.oauth2.rst index 2a8a7a5885d2..6f3ba50c217c 100644 --- a/packages/google-auth/docs/reference/google.oauth2.rst +++ b/packages/google-auth/docs/reference/google.oauth2.rst @@ -17,5 +17,3 @@ Submodules google.oauth2.id_token google.oauth2.service_account google.oauth2._service_account_async - google.oauth2.sts - google.oauth2.utils diff --git a/packages/google-auth/docs/reference/google.oauth2.sts.rst b/packages/google-auth/docs/reference/google.oauth2.sts.rst deleted file mode 100644 index 49d99dfe66f0..000000000000 --- a/packages/google-auth/docs/reference/google.oauth2.sts.rst +++ /dev/null @@ -1,7 +0,0 @@ -google.oauth2.sts module -======================== - -.. automodule:: google.oauth2.sts - :members: - :inherited-members: - :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.oauth2.utils.rst b/packages/google-auth/docs/reference/google.oauth2.utils.rst deleted file mode 100644 index 5b039eac825c..000000000000 --- a/packages/google-auth/docs/reference/google.oauth2.utils.rst +++ /dev/null @@ -1,7 +0,0 @@ -google.oauth2.utils module -========================== - -.. automodule:: google.oauth2.utils - :members: - :inherited-members: - :show-inheritance: diff --git a/packages/google-auth/docs/user-guide.rst b/packages/google-auth/docs/user-guide.rst index 7332bd48e0b1..08e7167dfe53 100644 --- a/packages/google-auth/docs/user-guide.rst +++ b/packages/google-auth/docs/user-guide.rst @@ -7,8 +7,8 @@ Credentials and account types ----------------------------- :class:`~credentials.Credentials` are the means of identifying an application or -user to a service or API. Credentials can be obtained with three different types -of accounts: *service accounts*, *user accounts* and *external accounts*. +user to a service or API. Credentials can be obtained with two different types +of accounts: *service accounts* and *user accounts*. Credentials from service accounts identify a particular application. These types of credentials are used in server-to-server use cases, such as accessing a @@ -21,11 +21,6 @@ a user's documents in Google Drive. This library provides no support for obtaining user credentials, but does provide limited support for using user credentials. -Credentials from external accounts (workload identity federation) are used to -identify a particular application from an on-prem or non-Google Cloud platform -including Amazon Web Services (AWS), Microsoft Azure or any identity provider -that supports OpenID Connect (OIDC). - Obtaining credentials --------------------- @@ -49,13 +44,6 @@ If your application requires specific scopes:: credentials, project = google.auth.default( scopes=['https://www.googleapis.com/auth/cloud-platform']) -Application Default Credentials also support workload identity federation to -access Google Cloud resources from non-Google Cloud platforms including Amazon -Web Services (AWS), Microsoft Azure or any identity provider that supports -OpenID Connect (OIDC). Workload identity federation is recommended for -non-Google Cloud environments as it avoids the need to download, manage and -store service account private keys locally. - .. _Google Application Default Credentials: https://developers.google.com/identity/protocols/ application-default-credentials @@ -231,163 +219,6 @@ You can also use :class:`google_auth_oauthlib.flow.Flow` to perform the OAuth .. _requests-oauthlib: https://requests-oauthlib.readthedocs.io/en/latest/ -External credentials (Workload identity federation) -+++++++++++++++++++++++++++++++++++++++++++++++++++ - -Using workload identity federation, your application can access Google Cloud -resources from Amazon Web Services (AWS), Microsoft Azure or any identity -provider that supports OpenID Connect (OIDC). - -Traditionally, applications running outside Google Cloud have used service -account keys to access Google Cloud resources. Using identity federation, -you can allow your workload to impersonate a service account. -This lets you access Google Cloud resources directly, eliminating the -maintenance and security burden associated with service account keys. - -Accessing resources from AWS -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -In order to access Google Cloud resources from Amazon Web Services (AWS), the -following requirements are needed: - -- A workload identity pool needs to be created. -- AWS needs to be added as an identity provider in the workload identity pool - (The Google organization policy needs to allow federation from AWS). -- Permission to impersonate a service account needs to be granted to the - external identity. -- A credential configuration file needs to be generated. Unlike service account - credential files, the generated credential configuration file will only - contain non-sensitive metadata to instruct the library on how to retrieve - external subject tokens and exchange them for service account access tokens. - -Follow the detailed instructions on how to -`Configure Workload Identity Federation from AWS`_. - -.. _Configure Workload Identity Federation from AWS: - https://cloud.google.com/iam/docs/access-resources-aws - -Accessing resources from Microsoft Azure -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -In order to access Google Cloud resources from Microsoft Azure, the following -requirements are needed: - -- A workload identity pool needs to be created. -- Azure needs to be added as an identity provider in the workload identity pool - (The Google organization policy needs to allow federation from Azure). -- The Azure tenant needs to be configured for identity federation. -- Permission to impersonate a service account needs to be granted to the - external identity. -- A credential configuration file needs to be generated. Unlike service account - credential files, the generated credential configuration file will only - contain non-sensitive metadata to instruct the library on how to retrieve - external subject tokens and exchange them for service account access tokens. - -Follow the detailed instructions on how to -`Configure Workload Identity Federation from Microsoft Azure`_. - -.. _Configure Workload Identity Federation from Microsoft Azure: - https://cloud.google.com/iam/docs/access-resources-azure - -Accessing resources from an OIDC identity provider -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -In order to access Google Cloud resources from an identity provider that -supports `OpenID Connect (OIDC)`_, the following requirements are needed: - -- A workload identity pool needs to be created. -- An OIDC identity provider needs to be added in the workload identity pool - (The Google organization policy needs to allow federation from the identity - provider). -- Permission to impersonate a service account needs to be granted to the - external identity. -- A credential configuration file needs to be generated. Unlike service account - credential files, the generated credential configuration file will only - contain non-sensitive metadata to instruct the library on how to retrieve - external subject tokens and exchange them for service account access tokens. - -For OIDC providers, the Auth library can retrieve OIDC tokens either from a -local file location (file-sourced credentials) or from a local server -(URL-sourced credentials). - -- For file-sourced credentials, a background process needs to be continuously - refreshing the file location with a new OIDC token prior to expiration. - For tokens with one hour lifetimes, the token needs to be updated in the file - every hour. The token can be stored directly as plain text or in JSON format. -- For URL-sourced credentials, a local server needs to host a GET endpoint to - return the OIDC token. The response can be in plain text or JSON. - Additional required request headers can also be specified. - -Follow the detailed instructions on how to -`Configure Workload Identity Federation from an OIDC identity provider`_. - -.. _OpenID Connect (OIDC): - https://openid.net/connect/ -.. _Configure Workload Identity Federation from an OIDC identity provider: - https://cloud.google.com/iam/docs/access-resources-oidc - -Using External Identities -~~~~~~~~~~~~~~~~~~~~~~~~~ - -External identities (AWS, Azure and OIDC identity providers) can be used with -Application Default Credentials. -In order to use external identities with Application Default Credentials, you -need to generate the JSON credentials configuration file for your external -identity. -Once generated, store the path to this file in the -``GOOGLE_APPLICATION_CREDENTIALS`` environment variable. - -.. code-block:: bash - - $ export GOOGLE_APPLICATION_CREDENTIALS=/path/to/config.json - -The library can now automatically choose the right type of client and initialize -credentials from the context provided in the configuration file:: - - import google.auth - - credentials, project = google.auth.default() - -When using external identities with Application Default Credentials, -the ``roles/browser`` role needs to be granted to the service account. -The ``Cloud Resource Manager API`` should also be enabled on the project. -This is needed since :func:`default` will try to auto-discover the project ID -from the current environment using the impersonated credential. -Otherwise, the project ID will resolve to ``None``. You can override the project -detection by setting the ``GOOGLE_CLOUD_PROJECT`` environment variable. - -You can also explicitly initialize external account clients using the generated -configuration file. - -For Azure and OIDC providers, use :meth:`identity_pool.Credentials.from_info -` or -:meth:`identity_pool.Credentials.from_file -`:: - - import json - - from google.auth import identity_pool - - json_config_info = json.loads(function_to_get_json_config()) - credentials = identity_pool.Credentials.from_info(json_config_info) - scoped_credentials = credentials.with_scopes( - ['https://www.googleapis.com/auth/cloud-platform']) - -For AWS providers, use :meth:`aws.Credentials.from_info -` or -:meth:`aws.Credentials.from_file -`:: - - import json - - from google.auth import aws - - json_config_info = json.loads(function_to_get_json_config()) - credentials = aws.Credentials.from_info(json_config_info) - scoped_credentials = credentials.with_scopes( - ['https://www.googleapis.com/auth/cloud-platform']) - - Impersonated credentials ++++++++++++++++++++++++ diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index 836c33915bed..3b8c281e72b6 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -34,8 +34,7 @@ # Valid types accepted for file-based credentials. _AUTHORIZED_USER_TYPE = "authorized_user" _SERVICE_ACCOUNT_TYPE = "service_account" -_EXTERNAL_ACCOUNT_TYPE = "external_account" -_VALID_TYPES = (_AUTHORIZED_USER_TYPE, _SERVICE_ACCOUNT_TYPE, _EXTERNAL_ACCOUNT_TYPE) +_VALID_TYPES = (_AUTHORIZED_USER_TYPE, _SERVICE_ACCOUNT_TYPE) # Help message when no credentials can be found. _HELP_MESSAGE = """\ @@ -71,12 +70,12 @@ def _warn_about_problematic_credentials(credentials): def load_credentials_from_file( - filename, scopes=None, default_scopes=None, quota_project_id=None, request=None + filename, scopes=None, default_scopes=None, quota_project_id=None ): """Loads Google credentials from a file. - The credentials file must be a service account key, stored authorized - user credentials or external account credentials. + The credentials file must be a service account key or stored authorized + user credentials. Args: filename (str): The full path to the credentials file. @@ -86,18 +85,12 @@ def load_credentials_from_file( default_scopes (Optional[Sequence[str]]): Default scopes passed by a Google client library. Use 'scopes' for user-defined scopes. quota_project_id (Optional[str]): The project ID used for - quota and billing. - request (Optional[google.auth.transport.Request]): An object used to make - HTTP requests. This is used to determine the associated project ID - for a workload identity pool resource (external account credentials). - If not specified, then it will use a - google.auth.transport.requests.Request client to make requests. + quota and billing. Returns: Tuple[google.auth.credentials.Credentials, Optional[str]]: Loaded credentials and the project ID. Authorized user credentials do not - have the project ID information. External account credentials project - IDs may not always be determined. + have the project ID information. Raises: google.auth.exceptions.DefaultCredentialsError: if the file is in the @@ -153,18 +146,6 @@ def load_credentials_from_file( credentials = credentials.with_quota_project(quota_project_id) return credentials, info.get("project_id") - elif credential_type == _EXTERNAL_ACCOUNT_TYPE: - credentials, project_id = _get_external_account_credentials( - info, - filename, - scopes=scopes, - default_scopes=default_scopes, - request=request, - ) - if quota_project_id: - credentials = credentials.with_quota_project(quota_project_id) - return credentials, project_id - else: raise exceptions.DefaultCredentialsError( "The file {file} does not have a valid type. " @@ -195,28 +176,9 @@ def _get_gcloud_sdk_credentials(): return credentials, project_id -def _get_explicit_environ_credentials(request=None, scopes=None, default_scopes=None): +def _get_explicit_environ_credentials(): """Gets credentials from the GOOGLE_APPLICATION_CREDENTIALS environment - variable. - - Args: - request (Optional[google.auth.transport.Request]): An object used to make - HTTP requests. This is used to determine the associated project ID - for a workload identity pool resource (external account credentials). - If not specified, then it will use a - google.auth.transport.requests.Request client to make requests. - scopes (Optional[Sequence[str]]): The list of scopes for the credentials. If - specified, the credentials will automatically be scoped if - necessary. - default_scopes (Optional[Sequence[str]]): Default scopes passed by a - Google client library. Use 'scopes' for user-defined scopes. - - Returns: - Tuple[Optional[google.auth.credentials.Credentials], Optional[str]]: Loaded - credentials and the project ID. Authorized user credentials do not - have the project ID information. External account credentials project - IDs may not always be determined. - """ + variable.""" explicit_file = os.environ.get(environment_vars.CREDENTIALS) _LOGGER.debug( @@ -225,11 +187,7 @@ def _get_explicit_environ_credentials(request=None, scopes=None, default_scopes= if explicit_file is not None: credentials, project_id = load_credentials_from_file( - os.environ[environment_vars.CREDENTIALS], - scopes=scopes, - default_scopes=default_scopes, - quota_project_id=None, - request=request, + os.environ[environment_vars.CREDENTIALS] ) return credentials, project_id @@ -294,65 +252,6 @@ def _get_gce_credentials(request=None): return None, None -def _get_external_account_credentials( - info, filename, scopes=None, default_scopes=None, request=None -): - """Loads external account Credentials from the parsed external account info. - - The credentials information must correspond to a supported external account - credentials. - - Args: - info (Mapping[str, str]): The external account info in Google format. - filename (str): The full path to the credentials file. - scopes (Optional[Sequence[str]]): The list of scopes for the credentials. If - specified, the credentials will automatically be scoped if - necessary. - default_scopes (Optional[Sequence[str]]): Default scopes passed by a - Google client library. Use 'scopes' for user-defined scopes. - request (Optional[google.auth.transport.Request]): An object used to make - HTTP requests. This is used to determine the associated project ID - for a workload identity pool resource (external account credentials). - If not specified, then it will use a - google.auth.transport.requests.Request client to make requests. - - Returns: - Tuple[google.auth.credentials.Credentials, Optional[str]]: Loaded - credentials and the project ID. External account credentials project - IDs may not always be determined. - - Raises: - google.auth.exceptions.DefaultCredentialsError: if the info dictionary - is in the wrong format or is missing required information. - """ - # There are currently 2 types of external_account credentials. - try: - # Check if configuration corresponds to an AWS credentials. - from google.auth import aws - - credentials = aws.Credentials.from_info( - info, scopes=scopes, default_scopes=default_scopes - ) - except ValueError: - try: - # Check if configuration corresponds to an Identity Pool credentials. - from google.auth import identity_pool - - credentials = identity_pool.Credentials.from_info( - info, scopes=scopes, default_scopes=default_scopes - ) - except ValueError: - # If the configuration is invalid or does not correspond to any - # supported external_account credentials, raise an error. - raise exceptions.DefaultCredentialsError( - "Failed to load external account credentials from {}".format(filename) - ) - if request is None: - request = google.auth.transport.requests.Request() - - return credentials, credentials.get_project_id(request=request) - - def default(scopes=None, request=None, quota_project_id=None, default_scopes=None): """Gets the default credentials for the current environment. @@ -366,15 +265,6 @@ def default(scopes=None, request=None, quota_project_id=None, default_scopes=Non loaded and returned. The project ID returned is the project ID defined in the service account file if available (some older files do not contain project ID information). - - If the environment variable is set to the path of a valid external - account JSON configuration file (workload identity federation), then the - configuration file is used to determine and retrieve the external - credentials from the current environment (AWS, Azure, etc). - These will then be exchanged for Google access tokens via the Google STS - endpoint. - The project ID returned in this case is the one corresponding to the - underlying workload identity pool resource if determinable. 2. If the `Google Cloud SDK`_ is installed and has application default credentials set they are loaded and returned. @@ -420,15 +310,11 @@ def default(scopes=None, request=None, quota_project_id=None, default_scopes=Non scopes (Sequence[str]): The list of scopes for the credentials. If specified, the credentials will automatically be scoped if necessary. - request (Optional[google.auth.transport.Request]): An object used to make - HTTP requests. This is used to either detect whether the application - is running on Compute Engine or to determine the associated project - ID for a workload identity pool resource (external account - credentials). If not specified, then it will either use the standard - library http client to make requests for Compute Engine credentials - or a google.auth.transport.requests.Request client for external - account credentials. - quota_project_id (Optional[str]): The project ID used for + request (google.auth.transport.Request): An object used to make + HTTP requests. This is used to detect whether the application + is running on Compute Engine. If not specified, then it will + use the standard library http client to make requests. + quota_project_id (Optional[str]): The project ID used for quota and billing. default_scopes (Optional[Sequence[str]]): Default scopes passed by a Google client library. Use 'scopes' for user-defined scopes. @@ -450,9 +336,7 @@ def default(scopes=None, request=None, quota_project_id=None, default_scopes=Non ) checkers = ( - lambda: _get_explicit_environ_credentials( - request=request, scopes=scopes, default_scopes=default_scopes - ), + _get_explicit_environ_credentials, _get_gcloud_sdk_credentials, _get_gae_credentials, lambda: _get_gce_credentials(request), diff --git a/packages/google-auth/google/auth/aws.py b/packages/google-auth/google/auth/aws.py deleted file mode 100644 index b362dd315299..000000000000 --- a/packages/google-auth/google/auth/aws.py +++ /dev/null @@ -1,714 +0,0 @@ -# Copyright 2020 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""AWS Credentials and AWS Signature V4 Request Signer. - -This module provides credentials to access Google Cloud resources from Amazon -Web Services (AWS) workloads. These credentials are recommended over the -use of service account credentials in AWS as they do not involve the management -of long-live service account private keys. - -AWS Credentials are initialized using external_account arguments which are -typically loaded from the external credentials JSON file. -Unlike other Credentials that can be initialized with a list of explicit -arguments, secrets or credentials, external account clients use the -environment and hints/guidelines provided by the external_account JSON -file to retrieve credentials and exchange them for Google access tokens. - -This module also provides a basic implementation of the -`AWS Signature Version 4`_ request signing algorithm. - -AWS Credentials use serialized signed requests to the -`AWS STS GetCallerIdentity`_ API that can be exchanged for Google access tokens -via the GCP STS endpoint. - -.. _AWS Signature Version 4: https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html -.. _AWS STS GetCallerIdentity: https://docs.aws.amazon.com/STS/latest/APIReference/API_GetCallerIdentity.html -""" - -import hashlib -import hmac -import io -import json -import os -import re - -from six.moves import http_client -from six.moves import urllib - -from google.auth import _helpers -from google.auth import environment_vars -from google.auth import exceptions -from google.auth import external_account - -# AWS Signature Version 4 signing algorithm identifier. -_AWS_ALGORITHM = "AWS4-HMAC-SHA256" -# The termination string for the AWS credential scope value as defined in -# https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html -_AWS_REQUEST_TYPE = "aws4_request" -# The AWS authorization header name for the security session token if available. -_AWS_SECURITY_TOKEN_HEADER = "x-amz-security-token" -# The AWS authorization header name for the auto-generated date. -_AWS_DATE_HEADER = "x-amz-date" - - -class RequestSigner(object): - """Implements an AWS request signer based on the AWS Signature Version 4 signing - process. - https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html - """ - - def __init__(self, region_name): - """Instantiates an AWS request signer used to compute authenticated signed - requests to AWS APIs based on the AWS Signature Version 4 signing process. - - Args: - region_name (str): The AWS region to use. - """ - - self._region_name = region_name - - def get_request_options( - self, - aws_security_credentials, - url, - method, - request_payload="", - additional_headers={}, - ): - """Generates the signed request for the provided HTTP request for calling - an AWS API. This follows the steps described at: - https://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html - - Args: - aws_security_credentials (Mapping[str, str]): A dictionary containing - the AWS security credentials. - url (str): The AWS service URL containing the canonical URI and - query string. - method (str): The HTTP method used to call this API. - request_payload (Optional[str]): The optional request payload if - available. - additional_headers (Optional[Mapping[str, str]]): The optional - additional headers needed for the requested AWS API. - - Returns: - Mapping[str, str]: The AWS signed request dictionary object. - """ - # Get AWS credentials. - access_key = aws_security_credentials.get("access_key_id") - secret_key = aws_security_credentials.get("secret_access_key") - security_token = aws_security_credentials.get("security_token") - - additional_headers = additional_headers or {} - - uri = urllib.parse.urlparse(url) - # Validate provided URL. - if not uri.hostname or uri.scheme != "https": - raise ValueError("Invalid AWS service URL") - - header_map = _generate_authentication_header_map( - host=uri.hostname, - canonical_uri=os.path.normpath(uri.path or "/"), - canonical_querystring=_get_canonical_querystring(uri.query), - method=method, - region=self._region_name, - access_key=access_key, - secret_key=secret_key, - security_token=security_token, - request_payload=request_payload, - additional_headers=additional_headers, - ) - headers = { - "Authorization": header_map.get("authorization_header"), - "host": uri.hostname, - } - # Add x-amz-date if available. - if "amz_date" in header_map: - headers[_AWS_DATE_HEADER] = header_map.get("amz_date") - # Append additional optional headers, eg. X-Amz-Target, Content-Type, etc. - for key in additional_headers: - headers[key] = additional_headers[key] - - # Add session token if available. - if security_token is not None: - headers[_AWS_SECURITY_TOKEN_HEADER] = security_token - - signed_request = {"url": url, "method": method, "headers": headers} - if request_payload: - signed_request["data"] = request_payload - return signed_request - - -def _get_canonical_querystring(query): - """Generates the canonical query string given a raw query string. - Logic is based on - https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html - - Args: - query (str): The raw query string. - - Returns: - str: The canonical query string. - """ - # Parse raw query string. - querystring = urllib.parse.parse_qs(query) - querystring_encoded_map = {} - for key in querystring: - quote_key = urllib.parse.quote(key, safe="-_.~") - # URI encode key. - querystring_encoded_map[quote_key] = [] - for item in querystring[key]: - # For each key, URI encode all values for that key. - querystring_encoded_map[quote_key].append( - urllib.parse.quote(item, safe="-_.~") - ) - # Sort values for each key. - querystring_encoded_map[quote_key].sort() - # Sort keys. - sorted_keys = list(querystring_encoded_map.keys()) - sorted_keys.sort() - # Reconstruct the query string. Preserve keys with multiple values. - querystring_encoded_pairs = [] - for key in sorted_keys: - for item in querystring_encoded_map[key]: - querystring_encoded_pairs.append("{}={}".format(key, item)) - return "&".join(querystring_encoded_pairs) - - -def _sign(key, msg): - """Creates the HMAC-SHA256 hash of the provided message using the provided - key. - - Args: - key (str): The HMAC-SHA256 key to use. - msg (str): The message to hash. - - Returns: - str: The computed hash bytes. - """ - return hmac.new(key, msg.encode("utf-8"), hashlib.sha256).digest() - - -def _get_signing_key(key, date_stamp, region_name, service_name): - """Calculates the signing key used to calculate the signature for - AWS Signature Version 4 based on: - https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html - - Args: - key (str): The AWS secret access key. - date_stamp (str): The '%Y%m%d' date format. - region_name (str): The AWS region. - service_name (str): The AWS service name, eg. sts. - - Returns: - str: The signing key bytes. - """ - k_date = _sign(("AWS4" + key).encode("utf-8"), date_stamp) - k_region = _sign(k_date, region_name) - k_service = _sign(k_region, service_name) - k_signing = _sign(k_service, "aws4_request") - return k_signing - - -def _generate_authentication_header_map( - host, - canonical_uri, - canonical_querystring, - method, - region, - access_key, - secret_key, - security_token, - request_payload="", - additional_headers={}, -): - """Generates the authentication header map needed for generating the AWS - Signature Version 4 signed request. - - Args: - host (str): The AWS service URL hostname. - canonical_uri (str): The AWS service URL path name. - canonical_querystring (str): The AWS service URL query string. - method (str): The HTTP method used to call this API. - region (str): The AWS region. - access_key (str): The AWS access key ID. - secret_key (str): The AWS secret access key. - security_token (Optional[str]): The AWS security session token. This is - available for temporary sessions. - request_payload (Optional[str]): The optional request payload if - available. - additional_headers (Optional[Mapping[str, str]]): The optional - additional headers needed for the requested AWS API. - - Returns: - Mapping[str, str]: The AWS authentication header dictionary object. - This contains the x-amz-date and authorization header information. - """ - # iam.amazonaws.com host => iam service. - # sts.us-east-2.amazonaws.com host => sts service. - service_name = host.split(".")[0] - - current_time = _helpers.utcnow() - amz_date = current_time.strftime("%Y%m%dT%H%M%SZ") - date_stamp = current_time.strftime("%Y%m%d") - - # Change all additional headers to be lower case. - full_headers = {} - for key in additional_headers: - full_headers[key.lower()] = additional_headers[key] - # Add AWS session token if available. - if security_token is not None: - full_headers[_AWS_SECURITY_TOKEN_HEADER] = security_token - - # Required headers - full_headers["host"] = host - # Do not use generated x-amz-date if the date header is provided. - # Previously the date was not fixed with x-amz- and could be provided - # manually. - # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/get-header-value-trim.req - if "date" not in full_headers: - full_headers[_AWS_DATE_HEADER] = amz_date - - # Header keys need to be sorted alphabetically. - canonical_headers = "" - header_keys = list(full_headers.keys()) - header_keys.sort() - for key in header_keys: - canonical_headers = "{}{}:{}\n".format( - canonical_headers, key, full_headers[key] - ) - signed_headers = ";".join(header_keys) - - payload_hash = hashlib.sha256((request_payload or "").encode("utf-8")).hexdigest() - - # https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html - canonical_request = "{}\n{}\n{}\n{}\n{}\n{}".format( - method, - canonical_uri, - canonical_querystring, - canonical_headers, - signed_headers, - payload_hash, - ) - - credential_scope = "{}/{}/{}/{}".format( - date_stamp, region, service_name, _AWS_REQUEST_TYPE - ) - - # https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html - string_to_sign = "{}\n{}\n{}\n{}".format( - _AWS_ALGORITHM, - amz_date, - credential_scope, - hashlib.sha256(canonical_request.encode("utf-8")).hexdigest(), - ) - - # https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html - signing_key = _get_signing_key(secret_key, date_stamp, region, service_name) - signature = hmac.new( - signing_key, string_to_sign.encode("utf-8"), hashlib.sha256 - ).hexdigest() - - # https://docs.aws.amazon.com/general/latest/gr/sigv4-add-signature-to-request.html - authorization_header = "{} Credential={}/{}, SignedHeaders={}, Signature={}".format( - _AWS_ALGORITHM, access_key, credential_scope, signed_headers, signature - ) - - authentication_header = {"authorization_header": authorization_header} - # Do not use generated x-amz-date if the date header is provided. - if "date" not in full_headers: - authentication_header["amz_date"] = amz_date - return authentication_header - - -class Credentials(external_account.Credentials): - """AWS external account credentials. - This is used to exchange serialized AWS signature v4 signed requests to - AWS STS GetCallerIdentity service for Google access tokens. - """ - - def __init__( - self, - audience, - subject_token_type, - token_url, - credential_source=None, - service_account_impersonation_url=None, - client_id=None, - client_secret=None, - quota_project_id=None, - scopes=None, - default_scopes=None, - ): - """Instantiates an AWS workload external account credentials object. - - Args: - audience (str): The STS audience field. - subject_token_type (str): The subject token type. - token_url (str): The STS endpoint URL. - credential_source (Mapping): The credential source dictionary used - to provide instructions on how to retrieve external credential - to be exchanged for Google access tokens. - service_account_impersonation_url (Optional[str]): The optional - service account impersonation getAccessToken URL. - client_id (Optional[str]): The optional client ID. - client_secret (Optional[str]): The optional client secret. - quota_project_id (Optional[str]): The optional quota project ID. - scopes (Optional[Sequence[str]]): Optional scopes to request during - the authorization grant. - default_scopes (Optional[Sequence[str]]): Default scopes passed by a - Google client library. Use 'scopes' for user-defined scopes. - - Raises: - google.auth.exceptions.RefreshError: If an error is encountered during - access token retrieval logic. - ValueError: For invalid parameters. - - .. note:: Typically one of the helper constructors - :meth:`from_file` or - :meth:`from_info` are used instead of calling the constructor directly. - """ - super(Credentials, self).__init__( - audience=audience, - subject_token_type=subject_token_type, - token_url=token_url, - credential_source=credential_source, - service_account_impersonation_url=service_account_impersonation_url, - client_id=client_id, - client_secret=client_secret, - quota_project_id=quota_project_id, - scopes=scopes, - default_scopes=default_scopes, - ) - credential_source = credential_source or {} - self._environment_id = credential_source.get("environment_id") or "" - self._region_url = credential_source.get("region_url") - self._security_credentials_url = credential_source.get("url") - self._cred_verification_url = credential_source.get( - "regional_cred_verification_url" - ) - self._region = None - self._request_signer = None - self._target_resource = audience - - # Get the environment ID. Currently, only one version supported (v1). - matches = re.match(r"^(aws)([\d]+)$", self._environment_id) - if matches: - env_id, env_version = matches.groups() - else: - env_id, env_version = (None, None) - - if env_id != "aws" or self._cred_verification_url is None: - raise ValueError("No valid AWS 'credential_source' provided") - elif int(env_version or "") != 1: - raise ValueError( - "aws version '{}' is not supported in the current build.".format( - env_version - ) - ) - - def retrieve_subject_token(self, request): - """Retrieves the subject token using the credential_source object. - The subject token is a serialized `AWS GetCallerIdentity signed request`_. - - The logic is summarized as: - - Retrieve the AWS region from the AWS_REGION environment variable or from - the AWS metadata server availability-zone if not found in the - environment variable. - - Check AWS credentials in environment variables. If not found, retrieve - from the AWS metadata server security-credentials endpoint. - - When retrieving AWS credentials from the metadata server - security-credentials endpoint, the AWS role needs to be determined by - calling the security-credentials endpoint without any argument. Then the - credentials can be retrieved via: security-credentials/role_name - - Generate the signed request to AWS STS GetCallerIdentity action. - - Inject x-goog-cloud-target-resource into header and serialize the - signed request. This will be the subject-token to pass to GCP STS. - - .. _AWS GetCallerIdentity signed request: - https://cloud.google.com/iam/docs/access-resources-aws#exchange-token - - Args: - request (google.auth.transport.Request): A callable used to make - HTTP requests. - Returns: - str: The retrieved subject token. - """ - # Initialize the request signer if not yet initialized after determining - # the current AWS region. - if self._request_signer is None: - self._region = self._get_region(request, self._region_url) - self._request_signer = RequestSigner(self._region) - - # Retrieve the AWS security credentials needed to generate the signed - # request. - aws_security_credentials = self._get_security_credentials(request) - # Generate the signed request to AWS STS GetCallerIdentity API. - # Use the required regional endpoint. Otherwise, the request will fail. - request_options = self._request_signer.get_request_options( - aws_security_credentials, - self._cred_verification_url.replace("{region}", self._region), - "POST", - ) - # The GCP STS endpoint expects the headers to be formatted as: - # [ - # {key: 'x-amz-date', value: '...'}, - # {key: 'Authorization', value: '...'}, - # ... - # ] - # And then serialized as: - # quote(json.dumps({ - # url: '...', - # method: 'POST', - # headers: [{key: 'x-amz-date', value: '...'}, ...] - # })) - request_headers = request_options.get("headers") - # The full, canonical resource name of the workload identity pool - # provider, with or without the HTTPS prefix. - # Including this header as part of the signature is recommended to - # ensure data integrity. - request_headers["x-goog-cloud-target-resource"] = self._target_resource - - # Serialize AWS signed request. - # Keeping inner keys in sorted order makes testing easier for Python - # versions <=3.5 as the stringified JSON string would have a predictable - # key order. - aws_signed_req = {} - aws_signed_req["url"] = request_options.get("url") - aws_signed_req["method"] = request_options.get("method") - aws_signed_req["headers"] = [] - # Reformat header to GCP STS expected format. - for key in sorted(request_headers.keys()): - aws_signed_req["headers"].append( - {"key": key, "value": request_headers[key]} - ) - - return urllib.parse.quote( - json.dumps(aws_signed_req, separators=(",", ":"), sort_keys=True) - ) - - def _get_region(self, request, url): - """Retrieves the current AWS region from either the AWS_REGION - environment variable or from the AWS metadata server. - - Args: - request (google.auth.transport.Request): A callable used to make - HTTP requests. - url (str): The AWS metadata server region URL. - - Returns: - str: The current AWS region. - - Raises: - google.auth.exceptions.RefreshError: If an error occurs while - retrieving the AWS region. - """ - # The AWS metadata server is not available in some AWS environments - # such as AWS lambda. Instead, it is available via environment - # variable. - env_aws_region = os.environ.get(environment_vars.AWS_REGION) - if env_aws_region is not None: - return env_aws_region - - if not self._region_url: - raise exceptions.RefreshError("Unable to determine AWS region") - response = request(url=self._region_url, method="GET") - - # Support both string and bytes type response.data. - response_body = ( - response.data.decode("utf-8") - if hasattr(response.data, "decode") - else response.data - ) - - if response.status != 200: - raise exceptions.RefreshError( - "Unable to retrieve AWS region", response_body - ) - - # This endpoint will return the region in format: us-east-2b. - # Only the us-east-2 part should be used. - return response_body[:-1] - - def _get_security_credentials(self, request): - """Retrieves the AWS security credentials required for signing AWS - requests from either the AWS security credentials environment variables - or from the AWS metadata server. - - Args: - request (google.auth.transport.Request): A callable used to make - HTTP requests. - - Returns: - Mapping[str, str]: The AWS security credentials dictionary object. - - Raises: - google.auth.exceptions.RefreshError: If an error occurs while - retrieving the AWS security credentials. - """ - - # Check environment variables for permanent credentials first. - # https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html - env_aws_access_key_id = os.environ.get(environment_vars.AWS_ACCESS_KEY_ID) - env_aws_secret_access_key = os.environ.get( - environment_vars.AWS_SECRET_ACCESS_KEY - ) - # This is normally not available for permanent credentials. - env_aws_session_token = os.environ.get(environment_vars.AWS_SESSION_TOKEN) - if env_aws_access_key_id and env_aws_secret_access_key: - return { - "access_key_id": env_aws_access_key_id, - "secret_access_key": env_aws_secret_access_key, - "security_token": env_aws_session_token, - } - - # Get role name. - role_name = self._get_metadata_role_name(request) - - # Get security credentials. - credentials = self._get_metadata_security_credentials(request, role_name) - - return { - "access_key_id": credentials.get("AccessKeyId"), - "secret_access_key": credentials.get("SecretAccessKey"), - "security_token": credentials.get("Token"), - } - - def _get_metadata_security_credentials(self, request, role_name): - """Retrieves the AWS security credentials required for signing AWS - requests from the AWS metadata server. - - Args: - request (google.auth.transport.Request): A callable used to make - HTTP requests. - role_name (str): The AWS role name required by the AWS metadata - server security_credentials endpoint in order to return the - credentials. - - Returns: - Mapping[str, str]: The AWS metadata server security credentials - response. - - Raises: - google.auth.exceptions.RefreshError: If an error occurs while - retrieving the AWS security credentials. - """ - headers = {"Content-Type": "application/json"} - response = request( - url="{}/{}".format(self._security_credentials_url, role_name), - method="GET", - headers=headers, - ) - - # support both string and bytes type response.data - response_body = ( - response.data.decode("utf-8") - if hasattr(response.data, "decode") - else response.data - ) - - if response.status != http_client.OK: - raise exceptions.RefreshError( - "Unable to retrieve AWS security credentials", response_body - ) - - credentials_response = json.loads(response_body) - - return credentials_response - - def _get_metadata_role_name(self, request): - """Retrieves the AWS role currently attached to the current AWS - workload by querying the AWS metadata server. This is needed for the - AWS metadata server security credentials endpoint in order to retrieve - the AWS security credentials needed to sign requests to AWS APIs. - - Args: - request (google.auth.transport.Request): A callable used to make - HTTP requests. - - Returns: - str: The AWS role name. - - Raises: - google.auth.exceptions.RefreshError: If an error occurs while - retrieving the AWS role name. - """ - if self._security_credentials_url is None: - raise exceptions.RefreshError( - "Unable to determine the AWS metadata server security credentials endpoint" - ) - response = request(url=self._security_credentials_url, method="GET") - - # support both string and bytes type response.data - response_body = ( - response.data.decode("utf-8") - if hasattr(response.data, "decode") - else response.data - ) - - if response.status != http_client.OK: - raise exceptions.RefreshError( - "Unable to retrieve AWS role name", response_body - ) - - return response_body - - @classmethod - def from_info(cls, info, **kwargs): - """Creates an AWS Credentials instance from parsed external account info. - - Args: - info (Mapping[str, str]): The AWS external account info in Google - format. - kwargs: Additional arguments to pass to the constructor. - - Returns: - google.auth.aws.Credentials: The constructed credentials. - - Raises: - ValueError: For invalid parameters. - """ - return cls( - audience=info.get("audience"), - subject_token_type=info.get("subject_token_type"), - token_url=info.get("token_url"), - service_account_impersonation_url=info.get( - "service_account_impersonation_url" - ), - client_id=info.get("client_id"), - client_secret=info.get("client_secret"), - credential_source=info.get("credential_source"), - quota_project_id=info.get("quota_project_id"), - **kwargs - ) - - @classmethod - def from_file(cls, filename, **kwargs): - """Creates an AWS Credentials instance from an external account json file. - - Args: - filename (str): The path to the AWS external account json file. - kwargs: Additional arguments to pass to the constructor. - - Returns: - google.auth.aws.Credentials: The constructed credentials. - """ - with io.open(filename, "r", encoding="utf-8") as json_file: - data = json.load(json_file) - return cls.from_info(data, **kwargs) diff --git a/packages/google-auth/google/auth/environment_vars.py b/packages/google-auth/google/auth/environment_vars.py index 416bab0c01ea..46a8926646b9 100644 --- a/packages/google-auth/google/auth/environment_vars.py +++ b/packages/google-auth/google/auth/environment_vars.py @@ -59,13 +59,3 @@ The default value is false. Users have to explicitly set this value to true in order to use client certificate to establish a mutual TLS channel.""" - -# AWS environment variables used with AWS workload identity pools to retrieve -# AWS security credentials and the AWS region needed to create a serialized -# signed requests to the AWS STS GetCalledIdentity API that can be exchanged -# for a Google access tokens via the GCP STS endpoint. -# When not available the AWS metadata server is used to retrieve these values. -AWS_ACCESS_KEY_ID = "AWS_ACCESS_KEY_ID" -AWS_SECRET_ACCESS_KEY = "AWS_SECRET_ACCESS_KEY" -AWS_SESSION_TOKEN = "AWS_SESSION_TOKEN" -AWS_REGION = "AWS_REGION" diff --git a/packages/google-auth/google/auth/exceptions.py b/packages/google-auth/google/auth/exceptions.py index b6f686bbb57c..da06d8696283 100644 --- a/packages/google-auth/google/auth/exceptions.py +++ b/packages/google-auth/google/auth/exceptions.py @@ -43,8 +43,3 @@ class MutualTLSChannelError(GoogleAuthError): class ClientCertError(GoogleAuthError): """Used to indicate that client certificate is missing or invalid.""" - - -class OAuthError(GoogleAuthError): - """Used to indicate an error occurred during an OAuth related HTTP - request.""" diff --git a/packages/google-auth/google/auth/external_account.py b/packages/google-auth/google/auth/external_account.py deleted file mode 100644 index 0429ee08f4ff..000000000000 --- a/packages/google-auth/google/auth/external_account.py +++ /dev/null @@ -1,305 +0,0 @@ -# Copyright 2020 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""External Account Credentials. - -This module provides credentials that exchange workload identity pool external -credentials for Google access tokens. This facilitates accessing Google Cloud -Platform resources from on-prem and non-Google Cloud platforms (e.g. AWS, -Microsoft Azure, OIDC identity providers), using native credentials retrieved -from the current environment without the need to copy, save and manage -long-lived service account credentials. - -Specifically, this is intended to use access tokens acquired using the GCP STS -token exchange endpoint following the `OAuth 2.0 Token Exchange`_ spec. - -.. _OAuth 2.0 Token Exchange: https://tools.ietf.org/html/rfc8693 -""" - -import abc -import datetime -import json - -import six - -from google.auth import _helpers -from google.auth import credentials -from google.auth import exceptions -from google.auth import impersonated_credentials -from google.oauth2 import sts -from google.oauth2 import utils - -# The token exchange grant_type used for exchanging credentials. -_STS_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:token-exchange" -# The token exchange requested_token_type. This is always an access_token. -_STS_REQUESTED_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token" -# Cloud resource manager URL used to retrieve project information. -_CLOUD_RESOURCE_MANAGER = "https://cloudresourcemanager.googleapis.com/v1/projects/" - - -@six.add_metaclass(abc.ABCMeta) -class Credentials(credentials.Scoped, credentials.CredentialsWithQuotaProject): - """Base class for all external account credentials. - - This is used to instantiate Credentials for exchanging external account - credentials for Google access token and authorizing requests to Google APIs. - The base class implements the common logic for exchanging external account - credentials for Google access tokens. - """ - - def __init__( - self, - audience, - subject_token_type, - token_url, - credential_source, - service_account_impersonation_url=None, - client_id=None, - client_secret=None, - quota_project_id=None, - scopes=None, - default_scopes=None, - ): - """Instantiates an external account credentials object. - - Args: - audience (str): The STS audience field. - subject_token_type (str): The subject token type. - token_url (str): The STS endpoint URL. - credential_source (Mapping): The credential source dictionary. - service_account_impersonation_url (Optional[str]): The optional service account - impersonation generateAccessToken URL. - client_id (Optional[str]): The optional client ID. - client_secret (Optional[str]): The optional client secret. - quota_project_id (Optional[str]): The optional quota project ID. - scopes (Optional[Sequence[str]]): Optional scopes to request during the - authorization grant. - default_scopes (Optional[Sequence[str]]): Default scopes passed by a - Google client library. Use 'scopes' for user-defined scopes. - Raises: - google.auth.exceptions.RefreshError: If the generateAccessToken - endpoint returned an error. - """ - super(Credentials, self).__init__() - self._audience = audience - self._subject_token_type = subject_token_type - self._token_url = token_url - self._credential_source = credential_source - self._service_account_impersonation_url = service_account_impersonation_url - self._client_id = client_id - self._client_secret = client_secret - self._quota_project_id = quota_project_id - self._scopes = scopes - self._default_scopes = default_scopes - - if self._client_id: - self._client_auth = utils.ClientAuthentication( - utils.ClientAuthType.basic, self._client_id, self._client_secret - ) - else: - self._client_auth = None - self._sts_client = sts.Client(self._token_url, self._client_auth) - - if self._service_account_impersonation_url: - self._impersonated_credentials = self._initialize_impersonated_credentials() - else: - self._impersonated_credentials = None - self._project_id = None - - @property - def requires_scopes(self): - """Checks if the credentials requires scopes. - - Returns: - bool: True if there are no scopes set otherwise False. - """ - return not self._scopes and not self._default_scopes - - @property - def project_number(self): - """Optional[str]: The project number corresponding to the workload identity pool.""" - - # STS audience pattern: - # //iam.googleapis.com/projects/$PROJECT_NUMBER/locations/... - components = self._audience.split("/") - try: - project_index = components.index("projects") - if project_index + 1 < len(components): - return components[project_index + 1] or None - except ValueError: - return None - - @_helpers.copy_docstring(credentials.Scoped) - def with_scopes(self, scopes, default_scopes=None): - return self.__class__( - audience=self._audience, - subject_token_type=self._subject_token_type, - token_url=self._token_url, - credential_source=self._credential_source, - service_account_impersonation_url=self._service_account_impersonation_url, - client_id=self._client_id, - client_secret=self._client_secret, - quota_project_id=self._quota_project_id, - scopes=scopes, - default_scopes=default_scopes, - ) - - @abc.abstractmethod - def retrieve_subject_token(self, request): - """Retrieves the subject token using the credential_source object. - - Args: - request (google.auth.transport.Request): A callable used to make - HTTP requests. - Returns: - str: The retrieved subject token. - """ - # pylint: disable=missing-raises-doc - # (pylint doesn't recognize that this is abstract) - raise NotImplementedError("retrieve_subject_token must be implemented") - - def get_project_id(self, request): - """Retrieves the project ID corresponding to the workload identity pool. - - When not determinable, None is returned. - - This is introduced to support the current pattern of using the Auth library: - - credentials, project_id = google.auth.default() - - The resource may not have permission (resourcemanager.projects.get) to - call this API or the required scopes may not be selected: - https://cloud.google.com/resource-manager/reference/rest/v1/projects/get#authorization-scopes - - Args: - request (google.auth.transport.Request): A callable used to make - HTTP requests. - Returns: - Optional[str]: The project ID corresponding to the workload identity pool - if determinable. - """ - if self._project_id: - # If already retrieved, return the cached project ID value. - return self._project_id - scopes = self._scopes if self._scopes is not None else self._default_scopes - # Scopes are required in order to retrieve a valid access token. - if self.project_number and scopes: - headers = {} - url = _CLOUD_RESOURCE_MANAGER + self.project_number - self.before_request(request, "GET", url, headers) - response = request(url=url, method="GET", headers=headers) - - response_body = ( - response.data.decode("utf-8") - if hasattr(response.data, "decode") - else response.data - ) - response_data = json.loads(response_body) - - if response.status == 200: - # Cache result as this field is immutable. - self._project_id = response_data.get("projectId") - return self._project_id - - return None - - @_helpers.copy_docstring(credentials.Credentials) - def refresh(self, request): - scopes = self._scopes if self._scopes is not None else self._default_scopes - if self._impersonated_credentials: - self._impersonated_credentials.refresh(request) - self.token = self._impersonated_credentials.token - self.expiry = self._impersonated_credentials.expiry - else: - now = _helpers.utcnow() - response_data = self._sts_client.exchange_token( - request=request, - grant_type=_STS_GRANT_TYPE, - subject_token=self.retrieve_subject_token(request), - subject_token_type=self._subject_token_type, - audience=self._audience, - scopes=scopes, - requested_token_type=_STS_REQUESTED_TOKEN_TYPE, - ) - self.token = response_data.get("access_token") - lifetime = datetime.timedelta(seconds=response_data.get("expires_in")) - self.expiry = now + lifetime - - @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) - def with_quota_project(self, quota_project_id): - # Return copy of instance with the provided quota project ID. - return self.__class__( - audience=self._audience, - subject_token_type=self._subject_token_type, - token_url=self._token_url, - credential_source=self._credential_source, - service_account_impersonation_url=self._service_account_impersonation_url, - client_id=self._client_id, - client_secret=self._client_secret, - quota_project_id=quota_project_id, - scopes=self._scopes, - default_scopes=self._default_scopes, - ) - - def _initialize_impersonated_credentials(self): - """Generates an impersonated credentials. - - For more details, see `projects.serviceAccounts.generateAccessToken`_. - - .. _projects.serviceAccounts.generateAccessToken: https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/generateAccessToken - - Returns: - impersonated_credentials.Credential: The impersonated credentials - object. - - Raises: - google.auth.exceptions.RefreshError: If the generateAccessToken - endpoint returned an error. - """ - # Return copy of instance with no service account impersonation. - source_credentials = self.__class__( - audience=self._audience, - subject_token_type=self._subject_token_type, - token_url=self._token_url, - credential_source=self._credential_source, - service_account_impersonation_url=None, - client_id=self._client_id, - client_secret=self._client_secret, - quota_project_id=self._quota_project_id, - scopes=self._scopes, - default_scopes=self._default_scopes, - ) - - # Determine target_principal. - start_index = self._service_account_impersonation_url.rfind("/") - end_index = self._service_account_impersonation_url.find(":generateAccessToken") - if start_index != -1 and end_index != -1 and start_index < end_index: - start_index = start_index + 1 - target_principal = self._service_account_impersonation_url[ - start_index:end_index - ] - else: - raise exceptions.RefreshError( - "Unable to determine target principal from service account impersonation URL." - ) - - scopes = self._scopes if self._scopes is not None else self._default_scopes - # Initialize and return impersonated credentials. - return impersonated_credentials.Credentials( - source_credentials=source_credentials, - target_principal=target_principal, - target_scopes=scopes, - quota_project_id=self._quota_project_id, - iam_endpoint_override=self._service_account_impersonation_url, - ) diff --git a/packages/google-auth/google/auth/identity_pool.py b/packages/google-auth/google/auth/identity_pool.py deleted file mode 100644 index 53621995557f..000000000000 --- a/packages/google-auth/google/auth/identity_pool.py +++ /dev/null @@ -1,279 +0,0 @@ -# Copyright 2020 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Identity Pool Credentials. - -This module provides credentials to access Google Cloud resources from on-prem -or non-Google Cloud platforms which support external credentials (e.g. OIDC ID -tokens) retrieved from local file locations or local servers. This includes -Microsoft Azure and OIDC identity providers (e.g. K8s workloads registered with -Hub with Hub workload identity enabled). - -These credentials are recommended over the use of service account credentials -in on-prem/non-Google Cloud platforms as they do not involve the management of -long-live service account private keys. - -Identity Pool Credentials are initialized using external_account -arguments which are typically loaded from an external credentials file or -an external credentials URL. Unlike other Credentials that can be initialized -with a list of explicit arguments, secrets or credentials, external account -clients use the environment and hints/guidelines provided by the -external_account JSON file to retrieve credentials and exchange them for Google -access tokens. -""" - -try: - from collections.abc import Mapping -# Python 2.7 compatibility -except ImportError: # pragma: NO COVER - from collections import Mapping -import io -import json -import os - -from google.auth import _helpers -from google.auth import exceptions -from google.auth import external_account - - -class Credentials(external_account.Credentials): - """External account credentials sourced from files and URLs.""" - - def __init__( - self, - audience, - subject_token_type, - token_url, - credential_source, - service_account_impersonation_url=None, - client_id=None, - client_secret=None, - quota_project_id=None, - scopes=None, - default_scopes=None, - ): - """Instantiates an external account credentials object from a file/URL. - - Args: - audience (str): The STS audience field. - subject_token_type (str): The subject token type. - token_url (str): The STS endpoint URL. - credential_source (Mapping): The credential source dictionary used to - provide instructions on how to retrieve external credential to be - exchanged for Google access tokens. - - Example credential_source for url-sourced credential:: - - { - "url": "http://www.example.com", - "format": { - "type": "json", - "subject_token_field_name": "access_token", - }, - "headers": {"foo": "bar"}, - } - - Example credential_source for file-sourced credential:: - - { - "file": "/path/to/token/file.txt" - } - - service_account_impersonation_url (Optional[str]): The optional service account - impersonation getAccessToken URL. - client_id (Optional[str]): The optional client ID. - client_secret (Optional[str]): The optional client secret. - quota_project_id (Optional[str]): The optional quota project ID. - scopes (Optional[Sequence[str]]): Optional scopes to request during the - authorization grant. - default_scopes (Optional[Sequence[str]]): Default scopes passed by a - Google client library. Use 'scopes' for user-defined scopes. - - Raises: - google.auth.exceptions.RefreshError: If an error is encountered during - access token retrieval logic. - ValueError: For invalid parameters. - - .. note:: Typically one of the helper constructors - :meth:`from_file` or - :meth:`from_info` are used instead of calling the constructor directly. - """ - - super(Credentials, self).__init__( - audience=audience, - subject_token_type=subject_token_type, - token_url=token_url, - credential_source=credential_source, - service_account_impersonation_url=service_account_impersonation_url, - client_id=client_id, - client_secret=client_secret, - quota_project_id=quota_project_id, - scopes=scopes, - default_scopes=default_scopes, - ) - if not isinstance(credential_source, Mapping): - self._credential_source_file = None - self._credential_source_url = None - else: - self._credential_source_file = credential_source.get("file") - self._credential_source_url = credential_source.get("url") - self._credential_source_headers = credential_source.get("headers") - credential_source_format = credential_source.get("format", {}) - # Get credential_source format type. When not provided, this - # defaults to text. - self._credential_source_format_type = ( - credential_source_format.get("type") or "text" - ) - # environment_id is only supported in AWS or dedicated future external - # account credentials. - if "environment_id" in credential_source: - raise ValueError( - "Invalid Identity Pool credential_source field 'environment_id'" - ) - if self._credential_source_format_type not in ["text", "json"]: - raise ValueError( - "Invalid credential_source format '{}'".format( - self._credential_source_format_type - ) - ) - # For JSON types, get the required subject_token field name. - if self._credential_source_format_type == "json": - self._credential_source_field_name = credential_source_format.get( - "subject_token_field_name" - ) - if self._credential_source_field_name is None: - raise ValueError( - "Missing subject_token_field_name for JSON credential_source format" - ) - else: - self._credential_source_field_name = None - - if self._credential_source_file and self._credential_source_url: - raise ValueError( - "Ambiguous credential_source. 'file' is mutually exclusive with 'url'." - ) - if not self._credential_source_file and not self._credential_source_url: - raise ValueError( - "Missing credential_source. A 'file' or 'url' must be provided." - ) - - @_helpers.copy_docstring(external_account.Credentials) - def retrieve_subject_token(self, request): - return self._parse_token_data( - self._get_token_data(request), - self._credential_source_format_type, - self._credential_source_field_name, - ) - - def _get_token_data(self, request): - if self._credential_source_file: - return self._get_file_data(self._credential_source_file) - else: - return self._get_url_data( - request, self._credential_source_url, self._credential_source_headers - ) - - def _get_file_data(self, filename): - if not os.path.exists(filename): - raise exceptions.RefreshError("File '{}' was not found.".format(filename)) - - with io.open(filename, "r", encoding="utf-8") as file_obj: - return file_obj.read(), filename - - def _get_url_data(self, request, url, headers): - response = request(url=url, method="GET", headers=headers) - - # support both string and bytes type response.data - response_body = ( - response.data.decode("utf-8") - if hasattr(response.data, "decode") - else response.data - ) - - if response.status != 200: - raise exceptions.RefreshError( - "Unable to retrieve Identity Pool subject token", response_body - ) - - return response_body, url - - def _parse_token_data( - self, token_content, format_type="text", subject_token_field_name=None - ): - content, filename = token_content - if format_type == "text": - token = content - else: - try: - # Parse file content as JSON. - response_data = json.loads(content) - # Get the subject_token. - token = response_data[subject_token_field_name] - except (KeyError, ValueError): - raise exceptions.RefreshError( - "Unable to parse subject_token from JSON file '{}' using key '{}'".format( - filename, subject_token_field_name - ) - ) - if not token: - raise exceptions.RefreshError( - "Missing subject_token in the credential_source file" - ) - return token - - @classmethod - def from_info(cls, info, **kwargs): - """Creates an Identity Pool Credentials instance from parsed external account info. - - Args: - info (Mapping[str, str]): The Identity Pool external account info in Google - format. - kwargs: Additional arguments to pass to the constructor. - - Returns: - google.auth.identity_pool.Credentials: The constructed - credentials. - - Raises: - ValueError: For invalid parameters. - """ - return cls( - audience=info.get("audience"), - subject_token_type=info.get("subject_token_type"), - token_url=info.get("token_url"), - service_account_impersonation_url=info.get( - "service_account_impersonation_url" - ), - client_id=info.get("client_id"), - client_secret=info.get("client_secret"), - credential_source=info.get("credential_source"), - quota_project_id=info.get("quota_project_id"), - **kwargs - ) - - @classmethod - def from_file(cls, filename, **kwargs): - """Creates an IdentityPool Credentials instance from an external account json file. - - Args: - filename (str): The path to the IdentityPool external account json file. - kwargs: Additional arguments to pass to the constructor. - - Returns: - google.auth.identity_pool.Credentials: The constructed - credentials. - """ - with io.open(filename, "r", encoding="utf-8") as json_file: - data = json.load(json_file) - return cls.from_info(data, **kwargs) diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index b8a6c49a1eba..4d158373a7e8 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -65,9 +65,7 @@ _DEFAULT_TOKEN_URI = "https://oauth2.googleapis.com/token" -def _make_iam_token_request( - request, principal, headers, body, iam_endpoint_override=None -): +def _make_iam_token_request(request, principal, headers, body): """Makes a request to the Google Cloud IAM service for an access token. Args: request (Request): The Request object to use. @@ -75,9 +73,6 @@ def _make_iam_token_request( headers (Mapping[str, str]): Map of headers to transmit. body (Mapping[str, str]): JSON Payload body for the iamcredentials API call. - iam_endpoint_override (Optiona[str]): The full IAM endpoint override - with the target_principal embedded. This is useful when supporting - impersonation with regional endpoints. Raises: google.auth.exceptions.TransportError: Raised if there is an underlying @@ -87,7 +82,7 @@ def _make_iam_token_request( `iamcredentials.googleapis.com` is not enabled or the `Service Account Token Creator` is not assigned """ - iam_endpoint = iam_endpoint_override or _IAM_ENDPOINT.format(principal) + iam_endpoint = _IAM_ENDPOINT.format(principal) body = json.dumps(body).encode("utf-8") @@ -190,7 +185,6 @@ def __init__( delegates=None, lifetime=_DEFAULT_TOKEN_LIFETIME_SECS, quota_project_id=None, - iam_endpoint_override=None, ): """ Args: @@ -215,9 +209,6 @@ def __init__( quota_project_id (Optional[str]): The project ID used for quota and billing. This project may be different from the project used to create the credentials. - iam_endpoint_override (Optiona[str]): The full IAM endpoint override - with the target_principal embedded. This is useful when supporting - impersonation with regional endpoints. """ super(Credentials, self).__init__() @@ -235,7 +226,6 @@ def __init__( self.token = None self.expiry = _helpers.utcnow() self._quota_project_id = quota_project_id - self._iam_endpoint_override = iam_endpoint_override @_helpers.copy_docstring(credentials.Credentials) def refresh(self, request): @@ -270,7 +260,6 @@ def _update_token(self, request): principal=self._target_principal, headers=headers, body=body, - iam_endpoint_override=self._iam_endpoint_override, ) def sign_bytes(self, message): @@ -313,7 +302,6 @@ def with_quota_project(self, quota_project_id): delegates=self._delegates, lifetime=self._lifetime, quota_project_id=quota_project_id, - iam_endpoint_override=self._iam_endpoint_override, ) diff --git a/packages/google-auth/google/oauth2/sts.py b/packages/google-auth/google/oauth2/sts.py deleted file mode 100644 index ae3c0146b114..000000000000 --- a/packages/google-auth/google/oauth2/sts.py +++ /dev/null @@ -1,155 +0,0 @@ -# Copyright 2020 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""OAuth 2.0 Token Exchange Spec. - -This module defines a token exchange utility based on the `OAuth 2.0 Token -Exchange`_ spec. This will be mainly used to exchange external credentials -for GCP access tokens in workload identity pools to access Google APIs. - -The implementation will support various types of client authentication as -allowed in the spec. - -A deviation on the spec will be for additional Google specific options that -cannot be easily mapped to parameters defined in the RFC. - -The returned dictionary response will be based on the `rfc8693 section 2.2.1`_ -spec JSON response. - -.. _OAuth 2.0 Token Exchange: https://tools.ietf.org/html/rfc8693 -.. _rfc8693 section 2.2.1: https://tools.ietf.org/html/rfc8693#section-2.2.1 -""" - -import json - -from six.moves import http_client -from six.moves import urllib - -from google.oauth2 import utils - - -_URLENCODED_HEADERS = {"Content-Type": "application/x-www-form-urlencoded"} - - -class Client(utils.OAuthClientAuthHandler): - """Implements the OAuth 2.0 token exchange spec based on - https://tools.ietf.org/html/rfc8693. - """ - - def __init__(self, token_exchange_endpoint, client_authentication=None): - """Initializes an STS client instance. - - Args: - token_exchange_endpoint (str): The token exchange endpoint. - client_authentication (Optional(google.oauth2.oauth2_utils.ClientAuthentication)): - The optional OAuth client authentication credentials if available. - """ - super(Client, self).__init__(client_authentication) - self._token_exchange_endpoint = token_exchange_endpoint - - def exchange_token( - self, - request, - grant_type, - subject_token, - subject_token_type, - resource=None, - audience=None, - scopes=None, - requested_token_type=None, - actor_token=None, - actor_token_type=None, - additional_options=None, - additional_headers=None, - ): - """Exchanges the provided token for another type of token based on the - rfc8693 spec. - - Args: - request (google.auth.transport.Request): A callable used to make - HTTP requests. - grant_type (str): The OAuth 2.0 token exchange grant type. - subject_token (str): The OAuth 2.0 token exchange subject token. - subject_token_type (str): The OAuth 2.0 token exchange subject token type. - resource (Optional[str]): The optional OAuth 2.0 token exchange resource field. - audience (Optional[str]): The optional OAuth 2.0 token exchange audience field. - scopes (Optional[Sequence[str]]): The optional list of scopes to use. - requested_token_type (Optional[str]): The optional OAuth 2.0 token exchange requested - token type. - actor_token (Optional[str]): The optional OAuth 2.0 token exchange actor token. - actor_token_type (Optional[str]): The optional OAuth 2.0 token exchange actor token type. - additional_options (Optional[Mapping[str, str]]): The optional additional - non-standard Google specific options. - additional_headers (Optional[Mapping[str, str]]): The optional additional - headers to pass to the token exchange endpoint. - - Returns: - Mapping[str, str]: The token exchange JSON-decoded response data containing - the requested token and its expiration time. - - Raises: - google.auth.exceptions.OAuthError: If the token endpoint returned - an error. - """ - # Initialize request headers. - headers = _URLENCODED_HEADERS.copy() - # Inject additional headers. - if additional_headers: - for k, v in dict(additional_headers).items(): - headers[k] = v - # Initialize request body. - request_body = { - "grant_type": grant_type, - "resource": resource, - "audience": audience, - "scope": " ".join(scopes or []), - "requested_token_type": requested_token_type, - "subject_token": subject_token, - "subject_token_type": subject_token_type, - "actor_token": actor_token, - "actor_token_type": actor_token_type, - "options": None, - } - # Add additional non-standard options. - if additional_options: - request_body["options"] = urllib.parse.quote(json.dumps(additional_options)) - # Remove empty fields in request body. - for k, v in dict(request_body).items(): - if v is None or v == "": - del request_body[k] - # Apply OAuth client authentication. - self.apply_client_authentication_options(headers, request_body) - - # Execute request. - response = request( - url=self._token_exchange_endpoint, - method="POST", - headers=headers, - body=urllib.parse.urlencode(request_body).encode("utf-8"), - ) - - response_body = ( - response.data.decode("utf-8") - if hasattr(response.data, "decode") - else response.data - ) - - # If non-200 response received, translate to OAuthError exception. - if response.status != http_client.OK: - utils.handle_error_response(response_body) - - response_data = json.loads(response_body) - - # Return successful response. - return response_data diff --git a/packages/google-auth/google/oauth2/utils.py b/packages/google-auth/google/oauth2/utils.py deleted file mode 100644 index efda7968dbaa..000000000000 --- a/packages/google-auth/google/oauth2/utils.py +++ /dev/null @@ -1,171 +0,0 @@ -# Copyright 2020 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""OAuth 2.0 Utilities. - -This module provides implementations for various OAuth 2.0 utilities. -This includes `OAuth error handling`_ and -`Client authentication for OAuth flows`_. - -OAuth error handling --------------------- -This will define interfaces for handling OAuth related error responses as -stated in `RFC 6749 section 5.2`_. -This will include a common function to convert these HTTP error responses to a -:class:`google.auth.exceptions.OAuthError` exception. - - -Client authentication for OAuth flows -------------------------------------- -We introduce an interface for defining client authentication credentials based -on `RFC 6749 section 2.3.1`_. This will expose the following -capabilities: - - * Ability to support basic authentication via request header. - * Ability to support bearer token authentication via request header. - * Ability to support client ID / secret authentication via request body. - -.. _RFC 6749 section 2.3.1: https://tools.ietf.org/html/rfc6749#section-2.3.1 -.. _RFC 6749 section 5.2: https://tools.ietf.org/html/rfc6749#section-5.2 -""" - -import abc -import base64 -import enum -import json - -import six - -from google.auth import exceptions - - -# OAuth client authentication based on -# https://tools.ietf.org/html/rfc6749#section-2.3. -class ClientAuthType(enum.Enum): - basic = 1 - request_body = 2 - - -class ClientAuthentication(object): - """Defines the client authentication credentials for basic and request-body - types based on https://tools.ietf.org/html/rfc6749#section-2.3.1. - """ - - def __init__(self, client_auth_type, client_id, client_secret=None): - """Instantiates a client authentication object containing the client ID - and secret credentials for basic and response-body auth. - - Args: - client_auth_type (google.oauth2.oauth_utils.ClientAuthType): The - client authentication type. - client_id (str): The client ID. - client_secret (Optional[str]): The client secret. - """ - self.client_auth_type = client_auth_type - self.client_id = client_id - self.client_secret = client_secret - - -@six.add_metaclass(abc.ABCMeta) -class OAuthClientAuthHandler(object): - """Abstract class for handling client authentication in OAuth-based - operations. - """ - - def __init__(self, client_authentication=None): - """Instantiates an OAuth client authentication handler. - - Args: - client_authentication (Optional[google.oauth2.utils.ClientAuthentication]): - The OAuth client authentication credentials if available. - """ - super(OAuthClientAuthHandler, self).__init__() - self._client_authentication = client_authentication - - def apply_client_authentication_options( - self, headers, request_body=None, bearer_token=None - ): - """Applies client authentication on the OAuth request's headers or POST - body. - - Args: - headers (Mapping[str, str]): The HTTP request header. - request_body (Optional[Mapping[str, str]): The HTTP request body - dictionary. For requests that do not support request body, this - is None and will be ignored. - bearer_token (Optional[str]): The optional bearer token. - """ - # Inject authenticated header. - self._inject_authenticated_headers(headers, bearer_token) - # Inject authenticated request body. - if bearer_token is None: - self._inject_authenticated_request_body(request_body) - - def _inject_authenticated_headers(self, headers, bearer_token=None): - if bearer_token is not None: - headers["Authorization"] = "Bearer %s" % bearer_token - elif ( - self._client_authentication is not None - and self._client_authentication.client_auth_type is ClientAuthType.basic - ): - username = self._client_authentication.client_id - password = self._client_authentication.client_secret or "" - - credentials = base64.b64encode( - ("%s:%s" % (username, password)).encode() - ).decode() - headers["Authorization"] = "Basic %s" % credentials - - def _inject_authenticated_request_body(self, request_body): - if ( - self._client_authentication is not None - and self._client_authentication.client_auth_type - is ClientAuthType.request_body - ): - if request_body is None: - raise exceptions.OAuthError( - "HTTP request does not support request-body" - ) - else: - request_body["client_id"] = self._client_authentication.client_id - request_body["client_secret"] = ( - self._client_authentication.client_secret or "" - ) - - -def handle_error_response(response_body): - """Translates an error response from an OAuth operation into an - OAuthError exception. - - Args: - response_body (str): The decoded response data. - - Raises: - google.auth.exceptions.OAuthError - """ - try: - error_components = [] - error_data = json.loads(response_body) - - error_components.append("Error code {}".format(error_data["error"])) - if "error_description" in error_data: - error_components.append(": {}".format(error_data["error_description"])) - if "error_uri" in error_data: - error_components.append(" - {}".format(error_data["error_uri"])) - error_details = "".join(error_components) - # If no details could be extracted, use the response data. - except (KeyError, ValueError): - error_details = response_body - - raise exceptions.OAuthError(error_details, response_body) diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index fa88d24b2695..adce2527c13d 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -64,7 +64,9 @@ def lint(session): @nox.session(python="3.6") def blacken(session): """Run black. + Format code to uniform standard. + This currently uses Python 3.6 due to the automated Kokoro run of synthtool. That run uses an image that doesn't have 3.6 installed. Before updating this check the state of the `gcp_ubuntu_config` we use for that Kokoro run. diff --git a/packages/google-auth/system_tests/system_tests_sync/secrets.tar.enc b/packages/google-auth/system_tests/system_tests_sync/secrets.tar.enc deleted file mode 100644 index 29e06923f0f028b54d1b571dc218cdd92f751bd8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10323 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 0: - # If service account impersonation is requested, mock the expected response. - status, data, extra_requests = ( - extra_requests[0], - extra_requests[1], - extra_requests[2:], - ) - responses.append(cls.make_mock_response(status, data)) - - request = mock.create_autospec(transport.Request) - request.side_effect = responses - - return request - - @classmethod - def assert_credential_request_kwargs( - cls, request_kwargs, headers, url=CREDENTIAL_URL - ): - assert request_kwargs["url"] == url - assert request_kwargs["method"] == "GET" - assert request_kwargs["headers"] == headers - assert request_kwargs.get("body", None) is None - - @classmethod - def assert_token_request_kwargs( - cls, request_kwargs, headers, request_data, token_url=TOKEN_URL - ): - assert request_kwargs["url"] == token_url - assert request_kwargs["method"] == "POST" - assert request_kwargs["headers"] == headers - assert request_kwargs["body"] is not None - body_tuples = urllib.parse.parse_qsl(request_kwargs["body"]) - assert len(body_tuples) == len(request_data.keys()) - for (k, v) in body_tuples: - assert v.decode("utf-8") == request_data[k.decode("utf-8")] - - @classmethod - def assert_impersonation_request_kwargs( - cls, - request_kwargs, - headers, - request_data, - service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, - ): - assert request_kwargs["url"] == service_account_impersonation_url - assert request_kwargs["method"] == "POST" - assert request_kwargs["headers"] == headers - assert request_kwargs["body"] is not None - body_json = json.loads(request_kwargs["body"].decode("utf-8")) - assert body_json == request_data - - @classmethod - def assert_underlying_credentials_refresh( - cls, - credentials, - audience, - subject_token, - subject_token_type, - token_url, - service_account_impersonation_url=None, - basic_auth_encoding=None, - quota_project_id=None, - used_scopes=None, - credential_data=None, - scopes=None, - default_scopes=None, - ): - """Utility to assert that a credentials are initialized with the expected - attributes by calling refresh functionality and confirming response matches - expected one and that the underlying requests were populated with the - expected parameters. - """ - # STS token exchange request/response. - token_response = cls.SUCCESS_RESPONSE.copy() - token_headers = {"Content-Type": "application/x-www-form-urlencoded"} - if basic_auth_encoding: - token_headers["Authorization"] = "Basic " + basic_auth_encoding - - if service_account_impersonation_url: - token_scopes = "https://www.googleapis.com/auth/iam" - else: - token_scopes = " ".join(used_scopes or []) - - token_request_data = { - "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange", - "audience": audience, - "requested_token_type": "urn:ietf:params:oauth:token-type:access_token", - "scope": token_scopes, - "subject_token": subject_token, - "subject_token_type": subject_token_type, - } - - if service_account_impersonation_url: - # Service account impersonation request/response. - expire_time = ( - _helpers.utcnow().replace(microsecond=0) - + datetime.timedelta(seconds=3600) - ).isoformat("T") + "Z" - impersonation_response = { - "accessToken": "SA_ACCESS_TOKEN", - "expireTime": expire_time, - } - impersonation_headers = { - "Content-Type": "application/json", - "authorization": "Bearer {}".format(token_response["access_token"]), - } - impersonation_request_data = { - "delegates": None, - "scope": used_scopes, - "lifetime": "3600s", - } - - # Initialize mock request to handle token retrieval, token exchange and - # service account impersonation request. - requests = [] - if credential_data: - requests.append((http_client.OK, credential_data)) - - token_request_index = len(requests) - requests.append((http_client.OK, token_response)) - - if service_account_impersonation_url: - impersonation_request_index = len(requests) - requests.append((http_client.OK, impersonation_response)) - - request = cls.make_mock_request(*[el for req in requests for el in req]) - - credentials.refresh(request) - - assert len(request.call_args_list) == len(requests) - if credential_data: - cls.assert_credential_request_kwargs(request.call_args_list[0].kwargs, None) - # Verify token exchange request parameters. - cls.assert_token_request_kwargs( - request.call_args_list[token_request_index].kwargs, - token_headers, - token_request_data, - token_url, - ) - # Verify service account impersonation request parameters if the request - # is processed. - if service_account_impersonation_url: - cls.assert_impersonation_request_kwargs( - request.call_args_list[impersonation_request_index].kwargs, - impersonation_headers, - impersonation_request_data, - service_account_impersonation_url, - ) - assert credentials.token == impersonation_response["accessToken"] - else: - assert credentials.token == token_response["access_token"] - assert credentials.quota_project_id == quota_project_id - assert credentials.scopes == scopes - assert credentials.default_scopes == default_scopes - - @classmethod - def make_credentials( - cls, - client_id=None, - client_secret=None, - quota_project_id=None, - scopes=None, - default_scopes=None, - service_account_impersonation_url=None, - credential_source=None, - ): - return identity_pool.Credentials( - audience=AUDIENCE, - subject_token_type=SUBJECT_TOKEN_TYPE, - token_url=TOKEN_URL, - service_account_impersonation_url=service_account_impersonation_url, - credential_source=credential_source, - client_id=client_id, - client_secret=client_secret, - quota_project_id=quota_project_id, - scopes=scopes, - default_scopes=default_scopes, - ) - - @mock.patch.object(identity_pool.Credentials, "__init__", return_value=None) - def test_from_info_full_options(self, mock_init): - credentials = identity_pool.Credentials.from_info( - { - "audience": AUDIENCE, - "subject_token_type": SUBJECT_TOKEN_TYPE, - "token_url": TOKEN_URL, - "service_account_impersonation_url": SERVICE_ACCOUNT_IMPERSONATION_URL, - "client_id": CLIENT_ID, - "client_secret": CLIENT_SECRET, - "quota_project_id": QUOTA_PROJECT_ID, - "credential_source": self.CREDENTIAL_SOURCE_TEXT, - } - ) - - # Confirm identity_pool.Credentials instantiated with expected attributes. - assert isinstance(credentials, identity_pool.Credentials) - mock_init.assert_called_once_with( - audience=AUDIENCE, - subject_token_type=SUBJECT_TOKEN_TYPE, - token_url=TOKEN_URL, - service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, - client_id=CLIENT_ID, - client_secret=CLIENT_SECRET, - credential_source=self.CREDENTIAL_SOURCE_TEXT, - quota_project_id=QUOTA_PROJECT_ID, - ) - - @mock.patch.object(identity_pool.Credentials, "__init__", return_value=None) - def test_from_info_required_options_only(self, mock_init): - credentials = identity_pool.Credentials.from_info( - { - "audience": AUDIENCE, - "subject_token_type": SUBJECT_TOKEN_TYPE, - "token_url": TOKEN_URL, - "credential_source": self.CREDENTIAL_SOURCE_TEXT, - } - ) - - # Confirm identity_pool.Credentials instantiated with expected attributes. - assert isinstance(credentials, identity_pool.Credentials) - mock_init.assert_called_once_with( - audience=AUDIENCE, - subject_token_type=SUBJECT_TOKEN_TYPE, - token_url=TOKEN_URL, - service_account_impersonation_url=None, - client_id=None, - client_secret=None, - credential_source=self.CREDENTIAL_SOURCE_TEXT, - quota_project_id=None, - ) - - @mock.patch.object(identity_pool.Credentials, "__init__", return_value=None) - def test_from_file_full_options(self, mock_init, tmpdir): - info = { - "audience": AUDIENCE, - "subject_token_type": SUBJECT_TOKEN_TYPE, - "token_url": TOKEN_URL, - "service_account_impersonation_url": SERVICE_ACCOUNT_IMPERSONATION_URL, - "client_id": CLIENT_ID, - "client_secret": CLIENT_SECRET, - "quota_project_id": QUOTA_PROJECT_ID, - "credential_source": self.CREDENTIAL_SOURCE_TEXT, - } - config_file = tmpdir.join("config.json") - config_file.write(json.dumps(info)) - credentials = identity_pool.Credentials.from_file(str(config_file)) - - # Confirm identity_pool.Credentials instantiated with expected attributes. - assert isinstance(credentials, identity_pool.Credentials) - mock_init.assert_called_once_with( - audience=AUDIENCE, - subject_token_type=SUBJECT_TOKEN_TYPE, - token_url=TOKEN_URL, - service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, - client_id=CLIENT_ID, - client_secret=CLIENT_SECRET, - credential_source=self.CREDENTIAL_SOURCE_TEXT, - quota_project_id=QUOTA_PROJECT_ID, - ) - - @mock.patch.object(identity_pool.Credentials, "__init__", return_value=None) - def test_from_file_required_options_only(self, mock_init, tmpdir): - info = { - "audience": AUDIENCE, - "subject_token_type": SUBJECT_TOKEN_TYPE, - "token_url": TOKEN_URL, - "credential_source": self.CREDENTIAL_SOURCE_TEXT, - } - config_file = tmpdir.join("config.json") - config_file.write(json.dumps(info)) - credentials = identity_pool.Credentials.from_file(str(config_file)) - - # Confirm identity_pool.Credentials instantiated with expected attributes. - assert isinstance(credentials, identity_pool.Credentials) - mock_init.assert_called_once_with( - audience=AUDIENCE, - subject_token_type=SUBJECT_TOKEN_TYPE, - token_url=TOKEN_URL, - service_account_impersonation_url=None, - client_id=None, - client_secret=None, - credential_source=self.CREDENTIAL_SOURCE_TEXT, - quota_project_id=None, - ) - - def test_constructor_invalid_options(self): - credential_source = {"unsupported": "value"} - - with pytest.raises(ValueError) as excinfo: - self.make_credentials(credential_source=credential_source) - - assert excinfo.match(r"Missing credential_source") - - def test_constructor_invalid_options_url_and_file(self): - credential_source = { - "url": self.CREDENTIAL_URL, - "file": SUBJECT_TOKEN_TEXT_FILE, - } - - with pytest.raises(ValueError) as excinfo: - self.make_credentials(credential_source=credential_source) - - assert excinfo.match(r"Ambiguous credential_source") - - def test_constructor_invalid_options_environment_id(self): - credential_source = {"url": self.CREDENTIAL_URL, "environment_id": "aws1"} - - with pytest.raises(ValueError) as excinfo: - self.make_credentials(credential_source=credential_source) - - assert excinfo.match( - r"Invalid Identity Pool credential_source field 'environment_id'" - ) - - def test_constructor_invalid_credential_source(self): - with pytest.raises(ValueError) as excinfo: - self.make_credentials(credential_source="non-dict") - - assert excinfo.match(r"Missing credential_source") - - def test_constructor_invalid_credential_source_format_type(self): - credential_source = {"format": {"type": "xml"}} - - with pytest.raises(ValueError) as excinfo: - self.make_credentials(credential_source=credential_source) - - assert excinfo.match(r"Invalid credential_source format 'xml'") - - def test_constructor_missing_subject_token_field_name(self): - credential_source = {"format": {"type": "json"}} - - with pytest.raises(ValueError) as excinfo: - self.make_credentials(credential_source=credential_source) - - assert excinfo.match( - r"Missing subject_token_field_name for JSON credential_source format" - ) - - def test_retrieve_subject_token_missing_subject_token(self, tmpdir): - # Provide empty text file. - empty_file = tmpdir.join("empty.txt") - empty_file.write("") - credential_source = {"file": str(empty_file)} - credentials = self.make_credentials(credential_source=credential_source) - - with pytest.raises(exceptions.RefreshError) as excinfo: - credentials.retrieve_subject_token(None) - - assert excinfo.match(r"Missing subject_token in the credential_source file") - - def test_retrieve_subject_token_text_file(self): - credentials = self.make_credentials( - credential_source=self.CREDENTIAL_SOURCE_TEXT - ) - - subject_token = credentials.retrieve_subject_token(None) - - assert subject_token == TEXT_FILE_SUBJECT_TOKEN - - def test_retrieve_subject_token_json_file(self): - credentials = self.make_credentials( - credential_source=self.CREDENTIAL_SOURCE_JSON - ) - - subject_token = credentials.retrieve_subject_token(None) - - assert subject_token == JSON_FILE_SUBJECT_TOKEN - - def test_retrieve_subject_token_json_file_invalid_field_name(self): - credential_source = { - "file": SUBJECT_TOKEN_JSON_FILE, - "format": {"type": "json", "subject_token_field_name": "not_found"}, - } - credentials = self.make_credentials(credential_source=credential_source) - - with pytest.raises(exceptions.RefreshError) as excinfo: - credentials.retrieve_subject_token(None) - - assert excinfo.match( - "Unable to parse subject_token from JSON file '{}' using key '{}'".format( - SUBJECT_TOKEN_JSON_FILE, "not_found" - ) - ) - - def test_retrieve_subject_token_invalid_json(self, tmpdir): - # Provide JSON file. This should result in JSON parsing error. - invalid_json_file = tmpdir.join("invalid.json") - invalid_json_file.write("{") - credential_source = { - "file": str(invalid_json_file), - "format": {"type": "json", "subject_token_field_name": "access_token"}, - } - credentials = self.make_credentials(credential_source=credential_source) - - with pytest.raises(exceptions.RefreshError) as excinfo: - credentials.retrieve_subject_token(None) - - assert excinfo.match( - "Unable to parse subject_token from JSON file '{}' using key '{}'".format( - str(invalid_json_file), "access_token" - ) - ) - - def test_retrieve_subject_token_file_not_found(self): - credential_source = {"file": "./not_found.txt"} - credentials = self.make_credentials(credential_source=credential_source) - - with pytest.raises(exceptions.RefreshError) as excinfo: - credentials.retrieve_subject_token(None) - - assert excinfo.match(r"File './not_found.txt' was not found") - - def test_refresh_text_file_success_without_impersonation_ignore_default_scopes( - self - ): - credentials = self.make_credentials( - client_id=CLIENT_ID, - client_secret=CLIENT_SECRET, - # Test with text format type. - credential_source=self.CREDENTIAL_SOURCE_TEXT, - scopes=SCOPES, - # Default scopes should be ignored. - default_scopes=["ignored"], - ) - - self.assert_underlying_credentials_refresh( - credentials=credentials, - audience=AUDIENCE, - subject_token=TEXT_FILE_SUBJECT_TOKEN, - subject_token_type=SUBJECT_TOKEN_TYPE, - token_url=TOKEN_URL, - service_account_impersonation_url=None, - basic_auth_encoding=BASIC_AUTH_ENCODING, - quota_project_id=None, - used_scopes=SCOPES, - scopes=SCOPES, - default_scopes=["ignored"], - ) - - def test_refresh_text_file_success_without_impersonation_use_default_scopes(self): - credentials = self.make_credentials( - client_id=CLIENT_ID, - client_secret=CLIENT_SECRET, - # Test with text format type. - credential_source=self.CREDENTIAL_SOURCE_TEXT, - scopes=None, - # Default scopes should be used since user specified scopes are none. - default_scopes=SCOPES, - ) - - self.assert_underlying_credentials_refresh( - credentials=credentials, - audience=AUDIENCE, - subject_token=TEXT_FILE_SUBJECT_TOKEN, - subject_token_type=SUBJECT_TOKEN_TYPE, - token_url=TOKEN_URL, - service_account_impersonation_url=None, - basic_auth_encoding=BASIC_AUTH_ENCODING, - quota_project_id=None, - used_scopes=SCOPES, - scopes=None, - default_scopes=SCOPES, - ) - - def test_refresh_text_file_success_with_impersonation_ignore_default_scopes(self): - # Initialize credentials with service account impersonation and basic auth. - credentials = self.make_credentials( - # Test with text format type. - credential_source=self.CREDENTIAL_SOURCE_TEXT, - service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, - scopes=SCOPES, - # Default scopes should be ignored. - default_scopes=["ignored"], - ) - - self.assert_underlying_credentials_refresh( - credentials=credentials, - audience=AUDIENCE, - subject_token=TEXT_FILE_SUBJECT_TOKEN, - subject_token_type=SUBJECT_TOKEN_TYPE, - token_url=TOKEN_URL, - service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, - basic_auth_encoding=None, - quota_project_id=None, - used_scopes=SCOPES, - scopes=SCOPES, - default_scopes=["ignored"], - ) - - def test_refresh_text_file_success_with_impersonation_use_default_scopes(self): - # Initialize credentials with service account impersonation, basic auth - # and default scopes (no user scopes). - credentials = self.make_credentials( - # Test with text format type. - credential_source=self.CREDENTIAL_SOURCE_TEXT, - service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, - scopes=None, - # Default scopes should be used since user specified scopes are none. - default_scopes=SCOPES, - ) - - self.assert_underlying_credentials_refresh( - credentials=credentials, - audience=AUDIENCE, - subject_token=TEXT_FILE_SUBJECT_TOKEN, - subject_token_type=SUBJECT_TOKEN_TYPE, - token_url=TOKEN_URL, - service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, - basic_auth_encoding=None, - quota_project_id=None, - used_scopes=SCOPES, - scopes=None, - default_scopes=SCOPES, - ) - - def test_refresh_json_file_success_without_impersonation(self): - credentials = self.make_credentials( - client_id=CLIENT_ID, - client_secret=CLIENT_SECRET, - # Test with JSON format type. - credential_source=self.CREDENTIAL_SOURCE_JSON, - scopes=SCOPES, - ) - - self.assert_underlying_credentials_refresh( - credentials=credentials, - audience=AUDIENCE, - subject_token=JSON_FILE_SUBJECT_TOKEN, - subject_token_type=SUBJECT_TOKEN_TYPE, - token_url=TOKEN_URL, - service_account_impersonation_url=None, - basic_auth_encoding=BASIC_AUTH_ENCODING, - quota_project_id=None, - used_scopes=SCOPES, - scopes=SCOPES, - default_scopes=None, - ) - - def test_refresh_json_file_success_with_impersonation(self): - # Initialize credentials with service account impersonation and basic auth. - credentials = self.make_credentials( - # Test with JSON format type. - credential_source=self.CREDENTIAL_SOURCE_JSON, - service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, - scopes=SCOPES, - ) - - self.assert_underlying_credentials_refresh( - credentials=credentials, - audience=AUDIENCE, - subject_token=JSON_FILE_SUBJECT_TOKEN, - subject_token_type=SUBJECT_TOKEN_TYPE, - token_url=TOKEN_URL, - service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, - basic_auth_encoding=None, - quota_project_id=None, - used_scopes=SCOPES, - scopes=SCOPES, - default_scopes=None, - ) - - def test_refresh_with_retrieve_subject_token_error(self): - credential_source = { - "file": SUBJECT_TOKEN_JSON_FILE, - "format": {"type": "json", "subject_token_field_name": "not_found"}, - } - credentials = self.make_credentials(credential_source=credential_source) - - with pytest.raises(exceptions.RefreshError) as excinfo: - credentials.refresh(None) - - assert excinfo.match( - "Unable to parse subject_token from JSON file '{}' using key '{}'".format( - SUBJECT_TOKEN_JSON_FILE, "not_found" - ) - ) - - def test_retrieve_subject_token_from_url(self): - credentials = self.make_credentials( - credential_source=self.CREDENTIAL_SOURCE_TEXT_URL - ) - request = self.make_mock_request(token_data=TEXT_FILE_SUBJECT_TOKEN) - subject_token = credentials.retrieve_subject_token(request) - - assert subject_token == TEXT_FILE_SUBJECT_TOKEN - self.assert_credential_request_kwargs(request.call_args_list[0].kwargs, None) - - def test_retrieve_subject_token_from_url_with_headers(self): - credentials = self.make_credentials( - credential_source={"url": self.CREDENTIAL_URL, "headers": {"foo": "bar"}} - ) - request = self.make_mock_request(token_data=TEXT_FILE_SUBJECT_TOKEN) - subject_token = credentials.retrieve_subject_token(request) - - assert subject_token == TEXT_FILE_SUBJECT_TOKEN - self.assert_credential_request_kwargs( - request.call_args_list[0].kwargs, {"foo": "bar"} - ) - - def test_retrieve_subject_token_from_url_json(self): - credentials = self.make_credentials( - credential_source=self.CREDENTIAL_SOURCE_JSON_URL - ) - request = self.make_mock_request(token_data=JSON_FILE_CONTENT) - subject_token = credentials.retrieve_subject_token(request) - - assert subject_token == JSON_FILE_SUBJECT_TOKEN - self.assert_credential_request_kwargs(request.call_args_list[0].kwargs, None) - - def test_retrieve_subject_token_from_url_json_with_headers(self): - credentials = self.make_credentials( - credential_source={ - "url": self.CREDENTIAL_URL, - "format": {"type": "json", "subject_token_field_name": "access_token"}, - "headers": {"foo": "bar"}, - } - ) - request = self.make_mock_request(token_data=JSON_FILE_CONTENT) - subject_token = credentials.retrieve_subject_token(request) - - assert subject_token == JSON_FILE_SUBJECT_TOKEN - self.assert_credential_request_kwargs( - request.call_args_list[0].kwargs, {"foo": "bar"} - ) - - def test_retrieve_subject_token_from_url_not_found(self): - credentials = self.make_credentials( - credential_source=self.CREDENTIAL_SOURCE_TEXT_URL - ) - with pytest.raises(exceptions.RefreshError) as excinfo: - credentials.retrieve_subject_token( - self.make_mock_request(token_status=404, token_data=JSON_FILE_CONTENT) - ) - - assert excinfo.match("Unable to retrieve Identity Pool subject token") - - def test_retrieve_subject_token_from_url_json_invalid_field(self): - credential_source = { - "url": self.CREDENTIAL_URL, - "format": {"type": "json", "subject_token_field_name": "not_found"}, - } - credentials = self.make_credentials(credential_source=credential_source) - - with pytest.raises(exceptions.RefreshError) as excinfo: - credentials.retrieve_subject_token( - self.make_mock_request(token_data=JSON_FILE_CONTENT) - ) - - assert excinfo.match( - "Unable to parse subject_token from JSON file '{}' using key '{}'".format( - self.CREDENTIAL_URL, "not_found" - ) - ) - - def test_retrieve_subject_token_from_url_json_invalid_format(self): - credentials = self.make_credentials( - credential_source=self.CREDENTIAL_SOURCE_JSON_URL - ) - - with pytest.raises(exceptions.RefreshError) as excinfo: - credentials.retrieve_subject_token(self.make_mock_request(token_data="{")) - - assert excinfo.match( - "Unable to parse subject_token from JSON file '{}' using key '{}'".format( - self.CREDENTIAL_URL, "access_token" - ) - ) - - def test_refresh_text_file_success_without_impersonation_url(self): - credentials = self.make_credentials( - client_id=CLIENT_ID, - client_secret=CLIENT_SECRET, - # Test with text format type. - credential_source=self.CREDENTIAL_SOURCE_TEXT_URL, - scopes=SCOPES, - ) - - self.assert_underlying_credentials_refresh( - credentials=credentials, - audience=AUDIENCE, - subject_token=TEXT_FILE_SUBJECT_TOKEN, - subject_token_type=SUBJECT_TOKEN_TYPE, - token_url=TOKEN_URL, - service_account_impersonation_url=None, - basic_auth_encoding=BASIC_AUTH_ENCODING, - quota_project_id=None, - used_scopes=SCOPES, - scopes=SCOPES, - default_scopes=None, - credential_data=TEXT_FILE_SUBJECT_TOKEN, - ) - - def test_refresh_text_file_success_with_impersonation_url(self): - # Initialize credentials with service account impersonation and basic auth. - credentials = self.make_credentials( - # Test with text format type. - credential_source=self.CREDENTIAL_SOURCE_TEXT_URL, - service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, - scopes=SCOPES, - ) - - self.assert_underlying_credentials_refresh( - credentials=credentials, - audience=AUDIENCE, - subject_token=TEXT_FILE_SUBJECT_TOKEN, - subject_token_type=SUBJECT_TOKEN_TYPE, - token_url=TOKEN_URL, - service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, - basic_auth_encoding=None, - quota_project_id=None, - used_scopes=SCOPES, - scopes=SCOPES, - default_scopes=None, - credential_data=TEXT_FILE_SUBJECT_TOKEN, - ) - - def test_refresh_json_file_success_without_impersonation_url(self): - credentials = self.make_credentials( - client_id=CLIENT_ID, - client_secret=CLIENT_SECRET, - # Test with JSON format type. - credential_source=self.CREDENTIAL_SOURCE_JSON_URL, - scopes=SCOPES, - ) - - self.assert_underlying_credentials_refresh( - credentials=credentials, - audience=AUDIENCE, - subject_token=JSON_FILE_SUBJECT_TOKEN, - subject_token_type=SUBJECT_TOKEN_TYPE, - token_url=TOKEN_URL, - service_account_impersonation_url=None, - basic_auth_encoding=BASIC_AUTH_ENCODING, - quota_project_id=None, - used_scopes=SCOPES, - scopes=SCOPES, - default_scopes=None, - credential_data=JSON_FILE_CONTENT, - ) - - def test_refresh_json_file_success_with_impersonation_url(self): - # Initialize credentials with service account impersonation and basic auth. - credentials = self.make_credentials( - # Test with JSON format type. - credential_source=self.CREDENTIAL_SOURCE_JSON_URL, - service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, - scopes=SCOPES, - ) - - self.assert_underlying_credentials_refresh( - credentials=credentials, - audience=AUDIENCE, - subject_token=JSON_FILE_SUBJECT_TOKEN, - subject_token_type=SUBJECT_TOKEN_TYPE, - token_url=TOKEN_URL, - service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, - basic_auth_encoding=None, - quota_project_id=None, - used_scopes=SCOPES, - scopes=SCOPES, - default_scopes=None, - credential_data=JSON_FILE_CONTENT, - ) - - def test_refresh_with_retrieve_subject_token_error_url(self): - credential_source = { - "url": self.CREDENTIAL_URL, - "format": {"type": "json", "subject_token_field_name": "not_found"}, - } - credentials = self.make_credentials(credential_source=credential_source) - - with pytest.raises(exceptions.RefreshError) as excinfo: - credentials.refresh(self.make_mock_request(token_data=JSON_FILE_CONTENT)) - - assert excinfo.match( - "Unable to parse subject_token from JSON file '{}' using key '{}'".format( - self.CREDENTIAL_URL, "not_found" - ) - ) diff --git a/packages/google-auth/tests/test_impersonated_credentials.py b/packages/google-auth/tests/test_impersonated_credentials.py index 430c770d3672..305f9392667a 100644 --- a/packages/google-auth/tests/test_impersonated_credentials.py +++ b/packages/google-auth/tests/test_impersonated_credentials.py @@ -104,17 +104,12 @@ class TestImpersonatedCredentials(object): SIGNER, SERVICE_ACCOUNT_EMAIL, TOKEN_URI ) USER_SOURCE_CREDENTIALS = credentials.Credentials(token="ABCDE") - IAM_ENDPOINT_OVERRIDE = ( - "https://us-east1-iamcredentials.googleapis.com/v1/projects/-" - + "/serviceAccounts/{}:generateAccessToken".format(SERVICE_ACCOUNT_EMAIL) - ) def make_credentials( self, source_credentials=SOURCE_CREDENTIALS, lifetime=LIFETIME, target_principal=TARGET_PRINCIPAL, - iam_endpoint_override=None, ): return Credentials( @@ -123,7 +118,6 @@ def make_credentials( target_scopes=self.TARGET_SCOPES, delegates=self.DELEGATES, lifetime=lifetime, - iam_endpoint_override=iam_endpoint_override, ) def test_make_from_user_credentials(self): @@ -178,34 +172,6 @@ def test_refresh_success(self, use_data_bytes, mock_donor_credentials): assert credentials.valid assert not credentials.expired - @pytest.mark.parametrize("use_data_bytes", [True, False]) - def test_refresh_success_iam_endpoint_override( - self, use_data_bytes, mock_donor_credentials - ): - credentials = self.make_credentials( - lifetime=None, iam_endpoint_override=self.IAM_ENDPOINT_OVERRIDE - ) - token = "token" - - expire_time = ( - _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=500) - ).isoformat("T") + "Z" - response_body = {"accessToken": token, "expireTime": expire_time} - - request = self.make_request( - data=json.dumps(response_body), - status=http_client.OK, - use_data_bytes=use_data_bytes, - ) - - credentials.refresh(request) - - assert credentials.valid - assert not credentials.expired - # Confirm override endpoint used. - request_kwargs = request.call_args.kwargs - assert request_kwargs["url"] == self.IAM_ENDPOINT_OVERRIDE - @pytest.mark.parametrize("time_skew", [100, -100]) def test_refresh_source_credentials(self, time_skew): credentials = self.make_credentials(lifetime=None) @@ -351,36 +317,6 @@ def test_with_quota_project(self): quota_project_creds = credentials.with_quota_project("project-foo") assert quota_project_creds._quota_project_id == "project-foo" - @pytest.mark.parametrize("use_data_bytes", [True, False]) - def test_with_quota_project_iam_endpoint_override( - self, use_data_bytes, mock_donor_credentials - ): - credentials = self.make_credentials( - lifetime=None, iam_endpoint_override=self.IAM_ENDPOINT_OVERRIDE - ) - token = "token" - # iam_endpoint_override should be copied to created credentials. - quota_project_creds = credentials.with_quota_project("project-foo") - - expire_time = ( - _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=500) - ).isoformat("T") + "Z" - response_body = {"accessToken": token, "expireTime": expire_time} - - request = self.make_request( - data=json.dumps(response_body), - status=http_client.OK, - use_data_bytes=use_data_bytes, - ) - - quota_project_creds.refresh(request) - - assert quota_project_creds.valid - assert not quota_project_creds.expired - # Confirm override endpoint used. - request_kwargs = request.call_args.kwargs - assert request_kwargs["url"] == self.IAM_ENDPOINT_OVERRIDE - def test_id_token_success( self, mock_donor_credentials, mock_authorizedsession_idtoken ): From 2a38dad2151ff36d89c4b76f4fc5adf169ec3d79 Mon Sep 17 00:00:00 2001 From: DanielLearner <4323083+DanielLearner@users.noreply.github.com> Date: Thu, 11 Feb 2021 14:20:03 -0600 Subject: [PATCH 379/966] docs: fix a typo in the user guide (avaiable -> available) (#680) --- packages/google-auth/docs/user-guide.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/docs/user-guide.rst b/packages/google-auth/docs/user-guide.rst index 08e7167dfe53..45ce14c8632f 100644 --- a/packages/google-auth/docs/user-guide.rst +++ b/packages/google-auth/docs/user-guide.rst @@ -251,7 +251,7 @@ to assume the identity of a target_principal that does have access. Identity Tokens +++++++++++++++ -`Google OpenID Connect`_ tokens are avaiable through :mod:`Service Account `, +`Google OpenID Connect`_ tokens are available through :mod:`Service Account `, :mod:`Impersonated `, and :mod:`Compute Engine `. These tokens can be used to authenticate against `Cloud Functions`_, `Cloud Run`_, a user service behind From 80d2a0b8d17186ba0a0d88d8fa89256b283634a0 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 11 Feb 2021 20:50:02 +0000 Subject: [PATCH 380/966] chore: release 1.26.1 (#693) :robot: I have created a release \*beep\* \*boop\* --- ### [1.26.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.26.0...v1.26.1) (2021-02-11) ### Documentation * fix a typo in the user guide (avaiable -> available) ([#680](https://www.github.com/googleapis/google-auth-library-python/issues/680)) ([684457a](https://www.github.com/googleapis/google-auth-library-python/commit/684457afd3f81892e12d983a61672d7ea9bbe296)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --- packages/google-auth/CHANGELOG.md | 11 +++++++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 21298a8c0fcf..b33eb1698661 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,17 @@ [1]: https://pypi.org/project/google-auth/#history +### [1.26.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.26.0...v1.26.1) (2021-02-11) + + +### Documentation + +* fix a typo in the user guide (avaiable -> available) ([#680](https://www.github.com/googleapis/google-auth-library-python/issues/680)) ([684457a](https://www.github.com/googleapis/google-auth-library-python/commit/684457afd3f81892e12d983a61672d7ea9bbe296)) + +### Bug Fixes + +* revert workload identity federation support ([#691](https://github.com/googleapis/google-auth-library-python/pull/691)) + ## [1.26.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.25.0...v1.26.0) (2021-02-09) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 2f16e44ff9ec..0df0a81ec8b1 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -34,7 +34,7 @@ with io.open("README.rst", "r") as fh: long_description = fh.read() -version = "1.26.0" +version = "1.26.1" setup( name="google-auth", From 5c47f8de1add2d92df632ebab46d93ee47c6f174 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Fri, 12 Feb 2021 11:57:12 -0800 Subject: [PATCH 381/966] fix: add pyopenssl as extra dependency (#697) * fix: add pyopenssl as extra dependency * update --- packages/google-auth/setup.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 0df0a81ec8b1..055fcabfd790 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -29,7 +29,10 @@ "six>=1.9.0", ) -extras = {"aiohttp": "aiohttp >= 3.6.2, < 4.0.0dev; python_version>='3.6'"} +extras = { + "aiohttp": "aiohttp >= 3.6.2, < 4.0.0dev; python_version>='3.6'", + "pyopenssl": "pyopenssl>=20.0.0", +} with io.open("README.rst", "r") as fh: long_description = fh.read() From f143ad57804e39d32d03bb5d426cea000d5fb7f1 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Tue, 16 Feb 2021 13:10:11 -0700 Subject: [PATCH 382/966] test: install package before tests deps in unit_prev_versions (#703) One of the test dependencies appears to require `rsa`, which resulted in a `rsa` version incompatible with 2.7 being installed. Switching the order resolves the issue (`rsa` is installed according to the requirements in `setup.py`) https://github.com/googleapis/google-auth-library-python/blob/aeab5d07c5538f3d8cce817df24199534572b97d/setup.py#L24-L27 --- packages/google-auth/noxfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index adce2527c13d..7bc1af24d8ca 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -92,8 +92,8 @@ def unit(session): @nox.session(python=["2.7"]) def unit_prev_versions(session): - session.install(*TEST_DEPENDENCIES) session.install(".") + session.install(*TEST_DEPENDENCIES) session.run( "pytest", "--cov=google.auth", "--cov=google.oauth2", "--cov=tests", "tests" ) From e66fcdeaab07e9d532ae138031987465bb048d80 Mon Sep 17 00:00:00 2001 From: bojeil-google Date: Tue, 16 Feb 2021 12:33:20 -0800 Subject: [PATCH 383/966] feat: workload identity federation support (#698) Using workload identity federation, applications can access Google Cloud resources from Amazon Web Services (AWS), Microsoft Azure or any identity provider that supports OpenID Connect (OIDC). Workload identity federation is recommended for non-Google Cloud environments as it avoids the need to download, manage and store service account private keys locally. This includes a rollforward of the [previous reverted PR](https://github.com/googleapis/google-auth-library-python/pull/686) and the [fix](https://github.com/googleapis/google-auth-library-python/pull/686) to not pass scopes to user credentials from `google.auth.default()`. --- packages/google-auth/docs/index.rst | 2 + .../docs/reference/google.auth.aws.rst | 7 + .../google.auth.external_account.rst | 7 + .../reference/google.auth.identity_pool.rst | 7 + .../docs/reference/google.auth.rst | 3 + .../docs/reference/google.oauth2.rst | 2 + .../docs/reference/google.oauth2.sts.rst | 7 + .../docs/reference/google.oauth2.utils.rst | 7 + packages/google-auth/docs/user-guide.rst | 173 +- packages/google-auth/google/auth/_default.py | 128 +- packages/google-auth/google/auth/aws.py | 714 ++++++++ .../google/auth/environment_vars.py | 10 + .../google-auth/google/auth/exceptions.py | 5 + .../google/auth/external_account.py | 305 ++++ .../google-auth/google/auth/identity_pool.py | 279 ++++ .../google/auth/impersonated_credentials.py | 16 +- packages/google-auth/google/oauth2/sts.py | 155 ++ packages/google-auth/google/oauth2/utils.py | 171 ++ packages/google-auth/noxfile.py | 2 - .../system_tests_sync/secrets.tar.enc | Bin 0 -> 10323 bytes .../tests/data/external_subject_token.json | 3 + .../tests/data/external_subject_token.txt | 1 + packages/google-auth/tests/oauth2/test_sts.py | 395 +++++ .../google-auth/tests/oauth2/test_utils.py | 264 +++ packages/google-auth/tests/test__default.py | 209 +++ packages/google-auth/tests/test_aws.py | 1434 +++++++++++++++++ .../tests/test_external_account.py | 1095 +++++++++++++ .../google-auth/tests/test_identity_pool.py | 873 ++++++++++ .../tests/test_impersonated_credentials.py | 64 + 29 files changed, 6321 insertions(+), 17 deletions(-) create mode 100644 packages/google-auth/docs/reference/google.auth.aws.rst create mode 100644 packages/google-auth/docs/reference/google.auth.external_account.rst create mode 100644 packages/google-auth/docs/reference/google.auth.identity_pool.rst create mode 100644 packages/google-auth/docs/reference/google.oauth2.sts.rst create mode 100644 packages/google-auth/docs/reference/google.oauth2.utils.rst create mode 100644 packages/google-auth/google/auth/aws.py create mode 100644 packages/google-auth/google/auth/external_account.py create mode 100644 packages/google-auth/google/auth/identity_pool.py create mode 100644 packages/google-auth/google/oauth2/sts.py create mode 100644 packages/google-auth/google/oauth2/utils.py create mode 100644 packages/google-auth/system_tests/system_tests_sync/secrets.tar.enc create mode 100644 packages/google-auth/tests/data/external_subject_token.json create mode 100644 packages/google-auth/tests/data/external_subject_token.txt create mode 100644 packages/google-auth/tests/oauth2/test_sts.py create mode 100644 packages/google-auth/tests/oauth2/test_utils.py create mode 100644 packages/google-auth/tests/test_aws.py create mode 100644 packages/google-auth/tests/test_external_account.py create mode 100644 packages/google-auth/tests/test_identity_pool.py diff --git a/packages/google-auth/docs/index.rst b/packages/google-auth/docs/index.rst index 4287c3db3eb5..17169109a4df 100644 --- a/packages/google-auth/docs/index.rst +++ b/packages/google-auth/docs/index.rst @@ -20,6 +20,8 @@ also provides integration with several HTTP libraries. - Support for Google :mod:`Impersonated Credentials `. - Support for :mod:`Google Compute Engine credentials `. - Support for :mod:`Google App Engine standard credentials `. +- Support for :mod:`Identity Pool credentials `. +- Support for :mod:`AWS credentials `. - Support for various transports, including :mod:`Requests `, :mod:`urllib3 `, and diff --git a/packages/google-auth/docs/reference/google.auth.aws.rst b/packages/google-auth/docs/reference/google.auth.aws.rst new file mode 100644 index 000000000000..9c3966bbad73 --- /dev/null +++ b/packages/google-auth/docs/reference/google.auth.aws.rst @@ -0,0 +1,7 @@ +google.auth.aws module +====================== + +.. automodule:: google.auth.aws + :members: + :inherited-members: + :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.auth.external_account.rst b/packages/google-auth/docs/reference/google.auth.external_account.rst new file mode 100644 index 000000000000..0681eaa277a2 --- /dev/null +++ b/packages/google-auth/docs/reference/google.auth.external_account.rst @@ -0,0 +1,7 @@ +google.auth.external\_account module +==================================== + +.. automodule:: google.auth.external_account + :members: + :inherited-members: + :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.auth.identity_pool.rst b/packages/google-auth/docs/reference/google.auth.identity_pool.rst new file mode 100644 index 000000000000..48d9902236dd --- /dev/null +++ b/packages/google-auth/docs/reference/google.auth.identity_pool.rst @@ -0,0 +1,7 @@ +google.auth.identity\_pool module +================================= + +.. automodule:: google.auth.identity_pool + :members: + :inherited-members: + :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.auth.rst b/packages/google-auth/docs/reference/google.auth.rst index 3acf7dfb8d99..e21eaf9e31df 100644 --- a/packages/google-auth/docs/reference/google.auth.rst +++ b/packages/google-auth/docs/reference/google.auth.rst @@ -23,11 +23,14 @@ Submodules :maxdepth: 4 google.auth.app_engine + google.auth.aws google.auth.credentials google.auth._credentials_async google.auth.environment_vars google.auth.exceptions + google.auth.external_account google.auth.iam + google.auth.identity_pool google.auth.impersonated_credentials google.auth.jwt google.auth.jwt_async diff --git a/packages/google-auth/docs/reference/google.oauth2.rst b/packages/google-auth/docs/reference/google.oauth2.rst index 6f3ba50c217c..2a8a7a5885d2 100644 --- a/packages/google-auth/docs/reference/google.oauth2.rst +++ b/packages/google-auth/docs/reference/google.oauth2.rst @@ -17,3 +17,5 @@ Submodules google.oauth2.id_token google.oauth2.service_account google.oauth2._service_account_async + google.oauth2.sts + google.oauth2.utils diff --git a/packages/google-auth/docs/reference/google.oauth2.sts.rst b/packages/google-auth/docs/reference/google.oauth2.sts.rst new file mode 100644 index 000000000000..49d99dfe66f0 --- /dev/null +++ b/packages/google-auth/docs/reference/google.oauth2.sts.rst @@ -0,0 +1,7 @@ +google.oauth2.sts module +======================== + +.. automodule:: google.oauth2.sts + :members: + :inherited-members: + :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.oauth2.utils.rst b/packages/google-auth/docs/reference/google.oauth2.utils.rst new file mode 100644 index 000000000000..5b039eac825c --- /dev/null +++ b/packages/google-auth/docs/reference/google.oauth2.utils.rst @@ -0,0 +1,7 @@ +google.oauth2.utils module +========================== + +.. automodule:: google.oauth2.utils + :members: + :inherited-members: + :show-inheritance: diff --git a/packages/google-auth/docs/user-guide.rst b/packages/google-auth/docs/user-guide.rst index 45ce14c8632f..b315cc908a74 100644 --- a/packages/google-auth/docs/user-guide.rst +++ b/packages/google-auth/docs/user-guide.rst @@ -7,8 +7,8 @@ Credentials and account types ----------------------------- :class:`~credentials.Credentials` are the means of identifying an application or -user to a service or API. Credentials can be obtained with two different types -of accounts: *service accounts* and *user accounts*. +user to a service or API. Credentials can be obtained with three different types +of accounts: *service accounts*, *user accounts* and *external accounts*. Credentials from service accounts identify a particular application. These types of credentials are used in server-to-server use cases, such as accessing a @@ -21,6 +21,11 @@ a user's documents in Google Drive. This library provides no support for obtaining user credentials, but does provide limited support for using user credentials. +Credentials from external accounts (workload identity federation) are used to +identify a particular application from an on-prem or non-Google Cloud platform +including Amazon Web Services (AWS), Microsoft Azure or any identity provider +that supports OpenID Connect (OIDC). + Obtaining credentials --------------------- @@ -44,6 +49,13 @@ If your application requires specific scopes:: credentials, project = google.auth.default( scopes=['https://www.googleapis.com/auth/cloud-platform']) +Application Default Credentials also support workload identity federation to +access Google Cloud resources from non-Google Cloud platforms including Amazon +Web Services (AWS), Microsoft Azure or any identity provider that supports +OpenID Connect (OIDC). Workload identity federation is recommended for +non-Google Cloud environments as it avoids the need to download, manage and +store service account private keys locally. + .. _Google Application Default Credentials: https://developers.google.com/identity/protocols/ application-default-credentials @@ -219,6 +231,163 @@ You can also use :class:`google_auth_oauthlib.flow.Flow` to perform the OAuth .. _requests-oauthlib: https://requests-oauthlib.readthedocs.io/en/latest/ +External credentials (Workload identity federation) ++++++++++++++++++++++++++++++++++++++++++++++++++++ + +Using workload identity federation, your application can access Google Cloud +resources from Amazon Web Services (AWS), Microsoft Azure or any identity +provider that supports OpenID Connect (OIDC). + +Traditionally, applications running outside Google Cloud have used service +account keys to access Google Cloud resources. Using identity federation, +you can allow your workload to impersonate a service account. +This lets you access Google Cloud resources directly, eliminating the +maintenance and security burden associated with service account keys. + +Accessing resources from AWS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In order to access Google Cloud resources from Amazon Web Services (AWS), the +following requirements are needed: + +- A workload identity pool needs to be created. +- AWS needs to be added as an identity provider in the workload identity pool + (The Google organization policy needs to allow federation from AWS). +- Permission to impersonate a service account needs to be granted to the + external identity. +- A credential configuration file needs to be generated. Unlike service account + credential files, the generated credential configuration file will only + contain non-sensitive metadata to instruct the library on how to retrieve + external subject tokens and exchange them for service account access tokens. + +Follow the detailed instructions on how to +`Configure Workload Identity Federation from AWS`_. + +.. _Configure Workload Identity Federation from AWS: + https://cloud.google.com/iam/docs/access-resources-aws + +Accessing resources from Microsoft Azure +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In order to access Google Cloud resources from Microsoft Azure, the following +requirements are needed: + +- A workload identity pool needs to be created. +- Azure needs to be added as an identity provider in the workload identity pool + (The Google organization policy needs to allow federation from Azure). +- The Azure tenant needs to be configured for identity federation. +- Permission to impersonate a service account needs to be granted to the + external identity. +- A credential configuration file needs to be generated. Unlike service account + credential files, the generated credential configuration file will only + contain non-sensitive metadata to instruct the library on how to retrieve + external subject tokens and exchange them for service account access tokens. + +Follow the detailed instructions on how to +`Configure Workload Identity Federation from Microsoft Azure`_. + +.. _Configure Workload Identity Federation from Microsoft Azure: + https://cloud.google.com/iam/docs/access-resources-azure + +Accessing resources from an OIDC identity provider +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In order to access Google Cloud resources from an identity provider that +supports `OpenID Connect (OIDC)`_, the following requirements are needed: + +- A workload identity pool needs to be created. +- An OIDC identity provider needs to be added in the workload identity pool + (The Google organization policy needs to allow federation from the identity + provider). +- Permission to impersonate a service account needs to be granted to the + external identity. +- A credential configuration file needs to be generated. Unlike service account + credential files, the generated credential configuration file will only + contain non-sensitive metadata to instruct the library on how to retrieve + external subject tokens and exchange them for service account access tokens. + +For OIDC providers, the Auth library can retrieve OIDC tokens either from a +local file location (file-sourced credentials) or from a local server +(URL-sourced credentials). + +- For file-sourced credentials, a background process needs to be continuously + refreshing the file location with a new OIDC token prior to expiration. + For tokens with one hour lifetimes, the token needs to be updated in the file + every hour. The token can be stored directly as plain text or in JSON format. +- For URL-sourced credentials, a local server needs to host a GET endpoint to + return the OIDC token. The response can be in plain text or JSON. + Additional required request headers can also be specified. + +Follow the detailed instructions on how to +`Configure Workload Identity Federation from an OIDC identity provider`_. + +.. _OpenID Connect (OIDC): + https://openid.net/connect/ +.. _Configure Workload Identity Federation from an OIDC identity provider: + https://cloud.google.com/iam/docs/access-resources-oidc + +Using External Identities +~~~~~~~~~~~~~~~~~~~~~~~~~ + +External identities (AWS, Azure and OIDC identity providers) can be used with +Application Default Credentials. +In order to use external identities with Application Default Credentials, you +need to generate the JSON credentials configuration file for your external +identity. +Once generated, store the path to this file in the +``GOOGLE_APPLICATION_CREDENTIALS`` environment variable. + +.. code-block:: bash + + $ export GOOGLE_APPLICATION_CREDENTIALS=/path/to/config.json + +The library can now automatically choose the right type of client and initialize +credentials from the context provided in the configuration file:: + + import google.auth + + credentials, project = google.auth.default() + +When using external identities with Application Default Credentials, +the ``roles/browser`` role needs to be granted to the service account. +The ``Cloud Resource Manager API`` should also be enabled on the project. +This is needed since :func:`default` will try to auto-discover the project ID +from the current environment using the impersonated credential. +Otherwise, the project ID will resolve to ``None``. You can override the project +detection by setting the ``GOOGLE_CLOUD_PROJECT`` environment variable. + +You can also explicitly initialize external account clients using the generated +configuration file. + +For Azure and OIDC providers, use :meth:`identity_pool.Credentials.from_info +` or +:meth:`identity_pool.Credentials.from_file +`:: + + import json + + from google.auth import identity_pool + + json_config_info = json.loads(function_to_get_json_config()) + credentials = identity_pool.Credentials.from_info(json_config_info) + scoped_credentials = credentials.with_scopes( + ['https://www.googleapis.com/auth/cloud-platform']) + +For AWS providers, use :meth:`aws.Credentials.from_info +` or +:meth:`aws.Credentials.from_file +`:: + + import json + + from google.auth import aws + + json_config_info = json.loads(function_to_get_json_config()) + credentials = aws.Credentials.from_info(json_config_info) + scoped_credentials = credentials.with_scopes( + ['https://www.googleapis.com/auth/cloud-platform']) + + Impersonated credentials ++++++++++++++++++++++++ diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index 3b8c281e72b6..8e3b2100d9fc 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -34,7 +34,8 @@ # Valid types accepted for file-based credentials. _AUTHORIZED_USER_TYPE = "authorized_user" _SERVICE_ACCOUNT_TYPE = "service_account" -_VALID_TYPES = (_AUTHORIZED_USER_TYPE, _SERVICE_ACCOUNT_TYPE) +_EXTERNAL_ACCOUNT_TYPE = "external_account" +_VALID_TYPES = (_AUTHORIZED_USER_TYPE, _SERVICE_ACCOUNT_TYPE, _EXTERNAL_ACCOUNT_TYPE) # Help message when no credentials can be found. _HELP_MESSAGE = """\ @@ -70,12 +71,12 @@ def _warn_about_problematic_credentials(credentials): def load_credentials_from_file( - filename, scopes=None, default_scopes=None, quota_project_id=None + filename, scopes=None, default_scopes=None, quota_project_id=None, request=None ): """Loads Google credentials from a file. - The credentials file must be a service account key or stored authorized - user credentials. + The credentials file must be a service account key, stored authorized + user credentials or external account credentials. Args: filename (str): The full path to the credentials file. @@ -85,12 +86,18 @@ def load_credentials_from_file( default_scopes (Optional[Sequence[str]]): Default scopes passed by a Google client library. Use 'scopes' for user-defined scopes. quota_project_id (Optional[str]): The project ID used for - quota and billing. + quota and billing. + request (Optional[google.auth.transport.Request]): An object used to make + HTTP requests. This is used to determine the associated project ID + for a workload identity pool resource (external account credentials). + If not specified, then it will use a + google.auth.transport.requests.Request client to make requests. Returns: Tuple[google.auth.credentials.Credentials, Optional[str]]: Loaded credentials and the project ID. Authorized user credentials do not - have the project ID information. + have the project ID information. External account credentials project + IDs may not always be determined. Raises: google.auth.exceptions.DefaultCredentialsError: if the file is in the @@ -146,6 +153,18 @@ def load_credentials_from_file( credentials = credentials.with_quota_project(quota_project_id) return credentials, info.get("project_id") + elif credential_type == _EXTERNAL_ACCOUNT_TYPE: + credentials, project_id = _get_external_account_credentials( + info, + filename, + scopes=scopes, + default_scopes=default_scopes, + request=request, + ) + if quota_project_id: + credentials = credentials.with_quota_project(quota_project_id) + return credentials, project_id + else: raise exceptions.DefaultCredentialsError( "The file {file} does not have a valid type. " @@ -252,6 +271,65 @@ def _get_gce_credentials(request=None): return None, None +def _get_external_account_credentials( + info, filename, scopes=None, default_scopes=None, request=None +): + """Loads external account Credentials from the parsed external account info. + + The credentials information must correspond to a supported external account + credentials. + + Args: + info (Mapping[str, str]): The external account info in Google format. + filename (str): The full path to the credentials file. + scopes (Optional[Sequence[str]]): The list of scopes for the credentials. If + specified, the credentials will automatically be scoped if + necessary. + default_scopes (Optional[Sequence[str]]): Default scopes passed by a + Google client library. Use 'scopes' for user-defined scopes. + request (Optional[google.auth.transport.Request]): An object used to make + HTTP requests. This is used to determine the associated project ID + for a workload identity pool resource (external account credentials). + If not specified, then it will use a + google.auth.transport.requests.Request client to make requests. + + Returns: + Tuple[google.auth.credentials.Credentials, Optional[str]]: Loaded + credentials and the project ID. External account credentials project + IDs may not always be determined. + + Raises: + google.auth.exceptions.DefaultCredentialsError: if the info dictionary + is in the wrong format or is missing required information. + """ + # There are currently 2 types of external_account credentials. + try: + # Check if configuration corresponds to an AWS credentials. + from google.auth import aws + + credentials = aws.Credentials.from_info( + info, scopes=scopes, default_scopes=default_scopes + ) + except ValueError: + try: + # Check if configuration corresponds to an Identity Pool credentials. + from google.auth import identity_pool + + credentials = identity_pool.Credentials.from_info( + info, scopes=scopes, default_scopes=default_scopes + ) + except ValueError: + # If the configuration is invalid or does not correspond to any + # supported external_account credentials, raise an error. + raise exceptions.DefaultCredentialsError( + "Failed to load external account credentials from {}".format(filename) + ) + if request is None: + request = google.auth.transport.requests.Request() + + return credentials, credentials.get_project_id(request=request) + + def default(scopes=None, request=None, quota_project_id=None, default_scopes=None): """Gets the default credentials for the current environment. @@ -265,6 +343,15 @@ def default(scopes=None, request=None, quota_project_id=None, default_scopes=Non loaded and returned. The project ID returned is the project ID defined in the service account file if available (some older files do not contain project ID information). + + If the environment variable is set to the path of a valid external + account JSON configuration file (workload identity federation), then the + configuration file is used to determine and retrieve the external + credentials from the current environment (AWS, Azure, etc). + These will then be exchanged for Google access tokens via the Google STS + endpoint. + The project ID returned in this case is the one corresponding to the + underlying workload identity pool resource if determinable. 2. If the `Google Cloud SDK`_ is installed and has application default credentials set they are loaded and returned. @@ -310,11 +397,15 @@ def default(scopes=None, request=None, quota_project_id=None, default_scopes=Non scopes (Sequence[str]): The list of scopes for the credentials. If specified, the credentials will automatically be scoped if necessary. - request (google.auth.transport.Request): An object used to make - HTTP requests. This is used to detect whether the application - is running on Compute Engine. If not specified, then it will - use the standard library http client to make requests. - quota_project_id (Optional[str]): The project ID used for + request (Optional[google.auth.transport.Request]): An object used to make + HTTP requests. This is used to either detect whether the application + is running on Compute Engine or to determine the associated project + ID for a workload identity pool resource (external account + credentials). If not specified, then it will either use the standard + library http client to make requests for Compute Engine credentials + or a google.auth.transport.requests.Request client for external + account credentials. + quota_project_id (Optional[str]): The project ID used for quota and billing. default_scopes (Optional[Sequence[str]]): Default scopes passed by a Google client library. Use 'scopes' for user-defined scopes. @@ -336,6 +427,10 @@ def default(scopes=None, request=None, quota_project_id=None, default_scopes=Non ) checkers = ( + # Avoid passing scopes here to prevent passing scopes to user credentials. + # with_scopes_if_required() below will ensure scopes/default scopes are + # safely set on the returned credentials since requires_scopes will + # guard against setting scopes on user credentials. _get_explicit_environ_credentials, _get_gcloud_sdk_credentials, _get_gae_credentials, @@ -348,6 +443,17 @@ def default(scopes=None, request=None, quota_project_id=None, default_scopes=Non credentials = with_scopes_if_required( credentials, scopes, default_scopes=default_scopes ) + + # For external account credentials, scopes are required to determine + # the project ID. Try to get the project ID again if not yet + # determined. + if not project_id and callable( + getattr(credentials, "get_project_id", None) + ): + if request is None: + request = google.auth.transport.requests.Request() + project_id = credentials.get_project_id(request=request) + if quota_project_id: credentials = credentials.with_quota_project(quota_project_id) diff --git a/packages/google-auth/google/auth/aws.py b/packages/google-auth/google/auth/aws.py new file mode 100644 index 000000000000..b362dd315299 --- /dev/null +++ b/packages/google-auth/google/auth/aws.py @@ -0,0 +1,714 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""AWS Credentials and AWS Signature V4 Request Signer. + +This module provides credentials to access Google Cloud resources from Amazon +Web Services (AWS) workloads. These credentials are recommended over the +use of service account credentials in AWS as they do not involve the management +of long-live service account private keys. + +AWS Credentials are initialized using external_account arguments which are +typically loaded from the external credentials JSON file. +Unlike other Credentials that can be initialized with a list of explicit +arguments, secrets or credentials, external account clients use the +environment and hints/guidelines provided by the external_account JSON +file to retrieve credentials and exchange them for Google access tokens. + +This module also provides a basic implementation of the +`AWS Signature Version 4`_ request signing algorithm. + +AWS Credentials use serialized signed requests to the +`AWS STS GetCallerIdentity`_ API that can be exchanged for Google access tokens +via the GCP STS endpoint. + +.. _AWS Signature Version 4: https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html +.. _AWS STS GetCallerIdentity: https://docs.aws.amazon.com/STS/latest/APIReference/API_GetCallerIdentity.html +""" + +import hashlib +import hmac +import io +import json +import os +import re + +from six.moves import http_client +from six.moves import urllib + +from google.auth import _helpers +from google.auth import environment_vars +from google.auth import exceptions +from google.auth import external_account + +# AWS Signature Version 4 signing algorithm identifier. +_AWS_ALGORITHM = "AWS4-HMAC-SHA256" +# The termination string for the AWS credential scope value as defined in +# https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html +_AWS_REQUEST_TYPE = "aws4_request" +# The AWS authorization header name for the security session token if available. +_AWS_SECURITY_TOKEN_HEADER = "x-amz-security-token" +# The AWS authorization header name for the auto-generated date. +_AWS_DATE_HEADER = "x-amz-date" + + +class RequestSigner(object): + """Implements an AWS request signer based on the AWS Signature Version 4 signing + process. + https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html + """ + + def __init__(self, region_name): + """Instantiates an AWS request signer used to compute authenticated signed + requests to AWS APIs based on the AWS Signature Version 4 signing process. + + Args: + region_name (str): The AWS region to use. + """ + + self._region_name = region_name + + def get_request_options( + self, + aws_security_credentials, + url, + method, + request_payload="", + additional_headers={}, + ): + """Generates the signed request for the provided HTTP request for calling + an AWS API. This follows the steps described at: + https://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html + + Args: + aws_security_credentials (Mapping[str, str]): A dictionary containing + the AWS security credentials. + url (str): The AWS service URL containing the canonical URI and + query string. + method (str): The HTTP method used to call this API. + request_payload (Optional[str]): The optional request payload if + available. + additional_headers (Optional[Mapping[str, str]]): The optional + additional headers needed for the requested AWS API. + + Returns: + Mapping[str, str]: The AWS signed request dictionary object. + """ + # Get AWS credentials. + access_key = aws_security_credentials.get("access_key_id") + secret_key = aws_security_credentials.get("secret_access_key") + security_token = aws_security_credentials.get("security_token") + + additional_headers = additional_headers or {} + + uri = urllib.parse.urlparse(url) + # Validate provided URL. + if not uri.hostname or uri.scheme != "https": + raise ValueError("Invalid AWS service URL") + + header_map = _generate_authentication_header_map( + host=uri.hostname, + canonical_uri=os.path.normpath(uri.path or "/"), + canonical_querystring=_get_canonical_querystring(uri.query), + method=method, + region=self._region_name, + access_key=access_key, + secret_key=secret_key, + security_token=security_token, + request_payload=request_payload, + additional_headers=additional_headers, + ) + headers = { + "Authorization": header_map.get("authorization_header"), + "host": uri.hostname, + } + # Add x-amz-date if available. + if "amz_date" in header_map: + headers[_AWS_DATE_HEADER] = header_map.get("amz_date") + # Append additional optional headers, eg. X-Amz-Target, Content-Type, etc. + for key in additional_headers: + headers[key] = additional_headers[key] + + # Add session token if available. + if security_token is not None: + headers[_AWS_SECURITY_TOKEN_HEADER] = security_token + + signed_request = {"url": url, "method": method, "headers": headers} + if request_payload: + signed_request["data"] = request_payload + return signed_request + + +def _get_canonical_querystring(query): + """Generates the canonical query string given a raw query string. + Logic is based on + https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html + + Args: + query (str): The raw query string. + + Returns: + str: The canonical query string. + """ + # Parse raw query string. + querystring = urllib.parse.parse_qs(query) + querystring_encoded_map = {} + for key in querystring: + quote_key = urllib.parse.quote(key, safe="-_.~") + # URI encode key. + querystring_encoded_map[quote_key] = [] + for item in querystring[key]: + # For each key, URI encode all values for that key. + querystring_encoded_map[quote_key].append( + urllib.parse.quote(item, safe="-_.~") + ) + # Sort values for each key. + querystring_encoded_map[quote_key].sort() + # Sort keys. + sorted_keys = list(querystring_encoded_map.keys()) + sorted_keys.sort() + # Reconstruct the query string. Preserve keys with multiple values. + querystring_encoded_pairs = [] + for key in sorted_keys: + for item in querystring_encoded_map[key]: + querystring_encoded_pairs.append("{}={}".format(key, item)) + return "&".join(querystring_encoded_pairs) + + +def _sign(key, msg): + """Creates the HMAC-SHA256 hash of the provided message using the provided + key. + + Args: + key (str): The HMAC-SHA256 key to use. + msg (str): The message to hash. + + Returns: + str: The computed hash bytes. + """ + return hmac.new(key, msg.encode("utf-8"), hashlib.sha256).digest() + + +def _get_signing_key(key, date_stamp, region_name, service_name): + """Calculates the signing key used to calculate the signature for + AWS Signature Version 4 based on: + https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html + + Args: + key (str): The AWS secret access key. + date_stamp (str): The '%Y%m%d' date format. + region_name (str): The AWS region. + service_name (str): The AWS service name, eg. sts. + + Returns: + str: The signing key bytes. + """ + k_date = _sign(("AWS4" + key).encode("utf-8"), date_stamp) + k_region = _sign(k_date, region_name) + k_service = _sign(k_region, service_name) + k_signing = _sign(k_service, "aws4_request") + return k_signing + + +def _generate_authentication_header_map( + host, + canonical_uri, + canonical_querystring, + method, + region, + access_key, + secret_key, + security_token, + request_payload="", + additional_headers={}, +): + """Generates the authentication header map needed for generating the AWS + Signature Version 4 signed request. + + Args: + host (str): The AWS service URL hostname. + canonical_uri (str): The AWS service URL path name. + canonical_querystring (str): The AWS service URL query string. + method (str): The HTTP method used to call this API. + region (str): The AWS region. + access_key (str): The AWS access key ID. + secret_key (str): The AWS secret access key. + security_token (Optional[str]): The AWS security session token. This is + available for temporary sessions. + request_payload (Optional[str]): The optional request payload if + available. + additional_headers (Optional[Mapping[str, str]]): The optional + additional headers needed for the requested AWS API. + + Returns: + Mapping[str, str]: The AWS authentication header dictionary object. + This contains the x-amz-date and authorization header information. + """ + # iam.amazonaws.com host => iam service. + # sts.us-east-2.amazonaws.com host => sts service. + service_name = host.split(".")[0] + + current_time = _helpers.utcnow() + amz_date = current_time.strftime("%Y%m%dT%H%M%SZ") + date_stamp = current_time.strftime("%Y%m%d") + + # Change all additional headers to be lower case. + full_headers = {} + for key in additional_headers: + full_headers[key.lower()] = additional_headers[key] + # Add AWS session token if available. + if security_token is not None: + full_headers[_AWS_SECURITY_TOKEN_HEADER] = security_token + + # Required headers + full_headers["host"] = host + # Do not use generated x-amz-date if the date header is provided. + # Previously the date was not fixed with x-amz- and could be provided + # manually. + # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/get-header-value-trim.req + if "date" not in full_headers: + full_headers[_AWS_DATE_HEADER] = amz_date + + # Header keys need to be sorted alphabetically. + canonical_headers = "" + header_keys = list(full_headers.keys()) + header_keys.sort() + for key in header_keys: + canonical_headers = "{}{}:{}\n".format( + canonical_headers, key, full_headers[key] + ) + signed_headers = ";".join(header_keys) + + payload_hash = hashlib.sha256((request_payload or "").encode("utf-8")).hexdigest() + + # https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html + canonical_request = "{}\n{}\n{}\n{}\n{}\n{}".format( + method, + canonical_uri, + canonical_querystring, + canonical_headers, + signed_headers, + payload_hash, + ) + + credential_scope = "{}/{}/{}/{}".format( + date_stamp, region, service_name, _AWS_REQUEST_TYPE + ) + + # https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html + string_to_sign = "{}\n{}\n{}\n{}".format( + _AWS_ALGORITHM, + amz_date, + credential_scope, + hashlib.sha256(canonical_request.encode("utf-8")).hexdigest(), + ) + + # https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html + signing_key = _get_signing_key(secret_key, date_stamp, region, service_name) + signature = hmac.new( + signing_key, string_to_sign.encode("utf-8"), hashlib.sha256 + ).hexdigest() + + # https://docs.aws.amazon.com/general/latest/gr/sigv4-add-signature-to-request.html + authorization_header = "{} Credential={}/{}, SignedHeaders={}, Signature={}".format( + _AWS_ALGORITHM, access_key, credential_scope, signed_headers, signature + ) + + authentication_header = {"authorization_header": authorization_header} + # Do not use generated x-amz-date if the date header is provided. + if "date" not in full_headers: + authentication_header["amz_date"] = amz_date + return authentication_header + + +class Credentials(external_account.Credentials): + """AWS external account credentials. + This is used to exchange serialized AWS signature v4 signed requests to + AWS STS GetCallerIdentity service for Google access tokens. + """ + + def __init__( + self, + audience, + subject_token_type, + token_url, + credential_source=None, + service_account_impersonation_url=None, + client_id=None, + client_secret=None, + quota_project_id=None, + scopes=None, + default_scopes=None, + ): + """Instantiates an AWS workload external account credentials object. + + Args: + audience (str): The STS audience field. + subject_token_type (str): The subject token type. + token_url (str): The STS endpoint URL. + credential_source (Mapping): The credential source dictionary used + to provide instructions on how to retrieve external credential + to be exchanged for Google access tokens. + service_account_impersonation_url (Optional[str]): The optional + service account impersonation getAccessToken URL. + client_id (Optional[str]): The optional client ID. + client_secret (Optional[str]): The optional client secret. + quota_project_id (Optional[str]): The optional quota project ID. + scopes (Optional[Sequence[str]]): Optional scopes to request during + the authorization grant. + default_scopes (Optional[Sequence[str]]): Default scopes passed by a + Google client library. Use 'scopes' for user-defined scopes. + + Raises: + google.auth.exceptions.RefreshError: If an error is encountered during + access token retrieval logic. + ValueError: For invalid parameters. + + .. note:: Typically one of the helper constructors + :meth:`from_file` or + :meth:`from_info` are used instead of calling the constructor directly. + """ + super(Credentials, self).__init__( + audience=audience, + subject_token_type=subject_token_type, + token_url=token_url, + credential_source=credential_source, + service_account_impersonation_url=service_account_impersonation_url, + client_id=client_id, + client_secret=client_secret, + quota_project_id=quota_project_id, + scopes=scopes, + default_scopes=default_scopes, + ) + credential_source = credential_source or {} + self._environment_id = credential_source.get("environment_id") or "" + self._region_url = credential_source.get("region_url") + self._security_credentials_url = credential_source.get("url") + self._cred_verification_url = credential_source.get( + "regional_cred_verification_url" + ) + self._region = None + self._request_signer = None + self._target_resource = audience + + # Get the environment ID. Currently, only one version supported (v1). + matches = re.match(r"^(aws)([\d]+)$", self._environment_id) + if matches: + env_id, env_version = matches.groups() + else: + env_id, env_version = (None, None) + + if env_id != "aws" or self._cred_verification_url is None: + raise ValueError("No valid AWS 'credential_source' provided") + elif int(env_version or "") != 1: + raise ValueError( + "aws version '{}' is not supported in the current build.".format( + env_version + ) + ) + + def retrieve_subject_token(self, request): + """Retrieves the subject token using the credential_source object. + The subject token is a serialized `AWS GetCallerIdentity signed request`_. + + The logic is summarized as: + + Retrieve the AWS region from the AWS_REGION environment variable or from + the AWS metadata server availability-zone if not found in the + environment variable. + + Check AWS credentials in environment variables. If not found, retrieve + from the AWS metadata server security-credentials endpoint. + + When retrieving AWS credentials from the metadata server + security-credentials endpoint, the AWS role needs to be determined by + calling the security-credentials endpoint without any argument. Then the + credentials can be retrieved via: security-credentials/role_name + + Generate the signed request to AWS STS GetCallerIdentity action. + + Inject x-goog-cloud-target-resource into header and serialize the + signed request. This will be the subject-token to pass to GCP STS. + + .. _AWS GetCallerIdentity signed request: + https://cloud.google.com/iam/docs/access-resources-aws#exchange-token + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + Returns: + str: The retrieved subject token. + """ + # Initialize the request signer if not yet initialized after determining + # the current AWS region. + if self._request_signer is None: + self._region = self._get_region(request, self._region_url) + self._request_signer = RequestSigner(self._region) + + # Retrieve the AWS security credentials needed to generate the signed + # request. + aws_security_credentials = self._get_security_credentials(request) + # Generate the signed request to AWS STS GetCallerIdentity API. + # Use the required regional endpoint. Otherwise, the request will fail. + request_options = self._request_signer.get_request_options( + aws_security_credentials, + self._cred_verification_url.replace("{region}", self._region), + "POST", + ) + # The GCP STS endpoint expects the headers to be formatted as: + # [ + # {key: 'x-amz-date', value: '...'}, + # {key: 'Authorization', value: '...'}, + # ... + # ] + # And then serialized as: + # quote(json.dumps({ + # url: '...', + # method: 'POST', + # headers: [{key: 'x-amz-date', value: '...'}, ...] + # })) + request_headers = request_options.get("headers") + # The full, canonical resource name of the workload identity pool + # provider, with or without the HTTPS prefix. + # Including this header as part of the signature is recommended to + # ensure data integrity. + request_headers["x-goog-cloud-target-resource"] = self._target_resource + + # Serialize AWS signed request. + # Keeping inner keys in sorted order makes testing easier for Python + # versions <=3.5 as the stringified JSON string would have a predictable + # key order. + aws_signed_req = {} + aws_signed_req["url"] = request_options.get("url") + aws_signed_req["method"] = request_options.get("method") + aws_signed_req["headers"] = [] + # Reformat header to GCP STS expected format. + for key in sorted(request_headers.keys()): + aws_signed_req["headers"].append( + {"key": key, "value": request_headers[key]} + ) + + return urllib.parse.quote( + json.dumps(aws_signed_req, separators=(",", ":"), sort_keys=True) + ) + + def _get_region(self, request, url): + """Retrieves the current AWS region from either the AWS_REGION + environment variable or from the AWS metadata server. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + url (str): The AWS metadata server region URL. + + Returns: + str: The current AWS region. + + Raises: + google.auth.exceptions.RefreshError: If an error occurs while + retrieving the AWS region. + """ + # The AWS metadata server is not available in some AWS environments + # such as AWS lambda. Instead, it is available via environment + # variable. + env_aws_region = os.environ.get(environment_vars.AWS_REGION) + if env_aws_region is not None: + return env_aws_region + + if not self._region_url: + raise exceptions.RefreshError("Unable to determine AWS region") + response = request(url=self._region_url, method="GET") + + # Support both string and bytes type response.data. + response_body = ( + response.data.decode("utf-8") + if hasattr(response.data, "decode") + else response.data + ) + + if response.status != 200: + raise exceptions.RefreshError( + "Unable to retrieve AWS region", response_body + ) + + # This endpoint will return the region in format: us-east-2b. + # Only the us-east-2 part should be used. + return response_body[:-1] + + def _get_security_credentials(self, request): + """Retrieves the AWS security credentials required for signing AWS + requests from either the AWS security credentials environment variables + or from the AWS metadata server. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + + Returns: + Mapping[str, str]: The AWS security credentials dictionary object. + + Raises: + google.auth.exceptions.RefreshError: If an error occurs while + retrieving the AWS security credentials. + """ + + # Check environment variables for permanent credentials first. + # https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html + env_aws_access_key_id = os.environ.get(environment_vars.AWS_ACCESS_KEY_ID) + env_aws_secret_access_key = os.environ.get( + environment_vars.AWS_SECRET_ACCESS_KEY + ) + # This is normally not available for permanent credentials. + env_aws_session_token = os.environ.get(environment_vars.AWS_SESSION_TOKEN) + if env_aws_access_key_id and env_aws_secret_access_key: + return { + "access_key_id": env_aws_access_key_id, + "secret_access_key": env_aws_secret_access_key, + "security_token": env_aws_session_token, + } + + # Get role name. + role_name = self._get_metadata_role_name(request) + + # Get security credentials. + credentials = self._get_metadata_security_credentials(request, role_name) + + return { + "access_key_id": credentials.get("AccessKeyId"), + "secret_access_key": credentials.get("SecretAccessKey"), + "security_token": credentials.get("Token"), + } + + def _get_metadata_security_credentials(self, request, role_name): + """Retrieves the AWS security credentials required for signing AWS + requests from the AWS metadata server. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + role_name (str): The AWS role name required by the AWS metadata + server security_credentials endpoint in order to return the + credentials. + + Returns: + Mapping[str, str]: The AWS metadata server security credentials + response. + + Raises: + google.auth.exceptions.RefreshError: If an error occurs while + retrieving the AWS security credentials. + """ + headers = {"Content-Type": "application/json"} + response = request( + url="{}/{}".format(self._security_credentials_url, role_name), + method="GET", + headers=headers, + ) + + # support both string and bytes type response.data + response_body = ( + response.data.decode("utf-8") + if hasattr(response.data, "decode") + else response.data + ) + + if response.status != http_client.OK: + raise exceptions.RefreshError( + "Unable to retrieve AWS security credentials", response_body + ) + + credentials_response = json.loads(response_body) + + return credentials_response + + def _get_metadata_role_name(self, request): + """Retrieves the AWS role currently attached to the current AWS + workload by querying the AWS metadata server. This is needed for the + AWS metadata server security credentials endpoint in order to retrieve + the AWS security credentials needed to sign requests to AWS APIs. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + + Returns: + str: The AWS role name. + + Raises: + google.auth.exceptions.RefreshError: If an error occurs while + retrieving the AWS role name. + """ + if self._security_credentials_url is None: + raise exceptions.RefreshError( + "Unable to determine the AWS metadata server security credentials endpoint" + ) + response = request(url=self._security_credentials_url, method="GET") + + # support both string and bytes type response.data + response_body = ( + response.data.decode("utf-8") + if hasattr(response.data, "decode") + else response.data + ) + + if response.status != http_client.OK: + raise exceptions.RefreshError( + "Unable to retrieve AWS role name", response_body + ) + + return response_body + + @classmethod + def from_info(cls, info, **kwargs): + """Creates an AWS Credentials instance from parsed external account info. + + Args: + info (Mapping[str, str]): The AWS external account info in Google + format. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.aws.Credentials: The constructed credentials. + + Raises: + ValueError: For invalid parameters. + """ + return cls( + audience=info.get("audience"), + subject_token_type=info.get("subject_token_type"), + token_url=info.get("token_url"), + service_account_impersonation_url=info.get( + "service_account_impersonation_url" + ), + client_id=info.get("client_id"), + client_secret=info.get("client_secret"), + credential_source=info.get("credential_source"), + quota_project_id=info.get("quota_project_id"), + **kwargs + ) + + @classmethod + def from_file(cls, filename, **kwargs): + """Creates an AWS Credentials instance from an external account json file. + + Args: + filename (str): The path to the AWS external account json file. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.aws.Credentials: The constructed credentials. + """ + with io.open(filename, "r", encoding="utf-8") as json_file: + data = json.load(json_file) + return cls.from_info(data, **kwargs) diff --git a/packages/google-auth/google/auth/environment_vars.py b/packages/google-auth/google/auth/environment_vars.py index 46a8926646b9..416bab0c01ea 100644 --- a/packages/google-auth/google/auth/environment_vars.py +++ b/packages/google-auth/google/auth/environment_vars.py @@ -59,3 +59,13 @@ The default value is false. Users have to explicitly set this value to true in order to use client certificate to establish a mutual TLS channel.""" + +# AWS environment variables used with AWS workload identity pools to retrieve +# AWS security credentials and the AWS region needed to create a serialized +# signed requests to the AWS STS GetCalledIdentity API that can be exchanged +# for a Google access tokens via the GCP STS endpoint. +# When not available the AWS metadata server is used to retrieve these values. +AWS_ACCESS_KEY_ID = "AWS_ACCESS_KEY_ID" +AWS_SECRET_ACCESS_KEY = "AWS_SECRET_ACCESS_KEY" +AWS_SESSION_TOKEN = "AWS_SESSION_TOKEN" +AWS_REGION = "AWS_REGION" diff --git a/packages/google-auth/google/auth/exceptions.py b/packages/google-auth/google/auth/exceptions.py index da06d8696283..b6f686bbb57c 100644 --- a/packages/google-auth/google/auth/exceptions.py +++ b/packages/google-auth/google/auth/exceptions.py @@ -43,3 +43,8 @@ class MutualTLSChannelError(GoogleAuthError): class ClientCertError(GoogleAuthError): """Used to indicate that client certificate is missing or invalid.""" + + +class OAuthError(GoogleAuthError): + """Used to indicate an error occurred during an OAuth related HTTP + request.""" diff --git a/packages/google-auth/google/auth/external_account.py b/packages/google-auth/google/auth/external_account.py new file mode 100644 index 000000000000..0429ee08f4ff --- /dev/null +++ b/packages/google-auth/google/auth/external_account.py @@ -0,0 +1,305 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""External Account Credentials. + +This module provides credentials that exchange workload identity pool external +credentials for Google access tokens. This facilitates accessing Google Cloud +Platform resources from on-prem and non-Google Cloud platforms (e.g. AWS, +Microsoft Azure, OIDC identity providers), using native credentials retrieved +from the current environment without the need to copy, save and manage +long-lived service account credentials. + +Specifically, this is intended to use access tokens acquired using the GCP STS +token exchange endpoint following the `OAuth 2.0 Token Exchange`_ spec. + +.. _OAuth 2.0 Token Exchange: https://tools.ietf.org/html/rfc8693 +""" + +import abc +import datetime +import json + +import six + +from google.auth import _helpers +from google.auth import credentials +from google.auth import exceptions +from google.auth import impersonated_credentials +from google.oauth2 import sts +from google.oauth2 import utils + +# The token exchange grant_type used for exchanging credentials. +_STS_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:token-exchange" +# The token exchange requested_token_type. This is always an access_token. +_STS_REQUESTED_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token" +# Cloud resource manager URL used to retrieve project information. +_CLOUD_RESOURCE_MANAGER = "https://cloudresourcemanager.googleapis.com/v1/projects/" + + +@six.add_metaclass(abc.ABCMeta) +class Credentials(credentials.Scoped, credentials.CredentialsWithQuotaProject): + """Base class for all external account credentials. + + This is used to instantiate Credentials for exchanging external account + credentials for Google access token and authorizing requests to Google APIs. + The base class implements the common logic for exchanging external account + credentials for Google access tokens. + """ + + def __init__( + self, + audience, + subject_token_type, + token_url, + credential_source, + service_account_impersonation_url=None, + client_id=None, + client_secret=None, + quota_project_id=None, + scopes=None, + default_scopes=None, + ): + """Instantiates an external account credentials object. + + Args: + audience (str): The STS audience field. + subject_token_type (str): The subject token type. + token_url (str): The STS endpoint URL. + credential_source (Mapping): The credential source dictionary. + service_account_impersonation_url (Optional[str]): The optional service account + impersonation generateAccessToken URL. + client_id (Optional[str]): The optional client ID. + client_secret (Optional[str]): The optional client secret. + quota_project_id (Optional[str]): The optional quota project ID. + scopes (Optional[Sequence[str]]): Optional scopes to request during the + authorization grant. + default_scopes (Optional[Sequence[str]]): Default scopes passed by a + Google client library. Use 'scopes' for user-defined scopes. + Raises: + google.auth.exceptions.RefreshError: If the generateAccessToken + endpoint returned an error. + """ + super(Credentials, self).__init__() + self._audience = audience + self._subject_token_type = subject_token_type + self._token_url = token_url + self._credential_source = credential_source + self._service_account_impersonation_url = service_account_impersonation_url + self._client_id = client_id + self._client_secret = client_secret + self._quota_project_id = quota_project_id + self._scopes = scopes + self._default_scopes = default_scopes + + if self._client_id: + self._client_auth = utils.ClientAuthentication( + utils.ClientAuthType.basic, self._client_id, self._client_secret + ) + else: + self._client_auth = None + self._sts_client = sts.Client(self._token_url, self._client_auth) + + if self._service_account_impersonation_url: + self._impersonated_credentials = self._initialize_impersonated_credentials() + else: + self._impersonated_credentials = None + self._project_id = None + + @property + def requires_scopes(self): + """Checks if the credentials requires scopes. + + Returns: + bool: True if there are no scopes set otherwise False. + """ + return not self._scopes and not self._default_scopes + + @property + def project_number(self): + """Optional[str]: The project number corresponding to the workload identity pool.""" + + # STS audience pattern: + # //iam.googleapis.com/projects/$PROJECT_NUMBER/locations/... + components = self._audience.split("/") + try: + project_index = components.index("projects") + if project_index + 1 < len(components): + return components[project_index + 1] or None + except ValueError: + return None + + @_helpers.copy_docstring(credentials.Scoped) + def with_scopes(self, scopes, default_scopes=None): + return self.__class__( + audience=self._audience, + subject_token_type=self._subject_token_type, + token_url=self._token_url, + credential_source=self._credential_source, + service_account_impersonation_url=self._service_account_impersonation_url, + client_id=self._client_id, + client_secret=self._client_secret, + quota_project_id=self._quota_project_id, + scopes=scopes, + default_scopes=default_scopes, + ) + + @abc.abstractmethod + def retrieve_subject_token(self, request): + """Retrieves the subject token using the credential_source object. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + Returns: + str: The retrieved subject token. + """ + # pylint: disable=missing-raises-doc + # (pylint doesn't recognize that this is abstract) + raise NotImplementedError("retrieve_subject_token must be implemented") + + def get_project_id(self, request): + """Retrieves the project ID corresponding to the workload identity pool. + + When not determinable, None is returned. + + This is introduced to support the current pattern of using the Auth library: + + credentials, project_id = google.auth.default() + + The resource may not have permission (resourcemanager.projects.get) to + call this API or the required scopes may not be selected: + https://cloud.google.com/resource-manager/reference/rest/v1/projects/get#authorization-scopes + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + Returns: + Optional[str]: The project ID corresponding to the workload identity pool + if determinable. + """ + if self._project_id: + # If already retrieved, return the cached project ID value. + return self._project_id + scopes = self._scopes if self._scopes is not None else self._default_scopes + # Scopes are required in order to retrieve a valid access token. + if self.project_number and scopes: + headers = {} + url = _CLOUD_RESOURCE_MANAGER + self.project_number + self.before_request(request, "GET", url, headers) + response = request(url=url, method="GET", headers=headers) + + response_body = ( + response.data.decode("utf-8") + if hasattr(response.data, "decode") + else response.data + ) + response_data = json.loads(response_body) + + if response.status == 200: + # Cache result as this field is immutable. + self._project_id = response_data.get("projectId") + return self._project_id + + return None + + @_helpers.copy_docstring(credentials.Credentials) + def refresh(self, request): + scopes = self._scopes if self._scopes is not None else self._default_scopes + if self._impersonated_credentials: + self._impersonated_credentials.refresh(request) + self.token = self._impersonated_credentials.token + self.expiry = self._impersonated_credentials.expiry + else: + now = _helpers.utcnow() + response_data = self._sts_client.exchange_token( + request=request, + grant_type=_STS_GRANT_TYPE, + subject_token=self.retrieve_subject_token(request), + subject_token_type=self._subject_token_type, + audience=self._audience, + scopes=scopes, + requested_token_type=_STS_REQUESTED_TOKEN_TYPE, + ) + self.token = response_data.get("access_token") + lifetime = datetime.timedelta(seconds=response_data.get("expires_in")) + self.expiry = now + lifetime + + @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) + def with_quota_project(self, quota_project_id): + # Return copy of instance with the provided quota project ID. + return self.__class__( + audience=self._audience, + subject_token_type=self._subject_token_type, + token_url=self._token_url, + credential_source=self._credential_source, + service_account_impersonation_url=self._service_account_impersonation_url, + client_id=self._client_id, + client_secret=self._client_secret, + quota_project_id=quota_project_id, + scopes=self._scopes, + default_scopes=self._default_scopes, + ) + + def _initialize_impersonated_credentials(self): + """Generates an impersonated credentials. + + For more details, see `projects.serviceAccounts.generateAccessToken`_. + + .. _projects.serviceAccounts.generateAccessToken: https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/generateAccessToken + + Returns: + impersonated_credentials.Credential: The impersonated credentials + object. + + Raises: + google.auth.exceptions.RefreshError: If the generateAccessToken + endpoint returned an error. + """ + # Return copy of instance with no service account impersonation. + source_credentials = self.__class__( + audience=self._audience, + subject_token_type=self._subject_token_type, + token_url=self._token_url, + credential_source=self._credential_source, + service_account_impersonation_url=None, + client_id=self._client_id, + client_secret=self._client_secret, + quota_project_id=self._quota_project_id, + scopes=self._scopes, + default_scopes=self._default_scopes, + ) + + # Determine target_principal. + start_index = self._service_account_impersonation_url.rfind("/") + end_index = self._service_account_impersonation_url.find(":generateAccessToken") + if start_index != -1 and end_index != -1 and start_index < end_index: + start_index = start_index + 1 + target_principal = self._service_account_impersonation_url[ + start_index:end_index + ] + else: + raise exceptions.RefreshError( + "Unable to determine target principal from service account impersonation URL." + ) + + scopes = self._scopes if self._scopes is not None else self._default_scopes + # Initialize and return impersonated credentials. + return impersonated_credentials.Credentials( + source_credentials=source_credentials, + target_principal=target_principal, + target_scopes=scopes, + quota_project_id=self._quota_project_id, + iam_endpoint_override=self._service_account_impersonation_url, + ) diff --git a/packages/google-auth/google/auth/identity_pool.py b/packages/google-auth/google/auth/identity_pool.py new file mode 100644 index 000000000000..53621995557f --- /dev/null +++ b/packages/google-auth/google/auth/identity_pool.py @@ -0,0 +1,279 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Identity Pool Credentials. + +This module provides credentials to access Google Cloud resources from on-prem +or non-Google Cloud platforms which support external credentials (e.g. OIDC ID +tokens) retrieved from local file locations or local servers. This includes +Microsoft Azure and OIDC identity providers (e.g. K8s workloads registered with +Hub with Hub workload identity enabled). + +These credentials are recommended over the use of service account credentials +in on-prem/non-Google Cloud platforms as they do not involve the management of +long-live service account private keys. + +Identity Pool Credentials are initialized using external_account +arguments which are typically loaded from an external credentials file or +an external credentials URL. Unlike other Credentials that can be initialized +with a list of explicit arguments, secrets or credentials, external account +clients use the environment and hints/guidelines provided by the +external_account JSON file to retrieve credentials and exchange them for Google +access tokens. +""" + +try: + from collections.abc import Mapping +# Python 2.7 compatibility +except ImportError: # pragma: NO COVER + from collections import Mapping +import io +import json +import os + +from google.auth import _helpers +from google.auth import exceptions +from google.auth import external_account + + +class Credentials(external_account.Credentials): + """External account credentials sourced from files and URLs.""" + + def __init__( + self, + audience, + subject_token_type, + token_url, + credential_source, + service_account_impersonation_url=None, + client_id=None, + client_secret=None, + quota_project_id=None, + scopes=None, + default_scopes=None, + ): + """Instantiates an external account credentials object from a file/URL. + + Args: + audience (str): The STS audience field. + subject_token_type (str): The subject token type. + token_url (str): The STS endpoint URL. + credential_source (Mapping): The credential source dictionary used to + provide instructions on how to retrieve external credential to be + exchanged for Google access tokens. + + Example credential_source for url-sourced credential:: + + { + "url": "http://www.example.com", + "format": { + "type": "json", + "subject_token_field_name": "access_token", + }, + "headers": {"foo": "bar"}, + } + + Example credential_source for file-sourced credential:: + + { + "file": "/path/to/token/file.txt" + } + + service_account_impersonation_url (Optional[str]): The optional service account + impersonation getAccessToken URL. + client_id (Optional[str]): The optional client ID. + client_secret (Optional[str]): The optional client secret. + quota_project_id (Optional[str]): The optional quota project ID. + scopes (Optional[Sequence[str]]): Optional scopes to request during the + authorization grant. + default_scopes (Optional[Sequence[str]]): Default scopes passed by a + Google client library. Use 'scopes' for user-defined scopes. + + Raises: + google.auth.exceptions.RefreshError: If an error is encountered during + access token retrieval logic. + ValueError: For invalid parameters. + + .. note:: Typically one of the helper constructors + :meth:`from_file` or + :meth:`from_info` are used instead of calling the constructor directly. + """ + + super(Credentials, self).__init__( + audience=audience, + subject_token_type=subject_token_type, + token_url=token_url, + credential_source=credential_source, + service_account_impersonation_url=service_account_impersonation_url, + client_id=client_id, + client_secret=client_secret, + quota_project_id=quota_project_id, + scopes=scopes, + default_scopes=default_scopes, + ) + if not isinstance(credential_source, Mapping): + self._credential_source_file = None + self._credential_source_url = None + else: + self._credential_source_file = credential_source.get("file") + self._credential_source_url = credential_source.get("url") + self._credential_source_headers = credential_source.get("headers") + credential_source_format = credential_source.get("format", {}) + # Get credential_source format type. When not provided, this + # defaults to text. + self._credential_source_format_type = ( + credential_source_format.get("type") or "text" + ) + # environment_id is only supported in AWS or dedicated future external + # account credentials. + if "environment_id" in credential_source: + raise ValueError( + "Invalid Identity Pool credential_source field 'environment_id'" + ) + if self._credential_source_format_type not in ["text", "json"]: + raise ValueError( + "Invalid credential_source format '{}'".format( + self._credential_source_format_type + ) + ) + # For JSON types, get the required subject_token field name. + if self._credential_source_format_type == "json": + self._credential_source_field_name = credential_source_format.get( + "subject_token_field_name" + ) + if self._credential_source_field_name is None: + raise ValueError( + "Missing subject_token_field_name for JSON credential_source format" + ) + else: + self._credential_source_field_name = None + + if self._credential_source_file and self._credential_source_url: + raise ValueError( + "Ambiguous credential_source. 'file' is mutually exclusive with 'url'." + ) + if not self._credential_source_file and not self._credential_source_url: + raise ValueError( + "Missing credential_source. A 'file' or 'url' must be provided." + ) + + @_helpers.copy_docstring(external_account.Credentials) + def retrieve_subject_token(self, request): + return self._parse_token_data( + self._get_token_data(request), + self._credential_source_format_type, + self._credential_source_field_name, + ) + + def _get_token_data(self, request): + if self._credential_source_file: + return self._get_file_data(self._credential_source_file) + else: + return self._get_url_data( + request, self._credential_source_url, self._credential_source_headers + ) + + def _get_file_data(self, filename): + if not os.path.exists(filename): + raise exceptions.RefreshError("File '{}' was not found.".format(filename)) + + with io.open(filename, "r", encoding="utf-8") as file_obj: + return file_obj.read(), filename + + def _get_url_data(self, request, url, headers): + response = request(url=url, method="GET", headers=headers) + + # support both string and bytes type response.data + response_body = ( + response.data.decode("utf-8") + if hasattr(response.data, "decode") + else response.data + ) + + if response.status != 200: + raise exceptions.RefreshError( + "Unable to retrieve Identity Pool subject token", response_body + ) + + return response_body, url + + def _parse_token_data( + self, token_content, format_type="text", subject_token_field_name=None + ): + content, filename = token_content + if format_type == "text": + token = content + else: + try: + # Parse file content as JSON. + response_data = json.loads(content) + # Get the subject_token. + token = response_data[subject_token_field_name] + except (KeyError, ValueError): + raise exceptions.RefreshError( + "Unable to parse subject_token from JSON file '{}' using key '{}'".format( + filename, subject_token_field_name + ) + ) + if not token: + raise exceptions.RefreshError( + "Missing subject_token in the credential_source file" + ) + return token + + @classmethod + def from_info(cls, info, **kwargs): + """Creates an Identity Pool Credentials instance from parsed external account info. + + Args: + info (Mapping[str, str]): The Identity Pool external account info in Google + format. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.identity_pool.Credentials: The constructed + credentials. + + Raises: + ValueError: For invalid parameters. + """ + return cls( + audience=info.get("audience"), + subject_token_type=info.get("subject_token_type"), + token_url=info.get("token_url"), + service_account_impersonation_url=info.get( + "service_account_impersonation_url" + ), + client_id=info.get("client_id"), + client_secret=info.get("client_secret"), + credential_source=info.get("credential_source"), + quota_project_id=info.get("quota_project_id"), + **kwargs + ) + + @classmethod + def from_file(cls, filename, **kwargs): + """Creates an IdentityPool Credentials instance from an external account json file. + + Args: + filename (str): The path to the IdentityPool external account json file. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.identity_pool.Credentials: The constructed + credentials. + """ + with io.open(filename, "r", encoding="utf-8") as json_file: + data = json.load(json_file) + return cls.from_info(data, **kwargs) diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index 4d158373a7e8..b8a6c49a1eba 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -65,7 +65,9 @@ _DEFAULT_TOKEN_URI = "https://oauth2.googleapis.com/token" -def _make_iam_token_request(request, principal, headers, body): +def _make_iam_token_request( + request, principal, headers, body, iam_endpoint_override=None +): """Makes a request to the Google Cloud IAM service for an access token. Args: request (Request): The Request object to use. @@ -73,6 +75,9 @@ def _make_iam_token_request(request, principal, headers, body): headers (Mapping[str, str]): Map of headers to transmit. body (Mapping[str, str]): JSON Payload body for the iamcredentials API call. + iam_endpoint_override (Optiona[str]): The full IAM endpoint override + with the target_principal embedded. This is useful when supporting + impersonation with regional endpoints. Raises: google.auth.exceptions.TransportError: Raised if there is an underlying @@ -82,7 +87,7 @@ def _make_iam_token_request(request, principal, headers, body): `iamcredentials.googleapis.com` is not enabled or the `Service Account Token Creator` is not assigned """ - iam_endpoint = _IAM_ENDPOINT.format(principal) + iam_endpoint = iam_endpoint_override or _IAM_ENDPOINT.format(principal) body = json.dumps(body).encode("utf-8") @@ -185,6 +190,7 @@ def __init__( delegates=None, lifetime=_DEFAULT_TOKEN_LIFETIME_SECS, quota_project_id=None, + iam_endpoint_override=None, ): """ Args: @@ -209,6 +215,9 @@ def __init__( quota_project_id (Optional[str]): The project ID used for quota and billing. This project may be different from the project used to create the credentials. + iam_endpoint_override (Optiona[str]): The full IAM endpoint override + with the target_principal embedded. This is useful when supporting + impersonation with regional endpoints. """ super(Credentials, self).__init__() @@ -226,6 +235,7 @@ def __init__( self.token = None self.expiry = _helpers.utcnow() self._quota_project_id = quota_project_id + self._iam_endpoint_override = iam_endpoint_override @_helpers.copy_docstring(credentials.Credentials) def refresh(self, request): @@ -260,6 +270,7 @@ def _update_token(self, request): principal=self._target_principal, headers=headers, body=body, + iam_endpoint_override=self._iam_endpoint_override, ) def sign_bytes(self, message): @@ -302,6 +313,7 @@ def with_quota_project(self, quota_project_id): delegates=self._delegates, lifetime=self._lifetime, quota_project_id=quota_project_id, + iam_endpoint_override=self._iam_endpoint_override, ) diff --git a/packages/google-auth/google/oauth2/sts.py b/packages/google-auth/google/oauth2/sts.py new file mode 100644 index 000000000000..ae3c0146b114 --- /dev/null +++ b/packages/google-auth/google/oauth2/sts.py @@ -0,0 +1,155 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""OAuth 2.0 Token Exchange Spec. + +This module defines a token exchange utility based on the `OAuth 2.0 Token +Exchange`_ spec. This will be mainly used to exchange external credentials +for GCP access tokens in workload identity pools to access Google APIs. + +The implementation will support various types of client authentication as +allowed in the spec. + +A deviation on the spec will be for additional Google specific options that +cannot be easily mapped to parameters defined in the RFC. + +The returned dictionary response will be based on the `rfc8693 section 2.2.1`_ +spec JSON response. + +.. _OAuth 2.0 Token Exchange: https://tools.ietf.org/html/rfc8693 +.. _rfc8693 section 2.2.1: https://tools.ietf.org/html/rfc8693#section-2.2.1 +""" + +import json + +from six.moves import http_client +from six.moves import urllib + +from google.oauth2 import utils + + +_URLENCODED_HEADERS = {"Content-Type": "application/x-www-form-urlencoded"} + + +class Client(utils.OAuthClientAuthHandler): + """Implements the OAuth 2.0 token exchange spec based on + https://tools.ietf.org/html/rfc8693. + """ + + def __init__(self, token_exchange_endpoint, client_authentication=None): + """Initializes an STS client instance. + + Args: + token_exchange_endpoint (str): The token exchange endpoint. + client_authentication (Optional(google.oauth2.oauth2_utils.ClientAuthentication)): + The optional OAuth client authentication credentials if available. + """ + super(Client, self).__init__(client_authentication) + self._token_exchange_endpoint = token_exchange_endpoint + + def exchange_token( + self, + request, + grant_type, + subject_token, + subject_token_type, + resource=None, + audience=None, + scopes=None, + requested_token_type=None, + actor_token=None, + actor_token_type=None, + additional_options=None, + additional_headers=None, + ): + """Exchanges the provided token for another type of token based on the + rfc8693 spec. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + grant_type (str): The OAuth 2.0 token exchange grant type. + subject_token (str): The OAuth 2.0 token exchange subject token. + subject_token_type (str): The OAuth 2.0 token exchange subject token type. + resource (Optional[str]): The optional OAuth 2.0 token exchange resource field. + audience (Optional[str]): The optional OAuth 2.0 token exchange audience field. + scopes (Optional[Sequence[str]]): The optional list of scopes to use. + requested_token_type (Optional[str]): The optional OAuth 2.0 token exchange requested + token type. + actor_token (Optional[str]): The optional OAuth 2.0 token exchange actor token. + actor_token_type (Optional[str]): The optional OAuth 2.0 token exchange actor token type. + additional_options (Optional[Mapping[str, str]]): The optional additional + non-standard Google specific options. + additional_headers (Optional[Mapping[str, str]]): The optional additional + headers to pass to the token exchange endpoint. + + Returns: + Mapping[str, str]: The token exchange JSON-decoded response data containing + the requested token and its expiration time. + + Raises: + google.auth.exceptions.OAuthError: If the token endpoint returned + an error. + """ + # Initialize request headers. + headers = _URLENCODED_HEADERS.copy() + # Inject additional headers. + if additional_headers: + for k, v in dict(additional_headers).items(): + headers[k] = v + # Initialize request body. + request_body = { + "grant_type": grant_type, + "resource": resource, + "audience": audience, + "scope": " ".join(scopes or []), + "requested_token_type": requested_token_type, + "subject_token": subject_token, + "subject_token_type": subject_token_type, + "actor_token": actor_token, + "actor_token_type": actor_token_type, + "options": None, + } + # Add additional non-standard options. + if additional_options: + request_body["options"] = urllib.parse.quote(json.dumps(additional_options)) + # Remove empty fields in request body. + for k, v in dict(request_body).items(): + if v is None or v == "": + del request_body[k] + # Apply OAuth client authentication. + self.apply_client_authentication_options(headers, request_body) + + # Execute request. + response = request( + url=self._token_exchange_endpoint, + method="POST", + headers=headers, + body=urllib.parse.urlencode(request_body).encode("utf-8"), + ) + + response_body = ( + response.data.decode("utf-8") + if hasattr(response.data, "decode") + else response.data + ) + + # If non-200 response received, translate to OAuthError exception. + if response.status != http_client.OK: + utils.handle_error_response(response_body) + + response_data = json.loads(response_body) + + # Return successful response. + return response_data diff --git a/packages/google-auth/google/oauth2/utils.py b/packages/google-auth/google/oauth2/utils.py new file mode 100644 index 000000000000..efda7968dbaa --- /dev/null +++ b/packages/google-auth/google/oauth2/utils.py @@ -0,0 +1,171 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""OAuth 2.0 Utilities. + +This module provides implementations for various OAuth 2.0 utilities. +This includes `OAuth error handling`_ and +`Client authentication for OAuth flows`_. + +OAuth error handling +-------------------- +This will define interfaces for handling OAuth related error responses as +stated in `RFC 6749 section 5.2`_. +This will include a common function to convert these HTTP error responses to a +:class:`google.auth.exceptions.OAuthError` exception. + + +Client authentication for OAuth flows +------------------------------------- +We introduce an interface for defining client authentication credentials based +on `RFC 6749 section 2.3.1`_. This will expose the following +capabilities: + + * Ability to support basic authentication via request header. + * Ability to support bearer token authentication via request header. + * Ability to support client ID / secret authentication via request body. + +.. _RFC 6749 section 2.3.1: https://tools.ietf.org/html/rfc6749#section-2.3.1 +.. _RFC 6749 section 5.2: https://tools.ietf.org/html/rfc6749#section-5.2 +""" + +import abc +import base64 +import enum +import json + +import six + +from google.auth import exceptions + + +# OAuth client authentication based on +# https://tools.ietf.org/html/rfc6749#section-2.3. +class ClientAuthType(enum.Enum): + basic = 1 + request_body = 2 + + +class ClientAuthentication(object): + """Defines the client authentication credentials for basic and request-body + types based on https://tools.ietf.org/html/rfc6749#section-2.3.1. + """ + + def __init__(self, client_auth_type, client_id, client_secret=None): + """Instantiates a client authentication object containing the client ID + and secret credentials for basic and response-body auth. + + Args: + client_auth_type (google.oauth2.oauth_utils.ClientAuthType): The + client authentication type. + client_id (str): The client ID. + client_secret (Optional[str]): The client secret. + """ + self.client_auth_type = client_auth_type + self.client_id = client_id + self.client_secret = client_secret + + +@six.add_metaclass(abc.ABCMeta) +class OAuthClientAuthHandler(object): + """Abstract class for handling client authentication in OAuth-based + operations. + """ + + def __init__(self, client_authentication=None): + """Instantiates an OAuth client authentication handler. + + Args: + client_authentication (Optional[google.oauth2.utils.ClientAuthentication]): + The OAuth client authentication credentials if available. + """ + super(OAuthClientAuthHandler, self).__init__() + self._client_authentication = client_authentication + + def apply_client_authentication_options( + self, headers, request_body=None, bearer_token=None + ): + """Applies client authentication on the OAuth request's headers or POST + body. + + Args: + headers (Mapping[str, str]): The HTTP request header. + request_body (Optional[Mapping[str, str]): The HTTP request body + dictionary. For requests that do not support request body, this + is None and will be ignored. + bearer_token (Optional[str]): The optional bearer token. + """ + # Inject authenticated header. + self._inject_authenticated_headers(headers, bearer_token) + # Inject authenticated request body. + if bearer_token is None: + self._inject_authenticated_request_body(request_body) + + def _inject_authenticated_headers(self, headers, bearer_token=None): + if bearer_token is not None: + headers["Authorization"] = "Bearer %s" % bearer_token + elif ( + self._client_authentication is not None + and self._client_authentication.client_auth_type is ClientAuthType.basic + ): + username = self._client_authentication.client_id + password = self._client_authentication.client_secret or "" + + credentials = base64.b64encode( + ("%s:%s" % (username, password)).encode() + ).decode() + headers["Authorization"] = "Basic %s" % credentials + + def _inject_authenticated_request_body(self, request_body): + if ( + self._client_authentication is not None + and self._client_authentication.client_auth_type + is ClientAuthType.request_body + ): + if request_body is None: + raise exceptions.OAuthError( + "HTTP request does not support request-body" + ) + else: + request_body["client_id"] = self._client_authentication.client_id + request_body["client_secret"] = ( + self._client_authentication.client_secret or "" + ) + + +def handle_error_response(response_body): + """Translates an error response from an OAuth operation into an + OAuthError exception. + + Args: + response_body (str): The decoded response data. + + Raises: + google.auth.exceptions.OAuthError + """ + try: + error_components = [] + error_data = json.loads(response_body) + + error_components.append("Error code {}".format(error_data["error"])) + if "error_description" in error_data: + error_components.append(": {}".format(error_data["error_description"])) + if "error_uri" in error_data: + error_components.append(" - {}".format(error_data["error_uri"])) + error_details = "".join(error_components) + # If no details could be extracted, use the response data. + except (KeyError, ValueError): + error_details = response_body + + raise exceptions.OAuthError(error_details, response_body) diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index 7bc1af24d8ca..6ce6346a5723 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -64,9 +64,7 @@ def lint(session): @nox.session(python="3.6") def blacken(session): """Run black. - Format code to uniform standard. - This currently uses Python 3.6 due to the automated Kokoro run of synthtool. That run uses an image that doesn't have 3.6 installed. Before updating this check the state of the `gcp_ubuntu_config` we use for that Kokoro run. diff --git a/packages/google-auth/system_tests/system_tests_sync/secrets.tar.enc b/packages/google-auth/system_tests/system_tests_sync/secrets.tar.enc new file mode 100644 index 0000000000000000000000000000000000000000..29e06923f0f028b54d1b571dc218cdd92f751bd8 GIT binary patch literal 10323 zcmV-ZD6H2CBmnkJRTBwSWM{gM-(V^p%EE!t47)2O-B@PEC;N&CK7lj=NfN410P28c zRW09t-E~tWuuT+K20dR7P>EbvKa*w=$5^VkvC9(3v$y#D`q%`*Uv;w4{KyWG5U~6; z(nGM#>68uajK;@SsI%yq=FECaK632Cu(T zReEXP{^cbLqNk^+VI8t)fyD}GSBJFk%F5pe1~S9%LdCxQOEQ8T{nh|srHG_wVplRRi~iTYp3adC5RbSm z%_uRC3F6H_$tx(>@Kl}5{7KIzMa1w`*OlAQ@n-zvca#n43xeXy<_AYgUi^C+ z>GcFEN0nwI2*r9lE(>!QVmoTbX98%0dLUUzv(K>D1JHNNPjSuR#Anz++_I#u!ak@$`Epd8!{oT>*d1f)k4%HBb=F)x zYeoFGcIp+G{NZzEgH|);=W96FhtjxAeF~HM^!2m%vH;k-I7cWdQECfp(KMIqaQB-+ z)YYb=Jy_lA7Vb)qzL=i5)Twp1D0b;Sm$UG#Rou9RN zoH*!(ydV~5gOuQ#o7X&$JQi+WwDgX%lZa`L02EYz_;L5gbMWe#9dc$r0>^oaKmqI+ z5rHIXiJd^lspPePBL~-{4I!a_!mGnv8fq)8|41{TTjCNcmq#^t9J8{Diyrcw6@_p9d|jebj6Wj=96xeSy+ZJ zdf{+PnRE@K6wXlitZL;_1ELh^MElqYUM`d4;2lpCb3+32=R5d+CjUj1U2gj?A$~)u zvr6Yo2Fj}D{=QTnmkQ;;gai<*n!0?C&3O)&t#J)tL6`ud2m@K5%Y;2f3HJ)N3F4l0eV= zpm*=~if;!lC+#{GIp=9Bd0tezk_}OX=-#4~QYR%y%k12stiC_?`k#PH2gfYgQ1ipf z)i->sY2#!fRy9n%AH_lsVCgsk8&UqGQKxSbIFJ8N;3)r=y?S^_pUOf~B=XdJIZ@RG ztYIe__zsVj-=gBL4d5q5Xj|k$oY5*?W&l=9%vHL_e?p*E+@!qdMpY}f2e21nksAo0 zsH@xO>MvuLDJLnztA;r4S*(uZ_wMU5NV?7YoE5l3{}#Gt@*yM{hX_^B5H+dV6GTnd zC|yDXJya+wW{Gtg{&VaBpC+$~oi_|Ykyu!lcUP*q(YU=3mkpy)TY}E8;}XK0D?lS+ zkrkTfm%w><9@d{DzwKxBynBWGha*45IS2A~p-`@GLDki>Ez0iTDjOdZIS55j6mP5j z(`&SqG5fNc9cXcmU=Z7P4sA+q+}r1MzjLeDEwg?|EY|gxU*S)9GdN>{Hs@;F6t#oa zOv3*dWL_9~cw1>R8sMb(l~>hFwikYZZt6tcc3GBADR9C^_E5V&lhO9ZQiTvtNJgDiKF5FRQ_7G^DaA?T`nw|p#pi+}5A9W*togScii0r!Et$%POAyZ#_?Z&$5u~~2IV~}$}BMJ&8pnS%q@*LkJV0j^{ z-lx}yu^>1$@m?SF%m!L&2nM3$Ou>^s^cC!d1FEf4ZH^i|^k*4cmf_ocAI6+W)5sEY zGzvj0)5>Q1xwg__=mUU}ZP=JU*o zLFT=3lOPHx+QHw{Qtv|aCiwHBWLRmm=zZ$lcZ%u9wYx;Ot`+t0%+GGf@&7EPn_a#5 zu*pKOjDCi3ksUYPmkLGJfa*mV69;eSI(nGIqRoP|_g2)WgWm|Fy=@Rx{))EVarU_d zE}?MRk5(7bc@j#eKLiCm)Dr#zT~BU1HP8`s3y_xb6#PkeEXy( z*{RhHs{*)8Z>(=ux9!Y6|5=4S9t;?$QgKK7^aC&!nhrot(smBmAb0qlLpYln4u%62 z)8m;{J8#ivJ6GKExUZ6k~6L0 z82Q}1zHVA0N&3vY$@5gWaWRJk>g=1>fPVCPpAl3R@mMC!94s6Ot$q=tLwmIlHQBEU zAul(a-imIh*4}My*K_d)!T1r7VYCQlYLU*5`QyJ)nt(kwTl|y2fm2DDxogm)u=#|L zpI&}b0C~be=-7!7SziBWXhlO#qhsgV9$>C!3oe20^cV>RTU_Z5(YRTkLj%G7bb>Yk zZCo`niEBzoWMFpQKg888MI0Yw|NqoIFvrVNntq;O8vrkoF_@`POwma9^jqMQdpg8J zKnASa90QuE#z<0S->3S0NHgWE9hT9{8Vw)B&3Lj%T>gimO=1vu6V_>ztJboYT|XLo znBtv5`{xrl{V#LlP|s2!VZ)z$zYIo@kXKfbalAxaW%$R}@h&O!Z2KeW$Jv9~6*z`T zGjo0k;C$9;HWENW;1bG2bV1YQCZpSY0+%jH5pZUspJAqT_|^F&$$xd*37Kt+go^`3 z@pc3>N+uxPXE;bikjKV(C%fIQkeKPYmD1ri+I9J1;@?zvKya=cg%x>!D9tb+o}JH3 zf1v{PoF0n@L!G%<3Gg}HRzE1t(Cg@noyCZT#Fzf2D!EjGCvf|8D`s zPv%^PrzdP}2V_d%_;R{bpKd~lNx6Un&KxD6J0qi{T?rAiXBHsf)qNo1S|(>3*EGk| ze$0qf`NJNvcKDhVo9lXU8W{wx<}s^BX|=h=(`r_A*_pz7_g=GRl62Tz{ZJMY^-ORw zZ1Wxo3gIfS-&WxVsU3Qa=gRl!dkwa6CB=-<3+F z04^NnQjUXKQ zNXnVd;j*P*SqVC$MBt~ zjdTasAjL&K!*bZa%-B@9Gi`86X}4i5o~IA{R_wq3-HuIbI;JMz+0yy1`IZunwxZ~^ zy)4aiCS{sIZf0Zs5c2cZ6tFw?NKD#w{=Cd98FCHRmN)BBmy&uOwbq3#-`_>@(c-qy z$#ZUu&aJ{3n@Mg=2HuW8(qGI45wtuvCkJki@D(}5n;s?59d7GmXKam@kQaJJ5eFAh z7CU3YU}gd}Er|hxM~suKIrKB`c9I>}{%0$e5A(S{tg9Gp9qfrzB8%pTG#dPyDO&ep za%7#XwE&&bGDCiZ-{8EtQ3@567Ocxwo!EYSQ}!G4Hzw~z+qqG*dTsW!I)GG*j9CL# z2Lv(X4>lsHzQ%C1J~T7|Mf#@EQY-XmoIjmFnCQU`n*SY&`a4(cL;nePv zUz>0WL-Im{f`mSfRrz@JGj?oB?Y0qufyDOp_I`qE`ByBN=osqB1yUD=oIeXYrd3>n!L z{}UAGv))klvC9L8*5{Njt{+E3QPj8_z_i8LiWDTbl}`wOW;ss9gPRNiBP7zMz7{*O zGw0HlVQ6#Dd*TJc3W96LVT$mRmwBWm%CD9Va@!BO6~%(9^ZuMOXB`Z_MX5(LUNF)` zaTEn(qdtPFj}bU!F!)`a^HXN{eC%oP{ha=mm`%|Fb`k>ZV?OJ`FJ|6Nk2RWqyLpLh zr~6sP?tYE)^VdfqbsA((7VapG6;-jwApHPmi1{tR$xAa9vcCI3jNV~PR%qmY=QWjg zNqZ1v|Lk(v@ZDRNZ?s-TzqPk_!Is*57!nFS+;&${DK>-D=d+i};S9jLUTh_kDbHl> z`+Rv?3Lc}F!QLX*FQy69Es|!05jM54ZO7GNBCch5MpgRUkiuW&f6G;GbUCi0wO=_Q zSb-kF4ni4RBecrU{PgNov{eAO*}2Il=_4~`V*xQM(zR&II~l93jd^t(j4)o-1)iUq z7GPNRG6m1wEu3XRo!eCuqS4)u(Q$T;xDdMsEtzY8z>++;>bP4`&hQ>gA*>_TX38;- z5JrWtYbB`d1Fvnt+00fhHZ)kU+giS1kjp{mzL28!&a$q5z8U4mjHEU**u@mxoAp*1 zIva0aYpUU>2c13QMqgl*!<*w`)U>QdrE*zf`-eNe>YwQKX8{sSzA89!jFgJDXjrLL zJwIG8JabUAxEq5$FGxed1rymol%%^T9I=(A`MJ?OI^UWaOKG*+eZyZ*zLbG#8Aulx zqq_5od7!l~C;2Js_=hp-Ofi(|)jXdIDFt(<1l{B82kh)1d{+tyI(P{P@w^CV#U@Z| zJ{0U6ZWpsxQXfU;Q?csTs+B5M#%Yy0`B@zaAa!tYJX6}iLM^AWjAZPs*t%?0 zDJ1uj^=7s*2Gdh4SJ+Wf)lBLnyH-0VCbKf9SoCxsw>V42VY**7L$q3mdxuHANt&hA z*#zomUJrnEdu**bfx>^^HN$dhhb5h;9uj~VB0Ev^OpTv24b1u%kZ|i(gN)9_RFBW* zGm+#jULux$ZZdz|?%i!$lh33P47I)I2|*_O@YG24t|#bz7XMa0kZmIEigcJ9r9TCU zwgv#e8&KP~<^CA_cfB$vPiftbfW-aIQKZ23vBh77L)Zaq}~#5t1_weq>I zBA|+VY_xAE98qjLolh&DMVy#Jh?CBPjc&=c5cW$8)`!i;s@bjXo)AH1s0$`Q#0nF) zy&Qi6!MN-=MhB4|(9F>szVm0Mc8+7voOK$l&6p`+7$tE_Z{{$^-glhUOCg3;%ZK;+ zHu?1)J373xzKG;qWVL51nZji>LS&w3o8bP|;ybq=P8DYT;?sL1nOSyXntsOR6_&@v zOVHbbJuey`8JCo2H~cNk;LvftUbD=+GtW_d$+oaDy5IcieR*1p(*WU5K@c3Fw~JjW z{SK=J+Fk_ZiGr>MUb&z#5KRYl(qY0?wt-O$ZgS5e?-G8D9bg3*B7^SvM52d|2Ko9HXL=KZPy48avjL z((G7m^VhFH!`5p^5##V~?gPBx+at^1 z5il|Tr(`);zeBK$$8~WarFnqE5&3cx5uBSuG|Px)Rp`wMWI+OA$O(%h8;8qdQN~@1p$Wp`+V%vd7iaOtY|@O1 z(5{^?0$=+cd?iI3jJD^n@up0$>jd_KW&ePHHog&#tE`ROHx`9)NujTBRF z7ZeUTjIvw6h4ku)HGWXf`}|_Y^RTt;+N$0k(Z^ToQXNargD$L&Fwlhspb^N z$A7zg06fXFjDAoaPl(vmWCZZ6`U5h!GEV=E302Z)UM*4^%u^E|gsY-n_7)sGuz(!S z-NMz78SUJPCP5ynQ9S!sB@FVKq75k)*k6_$C=sjP7Bd{W~}+4wZSZeyl4#!FC1+&C~UKSz4+eA~V%(~Q>1 z?g;wIyCTFenbeUWLg^6G1HzaU;?R$Xvg0?J#y?-SMiHxnx9x?qFg1SLlZ4$s9mv#I zc&N8_Dk=?(Xd}ZwrejR~5%|%AC}?DSm7f00F;z7~x?b5t^? zLzv}VcQ0$8qfuDlE5UiaoF)rilaeE646{$hmk_AGPP{i}DHk7ve=o>jL#w*rU> zjxs!-FB;mU*I`c`r07_2Nibgb7uK-%KpY`T_W&z*?k0Pw+(Q|$eXrKA@bAN%q%pz& z@pew=&h`-SPhEY_fbZX$S>aM%YQ@fm1<3jjI`C7DJ}1KKwAZr`vizO4ItV;-vsmNs zkHp8(2>JqQoV&)q26Q`{KSI7;ioQ0vfg!^o+Zb=7S5_B9Da-QQ31{ zlY|mD1^Ya$3U~#$CuG~A!}VX}xASAat#JEz1@w|W4(_ijTJ@WJ(;i)-g{2dc;a2>5 zpNt%#yqu8Lqqh28NDcX;DLBie?Zt4z(RwW7NmdDF8zc{-M%cu!rjv%!85gN_0PCk> zbX#t=UY#09A&W!tv}=|Iy4AS$=SMk$_fTD@dl&>8(Vtv9^1(UGlNeCl5 zAJmq97(4qF$?I|eadP1vXYI%dXav;%vyy7~J8_x~>3doYe2Pt*>e_z8S2SNn7%MU?Jk8ED74}QWYM8q( z8tF?Cn@bN-Y8#cJp;{=Y)#|kg3m=SxO0UW5QVR)|kx)M&)-i7; z{3n?~BOdxG!>j<9i-m{ph@ znfBR>JocJ6AW~jJP7o%YiZUK9MFYpww-#roQ*M3-a)B-N98@H|i@GF>;KqgYAX0mT|#s7zEzX?@M0ja7@#PaL*4LgOj`> zQ@EsiNGoOOXmcH`fGKC+(BpOdVxp)qdJ+*!fUc90!k}I_hX|jpwW3DzDQ< zXN2ITLuf*5o2Pl}h;NMidjTRubIK6;Gyt0{DszTu#ZAcDEGW>m6Y-!JcM_>B-kSEbE?m;`K7Q{n;XENQxHCy?~6XQpB zbW#S63$0O`>G4s9_16WLT|z32^dZ_j+L+y;xX|oHC!kq}JWU{&IGnNA1?~XxS{Os+ zWdr7V5h$Dk@hUj`cj57$@{;HJgEj{Mm7X;H*<4ZU^#Yw6JQz+6Dv;m>*M&-_T!Wl= z;^VY=PF-wU4-A=W(~mF@wHzeCDbTdHu&xaC@T~rXsvbe((G6&0FQ^X$s?r_WyLvd7 z&n1_5+VmGrUHm`;)(@c+Cf2RZ1xh!h1X%w7OsNG@C9kA7 zN>fHLrnxJCRHGA*wC5Sh)LKqM){YOZq@iI1SQ(#ATy|LzbfkH|0a@#sol~Eo>Y~Y% zJZPSb(HXXvJ3mzKhL=+zB|+~~L0QIg?>p>o9|Hl#5O~p1Q6KdXCDnFO4D*a$&uUxu zm>q8$r&kl%srQ>z?C#k~$=723DXF)r)@aMqu0|nYu*1r!5C2wS0qK(*kEag&pX5{} zN8fJxJL_#SpxF^2XL{+j(_)=pd)t9;;IGNhmao!DIivgt+qTnWvs!Q?a9ctwIU6X@ zha65kbgG`-erSBT!)5T(54uRvt4+WQ5s1ju)5jU=8kSyqC1Zkkyp@D@@G_C(St;Mq zxhg1Xmbb*Z)dD5|d2xam4t62Tq7}$V0W4Rd2fF2{q0nO^1I$2E66i~Zkul%8HQJbo zA9$3D5YiBd?$Ye3(@PGl(#jk~ss~1JxZ_k?U-VC=Q*zl;SGPU-O4~vM3Uz4;YCg&d zfq_ADL-X63XN0)sKZ}2XOzmC5=ZPCT)gc1>vIv_ly`=Kqm)#MKb)~i@0SCVXH2DGB z(Y~m4yCALDXT5LAD^{*g#8mBf*e>O+GPBafP5cVTZ&V^RX~4h26!l}=7(;CsQ9>c` z`gZ0>MeEN0iBFekYSCI!`}ZLteml}ls@csxrYySO^k93{D-o!)spPG$<#dUP58~v6 zLL*uRE-*Yt7aUC^P|)d<6lF%hn059zAlm2Dj%ik_xSMAV zvh`yiuZ+3YfWE*BlWqjxc5F%3JExR)@(aOAsl4@~`H2w=5XQrv6^ps5A+-1|Z!*&7 zSTU`Nn@b>ud2O(Ll4W-y_DTye`xDNu)peK1CMb&Q2ZkfMou!2CKMWDVU;d|i7w-4D z+zobP37)BfmO|TfM#+l13JZkXU?;$K0Vj79kwy9-kLq7LWX;1h6j^u<*pV;gk_U*G zhpd0VxmY`=@$~(c3=}G~T!2iB%QH3A*<)8yr?dWLj^3gq9dqW|t9@@fHnvVbZs2Y| zYOY#nl3RROWObA>WB30?k5`poVo=}_`nJ_bL~V8N1N20lF}HqaK2d-uZ?TZ6lBLP@ z4t1sSz@s4FU$~@Xw6M=dM6S1p*`!<>L0x-2=_@PH!&q<-Lm40*s(;V&sW=HqZ>!*h zK&Chu;HysHeF-`LGTCY;ULpqMCZo#b$eqd5u>PITz3cKSD=usrjL+vI*QKKW8*aF( zC2zZ&5*8~?;=^p}hFOb`s^h$rH8KO<72R2OX@6tVwLIAyVatnwmn1#;kjcJw?~8VjoNO(AsBHW1G2vlevL_4= z%>$mgi(CP1R_N7%0TPJtj;0Y-YXR|7I&a>9>ABCvuLE$Ed|MS{Rt3E`9(OTMagWS zvz9Fuxh%~}8wEC;J*thjzI|~8d-QPt29h z7`31)C5kuuNpu>vKsZRBXgHvMNLDkuI%~qBtgm(lzLhkR%@)c#4IZb}XSp28WWc57 z>k(OQ-Rd#?JWAGk{J?DbQO=X%cXaAudF=r}Kua)=n`X}F=C{>-GiLpxe4($VKVZ~J z1|47$J*yB78YN=P6!`jP#{<}RtHqr(sHDO16-e@^-4Fwa}++P zWrFmtvd{Phu@r3f{%Zt315WEZ^R%K9yK z2i_oJr0On@ULbZoqh>J4zREqa1mAZT?fu!M(OU~(5xWi(q&5t<#i;+GO*<8-_a|YZ z6>A`MB#&*KASHR2H#vod?a4CiWOvPSKM&g%(OxQ8F&tMq`nK8@F{d z5^N4glcmYdpYFHdOd%v2^KD-0psAcW2mR7=)_5Dw&ZtDy&+iGx;243OBRfd3P?{JX ztJz(3?F%!j^24}Uy(g(mFMVs&!IGJLj~kDnbLW?U#wqFwI<-BS*SU*?`DRW%TZED< zNxcNYsLX&P_-=9P0&{r{wO`C7k*p!Ug^LBK*&<~JS0ie$JWtlUWUTK0EKux8qqKkb;Y^#tUcyZ(17+RG;63iVMm zbdg#$OU8SnVVv%ZTWlGMm*#^#$nK}>lOEVhG-!{x{*1Db`D?w&7C;IZ&W8IfV3?mV ztHkYvz5IXkHe3n~ISfdc^fVn&;k`3Fj`bucCZS&!;&@pc|U+E&*kw7kCaR z%P+iU+nyV@hm~`l+L;(OXv>ZjXeovglog_E`-S(Ls1>CfIe_M2RzU znPMiGl1rE6kp6aEm@jfB~C;03wip2Zk3kK(XJ$3{Hk9% zC*`y9m4a4nn_Op}Q1#2LaoUrm-thVk28)Sn@b(QgTV@^9=C%y93y;bifu2(Sp!6$^r71?A-6bb|H;JdaMmqvUUfrtN)PI0!}GX@ zV*~dxvHY-!Kgyp{Di9r6%WCs<(Tl*h8nob*lj z09=>{KAy0iiSw6q_?FmnrBn#UwuWUj;|(Ge8?klSP@f8x!1x&%kh%-7F~w)?a6ER8 z9~1($^foTLQouiW^xF?H*!~ zEDLO9H$@sdX?(%P9d<6^Oao-x3>^sm;Dc&f`E%mO^Mgosda09mPW1i_q#e~3RBRjT zoqpsQxzhzO0NGUrdS^$GU^~qa_NppZUR$MDNXUELPGY*21#(xe^1$S7vuNhiMr7W) zW(-+I-66yoK#&Q>O~rf7^t#!xODSip&j-r8N!fBbjkYH-&OXK^pm3cL#s_sB#JTK% z^F~FZo@4@()kC#ie#xPYTyEX?gZ>t7Ax(CoDj38<7JL_UWK$v7O1`kFl>8h9Fctje zi&v-tQ8&>bAir)2t$ONBK4)7IKzpb>Rl*2>A`T2qt*d;F%ofH$NxO-+1?v}Ar;%Q5 zr4+cco+8NjAXV*9nI7DJa_9nPQ9` 0: + # If service account impersonation is requested, mock the expected response. + status, data, extra_requests = ( + extra_requests[0], + extra_requests[1], + extra_requests[2:], + ) + responses.append(cls.make_mock_response(status, data)) + + request = mock.create_autospec(transport.Request) + request.side_effect = responses + + return request + + @classmethod + def assert_credential_request_kwargs( + cls, request_kwargs, headers, url=CREDENTIAL_URL + ): + assert request_kwargs["url"] == url + assert request_kwargs["method"] == "GET" + assert request_kwargs["headers"] == headers + assert request_kwargs.get("body", None) is None + + @classmethod + def assert_token_request_kwargs( + cls, request_kwargs, headers, request_data, token_url=TOKEN_URL + ): + assert request_kwargs["url"] == token_url + assert request_kwargs["method"] == "POST" + assert request_kwargs["headers"] == headers + assert request_kwargs["body"] is not None + body_tuples = urllib.parse.parse_qsl(request_kwargs["body"]) + assert len(body_tuples) == len(request_data.keys()) + for (k, v) in body_tuples: + assert v.decode("utf-8") == request_data[k.decode("utf-8")] + + @classmethod + def assert_impersonation_request_kwargs( + cls, + request_kwargs, + headers, + request_data, + service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + ): + assert request_kwargs["url"] == service_account_impersonation_url + assert request_kwargs["method"] == "POST" + assert request_kwargs["headers"] == headers + assert request_kwargs["body"] is not None + body_json = json.loads(request_kwargs["body"].decode("utf-8")) + assert body_json == request_data + + @classmethod + def assert_underlying_credentials_refresh( + cls, + credentials, + audience, + subject_token, + subject_token_type, + token_url, + service_account_impersonation_url=None, + basic_auth_encoding=None, + quota_project_id=None, + used_scopes=None, + credential_data=None, + scopes=None, + default_scopes=None, + ): + """Utility to assert that a credentials are initialized with the expected + attributes by calling refresh functionality and confirming response matches + expected one and that the underlying requests were populated with the + expected parameters. + """ + # STS token exchange request/response. + token_response = cls.SUCCESS_RESPONSE.copy() + token_headers = {"Content-Type": "application/x-www-form-urlencoded"} + if basic_auth_encoding: + token_headers["Authorization"] = "Basic " + basic_auth_encoding + + if service_account_impersonation_url: + token_scopes = "https://www.googleapis.com/auth/iam" + else: + token_scopes = " ".join(used_scopes or []) + + token_request_data = { + "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange", + "audience": audience, + "requested_token_type": "urn:ietf:params:oauth:token-type:access_token", + "scope": token_scopes, + "subject_token": subject_token, + "subject_token_type": subject_token_type, + } + + if service_account_impersonation_url: + # Service account impersonation request/response. + expire_time = ( + _helpers.utcnow().replace(microsecond=0) + + datetime.timedelta(seconds=3600) + ).isoformat("T") + "Z" + impersonation_response = { + "accessToken": "SA_ACCESS_TOKEN", + "expireTime": expire_time, + } + impersonation_headers = { + "Content-Type": "application/json", + "authorization": "Bearer {}".format(token_response["access_token"]), + } + impersonation_request_data = { + "delegates": None, + "scope": used_scopes, + "lifetime": "3600s", + } + + # Initialize mock request to handle token retrieval, token exchange and + # service account impersonation request. + requests = [] + if credential_data: + requests.append((http_client.OK, credential_data)) + + token_request_index = len(requests) + requests.append((http_client.OK, token_response)) + + if service_account_impersonation_url: + impersonation_request_index = len(requests) + requests.append((http_client.OK, impersonation_response)) + + request = cls.make_mock_request(*[el for req in requests for el in req]) + + credentials.refresh(request) + + assert len(request.call_args_list) == len(requests) + if credential_data: + cls.assert_credential_request_kwargs(request.call_args_list[0].kwargs, None) + # Verify token exchange request parameters. + cls.assert_token_request_kwargs( + request.call_args_list[token_request_index].kwargs, + token_headers, + token_request_data, + token_url, + ) + # Verify service account impersonation request parameters if the request + # is processed. + if service_account_impersonation_url: + cls.assert_impersonation_request_kwargs( + request.call_args_list[impersonation_request_index].kwargs, + impersonation_headers, + impersonation_request_data, + service_account_impersonation_url, + ) + assert credentials.token == impersonation_response["accessToken"] + else: + assert credentials.token == token_response["access_token"] + assert credentials.quota_project_id == quota_project_id + assert credentials.scopes == scopes + assert credentials.default_scopes == default_scopes + + @classmethod + def make_credentials( + cls, + client_id=None, + client_secret=None, + quota_project_id=None, + scopes=None, + default_scopes=None, + service_account_impersonation_url=None, + credential_source=None, + ): + return identity_pool.Credentials( + audience=AUDIENCE, + subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + service_account_impersonation_url=service_account_impersonation_url, + credential_source=credential_source, + client_id=client_id, + client_secret=client_secret, + quota_project_id=quota_project_id, + scopes=scopes, + default_scopes=default_scopes, + ) + + @mock.patch.object(identity_pool.Credentials, "__init__", return_value=None) + def test_from_info_full_options(self, mock_init): + credentials = identity_pool.Credentials.from_info( + { + "audience": AUDIENCE, + "subject_token_type": SUBJECT_TOKEN_TYPE, + "token_url": TOKEN_URL, + "service_account_impersonation_url": SERVICE_ACCOUNT_IMPERSONATION_URL, + "client_id": CLIENT_ID, + "client_secret": CLIENT_SECRET, + "quota_project_id": QUOTA_PROJECT_ID, + "credential_source": self.CREDENTIAL_SOURCE_TEXT, + } + ) + + # Confirm identity_pool.Credentials instantiated with expected attributes. + assert isinstance(credentials, identity_pool.Credentials) + mock_init.assert_called_once_with( + audience=AUDIENCE, + subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + client_id=CLIENT_ID, + client_secret=CLIENT_SECRET, + credential_source=self.CREDENTIAL_SOURCE_TEXT, + quota_project_id=QUOTA_PROJECT_ID, + ) + + @mock.patch.object(identity_pool.Credentials, "__init__", return_value=None) + def test_from_info_required_options_only(self, mock_init): + credentials = identity_pool.Credentials.from_info( + { + "audience": AUDIENCE, + "subject_token_type": SUBJECT_TOKEN_TYPE, + "token_url": TOKEN_URL, + "credential_source": self.CREDENTIAL_SOURCE_TEXT, + } + ) + + # Confirm identity_pool.Credentials instantiated with expected attributes. + assert isinstance(credentials, identity_pool.Credentials) + mock_init.assert_called_once_with( + audience=AUDIENCE, + subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + service_account_impersonation_url=None, + client_id=None, + client_secret=None, + credential_source=self.CREDENTIAL_SOURCE_TEXT, + quota_project_id=None, + ) + + @mock.patch.object(identity_pool.Credentials, "__init__", return_value=None) + def test_from_file_full_options(self, mock_init, tmpdir): + info = { + "audience": AUDIENCE, + "subject_token_type": SUBJECT_TOKEN_TYPE, + "token_url": TOKEN_URL, + "service_account_impersonation_url": SERVICE_ACCOUNT_IMPERSONATION_URL, + "client_id": CLIENT_ID, + "client_secret": CLIENT_SECRET, + "quota_project_id": QUOTA_PROJECT_ID, + "credential_source": self.CREDENTIAL_SOURCE_TEXT, + } + config_file = tmpdir.join("config.json") + config_file.write(json.dumps(info)) + credentials = identity_pool.Credentials.from_file(str(config_file)) + + # Confirm identity_pool.Credentials instantiated with expected attributes. + assert isinstance(credentials, identity_pool.Credentials) + mock_init.assert_called_once_with( + audience=AUDIENCE, + subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + client_id=CLIENT_ID, + client_secret=CLIENT_SECRET, + credential_source=self.CREDENTIAL_SOURCE_TEXT, + quota_project_id=QUOTA_PROJECT_ID, + ) + + @mock.patch.object(identity_pool.Credentials, "__init__", return_value=None) + def test_from_file_required_options_only(self, mock_init, tmpdir): + info = { + "audience": AUDIENCE, + "subject_token_type": SUBJECT_TOKEN_TYPE, + "token_url": TOKEN_URL, + "credential_source": self.CREDENTIAL_SOURCE_TEXT, + } + config_file = tmpdir.join("config.json") + config_file.write(json.dumps(info)) + credentials = identity_pool.Credentials.from_file(str(config_file)) + + # Confirm identity_pool.Credentials instantiated with expected attributes. + assert isinstance(credentials, identity_pool.Credentials) + mock_init.assert_called_once_with( + audience=AUDIENCE, + subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + service_account_impersonation_url=None, + client_id=None, + client_secret=None, + credential_source=self.CREDENTIAL_SOURCE_TEXT, + quota_project_id=None, + ) + + def test_constructor_invalid_options(self): + credential_source = {"unsupported": "value"} + + with pytest.raises(ValueError) as excinfo: + self.make_credentials(credential_source=credential_source) + + assert excinfo.match(r"Missing credential_source") + + def test_constructor_invalid_options_url_and_file(self): + credential_source = { + "url": self.CREDENTIAL_URL, + "file": SUBJECT_TOKEN_TEXT_FILE, + } + + with pytest.raises(ValueError) as excinfo: + self.make_credentials(credential_source=credential_source) + + assert excinfo.match(r"Ambiguous credential_source") + + def test_constructor_invalid_options_environment_id(self): + credential_source = {"url": self.CREDENTIAL_URL, "environment_id": "aws1"} + + with pytest.raises(ValueError) as excinfo: + self.make_credentials(credential_source=credential_source) + + assert excinfo.match( + r"Invalid Identity Pool credential_source field 'environment_id'" + ) + + def test_constructor_invalid_credential_source(self): + with pytest.raises(ValueError) as excinfo: + self.make_credentials(credential_source="non-dict") + + assert excinfo.match(r"Missing credential_source") + + def test_constructor_invalid_credential_source_format_type(self): + credential_source = {"format": {"type": "xml"}} + + with pytest.raises(ValueError) as excinfo: + self.make_credentials(credential_source=credential_source) + + assert excinfo.match(r"Invalid credential_source format 'xml'") + + def test_constructor_missing_subject_token_field_name(self): + credential_source = {"format": {"type": "json"}} + + with pytest.raises(ValueError) as excinfo: + self.make_credentials(credential_source=credential_source) + + assert excinfo.match( + r"Missing subject_token_field_name for JSON credential_source format" + ) + + def test_retrieve_subject_token_missing_subject_token(self, tmpdir): + # Provide empty text file. + empty_file = tmpdir.join("empty.txt") + empty_file.write("") + credential_source = {"file": str(empty_file)} + credentials = self.make_credentials(credential_source=credential_source) + + with pytest.raises(exceptions.RefreshError) as excinfo: + credentials.retrieve_subject_token(None) + + assert excinfo.match(r"Missing subject_token in the credential_source file") + + def test_retrieve_subject_token_text_file(self): + credentials = self.make_credentials( + credential_source=self.CREDENTIAL_SOURCE_TEXT + ) + + subject_token = credentials.retrieve_subject_token(None) + + assert subject_token == TEXT_FILE_SUBJECT_TOKEN + + def test_retrieve_subject_token_json_file(self): + credentials = self.make_credentials( + credential_source=self.CREDENTIAL_SOURCE_JSON + ) + + subject_token = credentials.retrieve_subject_token(None) + + assert subject_token == JSON_FILE_SUBJECT_TOKEN + + def test_retrieve_subject_token_json_file_invalid_field_name(self): + credential_source = { + "file": SUBJECT_TOKEN_JSON_FILE, + "format": {"type": "json", "subject_token_field_name": "not_found"}, + } + credentials = self.make_credentials(credential_source=credential_source) + + with pytest.raises(exceptions.RefreshError) as excinfo: + credentials.retrieve_subject_token(None) + + assert excinfo.match( + "Unable to parse subject_token from JSON file '{}' using key '{}'".format( + SUBJECT_TOKEN_JSON_FILE, "not_found" + ) + ) + + def test_retrieve_subject_token_invalid_json(self, tmpdir): + # Provide JSON file. This should result in JSON parsing error. + invalid_json_file = tmpdir.join("invalid.json") + invalid_json_file.write("{") + credential_source = { + "file": str(invalid_json_file), + "format": {"type": "json", "subject_token_field_name": "access_token"}, + } + credentials = self.make_credentials(credential_source=credential_source) + + with pytest.raises(exceptions.RefreshError) as excinfo: + credentials.retrieve_subject_token(None) + + assert excinfo.match( + "Unable to parse subject_token from JSON file '{}' using key '{}'".format( + str(invalid_json_file), "access_token" + ) + ) + + def test_retrieve_subject_token_file_not_found(self): + credential_source = {"file": "./not_found.txt"} + credentials = self.make_credentials(credential_source=credential_source) + + with pytest.raises(exceptions.RefreshError) as excinfo: + credentials.retrieve_subject_token(None) + + assert excinfo.match(r"File './not_found.txt' was not found") + + def test_refresh_text_file_success_without_impersonation_ignore_default_scopes( + self + ): + credentials = self.make_credentials( + client_id=CLIENT_ID, + client_secret=CLIENT_SECRET, + # Test with text format type. + credential_source=self.CREDENTIAL_SOURCE_TEXT, + scopes=SCOPES, + # Default scopes should be ignored. + default_scopes=["ignored"], + ) + + self.assert_underlying_credentials_refresh( + credentials=credentials, + audience=AUDIENCE, + subject_token=TEXT_FILE_SUBJECT_TOKEN, + subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + service_account_impersonation_url=None, + basic_auth_encoding=BASIC_AUTH_ENCODING, + quota_project_id=None, + used_scopes=SCOPES, + scopes=SCOPES, + default_scopes=["ignored"], + ) + + def test_refresh_text_file_success_without_impersonation_use_default_scopes(self): + credentials = self.make_credentials( + client_id=CLIENT_ID, + client_secret=CLIENT_SECRET, + # Test with text format type. + credential_source=self.CREDENTIAL_SOURCE_TEXT, + scopes=None, + # Default scopes should be used since user specified scopes are none. + default_scopes=SCOPES, + ) + + self.assert_underlying_credentials_refresh( + credentials=credentials, + audience=AUDIENCE, + subject_token=TEXT_FILE_SUBJECT_TOKEN, + subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + service_account_impersonation_url=None, + basic_auth_encoding=BASIC_AUTH_ENCODING, + quota_project_id=None, + used_scopes=SCOPES, + scopes=None, + default_scopes=SCOPES, + ) + + def test_refresh_text_file_success_with_impersonation_ignore_default_scopes(self): + # Initialize credentials with service account impersonation and basic auth. + credentials = self.make_credentials( + # Test with text format type. + credential_source=self.CREDENTIAL_SOURCE_TEXT, + service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + scopes=SCOPES, + # Default scopes should be ignored. + default_scopes=["ignored"], + ) + + self.assert_underlying_credentials_refresh( + credentials=credentials, + audience=AUDIENCE, + subject_token=TEXT_FILE_SUBJECT_TOKEN, + subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + basic_auth_encoding=None, + quota_project_id=None, + used_scopes=SCOPES, + scopes=SCOPES, + default_scopes=["ignored"], + ) + + def test_refresh_text_file_success_with_impersonation_use_default_scopes(self): + # Initialize credentials with service account impersonation, basic auth + # and default scopes (no user scopes). + credentials = self.make_credentials( + # Test with text format type. + credential_source=self.CREDENTIAL_SOURCE_TEXT, + service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + scopes=None, + # Default scopes should be used since user specified scopes are none. + default_scopes=SCOPES, + ) + + self.assert_underlying_credentials_refresh( + credentials=credentials, + audience=AUDIENCE, + subject_token=TEXT_FILE_SUBJECT_TOKEN, + subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + basic_auth_encoding=None, + quota_project_id=None, + used_scopes=SCOPES, + scopes=None, + default_scopes=SCOPES, + ) + + def test_refresh_json_file_success_without_impersonation(self): + credentials = self.make_credentials( + client_id=CLIENT_ID, + client_secret=CLIENT_SECRET, + # Test with JSON format type. + credential_source=self.CREDENTIAL_SOURCE_JSON, + scopes=SCOPES, + ) + + self.assert_underlying_credentials_refresh( + credentials=credentials, + audience=AUDIENCE, + subject_token=JSON_FILE_SUBJECT_TOKEN, + subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + service_account_impersonation_url=None, + basic_auth_encoding=BASIC_AUTH_ENCODING, + quota_project_id=None, + used_scopes=SCOPES, + scopes=SCOPES, + default_scopes=None, + ) + + def test_refresh_json_file_success_with_impersonation(self): + # Initialize credentials with service account impersonation and basic auth. + credentials = self.make_credentials( + # Test with JSON format type. + credential_source=self.CREDENTIAL_SOURCE_JSON, + service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + scopes=SCOPES, + ) + + self.assert_underlying_credentials_refresh( + credentials=credentials, + audience=AUDIENCE, + subject_token=JSON_FILE_SUBJECT_TOKEN, + subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + basic_auth_encoding=None, + quota_project_id=None, + used_scopes=SCOPES, + scopes=SCOPES, + default_scopes=None, + ) + + def test_refresh_with_retrieve_subject_token_error(self): + credential_source = { + "file": SUBJECT_TOKEN_JSON_FILE, + "format": {"type": "json", "subject_token_field_name": "not_found"}, + } + credentials = self.make_credentials(credential_source=credential_source) + + with pytest.raises(exceptions.RefreshError) as excinfo: + credentials.refresh(None) + + assert excinfo.match( + "Unable to parse subject_token from JSON file '{}' using key '{}'".format( + SUBJECT_TOKEN_JSON_FILE, "not_found" + ) + ) + + def test_retrieve_subject_token_from_url(self): + credentials = self.make_credentials( + credential_source=self.CREDENTIAL_SOURCE_TEXT_URL + ) + request = self.make_mock_request(token_data=TEXT_FILE_SUBJECT_TOKEN) + subject_token = credentials.retrieve_subject_token(request) + + assert subject_token == TEXT_FILE_SUBJECT_TOKEN + self.assert_credential_request_kwargs(request.call_args_list[0].kwargs, None) + + def test_retrieve_subject_token_from_url_with_headers(self): + credentials = self.make_credentials( + credential_source={"url": self.CREDENTIAL_URL, "headers": {"foo": "bar"}} + ) + request = self.make_mock_request(token_data=TEXT_FILE_SUBJECT_TOKEN) + subject_token = credentials.retrieve_subject_token(request) + + assert subject_token == TEXT_FILE_SUBJECT_TOKEN + self.assert_credential_request_kwargs( + request.call_args_list[0].kwargs, {"foo": "bar"} + ) + + def test_retrieve_subject_token_from_url_json(self): + credentials = self.make_credentials( + credential_source=self.CREDENTIAL_SOURCE_JSON_URL + ) + request = self.make_mock_request(token_data=JSON_FILE_CONTENT) + subject_token = credentials.retrieve_subject_token(request) + + assert subject_token == JSON_FILE_SUBJECT_TOKEN + self.assert_credential_request_kwargs(request.call_args_list[0].kwargs, None) + + def test_retrieve_subject_token_from_url_json_with_headers(self): + credentials = self.make_credentials( + credential_source={ + "url": self.CREDENTIAL_URL, + "format": {"type": "json", "subject_token_field_name": "access_token"}, + "headers": {"foo": "bar"}, + } + ) + request = self.make_mock_request(token_data=JSON_FILE_CONTENT) + subject_token = credentials.retrieve_subject_token(request) + + assert subject_token == JSON_FILE_SUBJECT_TOKEN + self.assert_credential_request_kwargs( + request.call_args_list[0].kwargs, {"foo": "bar"} + ) + + def test_retrieve_subject_token_from_url_not_found(self): + credentials = self.make_credentials( + credential_source=self.CREDENTIAL_SOURCE_TEXT_URL + ) + with pytest.raises(exceptions.RefreshError) as excinfo: + credentials.retrieve_subject_token( + self.make_mock_request(token_status=404, token_data=JSON_FILE_CONTENT) + ) + + assert excinfo.match("Unable to retrieve Identity Pool subject token") + + def test_retrieve_subject_token_from_url_json_invalid_field(self): + credential_source = { + "url": self.CREDENTIAL_URL, + "format": {"type": "json", "subject_token_field_name": "not_found"}, + } + credentials = self.make_credentials(credential_source=credential_source) + + with pytest.raises(exceptions.RefreshError) as excinfo: + credentials.retrieve_subject_token( + self.make_mock_request(token_data=JSON_FILE_CONTENT) + ) + + assert excinfo.match( + "Unable to parse subject_token from JSON file '{}' using key '{}'".format( + self.CREDENTIAL_URL, "not_found" + ) + ) + + def test_retrieve_subject_token_from_url_json_invalid_format(self): + credentials = self.make_credentials( + credential_source=self.CREDENTIAL_SOURCE_JSON_URL + ) + + with pytest.raises(exceptions.RefreshError) as excinfo: + credentials.retrieve_subject_token(self.make_mock_request(token_data="{")) + + assert excinfo.match( + "Unable to parse subject_token from JSON file '{}' using key '{}'".format( + self.CREDENTIAL_URL, "access_token" + ) + ) + + def test_refresh_text_file_success_without_impersonation_url(self): + credentials = self.make_credentials( + client_id=CLIENT_ID, + client_secret=CLIENT_SECRET, + # Test with text format type. + credential_source=self.CREDENTIAL_SOURCE_TEXT_URL, + scopes=SCOPES, + ) + + self.assert_underlying_credentials_refresh( + credentials=credentials, + audience=AUDIENCE, + subject_token=TEXT_FILE_SUBJECT_TOKEN, + subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + service_account_impersonation_url=None, + basic_auth_encoding=BASIC_AUTH_ENCODING, + quota_project_id=None, + used_scopes=SCOPES, + scopes=SCOPES, + default_scopes=None, + credential_data=TEXT_FILE_SUBJECT_TOKEN, + ) + + def test_refresh_text_file_success_with_impersonation_url(self): + # Initialize credentials with service account impersonation and basic auth. + credentials = self.make_credentials( + # Test with text format type. + credential_source=self.CREDENTIAL_SOURCE_TEXT_URL, + service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + scopes=SCOPES, + ) + + self.assert_underlying_credentials_refresh( + credentials=credentials, + audience=AUDIENCE, + subject_token=TEXT_FILE_SUBJECT_TOKEN, + subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + basic_auth_encoding=None, + quota_project_id=None, + used_scopes=SCOPES, + scopes=SCOPES, + default_scopes=None, + credential_data=TEXT_FILE_SUBJECT_TOKEN, + ) + + def test_refresh_json_file_success_without_impersonation_url(self): + credentials = self.make_credentials( + client_id=CLIENT_ID, + client_secret=CLIENT_SECRET, + # Test with JSON format type. + credential_source=self.CREDENTIAL_SOURCE_JSON_URL, + scopes=SCOPES, + ) + + self.assert_underlying_credentials_refresh( + credentials=credentials, + audience=AUDIENCE, + subject_token=JSON_FILE_SUBJECT_TOKEN, + subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + service_account_impersonation_url=None, + basic_auth_encoding=BASIC_AUTH_ENCODING, + quota_project_id=None, + used_scopes=SCOPES, + scopes=SCOPES, + default_scopes=None, + credential_data=JSON_FILE_CONTENT, + ) + + def test_refresh_json_file_success_with_impersonation_url(self): + # Initialize credentials with service account impersonation and basic auth. + credentials = self.make_credentials( + # Test with JSON format type. + credential_source=self.CREDENTIAL_SOURCE_JSON_URL, + service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + scopes=SCOPES, + ) + + self.assert_underlying_credentials_refresh( + credentials=credentials, + audience=AUDIENCE, + subject_token=JSON_FILE_SUBJECT_TOKEN, + subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + basic_auth_encoding=None, + quota_project_id=None, + used_scopes=SCOPES, + scopes=SCOPES, + default_scopes=None, + credential_data=JSON_FILE_CONTENT, + ) + + def test_refresh_with_retrieve_subject_token_error_url(self): + credential_source = { + "url": self.CREDENTIAL_URL, + "format": {"type": "json", "subject_token_field_name": "not_found"}, + } + credentials = self.make_credentials(credential_source=credential_source) + + with pytest.raises(exceptions.RefreshError) as excinfo: + credentials.refresh(self.make_mock_request(token_data=JSON_FILE_CONTENT)) + + assert excinfo.match( + "Unable to parse subject_token from JSON file '{}' using key '{}'".format( + self.CREDENTIAL_URL, "not_found" + ) + ) diff --git a/packages/google-auth/tests/test_impersonated_credentials.py b/packages/google-auth/tests/test_impersonated_credentials.py index 305f9392667a..430c770d3672 100644 --- a/packages/google-auth/tests/test_impersonated_credentials.py +++ b/packages/google-auth/tests/test_impersonated_credentials.py @@ -104,12 +104,17 @@ class TestImpersonatedCredentials(object): SIGNER, SERVICE_ACCOUNT_EMAIL, TOKEN_URI ) USER_SOURCE_CREDENTIALS = credentials.Credentials(token="ABCDE") + IAM_ENDPOINT_OVERRIDE = ( + "https://us-east1-iamcredentials.googleapis.com/v1/projects/-" + + "/serviceAccounts/{}:generateAccessToken".format(SERVICE_ACCOUNT_EMAIL) + ) def make_credentials( self, source_credentials=SOURCE_CREDENTIALS, lifetime=LIFETIME, target_principal=TARGET_PRINCIPAL, + iam_endpoint_override=None, ): return Credentials( @@ -118,6 +123,7 @@ def make_credentials( target_scopes=self.TARGET_SCOPES, delegates=self.DELEGATES, lifetime=lifetime, + iam_endpoint_override=iam_endpoint_override, ) def test_make_from_user_credentials(self): @@ -172,6 +178,34 @@ def test_refresh_success(self, use_data_bytes, mock_donor_credentials): assert credentials.valid assert not credentials.expired + @pytest.mark.parametrize("use_data_bytes", [True, False]) + def test_refresh_success_iam_endpoint_override( + self, use_data_bytes, mock_donor_credentials + ): + credentials = self.make_credentials( + lifetime=None, iam_endpoint_override=self.IAM_ENDPOINT_OVERRIDE + ) + token = "token" + + expire_time = ( + _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=500) + ).isoformat("T") + "Z" + response_body = {"accessToken": token, "expireTime": expire_time} + + request = self.make_request( + data=json.dumps(response_body), + status=http_client.OK, + use_data_bytes=use_data_bytes, + ) + + credentials.refresh(request) + + assert credentials.valid + assert not credentials.expired + # Confirm override endpoint used. + request_kwargs = request.call_args.kwargs + assert request_kwargs["url"] == self.IAM_ENDPOINT_OVERRIDE + @pytest.mark.parametrize("time_skew", [100, -100]) def test_refresh_source_credentials(self, time_skew): credentials = self.make_credentials(lifetime=None) @@ -317,6 +351,36 @@ def test_with_quota_project(self): quota_project_creds = credentials.with_quota_project("project-foo") assert quota_project_creds._quota_project_id == "project-foo" + @pytest.mark.parametrize("use_data_bytes", [True, False]) + def test_with_quota_project_iam_endpoint_override( + self, use_data_bytes, mock_donor_credentials + ): + credentials = self.make_credentials( + lifetime=None, iam_endpoint_override=self.IAM_ENDPOINT_OVERRIDE + ) + token = "token" + # iam_endpoint_override should be copied to created credentials. + quota_project_creds = credentials.with_quota_project("project-foo") + + expire_time = ( + _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=500) + ).isoformat("T") + "Z" + response_body = {"accessToken": token, "expireTime": expire_time} + + request = self.make_request( + data=json.dumps(response_body), + status=http_client.OK, + use_data_bytes=use_data_bytes, + ) + + quota_project_creds.refresh(request) + + assert quota_project_creds.valid + assert not quota_project_creds.expired + # Confirm override endpoint used. + request_kwargs = request.call_args.kwargs + assert request_kwargs["url"] == self.IAM_ENDPOINT_OVERRIDE + def test_id_token_success( self, mock_donor_credentials, mock_authorizedsession_idtoken ): From e33dad69fe0bafc4486ee1f0366a53fc04622bf0 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Wed, 17 Feb 2021 09:05:08 -0700 Subject: [PATCH 384/966] chore: release 1.27.0 (#704) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 12 ++++++++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index b33eb1698661..fb1b26727ead 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,18 @@ [1]: https://pypi.org/project/google-auth/#history +## [1.27.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.26.1...v1.27.0) (2021-02-16) + + +### Features + +* workload identity federation support ([#698](https://www.github.com/googleapis/google-auth-library-python/issues/698)) ([d4d7f38](https://www.github.com/googleapis/google-auth-library-python/commit/d4d7f3815e0cea3c9f39a5204a4f001de99568e9)) + + +### Bug Fixes + +* add pyopenssl as extra dependency ([#697](https://www.github.com/googleapis/google-auth-library-python/issues/697)) ([aeab5d0](https://www.github.com/googleapis/google-auth-library-python/commit/aeab5d07c5538f3d8cce817df24199534572b97d)) + ### [1.26.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.26.0...v1.26.1) (2021-02-11) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 055fcabfd790..0c30f0be94af 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -37,7 +37,7 @@ with io.open("README.rst", "r") as fh: long_description = fh.read() -version = "1.26.1" +version = "1.27.0" setup( name="google-auth", From e0ba13f67d348540aa580e73579890c3c7f4dbab Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Fri, 19 Feb 2021 10:26:48 -0800 Subject: [PATCH 385/966] fix: ignore gcloud warning when getting project id (#708) * fix: ignore gcloud warning when getting project id * update --- .../google-auth/google/auth/_cloud_sdk.py | 13 ++++++++++--- packages/google-auth/tests/test__cloud_sdk.py | 19 +++++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/packages/google-auth/google/auth/_cloud_sdk.py b/packages/google-auth/google/auth/_cloud_sdk.py index e772fe964154..40e6aec13a8e 100644 --- a/packages/google-auth/google/auth/_cloud_sdk.py +++ b/packages/google-auth/google/auth/_cloud_sdk.py @@ -84,6 +84,13 @@ def get_application_default_credentials_path(): return os.path.join(config_path, _CREDENTIALS_FILENAME) +def _run_subprocess_ignore_stderr(command): + """ Return subprocess.check_output with the given command and ignores stderr.""" + with open(os.devnull, "w") as devnull: + output = subprocess.check_output(command, stderr=devnull) + return output + + def get_project_id(): """Gets the project ID from the Cloud SDK. @@ -96,9 +103,9 @@ def get_project_id(): command = _CLOUD_SDK_POSIX_COMMAND try: - output = subprocess.check_output( - (command,) + _CLOUD_SDK_CONFIG_COMMAND, stderr=subprocess.STDOUT - ) + # Ignore the stderr coming from gcloud, so it won't be mixed into the output. + # https://github.com/googleapis/google-auth-library-python/issues/673 + output = _run_subprocess_ignore_stderr((command,) + _CLOUD_SDK_CONFIG_COMMAND) except (subprocess.CalledProcessError, OSError, IOError): return None diff --git a/packages/google-auth/tests/test__cloud_sdk.py b/packages/google-auth/tests/test__cloud_sdk.py index 337760426056..31cb6c22c427 100644 --- a/packages/google-auth/tests/test__cloud_sdk.py +++ b/packages/google-auth/tests/test__cloud_sdk.py @@ -71,6 +71,25 @@ def test_get_project_id_call_error(check_output): assert check_output.called +def test__run_subprocess_ignore_stderr(): + command = [ + "python", + "-c", + "from __future__ import print_function;" + + "import sys;" + + "print('error', file=sys.stderr);" + + "print('output', file=sys.stdout)", + ] + + # If we ignore stderr, then the output only has stdout + output = _cloud_sdk._run_subprocess_ignore_stderr(command) + assert output == b"output\n" + + # If we pipe stderr to stdout, then the output is mixed with stdout and stderr. + output = subprocess.check_output(command, stderr=subprocess.STDOUT) + assert output == b"output\nerror\n" or output == b"error\noutput\n" + + @mock.patch("os.name", new="nt") def test_get_project_id_windows(): check_output_patch = mock.patch( From 6107567eccda593a29b4874fd515bbdb09b3fed0 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Thu, 25 Feb 2021 15:42:32 -0800 Subject: [PATCH 386/966] fix: use gcloud creds flow (#705) --- packages/google-auth/google/auth/_default.py | 13 +++++++++++++ .../google-auth/google/auth/_default_async.py | 9 +++++++++ packages/google-auth/tests/test__default.py | 18 ++++++++++++++++++ .../tests_async/test__default_async.py | 18 ++++++++++++++++++ 4 files changed, 58 insertions(+) diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index 8e3b2100d9fc..4dc0725e7f03 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -198,12 +198,25 @@ def _get_gcloud_sdk_credentials(): def _get_explicit_environ_credentials(): """Gets credentials from the GOOGLE_APPLICATION_CREDENTIALS environment variable.""" + from google.auth import _cloud_sdk + + cloud_sdk_adc_path = _cloud_sdk.get_application_default_credentials_path() explicit_file = os.environ.get(environment_vars.CREDENTIALS) _LOGGER.debug( "Checking %s for explicit credentials as part of auth process...", explicit_file ) + if explicit_file is not None and explicit_file == cloud_sdk_adc_path: + # Cloud sdk flow calls gcloud to fetch project id, so if the explicit + # file path is cloud sdk credentials path, then we should fall back + # to cloud sdk flow, otherwise project id cannot be obtained. + _LOGGER.debug( + "Explicit credentials path %s is the same as Cloud SDK credentials path, fall back to Cloud SDK credentials flow...", + explicit_file, + ) + return _get_gcloud_sdk_credentials() + if explicit_file is not None: credentials, project_id = load_credentials_from_file( os.environ[environment_vars.CREDENTIALS] diff --git a/packages/google-auth/google/auth/_default_async.py b/packages/google-auth/google/auth/_default_async.py index 1a725afba414..d12a642afd3e 100644 --- a/packages/google-auth/google/auth/_default_async.py +++ b/packages/google-auth/google/auth/_default_async.py @@ -127,8 +127,17 @@ def _get_gcloud_sdk_credentials(): def _get_explicit_environ_credentials(): """Gets credentials from the GOOGLE_APPLICATION_CREDENTIALS environment variable.""" + from google.auth import _cloud_sdk + + cloud_sdk_adc_path = _cloud_sdk.get_application_default_credentials_path() explicit_file = os.environ.get(environment_vars.CREDENTIALS) + if explicit_file is not None and explicit_file == cloud_sdk_adc_path: + # Cloud sdk flow calls gcloud to fetch project id, so if the explicit + # file path is cloud sdk credentials path, then we should fall back + # to cloud sdk flow, otherwise project id cannot be obtained. + return _get_gcloud_sdk_credentials() + if explicit_file is not None: credentials, project_id = load_credentials_from_file( os.environ[environment_vars.CREDENTIALS] diff --git a/packages/google-auth/tests/test__default.py b/packages/google-auth/tests/test__default.py index 74ed61855ea0..e1368962525d 100644 --- a/packages/google-auth/tests/test__default.py +++ b/packages/google-auth/tests/test__default.py @@ -350,6 +350,24 @@ def test__get_explicit_environ_credentials_no_project_id(load, monkeypatch): assert project_id is None +@mock.patch( + "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True +) +@mock.patch("google.auth._default._get_gcloud_sdk_credentials", autospec=True) +def test__get_explicit_environ_credentials_fallback_to_gcloud( + get_gcloud_creds, get_adc_path, monkeypatch +): + # Set explicit credentials path to cloud sdk credentials path. + get_adc_path.return_value = "filename" + monkeypatch.setenv(environment_vars.CREDENTIALS, "filename") + + _default._get_explicit_environ_credentials() + + # Check we fall back to cloud sdk flow since explicit credentials path is + # cloud sdk credentials path + get_gcloud_creds.assert_called_once() + + @LOAD_FILE_PATCH @mock.patch( "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True diff --git a/packages/google-auth/tests_async/test__default_async.py b/packages/google-auth/tests_async/test__default_async.py index bca396aeef54..527a8da45674 100644 --- a/packages/google-auth/tests_async/test__default_async.py +++ b/packages/google-auth/tests_async/test__default_async.py @@ -187,6 +187,24 @@ def test__get_explicit_environ_credentials_no_project_id(load, monkeypatch): assert project_id is None +@mock.patch( + "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True +) +@mock.patch("google.auth._default_async._get_gcloud_sdk_credentials", autospec=True) +def test__get_explicit_environ_credentials_fallback_to_gcloud( + get_gcloud_creds, get_adc_path, monkeypatch +): + # Set explicit credentials path to cloud sdk credentials path. + get_adc_path.return_value = "filename" + monkeypatch.setenv(environment_vars.CREDENTIALS, "filename") + + _default._get_explicit_environ_credentials() + + # Check we fall back to cloud sdk flow since explicit credentials path is + # cloud sdk credentials path + get_gcloud_creds.assert_called_once() + + @LOAD_FILE_PATCH @mock.patch( "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True From 7b8aa044bcb2cc004db3d1ebfea1b07f9a0ed907 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Fri, 26 Feb 2021 11:50:02 -0700 Subject: [PATCH 387/966] build: fix warnings in docgen (#711) Resolved all the outstanding warnings from sphinx. Going forward, warnings will be treated as errors since the `-W` flag is being used with `sphinx-build` --- packages/google-auth/.trampolinerc | 52 ++++ packages/google-auth/docs/Makefile | 225 ------------------ packages/google-auth/docs/conf.py | 2 +- ...rst => google.auth._credentials_async.rst} | 0 ...t_async.rst => google.auth._jwt_async.rst} | 2 +- .../docs/reference/google.auth.rst | 2 +- ...ogle.auth.transport._aiohttp_requests.rst} | 0 ...t => google.oauth2._credentials_async.rst} | 0 ... google.oauth2._service_account_async.rst} | 0 packages/google-auth/docs/user-guide.rst | 1 - .../google/auth/crypt/_python_rsa.py | 2 +- .../auth/transport/_aiohttp_requests.py | 45 ++-- .../google/auth/transport/requests.py | 25 +- .../google/oauth2/_service_account_async.py | 2 +- .../google-auth/google/oauth2/id_token.py | 2 +- .../google/oauth2/service_account.py | 5 +- packages/google-auth/google/oauth2/utils.py | 2 +- packages/google-auth/noxfile.py | 25 +- 18 files changed, 120 insertions(+), 272 deletions(-) create mode 100644 packages/google-auth/.trampolinerc delete mode 100644 packages/google-auth/docs/Makefile rename packages/google-auth/docs/reference/{google.auth.credentials_async.rst => google.auth._credentials_async.rst} (100%) rename packages/google-auth/docs/reference/{google.auth.jwt_async.rst => google.auth._jwt_async.rst} (75%) rename packages/google-auth/docs/reference/{google.auth.transport.aiohttp_requests.rst => google.auth.transport._aiohttp_requests.rst} (100%) rename packages/google-auth/docs/reference/{google.oauth2.credentials_async.rst => google.oauth2._credentials_async.rst} (100%) rename packages/google-auth/docs/reference/{google.oauth2.service_account_async.rst => google.oauth2._service_account_async.rst} (100%) diff --git a/packages/google-auth/.trampolinerc b/packages/google-auth/.trampolinerc new file mode 100644 index 000000000000..383b6ec89fbc --- /dev/null +++ b/packages/google-auth/.trampolinerc @@ -0,0 +1,52 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Template for .trampolinerc + +# Add required env vars here. +required_envvars+=( + "STAGING_BUCKET" + "V2_STAGING_BUCKET" +) + +# Add env vars which are passed down into the container here. +pass_down_envvars+=( + "STAGING_BUCKET" + "V2_STAGING_BUCKET" + "NOX_SESSION" +) + +# Prevent unintentional override on the default image. +if [[ "${TRAMPOLINE_IMAGE_UPLOAD:-false}" == "true" ]] && \ + [[ -z "${TRAMPOLINE_IMAGE:-}" ]]; then + echo "Please set TRAMPOLINE_IMAGE if you want to upload the Docker image." + exit 1 +fi + +# Define the default value if it makes sense. +if [[ -z "${TRAMPOLINE_IMAGE_UPLOAD:-}" ]]; then + TRAMPOLINE_IMAGE_UPLOAD="" +fi + +if [[ -z "${TRAMPOLINE_IMAGE:-}" ]]; then + TRAMPOLINE_IMAGE="" +fi + +if [[ -z "${TRAMPOLINE_DOCKERFILE:-}" ]]; then + TRAMPOLINE_DOCKERFILE="" +fi + +if [[ -z "${TRAMPOLINE_BUILD_FILE:-}" ]]; then + TRAMPOLINE_BUILD_FILE="" +fi diff --git a/packages/google-auth/docs/Makefile b/packages/google-auth/docs/Makefile deleted file mode 100644 index 4887d62e3015..000000000000 --- a/packages/google-auth/docs/Makefile +++ /dev/null @@ -1,225 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = _build - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -# the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " applehelp to make an Apple Help Book" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " epub3 to make an epub3" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " xml to make Docutils-native XML files" - @echo " pseudoxml to make pseudoxml-XML files for display purposes" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - @echo " coverage to run coverage check of the documentation (if enabled)" - @echo " dummy to check syntax errors of document sources" - -.PHONY: clean -clean: - rm -rf $(BUILDDIR)/* - -.PHONY: html -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -.PHONY: dirhtml -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -.PHONY: singlehtml -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -.PHONY: pickle -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -.PHONY: json -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -.PHONY: htmlhelp -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -.PHONY: qthelp -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/google-auth.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/google-auth.qhc" - -.PHONY: applehelp -applehelp: - $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp - @echo - @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." - @echo "N.B. You won't be able to view it unless you put it in" \ - "~/Library/Documentation/Help or install it in your application" \ - "bundle." - -.PHONY: devhelp -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/google-auth" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/google-auth" - @echo "# devhelp" - -.PHONY: epub -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -.PHONY: epub3 -epub3: - $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 - @echo - @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." - -.PHONY: latex -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -.PHONY: latexpdf -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -.PHONY: latexpdfja -latexpdfja: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through platex and dvipdfmx..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -.PHONY: text -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -.PHONY: man -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -.PHONY: texinfo -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -.PHONY: info -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -.PHONY: gettext -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -.PHONY: changes -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -.PHONY: linkcheck -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -.PHONY: doctest -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." - -.PHONY: coverage -coverage: - $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage - @echo "Testing of coverage in the sources finished, look at the " \ - "results in $(BUILDDIR)/coverage/python.txt." - -.PHONY: xml -xml: - $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml - @echo - @echo "Build finished. The XML files are in $(BUILDDIR)/xml." - -.PHONY: pseudoxml -pseudoxml: - $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml - @echo - @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." - -.PHONY: dummy -dummy: - $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy - @echo - @echo "Build finished. Dummy builder generates no files." diff --git a/packages/google-auth/docs/conf.py b/packages/google-auth/docs/conf.py index db1872e5dbf2..b68467fabe57 100644 --- a/packages/google-auth/docs/conf.py +++ b/packages/google-auth/docs/conf.py @@ -369,7 +369,7 @@ "python": ("https://docs.python.org/3.5", None), "urllib3": ("https://urllib3.readthedocs.io/en/stable", None), "requests": ("https://requests.kennethreitz.org/en/master/", None), - "requests-oauthlib": ("http://requests-oauthlib.readthedocs.io/en/stable", None), + "requests-oauthlib": ("https://requests-oauthlib.readthedocs.io/en/stable/", None), } # Autodoc config diff --git a/packages/google-auth/docs/reference/google.auth.credentials_async.rst b/packages/google-auth/docs/reference/google.auth._credentials_async.rst similarity index 100% rename from packages/google-auth/docs/reference/google.auth.credentials_async.rst rename to packages/google-auth/docs/reference/google.auth._credentials_async.rst diff --git a/packages/google-auth/docs/reference/google.auth.jwt_async.rst b/packages/google-auth/docs/reference/google.auth._jwt_async.rst similarity index 75% rename from packages/google-auth/docs/reference/google.auth.jwt_async.rst rename to packages/google-auth/docs/reference/google.auth._jwt_async.rst index 4e56a6ea3539..d27984b6c5d6 100644 --- a/packages/google-auth/docs/reference/google.auth.jwt_async.rst +++ b/packages/google-auth/docs/reference/google.auth._jwt_async.rst @@ -1,7 +1,7 @@ google.auth.jwt\_async module ============================= -.. automodule:: google.auth.jwt_async +.. automodule:: google.auth._jwt_async :members: :inherited-members: :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.auth.rst b/packages/google-auth/docs/reference/google.auth.rst index e21eaf9e31df..eb8328ae0c5a 100644 --- a/packages/google-auth/docs/reference/google.auth.rst +++ b/packages/google-auth/docs/reference/google.auth.rst @@ -33,4 +33,4 @@ Submodules google.auth.identity_pool google.auth.impersonated_credentials google.auth.jwt - google.auth.jwt_async + google.auth._jwt_async diff --git a/packages/google-auth/docs/reference/google.auth.transport.aiohttp_requests.rst b/packages/google-auth/docs/reference/google.auth.transport._aiohttp_requests.rst similarity index 100% rename from packages/google-auth/docs/reference/google.auth.transport.aiohttp_requests.rst rename to packages/google-auth/docs/reference/google.auth.transport._aiohttp_requests.rst diff --git a/packages/google-auth/docs/reference/google.oauth2.credentials_async.rst b/packages/google-auth/docs/reference/google.oauth2._credentials_async.rst similarity index 100% rename from packages/google-auth/docs/reference/google.oauth2.credentials_async.rst rename to packages/google-auth/docs/reference/google.oauth2._credentials_async.rst diff --git a/packages/google-auth/docs/reference/google.oauth2.service_account_async.rst b/packages/google-auth/docs/reference/google.oauth2._service_account_async.rst similarity index 100% rename from packages/google-auth/docs/reference/google.oauth2.service_account_async.rst rename to packages/google-auth/docs/reference/google.oauth2._service_account_async.rst diff --git a/packages/google-auth/docs/user-guide.rst b/packages/google-auth/docs/user-guide.rst index b315cc908a74..6674c5aefc41 100644 --- a/packages/google-auth/docs/user-guide.rst +++ b/packages/google-auth/docs/user-guide.rst @@ -520,7 +520,6 @@ A sample end-to-end flow using an ID Token against a Cloud Run endpoint maybe :: .. _App Engine: https://cloud.google.com/appengine/ .. _Cloud Functions: https://cloud.google.com/functions/ .. _Cloud Run: https://cloud.google.com/run/ -.. _Compute Engine: https://cloud.google.com/compute/ .. _Identity Aware Proxy: https://cloud.google.com/iap/ .. _Google OpenID Connect: https://developers.google.com/identity/protocols/OpenIDConnect .. _Google ID Token: https://developers.google.com/identity/protocols/OpenIDConnect#validatinganidtoken diff --git a/packages/google-auth/google/auth/crypt/_python_rsa.py b/packages/google-auth/google/auth/crypt/_python_rsa.py index e288c501624e..ec30dd09a37e 100644 --- a/packages/google-auth/google/auth/crypt/_python_rsa.py +++ b/packages/google-auth/google/auth/crypt/_python_rsa.py @@ -88,7 +88,7 @@ def from_string(cls, public_key): x509 public key certificate. Returns: - Verifier: The constructed verifier. + google.auth.crypt._python_rsa.RSAVerifier: The constructed verifier. Raises: ValueError: If the public_key can't be parsed. diff --git a/packages/google-auth/google/auth/transport/_aiohttp_requests.py b/packages/google-auth/google/auth/transport/_aiohttp_requests.py index aaf4e2c0b23e..4293810dd81d 100644 --- a/packages/google-auth/google/auth/transport/_aiohttp_requests.py +++ b/packages/google-auth/google/auth/transport/_aiohttp_requests.py @@ -131,7 +131,7 @@ class Request(transport.Request): credentials.refresh(request) Args: - session (aiohttp.ClientSession): An instance :class: aiohttp.ClientSession used + session (aiohttp.ClientSession): An instance :class:`aiohttp.ClientSession` used to make HTTP requests. If not specified, a session will be created. .. automethod:: __call__ @@ -154,15 +154,17 @@ async def __call__( Args: url (str): The URL to be requested. - method (str): The HTTP method to use for the request. Defaults - to 'GET'. - body (bytes): The payload / body in HTTP request. - headers (Mapping[str, str]): Request headers. + method (Optional[str]): + The HTTP method to use for the request. Defaults to 'GET'. + body (Optional[bytes]): + The payload or body in HTTP request. + headers (Optional[Mapping[str, str]]): + Request headers. timeout (Optional[int]): The number of seconds to wait for a response from the server. If not specified or if None, the requests default timeout will be used. kwargs: Additional arguments passed through to the underlying - requests :meth:`~requests.Session.request` method. + requests :meth:`requests.Session.request` method. Returns: google.auth.transport.Response: The HTTP response. @@ -211,8 +213,8 @@ class AuthorizedSession(aiohttp.ClientSession): credentials' headers to the request and refreshing credentials as needed. Args: - credentials (google.auth._credentials_async.Credentials): The credentials to - add to the request. + credentials (google.auth._credentials_async.Credentials): + The credentials to add to the request. refresh_status_codes (Sequence[int]): Which HTTP status codes indicate that credentials should be refreshed and the request should be retried. @@ -264,29 +266,26 @@ async def request( """Implementation of Authorized Session aiohttp request. Args: - method: The http request method used (e.g. GET, PUT, DELETE) - - url: The url at which the http request is sent. - - data, headers: These fields parallel the associated data and headers - fields of a regular http request. Using the aiohttp client session to - send the http request allows us to use this parallel corresponding structure - in our Authorized Session class. - + method (str): + The http request method used (e.g. GET, PUT, DELETE) + url (str): + The url at which the http request is sent. + data (Optional[dict]): Dictionary, list of tuples, bytes, or file-like + object to send in the body of the Request. + headers (Optional[dict]): Dictionary of HTTP Headers to send with the + Request. timeout (Optional[Union[float, aiohttp.ClientTimeout]]): The amount of time in seconds to wait for the server response - with each individual request. - - Can also be passed as an `aiohttp.ClientTimeout` object. - + with each individual request. Can also be passed as an + ``aiohttp.ClientTimeout`` object. max_allowed_time (Optional[float]): If the method runs longer than this, a ``Timeout`` exception is - automatically raised. Unlike the ``timeout` parameter, this + automatically raised. Unlike the ``timeout`` parameter, this value applies to the total method execution time, even if multiple requests are made under the hood. Mind that it is not guaranteed that the timeout error is raised - at ``max_allowed_time`. It might take longer, for example, if + at ``max_allowed_time``. It might take longer, for example, if an underlying request takes a lot of time, but the request itself does not timeout, e.g. if a large file is being transmitted. The timout error will be raised after such diff --git a/packages/google-auth/google/auth/transport/requests.py b/packages/google-auth/google/auth/transport/requests.py index ef973fce4433..d317544b74f7 100644 --- a/packages/google-auth/google/auth/transport/requests.py +++ b/packages/google-auth/google/auth/transport/requests.py @@ -79,7 +79,7 @@ class TimeoutGuard(object): """A context manager raising an error if the suite execution took too long. Args: - timeout ([Union[None, float, Tuple[float, float]]]): + timeout (Union[None, Union[float, Tuple[float, float]]]): The maximum number of seconds a suite can run without the context manager raising a timeout exception on exit. If passed as a tuple, the smaller of the values is taken as a timeout. If ``None``, a @@ -164,7 +164,7 @@ def __call__( url (str): The URI to be requested. method (str): The HTTP method to use for the request. Defaults to 'GET'. - body (bytes): The payload / body in HTTP request. + body (bytes): The payload or body in HTTP request. headers (Mapping[str, str]): Request headers. timeout (Optional[int]): The number of seconds to wait for a response from the server. If not specified or if None, the @@ -248,21 +248,23 @@ class AuthorizedSession(requests.Session): response = authed_session.request( 'GET', 'https://www.googleapis.com/storage/v1/b') + The underlying :meth:`request` implementation handles adding the credentials' headers to the request and refreshing credentials as needed. This class also supports mutual TLS via :meth:`configure_mtls_channel` method. In order to use this method, the `GOOGLE_API_USE_CLIENT_CERTIFICATE` - environment variable must be explicitly set to `true`, otherwise it does - nothing. Assume the environment is set to `true`, the method behaves in the + environment variable must be explicitly set to ``true``, otherwise it does + nothing. Assume the environment is set to ``true``, the method behaves in the following manner: + If client_cert_callback is provided, client certificate and private key are loaded using the callback; if client_cert_callback is None, application default SSL credentials will be used. Exceptions are raised if there are problems with the certificate, private key, or the loading process, so it should be called within a try/except block. - First we set the environment variable to `true`, then create an :class:`AuthorizedSession` + First we set the environment variable to ``true``, then create an :class:`AuthorizedSession` instance and specify the endpoints:: regular_endpoint = 'https://pubsub.googleapis.com/v1/projects/{my_project_id}/topics' @@ -291,6 +293,7 @@ def my_cert_callback(): else: response = authed_session.request('GET', regular_endpoint) + You can alternatively use application default SSL credentials like this:: try: @@ -432,19 +435,17 @@ def request( Args: timeout (Optional[Union[float, Tuple[float, float]]]): The amount of time in seconds to wait for the server response - with each individual request. - - Can also be passed as a tuple (connect_timeout, read_timeout). - See :meth:`requests.Session.request` documentation for details. - + with each individual request. Can also be passed as a tuple + ``(connect_timeout, read_timeout)``. See :meth:`requests.Session.request` + documentation for details. max_allowed_time (Optional[float]): If the method runs longer than this, a ``Timeout`` exception is - automatically raised. Unlike the ``timeout` parameter, this + automatically raised. Unlike the ``timeout`` parameter, this value applies to the total method execution time, even if multiple requests are made under the hood. Mind that it is not guaranteed that the timeout error is raised - at ``max_allowed_time`. It might take longer, for example, if + at ``max_allowed_time``. It might take longer, for example, if an underlying request takes a lot of time, but the request itself does not timeout, e.g. if a large file is being transmitted. The timout error will be raised after such diff --git a/packages/google-auth/google/oauth2/_service_account_async.py b/packages/google-auth/google/oauth2/_service_account_async.py index 0a4e724a4f9b..cfd315a7ff1f 100644 --- a/packages/google-auth/google/oauth2/_service_account_async.py +++ b/packages/google-auth/google/oauth2/_service_account_async.py @@ -112,7 +112,7 @@ class IDTokenCredentials( 'service-account.json', scopes=['email'], subject='user@example.com')) -` + The credentials are considered immutable. If you want to modify the scopes or the subject used for delegation, use :meth:`with_scopes` or :meth:`with_subject`:: diff --git a/packages/google-auth/google/oauth2/id_token.py b/packages/google-auth/google/oauth2/id_token.py index d70782b51413..5e36260179b7 100644 --- a/packages/google-auth/google/oauth2/id_token.py +++ b/packages/google-auth/google/oauth2/id_token.py @@ -50,7 +50,7 @@ cached_session = cachecontrol.CacheControl(session) request = google.auth.transport.requests.Request(session=cached_session) -.. _OpenID Connect ID Token: +.. _OpenID Connect ID Tokens: http://openid.net/specs/openid-connect-core-1_0.html#IDToken .. _CacheControl: https://cachecontrol.readthedocs.io """ diff --git a/packages/google-auth/google/oauth2/service_account.py b/packages/google-auth/google/oauth2/service_account.py index ed91011428b0..1ccfa19c0377 100644 --- a/packages/google-auth/google/oauth2/service_account.py +++ b/packages/google-auth/google/oauth2/service_account.py @@ -424,6 +424,7 @@ class IDTokenCredentials(credentials.Signing, credentials.CredentialsWithQuotaPr service_account.IDTokenCredentials.from_service_account_file( 'service-account.json')) + Or if you already have the service account file loaded:: service_account_info = json.load(open('service_account.json')) @@ -431,6 +432,7 @@ class IDTokenCredentials(credentials.Signing, credentials.CredentialsWithQuotaPr service_account.IDTokenCredentials.from_service_account_info( service_account_info)) + Both helper methods pass on arguments to the constructor, so you can specify additional scopes and a subject if necessary:: @@ -439,7 +441,8 @@ class IDTokenCredentials(credentials.Signing, credentials.CredentialsWithQuotaPr 'service-account.json', scopes=['email'], subject='user@example.com')) -` + + The credentials are considered immutable. If you want to modify the scopes or the subject used for delegation, use :meth:`with_scopes` or :meth:`with_subject`:: diff --git a/packages/google-auth/google/oauth2/utils.py b/packages/google-auth/google/oauth2/utils.py index efda7968dbaa..593f03236ec8 100644 --- a/packages/google-auth/google/oauth2/utils.py +++ b/packages/google-auth/google/oauth2/utils.py @@ -101,7 +101,7 @@ def apply_client_authentication_options( Args: headers (Mapping[str, str]): The HTTP request header. - request_body (Optional[Mapping[str, str]): The HTTP request body + request_body (Optional[Mapping[str, str]]): The HTTP request body dictionary. For requests that do not support request body, this is None and will be ignored. bearer_token (Optional[str]): The optional bearer token. diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index 6ce6346a5723..d1f61b90c65a 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +import shutil +import os import nox TEST_DEPENDENCIES = [ @@ -134,9 +136,26 @@ def docgen(session): @nox.session(python="3.7") def docs(session): - session.install("sphinx", "-r", "docs/requirements-docs.txt") - session.install(".") - session.run("make", "-C", "docs", "html") + """Build the docs for this library.""" + + session.install("-e", ".[aiohttp]") + session.install( + "sphinx<3.0.0", "alabaster", "recommonmark", "sphinx-docstring-typing" + ) + + shutil.rmtree(os.path.join("docs", "_build"), ignore_errors=True) + session.run( + "sphinx-build", + "-T", # show full traceback on exception + "-W", # warnings as errors + "-N", # no colors + "-b", + "html", + "-d", + os.path.join("docs", "_build", "doctrees", ""), + os.path.join("docs", ""), + os.path.join("docs", "_build", "html", ""), + ) @nox.session(python="pypy") From 21d0565fc90def6612a6f3de1e12941a86a09e8d Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 1 Mar 2021 09:44:23 -0700 Subject: [PATCH 388/966] chore: release 1.27.1 (#710) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 8 ++++++++ packages/google-auth/setup.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index fb1b26727ead..3e1eef8f0448 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,14 @@ [1]: https://pypi.org/project/google-auth/#history +### [1.27.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.27.0...v1.27.1) (2021-02-26) + + +### Bug Fixes + +* ignore gcloud warning when getting project id ([#708](https://www.github.com/googleapis/google-auth-library-python/issues/708)) ([3f2f3ea](https://www.github.com/googleapis/google-auth-library-python/commit/3f2f3eaf09006d3d0ec9c030d359114238479279)) +* use gcloud creds flow ([#705](https://www.github.com/googleapis/google-auth-library-python/issues/705)) ([333cb76](https://www.github.com/googleapis/google-auth-library-python/commit/333cb765b52028329ec3ca04edf32c5764b1db68)) + ## [1.27.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.26.1...v1.27.0) (2021-02-16) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 0c30f0be94af..33089cb71e5a 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -37,7 +37,7 @@ with io.open("README.rst", "r") as fh: long_description = fh.read() -version = "1.27.0" +version = "1.27.1" setup( name="google-auth", From 1d2b846fc0c34bb9c7871bdf7b86ef54466ac77c Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Mon, 8 Mar 2021 14:17:54 -0700 Subject: [PATCH 389/966] test: skip aiohttp version that breaks aioresponses (#717) --- packages/google-auth/noxfile.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index d1f61b90c65a..168c1e2b63f2 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -32,7 +32,12 @@ "grpcio", ] -ASYNC_DEPENDENCIES = ["pytest-asyncio", "aioresponses", "asynctest"] +ASYNC_DEPENDENCIES = [ + "pytest-asyncio", + "aioresponses", + "asynctest", + "aiohttp!=3.7.4.post0", +] BLACK_VERSION = "black==19.3b0" BLACK_PATHS = [ From c5aa9b4ac94b13bc0e14863b7f817a830304695f Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Mon, 8 Mar 2021 13:35:44 -0800 Subject: [PATCH 390/966] fix: fix unit tests so they can work in g3 (#714) * fix: mock not working well with g3 test * update * update --- .../tests/oauth2/test_service_account.py | 12 +++---- packages/google-auth/tests/oauth2/test_sts.py | 12 +++---- packages/google-auth/tests/test_aws.py | 22 ++++++------ .../tests/test_external_account.py | 36 ++++++++----------- packages/google-auth/tests/test_iam.py | 2 +- .../google-auth/tests/test_identity_pool.py | 16 ++++----- .../tests/test_impersonated_credentials.py | 4 +-- 7 files changed, 48 insertions(+), 56 deletions(-) diff --git a/packages/google-auth/tests/oauth2/test_service_account.py b/packages/google-auth/tests/oauth2/test_service_account.py index 40a4ca219760..648541e2bb85 100644 --- a/packages/google-auth/tests/oauth2/test_service_account.py +++ b/packages/google-auth/tests/oauth2/test_service_account.py @@ -203,18 +203,18 @@ def test_apply_with_no_quota_project_id(self): assert "x-goog-user-project" not in headers assert "token" in headers["authorization"] - @mock.patch("google.auth.jwt.Credentials.from_signing_credentials", autospec=True) - def test__create_self_signed_jwt(self, from_signing_credentials): + @mock.patch("google.auth.jwt.Credentials", instance=True, autospec=True) + def test__create_self_signed_jwt(self, jwt): credentials = service_account.Credentials( SIGNER, self.SERVICE_ACCOUNT_EMAIL, self.TOKEN_URI ) audience = "https://pubsub.googleapis.com" credentials._create_self_signed_jwt(audience) - from_signing_credentials.assert_called_once_with(credentials, audience) + jwt.from_signing_credentials.assert_called_once_with(credentials, audience) - @mock.patch("google.auth.jwt.Credentials.from_signing_credentials", autospec=True) - def test__create_self_signed_jwt_with_user_scopes(self, from_signing_credentials): + @mock.patch("google.auth.jwt.Credentials", instance=True, autospec=True) + def test__create_self_signed_jwt_with_user_scopes(self, jwt): credentials = service_account.Credentials( SIGNER, self.SERVICE_ACCOUNT_EMAIL, self.TOKEN_URI, scopes=["foo"] ) @@ -223,7 +223,7 @@ def test__create_self_signed_jwt_with_user_scopes(self, from_signing_credentials credentials._create_self_signed_jwt(audience) # JWT should not be created if there are user-defined scopes - from_signing_credentials.assert_not_called() + jwt.from_signing_credentials.assert_not_called() @mock.patch("google.oauth2._client.jwt_grant", autospec=True) def test_refresh_success(self, jwt_grant): diff --git a/packages/google-auth/tests/oauth2/test_sts.py b/packages/google-auth/tests/oauth2/test_sts.py index 8792bd6bc8b3..e8e008df5da8 100644 --- a/packages/google-auth/tests/oauth2/test_sts.py +++ b/packages/google-auth/tests/oauth2/test_sts.py @@ -128,7 +128,7 @@ def test_exchange_token_full_success_without_auth(self): self.ADDON_HEADERS, ) - self.assert_request_kwargs(request.call_args.kwargs, headers, request_data) + self.assert_request_kwargs(request.call_args[1], headers, request_data) assert response == self.SUCCESS_RESPONSE def test_exchange_token_partial_success_without_auth(self): @@ -157,7 +157,7 @@ def test_exchange_token_partial_success_without_auth(self): requested_token_type=self.REQUESTED_TOKEN_TYPE, ) - self.assert_request_kwargs(request.call_args.kwargs, headers, request_data) + self.assert_request_kwargs(request.call_args[1], headers, request_data) assert response == self.SUCCESS_RESPONSE def test_exchange_token_non200_without_auth(self): @@ -227,7 +227,7 @@ def test_exchange_token_full_success_with_basic_auth(self): self.ADDON_HEADERS, ) - self.assert_request_kwargs(request.call_args.kwargs, headers, request_data) + self.assert_request_kwargs(request.call_args[1], headers, request_data) assert response == self.SUCCESS_RESPONSE def test_exchange_token_partial_success_with_basic_auth(self): @@ -259,7 +259,7 @@ def test_exchange_token_partial_success_with_basic_auth(self): requested_token_type=self.REQUESTED_TOKEN_TYPE, ) - self.assert_request_kwargs(request.call_args.kwargs, headers, request_data) + self.assert_request_kwargs(request.call_args[1], headers, request_data) assert response == self.SUCCESS_RESPONSE def test_exchange_token_non200_with_basic_auth(self): @@ -331,7 +331,7 @@ def test_exchange_token_full_success_with_reqbody_auth(self): self.ADDON_HEADERS, ) - self.assert_request_kwargs(request.call_args.kwargs, headers, request_data) + self.assert_request_kwargs(request.call_args[1], headers, request_data) assert response == self.SUCCESS_RESPONSE def test_exchange_token_partial_success_with_reqbody_auth(self): @@ -362,7 +362,7 @@ def test_exchange_token_partial_success_with_reqbody_auth(self): requested_token_type=self.REQUESTED_TOKEN_TYPE, ) - self.assert_request_kwargs(request.call_args.kwargs, headers, request_data) + self.assert_request_kwargs(request.call_args[1], headers, request_data) assert response == self.SUCCESS_RESPONSE def test_exchange_token_non200_with_reqbody_auth(self): diff --git a/packages/google-auth/tests/test_aws.py b/packages/google-auth/tests/test_aws.py index 9a8f98eecc47..7a55841ca353 100644 --- a/packages/google-auth/tests/test_aws.py +++ b/packages/google-auth/tests/test_aws.py @@ -959,15 +959,15 @@ def test_retrieve_subject_token_success_temp_creds_no_environment_vars( ) # Assert region request. self.assert_aws_metadata_request_kwargs( - request.call_args_list[0].kwargs, REGION_URL + request.call_args_list[0][1], REGION_URL ) # Assert role request. self.assert_aws_metadata_request_kwargs( - request.call_args_list[1].kwargs, SECURITY_CREDS_URL + request.call_args_list[1][1], SECURITY_CREDS_URL ) # Assert security credentials request. self.assert_aws_metadata_request_kwargs( - request.call_args_list[2].kwargs, + request.call_args_list[2][1], "{}/{}".format(SECURITY_CREDS_URL, self.AWS_ROLE), {"Content-Type": "application/json"}, ) @@ -986,11 +986,11 @@ def test_retrieve_subject_token_success_temp_creds_no_environment_vars( assert len(new_request.call_args_list) == 2 # Assert role request. self.assert_aws_metadata_request_kwargs( - new_request.call_args_list[0].kwargs, SECURITY_CREDS_URL + new_request.call_args_list[0][1], SECURITY_CREDS_URL ) # Assert security credentials request. self.assert_aws_metadata_request_kwargs( - new_request.call_args_list[1].kwargs, + new_request.call_args_list[1][1], "{}/{}".format(SECURITY_CREDS_URL, self.AWS_ROLE), {"Content-Type": "application/json"}, ) @@ -1193,7 +1193,7 @@ def test_refresh_success_without_impersonation_ignore_default_scopes(self, utcno assert len(request.call_args_list) == 4 # Fourth request should be sent to GCP STS endpoint. self.assert_token_request_kwargs( - request.call_args_list[3].kwargs, token_headers, token_request_data + request.call_args_list[3][1], token_headers, token_request_data ) assert credentials.token == self.SUCCESS_RESPONSE["access_token"] assert credentials.quota_project_id == QUOTA_PROJECT_ID @@ -1249,7 +1249,7 @@ def test_refresh_success_without_impersonation_use_default_scopes(self, utcnow): assert len(request.call_args_list) == 4 # Fourth request should be sent to GCP STS endpoint. self.assert_token_request_kwargs( - request.call_args_list[3].kwargs, token_headers, token_request_data + request.call_args_list[3][1], token_headers, token_request_data ) assert credentials.token == self.SUCCESS_RESPONSE["access_token"] assert credentials.quota_project_id == QUOTA_PROJECT_ID @@ -1326,12 +1326,12 @@ def test_refresh_success_with_impersonation_ignore_default_scopes(self, utcnow): assert len(request.call_args_list) == 5 # Fourth request should be sent to GCP STS endpoint. self.assert_token_request_kwargs( - request.call_args_list[3].kwargs, token_headers, token_request_data + request.call_args_list[3][1], token_headers, token_request_data ) # Fifth request should be sent to iamcredentials endpoint for service # account impersonation. self.assert_impersonation_request_kwargs( - request.call_args_list[4].kwargs, + request.call_args_list[4][1], impersonation_headers, impersonation_request_data, ) @@ -1410,12 +1410,12 @@ def test_refresh_success_with_impersonation_use_default_scopes(self, utcnow): assert len(request.call_args_list) == 5 # Fourth request should be sent to GCP STS endpoint. self.assert_token_request_kwargs( - request.call_args_list[3].kwargs, token_headers, token_request_data + request.call_args_list[3][1], token_headers, token_request_data ) # Fifth request should be sent to iamcredentials endpoint for service # account impersonation. self.assert_impersonation_request_kwargs( - request.call_args_list[4].kwargs, + request.call_args_list[4][1], impersonation_headers, impersonation_request_data, ) diff --git a/packages/google-auth/tests/test_external_account.py b/packages/google-auth/tests/test_external_account.py index 42e53ecb5bf6..8f8d98009c1f 100644 --- a/packages/google-auth/tests/test_external_account.py +++ b/packages/google-auth/tests/test_external_account.py @@ -363,9 +363,7 @@ def test_refresh_without_client_auth_success(self, unused_utcnow): credentials.refresh(request) - self.assert_token_request_kwargs( - request.call_args.kwargs, headers, request_data - ) + self.assert_token_request_kwargs(request.call_args[1], headers, request_data) assert credentials.valid assert credentials.expiry == expected_expiry assert not credentials.expired @@ -422,11 +420,11 @@ def test_refresh_impersonation_without_client_auth_success(self): assert len(request.call_args_list) == 2 # Verify token exchange request parameters. self.assert_token_request_kwargs( - request.call_args_list[0].kwargs, token_headers, token_request_data + request.call_args_list[0][1], token_headers, token_request_data ) # Verify service account impersonation request parameters. self.assert_impersonation_request_kwargs( - request.call_args_list[1].kwargs, + request.call_args_list[1][1], impersonation_headers, impersonation_request_data, ) @@ -436,7 +434,7 @@ def test_refresh_impersonation_without_client_auth_success(self): assert credentials.token == impersonation_response["accessToken"] def test_refresh_without_client_auth_success_explicit_user_scopes_ignore_default_scopes( - self + self, ): headers = {"Content-Type": "application/x-www-form-urlencoded"} request_data = { @@ -458,9 +456,7 @@ def test_refresh_without_client_auth_success_explicit_user_scopes_ignore_default credentials.refresh(request) - self.assert_token_request_kwargs( - request.call_args.kwargs, headers, request_data - ) + self.assert_token_request_kwargs(request.call_args[1], headers, request_data) assert credentials.valid assert not credentials.expired assert credentials.token == self.SUCCESS_RESPONSE["access_token"] @@ -488,9 +484,7 @@ def test_refresh_without_client_auth_success_explicit_default_scopes_only(self): credentials.refresh(request) - self.assert_token_request_kwargs( - request.call_args.kwargs, headers, request_data - ) + self.assert_token_request_kwargs(request.call_args[1], headers, request_data) assert credentials.valid assert not credentials.expired assert credentials.token == self.SUCCESS_RESPONSE["access_token"] @@ -551,9 +545,7 @@ def test_refresh_with_client_auth_success(self): credentials.refresh(request) - self.assert_token_request_kwargs( - request.call_args.kwargs, headers, request_data - ) + self.assert_token_request_kwargs(request.call_args[1], headers, request_data) assert credentials.valid assert not credentials.expired assert credentials.token == self.SUCCESS_RESPONSE["access_token"] @@ -616,11 +608,11 @@ def test_refresh_impersonation_with_client_auth_success_ignore_default_scopes(se assert len(request.call_args_list) == 2 # Verify token exchange request parameters. self.assert_token_request_kwargs( - request.call_args_list[0].kwargs, token_headers, token_request_data + request.call_args_list[0][1], token_headers, token_request_data ) # Verify service account impersonation request parameters. self.assert_impersonation_request_kwargs( - request.call_args_list[1].kwargs, + request.call_args_list[1][1], impersonation_headers, impersonation_request_data, ) @@ -687,11 +679,11 @@ def test_refresh_impersonation_with_client_auth_success_use_default_scopes(self) assert len(request.call_args_list) == 2 # Verify token exchange request parameters. self.assert_token_request_kwargs( - request.call_args_list[0].kwargs, token_headers, token_request_data + request.call_args_list[0][1], token_headers, token_request_data ) # Verify service account impersonation request parameters. self.assert_impersonation_request_kwargs( - request.call_args_list[1].kwargs, + request.call_args_list[1][1], impersonation_headers, impersonation_request_data, ) @@ -1045,11 +1037,11 @@ def test_get_project_id_cloud_resource_manager_success(self): assert len(request.call_args_list) == 3 # Verify token exchange request parameters. self.assert_token_request_kwargs( - request.call_args_list[0].kwargs, token_headers, token_request_data + request.call_args_list[0][1], token_headers, token_request_data ) # Verify service account impersonation request parameters. self.assert_impersonation_request_kwargs( - request.call_args_list[1].kwargs, + request.call_args_list[1][1], impersonation_headers, impersonation_request_data, ) @@ -1061,7 +1053,7 @@ def test_get_project_id_cloud_resource_manager_success(self): assert credentials.token == impersonation_response["accessToken"] # Verify cloud resource manager request parameters. self.assert_resource_manager_request_kwargs( - request.call_args_list[2].kwargs, + request.call_args_list[2][1], self.PROJECT_NUMBER, { "x-goog-user-project": self.QUOTA_PROJECT_ID, diff --git a/packages/google-auth/tests/test_iam.py b/packages/google-auth/tests/test_iam.py index fbd3e418dac5..382713b9b146 100644 --- a/packages/google-auth/tests/test_iam.py +++ b/packages/google-auth/tests/test_iam.py @@ -89,7 +89,7 @@ def test_sign_bytes(self): returned_signature = signer.sign("123") assert returned_signature == signature - kwargs = request.call_args.kwargs + kwargs = request.call_args[1] assert kwargs["headers"]["Content-Type"] == "application/json" def test_sign_bytes_failure(self): diff --git a/packages/google-auth/tests/test_identity_pool.py b/packages/google-auth/tests/test_identity_pool.py index c017ab59f64f..90a0e2549a1e 100644 --- a/packages/google-auth/tests/test_identity_pool.py +++ b/packages/google-auth/tests/test_identity_pool.py @@ -223,10 +223,10 @@ def assert_underlying_credentials_refresh( assert len(request.call_args_list) == len(requests) if credential_data: - cls.assert_credential_request_kwargs(request.call_args_list[0].kwargs, None) + cls.assert_credential_request_kwargs(request.call_args_list[0][1], None) # Verify token exchange request parameters. cls.assert_token_request_kwargs( - request.call_args_list[token_request_index].kwargs, + request.call_args_list[token_request_index][1], token_headers, token_request_data, token_url, @@ -235,7 +235,7 @@ def assert_underlying_credentials_refresh( # is processed. if service_account_impersonation_url: cls.assert_impersonation_request_kwargs( - request.call_args_list[impersonation_request_index].kwargs, + request.call_args_list[impersonation_request_index][1], impersonation_headers, impersonation_request_data, service_account_impersonation_url, @@ -505,7 +505,7 @@ def test_retrieve_subject_token_file_not_found(self): assert excinfo.match(r"File './not_found.txt' was not found") def test_refresh_text_file_success_without_impersonation_ignore_default_scopes( - self + self, ): credentials = self.make_credentials( client_id=CLIENT_ID, @@ -677,7 +677,7 @@ def test_retrieve_subject_token_from_url(self): subject_token = credentials.retrieve_subject_token(request) assert subject_token == TEXT_FILE_SUBJECT_TOKEN - self.assert_credential_request_kwargs(request.call_args_list[0].kwargs, None) + self.assert_credential_request_kwargs(request.call_args_list[0][1], None) def test_retrieve_subject_token_from_url_with_headers(self): credentials = self.make_credentials( @@ -688,7 +688,7 @@ def test_retrieve_subject_token_from_url_with_headers(self): assert subject_token == TEXT_FILE_SUBJECT_TOKEN self.assert_credential_request_kwargs( - request.call_args_list[0].kwargs, {"foo": "bar"} + request.call_args_list[0][1], {"foo": "bar"} ) def test_retrieve_subject_token_from_url_json(self): @@ -699,7 +699,7 @@ def test_retrieve_subject_token_from_url_json(self): subject_token = credentials.retrieve_subject_token(request) assert subject_token == JSON_FILE_SUBJECT_TOKEN - self.assert_credential_request_kwargs(request.call_args_list[0].kwargs, None) + self.assert_credential_request_kwargs(request.call_args_list[0][1], None) def test_retrieve_subject_token_from_url_json_with_headers(self): credentials = self.make_credentials( @@ -714,7 +714,7 @@ def test_retrieve_subject_token_from_url_json_with_headers(self): assert subject_token == JSON_FILE_SUBJECT_TOKEN self.assert_credential_request_kwargs( - request.call_args_list[0].kwargs, {"foo": "bar"} + request.call_args_list[0][1], {"foo": "bar"} ) def test_retrieve_subject_token_from_url_not_found(self): diff --git a/packages/google-auth/tests/test_impersonated_credentials.py b/packages/google-auth/tests/test_impersonated_credentials.py index 430c770d3672..90de704a265c 100644 --- a/packages/google-auth/tests/test_impersonated_credentials.py +++ b/packages/google-auth/tests/test_impersonated_credentials.py @@ -203,7 +203,7 @@ def test_refresh_success_iam_endpoint_override( assert credentials.valid assert not credentials.expired # Confirm override endpoint used. - request_kwargs = request.call_args.kwargs + request_kwargs = request.call_args[1] assert request_kwargs["url"] == self.IAM_ENDPOINT_OVERRIDE @pytest.mark.parametrize("time_skew", [100, -100]) @@ -378,7 +378,7 @@ def test_with_quota_project_iam_endpoint_override( assert quota_project_creds.valid assert not quota_project_creds.expired # Confirm override endpoint used. - request_kwargs = request.call_args.kwargs + request_kwargs = request.call_args[1] assert request_kwargs["url"] == self.IAM_ENDPOINT_OVERRIDE def test_id_token_success( From 8f26c1cb28056efeff084a42e9759ddf13823363 Mon Sep 17 00:00:00 2001 From: Ryan Kohler Date: Fri, 12 Mar 2021 16:04:02 -0800 Subject: [PATCH 391/966] feat: allow the AWS_DEFAULT_REGION environment variable (#721) Amazon has this variable documented, and apparently people are trying to use it, so we should support it --- packages/google-auth/google/auth/aws.py | 14 ++++-- .../google/auth/environment_vars.py | 1 + packages/google-auth/tests/test_aws.py | 50 +++++++++++++++++++ 3 files changed, 60 insertions(+), 5 deletions(-) diff --git a/packages/google-auth/google/auth/aws.py b/packages/google-auth/google/auth/aws.py index b362dd315299..c2b521c360f6 100644 --- a/packages/google-auth/google/auth/aws.py +++ b/packages/google-auth/google/auth/aws.py @@ -424,9 +424,9 @@ def retrieve_subject_token(self, request): The logic is summarized as: - Retrieve the AWS region from the AWS_REGION environment variable or from - the AWS metadata server availability-zone if not found in the - environment variable. + Retrieve the AWS region from the AWS_REGION or AWS_DEFAULT_REGION + environment variable or from the AWS metadata server availability-zone + if not found in the environment variable. Check AWS credentials in environment variables. If not found, retrieve from the AWS metadata server security-credentials endpoint. @@ -504,8 +504,8 @@ def retrieve_subject_token(self, request): ) def _get_region(self, request, url): - """Retrieves the current AWS region from either the AWS_REGION - environment variable or from the AWS metadata server. + """Retrieves the current AWS region from either the AWS_REGION or + AWS_DEFAULT_REGION environment variable or from the AWS metadata server. Args: request (google.auth.transport.Request): A callable used to make @@ -526,6 +526,10 @@ def _get_region(self, request, url): if env_aws_region is not None: return env_aws_region + env_aws_region = os.environ.get(environment_vars.AWS_DEFAULT_REGION) + if env_aws_region is not None: + return env_aws_region + if not self._region_url: raise exceptions.RefreshError("Unable to determine AWS region") response = request(url=self._region_url, method="GET") diff --git a/packages/google-auth/google/auth/environment_vars.py b/packages/google-auth/google/auth/environment_vars.py index 416bab0c01ea..f02774181345 100644 --- a/packages/google-auth/google/auth/environment_vars.py +++ b/packages/google-auth/google/auth/environment_vars.py @@ -69,3 +69,4 @@ AWS_SECRET_ACCESS_KEY = "AWS_SECRET_ACCESS_KEY" AWS_SESSION_TOKEN = "AWS_SESSION_TOKEN" AWS_REGION = "AWS_REGION" +AWS_DEFAULT_REGION = "AWS_DEFAULT_REGION" diff --git a/packages/google-auth/tests/test_aws.py b/packages/google-auth/tests/test_aws.py index 7a55841ca353..7c7ee36bee21 100644 --- a/packages/google-auth/tests/test_aws.py +++ b/packages/google-auth/tests/test_aws.py @@ -1043,6 +1043,56 @@ def test_retrieve_subject_token_success_environment_vars(self, utcnow, monkeypat } ) + @mock.patch("google.auth._helpers.utcnow") + def test_retrieve_subject_token_success_environment_vars_with_default_region( + self, utcnow, monkeypatch + ): + monkeypatch.setenv(environment_vars.AWS_ACCESS_KEY_ID, ACCESS_KEY_ID) + monkeypatch.setenv(environment_vars.AWS_SECRET_ACCESS_KEY, SECRET_ACCESS_KEY) + monkeypatch.setenv(environment_vars.AWS_SESSION_TOKEN, TOKEN) + monkeypatch.setenv(environment_vars.AWS_DEFAULT_REGION, self.AWS_REGION) + utcnow.return_value = datetime.datetime.strptime( + self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ" + ) + credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE) + + subject_token = credentials.retrieve_subject_token(None) + + assert subject_token == self.make_serialized_aws_signed_request( + { + "access_key_id": ACCESS_KEY_ID, + "secret_access_key": SECRET_ACCESS_KEY, + "security_token": TOKEN, + } + ) + + @mock.patch("google.auth._helpers.utcnow") + def test_retrieve_subject_token_success_environment_vars_with_both_regions_set( + self, utcnow, monkeypatch + ): + monkeypatch.setenv(environment_vars.AWS_ACCESS_KEY_ID, ACCESS_KEY_ID) + monkeypatch.setenv(environment_vars.AWS_SECRET_ACCESS_KEY, SECRET_ACCESS_KEY) + monkeypatch.setenv(environment_vars.AWS_SESSION_TOKEN, TOKEN) + monkeypatch.setenv(environment_vars.AWS_DEFAULT_REGION, "Malformed AWS Region") + # This test makes sure that the AWS_REGION gets used over AWS_DEFAULT_REGION, + # So, AWS_DEFAULT_REGION is set to something that would cause the test to fail, + # And AWS_REGION is set to the a valid value, and it should succeed + monkeypatch.setenv(environment_vars.AWS_REGION, self.AWS_REGION) + utcnow.return_value = datetime.datetime.strptime( + self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ" + ) + credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE) + + subject_token = credentials.retrieve_subject_token(None) + + assert subject_token == self.make_serialized_aws_signed_request( + { + "access_key_id": ACCESS_KEY_ID, + "secret_access_key": SECRET_ACCESS_KEY, + "security_token": TOKEN, + } + ) + @mock.patch("google.auth._helpers.utcnow") def test_retrieve_subject_token_success_environment_vars_no_session_token( self, utcnow, monkeypatch From bc6c16090df4e4c31fab22e02988bf9944693e49 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Mon, 15 Mar 2021 09:02:06 -0600 Subject: [PATCH 392/966] ci: create sponge_log.xml files (#722) * ci: create sponge_log.xml files * test: fix noxfile Co-authored-by: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> --- packages/google-auth/.gitignore | 1 + packages/google-auth/noxfile.py | 9 +- packages/google-auth/system_tests/noxfile.py | 99 +++++++++++++++----- 3 files changed, 85 insertions(+), 24 deletions(-) diff --git a/packages/google-auth/.gitignore b/packages/google-auth/.gitignore index 1f0b7e3c7836..ca0c0742c42c 100644 --- a/packages/google-auth/.gitignore +++ b/packages/google-auth/.gitignore @@ -22,6 +22,7 @@ db.sqlite3 # Coverage files .coverage coverage.xml +*sponge_log.xml nosetests.xml htmlcov/ diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index 168c1e2b63f2..3b4863c2ded2 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -87,6 +87,7 @@ def unit(session): session.install(".") session.run( "pytest", + f"--junitxml=unit_{session.python}_sponge_log.xml", "--cov=google.auth", "--cov=google.oauth2", "--cov=tests", @@ -100,7 +101,12 @@ def unit_prev_versions(session): session.install(".") session.install(*TEST_DEPENDENCIES) session.run( - "pytest", "--cov=google.auth", "--cov=google.oauth2", "--cov=tests", "tests" + "pytest", + f"--junitxml=unit_{session.python}_sponge_log.xml", + "--cov=google.auth", + "--cov=google.oauth2", + "--cov=tests", + "tests", ) @@ -170,6 +176,7 @@ def pypy(session): session.install(".") session.run( "pytest", + f"--junitxml=unit_{session.python}_sponge_log.xml", "--cov=google.auth", "--cov=google.oauth2", "--cov=tests", diff --git a/packages/google-auth/system_tests/noxfile.py b/packages/google-auth/system_tests/noxfile.py index 4ba7cc3efedc..6e2e1b885755 100644 --- a/packages/google-auth/system_tests/noxfile.py +++ b/packages/google-auth/system_tests/noxfile.py @@ -174,11 +174,21 @@ def configure_cloud_sdk(session, application_default_credentials, project=False) PYTHON_VERSIONS_SYNC = ["2.7", "3.7"] +def default(session, *test_paths): + # replace 'session._runner.friendly_name' with + # session.name once nox has released a new version + # https://github.com/theacodes/nox/pull/386 + sponge_log = f"--junitxml=system_{str(session._runner.friendly_name)}_sponge_log.xml" + session.run( + "pytest", sponge_log, *test_paths, + ) + + @nox.session(python=PYTHON_VERSIONS_SYNC) def service_account_sync(session): session.install(*TEST_DEPENDENCIES_SYNC) session.install(LIBRARY_DIR) - session.run("pytest", "system_tests_sync/test_service_account.py") + default(session, "system_tests_sync/test_service_account.py") @nox.session(python=PYTHON_VERSIONS_SYNC) @@ -187,7 +197,11 @@ def default_explicit_service_account(session): session.env[EXPECT_PROJECT_ENV] = "1" session.install(*TEST_DEPENDENCIES_SYNC) session.install(LIBRARY_DIR) - session.run("pytest", "system_tests_sync/test_default.py", "system_tests_sync/test_id_token.py") + default( + session, + "system_tests_sync/test_default.py", + "system_tests_sync/test_id_token.py", + ) @nox.session(python=PYTHON_VERSIONS_SYNC) @@ -195,7 +209,9 @@ def default_explicit_authorized_user(session): session.env[EXPLICIT_CREDENTIALS_ENV] = AUTHORIZED_USER_FILE session.install(*TEST_DEPENDENCIES_SYNC) session.install(LIBRARY_DIR) - session.run("pytest", "system_tests_sync/test_default.py") + default( + session, "system_tests_sync/test_default.py", + ) @nox.session(python=PYTHON_VERSIONS_SYNC) @@ -205,7 +221,9 @@ def default_explicit_authorized_user_explicit_project(session): session.env[EXPECT_PROJECT_ENV] = "1" session.install(*TEST_DEPENDENCIES_SYNC) session.install(LIBRARY_DIR) - session.run("pytest", "system_tests_sync/test_default.py") + default( + session, "system_tests_sync/test_default.py", + ) @nox.session(python=PYTHON_VERSIONS_SYNC) @@ -214,7 +232,9 @@ def default_cloud_sdk_service_account(session): session.env[EXPECT_PROJECT_ENV] = "1" session.install(*TEST_DEPENDENCIES_SYNC) session.install(LIBRARY_DIR) - session.run("pytest", "system_tests_sync/test_default.py") + default( + session, "system_tests_sync/test_default.py", + ) @nox.session(python=PYTHON_VERSIONS_SYNC) @@ -222,7 +242,9 @@ def default_cloud_sdk_authorized_user(session): configure_cloud_sdk(session, AUTHORIZED_USER_FILE) session.install(*TEST_DEPENDENCIES_SYNC) session.install(LIBRARY_DIR) - session.run("pytest", "system_tests_sync/test_default.py") + default( + session, "system_tests_sync/test_default.py", + ) @nox.session(python=PYTHON_VERSIONS_SYNC) @@ -231,7 +253,10 @@ def default_cloud_sdk_authorized_user_configured_project(session): session.env[EXPECT_PROJECT_ENV] = "1" session.install(*TEST_DEPENDENCIES_SYNC) session.install(LIBRARY_DIR) - session.run("pytest", "system_tests_sync/test_default.py") + default( + session, "system_tests_sync/test_default.py", + ) + @nox.session(python=PYTHON_VERSIONS_SYNC) def compute_engine(session): @@ -240,7 +265,9 @@ def compute_engine(session): # credentials are detected from environment del session.virtualenv.env["GOOGLE_APPLICATION_CREDENTIALS"] session.install(LIBRARY_DIR) - session.run("pytest", "system_tests_sync/test_compute_engine.py") + default( + session, "system_tests_sync/test_compute_engine.py", + ) @nox.session(python=["2.7"]) @@ -282,7 +309,9 @@ def app_engine(session): # Run the tests session.env["TEST_APP_URL"] = application_url session.chdir(HERE) - session.run("pytest", "system_tests_sync/test_app_engine.py") + default( + session, "system_tests_sync/test_app_engine.py", + ) @nox.session(python=PYTHON_VERSIONS_SYNC) @@ -290,7 +319,9 @@ def grpc(session): session.install(LIBRARY_DIR) session.install(*TEST_DEPENDENCIES_SYNC, "google-cloud-pubsub==1.7.0") session.env[EXPLICIT_CREDENTIALS_ENV] = SERVICE_ACCOUNT_FILE - session.run("pytest", "system_tests_sync/test_grpc.py") + default( + session, "system_tests_sync/test_grpc.py", + ) @nox.session(python=PYTHON_VERSIONS_SYNC) @@ -298,7 +329,9 @@ def requests(session): session.install(LIBRARY_DIR) session.install(*TEST_DEPENDENCIES_SYNC) session.env[EXPLICIT_CREDENTIALS_ENV] = SERVICE_ACCOUNT_FILE - session.run("pytest", "system_tests_sync/test_requests.py") + default( + session, "system_tests_sync/test_requests.py", + ) @nox.session(python=PYTHON_VERSIONS_SYNC) @@ -306,7 +339,9 @@ def urllib3(session): session.install(LIBRARY_DIR) session.install(*TEST_DEPENDENCIES_SYNC) session.env[EXPLICIT_CREDENTIALS_ENV] = SERVICE_ACCOUNT_FILE - session.run("pytest", "system_tests_sync/test_urllib3.py") + default( + session, "system_tests_sync/test_urllib3.py", + ) @nox.session(python=PYTHON_VERSIONS_SYNC) @@ -314,16 +349,21 @@ def mtls_http(session): session.install(LIBRARY_DIR) session.install(*TEST_DEPENDENCIES_SYNC, "pyopenssl") session.env[EXPLICIT_CREDENTIALS_ENV] = SERVICE_ACCOUNT_FILE - session.run("pytest", "system_tests_sync/test_mtls_http.py") + default( + session, "system_tests_sync/test_mtls_http.py", + ) -#ASYNC SYSTEM TESTS +# ASYNC SYSTEM TESTS + @nox.session(python=PYTHON_VERSIONS_ASYNC) def service_account_async(session): - session.install(*(TEST_DEPENDENCIES_SYNC+TEST_DEPENDENCIES_ASYNC)) + session.install(*(TEST_DEPENDENCIES_SYNC + TEST_DEPENDENCIES_ASYNC)) session.install(LIBRARY_DIR) - session.run("pytest", "system_tests_async/test_service_account.py") + default( + session, "system_tests_async/test_service_account.py", + ) @nox.session(python=PYTHON_VERSIONS_ASYNC) @@ -332,8 +372,11 @@ def default_explicit_service_account_async(session): session.env[EXPECT_PROJECT_ENV] = "1" session.install(*(TEST_DEPENDENCIES_SYNC + TEST_DEPENDENCIES_ASYNC)) session.install(LIBRARY_DIR) - session.run("pytest", "system_tests_async/test_default.py", - "system_tests_async/test_id_token.py") + default( + session, + "system_tests_async/test_default.py", + "system_tests_async/test_id_token.py", + ) @nox.session(python=PYTHON_VERSIONS_ASYNC) @@ -341,7 +384,9 @@ def default_explicit_authorized_user_async(session): session.env[EXPLICIT_CREDENTIALS_ENV] = AUTHORIZED_USER_FILE session.install(*(TEST_DEPENDENCIES_SYNC + TEST_DEPENDENCIES_ASYNC)) session.install(LIBRARY_DIR) - session.run("pytest", "system_tests_async/test_default.py") + default( + session, "system_tests_async/test_default.py", + ) @nox.session(python=PYTHON_VERSIONS_ASYNC) @@ -351,7 +396,9 @@ def default_explicit_authorized_user_explicit_project_async(session): session.env[EXPECT_PROJECT_ENV] = "1" session.install(*(TEST_DEPENDENCIES_SYNC + TEST_DEPENDENCIES_ASYNC)) session.install(LIBRARY_DIR) - session.run("pytest", "system_tests_async/test_default.py") + default( + session, "system_tests_async/test_default.py", + ) @nox.session(python=PYTHON_VERSIONS_ASYNC) @@ -360,7 +407,9 @@ def default_cloud_sdk_service_account_async(session): session.env[EXPECT_PROJECT_ENV] = "1" session.install(*(TEST_DEPENDENCIES_SYNC + TEST_DEPENDENCIES_ASYNC)) session.install(LIBRARY_DIR) - session.run("pytest", "system_tests_async/test_default.py") + default( + session, "system_tests_async/test_default.py", + ) @nox.session(python=PYTHON_VERSIONS_ASYNC) @@ -368,7 +417,9 @@ def default_cloud_sdk_authorized_user_async(session): configure_cloud_sdk(session, AUTHORIZED_USER_FILE) session.install(*(TEST_DEPENDENCIES_SYNC + TEST_DEPENDENCIES_ASYNC)) session.install(LIBRARY_DIR) - session.run("pytest", "system_tests_async/test_default.py") + default( + session, "system_tests_async/test_default.py", + ) @nox.session(python=PYTHON_VERSIONS_ASYNC) @@ -377,4 +428,6 @@ def default_cloud_sdk_authorized_user_configured_project_async(session): session.env[EXPECT_PROJECT_ENV] = "1" session.install(*(TEST_DEPENDENCIES_SYNC + TEST_DEPENDENCIES_ASYNC)) session.install(LIBRARY_DIR) - session.run("pytest", "system_tests_async/test_default.py") + default( + session, "system_tests_async/test_default.py", + ) From 286b964927589c5befb1d104ede3b56e74746d27 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Tue, 16 Mar 2021 14:56:02 -0600 Subject: [PATCH 393/966] feat: expose library version at `google.auth.__version` (#683) Move the version from `setup.py` to `google/auth/version.py`. Same as https://github.com/googleapis/python-api-core/pull/80. (see https://github.com/googleapis/python-api-core/issues/27 for motivation). This is option 3 in https://packaging.python.org/guides/single-sourcing-package-version/. This unblocks a version check I'd like to add in https://github.com/googleapis/python-api-core/pull/134. Usage: ```py >>> import google.auth >>> google.auth.__version__ '1.25.0' ``` --- packages/google-auth/google/auth/__init__.py | 5 +++++ packages/google-auth/google/auth/version.py | 15 +++++++++++++++ packages/google-auth/setup.py | 8 +++++++- 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 packages/google-auth/google/auth/version.py diff --git a/packages/google-auth/google/auth/__init__.py b/packages/google-auth/google/auth/__init__.py index 22d61c66fe0c..861abe7ea60f 100644 --- a/packages/google-auth/google/auth/__init__.py +++ b/packages/google-auth/google/auth/__init__.py @@ -16,8 +16,13 @@ import logging +from google.auth import version as google_auth_version from google.auth._default import default, load_credentials_from_file + +__version__ = google_auth_version.__version__ + + __all__ = ["default", "load_credentials_from_file"] # Set default logging handler to avoid "No handler found" warnings. diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py new file mode 100644 index 000000000000..54ad93268bf4 --- /dev/null +++ b/packages/google-auth/google/auth/version.py @@ -0,0 +1,15 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +__version__ = "1.27.1" diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 33089cb71e5a..16ba98cfd626 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -13,6 +13,7 @@ # limitations under the License. import io +import os from setuptools import find_packages from setuptools import setup @@ -37,7 +38,12 @@ with io.open("README.rst", "r") as fh: long_description = fh.read() -version = "1.27.1" +package_root = os.path.abspath(os.path.dirname(__file__)) + +version = {} +with open(os.path.join(package_root, "google/auth/version.py")) as fp: + exec(fp.read(), version) +version = version["__version__"] setup( name="google-auth", From 1dcc804e5ed2d4fa97ae337f2da60148f9e74126 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 16 Mar 2021 16:17:05 -0600 Subject: [PATCH 394/966] chore: release 1.28.0 (#723) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 13 +++++++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 3e1eef8f0448..a43f65896a4c 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,19 @@ [1]: https://pypi.org/project/google-auth/#history +## [1.28.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.27.1...v1.28.0) (2021-03-16) + + +### Features + +* allow the AWS_DEFAULT_REGION environment variable ([#721](https://www.github.com/googleapis/google-auth-library-python/issues/721)) ([199da47](https://www.github.com/googleapis/google-auth-library-python/commit/199da4781029916dc075738ec7bd173bd89abe54)) +* expose library version at `google.auth.__version` ([#683](https://www.github.com/googleapis/google-auth-library-python/issues/683)) ([a2cbc32](https://www.github.com/googleapis/google-auth-library-python/commit/a2cbc3245460e1ae1d310de6a2a4007d5a3a06b7)) + + +### Bug Fixes + +* fix unit tests so they can work in g3 ([#714](https://www.github.com/googleapis/google-auth-library-python/issues/714)) ([d80c85f](https://www.github.com/googleapis/google-auth-library-python/commit/d80c85f285ae1a44ddc5a5d94a66e065a79f6d19)) + ### [1.27.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.27.0...v1.27.1) (2021-02-26) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 54ad93268bf4..603212eb2abb 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "1.27.1" +__version__ = "1.28.0" From 7ad11b25d94dd1844c7cf1f74ab3d237b0f077c9 Mon Sep 17 00:00:00 2001 From: Ryan Kohler Date: Thu, 25 Mar 2021 17:35:43 -0700 Subject: [PATCH 395/966] test: Create BYOID Integration tests (#719) --- packages/google-auth/CONTRIBUTING.rst | 32 ++-- .../scripts/setup_external_accounts.sh | 112 ++++++++++++++ packages/google-auth/system_tests/noxfile.py | 9 +- .../test_external_accounts.py | 138 ++++++++++++++++++ 4 files changed, 280 insertions(+), 11 deletions(-) create mode 100644 packages/google-auth/scripts/setup_external_accounts.sh create mode 100644 packages/google-auth/system_tests/system_tests_sync/test_external_accounts.py diff --git a/packages/google-auth/CONTRIBUTING.rst b/packages/google-auth/CONTRIBUTING.rst index ac65343ee12d..175e76634f01 100644 --- a/packages/google-auth/CONTRIBUTING.rst +++ b/packages/google-auth/CONTRIBUTING.rst @@ -42,9 +42,9 @@ You can run the system tests with ``nox``:: To run a single session, specify it with ``nox -s``:: $ nox -f system_tests/noxfile.py -s service_account - -First, set the environemnt variable ``GOOGLE_APPLICATION_CREDENTIALS`` to a valid service account. -See `Creating and Managing Service Account Keys`_ for how to obtain a service account. + +First, set the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` to a valid service account. +See `Creating and Managing Service Account Keys`_ for how to obtain a service account. Project and Credentials Setup ------------------------------- @@ -86,26 +86,40 @@ This will allow the user to impersonate service accounts on the project. ``service_account.json`` ~~~~~~~~~~~~~~~~~~~~~~~~ -Follow `Creating and Managing Service Account Keys`_ to create a service account. +Follow `Creating and Managing Service Account Keys`_ to create a service account. Copy the credentials file to ``service_account.json``. Grant the account associated with ``service_account.json`` the following roles. - App Engine Admin (for App Engine tests) -- Service Account Token Creator (for impersonated credentials tests) +- Service Account Token Creator (for impersonated credentials and workload identity federation tests) - Pub/Sub Viewer (for gRPC tests) - Storage Object Viewer (for impersonated credentials tests) +- DNS Viewer (for workload identity federation tests) ``impersonated_service_account.json`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Follow `Creating and Managing Service Account Keys`_ to create a service account. +Follow `Creating and Managing Service Account Keys`_ to create a service account. Copy the credentials file to ``impersonated_service_account.json``. .. _Creating and Managing Service Account Keys: https://cloud.google.com/iam/docs/creating-managing-service-account-keys +``setup_external_accounts`` +~~~~~~~~~~~~~~~~ + +In order to run the workload identity federation tests, you will need to set up +a Workload Identity Pool, as well as attach relevant policy bindings for this +new resource to our service account. To do this, make sure you have IAM Workload +Identity Pool Admin and Security Admin permissions, and then run: + + $ ./scripts/setup_external_accounts.sh + +and then use the output to replace the variables near +the top of system_tests/system_tests_sync/test_external_accounts.py + App Engine System Tests ~~~~~~~~~~~~~~~~~~~~~~~~ @@ -118,16 +132,16 @@ From ``system_tests/app_engine_test_app`` run the following commands :: $ pip install --target lib -r requirements.txt $ gcloud app deploy -q app.yaml -After the app is deployed, change ``service`` in ``app.yaml`` back to ``google-auth-system-tests``. +After the app is deployed, change ``service`` in ``app.yaml`` back to ``google-auth-system-tests``. You can now run the App Engine tests: :: $ nox -f system_tests/noxfile.py -s app_engine - + Compute Engine Tests ^^^^^^^^^^^^^^^^^^^^ These tests cannot be run locally and will be skipped if they are run outside of Google Compute Engine. - + grpc Tests ^^^^^^^^^^^^ diff --git a/packages/google-auth/scripts/setup_external_accounts.sh b/packages/google-auth/scripts/setup_external_accounts.sh new file mode 100644 index 000000000000..2fd04e26cbd1 --- /dev/null +++ b/packages/google-auth/scripts/setup_external_accounts.sh @@ -0,0 +1,112 @@ +#!/bin/bash +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This file is a mostly common setup file to ensure all workload identity +# federation integration tests are set up in a consistent fashion across the +# languages in our various client libraries. It assumes that the current user +# has the relevant permissions to run each of the commands listed. + +# This script needs to be run once. It will do the following: +# 1. Create a random workload identity pool. +# 2. Create a random OIDC provider in that pool which uses the +# accounts.google.com as the issuer and the default STS audience as the +# allowed audience. This audience will be validated on STS token exchange. +# 3. Enable OIDC tokens generated by the current service account to impersonate +# the service account. (Identified by the OIDC token sub field which is the +# service account client ID). +# 4. Create a random AWS provider in that pool which uses the provided AWS +# account ID. +# 5. Enable AWS provider to impersonate the service account. (Principal is +# identified by the AWS role name). +# 6. Print out the STS audience fields associated with the created providers +# after the setup completes successfully so that they can be used in the +# tests. These will be copied and used as the global _AUDIENCE_OIDC and +# _AUDIENCE_AWS constants in system_tests/system_tests_sync/test_external_accounts.py. +# +# It is safe to run the setup script again. A new pool is created and new +# audiences are printed. If run multiple times, it is advisable to delete +# unused pools. Note that deleted pools are soft deleted and may remain for +# a while before they are completely deleted. The old pool ID cannot be used +# in the meantime. +# +# For AWS tests, an AWS developer account is needed. +# The following AWS prerequisite setup is needed. +# 1. An OIDC Google identity provider needs to be created with the following: +# issuer: accounts.google.com +# audience: Use the client_id of the service account. +# 2. A role for OIDC web identity federation is needed with the created Google +# provider as a trusted entity: +# "accounts.google.com:aud": "$CLIENT_ID" +# The steps are documented at: +# https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-idp_oidc.html + +suffix="" + +function generate_random_string () { + local valid_chars=abcdefghijklmnopqrstuvwxyz0123456789 + for i in {1..8} ; do + suffix+="${valid_chars:RANDOM%${#valid_chars}:1}" + done +} + +generate_random_string + +pool_id="pool-"$suffix +oidc_provider_id="oidc-"$suffix +aws_provider_id="aws-"$suffix + +# TODO: Fill in. +project_id="stellar-day-254222" +project_number="79992041559" +aws_account_id="077071391996" +aws_role_name="ci-python-test" +service_account_email="kokoro@stellar-day-254222.iam.gserviceaccount.com" +sub="104692443208068386138" + +oidc_aud="//iam.googleapis.com/projects/$project_number/locations/global/workloadIdentityPools/$pool_id/providers/$oidc_provider_id" +aws_aud="//iam.googleapis.com/projects/$project_number/locations/global/workloadIdentityPools/$pool_id/providers/$aws_provider_id" + +gcloud config set project $project_id + +# Create the Workload Identity Pool. +gcloud beta iam workload-identity-pools create $pool_id \ + --location="global" \ + --description="Test pool" \ + --display-name="Test pool for Python" + +# Create the OIDC Provider. +gcloud beta iam workload-identity-pools providers create-oidc $oidc_provider_id \ + --workload-identity-pool=$pool_id \ + --issuer-uri="https://accounts.google.com" \ + --location="global" \ + --attribute-mapping="google.subject=assertion.sub" + +# Create the AWS Provider. +gcloud beta iam workload-identity-pools providers create-aws $aws_provider_id \ + --workload-identity-pool=$pool_id \ + --account-id=$aws_account_id \ + --location="global" + +# Give permission to impersonate the service account. +gcloud iam service-accounts add-iam-policy-binding $service_account_email \ +--role roles/iam.workloadIdentityUser \ +--member "principal://iam.googleapis.com/projects/$project_number/locations/global/workloadIdentityPools/$pool_id/subject/$sub" + +gcloud iam service-accounts add-iam-policy-binding $service_account_email \ + --role roles/iam.workloadIdentityUser \ + --member "principalSet://iam.googleapis.com/projects/$project_number/locations/global/workloadIdentityPools/$pool_id/attribute.aws_role/arn:aws:sts::$aws_account_id:assumed-role/$aws_role_name" + +echo "OIDC audience: "$oidc_aud +echo "AWS audience: "$aws_aud diff --git a/packages/google-auth/system_tests/noxfile.py b/packages/google-auth/system_tests/noxfile.py index 6e2e1b885755..f177dcd6a35a 100644 --- a/packages/google-auth/system_tests/noxfile.py +++ b/packages/google-auth/system_tests/noxfile.py @@ -354,9 +354,14 @@ def mtls_http(session): ) -# ASYNC SYSTEM TESTS +@nox.session(python=PYTHON_VERSIONS_SYNC) +def external_accounts(session): + session.install(*TEST_DEPENDENCIES_SYNC, "google-auth", "google-api-python-client", "enum34") + default(session, "system_tests_sync/test_external_accounts.py") +# ASYNC SYSTEM TESTS + @nox.session(python=PYTHON_VERSIONS_ASYNC) def service_account_async(session): session.install(*(TEST_DEPENDENCIES_SYNC + TEST_DEPENDENCIES_ASYNC)) @@ -374,7 +379,7 @@ def default_explicit_service_account_async(session): session.install(LIBRARY_DIR) default( session, - "system_tests_async/test_default.py", + "system_tests_async/test_default.py", "system_tests_async/test_id_token.py", ) diff --git a/packages/google-auth/system_tests/system_tests_sync/test_external_accounts.py b/packages/google-auth/system_tests/system_tests_sync/test_external_accounts.py new file mode 100644 index 000000000000..0e8ad1698fcc --- /dev/null +++ b/packages/google-auth/system_tests/system_tests_sync/test_external_accounts.py @@ -0,0 +1,138 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Prerequisites: +# Make sure to run the setup in scripts/setup_external_accounts.sh +# and copy the logged constant strings (_AUDIENCE_OIDC, _AUDIENCE_AWS) +# into this file before running this test suite. +# Once that is done, this test can be run indefinitely. +# +# The only requirement for this test suite to run is to set the environment +# variable GOOGLE_APPLICATION_CREDENTIALS to point to the expected service +# account keys whose email is referred to in the setup script. +# +# This script follows the following logic. +# OIDC provider (file-sourced and url-sourced credentials): +# Use the service account keys to generate a Google ID token using the +# iamcredentials generateIdToken API, using the default STS audience. +# This will use the service account client ID as the sub field of the token. +# This OIDC token will be used as the external subject token to be exchanged +# for a Google access token via GCP STS endpoint and then to impersonate the +# original service account key. + + +import json +import os +from tempfile import NamedTemporaryFile + +import sys +import google.auth +from googleapiclient import discovery +from google.oauth2 import service_account +import pytest +from mock import patch + +# Populate values from the output of scripts/setup_external_accounts.sh. +_AUDIENCE_OIDC = "//iam.googleapis.com/projects/79992041559/locations/global/workloadIdentityPools/pool-73wslmxn/providers/oidc-73wslmxn" + + +def dns_access_direct(request, project_id): + # First, get the default credentials. + credentials, _ = google.auth.default( + scopes=["https://www.googleapis.com/auth/cloud-platform.read-only"], + request=request, + ) + + # Apply the default credentials to the headers to make the request. + headers = {} + credentials.apply(headers) + response = request( + url="https://dns.googleapis.com/dns/v1/projects/{}".format(project_id), + headers=headers, + ) + + if response.status == 200: + return response.data + + +def dns_access_client_library(_, project_id): + service = discovery.build("dns", "v1") + request = service.projects().get(project=project_id) + return request.execute() + + +@pytest.fixture(params=[dns_access_direct, dns_access_client_library]) +def dns_access(request, http_request, service_account_info): + # Fill in the fixtures on the functions, + # so that we don't have to fill in the parameters manually. + def wrapper(): + return request.param(http_request, service_account_info["project_id"]) + + yield wrapper + + +@pytest.fixture +def oidc_credentials(service_account_file, http_request): + result = service_account.IDTokenCredentials.from_service_account_file( + service_account_file, target_audience=_AUDIENCE_OIDC + ) + result.refresh(http_request) + yield result + + +@pytest.fixture +def service_account_info(service_account_file): + with open(service_account_file) as f: + yield json.load(f) + + +# Our external accounts tests involve setting up some preconditions, setting a +# credential file, and then making sure that our client libraries can work with +# the set credentials. +def get_project_dns(dns_access, credential_data): + with NamedTemporaryFile() as credfile: + credfile.write(json.dumps(credential_data).encode("utf-8")) + credfile.flush() + old_credentials = os.environ.get("GOOGLE_APPLICATION_CREDENTIALS") + + with patch.dict(os.environ, {"GOOGLE_APPLICATION_CREDENTIALS": credfile.name}): + # If our setup and credential file are correct, + # discovery.build should be able to establish these as the default credentials. + return dns_access() + + +# This test makes sure that setting an accesible credential file +# works to allow access to Google resources. +def test_file_based_external_account( + oidc_credentials, service_account_info, dns_access +): + with NamedTemporaryFile() as tmpfile: + tmpfile.write(oidc_credentials.token.encode("utf-8")) + tmpfile.flush() + + assert get_project_dns( + dns_access, + { + "type": "external_account", + "audience": _AUDIENCE_OIDC, + "subject_token_type": "urn:ietf:params:oauth:token-type:jwt", + "token_url": "https://sts.googleapis.com/v1/token", + "service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/{}:generateAccessToken".format( + oidc_credentials.service_account_email + ), + "credential_source": { + "file": tmpfile.name, + }, + }, + ) From 0bee71d4b52226767c025b9585b9c022d1af2eb3 Mon Sep 17 00:00:00 2001 From: Ryan Kohler Date: Wed, 7 Apr 2021 12:32:02 -0700 Subject: [PATCH 396/966] test: Create external account integration tests for URL-based credentials (#726) * Create external account integration tests for URL-based credentials * rename byoid to external_account * changes requested by bojeil@ --- .../test_external_accounts.py | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/packages/google-auth/system_tests/system_tests_sync/test_external_accounts.py b/packages/google-auth/system_tests/system_tests_sync/test_external_accounts.py index 0e8ad1698fcc..db6f28169fa0 100644 --- a/packages/google-auth/system_tests/system_tests_sync/test_external_accounts.py +++ b/packages/google-auth/system_tests/system_tests_sync/test_external_accounts.py @@ -34,11 +34,14 @@ import json import os +import socket from tempfile import NamedTemporaryFile +import threading import sys import google.auth from googleapiclient import discovery +from six.moves import BaseHTTPServer from google.oauth2 import service_account import pytest from mock import patch @@ -136,3 +139,75 @@ def test_file_based_external_account( }, }, ) + + +# This test makes sure that setting up an http server to provide credentials +# works to allow access to Google resources. +def test_url_based_external_account(dns_access, oidc_credentials, service_account_info): + class TestResponseHandler(BaseHTTPServer.BaseHTTPRequestHandler): + def do_GET(self): + if self.headers["my-header"] != "expected-value": + self.send_response(400) + self.send_header("Content-Type", "application/json") + self.end_headers() + self.wfile.write( + json.dumps({"error": "missing header"}).encode("utf-8") + ) + elif self.path != "/token": + self.send_response(400) + self.send_header("Content-Type", "application/json") + self.end_headers() + self.wfile.write( + json.dumps({"error": "incorrect token path"}).encode("utf-8") + ) + else: + self.send_response(200) + self.send_header("Content-Type", "application/json") + self.end_headers() + self.wfile.write( + json.dumps({"access_token": oidc_credentials.token}).encode("utf-8") + ) + + class TestHTTPServer(BaseHTTPServer.HTTPServer, object): + def __init__(self): + self.port = self._find_open_port() + super(TestHTTPServer, self).__init__(("", self.port), TestResponseHandler) + + @staticmethod + def _find_open_port(): + s = socket.socket() + s.bind(("", 0)) + return s.getsockname()[1] + + # This makes sure that the server gets shut down when this variable leaves its "with" block + # The python3 HttpServer has __enter__ and __exit__ methods, but python2 does not. + # By redefining the __enter__ and __exit__ methods, we ensure that python2 and python3 act similarly + def __exit__(self, *args): + self.shutdown() + + def __enter__(self): + return self + + with TestHTTPServer() as server: + threading.Thread(target=server.serve_forever).start() + + assert get_project_dns( + dns_access, + { + "type": "external_account", + "audience": _AUDIENCE_OIDC, + "subject_token_type": "urn:ietf:params:oauth:token-type:jwt", + "token_url": "https://sts.googleapis.com/v1/token", + "service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/{}:generateAccessToken".format( + oidc_credentials.service_account_email + ), + "credential_source": { + "url": "http://localhost:{}/token".format(server.port), + "headers": {"my-header": "expected-value"}, + "format": { + "type": "json", + "subject_token_field_name": "access_token", + }, + }, + }, + ) From e003bcac4d7aba8f0fbe3d8459c7b3d6f58d9bc2 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Thu, 8 Apr 2021 10:58:38 -0700 Subject: [PATCH 397/966] fix: support custom alg in jwt header for signing (#729) * fix: support custom alg in jwt header for signing * lint --- packages/google-auth/google/auth/jwt.py | 9 +++++---- packages/google-auth/tests/test_jwt.py | 6 ++++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/google-auth/google/auth/jwt.py b/packages/google-auth/google/auth/jwt.py index a4f04f529e03..8165ddad7767 100644 --- a/packages/google-auth/google/auth/jwt.py +++ b/packages/google-auth/google/auth/jwt.py @@ -95,10 +95,11 @@ def encode(signer, payload, header=None, key_id=None): header.update({"typ": "JWT"}) - if es256 is not None and isinstance(signer, es256.ES256Signer): - header.update({"alg": "ES256"}) - else: - header.update({"alg": "RS256"}) + if "alg" not in header: + if es256 is not None and isinstance(signer, es256.ES256Signer): + header.update({"alg": "ES256"}) + else: + header.update({"alg": "RS256"}) if key_id is not None: header["kid"] = key_id diff --git a/packages/google-auth/tests/test_jwt.py b/packages/google-auth/tests/test_jwt.py index 7aa031ec533d..7b5ba5cdcb80 100644 --- a/packages/google-auth/tests/test_jwt.py +++ b/packages/google-auth/tests/test_jwt.py @@ -73,6 +73,12 @@ def test_encode_extra_headers(signer): } +def test_encode_custom_alg_in_headers(signer): + encoded = jwt.encode(signer, {}, header={"alg": "foo"}) + header = jwt.decode_header(encoded) + assert header == {"typ": "JWT", "alg": "foo", "kid": signer.key_id} + + @pytest.fixture def es256_signer(): return crypt.ES256Signer.from_string(EC_PRIVATE_KEY_BYTES, "1") From de2ca21c92e1e1223c55d288ed83f3998bdd33b3 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 8 Apr 2021 13:29:27 -0700 Subject: [PATCH 398/966] chore: release 1.28.1 (#730) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index a43f65896a4c..081e5540e20e 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +### [1.28.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.28.0...v1.28.1) (2021-04-08) + + +### Bug Fixes + +* support custom alg in jwt header for signing ([#729](https://www.github.com/googleapis/google-auth-library-python/issues/729)) ([0a83706](https://www.github.com/googleapis/google-auth-library-python/commit/0a83706c9d65f7d5a30ea3b42c5beac269ed2a25)) + ## [1.28.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.27.1...v1.28.0) (2021-03-16) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 603212eb2abb..1faab4d1e1ac 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "1.28.0" +__version__ = "1.28.1" From 10b539d70f880b7508962854866a8ab44adde738 Mon Sep 17 00:00:00 2001 From: Ryan Kohler Date: Wed, 14 Apr 2021 11:14:41 -0400 Subject: [PATCH 399/966] test: Create AWS-based external account integration tests (#731) --- .../scripts/setup_external_accounts.sh | 1 + .../system_tests_sync/conftest.py | 32 ++++++- .../test_external_accounts.py | 92 +++++++++++++++++++ 3 files changed, 122 insertions(+), 3 deletions(-) diff --git a/packages/google-auth/scripts/setup_external_accounts.sh b/packages/google-auth/scripts/setup_external_accounts.sh index 2fd04e26cbd1..ecc879b9395f 100644 --- a/packages/google-auth/scripts/setup_external_accounts.sh +++ b/packages/google-auth/scripts/setup_external_accounts.sh @@ -110,3 +110,4 @@ gcloud iam service-accounts add-iam-policy-binding $service_account_email \ echo "OIDC audience: "$oidc_aud echo "AWS audience: "$aws_aud +echo "AWS role: arn:aws:iam::$aws_account_id:role/$aws_role_name" diff --git a/packages/google-auth/system_tests/system_tests_sync/conftest.py b/packages/google-auth/system_tests/system_tests_sync/conftest.py index 37a6fd346bf9..16caa659ee61 100644 --- a/packages/google-auth/system_tests/system_tests_sync/conftest.py +++ b/packages/google-auth/system_tests/system_tests_sync/conftest.py @@ -54,14 +54,40 @@ def authorized_user_file(): @pytest.fixture(params=["urllib3", "requests"]) -def http_request(request): +def request_type(request): + yield request.param + + +@pytest.fixture +def http_request(request_type): """A transport.request object.""" - if request.param == "urllib3": + if request_type == "urllib3": yield google.auth.transport.urllib3.Request(URLLIB3_HTTP) - elif request.param == "requests": + elif request_type == "requests": yield google.auth.transport.requests.Request(REQUESTS_SESSION) +@pytest.fixture +def authenticated_request(request_type): + """A transport.request object that takes credentials""" + if request_type == "urllib3": + + def wrapper(credentials): + return google.auth.transport.urllib3.AuthorizedHttp( + credentials, http=URLLIB3_HTTP + ).request + + yield wrapper + elif request_type == "requests": + + def wrapper(credentials): + session = google.auth.transport.requests.AuthorizedSession(credentials) + session.verify = False + return google.auth.transport.requests.Request(session) + + yield wrapper + + @pytest.fixture def token_info(http_request): """Returns a function that obtains OAuth2 token info.""" diff --git a/packages/google-auth/system_tests/system_tests_sync/test_external_accounts.py b/packages/google-auth/system_tests/system_tests_sync/test_external_accounts.py index db6f28169fa0..e24c7b40a54a 100644 --- a/packages/google-auth/system_tests/system_tests_sync/test_external_accounts.py +++ b/packages/google-auth/system_tests/system_tests_sync/test_external_accounts.py @@ -48,6 +48,8 @@ # Populate values from the output of scripts/setup_external_accounts.sh. _AUDIENCE_OIDC = "//iam.googleapis.com/projects/79992041559/locations/global/workloadIdentityPools/pool-73wslmxn/providers/oidc-73wslmxn" +_AUDIENCE_AWS = "//iam.googleapis.com/projects/79992041559/locations/global/workloadIdentityPools/pool-73wslmxn/providers/aws-73wslmxn" +_ROLE_AWS = "arn:aws:iam::077071391996:role/ci-python-test" def dns_access_direct(request, project_id): @@ -100,6 +102,27 @@ def service_account_info(service_account_file): yield json.load(f) +@pytest.fixture +def aws_oidc_credentials( + service_account_file, service_account_info, authenticated_request +): + credentials = service_account.Credentials.from_service_account_file( + service_account_file, scopes=["https://www.googleapis.com/auth/cloud-platform"] + ) + result = authenticated_request(credentials)( + url="https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/{}:generateIdToken".format( + service_account_info["client_email"] + ), + method="POST", + body=json.dumps( + {"audience": service_account_info["client_id"], "includeEmail": True} + ), + ) + assert result.status == 200 + + yield json.loads(result.data)["token"] + + # Our external accounts tests involve setting up some preconditions, setting a # credential file, and then making sure that our client libraries can work with # the set credentials. @@ -115,6 +138,14 @@ def get_project_dns(dns_access, credential_data): return dns_access() +def get_xml_value_by_tagname(data, tagname): + startIndex = data.index("<{}>".format(tagname)) + if startIndex >= 0: + endIndex = data.index("".format(tagname), startIndex) + if endIndex > startIndex: + return data[startIndex + len(tagname) + 2 : endIndex] + + # This test makes sure that setting an accesible credential file # works to allow access to Google resources. def test_file_based_external_account( @@ -211,3 +242,64 @@ def __enter__(self): }, }, ) + + +# AWS provider tests for AWS credentials +# The test suite will also run tests for AWS credentials. This works as +# follows. (Note prequisite setup is needed. This is documented in +# setup_external_accounts.sh). +# - iamcredentials:generateIdToken is used to generate a Google ID token using +# the service account access token. The service account client_id is used as +# audience. +# - AWS STS AssumeRoleWithWebIdentity API is used to exchange this token for +# temporary AWS security credentials for a specified AWS ARN role. +# - AWS_REGION, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_SESSION_TOKEN +# environment variables are set using these credentials before the test is +# run simulating an AWS VM. +# - The test can now be run. +def test_aws_based_external_account( + aws_oidc_credentials, service_account_info, dns_access, http_request +): + + response = http_request( + url=( + "https://sts.amazonaws.com/" + "?Action=AssumeRoleWithWebIdentity" + "&Version=2011-06-15" + "&DurationSeconds=3600" + "&RoleSessionName=python-test" + "&RoleArn={}" + "&WebIdentityToken={}" + ).format(_ROLE_AWS, aws_oidc_credentials) + ) + assert response.status == 200 + + # The returned data is in XML, but loading an XML parser would be overkill. + # Searching the return text manually for the start and finish tag. + data = response.data.decode("utf-8") + + with patch.dict( + os.environ, + { + "AWS_REGION": "us-east-2", + "AWS_ACCESS_KEY_ID": get_xml_value_by_tagname(data, "AccessKeyId"), + "AWS_SECRET_ACCESS_KEY": get_xml_value_by_tagname(data, "SecretAccessKey"), + "AWS_SESSION_TOKEN": get_xml_value_by_tagname(data, "SessionToken"), + }, + ): + assert get_project_dns( + dns_access, + { + "type": "external_account", + "audience": _AUDIENCE_AWS, + "subject_token_type": "urn:ietf:params:aws:token-type:aws4_request", + "token_url": "https://sts.googleapis.com/v1/token", + "service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/{}:generateAccessToken".format( + service_account_info["client_email"] + ), + "credential_source": { + "environment_id": "aws1", + "regional_cred_verification_url": "https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15", + }, + }, + ) From 1fd78c767eae545237b06436bc60bfd9a79faed3 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Wed, 14 Apr 2021 11:22:13 -0700 Subject: [PATCH 400/966] feat: add reauth feature to user credentials (#727) * feat: add reauth support to oauth2 credentials * update --- .../google-auth/google/auth/exceptions.py | 9 + packages/google-auth/google/oauth2/_client.py | 130 +++++-- .../google-auth/google/oauth2/challenges.py | 157 ++++++++ .../google-auth/google/oauth2/credentials.py | 33 +- packages/google-auth/google/oauth2/reauth.py | 341 ++++++++++++++++++ packages/google-auth/noxfile.py | 1 + packages/google-auth/setup.py | 1 + .../google-auth/tests/oauth2/test__client.py | 42 ++- .../tests/oauth2/test_challenges.py | 132 +++++++ .../tests/oauth2/test_credentials.py | 46 ++- .../google-auth/tests/oauth2/test_reauth.py | 308 ++++++++++++++++ 11 files changed, 1152 insertions(+), 48 deletions(-) create mode 100644 packages/google-auth/google/oauth2/challenges.py create mode 100644 packages/google-auth/google/oauth2/reauth.py create mode 100644 packages/google-auth/tests/oauth2/test_challenges.py create mode 100644 packages/google-auth/tests/oauth2/test_reauth.py diff --git a/packages/google-auth/google/auth/exceptions.py b/packages/google-auth/google/auth/exceptions.py index b6f686bbb57c..57f181ea1a40 100644 --- a/packages/google-auth/google/auth/exceptions.py +++ b/packages/google-auth/google/auth/exceptions.py @@ -48,3 +48,12 @@ class ClientCertError(GoogleAuthError): class OAuthError(GoogleAuthError): """Used to indicate an error occurred during an OAuth related HTTP request.""" + + +class ReauthFailError(RefreshError): + """An exception for when reauth failed.""" + + def __init__(self, message=None): + super(ReauthFailError, self).__init__( + "Reauthentication failed. {0}".format(message) + ) diff --git a/packages/google-auth/google/oauth2/_client.py b/packages/google-auth/google/oauth2/_client.py index 4487163295f0..2f4e8474b557 100644 --- a/packages/google-auth/google/oauth2/_client.py +++ b/packages/google-auth/google/oauth2/_client.py @@ -35,29 +35,29 @@ from google.auth import jwt _URLENCODED_CONTENT_TYPE = "application/x-www-form-urlencoded" +_JSON_CONTENT_TYPE = "application/json" _JWT_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer" _REFRESH_GRANT_TYPE = "refresh_token" -def _handle_error_response(response_body): - """"Translates an error response into an exception. +def _handle_error_response(response_data): + """Translates an error response into an exception. Args: - response_body (str): The decoded response data. + response_data (Mapping): The decoded response data. Raises: - google.auth.exceptions.RefreshError + google.auth.exceptions.RefreshError: The errors contained in response_data. """ try: - error_data = json.loads(response_body) error_details = "{}: {}".format( - error_data["error"], error_data.get("error_description") + response_data["error"], response_data.get("error_description") ) # If no details could be extracted, use the response data. except (KeyError, ValueError): - error_details = response_body + error_details = json.dumps(response_data) - raise exceptions.RefreshError(error_details, response_body) + raise exceptions.RefreshError(error_details, response_data) def _parse_expiry(response_data): @@ -78,8 +78,11 @@ def _parse_expiry(response_data): return None -def _token_endpoint_request(request, token_uri, body): +def _token_endpoint_request_no_throw( + request, token_uri, body, access_token=None, use_json=False +): """Makes a request to the OAuth 2.0 authorization server's token endpoint. + This function doesn't throw on response errors. Args: request (google.auth.transport.Request): A callable used to make @@ -87,16 +90,23 @@ def _token_endpoint_request(request, token_uri, body): token_uri (str): The OAuth 2.0 authorizations server's token endpoint URI. body (Mapping[str, str]): The parameters to send in the request body. + access_token (Optional(str)): The access token needed to make the request. + use_json (Optional(bool)): Use urlencoded format or json format for the + content type. The default value is False. Returns: - Mapping[str, str]: The JSON-decoded response data. - - Raises: - google.auth.exceptions.RefreshError: If the token endpoint returned - an error. + Tuple(bool, Mapping[str, str]): A boolean indicating if the request is + successful, and a mapping for the JSON-decoded response data. """ - body = urllib.parse.urlencode(body).encode("utf-8") - headers = {"content-type": _URLENCODED_CONTENT_TYPE} + if use_json: + headers = {"Content-Type": _JSON_CONTENT_TYPE} + body = json.dumps(body).encode("utf-8") + else: + headers = {"Content-Type": _URLENCODED_CONTENT_TYPE} + body = urllib.parse.urlencode(body).encode("utf-8") + + if access_token: + headers["Authorization"] = "Bearer {}".format(access_token) retry = 0 # retry to fetch token for maximum of two times if any internal failure @@ -121,8 +131,38 @@ def _token_endpoint_request(request, token_uri, body): ): retry += 1 continue - _handle_error_response(response_body) + return response.status == http_client.OK, response_data + + return response.status == http_client.OK, response_data + + +def _token_endpoint_request( + request, token_uri, body, access_token=None, use_json=False +): + """Makes a request to the OAuth 2.0 authorization server's token endpoint. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + token_uri (str): The OAuth 2.0 authorizations server's token endpoint + URI. + body (Mapping[str, str]): The parameters to send in the request body. + access_token (Optional(str)): The access token needed to make the request. + use_json (Optional(bool)): Use urlencoded format or json format for the + content type. The default value is False. + + Returns: + Mapping[str, str]: The JSON-decoded response data. + Raises: + google.auth.exceptions.RefreshError: If the token endpoint returned + an error. + """ + response_status_ok, response_data = _token_endpoint_request_no_throw( + request, token_uri, body, access_token=access_token, use_json=use_json + ) + if not response_status_ok: + _handle_error_response(response_data) return response_data @@ -204,8 +244,43 @@ def id_token_jwt_grant(request, token_uri, assertion): return id_token, expiry, response_data +def _handle_refresh_grant_response(response_data, refresh_token): + """Extract tokens from refresh grant response. + + Args: + response_data (Mapping[str, str]): Refresh grant response data. + refresh_token (str): Current refresh token. + + Returns: + Tuple[str, str, Optional[datetime], Mapping[str, str]]: The access token, + refresh token, expiration, and additional data returned by the token + endpoint. If response_data doesn't have refresh token, then the current + refresh token will be returned. + + Raises: + google.auth.exceptions.RefreshError: If the token endpoint returned + an error. + """ + try: + access_token = response_data["access_token"] + except KeyError as caught_exc: + new_exc = exceptions.RefreshError("No access token in response.", response_data) + six.raise_from(new_exc, caught_exc) + + refresh_token = response_data.get("refresh_token", refresh_token) + expiry = _parse_expiry(response_data) + + return access_token, refresh_token, expiry, response_data + + def refresh_grant( - request, token_uri, refresh_token, client_id, client_secret, scopes=None + request, + token_uri, + refresh_token, + client_id, + client_secret, + scopes=None, + rapt_token=None, ): """Implements the OAuth 2.0 refresh token grant. @@ -224,10 +299,11 @@ def refresh_grant( scopes must be authorized for the refresh token. Useful if refresh token has a wild card scope (e.g. 'https://www.googleapis.com/auth/any-api'). + rapt_token (Optional(str)): The reauth Proof Token. Returns: - Tuple[str, Optional[str], Optional[datetime], Mapping[str, str]]: The - access token, new refresh token, expiration, and additional data + Tuple[str, str, Optional[datetime], Mapping[str, str]]: The access + token, new or current refresh token, expiration, and additional data returned by the token endpoint. Raises: @@ -244,16 +320,8 @@ def refresh_grant( } if scopes: body["scope"] = " ".join(scopes) + if rapt_token: + body["rapt"] = rapt_token response_data = _token_endpoint_request(request, token_uri, body) - - try: - access_token = response_data["access_token"] - except KeyError as caught_exc: - new_exc = exceptions.RefreshError("No access token in response.", response_data) - six.raise_from(new_exc, caught_exc) - - refresh_token = response_data.get("refresh_token", refresh_token) - expiry = _parse_expiry(response_data) - - return access_token, refresh_token, expiry, response_data + return _handle_refresh_grant_response(response_data, refresh_token) diff --git a/packages/google-auth/google/oauth2/challenges.py b/packages/google-auth/google/oauth2/challenges.py new file mode 100644 index 000000000000..d0b070eda670 --- /dev/null +++ b/packages/google-auth/google/oauth2/challenges.py @@ -0,0 +1,157 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" Challenges for reauthentication. +""" + +import abc +import base64 +import getpass +import sys + +import six + +from google.auth import _helpers +from google.auth import exceptions + + +REAUTH_ORIGIN = "https://accounts.google.com" + + +def get_user_password(text): + """Get password from user. + + Override this function with a different logic if you are using this library + outside a CLI. + + Args: + text (str): message for the password prompt. + + Returns: + str: password string. + """ + return getpass.getpass(text) + + +@six.add_metaclass(abc.ABCMeta) +class ReauthChallenge(object): + """Base class for reauth challenges.""" + + @property + @abc.abstractmethod + def name(self): # pragma: NO COVER + """Returns the name of the challenge.""" + raise NotImplementedError("name property must be implemented") + + @property + @abc.abstractmethod + def is_locally_eligible(self): # pragma: NO COVER + """Returns true if a challenge is supported locally on this machine.""" + raise NotImplementedError("is_locally_eligible property must be implemented") + + @abc.abstractmethod + def obtain_challenge_input(self, metadata): # pragma: NO COVER + """Performs logic required to obtain credentials and returns it. + + Args: + metadata (Mapping): challenge metadata returned in the 'challenges' field in + the initial reauth request. Includes the 'challengeType' field + and other challenge-specific fields. + + Returns: + response that will be send to the reauth service as the content of + the 'proposalResponse' field in the request body. Usually a dict + with the keys specific to the challenge. For example, + ``{'credential': password}`` for password challenge. + """ + raise NotImplementedError("obtain_challenge_input method must be implemented") + + +class PasswordChallenge(ReauthChallenge): + """Challenge that asks for user's password.""" + + @property + def name(self): + return "PASSWORD" + + @property + def is_locally_eligible(self): + return True + + @_helpers.copy_docstring(ReauthChallenge) + def obtain_challenge_input(self, unused_metadata): + passwd = get_user_password("Please enter your password:") + if not passwd: + passwd = " " # avoid the server crashing in case of no password :D + return {"credential": passwd} + + +class SecurityKeyChallenge(ReauthChallenge): + """Challenge that asks for user's security key touch.""" + + @property + def name(self): + return "SECURITY_KEY" + + @property + def is_locally_eligible(self): + return True + + @_helpers.copy_docstring(ReauthChallenge) + def obtain_challenge_input(self, metadata): + try: + import pyu2f.convenience.authenticator + import pyu2f.errors + import pyu2f.model + except ImportError: + raise exceptions.ReauthFailError( + "pyu2f dependency is required to use Security key reauth feature. " + "It can be installed via `pip install pyu2f` or `pip install google-auth[reauth]`." + ) + sk = metadata["securityKey"] + challenges = sk["challenges"] + app_id = sk["applicationId"] + + challenge_data = [] + for c in challenges: + kh = c["keyHandle"].encode("ascii") + key = pyu2f.model.RegisteredKey(bytearray(base64.urlsafe_b64decode(kh))) + challenge = c["challenge"].encode("ascii") + challenge = base64.urlsafe_b64decode(challenge) + challenge_data.append({"key": key, "challenge": challenge}) + + try: + api = pyu2f.convenience.authenticator.CreateCompositeAuthenticator( + REAUTH_ORIGIN + ) + response = api.Authenticate( + app_id, challenge_data, print_callback=sys.stderr.write + ) + return {"securityKey": response} + except pyu2f.errors.U2FError as e: + if e.code == pyu2f.errors.U2FError.DEVICE_INELIGIBLE: + sys.stderr.write("Ineligible security key.\n") + elif e.code == pyu2f.errors.U2FError.TIMEOUT: + sys.stderr.write("Timed out while waiting for security key touch.\n") + else: + raise e + except pyu2f.errors.NoDeviceFoundError: + sys.stderr.write("No security key found.\n") + return None + + +AVAILABLE_CHALLENGES = { + challenge.name: challenge + for challenge in [SecurityKeyChallenge(), PasswordChallenge()] +} diff --git a/packages/google-auth/google/oauth2/credentials.py b/packages/google-auth/google/oauth2/credentials.py index 464cc4878ca6..dcfa5f91222d 100644 --- a/packages/google-auth/google/oauth2/credentials.py +++ b/packages/google-auth/google/oauth2/credentials.py @@ -41,7 +41,7 @@ from google.auth import _helpers from google.auth import credentials from google.auth import exceptions -from google.oauth2 import _client +from google.oauth2 import reauth # The Google OAuth 2.0 token endpoint. Used for authorized user credentials. @@ -55,6 +55,10 @@ class Credentials(credentials.ReadOnlyScoped, credentials.CredentialsWithQuotaPr quota project, use :meth:`with_quota_project` or :: credentials = credentials.with_quota_project('myproject-123) + + If reauth is enabled, `pyu2f` dependency has to be installed in order to use security + key reauth feature. Dependency can be installed via `pip install pyu2f` or `pip install + google-auth[reauth]`. """ def __init__( @@ -69,6 +73,7 @@ def __init__( default_scopes=None, quota_project_id=None, expiry=None, + rapt_token=None, ): """ Args: @@ -97,6 +102,7 @@ def __init__( quota_project_id (Optional[str]): The project ID used for quota and billing. This project may be different from the project used to create the credentials. + rapt_token (Optional[str]): The reauth Proof Token. """ super(Credentials, self).__init__() self.token = token @@ -109,6 +115,7 @@ def __init__( self._client_id = client_id self._client_secret = client_secret self._quota_project_id = quota_project_id + self._rapt_token = rapt_token def __getstate__(self): """A __getstate__ method must exist for the __setstate__ to be called @@ -130,6 +137,7 @@ def __setstate__(self, d): self._client_id = d.get("_client_id") self._client_secret = d.get("_client_secret") self._quota_project_id = d.get("_quota_project_id") + self._rapt_token = d.get("_rapt_token") @property def refresh_token(self): @@ -174,6 +182,11 @@ def requires_scopes(self): the initial token is requested and can not be changed.""" return False + @property + def rapt_token(self): + """Optional[str]: The reauth Proof Token.""" + return self._rapt_token + @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) def with_quota_project(self, quota_project_id): @@ -187,6 +200,7 @@ def with_quota_project(self, quota_project_id): scopes=self.scopes, default_scopes=self.default_scopes, quota_project_id=quota_project_id, + rapt_token=self.rapt_token, ) @_helpers.copy_docstring(credentials.Credentials) @@ -205,23 +219,31 @@ def refresh(self, request): scopes = self._scopes if self._scopes is not None else self._default_scopes - access_token, refresh_token, expiry, grant_response = _client.refresh_grant( + ( + access_token, + refresh_token, + expiry, + grant_response, + rapt_token, + ) = reauth.refresh_grant( request, self._token_uri, self._refresh_token, self._client_id, self._client_secret, - scopes, + scopes=scopes, + rapt_token=self._rapt_token, ) self.token = access_token self.expiry = expiry self._refresh_token = refresh_token self._id_token = grant_response.get("id_token") + self._rapt_token = rapt_token - if scopes and "scopes" in grant_response: + if scopes and "scope" in grant_response: requested_scopes = frozenset(scopes) - granted_scopes = frozenset(grant_response["scopes"].split()) + granted_scopes = frozenset(grant_response["scope"].split()) scopes_requested_but_not_granted = requested_scopes - granted_scopes if scopes_requested_but_not_granted: raise exceptions.RefreshError( @@ -323,6 +345,7 @@ def to_json(self, strip=None): "client_id": self.client_id, "client_secret": self.client_secret, "scopes": self.scopes, + "rapt_token": self.rapt_token, } if self.expiry: # flatten expiry timestamp prep["expiry"] = self.expiry.isoformat() + "Z" diff --git a/packages/google-auth/google/oauth2/reauth.py b/packages/google-auth/google/oauth2/reauth.py new file mode 100644 index 000000000000..d539d7c9e14c --- /dev/null +++ b/packages/google-auth/google/oauth2/reauth.py @@ -0,0 +1,341 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""A module that provides functions for handling rapt authentication. + +Reauth is a process of obtaining additional authentication (such as password, +security token, etc.) while refreshing OAuth 2.0 credentials for a user. + +Credentials that use the Reauth flow must have the reauth scope, +``https://www.googleapis.com/auth/accounts.reauth``. + +This module provides a high-level function for executing the Reauth process, +:func:`refresh_grant`, and lower-level helpers for doing the individual +steps of the reauth process. + +Those steps are: + +1. Obtaining a list of challenges from the reauth server. +2. Running through each challenge and sending the result back to the reauth + server. +3. Refreshing the access token using the returned rapt token. +""" + +import sys + +from six.moves import range + +from google.auth import exceptions +from google.oauth2 import _client +from google.oauth2 import challenges + + +_REAUTH_SCOPE = "https://www.googleapis.com/auth/accounts.reauth" +_REAUTH_API = "https://reauth.googleapis.com/v2/sessions" + +_REAUTH_NEEDED_ERROR = "invalid_grant" +_REAUTH_NEEDED_ERROR_INVALID_RAPT = "invalid_rapt" +_REAUTH_NEEDED_ERROR_RAPT_REQUIRED = "rapt_required" + +_AUTHENTICATED = "AUTHENTICATED" +_CHALLENGE_REQUIRED = "CHALLENGE_REQUIRED" +_CHALLENGE_PENDING = "CHALLENGE_PENDING" + + +# Override this global variable to set custom max number of rounds of reauth +# challenges should be run. +RUN_CHALLENGE_RETRY_LIMIT = 5 + + +def is_interactive(): + """Check if we are in an interractive environment. + + Override this function with a different logic if you are using this library + outside a CLI. + + If the rapt token needs refreshing, the user needs to answer the challenges. + If the user is not in an interractive environment, the challenges can not + be answered and we just wait for timeout for no reason. + + Returns: + bool: True if is interactive environment, False otherwise. + """ + + return sys.stdin.isatty() + + +def _get_challenges( + request, supported_challenge_types, access_token, requested_scopes=None +): + """Does initial request to reauth API to get the challenges. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + supported_challenge_types (Sequence[str]): list of challenge names + supported by the manager. + access_token (str): Access token with reauth scopes. + requested_scopes (Optional(Sequence[str])): Authorized scopes for the credentials. + + Returns: + dict: The response from the reauth API. + """ + body = {"supportedChallengeTypes": supported_challenge_types} + if requested_scopes: + body["oauthScopesForDomainPolicyLookup"] = requested_scopes + + return _client._token_endpoint_request( + request, _REAUTH_API + ":start", body, access_token=access_token, use_json=True + ) + + +def _send_challenge_result( + request, session_id, challenge_id, client_input, access_token +): + """Attempt to refresh access token by sending next challenge result. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + session_id (str): session id returned by the initial reauth call. + challenge_id (str): challenge id returned by the initial reauth call. + client_input: dict with a challenge-specific client input. For example: + ``{'credential': password}`` for password challenge. + access_token (str): Access token with reauth scopes. + + Returns: + dict: The response from the reauth API. + """ + body = { + "sessionId": session_id, + "challengeId": challenge_id, + "action": "RESPOND", + "proposalResponse": client_input, + } + + return _client._token_endpoint_request( + request, + _REAUTH_API + "/{}:continue".format(session_id), + body, + access_token=access_token, + use_json=True, + ) + + +def _run_next_challenge(msg, request, access_token): + """Get the next challenge from msg and run it. + + Args: + msg (dict): Reauth API response body (either from the initial request to + https://reauth.googleapis.com/v2/sessions:start or from sending the + previous challenge response to + https://reauth.googleapis.com/v2/sessions/id:continue) + request (google.auth.transport.Request): A callable used to make + HTTP requests. + access_token (str): reauth access token + + Returns: + dict: The response from the reauth API. + + Raises: + google.auth.exceptions.ReauthError: if reauth failed. + """ + for challenge in msg["challenges"]: + if challenge["status"] != "READY": + # Skip non-activated challenges. + continue + c = challenges.AVAILABLE_CHALLENGES.get(challenge["challengeType"], None) + if not c: + raise exceptions.ReauthFailError( + "Unsupported challenge type {0}. Supported types: {1}".format( + challenge["challengeType"], + ",".join(list(challenges.AVAILABLE_CHALLENGES.keys())), + ) + ) + if not c.is_locally_eligible: + raise exceptions.ReauthFailError( + "Challenge {0} is not locally eligible".format( + challenge["challengeType"] + ) + ) + client_input = c.obtain_challenge_input(challenge) + if not client_input: + return None + return _send_challenge_result( + request, + msg["sessionId"], + challenge["challengeId"], + client_input, + access_token, + ) + return None + + +def _obtain_rapt(request, access_token, requested_scopes): + """Given an http request method and reauth access token, get rapt token. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + access_token (str): reauth access token + requested_scopes (Sequence[str]): scopes required by the client application + + Returns: + str: The rapt token. + + Raises: + google.auth.exceptions.ReauthError: if reauth failed + """ + msg = _get_challenges( + request, + list(challenges.AVAILABLE_CHALLENGES.keys()), + access_token, + requested_scopes, + ) + + if msg["status"] == _AUTHENTICATED: + return msg["encodedProofOfReauthToken"] + + for _ in range(0, RUN_CHALLENGE_RETRY_LIMIT): + if not ( + msg["status"] == _CHALLENGE_REQUIRED or msg["status"] == _CHALLENGE_PENDING + ): + raise exceptions.ReauthFailError( + "Reauthentication challenge failed due to API error: {}".format( + msg["status"] + ) + ) + + if not is_interactive(): + raise exceptions.ReauthFailError( + "Reauthentication challenge could not be answered because you are not" + " in an interactive session." + ) + + msg = _run_next_challenge(msg, request, access_token) + + if msg["status"] == _AUTHENTICATED: + return msg["encodedProofOfReauthToken"] + + # If we got here it means we didn't get authenticated. + raise exceptions.ReauthFailError("Failed to obtain rapt token.") + + +def get_rapt_token( + request, client_id, client_secret, refresh_token, token_uri, scopes=None +): + """Given an http request method and refresh_token, get rapt token. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + client_id (str): client id to get access token for reauth scope. + client_secret (str): client secret for the client_id + refresh_token (str): refresh token to refresh access token + token_uri (str): uri to refresh access token + scopes (Optional(Sequence[str])): scopes required by the client application + + Returns: + str: The rapt token. + Raises: + google.auth.exceptions.RefreshError: If reauth failed. + """ + sys.stderr.write("Reauthentication required.\n") + + # Get access token for reauth. + access_token, _, _, _ = _client.refresh_grant( + request=request, + client_id=client_id, + client_secret=client_secret, + refresh_token=refresh_token, + token_uri=token_uri, + scopes=[_REAUTH_SCOPE], + ) + + # Get rapt token from reauth API. + rapt_token = _obtain_rapt(request, access_token, requested_scopes=scopes) + + return rapt_token + + +def refresh_grant( + request, + token_uri, + refresh_token, + client_id, + client_secret, + scopes=None, + rapt_token=None, +): + """Implements the reauthentication flow. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + token_uri (str): The OAuth 2.0 authorizations server's token endpoint + URI. + refresh_token (str): The refresh token to use to get a new access + token. + client_id (str): The OAuth 2.0 application's client ID. + client_secret (str): The Oauth 2.0 appliaction's client secret. + scopes (Optional(Sequence[str])): Scopes to request. If present, all + scopes must be authorized for the refresh token. Useful if refresh + token has a wild card scope (e.g. + 'https://www.googleapis.com/auth/any-api'). + rapt_token (Optional(str)): The rapt token for reauth. + + Returns: + Tuple[str, Optional[str], Optional[datetime], Mapping[str, str]]: The + access token, new refresh token, expiration, and additional data + returned by the token endpoint. + + Raises: + google.auth.exceptions.RefreshError: If the token endpoint returned + an error. + """ + body = { + "grant_type": _client._REFRESH_GRANT_TYPE, + "client_id": client_id, + "client_secret": client_secret, + "refresh_token": refresh_token, + } + if scopes: + body["scope"] = " ".join(scopes) + if rapt_token: + body["rapt"] = rapt_token + + response_status_ok, response_data = _client._token_endpoint_request_no_throw( + request, token_uri, body + ) + if ( + not response_status_ok + and response_data.get("error") == _REAUTH_NEEDED_ERROR + and ( + response_data.get("error_subtype") == _REAUTH_NEEDED_ERROR_INVALID_RAPT + or response_data.get("error_subtype") == _REAUTH_NEEDED_ERROR_RAPT_REQUIRED + ) + ): + rapt_token = get_rapt_token( + request, client_id, client_secret, refresh_token, token_uri, scopes=scopes + ) + body["rapt"] = rapt_token + (response_status_ok, response_data) = _client._token_endpoint_request_no_throw( + request, token_uri, body + ) + + if not response_status_ok: + _client._handle_error_response(response_data) + return _client._handle_refresh_grant_response(response_data, refresh_token) + ( + rapt_token, + ) diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index 3b4863c2ded2..0bd7f6c6c40f 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -25,6 +25,7 @@ "pytest", "pytest-cov", "pytest-localserver", + "pyu2f", "requests", "urllib3", "cryptography", diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 16ba98cfd626..ef723f8af512 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -33,6 +33,7 @@ extras = { "aiohttp": "aiohttp >= 3.6.2, < 4.0.0dev; python_version>='3.6'", "pyopenssl": "pyopenssl>=20.0.0", + "reauth": "pyu2f>=0.1.5", } with io.open("README.rst", "r") as fh: diff --git a/packages/google-auth/tests/oauth2/test__client.py b/packages/google-auth/tests/oauth2/test__client.py index c3ae2af98864..54686df594bd 100644 --- a/packages/google-auth/tests/oauth2/test__client.py +++ b/packages/google-auth/tests/oauth2/test__client.py @@ -48,7 +48,7 @@ def test__handle_error_response(): - response_data = json.dumps({"error": "help", "error_description": "I'm alive"}) + response_data = {"error": "help", "error_description": "I'm alive"} with pytest.raises(exceptions.RefreshError) as excinfo: _client._handle_error_response(response_data) @@ -57,12 +57,12 @@ def test__handle_error_response(): def test__handle_error_response_non_json(): - response_data = "Help, I'm alive" + response_data = {"foo": "bar"} with pytest.raises(exceptions.RefreshError) as excinfo: _client._handle_error_response(response_data) - assert excinfo.match(r"Help, I\'m alive") + assert excinfo.match(r"{\"foo\": \"bar\"}") @mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) @@ -95,7 +95,7 @@ def test__token_endpoint_request(): request.assert_called_with( method="POST", url="http://example.com", - headers={"content-type": "application/x-www-form-urlencoded"}, + headers={"Content-Type": "application/x-www-form-urlencoded"}, body="test=params".encode("utf-8"), ) @@ -103,6 +103,32 @@ def test__token_endpoint_request(): assert result == {"test": "response"} +def test__token_endpoint_request_use_json(): + request = make_request({"test": "response"}) + + result = _client._token_endpoint_request( + request, + "http://example.com", + {"test": "params"}, + access_token="access_token", + use_json=True, + ) + + # Check request call + request.assert_called_with( + method="POST", + url="http://example.com", + headers={ + "Content-Type": "application/json", + "Authorization": "Bearer access_token", + }, + body=b'{"test": "params"}', + ) + + # Check result + assert result == {"test": "response"} + + def test__token_endpoint_request_error(): request = make_request({}, status=http_client.BAD_REQUEST) @@ -220,7 +246,12 @@ def test_refresh_grant(unused_utcnow): ) token, refresh_token, expiry, extra_data = _client.refresh_grant( - request, "http://example.com", "refresh_token", "client_id", "client_secret" + request, + "http://example.com", + "refresh_token", + "client_id", + "client_secret", + rapt_token="rapt_token", ) # Check request call @@ -231,6 +262,7 @@ def test_refresh_grant(unused_utcnow): "refresh_token": "refresh_token", "client_id": "client_id", "client_secret": "client_secret", + "rapt": "rapt_token", }, ) diff --git a/packages/google-auth/tests/oauth2/test_challenges.py b/packages/google-auth/tests/oauth2/test_challenges.py new file mode 100644 index 000000000000..019b908dae37 --- /dev/null +++ b/packages/google-auth/tests/oauth2/test_challenges.py @@ -0,0 +1,132 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for the reauth module.""" + +import base64 +import sys + +import mock +import pytest +import pyu2f + +from google.auth import exceptions +from google.oauth2 import challenges + + +def test_get_user_password(): + with mock.patch("getpass.getpass", return_value="foo"): + assert challenges.get_user_password("") == "foo" + + +def test_security_key(): + metadata = { + "status": "READY", + "challengeId": 2, + "challengeType": "SECURITY_KEY", + "securityKey": { + "applicationId": "security_key_application_id", + "challenges": [ + { + "keyHandle": "some_key", + "challenge": base64.urlsafe_b64encode( + "some_challenge".encode("ascii") + ).decode("ascii"), + } + ], + }, + } + mock_key = mock.Mock() + + challenge = challenges.SecurityKeyChallenge() + + # Test the case that security key challenge is passed. + with mock.patch("pyu2f.model.RegisteredKey", return_value=mock_key): + with mock.patch( + "pyu2f.convenience.authenticator.CompositeAuthenticator.Authenticate" + ) as mock_authenticate: + mock_authenticate.return_value = "security key response" + assert challenge.name == "SECURITY_KEY" + assert challenge.is_locally_eligible + assert challenge.obtain_challenge_input(metadata) == { + "securityKey": "security key response" + } + mock_authenticate.assert_called_with( + "security_key_application_id", + [{"key": mock_key, "challenge": b"some_challenge"}], + print_callback=sys.stderr.write, + ) + + # Test various types of exceptions. + with mock.patch("pyu2f.model.RegisteredKey", return_value=mock_key): + with mock.patch( + "pyu2f.convenience.authenticator.CompositeAuthenticator.Authenticate" + ) as mock_authenticate: + mock_authenticate.side_effect = pyu2f.errors.U2FError( + pyu2f.errors.U2FError.DEVICE_INELIGIBLE + ) + assert challenge.obtain_challenge_input(metadata) is None + + with mock.patch( + "pyu2f.convenience.authenticator.CompositeAuthenticator.Authenticate" + ) as mock_authenticate: + mock_authenticate.side_effect = pyu2f.errors.U2FError( + pyu2f.errors.U2FError.TIMEOUT + ) + assert challenge.obtain_challenge_input(metadata) is None + + with mock.patch( + "pyu2f.convenience.authenticator.CompositeAuthenticator.Authenticate" + ) as mock_authenticate: + mock_authenticate.side_effect = pyu2f.errors.U2FError( + pyu2f.errors.U2FError.BAD_REQUEST + ) + with pytest.raises(pyu2f.errors.U2FError): + challenge.obtain_challenge_input(metadata) + + with mock.patch( + "pyu2f.convenience.authenticator.CompositeAuthenticator.Authenticate" + ) as mock_authenticate: + mock_authenticate.side_effect = pyu2f.errors.NoDeviceFoundError() + assert challenge.obtain_challenge_input(metadata) is None + + with mock.patch( + "pyu2f.convenience.authenticator.CompositeAuthenticator.Authenticate" + ) as mock_authenticate: + mock_authenticate.side_effect = pyu2f.errors.UnsupportedVersionException() + with pytest.raises(pyu2f.errors.UnsupportedVersionException): + challenge.obtain_challenge_input(metadata) + + with mock.patch.dict("sys.modules"): + sys.modules["pyu2f"] = None + with pytest.raises(exceptions.ReauthFailError) as excinfo: + challenge.obtain_challenge_input(metadata) + assert excinfo.match(r"pyu2f dependency is required") + + +@mock.patch("getpass.getpass", return_value="foo") +def test_password_challenge(getpass_mock): + challenge = challenges.PasswordChallenge() + + with mock.patch("getpass.getpass", return_value="foo"): + assert challenge.is_locally_eligible + assert challenge.name == "PASSWORD" + assert challenges.PasswordChallenge().obtain_challenge_input({}) == { + "credential": "foo" + } + + with mock.patch("getpass.getpass", return_value=None): + assert challenges.PasswordChallenge().obtain_challenge_input({}) == { + "credential": " " + } diff --git a/packages/google-auth/tests/oauth2/test_credentials.py b/packages/google-auth/tests/oauth2/test_credentials.py index b885d2973db9..4a387a58e068 100644 --- a/packages/google-auth/tests/oauth2/test_credentials.py +++ b/packages/google-auth/tests/oauth2/test_credentials.py @@ -38,6 +38,7 @@ class TestCredentials(object): TOKEN_URI = "https://example.com/oauth2/token" REFRESH_TOKEN = "refresh_token" + RAPT_TOKEN = "rapt_token" CLIENT_ID = "client_id" CLIENT_SECRET = "client_secret" @@ -49,6 +50,7 @@ def make_credentials(cls): token_uri=cls.TOKEN_URI, client_id=cls.CLIENT_ID, client_secret=cls.CLIENT_SECRET, + rapt_token=cls.RAPT_TOKEN, ) def test_default_state(self): @@ -63,14 +65,16 @@ def test_default_state(self): assert credentials.token_uri == self.TOKEN_URI assert credentials.client_id == self.CLIENT_ID assert credentials.client_secret == self.CLIENT_SECRET + assert credentials.rapt_token == self.RAPT_TOKEN - @mock.patch("google.oauth2._client.refresh_grant", autospec=True) + @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", return_value=datetime.datetime.min + _helpers.CLOCK_SKEW, ) def test_refresh_success(self, unused_utcnow, refresh_grant): token = "token" + new_rapt_token = "new_rapt_token" expiry = _helpers.utcnow() + datetime.timedelta(seconds=500) grant_response = {"id_token": mock.sentinel.id_token} refresh_grant.return_value = ( @@ -82,6 +86,8 @@ def test_refresh_success(self, unused_utcnow, refresh_grant): expiry, # Extra data grant_response, + # rapt_token + new_rapt_token, ) request = mock.create_autospec(transport.Request) @@ -98,12 +104,14 @@ def test_refresh_success(self, unused_utcnow, refresh_grant): self.CLIENT_ID, self.CLIENT_SECRET, None, + self.RAPT_TOKEN, ) # Check that the credentials have the token and expiry assert credentials.token == token assert credentials.expiry == expiry assert credentials.id_token == mock.sentinel.id_token + assert credentials.rapt_token == new_rapt_token # Check that the credentials are valid (have a token and are not # expired) @@ -118,7 +126,7 @@ def test_refresh_no_refresh_token(self): request.assert_not_called() - @mock.patch("google.oauth2._client.refresh_grant", autospec=True) + @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", return_value=datetime.datetime.min + _helpers.CLOCK_SKEW, @@ -129,8 +137,9 @@ def test_credentials_with_scopes_requested_refresh_success( scopes = ["email", "profile"] default_scopes = ["https://www.googleapis.com/auth/cloud-platform"] token = "token" + new_rapt_token = "new_rapt_token" expiry = _helpers.utcnow() + datetime.timedelta(seconds=500) - grant_response = {"id_token": mock.sentinel.id_token} + grant_response = {"id_token": mock.sentinel.id_token, "scope": "email profile"} refresh_grant.return_value = ( # Access token token, @@ -140,6 +149,8 @@ def test_credentials_with_scopes_requested_refresh_success( expiry, # Extra data grant_response, + # rapt token + new_rapt_token, ) request = mock.create_autospec(transport.Request) @@ -151,6 +162,7 @@ def test_credentials_with_scopes_requested_refresh_success( client_secret=self.CLIENT_SECRET, scopes=scopes, default_scopes=default_scopes, + rapt_token=self.RAPT_TOKEN, ) # Refresh credentials @@ -164,6 +176,7 @@ def test_credentials_with_scopes_requested_refresh_success( self.CLIENT_ID, self.CLIENT_SECRET, scopes, + self.RAPT_TOKEN, ) # Check that the credentials have the token and expiry @@ -171,12 +184,13 @@ def test_credentials_with_scopes_requested_refresh_success( assert creds.expiry == expiry assert creds.id_token == mock.sentinel.id_token assert creds.has_scopes(scopes) + assert creds.rapt_token == new_rapt_token # Check that the credentials are valid (have a token and are not # expired.) assert creds.valid - @mock.patch("google.oauth2._client.refresh_grant", autospec=True) + @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", return_value=datetime.datetime.min + _helpers.CLOCK_SKEW, @@ -186,6 +200,7 @@ def test_credentials_with_only_default_scopes_requested( ): default_scopes = ["email", "profile"] token = "token" + new_rapt_token = "new_rapt_token" expiry = _helpers.utcnow() + datetime.timedelta(seconds=500) grant_response = {"id_token": mock.sentinel.id_token} refresh_grant.return_value = ( @@ -197,6 +212,8 @@ def test_credentials_with_only_default_scopes_requested( expiry, # Extra data grant_response, + # rapt token + new_rapt_token, ) request = mock.create_autospec(transport.Request) @@ -207,6 +224,7 @@ def test_credentials_with_only_default_scopes_requested( client_id=self.CLIENT_ID, client_secret=self.CLIENT_SECRET, default_scopes=default_scopes, + rapt_token=self.RAPT_TOKEN, ) # Refresh credentials @@ -220,6 +238,7 @@ def test_credentials_with_only_default_scopes_requested( self.CLIENT_ID, self.CLIENT_SECRET, default_scopes, + self.RAPT_TOKEN, ) # Check that the credentials have the token and expiry @@ -227,12 +246,13 @@ def test_credentials_with_only_default_scopes_requested( assert creds.expiry == expiry assert creds.id_token == mock.sentinel.id_token assert creds.has_scopes(default_scopes) + assert creds.rapt_token == new_rapt_token # Check that the credentials are valid (have a token and are not # expired.) assert creds.valid - @mock.patch("google.oauth2._client.refresh_grant", autospec=True) + @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", return_value=datetime.datetime.min + _helpers.CLOCK_SKEW, @@ -242,6 +262,7 @@ def test_credentials_with_scopes_returned_refresh_success( ): scopes = ["email", "profile"] token = "token" + new_rapt_token = "new_rapt_token" expiry = _helpers.utcnow() + datetime.timedelta(seconds=500) grant_response = { "id_token": mock.sentinel.id_token, @@ -256,6 +277,8 @@ def test_credentials_with_scopes_returned_refresh_success( expiry, # Extra data grant_response, + # rapt token + new_rapt_token, ) request = mock.create_autospec(transport.Request) @@ -266,6 +289,7 @@ def test_credentials_with_scopes_returned_refresh_success( client_id=self.CLIENT_ID, client_secret=self.CLIENT_SECRET, scopes=scopes, + rapt_token=self.RAPT_TOKEN, ) # Refresh credentials @@ -279,6 +303,7 @@ def test_credentials_with_scopes_returned_refresh_success( self.CLIENT_ID, self.CLIENT_SECRET, scopes, + self.RAPT_TOKEN, ) # Check that the credentials have the token and expiry @@ -286,12 +311,13 @@ def test_credentials_with_scopes_returned_refresh_success( assert creds.expiry == expiry assert creds.id_token == mock.sentinel.id_token assert creds.has_scopes(scopes) + assert creds.rapt_token == new_rapt_token # Check that the credentials are valid (have a token and are not # expired.) assert creds.valid - @mock.patch("google.oauth2._client.refresh_grant", autospec=True) + @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", return_value=datetime.datetime.min + _helpers.CLOCK_SKEW, @@ -302,10 +328,11 @@ def test_credentials_with_scopes_refresh_failure_raises_refresh_error( scopes = ["email", "profile"] scopes_returned = ["email"] token = "token" + new_rapt_token = "new_rapt_token" expiry = _helpers.utcnow() + datetime.timedelta(seconds=500) grant_response = { "id_token": mock.sentinel.id_token, - "scopes": " ".join(scopes_returned), + "scope": " ".join(scopes_returned), } refresh_grant.return_value = ( # Access token @@ -316,6 +343,8 @@ def test_credentials_with_scopes_refresh_failure_raises_refresh_error( expiry, # Extra data grant_response, + # rapt token + new_rapt_token, ) request = mock.create_autospec(transport.Request) @@ -326,6 +355,7 @@ def test_credentials_with_scopes_refresh_failure_raises_refresh_error( client_id=self.CLIENT_ID, client_secret=self.CLIENT_SECRET, scopes=scopes, + rapt_token=self.RAPT_TOKEN, ) # Refresh credentials @@ -342,6 +372,7 @@ def test_credentials_with_scopes_refresh_failure_raises_refresh_error( self.CLIENT_ID, self.CLIENT_SECRET, scopes, + self.RAPT_TOKEN, ) # Check that the credentials have the token and expiry @@ -349,6 +380,7 @@ def test_credentials_with_scopes_refresh_failure_raises_refresh_error( assert creds.expiry == expiry assert creds.id_token == mock.sentinel.id_token assert creds.has_scopes(scopes) + assert creds.rapt_token == new_rapt_token # Check that the credentials are valid (have a token and are not # expired.) diff --git a/packages/google-auth/tests/oauth2/test_reauth.py b/packages/google-auth/tests/oauth2/test_reauth.py new file mode 100644 index 000000000000..e9ffa8a79e8f --- /dev/null +++ b/packages/google-auth/tests/oauth2/test_reauth.py @@ -0,0 +1,308 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import copy + +import mock +import pytest + +from google.auth import exceptions +from google.oauth2 import reauth + + +MOCK_REQUEST = mock.Mock() +CHALLENGES_RESPONSE_TEMPLATE = { + "status": "CHALLENGE_REQUIRED", + "sessionId": "123", + "challenges": [ + { + "status": "READY", + "challengeId": 1, + "challengeType": "PASSWORD", + "securityKey": {}, + } + ], +} +CHALLENGES_RESPONSE_AUTHENTICATED = { + "status": "AUTHENTICATED", + "sessionId": "123", + "encodedProofOfReauthToken": "new_rapt_token", +} + + +class MockChallenge(object): + def __init__(self, name, locally_eligible, challenge_input): + self.name = name + self.is_locally_eligible = locally_eligible + self.challenge_input = challenge_input + + def obtain_challenge_input(self, metadata): + return self.challenge_input + + +def test_is_interactive(): + with mock.patch("sys.stdin.isatty", return_value=True): + assert reauth.is_interactive() + + +def test__get_challenges(): + with mock.patch( + "google.oauth2._client._token_endpoint_request" + ) as mock_token_endpoint_request: + reauth._get_challenges(MOCK_REQUEST, ["SAML"], "token") + mock_token_endpoint_request.assert_called_with( + MOCK_REQUEST, + reauth._REAUTH_API + ":start", + {"supportedChallengeTypes": ["SAML"]}, + access_token="token", + use_json=True, + ) + + +def test__get_challenges_with_scopes(): + with mock.patch( + "google.oauth2._client._token_endpoint_request" + ) as mock_token_endpoint_request: + reauth._get_challenges( + MOCK_REQUEST, ["SAML"], "token", requested_scopes=["scope"] + ) + mock_token_endpoint_request.assert_called_with( + MOCK_REQUEST, + reauth._REAUTH_API + ":start", + { + "supportedChallengeTypes": ["SAML"], + "oauthScopesForDomainPolicyLookup": ["scope"], + }, + access_token="token", + use_json=True, + ) + + +def test__send_challenge_result(): + with mock.patch( + "google.oauth2._client._token_endpoint_request" + ) as mock_token_endpoint_request: + reauth._send_challenge_result( + MOCK_REQUEST, "123", "1", {"credential": "password"}, "token" + ) + mock_token_endpoint_request.assert_called_with( + MOCK_REQUEST, + reauth._REAUTH_API + "/123:continue", + { + "sessionId": "123", + "challengeId": "1", + "action": "RESPOND", + "proposalResponse": {"credential": "password"}, + }, + access_token="token", + use_json=True, + ) + + +def test__run_next_challenge_not_ready(): + challenges_response = copy.deepcopy(CHALLENGES_RESPONSE_TEMPLATE) + challenges_response["challenges"][0]["status"] = "STATUS_UNSPECIFIED" + assert ( + reauth._run_next_challenge(challenges_response, MOCK_REQUEST, "token") is None + ) + + +def test__run_next_challenge_not_supported(): + challenges_response = copy.deepcopy(CHALLENGES_RESPONSE_TEMPLATE) + challenges_response["challenges"][0]["challengeType"] = "CHALLENGE_TYPE_UNSPECIFIED" + with pytest.raises(exceptions.ReauthFailError) as excinfo: + reauth._run_next_challenge(challenges_response, MOCK_REQUEST, "token") + assert excinfo.match(r"Unsupported challenge type CHALLENGE_TYPE_UNSPECIFIED") + + +def test__run_next_challenge_not_locally_eligible(): + mock_challenge = MockChallenge("PASSWORD", False, "challenge_input") + with mock.patch( + "google.oauth2.challenges.AVAILABLE_CHALLENGES", {"PASSWORD": mock_challenge} + ): + with pytest.raises(exceptions.ReauthFailError) as excinfo: + reauth._run_next_challenge( + CHALLENGES_RESPONSE_TEMPLATE, MOCK_REQUEST, "token" + ) + assert excinfo.match(r"Challenge PASSWORD is not locally eligible") + + +def test__run_next_challenge_no_challenge_input(): + mock_challenge = MockChallenge("PASSWORD", True, None) + with mock.patch( + "google.oauth2.challenges.AVAILABLE_CHALLENGES", {"PASSWORD": mock_challenge} + ): + assert ( + reauth._run_next_challenge( + CHALLENGES_RESPONSE_TEMPLATE, MOCK_REQUEST, "token" + ) + is None + ) + + +def test__run_next_challenge_success(): + mock_challenge = MockChallenge("PASSWORD", True, {"credential": "password"}) + with mock.patch( + "google.oauth2.challenges.AVAILABLE_CHALLENGES", {"PASSWORD": mock_challenge} + ): + with mock.patch( + "google.oauth2.reauth._send_challenge_result" + ) as mock_send_challenge_result: + reauth._run_next_challenge( + CHALLENGES_RESPONSE_TEMPLATE, MOCK_REQUEST, "token" + ) + mock_send_challenge_result.assert_called_with( + MOCK_REQUEST, "123", 1, {"credential": "password"}, "token" + ) + + +def test__obtain_rapt_authenticated(): + with mock.patch( + "google.oauth2.reauth._get_challenges", + return_value=CHALLENGES_RESPONSE_AUTHENTICATED, + ): + assert reauth._obtain_rapt(MOCK_REQUEST, "token", None) == "new_rapt_token" + + +def test__obtain_rapt_authenticated_after_run_next_challenge(): + with mock.patch( + "google.oauth2.reauth._get_challenges", + return_value=CHALLENGES_RESPONSE_TEMPLATE, + ): + with mock.patch( + "google.oauth2.reauth._run_next_challenge", + side_effect=[ + CHALLENGES_RESPONSE_TEMPLATE, + CHALLENGES_RESPONSE_AUTHENTICATED, + ], + ): + with mock.patch("google.oauth2.reauth.is_interactive", return_value=True): + assert ( + reauth._obtain_rapt(MOCK_REQUEST, "token", None) == "new_rapt_token" + ) + + +def test__obtain_rapt_unsupported_status(): + challenges_response = copy.deepcopy(CHALLENGES_RESPONSE_TEMPLATE) + challenges_response["status"] = "STATUS_UNSPECIFIED" + with mock.patch( + "google.oauth2.reauth._get_challenges", return_value=challenges_response + ): + with pytest.raises(exceptions.ReauthFailError) as excinfo: + reauth._obtain_rapt(MOCK_REQUEST, "token", None) + assert excinfo.match(r"API error: STATUS_UNSPECIFIED") + + +def test__obtain_rapt_not_interactive(): + with mock.patch( + "google.oauth2.reauth._get_challenges", + return_value=CHALLENGES_RESPONSE_TEMPLATE, + ): + with mock.patch("google.oauth2.reauth.is_interactive", return_value=False): + with pytest.raises(exceptions.ReauthFailError) as excinfo: + reauth._obtain_rapt(MOCK_REQUEST, "token", None) + assert excinfo.match(r"not in an interactive session") + + +def test__obtain_rapt_not_authenticated(): + with mock.patch( + "google.oauth2.reauth._get_challenges", + return_value=CHALLENGES_RESPONSE_TEMPLATE, + ): + with mock.patch("google.oauth2.reauth.RUN_CHALLENGE_RETRY_LIMIT", 0): + with pytest.raises(exceptions.ReauthFailError) as excinfo: + reauth._obtain_rapt(MOCK_REQUEST, "token", None) + assert excinfo.match(r"Reauthentication failed") + + +def test_get_rapt_token(): + with mock.patch( + "google.oauth2._client.refresh_grant", return_value=("token", None, None, None) + ) as mock_refresh_grant: + with mock.patch( + "google.oauth2.reauth._obtain_rapt", return_value="new_rapt_token" + ) as mock_obtain_rapt: + assert ( + reauth.get_rapt_token( + MOCK_REQUEST, + "client_id", + "client_secret", + "refresh_token", + "token_uri", + ) + == "new_rapt_token" + ) + mock_refresh_grant.assert_called_with( + request=MOCK_REQUEST, + client_id="client_id", + client_secret="client_secret", + refresh_token="refresh_token", + token_uri="token_uri", + scopes=[reauth._REAUTH_SCOPE], + ) + mock_obtain_rapt.assert_called_with( + MOCK_REQUEST, "token", requested_scopes=None + ) + + +def test_refresh_grant_failed(): + with mock.patch( + "google.oauth2._client._token_endpoint_request_no_throw" + ) as mock_token_request: + mock_token_request.return_value = (False, {"error": "Bad request"}) + with pytest.raises(exceptions.RefreshError) as excinfo: + reauth.refresh_grant( + MOCK_REQUEST, + "token_uri", + "refresh_token", + "client_id", + "client_secret", + scopes=["foo", "bar"], + rapt_token="rapt_token", + ) + assert excinfo.match(r"Bad request") + mock_token_request.assert_called_with( + MOCK_REQUEST, + "token_uri", + { + "grant_type": "refresh_token", + "client_id": "client_id", + "client_secret": "client_secret", + "refresh_token": "refresh_token", + "scope": "foo bar", + "rapt": "rapt_token", + }, + ) + + +def test_refresh_grant_success(): + with mock.patch( + "google.oauth2._client._token_endpoint_request_no_throw" + ) as mock_token_request: + mock_token_request.side_effect = [ + (False, {"error": "invalid_grant", "error_subtype": "rapt_required"}), + (True, {"access_token": "access_token"}), + ] + with mock.patch( + "google.oauth2.reauth.get_rapt_token", return_value="new_rapt_token" + ): + assert reauth.refresh_grant( + MOCK_REQUEST, "token_uri", "refresh_token", "client_id", "client_secret" + ) == ( + "access_token", + "refresh_token", + None, + {"access_token": "access_token"}, + "new_rapt_token", + ) From 0cae0180c0079ca654d2f45487c06f2259a5be3f Mon Sep 17 00:00:00 2001 From: Jonathan Beaulieu <123.jonathan@gmail.com> Date: Thu, 15 Apr 2021 04:28:04 -0400 Subject: [PATCH 401/966] fix: Allow multiple audiences for id_token.verify_token (#733) * feat: Allow multiple audiences for id_token.verify_token (#732) * running black Co-authored-by: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> --- packages/google-auth/google/auth/jwt.py | 11 +++++++---- .../google-auth/google/oauth2/id_token.py | 4 ++-- packages/google-auth/tests/test_jwt.py | 19 +++++++++++++++++++ 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/packages/google-auth/google/auth/jwt.py b/packages/google-auth/google/auth/jwt.py index 8165ddad7767..892f3a88a496 100644 --- a/packages/google-auth/google/auth/jwt.py +++ b/packages/google-auth/google/auth/jwt.py @@ -219,8 +219,9 @@ def decode(token, certs=None, verify=True, audience=None): in the token's header. verify (bool): Whether to perform signature and claim validation. Verification is done by default. - audience (str): The audience claim, 'aud', that this JWT should - contain. If None then the JWT's 'aud' parameter is not verified. + audience (str or list): The audience claim, 'aud', that this JWT should + contain. Or a list of audience claims. If None then the JWT's 'aud' + parameter is not verified. Returns: Mapping[str, str]: The deserialized JSON payload in the JWT. @@ -279,9 +280,11 @@ def decode(token, certs=None, verify=True, audience=None): # Check audience. if audience is not None: claim_audience = payload.get("aud") - if audience != claim_audience: + if isinstance(audience, str): + audience = [audience] + if claim_audience not in audience: raise ValueError( - "Token has wrong audience {}, expected {}".format( + "Token has wrong audience {}, expected one of {}".format( claim_audience, audience ) ) diff --git a/packages/google-auth/google/oauth2/id_token.py b/packages/google-auth/google/oauth2/id_token.py index 5e36260179b7..5fbb6a13371c 100644 --- a/packages/google-auth/google/oauth2/id_token.py +++ b/packages/google-auth/google/oauth2/id_token.py @@ -112,8 +112,8 @@ def verify_token(id_token, request, audience=None, certs_url=_GOOGLE_OAUTH2_CERT id_token (Union[str, bytes]): The encoded token. request (google.auth.transport.Request): The object used to make HTTP requests. - audience (str): The audience that this token is intended for. If None - then the audience is not verified. + audience (str or list): The audience or audiences that this token is + intended for. If None then the audience is not verified. certs_url (str): The URL that specifies the certificates to use to verify the token. This URL should return JSON in the format of ``{'key id': 'x509 certificate'}``. diff --git a/packages/google-auth/tests/test_jwt.py b/packages/google-auth/tests/test_jwt.py index 7b5ba5cdcb80..c5290eb07d59 100644 --- a/packages/google-auth/tests/test_jwt.py +++ b/packages/google-auth/tests/test_jwt.py @@ -144,6 +144,17 @@ def test_decode_valid_with_audience(token_factory): assert payload["metadata"]["meta"] == "data" +def test_decode_valid_with_audience_list(token_factory): + payload = jwt.decode( + token_factory(), + certs=PUBLIC_CERT_BYTES, + audience=["audience@example.com", "another_audience@example.com"], + ) + assert payload["aud"] == "audience@example.com" + assert payload["user"] == "billy bob" + assert payload["metadata"]["meta"] == "data" + + def test_decode_valid_unverified(token_factory): payload = jwt.decode(token_factory(), certs=OTHER_CERT_BYTES, verify=False) assert payload["aud"] == "audience@example.com" @@ -211,6 +222,14 @@ def test_decode_bad_token_wrong_audience(token_factory): assert excinfo.match(r"Token has wrong audience") +def test_decode_bad_token_wrong_audience_list(token_factory): + token = token_factory() + audience = ["audience2@example.com", "audience3@example.com"] + with pytest.raises(ValueError) as excinfo: + jwt.decode(token, PUBLIC_CERT_BYTES, audience=audience) + assert excinfo.match(r"Token has wrong audience") + + def test_decode_wrong_cert(token_factory): with pytest.raises(ValueError) as excinfo: jwt.decode(token_factory(), OTHER_CERT_BYTES) From 4195ec2961dc098fcd36381cbb56d8f6c6f3b589 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 15 Apr 2021 10:37:58 -0700 Subject: [PATCH 402/966] chore: release 1.29.0 (#735) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 12 ++++++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 081e5540e20e..b792a102a0f4 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,18 @@ [1]: https://pypi.org/project/google-auth/#history +## [1.29.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.28.1...v1.29.0) (2021-04-15) + + +### Features + +* add reauth feature to user credentials ([#727](https://www.github.com/googleapis/google-auth-library-python/issues/727)) ([82293fe](https://www.github.com/googleapis/google-auth-library-python/commit/82293fe2caaf5258babb5df1cff0a5ddc9e44b38)) + + +### Bug Fixes + +* Allow multiple audiences for id_token.verify_token ([#733](https://www.github.com/googleapis/google-auth-library-python/issues/733)) ([56c3946](https://www.github.com/googleapis/google-auth-library-python/commit/56c394680ac6dfc07c611a9eb1e030e32edd4fe1)) + ### [1.28.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.28.0...v1.28.1) (2021-04-08) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 1faab4d1e1ac..abae313d0596 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "1.28.1" +__version__ = "1.29.0" From 94eba0d10c389e0d318afa0499000aa8f5eef5f1 Mon Sep 17 00:00:00 2001 From: Dan Lee <71398022+dandhlee@users.noreply.github.com> Date: Fri, 16 Apr 2021 19:21:57 -0400 Subject: [PATCH 403/966] chore: prevent normalization of semver versioning (#736) --- packages/google-auth/setup.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index ef723f8af512..0c2c5bb39bfb 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -15,9 +15,24 @@ import io import os -from setuptools import find_packages -from setuptools import setup +import setuptools +# Disable version normalization performed by setuptools.setup() +# Adding this in even though it works for Python3.x, but does not +# work for Python 2.7 +try: + # Try the approach of using sic(), added in setuptools 46.1.0 + from setuptools import sic +except ImportError: + # Try the approach of replacing packaging.version.Version + sic = lambda v: v + try: + # setuptools >=39.0.0 uses packaging from setuptools.extern + from setuptools.extern import packaging + except ImportError: + # setuptools <39.0.0 uses packaging from pkg_resources.extern + from pkg_resources.extern import packaging + packaging.version.Version = packaging.version.LegacyVersion DEPENDENCIES = ( "cachetools>=2.0.0,<5.0", @@ -46,15 +61,15 @@ exec(fp.read(), version) version = version["__version__"] -setup( +setuptools.setup( name="google-auth", - version=version, + version=sic(version), author="Google Cloud Platform", author_email="googleapis-packages@google.com", description="Google Authentication Library", long_description=long_description, url="https://github.com/googleapis/google-auth-library-python", - packages=find_packages(exclude=("tests*", "system_tests*")), + packages=setuptools.find_packages(exclude=("tests*", "system_tests*")), namespace_packages=("google",), install_requires=DEPENDENCIES, extras_require=extras, From 90ce859f37ad1fd858a586b4110a070e454d0147 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Fri, 23 Apr 2021 15:27:02 -0700 Subject: [PATCH 404/966] feat: add reauth support to async user credentials (#738) --- .../google/oauth2/_client_async.py | 125 ++++--- .../google/oauth2/_credentials_async.py | 13 +- .../google/oauth2/_reauth_async.py | 320 +++++++++++++++++ packages/google-auth/google/oauth2/reauth.py | 6 +- .../tests_async/oauth2/test__client_async.py | 67 ++-- .../oauth2/test_credentials_async.py | 36 +- .../tests_async/oauth2/test_reauth_async.py | 328 ++++++++++++++++++ 7 files changed, 785 insertions(+), 110 deletions(-) create mode 100644 packages/google-auth/google/oauth2/_reauth_async.py create mode 100644 packages/google-auth/tests_async/oauth2/test_reauth_async.py diff --git a/packages/google-auth/google/oauth2/_client_async.py b/packages/google-auth/google/oauth2/_client_async.py index 4817ea40e559..cf51211379ee 100644 --- a/packages/google-auth/google/oauth2/_client_async.py +++ b/packages/google-auth/google/oauth2/_client_async.py @@ -30,53 +30,16 @@ from six.moves import http_client from six.moves import urllib -from google.auth import _helpers from google.auth import exceptions from google.auth import jwt from google.oauth2 import _client as client -def _handle_error_response(response_body): - """"Translates an error response into an exception. - - Args: - response_body (str): The decoded response data. - - Raises: - google.auth.exceptions.RefreshError - """ - try: - error_data = json.loads(response_body) - error_details = "{}: {}".format( - error_data["error"], error_data.get("error_description") - ) - # If no details could be extracted, use the response data. - except (KeyError, ValueError): - error_details = response_body - - raise exceptions.RefreshError(error_details, response_body) - - -def _parse_expiry(response_data): - """Parses the expiry field from a response into a datetime. - - Args: - response_data (Mapping): The JSON-parsed response data. - - Returns: - Optional[datetime]: The expiration or ``None`` if no expiration was - specified. - """ - expires_in = response_data.get("expires_in", None) - - if expires_in is not None: - return _helpers.utcnow() + datetime.timedelta(seconds=expires_in) - else: - return None - - -async def _token_endpoint_request(request, token_uri, body): +async def _token_endpoint_request_no_throw( + request, token_uri, body, access_token=None, use_json=False +): """Makes a request to the OAuth 2.0 authorization server's token endpoint. + This function doesn't throw on response errors. Args: request (google.auth.transport.Request): A callable used to make @@ -84,16 +47,23 @@ async def _token_endpoint_request(request, token_uri, body): token_uri (str): The OAuth 2.0 authorizations server's token endpoint URI. body (Mapping[str, str]): The parameters to send in the request body. + access_token (Optional(str)): The access token needed to make the request. + use_json (Optional(bool)): Use urlencoded format or json format for the + content type. The default value is False. Returns: - Mapping[str, str]: The JSON-decoded response data. - - Raises: - google.auth.exceptions.RefreshError: If the token endpoint returned - an error. + Tuple(bool, Mapping[str, str]): A boolean indicating if the request is + successful, and a mapping for the JSON-decoded response data. """ - body = urllib.parse.urlencode(body).encode("utf-8") - headers = {"content-type": client._URLENCODED_CONTENT_TYPE} + if use_json: + headers = {"Content-Type": client._JSON_CONTENT_TYPE} + body = json.dumps(body).encode("utf-8") + else: + headers = {"Content-Type": client._URLENCODED_CONTENT_TYPE} + body = urllib.parse.urlencode(body).encode("utf-8") + + if access_token: + headers["Authorization"] = "Bearer {}".format(access_token) retry = 0 # retry to fetch token for maximum of two times if any internal failure @@ -126,8 +96,38 @@ async def _token_endpoint_request(request, token_uri, body): ): retry += 1 continue - _handle_error_response(response_body) + return response.status == http_client.OK, response_data + return response.status == http_client.OK, response_data + + +async def _token_endpoint_request( + request, token_uri, body, access_token=None, use_json=False +): + """Makes a request to the OAuth 2.0 authorization server's token endpoint. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + token_uri (str): The OAuth 2.0 authorizations server's token endpoint + URI. + body (Mapping[str, str]): The parameters to send in the request body. + access_token (Optional(str)): The access token needed to make the request. + use_json (Optional(bool)): Use urlencoded format or json format for the + content type. The default value is False. + + Returns: + Mapping[str, str]: The JSON-decoded response data. + + Raises: + google.auth.exceptions.RefreshError: If the token endpoint returned + an error. + """ + response_status_ok, response_data = await _token_endpoint_request_no_throw( + request, token_uri, body, access_token=access_token, use_json=use_json + ) + if not response_status_ok: + client._handle_error_response(response_data) return response_data @@ -163,7 +163,7 @@ async def jwt_grant(request, token_uri, assertion): new_exc = exceptions.RefreshError("No access token in response.", response_data) six.raise_from(new_exc, caught_exc) - expiry = _parse_expiry(response_data) + expiry = client._parse_expiry(response_data) return access_token, expiry, response_data @@ -210,7 +210,13 @@ async def id_token_jwt_grant(request, token_uri, assertion): async def refresh_grant( - request, token_uri, refresh_token, client_id, client_secret, scopes=None + request, + token_uri, + refresh_token, + client_id, + client_secret, + scopes=None, + rapt_token=None, ): """Implements the OAuth 2.0 refresh token grant. @@ -229,10 +235,11 @@ async def refresh_grant( scopes must be authorized for the refresh token. Useful if refresh token has a wild card scope (e.g. 'https://www.googleapis.com/auth/any-api'). + rapt_token (Optional(str)): The reauth Proof Token. Returns: Tuple[str, Optional[str], Optional[datetime], Mapping[str, str]]: The - access token, new refresh token, expiration, and additional data + access token, new or current refresh token, expiration, and additional data returned by the token endpoint. Raises: @@ -249,16 +256,8 @@ async def refresh_grant( } if scopes: body["scope"] = " ".join(scopes) + if rapt_token: + body["rapt"] = rapt_token response_data = await _token_endpoint_request(request, token_uri, body) - - try: - access_token = response_data["access_token"] - except KeyError as caught_exc: - new_exc = exceptions.RefreshError("No access token in response.", response_data) - six.raise_from(new_exc, caught_exc) - - refresh_token = response_data.get("refresh_token", refresh_token) - expiry = _parse_expiry(response_data) - - return access_token, refresh_token, expiry, response_data + return client._handle_refresh_grant_response(response_data, refresh_token) diff --git a/packages/google-auth/google/oauth2/_credentials_async.py b/packages/google-auth/google/oauth2/_credentials_async.py index eb3e97c08066..b4878c5430f8 100644 --- a/packages/google-auth/google/oauth2/_credentials_async.py +++ b/packages/google-auth/google/oauth2/_credentials_async.py @@ -34,7 +34,7 @@ from google.auth import _credentials_async as credentials from google.auth import _helpers from google.auth import exceptions -from google.oauth2 import _client_async as _client +from google.oauth2 import _reauth_async as reauth from google.oauth2 import credentials as oauth2_credentials @@ -66,23 +66,26 @@ async def refresh(self, request): refresh_token, expiry, grant_response, - ) = await _client.refresh_grant( + rapt_token, + ) = await reauth.refresh_grant( request, self._token_uri, self._refresh_token, self._client_id, self._client_secret, - self._scopes, + scopes=self._scopes, + rapt_token=self._rapt_token, ) self.token = access_token self.expiry = expiry self._refresh_token = refresh_token self._id_token = grant_response.get("id_token") + self._rapt_token = rapt_token - if self._scopes and "scopes" in grant_response: + if self._scopes and "scope" in grant_response: requested_scopes = frozenset(self._scopes) - granted_scopes = frozenset(grant_response["scopes"].split()) + granted_scopes = frozenset(grant_response["scope"].split()) scopes_requested_but_not_granted = requested_scopes - granted_scopes if scopes_requested_but_not_granted: raise exceptions.RefreshError( diff --git a/packages/google-auth/google/oauth2/_reauth_async.py b/packages/google-auth/google/oauth2/_reauth_async.py new file mode 100644 index 000000000000..09e076090afb --- /dev/null +++ b/packages/google-auth/google/oauth2/_reauth_async.py @@ -0,0 +1,320 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""A module that provides functions for handling rapt authentication. + +Reauth is a process of obtaining additional authentication (such as password, +security token, etc.) while refreshing OAuth 2.0 credentials for a user. + +Credentials that use the Reauth flow must have the reauth scope, +``https://www.googleapis.com/auth/accounts.reauth``. + +This module provides a high-level function for executing the Reauth process, +:func:`refresh_grant`, and lower-level helpers for doing the individual +steps of the reauth process. + +Those steps are: + +1. Obtaining a list of challenges from the reauth server. +2. Running through each challenge and sending the result back to the reauth + server. +3. Refreshing the access token using the returned rapt token. +""" + +import sys + +from six.moves import range + +from google.auth import exceptions +from google.oauth2 import _client +from google.oauth2 import _client_async +from google.oauth2 import challenges +from google.oauth2 import reauth + + +async def _get_challenges( + request, supported_challenge_types, access_token, requested_scopes=None +): + """Does initial request to reauth API to get the challenges. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. This must be an aiohttp request. + supported_challenge_types (Sequence[str]): list of challenge names + supported by the manager. + access_token (str): Access token with reauth scopes. + requested_scopes (Optional(Sequence[str])): Authorized scopes for the credentials. + + Returns: + dict: The response from the reauth API. + """ + body = {"supportedChallengeTypes": supported_challenge_types} + if requested_scopes: + body["oauthScopesForDomainPolicyLookup"] = requested_scopes + + return await _client_async._token_endpoint_request( + request, + reauth._REAUTH_API + ":start", + body, + access_token=access_token, + use_json=True, + ) + + +async def _send_challenge_result( + request, session_id, challenge_id, client_input, access_token +): + """Attempt to refresh access token by sending next challenge result. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. This must be an aiohttp request. + session_id (str): session id returned by the initial reauth call. + challenge_id (str): challenge id returned by the initial reauth call. + client_input: dict with a challenge-specific client input. For example: + ``{'credential': password}`` for password challenge. + access_token (str): Access token with reauth scopes. + + Returns: + dict: The response from the reauth API. + """ + body = { + "sessionId": session_id, + "challengeId": challenge_id, + "action": "RESPOND", + "proposalResponse": client_input, + } + + return await _client_async._token_endpoint_request( + request, + reauth._REAUTH_API + "/{}:continue".format(session_id), + body, + access_token=access_token, + use_json=True, + ) + + +async def _run_next_challenge(msg, request, access_token): + """Get the next challenge from msg and run it. + + Args: + msg (dict): Reauth API response body (either from the initial request to + https://reauth.googleapis.com/v2/sessions:start or from sending the + previous challenge response to + https://reauth.googleapis.com/v2/sessions/id:continue) + request (google.auth.transport.Request): A callable used to make + HTTP requests. This must be an aiohttp request. + access_token (str): reauth access token + + Returns: + dict: The response from the reauth API. + + Raises: + google.auth.exceptions.ReauthError: if reauth failed. + """ + for challenge in msg["challenges"]: + if challenge["status"] != "READY": + # Skip non-activated challenges. + continue + c = challenges.AVAILABLE_CHALLENGES.get(challenge["challengeType"], None) + if not c: + raise exceptions.ReauthFailError( + "Unsupported challenge type {0}. Supported types: {1}".format( + challenge["challengeType"], + ",".join(list(challenges.AVAILABLE_CHALLENGES.keys())), + ) + ) + if not c.is_locally_eligible: + raise exceptions.ReauthFailError( + "Challenge {0} is not locally eligible".format( + challenge["challengeType"] + ) + ) + client_input = c.obtain_challenge_input(challenge) + if not client_input: + return None + return await _send_challenge_result( + request, + msg["sessionId"], + challenge["challengeId"], + client_input, + access_token, + ) + return None + + +async def _obtain_rapt(request, access_token, requested_scopes): + """Given an http request method and reauth access token, get rapt token. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. This must be an aiohttp request. + access_token (str): reauth access token + requested_scopes (Sequence[str]): scopes required by the client application + + Returns: + str: The rapt token. + + Raises: + google.auth.exceptions.ReauthError: if reauth failed + """ + msg = await _get_challenges( + request, + list(challenges.AVAILABLE_CHALLENGES.keys()), + access_token, + requested_scopes, + ) + + if msg["status"] == reauth._AUTHENTICATED: + return msg["encodedProofOfReauthToken"] + + for _ in range(0, reauth.RUN_CHALLENGE_RETRY_LIMIT): + if not ( + msg["status"] == reauth._CHALLENGE_REQUIRED + or msg["status"] == reauth._CHALLENGE_PENDING + ): + raise exceptions.ReauthFailError( + "Reauthentication challenge failed due to API error: {}".format( + msg["status"] + ) + ) + + if not reauth.is_interactive(): + raise exceptions.ReauthFailError( + "Reauthentication challenge could not be answered because you are not" + " in an interactive session." + ) + + msg = await _run_next_challenge(msg, request, access_token) + + if msg["status"] == reauth._AUTHENTICATED: + return msg["encodedProofOfReauthToken"] + + # If we got here it means we didn't get authenticated. + raise exceptions.ReauthFailError("Failed to obtain rapt token.") + + +async def get_rapt_token( + request, client_id, client_secret, refresh_token, token_uri, scopes=None +): + """Given an http request method and refresh_token, get rapt token. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. This must be an aiohttp request. + client_id (str): client id to get access token for reauth scope. + client_secret (str): client secret for the client_id + refresh_token (str): refresh token to refresh access token + token_uri (str): uri to refresh access token + scopes (Optional(Sequence[str])): scopes required by the client application + + Returns: + str: The rapt token. + Raises: + google.auth.exceptions.RefreshError: If reauth failed. + """ + sys.stderr.write("Reauthentication required.\n") + + # Get access token for reauth. + access_token, _, _, _ = await _client_async.refresh_grant( + request=request, + client_id=client_id, + client_secret=client_secret, + refresh_token=refresh_token, + token_uri=token_uri, + scopes=[reauth._REAUTH_SCOPE], + ) + + # Get rapt token from reauth API. + rapt_token = await _obtain_rapt(request, access_token, requested_scopes=scopes) + + return rapt_token + + +async def refresh_grant( + request, + token_uri, + refresh_token, + client_id, + client_secret, + scopes=None, + rapt_token=None, +): + """Implements the reauthentication flow. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. This must be an aiohttp request. + token_uri (str): The OAuth 2.0 authorizations server's token endpoint + URI. + refresh_token (str): The refresh token to use to get a new access + token. + client_id (str): The OAuth 2.0 application's client ID. + client_secret (str): The Oauth 2.0 appliaction's client secret. + scopes (Optional(Sequence[str])): Scopes to request. If present, all + scopes must be authorized for the refresh token. Useful if refresh + token has a wild card scope (e.g. + 'https://www.googleapis.com/auth/any-api'). + rapt_token (Optional(str)): The rapt token for reauth. + + Returns: + Tuple[str, Optional[str], Optional[datetime], Mapping[str, str], str]: The + access token, new refresh token, expiration, the additional data + returned by the token endpoint, and the rapt token. + + Raises: + google.auth.exceptions.RefreshError: If the token endpoint returned + an error. + """ + body = { + "grant_type": _client._REFRESH_GRANT_TYPE, + "client_id": client_id, + "client_secret": client_secret, + "refresh_token": refresh_token, + } + if scopes: + body["scope"] = " ".join(scopes) + if rapt_token: + body["rapt"] = rapt_token + + response_status_ok, response_data = await _client_async._token_endpoint_request_no_throw( + request, token_uri, body + ) + if ( + not response_status_ok + and response_data.get("error") == reauth._REAUTH_NEEDED_ERROR + and ( + response_data.get("error_subtype") + == reauth._REAUTH_NEEDED_ERROR_INVALID_RAPT + or response_data.get("error_subtype") + == reauth._REAUTH_NEEDED_ERROR_RAPT_REQUIRED + ) + ): + rapt_token = await get_rapt_token( + request, client_id, client_secret, refresh_token, token_uri, scopes=scopes + ) + body["rapt"] = rapt_token + ( + response_status_ok, + response_data, + ) = await _client_async._token_endpoint_request_no_throw( + request, token_uri, body + ) + + if not response_status_ok: + _client._handle_error_response(response_data) + refresh_response = _client._handle_refresh_grant_response( + response_data, refresh_token + ) + return refresh_response + (rapt_token,) diff --git a/packages/google-auth/google/oauth2/reauth.py b/packages/google-auth/google/oauth2/reauth.py index d539d7c9e14c..d914fe9a7ddb 100644 --- a/packages/google-auth/google/oauth2/reauth.py +++ b/packages/google-auth/google/oauth2/reauth.py @@ -296,9 +296,9 @@ def refresh_grant( rapt_token (Optional(str)): The rapt token for reauth. Returns: - Tuple[str, Optional[str], Optional[datetime], Mapping[str, str]]: The - access token, new refresh token, expiration, and additional data - returned by the token endpoint. + Tuple[str, Optional[str], Optional[datetime], Mapping[str, str], str]: The + access token, new refresh token, expiration, the additional data + returned by the token endpoint, and the rapt token. Raises: google.auth.exceptions.RefreshError: If the token endpoint returned diff --git a/packages/google-auth/tests_async/oauth2/test__client_async.py b/packages/google-auth/tests_async/oauth2/test__client_async.py index 458937ac1f5f..6e48c4590fcb 100644 --- a/packages/google-auth/tests_async/oauth2/test__client_async.py +++ b/packages/google-auth/tests_async/oauth2/test__client_async.py @@ -29,34 +29,6 @@ from tests.oauth2 import test__client as test_client -def test__handle_error_response(): - response_data = json.dumps({"error": "help", "error_description": "I'm alive"}) - - with pytest.raises(exceptions.RefreshError) as excinfo: - _client._handle_error_response(response_data) - - assert excinfo.match(r"help: I\'m alive") - - -def test__handle_error_response_non_json(): - response_data = "Help, I'm alive" - - with pytest.raises(exceptions.RefreshError) as excinfo: - _client._handle_error_response(response_data) - - assert excinfo.match(r"Help, I\'m alive") - - -@mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) -def test__parse_expiry(unused_utcnow): - result = _client._parse_expiry({"expires_in": 500}) - assert result == datetime.datetime.min + datetime.timedelta(seconds=500) - - -def test__parse_expiry_none(): - assert _client._parse_expiry({}) is None - - def make_request(response_data, status=http_client.OK): response = mock.AsyncMock(spec=["transport.Response"]) response.status = status @@ -82,7 +54,7 @@ async def test__token_endpoint_request(): request.assert_called_with( method="POST", url="http://example.com", - headers={"content-type": "application/x-www-form-urlencoded"}, + headers={"Content-Type": "application/x-www-form-urlencoded"}, body="test=params".encode("utf-8"), ) @@ -90,6 +62,35 @@ async def test__token_endpoint_request(): assert result == {"test": "response"} +@pytest.mark.asyncio +async def test__token_endpoint_request_json(): + + request = make_request({"test": "response"}) + access_token = "access_token" + + result = await _client._token_endpoint_request( + request, + "http://example.com", + {"test": "params"}, + access_token=access_token, + use_json=True, + ) + + # Check request call + request.assert_called_with( + method="POST", + url="http://example.com", + headers={ + "Content-Type": "application/json", + "Authorization": "Bearer access_token", + }, + body=b'{"test": "params"}', + ) + + # Check result + assert result == {"test": "response"} + + @pytest.mark.asyncio async def test__token_endpoint_request_error(): request = make_request({}, status=http_client.BAD_REQUEST) @@ -218,7 +219,12 @@ async def test_refresh_grant(unused_utcnow): ) token, refresh_token, expiry, extra_data = await _client.refresh_grant( - request, "http://example.com", "refresh_token", "client_id", "client_secret" + request, + "http://example.com", + "refresh_token", + "client_id", + "client_secret", + rapt_token="rapt_token", ) # Check request call @@ -229,6 +235,7 @@ async def test_refresh_grant(unused_utcnow): "refresh_token": "refresh_token", "client_id": "client_id", "client_secret": "client_secret", + "rapt": "rapt_token", }, ) diff --git a/packages/google-auth/tests_async/oauth2/test_credentials_async.py b/packages/google-auth/tests_async/oauth2/test_credentials_async.py index 5c883d614768..99cf16f80e49 100644 --- a/packages/google-auth/tests_async/oauth2/test_credentials_async.py +++ b/packages/google-auth/tests_async/oauth2/test_credentials_async.py @@ -58,7 +58,7 @@ def test_default_state(self): assert credentials.client_id == self.CLIENT_ID assert credentials.client_secret == self.CLIENT_SECRET - @mock.patch("google.oauth2._client_async.refresh_grant", autospec=True) + @mock.patch("google.oauth2._reauth_async.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", return_value=datetime.datetime.min + _helpers.CLOCK_SKEW, @@ -68,6 +68,7 @@ async def test_refresh_success(self, unused_utcnow, refresh_grant): token = "token" expiry = _helpers.utcnow() + datetime.timedelta(seconds=500) grant_response = {"id_token": mock.sentinel.id_token} + rapt_token = "rapt_token" refresh_grant.return_value = ( # Access token token, @@ -77,6 +78,8 @@ async def test_refresh_success(self, unused_utcnow, refresh_grant): expiry, # Extra data grant_response, + # Rapt token + rapt_token, ) request = mock.AsyncMock(spec=["transport.Request"]) @@ -93,12 +96,14 @@ async def test_refresh_success(self, unused_utcnow, refresh_grant): self.CLIENT_ID, self.CLIENT_SECRET, None, + None, ) # Check that the credentials have the token and expiry assert creds.token == token assert creds.expiry == expiry assert creds.id_token == mock.sentinel.id_token + assert creds.rapt_token == rapt_token # Check that the credentials are valid (have a token and are not # expired) @@ -114,7 +119,7 @@ async def test_refresh_no_refresh_token(self): request.assert_not_called() - @mock.patch("google.oauth2._client_async.refresh_grant", autospec=True) + @mock.patch("google.oauth2._reauth_async.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", return_value=datetime.datetime.min + _helpers.CLOCK_SKEW, @@ -127,6 +132,7 @@ async def test_credentials_with_scopes_requested_refresh_success( token = "token" expiry = _helpers.utcnow() + datetime.timedelta(seconds=500) grant_response = {"id_token": mock.sentinel.id_token} + rapt_token = "rapt_token" refresh_grant.return_value = ( # Access token token, @@ -136,6 +142,8 @@ async def test_credentials_with_scopes_requested_refresh_success( expiry, # Extra data grant_response, + # Rapt token + rapt_token, ) request = mock.AsyncMock(spec=["transport.Request"]) @@ -146,6 +154,7 @@ async def test_credentials_with_scopes_requested_refresh_success( client_id=self.CLIENT_ID, client_secret=self.CLIENT_SECRET, scopes=scopes, + rapt_token="old_rapt_token", ) # Refresh credentials @@ -159,6 +168,7 @@ async def test_credentials_with_scopes_requested_refresh_success( self.CLIENT_ID, self.CLIENT_SECRET, scopes, + "old_rapt_token", ) # Check that the credentials have the token and expiry @@ -166,12 +176,13 @@ async def test_credentials_with_scopes_requested_refresh_success( assert creds.expiry == expiry assert creds.id_token == mock.sentinel.id_token assert creds.has_scopes(scopes) + assert creds.rapt_token == rapt_token # Check that the credentials are valid (have a token and are not # expired.) assert creds.valid - @mock.patch("google.oauth2._client_async.refresh_grant", autospec=True) + @mock.patch("google.oauth2._reauth_async.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", return_value=datetime.datetime.min + _helpers.CLOCK_SKEW, @@ -183,10 +194,8 @@ async def test_credentials_with_scopes_returned_refresh_success( scopes = ["email", "profile"] token = "token" expiry = _helpers.utcnow() + datetime.timedelta(seconds=500) - grant_response = { - "id_token": mock.sentinel.id_token, - "scopes": " ".join(scopes), - } + grant_response = {"id_token": mock.sentinel.id_token, "scope": " ".join(scopes)} + rapt_token = "rapt_token" refresh_grant.return_value = ( # Access token token, @@ -196,6 +205,8 @@ async def test_credentials_with_scopes_returned_refresh_success( expiry, # Extra data grant_response, + # Rapt token + rapt_token, ) request = mock.AsyncMock(spec=["transport.Request"]) @@ -219,6 +230,7 @@ async def test_credentials_with_scopes_returned_refresh_success( self.CLIENT_ID, self.CLIENT_SECRET, scopes, + None, ) # Check that the credentials have the token and expiry @@ -226,12 +238,13 @@ async def test_credentials_with_scopes_returned_refresh_success( assert creds.expiry == expiry assert creds.id_token == mock.sentinel.id_token assert creds.has_scopes(scopes) + assert creds.rapt_token == rapt_token # Check that the credentials are valid (have a token and are not # expired.) assert creds.valid - @mock.patch("google.oauth2._client_async.refresh_grant", autospec=True) + @mock.patch("google.oauth2._reauth_async.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", return_value=datetime.datetime.min + _helpers.CLOCK_SKEW, @@ -246,8 +259,9 @@ async def test_credentials_with_scopes_refresh_failure_raises_refresh_error( expiry = _helpers.utcnow() + datetime.timedelta(seconds=500) grant_response = { "id_token": mock.sentinel.id_token, - "scopes": " ".join(scopes_returned), + "scope": " ".join(scopes_returned), } + rapt_token = "rapt_token" refresh_grant.return_value = ( # Access token token, @@ -257,6 +271,8 @@ async def test_credentials_with_scopes_refresh_failure_raises_refresh_error( expiry, # Extra data grant_response, + # Rapt token + rapt_token, ) request = mock.AsyncMock(spec=["transport.Request"]) @@ -267,6 +283,7 @@ async def test_credentials_with_scopes_refresh_failure_raises_refresh_error( client_id=self.CLIENT_ID, client_secret=self.CLIENT_SECRET, scopes=scopes, + rapt_token=None, ) # Refresh credentials @@ -283,6 +300,7 @@ async def test_credentials_with_scopes_refresh_failure_raises_refresh_error( self.CLIENT_ID, self.CLIENT_SECRET, scopes, + None, ) # Check that the credentials have the token and expiry diff --git a/packages/google-auth/tests_async/oauth2/test_reauth_async.py b/packages/google-auth/tests_async/oauth2/test_reauth_async.py new file mode 100644 index 000000000000..f144d89f527f --- /dev/null +++ b/packages/google-auth/tests_async/oauth2/test_reauth_async.py @@ -0,0 +1,328 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import copy + +import mock +import pytest + +from google.auth import exceptions +from google.oauth2 import _reauth_async +from google.oauth2 import reauth + + +MOCK_REQUEST = mock.AsyncMock(spec=["transport.Request"]) +CHALLENGES_RESPONSE_TEMPLATE = { + "status": "CHALLENGE_REQUIRED", + "sessionId": "123", + "challenges": [ + { + "status": "READY", + "challengeId": 1, + "challengeType": "PASSWORD", + "securityKey": {}, + } + ], +} +CHALLENGES_RESPONSE_AUTHENTICATED = { + "status": "AUTHENTICATED", + "sessionId": "123", + "encodedProofOfReauthToken": "new_rapt_token", +} + + +class MockChallenge(object): + def __init__(self, name, locally_eligible, challenge_input): + self.name = name + self.is_locally_eligible = locally_eligible + self.challenge_input = challenge_input + + def obtain_challenge_input(self, metadata): + return self.challenge_input + + +@pytest.mark.asyncio +async def test__get_challenges(): + with mock.patch( + "google.oauth2._client_async._token_endpoint_request" + ) as mock_token_endpoint_request: + await _reauth_async._get_challenges(MOCK_REQUEST, ["SAML"], "token") + mock_token_endpoint_request.assert_called_with( + MOCK_REQUEST, + reauth._REAUTH_API + ":start", + {"supportedChallengeTypes": ["SAML"]}, + access_token="token", + use_json=True, + ) + + +@pytest.mark.asyncio +async def test__get_challenges_with_scopes(): + with mock.patch( + "google.oauth2._client_async._token_endpoint_request" + ) as mock_token_endpoint_request: + await _reauth_async._get_challenges( + MOCK_REQUEST, ["SAML"], "token", requested_scopes=["scope"] + ) + mock_token_endpoint_request.assert_called_with( + MOCK_REQUEST, + reauth._REAUTH_API + ":start", + { + "supportedChallengeTypes": ["SAML"], + "oauthScopesForDomainPolicyLookup": ["scope"], + }, + access_token="token", + use_json=True, + ) + + +@pytest.mark.asyncio +async def test__send_challenge_result(): + with mock.patch( + "google.oauth2._client_async._token_endpoint_request" + ) as mock_token_endpoint_request: + await _reauth_async._send_challenge_result( + MOCK_REQUEST, "123", "1", {"credential": "password"}, "token" + ) + mock_token_endpoint_request.assert_called_with( + MOCK_REQUEST, + reauth._REAUTH_API + "/123:continue", + { + "sessionId": "123", + "challengeId": "1", + "action": "RESPOND", + "proposalResponse": {"credential": "password"}, + }, + access_token="token", + use_json=True, + ) + + +@pytest.mark.asyncio +async def test__run_next_challenge_not_ready(): + challenges_response = copy.deepcopy(CHALLENGES_RESPONSE_TEMPLATE) + challenges_response["challenges"][0]["status"] = "STATUS_UNSPECIFIED" + assert ( + await _reauth_async._run_next_challenge( + challenges_response, MOCK_REQUEST, "token" + ) + is None + ) + + +@pytest.mark.asyncio +async def test__run_next_challenge_not_supported(): + challenges_response = copy.deepcopy(CHALLENGES_RESPONSE_TEMPLATE) + challenges_response["challenges"][0]["challengeType"] = "CHALLENGE_TYPE_UNSPECIFIED" + with pytest.raises(exceptions.ReauthFailError) as excinfo: + await _reauth_async._run_next_challenge( + challenges_response, MOCK_REQUEST, "token" + ) + assert excinfo.match(r"Unsupported challenge type CHALLENGE_TYPE_UNSPECIFIED") + + +@pytest.mark.asyncio +async def test__run_next_challenge_not_locally_eligible(): + mock_challenge = MockChallenge("PASSWORD", False, "challenge_input") + with mock.patch( + "google.oauth2.challenges.AVAILABLE_CHALLENGES", {"PASSWORD": mock_challenge} + ): + with pytest.raises(exceptions.ReauthFailError) as excinfo: + await _reauth_async._run_next_challenge( + CHALLENGES_RESPONSE_TEMPLATE, MOCK_REQUEST, "token" + ) + assert excinfo.match(r"Challenge PASSWORD is not locally eligible") + + +@pytest.mark.asyncio +async def test__run_next_challenge_no_challenge_input(): + mock_challenge = MockChallenge("PASSWORD", True, None) + with mock.patch( + "google.oauth2.challenges.AVAILABLE_CHALLENGES", {"PASSWORD": mock_challenge} + ): + assert ( + await _reauth_async._run_next_challenge( + CHALLENGES_RESPONSE_TEMPLATE, MOCK_REQUEST, "token" + ) + is None + ) + + +@pytest.mark.asyncio +async def test__run_next_challenge_success(): + mock_challenge = MockChallenge("PASSWORD", True, {"credential": "password"}) + with mock.patch( + "google.oauth2.challenges.AVAILABLE_CHALLENGES", {"PASSWORD": mock_challenge} + ): + with mock.patch( + "google.oauth2._reauth_async._send_challenge_result" + ) as mock_send_challenge_result: + await _reauth_async._run_next_challenge( + CHALLENGES_RESPONSE_TEMPLATE, MOCK_REQUEST, "token" + ) + mock_send_challenge_result.assert_called_with( + MOCK_REQUEST, "123", 1, {"credential": "password"}, "token" + ) + + +@pytest.mark.asyncio +async def test__obtain_rapt_authenticated(): + with mock.patch( + "google.oauth2._reauth_async._get_challenges", + return_value=CHALLENGES_RESPONSE_AUTHENTICATED, + ): + new_rapt_token = await _reauth_async._obtain_rapt(MOCK_REQUEST, "token", None) + assert new_rapt_token == "new_rapt_token" + + +@pytest.mark.asyncio +async def test__obtain_rapt_authenticated_after_run_next_challenge(): + with mock.patch( + "google.oauth2._reauth_async._get_challenges", + return_value=CHALLENGES_RESPONSE_TEMPLATE, + ): + with mock.patch( + "google.oauth2._reauth_async._run_next_challenge", + side_effect=[ + CHALLENGES_RESPONSE_TEMPLATE, + CHALLENGES_RESPONSE_AUTHENTICATED, + ], + ): + with mock.patch("google.oauth2.reauth.is_interactive", return_value=True): + assert ( + await _reauth_async._obtain_rapt(MOCK_REQUEST, "token", None) + == "new_rapt_token" + ) + + +@pytest.mark.asyncio +async def test__obtain_rapt_unsupported_status(): + challenges_response = copy.deepcopy(CHALLENGES_RESPONSE_TEMPLATE) + challenges_response["status"] = "STATUS_UNSPECIFIED" + with mock.patch( + "google.oauth2._reauth_async._get_challenges", return_value=challenges_response + ): + with pytest.raises(exceptions.ReauthFailError) as excinfo: + await _reauth_async._obtain_rapt(MOCK_REQUEST, "token", None) + assert excinfo.match(r"API error: STATUS_UNSPECIFIED") + + +@pytest.mark.asyncio +async def test__obtain_rapt_not_interactive(): + with mock.patch( + "google.oauth2._reauth_async._get_challenges", + return_value=CHALLENGES_RESPONSE_TEMPLATE, + ): + with mock.patch("google.oauth2.reauth.is_interactive", return_value=False): + with pytest.raises(exceptions.ReauthFailError) as excinfo: + await _reauth_async._obtain_rapt(MOCK_REQUEST, "token", None) + assert excinfo.match(r"not in an interactive session") + + +@pytest.mark.asyncio +async def test__obtain_rapt_not_authenticated(): + with mock.patch( + "google.oauth2._reauth_async._get_challenges", + return_value=CHALLENGES_RESPONSE_TEMPLATE, + ): + with mock.patch("google.oauth2.reauth.RUN_CHALLENGE_RETRY_LIMIT", 0): + with pytest.raises(exceptions.ReauthFailError) as excinfo: + await _reauth_async._obtain_rapt(MOCK_REQUEST, "token", None) + assert excinfo.match(r"Reauthentication failed") + + +@pytest.mark.asyncio +async def test_get_rapt_token(): + with mock.patch( + "google.oauth2._client_async.refresh_grant", + return_value=("token", None, None, None), + ) as mock_refresh_grant: + with mock.patch( + "google.oauth2._reauth_async._obtain_rapt", return_value="new_rapt_token" + ) as mock_obtain_rapt: + assert ( + await _reauth_async.get_rapt_token( + MOCK_REQUEST, + "client_id", + "client_secret", + "refresh_token", + "token_uri", + ) + == "new_rapt_token" + ) + mock_refresh_grant.assert_called_with( + request=MOCK_REQUEST, + client_id="client_id", + client_secret="client_secret", + refresh_token="refresh_token", + token_uri="token_uri", + scopes=[reauth._REAUTH_SCOPE], + ) + mock_obtain_rapt.assert_called_with( + MOCK_REQUEST, "token", requested_scopes=None + ) + + +@pytest.mark.asyncio +async def test_refresh_grant_failed(): + with mock.patch( + "google.oauth2._client_async._token_endpoint_request_no_throw" + ) as mock_token_request: + mock_token_request.return_value = (False, {"error": "Bad request"}) + with pytest.raises(exceptions.RefreshError) as excinfo: + await _reauth_async.refresh_grant( + MOCK_REQUEST, + "token_uri", + "refresh_token", + "client_id", + "client_secret", + scopes=["foo", "bar"], + rapt_token="rapt_token", + ) + assert excinfo.match(r"Bad request") + mock_token_request.assert_called_with( + MOCK_REQUEST, + "token_uri", + { + "grant_type": "refresh_token", + "client_id": "client_id", + "client_secret": "client_secret", + "refresh_token": "refresh_token", + "scope": "foo bar", + "rapt": "rapt_token", + }, + ) + + +@pytest.mark.asyncio +async def test_refresh_grant_success(): + with mock.patch( + "google.oauth2._client_async._token_endpoint_request_no_throw" + ) as mock_token_request: + mock_token_request.side_effect = [ + (False, {"error": "invalid_grant", "error_subtype": "rapt_required"}), + (True, {"access_token": "access_token"}), + ] + with mock.patch( + "google.oauth2._reauth_async.get_rapt_token", return_value="new_rapt_token" + ): + assert await _reauth_async.refresh_grant( + MOCK_REQUEST, "token_uri", "refresh_token", "client_id", "client_secret" + ) == ( + "access_token", + "refresh_token", + None, + {"access_token": "access_token"}, + "new_rapt_token", + ) From 4669dd7cd7f4451e6446f682e7d23e107bbc4834 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 26 Apr 2021 13:26:11 -0700 Subject: [PATCH 405/966] chore: release 1.30.0 (#741) * chore: release 1.30.0 * chore: update changelog * chore: update changelog Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> Co-authored-by: arithmetic1728 --- packages/google-auth/CHANGELOG.md | 9 ++++++++- packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index b792a102a0f4..096df1734835 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,12 +4,19 @@ [1]: https://pypi.org/project/google-auth/#history +## [1.30.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.29.0...v1.30.0) (2021-04-23) + + +### Features + +* add reauth support to async user credentials for gcloud ([#738](https://www.github.com/googleapis/google-auth-library-python/issues/738)) ([9e10823](https://www.github.com/googleapis/google-auth-library-python/commit/9e1082366d113286bc063051fd76b4799791d943)). This internal feature is for gcloud developers only. + ## [1.29.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.28.1...v1.29.0) (2021-04-15) ### Features -* add reauth feature to user credentials ([#727](https://www.github.com/googleapis/google-auth-library-python/issues/727)) ([82293fe](https://www.github.com/googleapis/google-auth-library-python/commit/82293fe2caaf5258babb5df1cff0a5ddc9e44b38)) +* add reauth feature to user credentials for gcloud ([#727](https://www.github.com/googleapis/google-auth-library-python/issues/727)) ([82293fe](https://www.github.com/googleapis/google-auth-library-python/commit/82293fe2caaf5258babb5df1cff0a5ddc9e44b38)). This internal feature is for gcloud developers only. ### Bug Fixes diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index abae313d0596..4a28e390423e 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "1.29.0" +__version__ = "1.30.0" From e38fe681dad3a7e6d97ab34eb92491157913b619 Mon Sep 17 00:00:00 2001 From: Dan Lee <71398022+dandhlee@users.noreply.github.com> Date: Mon, 26 Apr 2021 21:22:17 -0400 Subject: [PATCH 406/966] chore(revert): revert preventing normalization (#740) reverts previous commit for preventing normalization of versioning --- packages/google-auth/setup.py | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 0c2c5bb39bfb..ef723f8af512 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -15,24 +15,9 @@ import io import os -import setuptools +from setuptools import find_packages +from setuptools import setup -# Disable version normalization performed by setuptools.setup() -# Adding this in even though it works for Python3.x, but does not -# work for Python 2.7 -try: - # Try the approach of using sic(), added in setuptools 46.1.0 - from setuptools import sic -except ImportError: - # Try the approach of replacing packaging.version.Version - sic = lambda v: v - try: - # setuptools >=39.0.0 uses packaging from setuptools.extern - from setuptools.extern import packaging - except ImportError: - # setuptools <39.0.0 uses packaging from pkg_resources.extern - from pkg_resources.extern import packaging - packaging.version.Version = packaging.version.LegacyVersion DEPENDENCIES = ( "cachetools>=2.0.0,<5.0", @@ -61,15 +46,15 @@ exec(fp.read(), version) version = version["__version__"] -setuptools.setup( +setup( name="google-auth", - version=sic(version), + version=version, author="Google Cloud Platform", author_email="googleapis-packages@google.com", description="Google Authentication Library", long_description=long_description, url="https://github.com/googleapis/google-auth-library-python", - packages=setuptools.find_packages(exclude=("tests*", "system_tests*")), + packages=find_packages(exclude=("tests*", "system_tests*")), namespace_packages=("google",), install_requires=DEPENDENCIES, extras_require=extras, From 905a40dd8a219382cb836fd270dcbb5a61c427d5 Mon Sep 17 00:00:00 2001 From: "google-cloud-policy-bot[bot]" <80869356+google-cloud-policy-bot[bot]@users.noreply.github.com> Date: Wed, 12 May 2021 09:15:31 -0400 Subject: [PATCH 407/966] chore: add SECURITY.md (#743) Co-authored-by: google-cloud-policy-bot[bot] <80869356+google-cloud-policy-bot[bot]@users.noreply.github.com> --- packages/google-auth/SECURITY.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 packages/google-auth/SECURITY.md diff --git a/packages/google-auth/SECURITY.md b/packages/google-auth/SECURITY.md new file mode 100644 index 000000000000..8b58ae9c01ae --- /dev/null +++ b/packages/google-auth/SECURITY.md @@ -0,0 +1,7 @@ +# Security Policy + +To report a security issue, please use [g.co/vulnz](https://g.co/vulnz). + +The Google Security Team will respond within 5 working days of your report on g.co/vulnz. + +We use g.co/vulnz for our intake, and do coordination and disclosure here using GitHub Security Advisory to privately discuss and fix the issue. From 2efe4b9c074458f1f16e0e991eabcc16cbc81468 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Wed, 12 May 2021 13:04:02 -0400 Subject: [PATCH 408/966] chore: add library type to .repo-metadata.json (#749) --- packages/google-auth/.repo-metadata.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/google-auth/.repo-metadata.json b/packages/google-auth/.repo-metadata.json index ac3191ce8721..9d799aa38aff 100644 --- a/packages/google-auth/.repo-metadata.json +++ b/packages/google-auth/.repo-metadata.json @@ -5,6 +5,7 @@ "issue_tracker": "https://github.com/googleapis/google-auth-library-python/issues", "release_level": "ga", "language": "python", + "library_type": "AUTH", "repo": "googleapis/google-auth-library-python", "distribution_name": "google-auth" } From 2e50df0f67997b958f102180208259a4b97b7471 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Tue, 18 May 2021 18:08:05 +0300 Subject: [PATCH 409/966] fix: fix function name in signing error message (#751) The name of the API is `signBlob`, not `signBytes`. See https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/signBlob --- packages/google-auth/google/auth/iam.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/iam.py b/packages/google-auth/google/auth/iam.py index 5e88a0435c50..5d63dc5d8a91 100644 --- a/packages/google-auth/google/auth/iam.py +++ b/packages/google-auth/google/auth/iam.py @@ -79,7 +79,7 @@ def _make_signing_request(self, message): if response.status != http_client.OK: raise exceptions.TransportError( - "Error calling the IAM signBytes API: {}".format(response.data) + "Error calling the IAM signBlob API: {}".format(response.data) ) return json.loads(response.data.decode("utf-8")) From 2789c367a0714fa1c464ccac97ed7a313832711f Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Thu, 20 May 2021 16:04:06 -0700 Subject: [PATCH 410/966] fix: allow user to customize context aware metadata path in _mtls_helper (#754) --- .../google/auth/transport/_mtls_helper.py | 8 +++-- .../tests/transport/test__mtls_helper.py | 33 +++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/google/auth/transport/_mtls_helper.py b/packages/google-auth/google/auth/transport/_mtls_helper.py index 388ae3c1586b..4dccb1062f88 100644 --- a/packages/google-auth/google/auth/transport/_mtls_helper.py +++ b/packages/google-auth/google/auth/transport/_mtls_helper.py @@ -141,13 +141,17 @@ def _run_cert_provider_command(command, expect_encrypted_key=False): return cert_match[0], key_match[0], None -def get_client_ssl_credentials(generate_encrypted_key=False): +def get_client_ssl_credentials( + generate_encrypted_key=False, + context_aware_metadata_path=CONTEXT_AWARE_METADATA_PATH, +): """Returns the client side certificate, private key and passphrase. Args: generate_encrypted_key (bool): If set to True, encrypted private key and passphrase will be generated; otherwise, unencrypted private key will be generated and passphrase will be None. + context_aware_metadata_path (str): The context_aware_metadata.json file path. Returns: Tuple[bool, bytes, bytes, bytes]: @@ -158,7 +162,7 @@ def get_client_ssl_credentials(generate_encrypted_key=False): google.auth.exceptions.ClientCertError: if problems occurs when getting the cert, key and passphrase. """ - metadata_path = _check_dca_metadata_path(CONTEXT_AWARE_METADATA_PATH) + metadata_path = _check_dca_metadata_path(context_aware_metadata_path) if metadata_path: metadata_json = _read_dca_metadata_file(metadata_path) diff --git a/packages/google-auth/tests/transport/test__mtls_helper.py b/packages/google-auth/tests/transport/test__mtls_helper.py index 04d0b56d7c03..3b6349a1dcad 100644 --- a/packages/google-auth/tests/transport/test__mtls_helper.py +++ b/packages/google-auth/tests/transport/test__mtls_helper.py @@ -358,6 +358,39 @@ def test_missing_cert_command( with pytest.raises(exceptions.ClientCertError): _mtls_helper.get_client_ssl_credentials() + @mock.patch( + "google.auth.transport._mtls_helper._run_cert_provider_command", autospec=True + ) + @mock.patch( + "google.auth.transport._mtls_helper._read_dca_metadata_file", autospec=True + ) + @mock.patch( + "google.auth.transport._mtls_helper._check_dca_metadata_path", autospec=True + ) + def test_customize_context_aware_metadata_path( + self, + mock_check_dca_metadata_path, + mock_read_dca_metadata_file, + mock_run_cert_provider_command, + ): + context_aware_metadata_path = "/path/to/metata/data" + mock_check_dca_metadata_path.return_value = context_aware_metadata_path + mock_read_dca_metadata_file.return_value = { + "cert_provider_command": ["command"] + } + mock_run_cert_provider_command.return_value = (b"cert", b"key", None) + + has_cert, cert, key, passphrase = _mtls_helper.get_client_ssl_credentials( + context_aware_metadata_path=context_aware_metadata_path + ) + + assert has_cert + assert cert == b"cert" + assert key == b"key" + assert passphrase is None + mock_check_dca_metadata_path.assert_called_with(context_aware_metadata_path) + mock_read_dca_metadata_file.assert_called_with(context_aware_metadata_path) + class TestGetClientCertAndKey(object): def test_callback_success(self): From 4f14b93781dcda167f2da8f101905ece11770b79 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 24 May 2021 14:03:51 -0700 Subject: [PATCH 411/966] chore: release 1.30.1 (#752) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 8 ++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 096df1734835..d75f6028d4ab 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,14 @@ [1]: https://pypi.org/project/google-auth/#history +### [1.30.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.30.0...v1.30.1) (2021-05-20) + + +### Bug Fixes + +* allow user to customize context aware metadata path in _mtls_helper ([#754](https://www.github.com/googleapis/google-auth-library-python/issues/754)) ([e697687](https://www.github.com/googleapis/google-auth-library-python/commit/e6976879b392508c022610ab3ea2ea55c7089c63)) +* fix function name in signing error message ([#751](https://www.github.com/googleapis/google-auth-library-python/issues/751)) ([e9ca25f](https://www.github.com/googleapis/google-auth-library-python/commit/e9ca25fa39a112cc1a376388ab47a4e1b3ea746c)) + ## [1.30.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.29.0...v1.30.0) (2021-04-23) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 4a28e390423e..6f306a7f70d3 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "1.30.0" +__version__ = "1.30.1" From c1e05550e1e7028fc7d669885fef6a5d6be84098 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Mon, 24 May 2021 18:16:01 -0600 Subject: [PATCH 412/966] fix(dependencies): add urllib3 and requests to aiohttp extra (#755) Fixes #707. --- packages/google-auth/noxfile.py | 9 +++++---- packages/google-auth/setup.py | 6 +++++- packages/google-auth/testing/constraints-3.6.txt | 4 +++- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index 0bd7f6c6c40f..236b59c4bfc7 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -69,13 +69,14 @@ def lint(session): ) -@nox.session(python="3.6") +@nox.session(python="3.8") def blacken(session): """Run black. Format code to uniform standard. - This currently uses Python 3.6 due to the automated Kokoro run of synthtool. - That run uses an image that doesn't have 3.6 installed. Before updating this - check the state of the `gcp_ubuntu_config` we use for that Kokoro run. + The Python version should be consistent with what is + supplied in the Python Owlbot postprocessor. + + https://github.com/googleapis/synthtool/blob/master/docker/owlbot/python/Dockerfile """ session.install(BLACK_VERSION) session.run("black", *BLACK_PATHS) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index ef723f8af512..f2fd4e539684 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -31,7 +31,11 @@ ) extras = { - "aiohttp": "aiohttp >= 3.6.2, < 4.0.0dev; python_version>='3.6'", + "aiohttp": [ + "aiohttp >= 3.6.2, < 4.0.0dev; python_version>='3.6'", + "requests >= 2.18.0, < 3.0.0dev", + "urllib3 >= 1.0.0, <2.0.0dev", + ], "pyopenssl": "pyopenssl>=20.0.0", "reauth": "pyu2f>=0.1.5", } diff --git a/packages/google-auth/testing/constraints-3.6.txt b/packages/google-auth/testing/constraints-3.6.txt index ff7f099d4eae..7cb802a8b740 100644 --- a/packages/google-auth/testing/constraints-3.6.txt +++ b/packages/google-auth/testing/constraints-3.6.txt @@ -11,4 +11,6 @@ setuptools==40.3.0 six==1.9.0 rsa==4.6 rsa==3.1.4 -aiohttp==3.6.2 \ No newline at end of file +aiohttp==3.6.2 +requests==2.18.0 +urllib3==1.0.0 From d70edbb5bb8ac302c2bf2ab17c34bd713d4cd5ff Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Tue, 25 May 2021 11:57:22 -0400 Subject: [PATCH 413/966] fix: enforce constraints during unit tests (#760) Drop explicit pin / constraint on 'urllib3': specific 'requests' versions have very narrow pins, and ours is only likely to create conflicts. Bump the 'requests' lower bound to '2.20.0', the lowest version for which our tests pass once constraints are being checked. Closes #759 --- packages/google-auth/noxfile.py | 19 ++++++++++++++----- packages/google-auth/setup.py | 3 +-- .../google-auth/testing/constraints-3.6.txt | 4 +--- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index 236b59c4bfc7..94661df319c7 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -12,10 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -import shutil import os +import pathlib +import shutil + import nox +CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() + TEST_DEPENDENCIES = [ "flask", "freezegun", @@ -84,15 +88,20 @@ def blacken(session): @nox.session(python=["3.6", "3.7", "3.8", "3.9"]) def unit(session): - session.install(*TEST_DEPENDENCIES) - session.install(*(ASYNC_DEPENDENCIES)) - session.install(".") + constraints_path = str( + CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt" + ) + add_constraints = ["-c", constraints_path] + session.install(*(TEST_DEPENDENCIES + add_constraints)) + session.install(*(ASYNC_DEPENDENCIES + add_constraints)) + session.install(".", *add_constraints) session.run( "pytest", f"--junitxml=unit_{session.python}_sponge_log.xml", "--cov=google.auth", "--cov=google.oauth2", "--cov=tests", + "--cov-report=term-missing", "tests", "tests_async", ) @@ -123,7 +132,7 @@ def cover(session): "--cov=google.oauth2", "--cov=tests", "--cov=tests_async", - "--cov-report=", + "--cov-report=term-missing", "tests", "tests_async", ) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index f2fd4e539684..a9bfc9738a20 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -33,8 +33,7 @@ extras = { "aiohttp": [ "aiohttp >= 3.6.2, < 4.0.0dev; python_version>='3.6'", - "requests >= 2.18.0, < 3.0.0dev", - "urllib3 >= 1.0.0, <2.0.0dev", + "requests >= 2.20.0, < 3.0.0dev", ], "pyopenssl": "pyopenssl>=20.0.0", "reauth": "pyu2f>=0.1.5", diff --git a/packages/google-auth/testing/constraints-3.6.txt b/packages/google-auth/testing/constraints-3.6.txt index 7cb802a8b740..ad6f5984605b 100644 --- a/packages/google-auth/testing/constraints-3.6.txt +++ b/packages/google-auth/testing/constraints-3.6.txt @@ -9,8 +9,6 @@ cachetools==2.0.0 pyasn1-modules==0.2.1 setuptools==40.3.0 six==1.9.0 -rsa==4.6 rsa==3.1.4 aiohttp==3.6.2 -requests==2.18.0 -urllib3==1.0.0 +requests==2.20.0 From 4f8f6b6703e8111f91f9883e61cc5074c7651109 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Tue, 1 Jun 2021 14:54:59 -0400 Subject: [PATCH 414/966] chore: migrate to owl bot (#763) * chore: migrate to owl bot * run the post processor --- .../google-auth/.github/.OwlBot.lock.yaml | 18 ++++ packages/google-auth/.github/.OwlBot.yaml | 18 ++++ packages/google-auth/.kokoro/release.sh | 4 +- .../google-auth/.kokoro/release/common.cfg | 14 +-- .../samples/python3.6/periodic-head.cfg | 11 ++ .../samples/python3.7/periodic-head.cfg | 11 ++ .../samples/python3.8/periodic-head.cfg | 11 ++ .../.kokoro/test-samples-against-head.sh | 28 +++++ .../google-auth/.kokoro/test-samples-impl.sh | 102 ++++++++++++++++++ packages/google-auth/.kokoro/test-samples.sh | 96 +++-------------- packages/google-auth/{synth.py => owlbot.py} | 0 packages/google-auth/synth.metadata | 54 ---------- 12 files changed, 219 insertions(+), 148 deletions(-) create mode 100644 packages/google-auth/.github/.OwlBot.lock.yaml create mode 100644 packages/google-auth/.github/.OwlBot.yaml create mode 100644 packages/google-auth/.kokoro/samples/python3.6/periodic-head.cfg create mode 100644 packages/google-auth/.kokoro/samples/python3.7/periodic-head.cfg create mode 100644 packages/google-auth/.kokoro/samples/python3.8/periodic-head.cfg create mode 100755 packages/google-auth/.kokoro/test-samples-against-head.sh create mode 100755 packages/google-auth/.kokoro/test-samples-impl.sh rename packages/google-auth/{synth.py => owlbot.py} (100%) delete mode 100644 packages/google-auth/synth.metadata diff --git a/packages/google-auth/.github/.OwlBot.lock.yaml b/packages/google-auth/.github/.OwlBot.lock.yaml new file mode 100644 index 000000000000..c2fc27e46396 --- /dev/null +++ b/packages/google-auth/.github/.OwlBot.lock.yaml @@ -0,0 +1,18 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +docker: + image: gcr.io/repo-automation-bots/owlbot-python:latest + digest: sha256:c66ba3c8d7bc8566f47df841f98cd0097b28fff0b1864c86f5817f4c8c3e8600 + diff --git a/packages/google-auth/.github/.OwlBot.yaml b/packages/google-auth/.github/.OwlBot.yaml new file mode 100644 index 000000000000..892fbc2910bf --- /dev/null +++ b/packages/google-auth/.github/.OwlBot.yaml @@ -0,0 +1,18 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +docker: + image: gcr.io/repo-automation-bots/owlbot-python:latest + +begin-after-commit-hash: ee56c3493ec6aeb237ff515ecea949710944a20f diff --git a/packages/google-auth/.kokoro/release.sh b/packages/google-auth/.kokoro/release.sh index ded1baeda26e..967bc917e8f3 100755 --- a/packages/google-auth/.kokoro/release.sh +++ b/packages/google-auth/.kokoro/release.sh @@ -26,7 +26,7 @@ python3 -m pip install --upgrade twine wheel setuptools export PYTHONUNBUFFERED=1 # Move into the package, build the distribution and upload. -TWINE_PASSWORD=$(cat "${KOKORO_KEYSTORE_DIR}/73713_google_cloud_pypi_password") +TWINE_PASSWORD=$(cat "${KOKORO_GFILE_DIR}/secret_manager/google-cloud-pypi-token") cd github/google-auth-library-python python3 setup.py sdist bdist_wheel -twine upload --username gcloudpypi --password "${TWINE_PASSWORD}" dist/* +twine upload --username __token__ --password "${TWINE_PASSWORD}" dist/* diff --git a/packages/google-auth/.kokoro/release/common.cfg b/packages/google-auth/.kokoro/release/common.cfg index b56ca902dff7..07334fd501eb 100644 --- a/packages/google-auth/.kokoro/release/common.cfg +++ b/packages/google-auth/.kokoro/release/common.cfg @@ -23,18 +23,8 @@ env_vars: { value: "github/google-auth-library-python/.kokoro/release.sh" } -# Fetch PyPI password -before_action { - fetch_keystore { - keystore_resource { - keystore_config_id: 73713 - keyname: "google_cloud_pypi_password" - } - } -} - # Tokens needed to report release status back to GitHub env_vars: { key: "SECRET_MANAGER_KEYS" - value: "releasetool-publish-reporter-app,releasetool-publish-reporter-googleapis-installation,releasetool-publish-reporter-pem" -} \ No newline at end of file + value: "releasetool-publish-reporter-app,releasetool-publish-reporter-googleapis-installation,releasetool-publish-reporter-pem,google-cloud-pypi-token" +} diff --git a/packages/google-auth/.kokoro/samples/python3.6/periodic-head.cfg b/packages/google-auth/.kokoro/samples/python3.6/periodic-head.cfg new file mode 100644 index 000000000000..f9cfcd33e058 --- /dev/null +++ b/packages/google-auth/.kokoro/samples/python3.6/periodic-head.cfg @@ -0,0 +1,11 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/python-pubsub/.kokoro/test-samples-against-head.sh" +} diff --git a/packages/google-auth/.kokoro/samples/python3.7/periodic-head.cfg b/packages/google-auth/.kokoro/samples/python3.7/periodic-head.cfg new file mode 100644 index 000000000000..f9cfcd33e058 --- /dev/null +++ b/packages/google-auth/.kokoro/samples/python3.7/periodic-head.cfg @@ -0,0 +1,11 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/python-pubsub/.kokoro/test-samples-against-head.sh" +} diff --git a/packages/google-auth/.kokoro/samples/python3.8/periodic-head.cfg b/packages/google-auth/.kokoro/samples/python3.8/periodic-head.cfg new file mode 100644 index 000000000000..f9cfcd33e058 --- /dev/null +++ b/packages/google-auth/.kokoro/samples/python3.8/periodic-head.cfg @@ -0,0 +1,11 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/python-pubsub/.kokoro/test-samples-against-head.sh" +} diff --git a/packages/google-auth/.kokoro/test-samples-against-head.sh b/packages/google-auth/.kokoro/test-samples-against-head.sh new file mode 100755 index 000000000000..e974d9d016ff --- /dev/null +++ b/packages/google-auth/.kokoro/test-samples-against-head.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# A customized test runner for samples. +# +# For periodic builds, you can specify this file for testing against head. + +# `-e` enables the script to automatically fail when a command fails +# `-o pipefail` sets the exit code to the rightmost comment to exit with a non-zero +set -eo pipefail +# Enables `**` to include files nested inside sub-folders +shopt -s globstar + +cd github/google-auth-library-python + +exec .kokoro/test-samples-impl.sh diff --git a/packages/google-auth/.kokoro/test-samples-impl.sh b/packages/google-auth/.kokoro/test-samples-impl.sh new file mode 100755 index 000000000000..cf5de74c17a5 --- /dev/null +++ b/packages/google-auth/.kokoro/test-samples-impl.sh @@ -0,0 +1,102 @@ +#!/bin/bash +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +# `-e` enables the script to automatically fail when a command fails +# `-o pipefail` sets the exit code to the rightmost comment to exit with a non-zero +set -eo pipefail +# Enables `**` to include files nested inside sub-folders +shopt -s globstar + +# Exit early if samples directory doesn't exist +if [ ! -d "./samples" ]; then + echo "No tests run. `./samples` not found" + exit 0 +fi + +# Disable buffering, so that the logs stream through. +export PYTHONUNBUFFERED=1 + +# Debug: show build environment +env | grep KOKORO + +# Install nox +python3.6 -m pip install --upgrade --quiet nox + +# Use secrets acessor service account to get secrets +if [[ -f "${KOKORO_GFILE_DIR}/secrets_viewer_service_account.json" ]]; then + gcloud auth activate-service-account \ + --key-file="${KOKORO_GFILE_DIR}/secrets_viewer_service_account.json" \ + --project="cloud-devrel-kokoro-resources" +fi + +# This script will create 3 files: +# - testing/test-env.sh +# - testing/service-account.json +# - testing/client-secrets.json +./scripts/decrypt-secrets.sh + +source ./testing/test-env.sh +export GOOGLE_APPLICATION_CREDENTIALS=$(pwd)/testing/service-account.json + +# For cloud-run session, we activate the service account for gcloud sdk. +gcloud auth activate-service-account \ + --key-file "${GOOGLE_APPLICATION_CREDENTIALS}" + +export GOOGLE_CLIENT_SECRETS=$(pwd)/testing/client-secrets.json + +echo -e "\n******************** TESTING PROJECTS ********************" + +# Switch to 'fail at end' to allow all tests to complete before exiting. +set +e +# Use RTN to return a non-zero value if the test fails. +RTN=0 +ROOT=$(pwd) +# Find all requirements.txt in the samples directory (may break on whitespace). +for file in samples/**/requirements.txt; do + cd "$ROOT" + # Navigate to the project folder. + file=$(dirname "$file") + cd "$file" + + echo "------------------------------------------------------------" + echo "- testing $file" + echo "------------------------------------------------------------" + + # Use nox to execute the tests for the project. + python3.6 -m nox -s "$RUN_TESTS_SESSION" + EXIT=$? + + # If this is a periodic build, send the test log to the FlakyBot. + # See https://github.com/googleapis/repo-automation-bots/tree/master/packages/flakybot. + if [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"periodic"* ]]; then + chmod +x $KOKORO_GFILE_DIR/linux_amd64/flakybot + $KOKORO_GFILE_DIR/linux_amd64/flakybot + fi + + if [[ $EXIT -ne 0 ]]; then + RTN=1 + echo -e "\n Testing failed: Nox returned a non-zero exit code. \n" + else + echo -e "\n Testing completed.\n" + fi + +done +cd "$ROOT" + +# Workaround for Kokoro permissions issue: delete secrets +rm testing/{test-env.sh,client-secrets.json,service-account.json} + +exit "$RTN" diff --git a/packages/google-auth/.kokoro/test-samples.sh b/packages/google-auth/.kokoro/test-samples.sh index dbfe9fbb028b..6cea6d4f3733 100755 --- a/packages/google-auth/.kokoro/test-samples.sh +++ b/packages/google-auth/.kokoro/test-samples.sh @@ -13,6 +13,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +# The default test runner for samples. +# +# For periodic builds, we rewinds the repo to the latest release, and +# run test-samples-impl.sh. # `-e` enables the script to automatically fail when a command fails # `-o pipefail` sets the exit code to the rightmost comment to exit with a non-zero @@ -24,87 +28,19 @@ cd github/google-auth-library-python # Run periodic samples tests at latest release if [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"periodic"* ]]; then + # preserving the test runner implementation. + cp .kokoro/test-samples-impl.sh "${TMPDIR}/test-samples-impl.sh" + echo "--- IMPORTANT IMPORTANT IMPORTANT ---" + echo "Now we rewind the repo back to the latest release..." LATEST_RELEASE=$(git describe --abbrev=0 --tags) git checkout $LATEST_RELEASE -fi - -# Exit early if samples directory doesn't exist -if [ ! -d "./samples" ]; then - echo "No tests run. `./samples` not found" - exit 0 -fi - -# Disable buffering, so that the logs stream through. -export PYTHONUNBUFFERED=1 - -# Debug: show build environment -env | grep KOKORO - -# Install nox -python3.6 -m pip install --upgrade --quiet nox - -# Use secrets acessor service account to get secrets -if [[ -f "${KOKORO_GFILE_DIR}/secrets_viewer_service_account.json" ]]; then - gcloud auth activate-service-account \ - --key-file="${KOKORO_GFILE_DIR}/secrets_viewer_service_account.json" \ - --project="cloud-devrel-kokoro-resources" -fi - -# This script will create 3 files: -# - testing/test-env.sh -# - testing/service-account.json -# - testing/client-secrets.json -./scripts/decrypt-secrets.sh - -source ./testing/test-env.sh -export GOOGLE_APPLICATION_CREDENTIALS=$(pwd)/testing/service-account.json - -# For cloud-run session, we activate the service account for gcloud sdk. -gcloud auth activate-service-account \ - --key-file "${GOOGLE_APPLICATION_CREDENTIALS}" - -export GOOGLE_CLIENT_SECRETS=$(pwd)/testing/client-secrets.json - -echo -e "\n******************** TESTING PROJECTS ********************" - -# Switch to 'fail at end' to allow all tests to complete before exiting. -set +e -# Use RTN to return a non-zero value if the test fails. -RTN=0 -ROOT=$(pwd) -# Find all requirements.txt in the samples directory (may break on whitespace). -for file in samples/**/requirements.txt; do - cd "$ROOT" - # Navigate to the project folder. - file=$(dirname "$file") - cd "$file" - - echo "------------------------------------------------------------" - echo "- testing $file" - echo "------------------------------------------------------------" - - # Use nox to execute the tests for the project. - python3.6 -m nox -s "$RUN_TESTS_SESSION" - EXIT=$? - - # If this is a periodic build, send the test log to the FlakyBot. - # See https://github.com/googleapis/repo-automation-bots/tree/master/packages/flakybot. - if [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"periodic"* ]]; then - chmod +x $KOKORO_GFILE_DIR/linux_amd64/flakybot - $KOKORO_GFILE_DIR/linux_amd64/flakybot + echo "The current head is: " + echo $(git rev-parse --verify HEAD) + echo "--- IMPORTANT IMPORTANT IMPORTANT ---" + # move back the test runner implementation if there's no file. + if [ ! -f .kokoro/test-samples-impl.sh ]; then + cp "${TMPDIR}/test-samples-impl.sh" .kokoro/test-samples-impl.sh fi +fi - if [[ $EXIT -ne 0 ]]; then - RTN=1 - echo -e "\n Testing failed: Nox returned a non-zero exit code. \n" - else - echo -e "\n Testing completed.\n" - fi - -done -cd "$ROOT" - -# Workaround for Kokoro permissions issue: delete secrets -rm testing/{test-env.sh,client-secrets.json,service-account.json} - -exit "$RTN" +exec .kokoro/test-samples-impl.sh diff --git a/packages/google-auth/synth.py b/packages/google-auth/owlbot.py similarity index 100% rename from packages/google-auth/synth.py rename to packages/google-auth/owlbot.py diff --git a/packages/google-auth/synth.metadata b/packages/google-auth/synth.metadata deleted file mode 100644 index 0de642bf785c..000000000000 --- a/packages/google-auth/synth.metadata +++ /dev/null @@ -1,54 +0,0 @@ -{ - "sources": [ - { - "git": { - "name": ".", - "remote": "https://github.com/googleapis/google-auth-library-python.git", - "sha": "f062da8392c32fb3306cdc6e4dbae78212aa0dc7" - } - }, - { - "git": { - "name": "synthtool", - "remote": "https://github.com/googleapis/synthtool.git", - "sha": "16ec872dd898d7de6e1822badfac32484b5d9031" - } - } - ], - "generatedFiles": [ - ".kokoro/build.sh", - ".kokoro/continuous/common.cfg", - ".kokoro/continuous/continuous.cfg", - ".kokoro/docker/docs/Dockerfile", - ".kokoro/docker/docs/fetch_gpg_keys.sh", - ".kokoro/docs/common.cfg", - ".kokoro/docs/docs-presubmit.cfg", - ".kokoro/docs/docs.cfg", - ".kokoro/populate-secrets.sh", - ".kokoro/presubmit/common.cfg", - ".kokoro/presubmit/presubmit.cfg", - ".kokoro/publish-docs.sh", - ".kokoro/release.sh", - ".kokoro/release/common.cfg", - ".kokoro/release/release.cfg", - ".kokoro/samples/lint/common.cfg", - ".kokoro/samples/lint/continuous.cfg", - ".kokoro/samples/lint/periodic.cfg", - ".kokoro/samples/lint/presubmit.cfg", - ".kokoro/samples/python3.6/common.cfg", - ".kokoro/samples/python3.6/continuous.cfg", - ".kokoro/samples/python3.6/periodic.cfg", - ".kokoro/samples/python3.6/presubmit.cfg", - ".kokoro/samples/python3.7/common.cfg", - ".kokoro/samples/python3.7/continuous.cfg", - ".kokoro/samples/python3.7/periodic.cfg", - ".kokoro/samples/python3.7/presubmit.cfg", - ".kokoro/samples/python3.8/common.cfg", - ".kokoro/samples/python3.8/continuous.cfg", - ".kokoro/samples/python3.8/periodic.cfg", - ".kokoro/samples/python3.8/presubmit.cfg", - ".kokoro/test-samples.sh", - ".kokoro/trampoline.sh", - ".kokoro/trampoline_v2.sh" - ] -} \ No newline at end of file From fd8efd15df87833cb067c2e12736938a1935ea09 Mon Sep 17 00:00:00 2001 From: Slava Date: Thu, 3 Jun 2021 10:44:40 +0300 Subject: [PATCH 415/966] fix: session object was never used in aiohttp request (#700) (#701) * fix: session object was never used in aiohttp request (#700) * fixup! fix: session object was never used in aiohttp request (#700) * fixup! fixup! fix: session object was never used in aiohttp request (#700) Co-authored-by: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> --- .../google/auth/transport/_aiohttp_requests.py | 7 ++++++- .../system_tests/system_tests_async/conftest.py | 14 ++++++++++---- .../transport/test_aiohttp_requests.py | 15 ++++++++++++--- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/packages/google-auth/google/auth/transport/_aiohttp_requests.py b/packages/google-auth/google/auth/transport/_aiohttp_requests.py index 4293810dd81d..ab7dfef67740 100644 --- a/packages/google-auth/google/auth/transport/_aiohttp_requests.py +++ b/packages/google-auth/google/auth/transport/_aiohttp_requests.py @@ -138,7 +138,12 @@ class Request(transport.Request): """ def __init__(self, session=None): - self.session = None + # TODO: Use auto_decompress property for aiohttp 3.7+ + if session is not None and session._auto_decompress: + raise ValueError( + "Client sessions with auto_decompress=True are not supported." + ) + self.session = session async def __call__( self, diff --git a/packages/google-auth/system_tests/system_tests_async/conftest.py b/packages/google-auth/system_tests/system_tests_async/conftest.py index 47a473e7fe1a..9669099245dc 100644 --- a/packages/google-auth/system_tests/system_tests_async/conftest.py +++ b/packages/google-auth/system_tests/system_tests_async/conftest.py @@ -26,9 +26,7 @@ from google.auth.transport import _aiohttp_requests as aiohttp_requests from system_tests.system_tests_sync import conftest as sync_conftest -ASYNC_REQUESTS_SESSION = aiohttp.ClientSession() -ASYNC_REQUESTS_SESSION.verify = False TOKEN_INFO_URL = "https://www.googleapis.com/oauth2/v3/tokeninfo" @@ -49,10 +47,18 @@ def authorized_user_file(): """The full path to a valid authorized user file.""" yield sync_conftest.AUTHORIZED_USER_FILE + +@pytest.fixture +async def aiohttp_session(): + async with aiohttp.ClientSession(auto_decompress=False) as session: + yield session + + @pytest.fixture(params=["aiohttp"]) -async def http_request(request): +async def http_request(request, aiohttp_session): """A transport.request object.""" - yield aiohttp_requests.Request(ASYNC_REQUESTS_SESSION) + yield aiohttp_requests.Request(aiohttp_session) + @pytest.fixture async def token_info(http_request): diff --git a/packages/google-auth/tests_async/transport/test_aiohttp_requests.py b/packages/google-auth/tests_async/transport/test_aiohttp_requests.py index 10c31db8ff65..a64a4eec9aec 100644 --- a/packages/google-auth/tests_async/transport/test_aiohttp_requests.py +++ b/packages/google-auth/tests_async/transport/test_aiohttp_requests.py @@ -112,11 +112,18 @@ def make_request(self): return aiohttp_requests.Request() def make_with_parameter_request(self): - http = mock.create_autospec(aiohttp.ClientSession, instance=True) + http = aiohttp.ClientSession(auto_decompress=False) return aiohttp_requests.Request(http) + def test_unsupported_session(self): + http = aiohttp.ClientSession(auto_decompress=True) + with pytest.raises(ValueError): + aiohttp_requests.Request(http) + def test_timeout(self): - http = mock.create_autospec(aiohttp.ClientSession, instance=True) + http = mock.create_autospec( + aiohttp.ClientSession, instance=True, _auto_decompress=False + ) request = aiohttp_requests.Request(http) request(url="http://example.com", method="GET", timeout=5) @@ -142,7 +149,9 @@ def test_constructor(self): assert authed_session.credentials == mock.sentinel.credentials def test_constructor_with_auth_request(self): - http = mock.create_autospec(aiohttp.ClientSession) + http = mock.create_autospec( + aiohttp.ClientSession, instance=True, _auto_decompress=False + ) auth_request = aiohttp_requests.Request(http) authed_session = aiohttp_requests.AuthorizedSession( From e46043d74cdcd43766d61e891e481b329b846408 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 8 Jun 2021 09:00:36 -0600 Subject: [PATCH 416/966] chore: release 1.30.2 (#756) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 9 +++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index d75f6028d4ab..ef65ebd9e619 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,15 @@ [1]: https://pypi.org/project/google-auth/#history +### [1.30.2](https://www.github.com/googleapis/google-auth-library-python/compare/v1.30.1...v1.30.2) (2021-06-03) + + +### Bug Fixes + +* **dependencies:** add urllib3 and requests to aiohttp extra ([#755](https://www.github.com/googleapis/google-auth-library-python/issues/755)) ([a923442](https://www.github.com/googleapis/google-auth-library-python/commit/a9234423cb2b69068fc0d30a5a0ee86a599ab8b7)) +* enforce constraints during unit tests ([#760](https://www.github.com/googleapis/google-auth-library-python/issues/760)) ([1a6496a](https://www.github.com/googleapis/google-auth-library-python/commit/1a6496abfc17ab781bfa485dc74d0f7dbbe0c44b)), closes [#759](https://www.github.com/googleapis/google-auth-library-python/issues/759) +* session object was never used in aiohttp request ([#700](https://www.github.com/googleapis/google-auth-library-python/issues/700)) ([#701](https://www.github.com/googleapis/google-auth-library-python/issues/701)) ([09e0389](https://www.github.com/googleapis/google-auth-library-python/commit/09e0389db72cc9d6c5dde34864cb54d717dc0b92)) + ### [1.30.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.30.0...v1.30.1) (2021-05-20) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 6f306a7f70d3..5e7a79cc22cf 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "1.30.1" +__version__ = "1.30.2" From e909305b02a41a8b7e56f1cdb9fba0d83c40b6ee Mon Sep 17 00:00:00 2001 From: bojeil-google Date: Wed, 9 Jun 2021 07:58:25 -0700 Subject: [PATCH 417/966] feat: define useful properties on `google.auth.external_account.Credentials` (#770) This includes the following properties: - `info`: This is the reverse of `from_info` defined on subclasses and useful to serialize external account credentials. - `service_account_email`: This is the corresponding service account email if impersonation is used. - `is_user`: This is `False` for workload identity pools and `True` for workforce pools (not yet supported). This can be mainly determined from the STS audience. While the properties will primarily facilitate integration with gcloud, they are publicly useful for other contexts. --- .../google/auth/external_account.py | 84 +++++++++++-- packages/google-auth/tests/test_aws.py | 13 ++ .../tests/test_external_account.py | 116 ++++++++++++++++++ .../google-auth/tests/test_identity_pool.py | 26 ++++ 4 files changed, 231 insertions(+), 8 deletions(-) diff --git a/packages/google-auth/google/auth/external_account.py b/packages/google-auth/google/auth/external_account.py index 0429ee08f4ff..e40c6528bd75 100644 --- a/packages/google-auth/google/auth/external_account.py +++ b/packages/google-auth/google/auth/external_account.py @@ -28,8 +28,10 @@ """ import abc +import copy import datetime import json +import re import six @@ -40,6 +42,8 @@ from google.oauth2 import sts from google.oauth2 import utils +# External account JSON type identifier. +_EXTERNAL_ACCOUNT_JSON_TYPE = "external_account" # The token exchange grant_type used for exchanging credentials. _STS_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:token-exchange" # The token exchange requested_token_type. This is always an access_token. @@ -117,6 +121,76 @@ def __init__( self._impersonated_credentials = None self._project_id = None + @property + def info(self): + """Generates the dictionary representation of the current credentials. + + Returns: + Mapping: The dictionary representation of the credentials. This is the + reverse of "from_info" defined on the subclasses of this class. It is + useful for serializing the current credentials so it can deserialized + later. + """ + config_info = { + "type": _EXTERNAL_ACCOUNT_JSON_TYPE, + "audience": self._audience, + "subject_token_type": self._subject_token_type, + "token_url": self._token_url, + "service_account_impersonation_url": self._service_account_impersonation_url, + "credential_source": copy.deepcopy(self._credential_source), + "quota_project_id": self._quota_project_id, + "client_id": self._client_id, + "client_secret": self._client_secret, + } + # Remove None fields in the info dictionary. + for k, v in dict(config_info).items(): + if v is None: + del config_info[k] + + return config_info + + @property + def service_account_email(self): + """Returns the service account email if service account impersonation is used. + + Returns: + Optional[str]: The service account email if impersonation is used. Otherwise + None is returned. + """ + if self._service_account_impersonation_url: + # Parse email from URL. The formal looks as follows: + # https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/name@project-id.iam.gserviceaccount.com:generateAccessToken + url = self._service_account_impersonation_url + start_index = url.rfind("/") + end_index = url.find(":generateAccessToken") + if start_index != -1 and end_index != -1 and start_index < end_index: + start_index = start_index + 1 + return url[start_index:end_index] + return None + + @property + def is_user(self): + """Returns whether the credentials represent a user (True) or workload (False). + Workloads behave similarly to service accounts. Currently workloads will use + service account impersonation but will eventually not require impersonation. + As a result, this property is more reliable than the service account email + property in determining if the credentials represent a user or workload. + + Returns: + bool: True if the credentials represent a user. False if they represent a + workload. + """ + # If service account impersonation is used, the credentials will always represent a + # service account. + if self._service_account_impersonation_url: + return False + # Workforce pools representing users have the following audience format: + # //iam.googleapis.com/locations/$location/workforcePools/$poolId/providers/$providerId + p = re.compile(r"//iam\.googleapis\.com/locations/[^/]+/workforcePools/") + if p.match(self._audience): + return True + return False + @property def requires_scopes(self): """Checks if the credentials requires scopes. @@ -282,14 +356,8 @@ def _initialize_impersonated_credentials(self): ) # Determine target_principal. - start_index = self._service_account_impersonation_url.rfind("/") - end_index = self._service_account_impersonation_url.find(":generateAccessToken") - if start_index != -1 and end_index != -1 and start_index < end_index: - start_index = start_index + 1 - target_principal = self._service_account_impersonation_url[ - start_index:end_index - ] - else: + target_principal = self.service_account_email + if not target_principal: raise exceptions.RefreshError( "Unable to determine target principal from service account impersonation URL." ) diff --git a/packages/google-auth/tests/test_aws.py b/packages/google-auth/tests/test_aws.py index 7c7ee36bee21..9ca08d5b2cdc 100644 --- a/packages/google-auth/tests/test_aws.py +++ b/packages/google-auth/tests/test_aws.py @@ -919,6 +919,19 @@ def test_constructor_invalid_environment_id_version(self): assert excinfo.match(r"aws version '3' is not supported in the current build.") + def test_info(self): + credentials = self.make_credentials( + credential_source=self.CREDENTIAL_SOURCE.copy() + ) + + assert credentials.info == { + "type": "external_account", + "audience": AUDIENCE, + "subject_token_type": SUBJECT_TOKEN_TYPE, + "token_url": TOKEN_URL, + "credential_source": self.CREDENTIAL_SOURCE, + } + def test_retrieve_subject_token_missing_region_url(self): # When AWS_REGION envvar is not available, region_url is required for # determining the current AWS region. diff --git a/packages/google-auth/tests/test_external_account.py b/packages/google-auth/tests/test_external_account.py index 8f8d98009c1f..7390fb980f2d 100644 --- a/packages/google-auth/tests/test_external_account.py +++ b/packages/google-auth/tests/test_external_account.py @@ -31,6 +31,12 @@ # Base64 encoding of "username:password" BASIC_AUTH_ENCODING = "dXNlcm5hbWU6cGFzc3dvcmQ=" SERVICE_ACCOUNT_EMAIL = "service-1234@service-name.iam.gserviceaccount.com" +# List of valid workforce pool audiences. +TEST_USER_AUDIENCES = [ + "//iam.googleapis.com/locations/global/workforcePools/pool-id/providers/provider-id", + "//iam.googleapis.com/locations/eu/workforcePools/pool-id/providers/provider-id", + "//iam.googleapis.com/locations/eu/workforcePools/workloadIdentityPools/providers/provider-id", +] class CredentialsImpl(external_account.Credentials): @@ -342,6 +348,116 @@ def test_with_invalid_impersonation_target_principal(self): r"Unable to determine target principal from service account impersonation URL." ) + def test_info(self): + credentials = self.make_credentials() + + assert credentials.info == { + "type": "external_account", + "audience": self.AUDIENCE, + "subject_token_type": self.SUBJECT_TOKEN_TYPE, + "token_url": self.TOKEN_URL, + "credential_source": self.CREDENTIAL_SOURCE.copy(), + } + + def test_info_with_full_options(self): + credentials = self.make_credentials( + client_id=CLIENT_ID, + client_secret=CLIENT_SECRET, + quota_project_id=self.QUOTA_PROJECT_ID, + service_account_impersonation_url=self.SERVICE_ACCOUNT_IMPERSONATION_URL, + ) + + assert credentials.info == { + "type": "external_account", + "audience": self.AUDIENCE, + "subject_token_type": self.SUBJECT_TOKEN_TYPE, + "token_url": self.TOKEN_URL, + "service_account_impersonation_url": self.SERVICE_ACCOUNT_IMPERSONATION_URL, + "credential_source": self.CREDENTIAL_SOURCE.copy(), + "quota_project_id": self.QUOTA_PROJECT_ID, + "client_id": CLIENT_ID, + "client_secret": CLIENT_SECRET, + } + + def test_service_account_email_without_impersonation(self): + credentials = self.make_credentials() + + assert credentials.service_account_email is None + + def test_service_account_email_with_impersonation(self): + credentials = self.make_credentials( + service_account_impersonation_url=self.SERVICE_ACCOUNT_IMPERSONATION_URL + ) + + assert credentials.service_account_email == SERVICE_ACCOUNT_EMAIL + + @pytest.mark.parametrize( + "audience", + # Workload identity pool audiences or invalid workforce pool audiences. + [ + # Legacy K8s audience format. + "identitynamespace:1f12345:my_provider", + ( + "//iam.googleapis.com/projects/123456/locations/" + "global/workloadIdentityPools/pool-id/providers/" + "provider-id" + ), + ( + "//iam.googleapis.com/projects/123456/locations/" + "eu/workloadIdentityPools/pool-id/providers/" + "provider-id" + ), + # Pool ID with workforcePools string. + ( + "//iam.googleapis.com/projects/123456/locations/" + "global/workloadIdentityPools/workforcePools/providers/" + "provider-id" + ), + # Unrealistic / incorrect workforce pool audiences. + "//iamgoogleapis.com/locations/eu/workforcePools/pool-id/providers/provider-id", + "//iam.googleapiscom/locations/eu/workforcePools/pool-id/providers/provider-id", + "//iam.googleapis.com/locations/workforcePools/pool-id/providers/provider-id", + "//iam.googleapis.com/locations/eu/workforcePool/pool-id/providers/provider-id", + "//iam.googleapis.com/locations//workforcePool/pool-id/providers/provider-id", + ], + ) + def test_is_user_with_non_users(self, audience): + credentials = CredentialsImpl( + audience=audience, + subject_token_type=self.SUBJECT_TOKEN_TYPE, + token_url=self.TOKEN_URL, + credential_source=self.CREDENTIAL_SOURCE, + ) + + assert credentials.is_user is False + + @pytest.mark.parametrize("audience", TEST_USER_AUDIENCES) + def test_is_user_with_users(self, audience): + credentials = CredentialsImpl( + audience=audience, + subject_token_type=self.SUBJECT_TOKEN_TYPE, + token_url=self.TOKEN_URL, + credential_source=self.CREDENTIAL_SOURCE, + ) + + assert credentials.is_user is True + + @pytest.mark.parametrize("audience", TEST_USER_AUDIENCES) + def test_is_user_with_users_and_impersonation(self, audience): + # Initialize the credentials with service account impersonation. + credentials = CredentialsImpl( + audience=audience, + subject_token_type=self.SUBJECT_TOKEN_TYPE, + token_url=self.TOKEN_URL, + credential_source=self.CREDENTIAL_SOURCE, + service_account_impersonation_url=self.SERVICE_ACCOUNT_IMPERSONATION_URL, + ) + + # Even though the audience is for a workforce pool, since service account + # impersonation is used, the credentials will represent a service account and + # not a user. + assert credentials.is_user is False + @mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) def test_refresh_without_client_auth_success(self, unused_utcnow): response = self.SUCCESS_RESPONSE.copy() diff --git a/packages/google-auth/tests/test_identity_pool.py b/packages/google-auth/tests/test_identity_pool.py index 90a0e2549a1e..b529268fb0ec 100644 --- a/packages/google-auth/tests/test_identity_pool.py +++ b/packages/google-auth/tests/test_identity_pool.py @@ -430,6 +430,32 @@ def test_constructor_missing_subject_token_field_name(self): r"Missing subject_token_field_name for JSON credential_source format" ) + def test_info_with_file_credential_source(self): + credentials = self.make_credentials( + credential_source=self.CREDENTIAL_SOURCE_TEXT_URL.copy() + ) + + assert credentials.info == { + "type": "external_account", + "audience": AUDIENCE, + "subject_token_type": SUBJECT_TOKEN_TYPE, + "token_url": TOKEN_URL, + "credential_source": self.CREDENTIAL_SOURCE_TEXT_URL, + } + + def test_info_with_url_credential_source(self): + credentials = self.make_credentials( + credential_source=self.CREDENTIAL_SOURCE_JSON_URL.copy() + ) + + assert credentials.info == { + "type": "external_account", + "audience": AUDIENCE, + "subject_token_type": SUBJECT_TOKEN_TYPE, + "token_url": TOKEN_URL, + "credential_source": self.CREDENTIAL_SOURCE_JSON_URL, + } + def test_retrieve_subject_token_missing_subject_token(self, tmpdir): # Provide empty text file. empty_file = tmpdir.join("empty.txt") From 0915dc1f1e07655e79b7657e25df48d8b2d3636e Mon Sep 17 00:00:00 2001 From: bojeil-google Date: Wed, 9 Jun 2021 12:27:21 -0700 Subject: [PATCH 418/966] fix: avoid deleting items while iterating (#772) Updates `google.auth.external_account.Credentials#info` to not delete items in the dictionary while iterating. --- packages/google-auth/google/auth/external_account.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/google-auth/google/auth/external_account.py b/packages/google-auth/google/auth/external_account.py index e40c6528bd75..1f3034ac3533 100644 --- a/packages/google-auth/google/auth/external_account.py +++ b/packages/google-auth/google/auth/external_account.py @@ -142,12 +142,7 @@ def info(self): "client_id": self._client_id, "client_secret": self._client_secret, } - # Remove None fields in the info dictionary. - for k, v in dict(config_info).items(): - if v is None: - del config_info[k] - - return config_info + return {key: value for key, value in config_info.items() if value is not None} @property def service_account_email(self): From 7856f86899c764f5ecdb199bfd9000e4ca685fe6 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 10 Jun 2021 08:37:21 -0600 Subject: [PATCH 419/966] chore: release 1.31.0 (#771) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 12 ++++++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index ef65ebd9e619..8a622423daa5 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,18 @@ [1]: https://pypi.org/project/google-auth/#history +## [1.31.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.30.2...v1.31.0) (2021-06-09) + + +### Features + +* define useful properties on `google.auth.external_account.Credentials` ([#770](https://www.github.com/googleapis/google-auth-library-python/issues/770)) ([f97499c](https://www.github.com/googleapis/google-auth-library-python/commit/f97499c718af70d17c17e0c58d6381273eceabcd)) + + +### Bug Fixes + +* avoid deleting items while iterating ([#772](https://www.github.com/googleapis/google-auth-library-python/issues/772)) ([a5e6b65](https://www.github.com/googleapis/google-auth-library-python/commit/a5e6b651aa8ad407ce087fe32f40b46925bae527)) + ### [1.30.2](https://www.github.com/googleapis/google-auth-library-python/compare/v1.30.1...v1.30.2) (2021-06-03) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 5e7a79cc22cf..a8d55991eee1 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "1.30.2" +__version__ = "1.31.0" From 4057e0aad3bb045c37c6d9e7fa31abc1806641c8 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Fri, 11 Jun 2021 00:29:59 -0600 Subject: [PATCH 420/966] test: switch authorized user account (#774) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10323 -> 10323 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 29e06923f0f028b54d1b571dc218cdd92f751bd8..2e9cacd05c2b5940cfca5ab0315731caf5c1ccb0 100644 GIT binary patch literal 10323 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 Date: Wed, 16 Jun 2021 15:30:36 -0700 Subject: [PATCH 421/966] feat: allow scopes for self signed jwt (#776) * feat: allow scopes for self signed jwt * Update service_account.py * add http changes * Update google/auth/jwt.py --- packages/google-auth/google/auth/jwt.py | 3 +- .../google-auth/google/auth/transport/grpc.py | 7 +- .../google/auth/transport/requests.py | 8 +-- .../google/auth/transport/urllib3.py | 8 +-- .../google/oauth2/service_account.py | 49 +++++++++++++- .../tests/oauth2/test_service_account.py | 66 +++++++++++++++++++ packages/google-auth/tests/test_jwt.py | 12 ++++ .../google-auth/tests/transport/test_grpc.py | 3 +- .../tests/transport/test_requests.py | 2 +- .../tests/transport/test_urllib3.py | 2 +- 10 files changed, 136 insertions(+), 24 deletions(-) diff --git a/packages/google-auth/google/auth/jwt.py b/packages/google-auth/google/auth/jwt.py index 892f3a88a496..e9f4f69ca0a6 100644 --- a/packages/google-auth/google/auth/jwt.py +++ b/packages/google-auth/google/auth/jwt.py @@ -525,8 +525,9 @@ def _make_jwt(self): "sub": self._subject, "iat": _helpers.datetime_to_secs(now), "exp": _helpers.datetime_to_secs(expiry), - "aud": self._audience, } + if self._audience: + payload["aud"] = self._audience payload.update(self._additional_claims) diff --git a/packages/google-auth/google/auth/transport/grpc.py b/packages/google-auth/google/auth/transport/grpc.py index 04c0f4f55fee..c47cb3ddafcd 100644 --- a/packages/google-auth/google/auth/transport/grpc.py +++ b/packages/google-auth/google/auth/transport/grpc.py @@ -79,12 +79,9 @@ def _get_authorization_headers(self, context): # Attempt to use self-signed JWTs when a service account is used. # A default host must be explicitly provided since it cannot always # be determined from the context.service_url. - if ( - isinstance(self._credentials, service_account.Credentials) - and self._default_host - ): + if isinstance(self._credentials, service_account.Credentials): self._credentials._create_self_signed_jwt( - "https://{}/".format(self._default_host) + "https://{}/".format(self._default_host) if self._default_host else None ) self._credentials.before_request( diff --git a/packages/google-auth/google/auth/transport/requests.py b/packages/google-auth/google/auth/transport/requests.py index d317544b74f7..a4784b3865bd 100644 --- a/packages/google-auth/google/auth/transport/requests.py +++ b/packages/google-auth/google/auth/transport/requests.py @@ -358,13 +358,9 @@ def __init__( # https://google.aip.dev/auth/4111 # Attempt to use self-signed JWTs when a service account is used. - # A default host must be explicitly provided. - if ( - isinstance(self.credentials, service_account.Credentials) - and self._default_host - ): + if isinstance(self.credentials, service_account.Credentials): self.credentials._create_self_signed_jwt( - "https://{}/".format(self._default_host) + "https://{}/".format(self._default_host) if self._default_host else None ) def configure_mtls_channel(self, client_cert_callback=None): diff --git a/packages/google-auth/google/auth/transport/urllib3.py b/packages/google-auth/google/auth/transport/urllib3.py index aadd116e84a0..6a2504d972ef 100644 --- a/packages/google-auth/google/auth/transport/urllib3.py +++ b/packages/google-auth/google/auth/transport/urllib3.py @@ -293,13 +293,9 @@ def __init__( # https://google.aip.dev/auth/4111 # Attempt to use self-signed JWTs when a service account is used. - # A default host must be explicitly provided. - if ( - isinstance(self.credentials, service_account.Credentials) - and self._default_host - ): + if isinstance(self.credentials, service_account.Credentials): self.credentials._create_self_signed_jwt( - "https://{}/".format(self._default_host) + "https://{}/".format(self._default_host) if self._default_host else None ) super(AuthorizedHttp, self).__init__() diff --git a/packages/google-auth/google/oauth2/service_account.py b/packages/google-auth/google/oauth2/service_account.py index 1ccfa19c0377..dd3658994336 100644 --- a/packages/google-auth/google/oauth2/service_account.py +++ b/packages/google-auth/google/oauth2/service_account.py @@ -131,6 +131,7 @@ def __init__( project_id=None, quota_project_id=None, additional_claims=None, + always_use_jwt_access=False, ): """ Args: @@ -149,6 +150,8 @@ def __init__( billing. additional_claims (Mapping[str, str]): Any additional claims for the JWT assertion used in the authorization grant. + always_use_jwt_access (Optional[bool]): Whether self signed JWT should + be always used. .. note:: Typically one of the helper constructors :meth:`from_service_account_file` or @@ -165,6 +168,7 @@ def __init__( self._project_id = project_id self._quota_project_id = quota_project_id self._token_uri = token_uri + self._always_use_jwt_access = always_use_jwt_access self._jwt_credentials = None @@ -266,6 +270,30 @@ def with_scopes(self, scopes, default_scopes=None): project_id=self._project_id, quota_project_id=self._quota_project_id, additional_claims=self._additional_claims.copy(), + always_use_jwt_access=self._always_use_jwt_access, + ) + + def with_always_use_jwt_access(self, always_use_jwt_access): + """Create a copy of these credentials with the specified always_use_jwt_access value. + + Args: + always_use_jwt_access (bool): Whether always use self signed JWT or not. + + Returns: + google.auth.service_account.Credentials: A new credentials + instance. + """ + return self.__class__( + self._signer, + service_account_email=self._service_account_email, + scopes=self._scopes, + default_scopes=self._default_scopes, + token_uri=self._token_uri, + subject=self._subject, + project_id=self._project_id, + quota_project_id=self._quota_project_id, + additional_claims=self._additional_claims.copy(), + always_use_jwt_access=always_use_jwt_access, ) def with_subject(self, subject): @@ -288,6 +316,7 @@ def with_subject(self, subject): project_id=self._project_id, quota_project_id=self._quota_project_id, additional_claims=self._additional_claims.copy(), + always_use_jwt_access=self._always_use_jwt_access, ) def with_claims(self, additional_claims): @@ -315,6 +344,7 @@ def with_claims(self, additional_claims): project_id=self._project_id, quota_project_id=self._quota_project_id, additional_claims=new_additional_claims, + always_use_jwt_access=self._always_use_jwt_access, ) @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) @@ -330,6 +360,7 @@ def with_quota_project(self, quota_project_id): project_id=self._project_id, quota_project_id=quota_project_id, additional_claims=self._additional_claims.copy(), + always_use_jwt_access=self._always_use_jwt_access, ) def _make_authorization_grant_assertion(self): @@ -386,8 +417,22 @@ def _create_self_signed_jwt(self, audience): audience (str): The service URL. ``https://[API_ENDPOINT]/`` """ # https://google.aip.dev/auth/4111 - # If the user has not defined scopes, create a self-signed jwt - if not self.scopes: + if self._always_use_jwt_access: + if self._scopes: + self._jwt_credentials = jwt.Credentials.from_signing_credentials( + self, None, additional_claims={"scope": " ".join(self._scopes)} + ) + elif audience: + self._jwt_credentials = jwt.Credentials.from_signing_credentials( + self, audience + ) + elif self._default_scopes: + self._jwt_credentials = jwt.Credentials.from_signing_credentials( + self, + None, + additional_claims={"scope": " ".join(self._default_scopes)}, + ) + elif not self._scopes and audience: self._jwt_credentials = jwt.Credentials.from_signing_credentials( self, audience ) diff --git a/packages/google-auth/tests/oauth2/test_service_account.py b/packages/google-auth/tests/oauth2/test_service_account.py index 648541e2bb85..5852d37146b8 100644 --- a/packages/google-auth/tests/oauth2/test_service_account.py +++ b/packages/google-auth/tests/oauth2/test_service_account.py @@ -155,6 +155,13 @@ def test_with_quota_project(self): new_credentials.apply(hdrs, token="tok") assert "x-goog-user-project" in hdrs + def test__with_always_use_jwt_access(self): + credentials = self.make_credentials() + assert not credentials._always_use_jwt_access + + new_credentials = credentials.with_always_use_jwt_access(True) + assert new_credentials._always_use_jwt_access + def test__make_authorization_grant_assertion(self): credentials = self.make_credentials() token = credentials._make_authorization_grant_assertion() @@ -225,6 +232,65 @@ def test__create_self_signed_jwt_with_user_scopes(self, jwt): # JWT should not be created if there are user-defined scopes jwt.from_signing_credentials.assert_not_called() + @mock.patch("google.auth.jwt.Credentials", instance=True, autospec=True) + def test__create_self_signed_jwt_always_use_jwt_access_with_audience(self, jwt): + credentials = service_account.Credentials( + SIGNER, + self.SERVICE_ACCOUNT_EMAIL, + self.TOKEN_URI, + default_scopes=["bar", "foo"], + always_use_jwt_access=True, + ) + + audience = "https://pubsub.googleapis.com" + credentials._create_self_signed_jwt(audience) + jwt.from_signing_credentials.assert_called_once_with(credentials, audience) + + @mock.patch("google.auth.jwt.Credentials", instance=True, autospec=True) + def test__create_self_signed_jwt_always_use_jwt_access_with_scopes(self, jwt): + credentials = service_account.Credentials( + SIGNER, + self.SERVICE_ACCOUNT_EMAIL, + self.TOKEN_URI, + scopes=["bar", "foo"], + always_use_jwt_access=True, + ) + + audience = "https://pubsub.googleapis.com" + credentials._create_self_signed_jwt(audience) + jwt.from_signing_credentials.assert_called_once_with( + credentials, None, additional_claims={"scope": "bar foo"} + ) + + @mock.patch("google.auth.jwt.Credentials", instance=True, autospec=True) + def test__create_self_signed_jwt_always_use_jwt_access_with_default_scopes( + self, jwt + ): + credentials = service_account.Credentials( + SIGNER, + self.SERVICE_ACCOUNT_EMAIL, + self.TOKEN_URI, + default_scopes=["bar", "foo"], + always_use_jwt_access=True, + ) + + credentials._create_self_signed_jwt(None) + jwt.from_signing_credentials.assert_called_once_with( + credentials, None, additional_claims={"scope": "bar foo"} + ) + + @mock.patch("google.auth.jwt.Credentials", instance=True, autospec=True) + def test__create_self_signed_jwt_always_use_jwt_access(self, jwt): + credentials = service_account.Credentials( + SIGNER, + self.SERVICE_ACCOUNT_EMAIL, + self.TOKEN_URI, + always_use_jwt_access=True, + ) + + credentials._create_self_signed_jwt(None) + jwt.from_signing_credentials.assert_not_called() + @mock.patch("google.oauth2._client.jwt_grant", autospec=True) def test_refresh_success(self, jwt_grant): credentials = self.make_credentials() diff --git a/packages/google-auth/tests/test_jwt.py b/packages/google-auth/tests/test_jwt.py index c5290eb07d59..39c45bd236d5 100644 --- a/packages/google-auth/tests/test_jwt.py +++ b/packages/google-auth/tests/test_jwt.py @@ -390,6 +390,18 @@ def test_with_claims(self): assert new_credentials._additional_claims == self.credentials._additional_claims assert new_credentials._quota_project_id == self.credentials._quota_project_id + def test__make_jwt_without_audience(self): + cred = jwt.Credentials.from_service_account_info( + SERVICE_ACCOUNT_INFO.copy(), + subject=self.SUBJECT, + audience=None, + additional_claims={"scope": "foo bar"}, + ) + token, _ = cred._make_jwt() + payload = jwt.decode(token, PUBLIC_CERT_BYTES) + assert payload["scope"] == "foo bar" + assert "aud" not in payload + def test_with_quota_project(self): quota_project_id = "project-foo" diff --git a/packages/google-auth/tests/transport/test_grpc.py b/packages/google-auth/tests/transport/test_grpc.py index 1602f4c0f071..926c1bc400bb 100644 --- a/packages/google-auth/tests/transport/test_grpc.py +++ b/packages/google-auth/tests/transport/test_grpc.py @@ -111,8 +111,7 @@ def test__get_authorization_headers_with_service_account(self): plugin._get_authorization_headers(context) - # self-signed JWT should not be created when default_host is not set - credentials._create_self_signed_jwt.assert_not_called() + credentials._create_self_signed_jwt.assert_called_once_with(None) def test__get_authorization_headers_with_service_account_and_default_host(self): credentials = mock.create_autospec(service_account.Credentials) diff --git a/packages/google-auth/tests/transport/test_requests.py b/packages/google-auth/tests/transport/test_requests.py index 3fdd17c3e480..f494c14430ec 100644 --- a/packages/google-auth/tests/transport/test_requests.py +++ b/packages/google-auth/tests/transport/test_requests.py @@ -378,7 +378,7 @@ def test_authorized_session_without_default_host(self): authed_session = google.auth.transport.requests.AuthorizedSession(credentials) - authed_session.credentials._create_self_signed_jwt.assert_not_called() + authed_session.credentials._create_self_signed_jwt.assert_called_once_with(None) def test_authorized_session_with_default_host(self): default_host = "pubsub.googleapis.com" diff --git a/packages/google-auth/tests/transport/test_urllib3.py b/packages/google-auth/tests/transport/test_urllib3.py index 7c0693476074..e3848c177aad 100644 --- a/packages/google-auth/tests/transport/test_urllib3.py +++ b/packages/google-auth/tests/transport/test_urllib3.py @@ -164,7 +164,7 @@ def test_urlopen_no_default_host(self): authed_http = google.auth.transport.urllib3.AuthorizedHttp(credentials) - authed_http.credentials._create_self_signed_jwt.assert_not_called() + authed_http.credentials._create_self_signed_jwt.assert_called_once_with(None) def test_urlopen_with_default_host(self): default_host = "pubsub.googleapis.com" From 04b2f6d113fd650df36cd8299c545d8f39b52433 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 21 Jun 2021 11:40:08 -0700 Subject: [PATCH 422/966] chore: release 1.32.0 (#779) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 8a622423daa5..aae65ed596d0 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +## [1.32.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.31.0...v1.32.0) (2021-06-16) + + +### Features + +* allow scopes for self signed jwt ([#776](https://www.github.com/googleapis/google-auth-library-python/issues/776)) ([2cfe655](https://www.github.com/googleapis/google-auth-library-python/commit/2cfe655bba837170abc07701557a1a5e0fe3294e)) + ## [1.31.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.30.2...v1.31.0) (2021-06-09) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index a8d55991eee1..d675c0bff97a 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "1.31.0" +__version__ = "1.32.0" From d1417ac0a2349bfa6222234d3a08b889377044ee Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Wed, 23 Jun 2021 16:56:58 -0600 Subject: [PATCH 423/966] test: refresh authorized_user.json (#781) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10323 -> 10323 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 2e9cacd05c2b5940cfca5ab0315731caf5c1ccb0..24d89b1bf58cd6c162ccfbf5da0ab2b8b05ff6fe 100644 GIT binary patch literal 10323 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 literal 10323 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 l` Date: Fri, 25 Jun 2021 22:40:24 +0000 Subject: [PATCH 424/966] chore: add kokoro 3.9 config templates (#780) Source-Link: https://github.com/googleapis/synthtool/commit/b0eb8a8b30b46a3c98d23c23107acb748c6601a1 Post-Processor: gcr.io/repo-automation-bots/owlbot-python:latest@sha256:df50e8d462f86d6bcb42f27ecad55bb12c404f1c65de9c6fe4c4d25120080bd6 --- .../google-auth/.github/.OwlBot.lock.yaml | 17 +------- .../.kokoro/samples/python3.9/common.cfg | 40 +++++++++++++++++++ .../.kokoro/samples/python3.9/continuous.cfg | 6 +++ .../samples/python3.9/periodic-head.cfg | 11 +++++ .../.kokoro/samples/python3.9/periodic.cfg | 6 +++ .../.kokoro/samples/python3.9/presubmit.cfg | 6 +++ 6 files changed, 70 insertions(+), 16 deletions(-) create mode 100644 packages/google-auth/.kokoro/samples/python3.9/common.cfg create mode 100644 packages/google-auth/.kokoro/samples/python3.9/continuous.cfg create mode 100644 packages/google-auth/.kokoro/samples/python3.9/periodic-head.cfg create mode 100644 packages/google-auth/.kokoro/samples/python3.9/periodic.cfg create mode 100644 packages/google-auth/.kokoro/samples/python3.9/presubmit.cfg diff --git a/packages/google-auth/.github/.OwlBot.lock.yaml b/packages/google-auth/.github/.OwlBot.lock.yaml index c2fc27e46396..0954585f2833 100644 --- a/packages/google-auth/.github/.OwlBot.lock.yaml +++ b/packages/google-auth/.github/.OwlBot.lock.yaml @@ -1,18 +1,3 @@ -# Copyright 2021 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - docker: image: gcr.io/repo-automation-bots/owlbot-python:latest - digest: sha256:c66ba3c8d7bc8566f47df841f98cd0097b28fff0b1864c86f5817f4c8c3e8600 - + digest: sha256:df50e8d462f86d6bcb42f27ecad55bb12c404f1c65de9c6fe4c4d25120080bd6 diff --git a/packages/google-auth/.kokoro/samples/python3.9/common.cfg b/packages/google-auth/.kokoro/samples/python3.9/common.cfg new file mode 100644 index 000000000000..b1eacb8f6aaf --- /dev/null +++ b/packages/google-auth/.kokoro/samples/python3.9/common.cfg @@ -0,0 +1,40 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Build logs will be here +action { + define_artifacts { + regex: "**/*sponge_log.xml" + } +} + +# Specify which tests to run +env_vars: { + key: "RUN_TESTS_SESSION" + value: "py-3.9" +} + +# Declare build specific Cloud project. +env_vars: { + key: "BUILD_SPECIFIC_GCLOUD_PROJECT" + value: "python-docs-samples-tests-py39" +} + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/google-auth-library-python/.kokoro/test-samples.sh" +} + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/python-samples-testing-docker" +} + +# Download secrets for samples +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/python-docs-samples" + +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" + +# Use the trampoline script to run in docker. +build_file: "google-auth-library-python/.kokoro/trampoline.sh" \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.9/continuous.cfg b/packages/google-auth/.kokoro/samples/python3.9/continuous.cfg new file mode 100644 index 000000000000..a1c8d9759c88 --- /dev/null +++ b/packages/google-auth/.kokoro/samples/python3.9/continuous.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.9/periodic-head.cfg b/packages/google-auth/.kokoro/samples/python3.9/periodic-head.cfg new file mode 100644 index 000000000000..f9cfcd33e058 --- /dev/null +++ b/packages/google-auth/.kokoro/samples/python3.9/periodic-head.cfg @@ -0,0 +1,11 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/python-pubsub/.kokoro/test-samples-against-head.sh" +} diff --git a/packages/google-auth/.kokoro/samples/python3.9/periodic.cfg b/packages/google-auth/.kokoro/samples/python3.9/periodic.cfg new file mode 100644 index 000000000000..50fec9649732 --- /dev/null +++ b/packages/google-auth/.kokoro/samples/python3.9/periodic.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "False" +} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.9/presubmit.cfg b/packages/google-auth/.kokoro/samples/python3.9/presubmit.cfg new file mode 100644 index 000000000000..a1c8d9759c88 --- /dev/null +++ b/packages/google-auth/.kokoro/samples/python3.9/presubmit.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} \ No newline at end of file From e68c9083e7e70898b27d482bfee9a439dbb11ce6 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Wed, 30 Jun 2021 15:59:28 -0400 Subject: [PATCH 425/966] fix: avoid leaking sub-session created for '_auth_request' (#789) Closes #658. --- .../google/auth/transport/requests.py | 13 +++++++++--- .../system_tests_sync/test_requests.py | 6 ++++-- .../tests/transport/test_requests.py | 21 ++++++++++++++++++- 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/packages/google-auth/google/auth/transport/requests.py b/packages/google-auth/google/auth/transport/requests.py index a4784b3865bd..817176befa20 100644 --- a/packages/google-auth/google/auth/transport/requests.py +++ b/packages/google-auth/google/auth/transport/requests.py @@ -340,17 +340,19 @@ def __init__( self._default_host = default_host if auth_request is None: - auth_request_session = requests.Session() + self._auth_request_session = requests.Session() # Using an adapter to make HTTP requests robust to network errors. # This adapter retrys HTTP requests when network errors occur # and the requests seems safely retryable. retry_adapter = requests.adapters.HTTPAdapter(max_retries=3) - auth_request_session.mount("https://", retry_adapter) + self._auth_request_session.mount("https://", retry_adapter) # Do not pass `self` as the session here, as it can lead to # infinite recursion. - auth_request = Request(auth_request_session) + auth_request = Request(self._auth_request_session) + else: + self._auth_request_session = None # Request instance used by internal methods (for example, # credentials.refresh). @@ -533,3 +535,8 @@ def request( def is_mtls(self): """Indicates if the created SSL channel is mutual TLS.""" return self._is_mtls + + def close(self): + if self._auth_request_session is not None: + self._auth_request_session.close() + super(AuthorizedSession, self).close() diff --git a/packages/google-auth/system_tests/system_tests_sync/test_requests.py b/packages/google-auth/system_tests/system_tests_sync/test_requests.py index 3ac9179b5072..28004848be93 100644 --- a/packages/google-auth/system_tests/system_tests_sync/test_requests.py +++ b/packages/google-auth/system_tests/system_tests_sync/test_requests.py @@ -32,8 +32,10 @@ def test_authorized_session_with_service_account_and_self_signed_jwt(): # List Pub/Sub Topics through the REST API # https://cloud.google.com/pubsub/docs/reference/rest/v1/projects.topics/list - response = session.get("https://pubsub.googleapis.com/v1/projects/{}/topics".format(project_id)) - response.raise_for_status() + url = "https://pubsub.googleapis.com/v1/projects/{}/topics".format(project_id) + with session: + response = session.get(url) + response.raise_for_status() # Check that self-signed JWT was created and is being used assert credentials._jwt_credentials is not None diff --git a/packages/google-auth/tests/transport/test_requests.py b/packages/google-auth/tests/transport/test_requests.py index f494c14430ec..ed9300d7684e 100644 --- a/packages/google-auth/tests/transport/test_requests.py +++ b/packages/google-auth/tests/transport/test_requests.py @@ -213,7 +213,7 @@ def test_constructor_with_auth_request(self): mock.sentinel.credentials, auth_request=auth_request ) - assert authed_session._auth_request == auth_request + assert authed_session._auth_request is auth_request def test_request_default_timeout(self): credentials = mock.Mock(wraps=CredentialsStub()) @@ -504,3 +504,22 @@ def test_configure_mtls_channel_without_client_cert_env( auth_session.configure_mtls_channel(mock_callback) assert not auth_session.is_mtls mock_callback.assert_not_called() + + def test_close_wo_passed_in_auth_request(self): + authed_session = google.auth.transport.requests.AuthorizedSession( + mock.sentinel.credentials + ) + authed_session._auth_request_session = mock.Mock(spec=["close"]) + + authed_session.close() + + authed_session._auth_request_session.close.assert_called_once_with() + + def test_close_w_passed_in_auth_request(self): + http = mock.create_autospec(requests.Session) + auth_request = google.auth.transport.requests.Request(http) + authed_session = google.auth.transport.requests.AuthorizedSession( + mock.sentinel.credentials, auth_request=auth_request + ) + + authed_session.close() # no raise From 52b2c4c4402d15b982459d5a8f627c990ce07f7f Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Wed, 30 Jun 2021 20:22:22 +0000 Subject: [PATCH 426/966] chore: release 1.32.1 (#790) :robot: I have created a release \*beep\* \*boop\* --- ### [1.32.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.32.0...v1.32.1) (2021-06-30) ### Bug Fixes * avoid leaking sub-session created for '_auth_request' ([#789](https://www.github.com/googleapis/google-auth-library-python/issues/789)) ([2079ab5](https://www.github.com/googleapis/google-auth-library-python/commit/2079ab5e1db464f502248ae4f9e424deeef87fb2)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index aae65ed596d0..d04dad5b0425 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +### [1.32.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.32.0...v1.32.1) (2021-06-30) + + +### Bug Fixes + +* avoid leaking sub-session created for '_auth_request' ([#789](https://www.github.com/googleapis/google-auth-library-python/issues/789)) ([2079ab5](https://www.github.com/googleapis/google-auth-library-python/commit/2079ab5e1db464f502248ae4f9e424deeef87fb2)) + ## [1.32.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.31.0...v1.32.0) (2021-06-16) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index d675c0bff97a..2ffa8a43a5d8 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "1.32.0" +__version__ = "1.32.1" From 91f10308d9d438e15601899b9f48152456f80981 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Thu, 8 Jul 2021 10:51:10 -0600 Subject: [PATCH 427/966] chore: update authorized_user.json (#796) Closes #795 --- .../google-auth/system_tests/secrets.tar.enc | Bin 10323 -> 10323 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 24d89b1bf58cd6c162ccfbf5da0ab2b8b05ff6fe..e416918d4df6a2fc2c634d6db858c4f5b5d46729 100644 GIT binary patch literal 10323 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%0DW#ey5r0090R#F>~_7N+J zu#?>pDz%~{jpT()7ck3nW#0G-4s2JFZPDH$34evyx8uf?mkUepHv|-Pl-j9OR}L7Z zEIYI2oy%Aqybpgti%2`&8(V3FY?d+Y)Bb@WVBcGJ^&i=d43nJbZ|uIKT==5aQ@Guy zwHiiT%xU`N|1|vN(h9v5&3_28sONQsAj4nKBU!T5`?P}MZ&k9&Xhc_^^XojMzOXhZ zbpFk5KUchmXm_}d-?dETB0b=+mPo=JYH{e)4+u5s9LT(iy zU~gN~LA&f1A6A%a!OFF+LuDt%k(lAb(*a^~ZeoW`b zj7A=$+K3&rht|KT^=GwaaQ$-LO`3}AA1vutTybL?iV=gRcy!2QpgH>P0X|NnzNOu0 zdX3(lRj|NY&AW?$ykh%bCMAhpFn`nqE4EW(m=%&aU(BW!=`&&vm2mDfY8@URSDfwl zTTclWV9!l+I@H1&F{}e)x}w<4=_wvd)zXdyWJE=&ALWbp~{Zcyev;fq8^ z>Y}8aKAVnqJ{e$z;`L`Jb1I&KD%+2VNG4PXJ)1vYcy@5EfjiP4fjcG3+UU^o{7tMw zMjq}`OC?ti7mO9!d=3q=OX4TU(GTg#uj!S<*XEmu^>g8g@Dp4&OeKPlY?nqQqfH`! zk-?_{OAVIcAzhkEmf=A5?SMyd&)LiQX9qIoO;1B(%S;qLw3&pnB2wG)puEWZ&HIa9 zYkIo(S>{Ik`}?3MqjqIx8}@y%)yy-=ude{>!(*Kl*nX1GcB{q(Xc*(SgqvhlN~DBe zr_r)~FZ%7U@WfHN8^nNEes>?=#$EGhE<-$RG9~k_f7~b*OMUqc92gD|kmFV|7l|`ft$F z=F92N3=Tl?*@%t>CL&jN%ds&%c7phknUBTWaX{oiErj0rNqvvQ!*AQ3mMi$AA$NWs@_` zCKdPZfISOpDiSPA&P_KOo6US|+zqoN4}^(`tN<04r{0<$%Wti{8Q~w+Lh&uhHwzNe zow4LK3asEdjJAG>?pfPI9=7%_F5y#O=Hf?WoqLVMfEVlQy#=|P)Q#=={Rx<}=IIY% z0^+1VSk~1(VHQ~IUM1gO zcL;7#Ywme^^tDWKfDLz*ST)a5HvtMCGRNY6N3$3tF}~b4K#T>_5(3`QkGz0ec2@cx zQ3hF!HKF%ZUq5TqvBzwz%1}f|lTf%L^nij|Q6Q_TagF;}<9(c`*%9cg8bpC;Fa|VU zLVp1P(VHefM$P5gxG}huk`J~UzU*~fkPRJagd?`Pr09Z!5}B~K-o zEfR9(TXE}b_heQRj)hxzwE9r!1M09K%-U_E|mJBidQMI*

ri{f8pbZ@sJLkZP#k(D{yF_ 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 From f9295903bcf5c8b2d8060a491aec2662f5530191 Mon Sep 17 00:00:00 2001 From: Matt Seymour Date: Thu, 8 Jul 2021 18:33:42 +0100 Subject: [PATCH 428/966] docs: fix code block formatting in 'user-guide.rst' (#794) --- packages/google-auth/docs/user-guide.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/google-auth/docs/user-guide.rst b/packages/google-auth/docs/user-guide.rst index 6674c5aefc41..4a45cde9163e 100644 --- a/packages/google-auth/docs/user-guide.rst +++ b/packages/google-auth/docs/user-guide.rst @@ -479,16 +479,16 @@ IDToken verification can be done for various type of IDTokens using the and ES256 algorithms. However, ES256 algorithm won't be available unless `cryptography` dependency of version at least 1.4.0 is installed. You can check the dependency with `pip freeze` or try `from google.auth.crypt import es256`. -The following is an example of verifying ID tokens: +The following is an example of verifying ID tokens :: from google.auth2 import id_token request = google.auth.transport.requests.Request() try: - decoded_token = id_token.verify_token(token_to_verify,request) + decoded_token = id_token.verify_token(token_to_verify,request) except ValueError: - # Verification failed. + # Verification failed. A sample end-to-end flow using an ID Token against a Cloud Run endpoint maybe :: From f479c65b5a864c21189fa1ce266ec5d8e3f11f55 Mon Sep 17 00:00:00 2001 From: bojeil-google Date: Thu, 8 Jul 2021 10:56:22 -0700 Subject: [PATCH 429/966] feat: define `CredentialAccessBoundary` classes (#793) Defines the following classes: - `google.auth.downscoped.CredentialAccessBoundary` - `google.auth.downscoped.AccessBoundaryRule` - `google.auth.downscoped.AvailabilityCondition` This is based on [Downscoping with Credential Access Boundaries](https://cloud.google.com/iam/docs/downscoping-short-lived-credentials). These classes help define the list of access boundary rules, each of which contains information on the resource that the rule applies to, the upper bound of the permissions that are available on that resource and an optional condition to further restrict permissions. --- .../google-auth/google/auth/downscoped.py | 405 ++++++++++++++++++ packages/google-auth/tests/test_downscoped.py | 385 +++++++++++++++++ 2 files changed, 790 insertions(+) create mode 100644 packages/google-auth/google/auth/downscoped.py create mode 100644 packages/google-auth/tests/test_downscoped.py diff --git a/packages/google-auth/google/auth/downscoped.py b/packages/google-auth/google/auth/downscoped.py new file mode 100644 index 000000000000..beea50ec7d65 --- /dev/null +++ b/packages/google-auth/google/auth/downscoped.py @@ -0,0 +1,405 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Downscoping with Credential Access Boundaries + +This module provides the ability to downscope credentials using +`Downscoping with Credential Access Boundaries`_. This is useful to restrict the +Identity and Access Management (IAM) permissions that a short-lived credential +can use. + +To downscope permissions of a source credential, a Credential Access Boundary +that specifies which resources the new credential can access, as well as +an upper bound on the permissions that are available on each resource, has to +be defined. A downscoped credential can then be instantiated using the source +credential and the Credential Access Boundary. + +The common pattern of usage is to have a token broker with elevated access +generate these downscoped credentials from higher access source credentials and +pass the downscoped short-lived access tokens to a token consumer via some +secure authenticated channel for limited access to Google Cloud Storage +resources. + +For example, a token broker can be set up on a server in a private network. +Various workloads (token consumers) in the same network will send authenticated +requests to that broker for downscoped tokens to access or modify specific google +cloud storage buckets. + +The broker will instantiate downscoped credentials instances that can be used to +generate short lived downscoped access tokens that can be passed to the token +consumer. These downscoped access tokens can be injected by the consumer into +google.oauth2.Credentials and used to initialize a storage client instance to +access Google Cloud Storage resources with restricted access. + +Note: Only Cloud Storage supports Credential Access Boundaries. Other Google +Cloud services do not support this feature. + +.. _Downscoping with Credential Access Boundaries: https://cloud.google.com/iam/docs/downscoping-short-lived-credentials +""" + +# The maximum number of access boundary rules a Credential Access Boundary can +# contain. +_MAX_ACCESS_BOUNDARY_RULES_COUNT = 10 + + +class CredentialAccessBoundary(object): + """Defines a Credential Access Boundary which contains a list of access boundary + rules. Each rule contains information on the resource that the rule applies to, + the upper bound of the permissions that are available on that resource and an + optional condition to further restrict permissions. + """ + + def __init__(self, rules=[]): + """Instantiates a Credential Access Boundary. A Credential Access Boundary + can contain up to 10 access boundary rules. + + Args: + rules (Sequence[google.auth.downscoped.AccessBoundaryRule]): The list of + access boundary rules limiting the access that a downscoped credential + will have. + Raises: + TypeError: If any of the rules are not a valid type. + ValueError: If the provided rules exceed the maximum allowed. + """ + self.rules = rules + + @property + def rules(self): + """Returns the list of access boundary rules defined on the Credential + Access Boundary. + + Returns: + Tuple[google.auth.downscoped.AccessBoundaryRule, ...]: The list of access + boundary rules defined on the Credential Access Boundary. These are returned + as an immutable tuple to prevent modification. + """ + return tuple(self._rules) + + @rules.setter + def rules(self, value): + """Updates the current rules on the Credential Access Boundary. This will overwrite + the existing set of rules. + + Args: + value (Sequence[google.auth.downscoped.AccessBoundaryRule]): The list of + access boundary rules limiting the access that a downscoped credential + will have. + Raises: + TypeError: If any of the rules are not a valid type. + ValueError: If the provided rules exceed the maximum allowed. + """ + if len(value) > _MAX_ACCESS_BOUNDARY_RULES_COUNT: + raise ValueError( + "Credential access boundary rules can have a maximum of {} rules.".format( + _MAX_ACCESS_BOUNDARY_RULES_COUNT + ) + ) + for access_boundary_rule in value: + if not isinstance(access_boundary_rule, AccessBoundaryRule): + raise TypeError( + "List of rules provided do not contain a valid 'google.auth.downscoped.AccessBoundaryRule'." + ) + # Make a copy of the original list. + self._rules = list(value) + + def add_rule(self, rule): + """Adds a single access boundary rule to the existing rules. + + Args: + rule (google.auth.downscoped.AccessBoundaryRule): The access boundary rule, + limiting the access that a downscoped credential will have, to be added to + the existing rules. + Raises: + TypeError: If any of the rules are not a valid type. + ValueError: If the provided rules exceed the maximum allowed. + """ + if len(self.rules) == _MAX_ACCESS_BOUNDARY_RULES_COUNT: + raise ValueError( + "Credential access boundary rules can have a maximum of {} rules.".format( + _MAX_ACCESS_BOUNDARY_RULES_COUNT + ) + ) + if not isinstance(rule, AccessBoundaryRule): + raise TypeError( + "The provided rule does not contain a valid 'google.auth.downscoped.AccessBoundaryRule'." + ) + self._rules.append(rule) + + def to_json(self): + """Generates the dictionary representation of the Credential Access Boundary. + This uses the format expected by the Security Token Service API as documented in + `Defining a Credential Access Boundary`_. + + .. _Defining a Credential Access Boundary: + https://cloud.google.com/iam/docs/downscoping-short-lived-credentials#define-boundary + + Returns: + Mapping: Credential Access Boundary Rule represented in a dictionary object. + """ + rules = [] + for access_boundary_rule in self.rules: + rules.append(access_boundary_rule.to_json()) + + return {"accessBoundary": {"accessBoundaryRules": rules}} + + +class AccessBoundaryRule(object): + """Defines an access boundary rule which contains information on the resource that + the rule applies to, the upper bound of the permissions that are available on that + resource and an optional condition to further restrict permissions. + """ + + def __init__( + self, available_resource, available_permissions, availability_condition=None + ): + """Instantiates a single access boundary rule. + + Args: + available_resource (str): The full resource name of the Cloud Storage bucket + that the rule applies to. Use the format + "//storage.googleapis.com/projects/_/buckets/bucket-name". + available_permissions (Sequence[str]): A list defining the upper bound that + the downscoped token will have on the available permissions for the + resource. Each value is the identifier for an IAM predefined role or + custom role, with the prefix "inRole:". For example: + "inRole:roles/storage.objectViewer". + Only the permissions in these roles will be available. + availability_condition (Optional[google.auth.downscoped.AvailabilityCondition]): + Optional condition that restricts the availability of permissions to + specific Cloud Storage objects. + + Raises: + TypeError: If any of the parameters are not of the expected types. + ValueError: If any of the parameters are not of the expected values. + """ + self.available_resource = available_resource + self.available_permissions = available_permissions + self.availability_condition = availability_condition + + @property + def available_resource(self): + """Returns the current available resource. + + Returns: + str: The current available resource. + """ + return self._available_resource + + @available_resource.setter + def available_resource(self, value): + """Updates the current available resource. + + Args: + value (str): The updated value of the available resource. + + Raises: + TypeError: If the value is not a string. + """ + if not isinstance(value, str): + raise TypeError("The provided available_resource is not a string.") + self._available_resource = value + + @property + def available_permissions(self): + """Returns the current available permissions. + + Returns: + Tuple[str, ...]: The current available permissions. These are returned + as an immutable tuple to prevent modification. + """ + return tuple(self._available_permissions) + + @available_permissions.setter + def available_permissions(self, value): + """Updates the current available permissions. + + Args: + value (Sequence[str]): The updated value of the available permissions. + + Raises: + TypeError: If the value is not a list of strings. + ValueError: If the value is not valid. + """ + for available_permission in value: + if not isinstance(available_permission, str): + raise TypeError( + "Provided available_permissions are not a list of strings." + ) + if available_permission.find("inRole:") != 0: + raise ValueError( + "available_permissions must be prefixed with 'inRole:'." + ) + # Make a copy of the original list. + self._available_permissions = list(value) + + @property + def availability_condition(self): + """Returns the current availability condition. + + Returns: + Optional[google.auth.downscoped.AvailabilityCondition]: The current + availability condition. + """ + return self._availability_condition + + @availability_condition.setter + def availability_condition(self, value): + """Updates the current availability condition. + + Args: + value (Optional[google.auth.downscoped.AvailabilityCondition]): The updated + value of the availability condition. + + Raises: + TypeError: If the value is not of type google.auth.downscoped.AvailabilityCondition + or None. + """ + if not isinstance(value, AvailabilityCondition) and value is not None: + raise TypeError( + "The provided availability_condition is not a 'google.auth.downscoped.AvailabilityCondition' or None." + ) + self._availability_condition = value + + def to_json(self): + """Generates the dictionary representation of the access boundary rule. + This uses the format expected by the Security Token Service API as documented in + `Defining a Credential Access Boundary`_. + + .. _Defining a Credential Access Boundary: + https://cloud.google.com/iam/docs/downscoping-short-lived-credentials#define-boundary + + Returns: + Mapping: The access boundary rule represented in a dictionary object. + """ + json = { + "availablePermissions": list(self.available_permissions), + "availableResource": self.available_resource, + } + if self.availability_condition: + json["availabilityCondition"] = self.availability_condition.to_json() + return json + + +class AvailabilityCondition(object): + """An optional condition that can be used as part of a Credential Access Boundary + to further restrict permissions.""" + + def __init__(self, expression, title=None, description=None): + """Instantiates an availability condition using the provided expression and + optional title or description. + + Args: + expression (str): A condition expression that specifies the Cloud Storage + objects where permissions are available. For example, this expression + makes permissions available for objects whose name starts with "customer-a": + "resource.name.startsWith('projects/_/buckets/example-bucket/objects/customer-a')" + title (Optional[str]): An optional short string that identifies the purpose of + the condition. + description (Optional[str]): Optional details about the purpose of the condition. + + Raises: + TypeError: If any of the parameters are not of the expected types. + ValueError: If any of the parameters are not of the expected values. + """ + self.expression = expression + self.title = title + self.description = description + + @property + def expression(self): + """Returns the current condition expression. + + Returns: + str: The current conditon expression. + """ + return self._expression + + @expression.setter + def expression(self, value): + """Updates the current condition expression. + + Args: + value (str): The updated value of the condition expression. + + Raises: + TypeError: If the value is not of type string. + """ + if not isinstance(value, str): + raise TypeError("The provided expression is not a string.") + self._expression = value + + @property + def title(self): + """Returns the current title. + + Returns: + Optional[str]: The current title. + """ + return self._title + + @title.setter + def title(self, value): + """Updates the current title. + + Args: + value (Optional[str]): The updated value of the title. + + Raises: + TypeError: If the value is not of type string or None. + """ + if not isinstance(value, str) and value is not None: + raise TypeError("The provided title is not a string or None.") + self._title = value + + @property + def description(self): + """Returns the current description. + + Returns: + Optional[str]: The current description. + """ + return self._description + + @description.setter + def description(self, value): + """Updates the current description. + + Args: + value (Optional[str]): The updated value of the description. + + Raises: + TypeError: If the value is not of type string or None. + """ + if not isinstance(value, str) and value is not None: + raise TypeError("The provided description is not a string or None.") + self._description = value + + def to_json(self): + """Generates the dictionary representation of the availability condition. + This uses the format expected by the Security Token Service API as documented in + `Defining a Credential Access Boundary`_. + + .. _Defining a Credential Access Boundary: + https://cloud.google.com/iam/docs/downscoping-short-lived-credentials#define-boundary + + Returns: + Mapping[str, str]: The availability condition represented in a dictionary + object. + """ + json = {"expression": self.expression} + if self.title: + json["title"] = self.title + if self.description: + json["description"] = self.description + return json diff --git a/packages/google-auth/tests/test_downscoped.py b/packages/google-auth/tests/test_downscoped.py new file mode 100644 index 000000000000..61b1d1876691 --- /dev/null +++ b/packages/google-auth/tests/test_downscoped.py @@ -0,0 +1,385 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from google.auth import downscoped + + +EXPRESSION = ( + "resource.name.startsWith('projects/_/buckets/example-bucket/objects/customer-a')" +) +TITLE = "customer-a-objects" +DESCRIPTION = ( + "Condition to make permissions available for objects starting with customer-a" +) +AVAILABLE_RESOURCE = "//storage.googleapis.com/projects/_/buckets/example-bucket" +AVAILABLE_PERMISSIONS = ["inRole:roles/storage.objectViewer"] + +OTHER_EXPRESSION = ( + "resource.name.startsWith('projects/_/buckets/example-bucket/objects/customer-b')" +) +OTHER_TITLE = "customer-b-objects" +OTHER_DESCRIPTION = ( + "Condition to make permissions available for objects starting with customer-b" +) +OTHER_AVAILABLE_RESOURCE = "//storage.googleapis.com/projects/_/buckets/other-bucket" +OTHER_AVAILABLE_PERMISSIONS = ["inRole:roles/storage.objectCreator"] + + +def make_availability_condition(expression, title=None, description=None): + return downscoped.AvailabilityCondition(expression, title, description) + + +def make_access_boundary_rule( + available_resource, available_permissions, availability_condition=None +): + return downscoped.AccessBoundaryRule( + available_resource, available_permissions, availability_condition + ) + + +def make_credential_access_boundary(rules): + return downscoped.CredentialAccessBoundary(rules) + + +class TestAvailabilityCondition(object): + def test_constructor(self): + availability_condition = make_availability_condition( + EXPRESSION, TITLE, DESCRIPTION + ) + + assert availability_condition.expression == EXPRESSION + assert availability_condition.title == TITLE + assert availability_condition.description == DESCRIPTION + + def test_constructor_required_params_only(self): + availability_condition = make_availability_condition(EXPRESSION) + + assert availability_condition.expression == EXPRESSION + assert availability_condition.title is None + assert availability_condition.description is None + + def test_setters(self): + availability_condition = make_availability_condition( + EXPRESSION, TITLE, DESCRIPTION + ) + availability_condition.expression = OTHER_EXPRESSION + availability_condition.title = OTHER_TITLE + availability_condition.description = OTHER_DESCRIPTION + + assert availability_condition.expression == OTHER_EXPRESSION + assert availability_condition.title == OTHER_TITLE + assert availability_condition.description == OTHER_DESCRIPTION + + def test_invalid_expression_type(self): + with pytest.raises(TypeError) as excinfo: + make_availability_condition([EXPRESSION], TITLE, DESCRIPTION) + + assert excinfo.match("The provided expression is not a string.") + + def test_invalid_title_type(self): + with pytest.raises(TypeError) as excinfo: + make_availability_condition(EXPRESSION, False, DESCRIPTION) + + assert excinfo.match("The provided title is not a string or None.") + + def test_invalid_description_type(self): + with pytest.raises(TypeError) as excinfo: + make_availability_condition(EXPRESSION, TITLE, False) + + assert excinfo.match("The provided description is not a string or None.") + + def test_to_json_required_params_only(self): + availability_condition = make_availability_condition(EXPRESSION) + + assert availability_condition.to_json() == {"expression": EXPRESSION} + + def test_to_json_(self): + availability_condition = make_availability_condition( + EXPRESSION, TITLE, DESCRIPTION + ) + + assert availability_condition.to_json() == { + "expression": EXPRESSION, + "title": TITLE, + "description": DESCRIPTION, + } + + +class TestAccessBoundaryRule(object): + def test_constructor(self): + availability_condition = make_availability_condition( + EXPRESSION, TITLE, DESCRIPTION + ) + access_boundary_rule = make_access_boundary_rule( + AVAILABLE_RESOURCE, AVAILABLE_PERMISSIONS, availability_condition + ) + + assert access_boundary_rule.available_resource == AVAILABLE_RESOURCE + assert access_boundary_rule.available_permissions == tuple( + AVAILABLE_PERMISSIONS + ) + assert access_boundary_rule.availability_condition == availability_condition + + def test_constructor_required_params_only(self): + access_boundary_rule = make_access_boundary_rule( + AVAILABLE_RESOURCE, AVAILABLE_PERMISSIONS + ) + + assert access_boundary_rule.available_resource == AVAILABLE_RESOURCE + assert access_boundary_rule.available_permissions == tuple( + AVAILABLE_PERMISSIONS + ) + assert access_boundary_rule.availability_condition is None + + def test_setters(self): + availability_condition = make_availability_condition( + EXPRESSION, TITLE, DESCRIPTION + ) + other_availability_condition = make_availability_condition( + OTHER_EXPRESSION, OTHER_TITLE, OTHER_DESCRIPTION + ) + access_boundary_rule = make_access_boundary_rule( + AVAILABLE_RESOURCE, AVAILABLE_PERMISSIONS, availability_condition + ) + access_boundary_rule.available_resource = OTHER_AVAILABLE_RESOURCE + access_boundary_rule.available_permissions = OTHER_AVAILABLE_PERMISSIONS + access_boundary_rule.availability_condition = other_availability_condition + + assert access_boundary_rule.available_resource == OTHER_AVAILABLE_RESOURCE + assert access_boundary_rule.available_permissions == tuple( + OTHER_AVAILABLE_PERMISSIONS + ) + assert ( + access_boundary_rule.availability_condition == other_availability_condition + ) + + def test_invalid_available_resource_type(self): + availability_condition = make_availability_condition( + EXPRESSION, TITLE, DESCRIPTION + ) + with pytest.raises(TypeError) as excinfo: + make_access_boundary_rule( + None, AVAILABLE_PERMISSIONS, availability_condition + ) + + assert excinfo.match("The provided available_resource is not a string.") + + def test_invalid_available_permissions_type(self): + availability_condition = make_availability_condition( + EXPRESSION, TITLE, DESCRIPTION + ) + with pytest.raises(TypeError) as excinfo: + make_access_boundary_rule( + AVAILABLE_RESOURCE, [0, 1, 2], availability_condition + ) + + assert excinfo.match( + "Provided available_permissions are not a list of strings." + ) + + def test_invalid_available_permissions_value(self): + availability_condition = make_availability_condition( + EXPRESSION, TITLE, DESCRIPTION + ) + with pytest.raises(ValueError) as excinfo: + make_access_boundary_rule( + AVAILABLE_RESOURCE, + ["roles/storage.objectViewer"], + availability_condition, + ) + + assert excinfo.match("available_permissions must be prefixed with 'inRole:'.") + + def test_invalid_availability_condition_type(self): + with pytest.raises(TypeError) as excinfo: + make_access_boundary_rule( + AVAILABLE_RESOURCE, AVAILABLE_PERMISSIONS, {"foo": "bar"} + ) + + assert excinfo.match( + "The provided availability_condition is not a 'google.auth.downscoped.AvailabilityCondition' or None." + ) + + def test_to_json(self): + availability_condition = make_availability_condition( + EXPRESSION, TITLE, DESCRIPTION + ) + access_boundary_rule = make_access_boundary_rule( + AVAILABLE_RESOURCE, AVAILABLE_PERMISSIONS, availability_condition + ) + + assert access_boundary_rule.to_json() == { + "availablePermissions": AVAILABLE_PERMISSIONS, + "availableResource": AVAILABLE_RESOURCE, + "availabilityCondition": { + "expression": EXPRESSION, + "title": TITLE, + "description": DESCRIPTION, + }, + } + + def test_to_json_required_params_only(self): + access_boundary_rule = make_access_boundary_rule( + AVAILABLE_RESOURCE, AVAILABLE_PERMISSIONS + ) + + assert access_boundary_rule.to_json() == { + "availablePermissions": AVAILABLE_PERMISSIONS, + "availableResource": AVAILABLE_RESOURCE, + } + + +class TestCredentialAccessBoundary(object): + def test_constructor(self): + availability_condition = make_availability_condition( + EXPRESSION, TITLE, DESCRIPTION + ) + access_boundary_rule = make_access_boundary_rule( + AVAILABLE_RESOURCE, AVAILABLE_PERMISSIONS, availability_condition + ) + rules = [access_boundary_rule] + credential_access_boundary = make_credential_access_boundary(rules) + + assert credential_access_boundary.rules == tuple(rules) + + def test_setters(self): + availability_condition = make_availability_condition( + EXPRESSION, TITLE, DESCRIPTION + ) + access_boundary_rule = make_access_boundary_rule( + AVAILABLE_RESOURCE, AVAILABLE_PERMISSIONS, availability_condition + ) + rules = [access_boundary_rule] + other_availability_condition = make_availability_condition( + OTHER_EXPRESSION, OTHER_TITLE, OTHER_DESCRIPTION + ) + other_access_boundary_rule = make_access_boundary_rule( + OTHER_AVAILABLE_RESOURCE, + OTHER_AVAILABLE_PERMISSIONS, + other_availability_condition, + ) + other_rules = [other_access_boundary_rule] + credential_access_boundary = make_credential_access_boundary(rules) + credential_access_boundary.rules = other_rules + + assert credential_access_boundary.rules == tuple(other_rules) + + def test_add_rule(self): + availability_condition = make_availability_condition( + EXPRESSION, TITLE, DESCRIPTION + ) + access_boundary_rule = make_access_boundary_rule( + AVAILABLE_RESOURCE, AVAILABLE_PERMISSIONS, availability_condition + ) + rules = [access_boundary_rule] * 9 + credential_access_boundary = make_credential_access_boundary(rules) + + # Add one more rule. This should not raise an error. + additional_access_boundary_rule = make_access_boundary_rule( + OTHER_AVAILABLE_RESOURCE, OTHER_AVAILABLE_PERMISSIONS + ) + credential_access_boundary.add_rule(additional_access_boundary_rule) + + assert len(credential_access_boundary.rules) == 10 + assert credential_access_boundary.rules[9] == additional_access_boundary_rule + + def test_add_rule_invalid_value(self): + availability_condition = make_availability_condition( + EXPRESSION, TITLE, DESCRIPTION + ) + access_boundary_rule = make_access_boundary_rule( + AVAILABLE_RESOURCE, AVAILABLE_PERMISSIONS, availability_condition + ) + rules = [access_boundary_rule] * 10 + credential_access_boundary = make_credential_access_boundary(rules) + + # Add one more rule to exceed maximum allowed rules. + with pytest.raises(ValueError) as excinfo: + credential_access_boundary.add_rule(access_boundary_rule) + + assert excinfo.match( + "Credential access boundary rules can have a maximum of 10 rules." + ) + assert len(credential_access_boundary.rules) == 10 + + def test_add_rule_invalid_type(self): + availability_condition = make_availability_condition( + EXPRESSION, TITLE, DESCRIPTION + ) + access_boundary_rule = make_access_boundary_rule( + AVAILABLE_RESOURCE, AVAILABLE_PERMISSIONS, availability_condition + ) + rules = [access_boundary_rule] + credential_access_boundary = make_credential_access_boundary(rules) + + # Add an invalid rule to exceed maximum allowed rules. + with pytest.raises(TypeError) as excinfo: + credential_access_boundary.add_rule("invalid") + + assert excinfo.match( + "The provided rule does not contain a valid 'google.auth.downscoped.AccessBoundaryRule'." + ) + assert len(credential_access_boundary.rules) == 1 + assert credential_access_boundary.rules[0] == access_boundary_rule + + def test_invalid_rules_type(self): + with pytest.raises(TypeError) as excinfo: + make_credential_access_boundary(["invalid"]) + + assert excinfo.match( + "List of rules provided do not contain a valid 'google.auth.downscoped.AccessBoundaryRule'." + ) + + def test_invalid_rules_value(self): + availability_condition = make_availability_condition( + EXPRESSION, TITLE, DESCRIPTION + ) + access_boundary_rule = make_access_boundary_rule( + AVAILABLE_RESOURCE, AVAILABLE_PERMISSIONS, availability_condition + ) + too_many_rules = [access_boundary_rule] * 11 + with pytest.raises(ValueError) as excinfo: + make_credential_access_boundary(too_many_rules) + + assert excinfo.match( + "Credential access boundary rules can have a maximum of 10 rules." + ) + + def test_to_json(self): + availability_condition = make_availability_condition( + EXPRESSION, TITLE, DESCRIPTION + ) + access_boundary_rule = make_access_boundary_rule( + AVAILABLE_RESOURCE, AVAILABLE_PERMISSIONS, availability_condition + ) + rules = [access_boundary_rule] + credential_access_boundary = make_credential_access_boundary(rules) + + assert credential_access_boundary.to_json() == { + "accessBoundary": { + "accessBoundaryRules": [ + { + "availablePermissions": AVAILABLE_PERMISSIONS, + "availableResource": AVAILABLE_RESOURCE, + "availabilityCondition": { + "expression": EXPRESSION, + "title": TITLE, + "description": DESCRIPTION, + }, + } + ] + } + } From e303bef53c457f696c8e5d15c24f4616df117a08 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Thu, 8 Jul 2021 15:51:47 -0400 Subject: [PATCH 430/966] tests: pass '*session.posargs' for systests (#788) * tests: pass '*session.posargs' for systests Permits running single tests, debugging on errors, etc. * fix: typos --- packages/google-auth/system_tests/noxfile.py | 85 +++++++++++++++----- 1 file changed, 66 insertions(+), 19 deletions(-) diff --git a/packages/google-auth/system_tests/noxfile.py b/packages/google-auth/system_tests/noxfile.py index f177dcd6a35a..11c398b97423 100644 --- a/packages/google-auth/system_tests/noxfile.py +++ b/packages/google-auth/system_tests/noxfile.py @@ -188,7 +188,11 @@ def default(session, *test_paths): def service_account_sync(session): session.install(*TEST_DEPENDENCIES_SYNC) session.install(LIBRARY_DIR) - default(session, "system_tests_sync/test_service_account.py") + default( + session, + "system_tests_sync/test_service_account.py", + *session.posargs, + ) @nox.session(python=PYTHON_VERSIONS_SYNC) @@ -201,6 +205,7 @@ def default_explicit_service_account(session): session, "system_tests_sync/test_default.py", "system_tests_sync/test_id_token.py", + *session.posargs, ) @@ -210,7 +215,9 @@ def default_explicit_authorized_user(session): session.install(*TEST_DEPENDENCIES_SYNC) session.install(LIBRARY_DIR) default( - session, "system_tests_sync/test_default.py", + session, + "system_tests_sync/test_default.py", + *session.posargs, ) @@ -222,7 +229,9 @@ def default_explicit_authorized_user_explicit_project(session): session.install(*TEST_DEPENDENCIES_SYNC) session.install(LIBRARY_DIR) default( - session, "system_tests_sync/test_default.py", + session, + "system_tests_sync/test_default.py", + *session.posargs, ) @@ -233,7 +242,9 @@ def default_cloud_sdk_service_account(session): session.install(*TEST_DEPENDENCIES_SYNC) session.install(LIBRARY_DIR) default( - session, "system_tests_sync/test_default.py", + session, + "system_tests_sync/test_default.py", + *session.posargs, ) @@ -243,7 +254,9 @@ def default_cloud_sdk_authorized_user(session): session.install(*TEST_DEPENDENCIES_SYNC) session.install(LIBRARY_DIR) default( - session, "system_tests_sync/test_default.py", + session, + "system_tests_sync/test_default.py", + *session.posargs, ) @@ -254,7 +267,9 @@ def default_cloud_sdk_authorized_user_configured_project(session): session.install(*TEST_DEPENDENCIES_SYNC) session.install(LIBRARY_DIR) default( - session, "system_tests_sync/test_default.py", + session, + "system_tests_sync/test_default.py", + *session.posargs, ) @@ -266,7 +281,9 @@ def compute_engine(session): del session.virtualenv.env["GOOGLE_APPLICATION_CREDENTIALS"] session.install(LIBRARY_DIR) default( - session, "system_tests_sync/test_compute_engine.py", + session, + "system_tests_sync/test_compute_engine.py", + *session.posargs, ) @@ -320,7 +337,9 @@ def grpc(session): session.install(*TEST_DEPENDENCIES_SYNC, "google-cloud-pubsub==1.7.0") session.env[EXPLICIT_CREDENTIALS_ENV] = SERVICE_ACCOUNT_FILE default( - session, "system_tests_sync/test_grpc.py", + session, + "system_tests_sync/test_grpc.py", + *session.posargs, ) @@ -330,7 +349,9 @@ def requests(session): session.install(*TEST_DEPENDENCIES_SYNC) session.env[EXPLICIT_CREDENTIALS_ENV] = SERVICE_ACCOUNT_FILE default( - session, "system_tests_sync/test_requests.py", + session, + "system_tests_sync/test_requests.py", + *session.posargs, ) @@ -340,7 +361,9 @@ def urllib3(session): session.install(*TEST_DEPENDENCIES_SYNC) session.env[EXPLICIT_CREDENTIALS_ENV] = SERVICE_ACCOUNT_FILE default( - session, "system_tests_sync/test_urllib3.py", + session, + "system_tests_sync/test_urllib3.py", + *session.posargs, ) @@ -350,14 +373,25 @@ def mtls_http(session): session.install(*TEST_DEPENDENCIES_SYNC, "pyopenssl") session.env[EXPLICIT_CREDENTIALS_ENV] = SERVICE_ACCOUNT_FILE default( - session, "system_tests_sync/test_mtls_http.py", + session, + "system_tests_sync/test_mtls_http.py", + *session.posargs, ) @nox.session(python=PYTHON_VERSIONS_SYNC) def external_accounts(session): - session.install(*TEST_DEPENDENCIES_SYNC, "google-auth", "google-api-python-client", "enum34") - default(session, "system_tests_sync/test_external_accounts.py") + session.install( + *TEST_DEPENDENCIES_SYNC, + "google-auth", + "google-api-python-client", + "enum34", + ) + default( + session, + "system_tests_sync/test_external_accounts.py", + *session.posargs, + ) # ASYNC SYSTEM TESTS @@ -367,7 +401,9 @@ def service_account_async(session): session.install(*(TEST_DEPENDENCIES_SYNC + TEST_DEPENDENCIES_ASYNC)) session.install(LIBRARY_DIR) default( - session, "system_tests_async/test_service_account.py", + session, + "system_tests_async/test_service_account.py", + *session.posargs, ) @@ -381,6 +417,7 @@ def default_explicit_service_account_async(session): session, "system_tests_async/test_default.py", "system_tests_async/test_id_token.py", + *session.posargs, ) @@ -390,7 +427,9 @@ def default_explicit_authorized_user_async(session): session.install(*(TEST_DEPENDENCIES_SYNC + TEST_DEPENDENCIES_ASYNC)) session.install(LIBRARY_DIR) default( - session, "system_tests_async/test_default.py", + session, + "system_tests_async/test_default.py", + *session.posargs, ) @@ -402,7 +441,9 @@ def default_explicit_authorized_user_explicit_project_async(session): session.install(*(TEST_DEPENDENCIES_SYNC + TEST_DEPENDENCIES_ASYNC)) session.install(LIBRARY_DIR) default( - session, "system_tests_async/test_default.py", + session, + "system_tests_async/test_default.py", + *session.posargs, ) @@ -413,7 +454,9 @@ def default_cloud_sdk_service_account_async(session): session.install(*(TEST_DEPENDENCIES_SYNC + TEST_DEPENDENCIES_ASYNC)) session.install(LIBRARY_DIR) default( - session, "system_tests_async/test_default.py", + session, + "system_tests_async/test_default.py", + *session.posargs, ) @@ -423,7 +466,9 @@ def default_cloud_sdk_authorized_user_async(session): session.install(*(TEST_DEPENDENCIES_SYNC + TEST_DEPENDENCIES_ASYNC)) session.install(LIBRARY_DIR) default( - session, "system_tests_async/test_default.py", + session, + "system_tests_async/test_default.py", + *session.posargs, ) @@ -434,5 +479,7 @@ def default_cloud_sdk_authorized_user_configured_project_async(session): session.install(*(TEST_DEPENDENCIES_SYNC + TEST_DEPENDENCIES_ASYNC)) session.install(LIBRARY_DIR) default( - session, "system_tests_async/test_default.py", + session, + "system_tests_async/test_default.py", + *session.posargs, ) From bec94664efe5f8f7f0adb4cf9ec23d3ea653961c Mon Sep 17 00:00:00 2001 From: bojeil-google Date: Fri, 9 Jul 2021 13:27:02 -0700 Subject: [PATCH 431/966] feat: define `google.auth.downscoped.Credentials` class (#801) * feat: define `google.auth.downscoped.Credentials` class This is based on [Downscoping with Credential Access Boundaries](https://cloud.google.com/iam/docs/downscoping-short-lived-credentials). The new credentials are initialized mainly using elevated source credentials and a `google.auth.downscoped.CredentialAccessBoundary` instance. The credentials will then get access tokens from the source credentials and exchange them via the GCP STS token exchange endpoint using the provided credentials access boundary rules for downscoped access tokens. The new credentials will inherit the source credentials' scopes but the scopes are not exposed as we cannot always determine the scopes form the source credentials. * Fixes typos in comments. * Addresses review comments. * Moves all constants in the test file to module scope. --- .../google-auth/google/auth/downscoped.py | 86 ++++++ packages/google-auth/tests/test_downscoped.py | 267 ++++++++++++++++++ 2 files changed, 353 insertions(+) diff --git a/packages/google-auth/google/auth/downscoped.py b/packages/google-auth/google/auth/downscoped.py index beea50ec7d65..800f2894c809 100644 --- a/packages/google-auth/google/auth/downscoped.py +++ b/packages/google-auth/google/auth/downscoped.py @@ -48,9 +48,24 @@ .. _Downscoping with Credential Access Boundaries: https://cloud.google.com/iam/docs/downscoping-short-lived-credentials """ +import datetime + +from google.auth import _helpers +from google.auth import credentials +from google.oauth2 import sts + # The maximum number of access boundary rules a Credential Access Boundary can # contain. _MAX_ACCESS_BOUNDARY_RULES_COUNT = 10 +# The token exchange grant_type used for exchanging credentials. +_STS_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:token-exchange" +# The token exchange requested_token_type. This is always an access_token. +_STS_REQUESTED_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token" +# The STS token URL used to exchanged a short lived access token for a downscoped one. +_STS_TOKEN_URL = "https://sts.googleapis.com/v1/token" +# The subject token type to use when exchanging a short lived access token for a +# downscoped token. +_STS_SUBJECT_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token" class CredentialAccessBoundary(object): @@ -403,3 +418,74 @@ def to_json(self): if self.description: json["description"] = self.description return json + + +class Credentials(credentials.CredentialsWithQuotaProject): + """Defines a set of Google credentials that are downscoped from an existing set + of Google OAuth2 credentials. This is useful to restrict the Identity and Access + Management (IAM) permissions that a short-lived credential can use. + The common pattern of usage is to have a token broker with elevated access + generate these downscoped credentials from higher access source credentials and + pass the downscoped short-lived access tokens to a token consumer via some + secure authenticated channel for limited access to Google Cloud Storage + resources. + """ + + def __init__( + self, source_credentials, credential_access_boundary, quota_project_id=None + ): + """Instantiates a downscoped credentials object using the provided source + credentials and credential access boundary rules. + To downscope permissions of a source credential, a Credential Access Boundary + that specifies which resources the new credential can access, as well as an + upper bound on the permissions that are available on each resource, has to be + defined. A downscoped credential can then be instantiated using the source + credential and the Credential Access Boundary. + + Args: + source_credentials (google.auth.credentials.Credentials): The source credentials + to be downscoped based on the provided Credential Access Boundary rules. + credential_access_boundary (google.auth.downscoped.CredentialAccessBoundary): + The Credential Access Boundary which contains a list of access boundary + rules. Each rule contains information on the resource that the rule applies to, + the upper bound of the permissions that are available on that resource and an + optional condition to further restrict permissions. + quota_project_id (Optional[str]): The optional quota project ID. + Raises: + google.auth.exceptions.RefreshError: If the source credentials + return an error on token refresh. + google.auth.exceptions.OAuthError: If the STS token exchange + endpoint returned an error during downscoped token generation. + """ + + super(Credentials, self).__init__() + self._source_credentials = source_credentials + self._credential_access_boundary = credential_access_boundary + self._quota_project_id = quota_project_id + self._sts_client = sts.Client(_STS_TOKEN_URL) + + @_helpers.copy_docstring(credentials.Credentials) + def refresh(self, request): + # Generate an access token from the source credentials. + self._source_credentials.refresh(request) + now = _helpers.utcnow() + # Exchange the access token for a downscoped access token. + response_data = self._sts_client.exchange_token( + request=request, + grant_type=_STS_GRANT_TYPE, + subject_token=self._source_credentials.token, + subject_token_type=_STS_SUBJECT_TOKEN_TYPE, + requested_token_type=_STS_REQUESTED_TOKEN_TYPE, + additional_options=self._credential_access_boundary.to_json(), + ) + self.token = response_data.get("access_token") + lifetime = datetime.timedelta(seconds=response_data.get("expires_in")) + self.expiry = now + lifetime + + @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) + def with_quota_project(self, quota_project_id): + return self.__class__( + self._source_credentials, + self._credential_access_boundary, + quota_project_id=quota_project_id, + ) diff --git a/packages/google-auth/tests/test_downscoped.py b/packages/google-auth/tests/test_downscoped.py index 61b1d1876691..ac60e5b00d56 100644 --- a/packages/google-auth/tests/test_downscoped.py +++ b/packages/google-auth/tests/test_downscoped.py @@ -12,9 +12,19 @@ # See the License for the specific language governing permissions and # limitations under the License. +import datetime +import json + +import mock import pytest +from six.moves import http_client +from six.moves import urllib +from google.auth import _helpers +from google.auth import credentials from google.auth import downscoped +from google.auth import exceptions +from google.auth import transport EXPRESSION = ( @@ -36,6 +46,54 @@ ) OTHER_AVAILABLE_RESOURCE = "//storage.googleapis.com/projects/_/buckets/other-bucket" OTHER_AVAILABLE_PERMISSIONS = ["inRole:roles/storage.objectCreator"] +QUOTA_PROJECT_ID = "QUOTA_PROJECT_ID" +GRANT_TYPE = "urn:ietf:params:oauth:grant-type:token-exchange" +REQUESTED_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token" +TOKEN_EXCHANGE_ENDPOINT = "https://sts.googleapis.com/v1/token" +SUBJECT_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token" +SUCCESS_RESPONSE = { + "access_token": "ACCESS_TOKEN", + "issued_token_type": "urn:ietf:params:oauth:token-type:access_token", + "token_type": "Bearer", + "expires_in": 3600, +} +ERROR_RESPONSE = { + "error": "invalid_grant", + "error_description": "Subject token is invalid.", + "error_uri": "https://tools.ietf.org/html/rfc6749", +} +CREDENTIAL_ACCESS_BOUNDARY_JSON = { + "accessBoundary": { + "accessBoundaryRules": [ + { + "availablePermissions": AVAILABLE_PERMISSIONS, + "availableResource": AVAILABLE_RESOURCE, + "availabilityCondition": { + "expression": EXPRESSION, + "title": TITLE, + "description": DESCRIPTION, + }, + } + ] + } +} + + +class SourceCredentials(credentials.Credentials): + def __init__(self, raise_error=False): + super(SourceCredentials, self).__init__() + self._counter = 0 + self._raise_error = raise_error + + def refresh(self, request): + if self._raise_error: + raise exceptions.RefreshError( + "Failed to refresh access token in source credentials." + ) + now = _helpers.utcnow() + self._counter += 1 + self.token = "ACCESS_TOKEN_{}".format(self._counter) + self.expiry = now + datetime.timedelta(seconds=3600) def make_availability_condition(expression, title=None, description=None): @@ -383,3 +441,212 @@ def test_to_json(self): ] } } + + +class TestCredentials(object): + @staticmethod + def make_credentials(source_credentials=SourceCredentials(), quota_project_id=None): + availability_condition = make_availability_condition( + EXPRESSION, TITLE, DESCRIPTION + ) + access_boundary_rule = make_access_boundary_rule( + AVAILABLE_RESOURCE, AVAILABLE_PERMISSIONS, availability_condition + ) + rules = [access_boundary_rule] + credential_access_boundary = make_credential_access_boundary(rules) + + return downscoped.Credentials( + source_credentials, credential_access_boundary, quota_project_id + ) + + @staticmethod + def make_mock_request(data, status=http_client.OK): + response = mock.create_autospec(transport.Response, instance=True) + response.status = status + response.data = json.dumps(data).encode("utf-8") + + request = mock.create_autospec(transport.Request) + request.return_value = response + + return request + + @staticmethod + def assert_request_kwargs(request_kwargs, headers, request_data): + """Asserts the request was called with the expected parameters. + """ + assert request_kwargs["url"] == TOKEN_EXCHANGE_ENDPOINT + assert request_kwargs["method"] == "POST" + assert request_kwargs["headers"] == headers + assert request_kwargs["body"] is not None + body_tuples = urllib.parse.parse_qsl(request_kwargs["body"]) + for (k, v) in body_tuples: + assert v.decode("utf-8") == request_data[k.decode("utf-8")] + assert len(body_tuples) == len(request_data.keys()) + + def test_default_state(self): + credentials = self.make_credentials() + + # No token acquired yet. + assert not credentials.token + assert not credentials.valid + # Expiration hasn't been set yet. + assert not credentials.expiry + assert not credentials.expired + # No quota project ID set. + assert not credentials.quota_project_id + + def test_with_quota_project(self): + credentials = self.make_credentials() + + assert not credentials.quota_project_id + + quota_project_creds = credentials.with_quota_project("project-foo") + + assert quota_project_creds.quota_project_id == "project-foo" + + @mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) + def test_refresh(self, unused_utcnow): + response = SUCCESS_RESPONSE.copy() + # Test custom expiration to confirm expiry is set correctly. + response["expires_in"] = 2800 + expected_expiry = datetime.datetime.min + datetime.timedelta( + seconds=response["expires_in"] + ) + headers = {"Content-Type": "application/x-www-form-urlencoded"} + request_data = { + "grant_type": GRANT_TYPE, + "subject_token": "ACCESS_TOKEN_1", + "subject_token_type": SUBJECT_TOKEN_TYPE, + "requested_token_type": REQUESTED_TOKEN_TYPE, + "options": urllib.parse.quote(json.dumps(CREDENTIAL_ACCESS_BOUNDARY_JSON)), + } + request = self.make_mock_request(status=http_client.OK, data=response) + source_credentials = SourceCredentials() + credentials = self.make_credentials(source_credentials=source_credentials) + + # Spy on calls to source credentials refresh to confirm the expected request + # instance is used. + with mock.patch.object( + source_credentials, "refresh", wraps=source_credentials.refresh + ) as wrapped_souce_cred_refresh: + credentials.refresh(request) + + self.assert_request_kwargs(request.call_args[1], headers, request_data) + assert credentials.valid + assert credentials.expiry == expected_expiry + assert not credentials.expired + assert credentials.token == response["access_token"] + # Confirm source credentials called with the same request instance. + wrapped_souce_cred_refresh.assert_called_with(request) + + def test_refresh_token_exchange_error(self): + request = self.make_mock_request( + status=http_client.BAD_REQUEST, data=ERROR_RESPONSE + ) + credentials = self.make_credentials() + + with pytest.raises(exceptions.OAuthError) as excinfo: + credentials.refresh(request) + + assert excinfo.match( + r"Error code invalid_grant: Subject token is invalid. - https://tools.ietf.org/html/rfc6749" + ) + assert not credentials.expired + assert credentials.token is None + + def test_refresh_source_credentials_refresh_error(self): + # Initialize downscoped credentials with source credentials that raise + # an error on refresh. + credentials = self.make_credentials( + source_credentials=SourceCredentials(raise_error=True) + ) + + with pytest.raises(exceptions.RefreshError) as excinfo: + credentials.refresh(mock.sentinel.request) + + assert excinfo.match(r"Failed to refresh access token in source credentials.") + assert not credentials.expired + assert credentials.token is None + + def test_apply_without_quota_project_id(self): + headers = {} + request = self.make_mock_request(status=http_client.OK, data=SUCCESS_RESPONSE) + credentials = self.make_credentials() + + credentials.refresh(request) + credentials.apply(headers) + + assert headers == { + "authorization": "Bearer {}".format(SUCCESS_RESPONSE["access_token"]) + } + + def test_apply_with_quota_project_id(self): + headers = {"other": "header-value"} + request = self.make_mock_request(status=http_client.OK, data=SUCCESS_RESPONSE) + credentials = self.make_credentials(quota_project_id=QUOTA_PROJECT_ID) + + credentials.refresh(request) + credentials.apply(headers) + + assert headers == { + "other": "header-value", + "authorization": "Bearer {}".format(SUCCESS_RESPONSE["access_token"]), + "x-goog-user-project": QUOTA_PROJECT_ID, + } + + def test_before_request(self): + headers = {"other": "header-value"} + request = self.make_mock_request(status=http_client.OK, data=SUCCESS_RESPONSE) + credentials = self.make_credentials() + + # First call should call refresh, setting the token. + credentials.before_request(request, "POST", "https://example.com/api", headers) + + assert headers == { + "other": "header-value", + "authorization": "Bearer {}".format(SUCCESS_RESPONSE["access_token"]), + } + + # Second call shouldn't call refresh (request should be untouched). + credentials.before_request( + mock.sentinel.request, "POST", "https://example.com/api", headers + ) + + assert headers == { + "other": "header-value", + "authorization": "Bearer {}".format(SUCCESS_RESPONSE["access_token"]), + } + + @mock.patch("google.auth._helpers.utcnow") + def test_before_request_expired(self, utcnow): + headers = {} + request = self.make_mock_request(status=http_client.OK, data=SUCCESS_RESPONSE) + credentials = self.make_credentials() + credentials.token = "token" + utcnow.return_value = datetime.datetime.min + # Set the expiration to one second more than now plus the clock skew + # accommodation. These credentials should be valid. + credentials.expiry = ( + datetime.datetime.min + _helpers.CLOCK_SKEW + datetime.timedelta(seconds=1) + ) + + assert credentials.valid + assert not credentials.expired + + credentials.before_request(request, "POST", "https://example.com/api", headers) + + # Cached token should be used. + assert headers == {"authorization": "Bearer token"} + + # Next call should simulate 1 second passed. + utcnow.return_value = datetime.datetime.min + datetime.timedelta(seconds=1) + + assert not credentials.valid + assert credentials.expired + + credentials.before_request(request, "POST", "https://example.com/api", headers) + + # New token should be retrieved. + assert headers == { + "authorization": "Bearer {}".format(SUCCESS_RESPONSE["access_token"]) + } From 6ad1f22e7cca4aea3ed6b81f8f97d2c4a115463e Mon Sep 17 00:00:00 2001 From: Chaoren Date: Wed, 14 Jul 2021 11:02:10 -0400 Subject: [PATCH 432/966] feat: service account is able to use a private token endpoint (#784) In [Private Service Connect](https://cloud.google.com/vpc/docs/private-service-connect), users can use an endpoint which is private to their VPC network. The request is eventually routed to the oauth2.googleapis.com/token so the "aud" in the assertion still should be oauth2.googleapis.com/token. After this change, service account can send requests to the private endpoint (if configured) and still use the oauth2.googleapis.com/token in the assertion. --- packages/google-auth/google/oauth2/service_account.py | 5 +++-- .../google-auth/tests/oauth2/test_service_account.py | 4 ++-- .../tests_async/oauth2/test_service_account_async.py | 10 ++++++++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/packages/google-auth/google/oauth2/service_account.py b/packages/google-auth/google/oauth2/service_account.py index dd3658994336..8f18f26ea17b 100644 --- a/packages/google-auth/google/oauth2/service_account.py +++ b/packages/google-auth/google/oauth2/service_account.py @@ -80,6 +80,7 @@ from google.oauth2 import _client _DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds +_GOOGLE_OAUTH2_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token" class Credentials( @@ -382,7 +383,7 @@ def _make_authorization_grant_assertion(self): # The issuer must be the service account email. "iss": self._service_account_email, # The audience must be the auth token endpoint's URI - "aud": self._token_uri, + "aud": _GOOGLE_OAUTH2_TOKEN_ENDPOINT, "scope": _helpers.scopes_to_string(self._scopes or ()), } @@ -643,7 +644,7 @@ def _make_authorization_grant_assertion(self): # The issuer must be the service account email. "iss": self.service_account_email, # The audience must be the auth token endpoint's URI - "aud": self._token_uri, + "aud": _GOOGLE_OAUTH2_TOKEN_ENDPOINT, # The target audience specifies which service the ID token is # intended for. "target_audience": self._target_audience, diff --git a/packages/google-auth/tests/oauth2/test_service_account.py b/packages/google-auth/tests/oauth2/test_service_account.py index 5852d37146b8..370438f48d04 100644 --- a/packages/google-auth/tests/oauth2/test_service_account.py +++ b/packages/google-auth/tests/oauth2/test_service_account.py @@ -167,7 +167,7 @@ def test__make_authorization_grant_assertion(self): token = credentials._make_authorization_grant_assertion() payload = jwt.decode(token, PUBLIC_CERT_BYTES) assert payload["iss"] == self.SERVICE_ACCOUNT_EMAIL - assert payload["aud"] == self.TOKEN_URI + assert payload["aud"] == service_account._GOOGLE_OAUTH2_TOKEN_ENDPOINT def test__make_authorization_grant_assertion_scoped(self): credentials = self.make_credentials() @@ -440,7 +440,7 @@ def test__make_authorization_grant_assertion(self): token = credentials._make_authorization_grant_assertion() payload = jwt.decode(token, PUBLIC_CERT_BYTES) assert payload["iss"] == self.SERVICE_ACCOUNT_EMAIL - assert payload["aud"] == self.TOKEN_URI + assert payload["aud"] == service_account._GOOGLE_OAUTH2_TOKEN_ENDPOINT assert payload["target_audience"] == self.TARGET_AUDIENCE @mock.patch("google.oauth2._client.id_token_jwt_grant", autospec=True) diff --git a/packages/google-auth/tests_async/oauth2/test_service_account_async.py b/packages/google-auth/tests_async/oauth2/test_service_account_async.py index 40794536cee9..3dce13d82b02 100644 --- a/packages/google-auth/tests_async/oauth2/test_service_account_async.py +++ b/packages/google-auth/tests_async/oauth2/test_service_account_async.py @@ -152,7 +152,10 @@ def test__make_authorization_grant_assertion(self): token = credentials._make_authorization_grant_assertion() payload = jwt.decode(token, test_service_account.PUBLIC_CERT_BYTES) assert payload["iss"] == self.SERVICE_ACCOUNT_EMAIL - assert payload["aud"] == self.TOKEN_URI + assert ( + payload["aud"] + == service_account.service_account._GOOGLE_OAUTH2_TOKEN_ENDPOINT + ) def test__make_authorization_grant_assertion_scoped(self): credentials = self.make_credentials() @@ -311,7 +314,10 @@ def test__make_authorization_grant_assertion(self): token = credentials._make_authorization_grant_assertion() payload = jwt.decode(token, test_service_account.PUBLIC_CERT_BYTES) assert payload["iss"] == self.SERVICE_ACCOUNT_EMAIL - assert payload["aud"] == self.TOKEN_URI + assert ( + payload["aud"] + == service_account.service_account._GOOGLE_OAUTH2_TOKEN_ENDPOINT + ) assert payload["target_audience"] == self.TARGET_AUDIENCE @mock.patch("google.oauth2._client_async.id_token_jwt_grant", autospec=True) From 8f31d9375c548a9e9b2919866613f7d950ae917f Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Wed, 14 Jul 2021 16:08:26 +0000 Subject: [PATCH 433/966] build(python): exit with success status if no samples found (#802) Source-Link: https://github.com/googleapis/synthtool/commit/53ea3896a52f87c758e79b5a19fa338c83925a98 Post-Processor: gcr.io/repo-automation-bots/owlbot-python:latest@sha256:e1793a23ae0ee9aafb2e3a53b564a351f74790dbe3c2d75f8fc3b8c43e5c036c --- packages/google-auth/.github/.OwlBot.lock.yaml | 2 +- packages/google-auth/.kokoro/test-samples-impl.sh | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/google-auth/.github/.OwlBot.lock.yaml b/packages/google-auth/.github/.OwlBot.lock.yaml index 0954585f2833..a5d3697f2167 100644 --- a/packages/google-auth/.github/.OwlBot.lock.yaml +++ b/packages/google-auth/.github/.OwlBot.lock.yaml @@ -1,3 +1,3 @@ docker: image: gcr.io/repo-automation-bots/owlbot-python:latest - digest: sha256:df50e8d462f86d6bcb42f27ecad55bb12c404f1c65de9c6fe4c4d25120080bd6 + digest: sha256:e1793a23ae0ee9aafb2e3a53b564a351f74790dbe3c2d75f8fc3b8c43e5c036c diff --git a/packages/google-auth/.kokoro/test-samples-impl.sh b/packages/google-auth/.kokoro/test-samples-impl.sh index cf5de74c17a5..311a8d54b9f1 100755 --- a/packages/google-auth/.kokoro/test-samples-impl.sh +++ b/packages/google-auth/.kokoro/test-samples-impl.sh @@ -20,9 +20,9 @@ set -eo pipefail # Enables `**` to include files nested inside sub-folders shopt -s globstar -# Exit early if samples directory doesn't exist -if [ ! -d "./samples" ]; then - echo "No tests run. `./samples` not found" +# Exit early if samples don't exist +if ! find samples -name 'requirements.txt' | grep -q .; then + echo "No tests run. './samples/**/requirements.txt' not found" exit 0 fi From d0a828acc9028904368ae210eb7931d25f5827ce Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Wed, 14 Jul 2021 17:50:29 +0000 Subject: [PATCH 434/966] build(python): remove python 3.7 from kokoro Dockerfile (#803) Source-Link: https://github.com/googleapis/synthtool/commit/e44dc0c742b1230887a73552357e0c18dcc30b92 Post-Processor: gcr.io/repo-automation-bots/owlbot-python:latest@sha256:5ff7446edeaede81c3ed58b23a4e76a5403fba1350ce28478045657303b6479d --- .../google-auth/.github/.OwlBot.lock.yaml | 2 +- .../.kokoro/docker/docs/Dockerfile | 35 +-------------- .../.kokoro/docker/docs/fetch_gpg_keys.sh | 45 ------------------- 3 files changed, 3 insertions(+), 79 deletions(-) delete mode 100755 packages/google-auth/.kokoro/docker/docs/fetch_gpg_keys.sh diff --git a/packages/google-auth/.github/.OwlBot.lock.yaml b/packages/google-auth/.github/.OwlBot.lock.yaml index a5d3697f2167..cb06536dab0b 100644 --- a/packages/google-auth/.github/.OwlBot.lock.yaml +++ b/packages/google-auth/.github/.OwlBot.lock.yaml @@ -1,3 +1,3 @@ docker: image: gcr.io/repo-automation-bots/owlbot-python:latest - digest: sha256:e1793a23ae0ee9aafb2e3a53b564a351f74790dbe3c2d75f8fc3b8c43e5c036c + digest: sha256:5ff7446edeaede81c3ed58b23a4e76a5403fba1350ce28478045657303b6479d diff --git a/packages/google-auth/.kokoro/docker/docs/Dockerfile b/packages/google-auth/.kokoro/docker/docs/Dockerfile index 412b0b56a921..4e1b1fb8b5a5 100644 --- a/packages/google-auth/.kokoro/docker/docs/Dockerfile +++ b/packages/google-auth/.kokoro/docker/docs/Dockerfile @@ -40,6 +40,7 @@ RUN apt-get update \ libssl-dev \ libsqlite3-dev \ portaudio19-dev \ + python3-distutils \ redis-server \ software-properties-common \ ssh \ @@ -59,40 +60,8 @@ RUN apt-get update \ && rm -rf /var/lib/apt/lists/* \ && rm -f /var/cache/apt/archives/*.deb - -COPY fetch_gpg_keys.sh /tmp -# Install the desired versions of Python. -RUN set -ex \ - && export GNUPGHOME="$(mktemp -d)" \ - && echo "disable-ipv6" >> "${GNUPGHOME}/dirmngr.conf" \ - && /tmp/fetch_gpg_keys.sh \ - && for PYTHON_VERSION in 3.7.8 3.8.5; do \ - wget --no-check-certificate -O python-${PYTHON_VERSION}.tar.xz "https://www.python.org/ftp/python/${PYTHON_VERSION%%[a-z]*}/Python-$PYTHON_VERSION.tar.xz" \ - && wget --no-check-certificate -O python-${PYTHON_VERSION}.tar.xz.asc "https://www.python.org/ftp/python/${PYTHON_VERSION%%[a-z]*}/Python-$PYTHON_VERSION.tar.xz.asc" \ - && gpg --batch --verify python-${PYTHON_VERSION}.tar.xz.asc python-${PYTHON_VERSION}.tar.xz \ - && rm -r python-${PYTHON_VERSION}.tar.xz.asc \ - && mkdir -p /usr/src/python-${PYTHON_VERSION} \ - && tar -xJC /usr/src/python-${PYTHON_VERSION} --strip-components=1 -f python-${PYTHON_VERSION}.tar.xz \ - && rm python-${PYTHON_VERSION}.tar.xz \ - && cd /usr/src/python-${PYTHON_VERSION} \ - && ./configure \ - --enable-shared \ - # This works only on Python 2.7 and throws a warning on every other - # version, but seems otherwise harmless. - --enable-unicode=ucs4 \ - --with-system-ffi \ - --without-ensurepip \ - && make -j$(nproc) \ - && make install \ - && ldconfig \ - ; done \ - && rm -rf "${GNUPGHOME}" \ - && rm -rf /usr/src/python* \ - && rm -rf ~/.cache/ - RUN wget -O /tmp/get-pip.py 'https://bootstrap.pypa.io/get-pip.py' \ - && python3.7 /tmp/get-pip.py \ && python3.8 /tmp/get-pip.py \ && rm /tmp/get-pip.py -CMD ["python3.7"] +CMD ["python3.8"] diff --git a/packages/google-auth/.kokoro/docker/docs/fetch_gpg_keys.sh b/packages/google-auth/.kokoro/docker/docs/fetch_gpg_keys.sh deleted file mode 100755 index d653dd868e4b..000000000000 --- a/packages/google-auth/.kokoro/docker/docs/fetch_gpg_keys.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/bin/bash -# Copyright 2020 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# A script to fetch gpg keys with retry. -# Avoid jinja parsing the file. -# - -function retry { - if [[ "${#}" -le 1 ]]; then - echo "Usage: ${0} retry_count commands.." - exit 1 - fi - local retries=${1} - local command="${@:2}" - until [[ "${retries}" -le 0 ]]; do - $command && return 0 - if [[ $? -ne 0 ]]; then - echo "command failed, retrying" - ((retries--)) - fi - done - return 1 -} - -# 3.6.9, 3.7.5 (Ned Deily) -retry 3 gpg --keyserver ha.pool.sks-keyservers.net --recv-keys \ - 0D96DF4D4110E5C43FBFB17F2D347EA6AA65421D - -# 3.8.0 (Łukasz Langa) -retry 3 gpg --keyserver ha.pool.sks-keyservers.net --recv-keys \ - E3FF2839C048B25C084DEBE9B26995E310250568 - -# From 0633f98734d6763ed5dbfe7635bd3fcfc3f0845b Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Wed, 14 Jul 2021 11:40:05 -0700 Subject: [PATCH 435/966] fix: fix fetch_id_token credential lookup order to match adc (#748) * fix: fix fetch_id_token credential lookup order to match adc * fix tests * fix linter * update * update * add comments --- .../google/oauth2/_id_token_async.py | 93 ++++++++-------- .../google-auth/google/oauth2/id_token.py | 89 +++++++-------- .../google-auth/tests/oauth2/test_id_token.py | 98 +++++++++++------ .../tests_async/oauth2/test_id_token.py | 102 ++++++++++++------ 4 files changed, 227 insertions(+), 155 deletions(-) diff --git a/packages/google-auth/google/oauth2/_id_token_async.py b/packages/google-auth/google/oauth2/_id_token_async.py index f5ef8baff8a7..ab681a9cb730 100644 --- a/packages/google-auth/google/oauth2/_id_token_async.py +++ b/packages/google-auth/google/oauth2/_id_token_async.py @@ -180,13 +180,14 @@ async def verify_firebase_token(id_token, request, audience=None): async def fetch_id_token(request, audience): """Fetch the ID Token from the current environment. - This function acquires ID token from the environment in the following order: + This function acquires ID token from the environment in the following order. + See https://google.aip.dev/auth/4110. - 1. If the application is running in Compute Engine, App Engine or Cloud Run, - then the ID token are obtained from the metadata server. - 2. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set + 1. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set to the path of a valid service account JSON file, then ID token is acquired using this service account credentials. + 2. If the application is running in Compute Engine, App Engine or Cloud Run, + then the ID token are obtained from the metadata server. 3. If metadata server doesn't exist and no valid service account credentials are found, :class:`~google.auth.exceptions.DefaultCredentialsError` will be raised. @@ -214,54 +215,52 @@ async def fetch_id_token(request, audience): If metadata server doesn't exist and no valid service account credentials are found. """ - # 1. First try to fetch ID token from metadata server if it exists. The code - # works for GAE and Cloud Run metadata server as well. - try: - from google.auth import compute_engine - - request_new = requests.Request() - credentials = compute_engine.IDTokenCredentials( - request_new, audience, use_metadata_identity_endpoint=True - ) - credentials.refresh(request_new) - - return credentials.token - - except (ImportError, exceptions.TransportError, exceptions.RefreshError): - pass - - # 2. Try to use service account credentials to get ID token. - - # Try to get credentials from the GOOGLE_APPLICATION_CREDENTIALS environment + # 1. Try to get credentials from the GOOGLE_APPLICATION_CREDENTIALS environment # variable. credentials_filename = os.environ.get(environment_vars.CREDENTIALS) - if not ( - credentials_filename - and os.path.exists(credentials_filename) - and os.path.isfile(credentials_filename) - ): - raise exceptions.DefaultCredentialsError( - "Neither metadata server or valid service account credentials are found." - ) + if credentials_filename: + if not ( + os.path.exists(credentials_filename) + and os.path.isfile(credentials_filename) + ): + raise exceptions.DefaultCredentialsError( + "GOOGLE_APPLICATION_CREDENTIALS path is either not found or invalid." + ) - try: - with open(credentials_filename, "r") as f: - info = json.load(f) - credentials_content = ( - (info.get("type") == "service_account") and info or None + try: + with open(credentials_filename, "r") as f: + from google.oauth2 import _service_account_async as service_account + + info = json.load(f) + if info.get("type") == "service_account": + credentials = service_account.IDTokenCredentials.from_service_account_info( + info, target_audience=audience + ) + await credentials.refresh(request) + return credentials.token + except ValueError as caught_exc: + new_exc = exceptions.DefaultCredentialsError( + "GOOGLE_APPLICATION_CREDENTIALS is not valid service account credentials.", + caught_exc, ) + six.raise_from(new_exc, caught_exc) - from google.oauth2 import _service_account_async as service_account + # 2. Try to fetch ID token from metada server if it exists. The code works for GAE and + # Cloud Run metadata server as well. + try: + from google.auth import compute_engine + from google.auth.compute_engine import _metadata - credentials = service_account.IDTokenCredentials.from_service_account_info( - credentials_content, target_audience=audience + request_new = requests.Request() + if _metadata.ping(request_new): + credentials = compute_engine.IDTokenCredentials( + request_new, audience, use_metadata_identity_endpoint=True ) - except ValueError as caught_exc: - new_exc = exceptions.DefaultCredentialsError( - "Neither metadata server or valid service account credentials are found.", - caught_exc, - ) - six.raise_from(new_exc, caught_exc) + credentials.refresh(request_new) + return credentials.token + except (ImportError, exceptions.TransportError): + pass - await credentials.refresh(request) - return credentials.token + raise exceptions.DefaultCredentialsError( + "Neither metadata server or valid service account credentials are found." + ) diff --git a/packages/google-auth/google/oauth2/id_token.py b/packages/google-auth/google/oauth2/id_token.py index 5fbb6a13371c..540ccd125124 100644 --- a/packages/google-auth/google/oauth2/id_token.py +++ b/packages/google-auth/google/oauth2/id_token.py @@ -179,13 +179,14 @@ def verify_firebase_token(id_token, request, audience=None): def fetch_id_token(request, audience): """Fetch the ID Token from the current environment. - This function acquires ID token from the environment in the following order: + This function acquires ID token from the environment in the following order. + See https://google.aip.dev/auth/4110. - 1. If the application is running in Compute Engine, App Engine or Cloud Run, - then the ID token are obtained from the metadata server. - 2. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set + 1. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set to the path of a valid service account JSON file, then ID token is acquired using this service account credentials. + 2. If the application is running in Compute Engine, App Engine or Cloud Run, + then the ID token are obtained from the metadata server. 3. If metadata server doesn't exist and no valid service account credentials are found, :class:`~google.auth.exceptions.DefaultCredentialsError` will be raised. @@ -213,51 +214,51 @@ def fetch_id_token(request, audience): If metadata server doesn't exist and no valid service account credentials are found. """ - # 1. First try to fetch ID token from metada server if it exists. The code - # works for GAE and Cloud Run metadata server as well. - try: - from google.auth import compute_engine - - credentials = compute_engine.IDTokenCredentials( - request, audience, use_metadata_identity_endpoint=True - ) - credentials.refresh(request) - return credentials.token - except (ImportError, exceptions.TransportError, exceptions.RefreshError): - pass - - # 2. Try to use service account credentials to get ID token. - - # Try to get credentials from the GOOGLE_APPLICATION_CREDENTIALS environment + # 1. Try to get credentials from the GOOGLE_APPLICATION_CREDENTIALS environment # variable. credentials_filename = os.environ.get(environment_vars.CREDENTIALS) - if not ( - credentials_filename - and os.path.exists(credentials_filename) - and os.path.isfile(credentials_filename) - ): - raise exceptions.DefaultCredentialsError( - "Neither metadata server or valid service account credentials are found." - ) + if credentials_filename: + if not ( + os.path.exists(credentials_filename) + and os.path.isfile(credentials_filename) + ): + raise exceptions.DefaultCredentialsError( + "GOOGLE_APPLICATION_CREDENTIALS path is either not found or invalid." + ) - try: - with open(credentials_filename, "r") as f: - info = json.load(f) - credentials_content = ( - (info.get("type") == "service_account") and info or None + try: + with open(credentials_filename, "r") as f: + from google.oauth2 import service_account + + info = json.load(f) + if info.get("type") == "service_account": + credentials = service_account.IDTokenCredentials.from_service_account_info( + info, target_audience=audience + ) + credentials.refresh(request) + return credentials.token + except ValueError as caught_exc: + new_exc = exceptions.DefaultCredentialsError( + "GOOGLE_APPLICATION_CREDENTIALS is not valid service account credentials.", + caught_exc, ) + six.raise_from(new_exc, caught_exc) - from google.oauth2 import service_account + # 2. Try to fetch ID token from metada server if it exists. The code works for GAE and + # Cloud Run metadata server as well. + try: + from google.auth import compute_engine + from google.auth.compute_engine import _metadata - credentials = service_account.IDTokenCredentials.from_service_account_info( - credentials_content, target_audience=audience + if _metadata.ping(request): + credentials = compute_engine.IDTokenCredentials( + request, audience, use_metadata_identity_endpoint=True ) - except ValueError as caught_exc: - new_exc = exceptions.DefaultCredentialsError( - "Neither metadata server or valid service account credentials are found.", - caught_exc, - ) - six.raise_from(new_exc, caught_exc) + credentials.refresh(request) + return credentials.token + except (ImportError, exceptions.TransportError): + pass - credentials.refresh(request) - return credentials.token + raise exceptions.DefaultCredentialsError( + "Neither metadata server or valid service account credentials are found." + ) diff --git a/packages/google-auth/tests/oauth2/test_id_token.py b/packages/google-auth/tests/oauth2/test_id_token.py index 0c70d6891f52..ab6774355c37 100644 --- a/packages/google-auth/tests/oauth2/test_id_token.py +++ b/packages/google-auth/tests/oauth2/test_id_token.py @@ -23,6 +23,7 @@ from google.auth import transport import google.auth.compute_engine._metadata from google.oauth2 import id_token +from google.oauth2 import service_account SERVICE_ACCOUNT_FILE = os.path.join( os.path.dirname(__file__), "../data/service_account.json" @@ -134,62 +135,93 @@ def test_verify_firebase_token(verify_token): ) -def test_fetch_id_token_from_metadata_server(): +def test_fetch_id_token_from_metadata_server(monkeypatch): + monkeypatch.delenv(environment_vars.CREDENTIALS, raising=False) + def mock_init(self, request, audience, use_metadata_identity_endpoint): assert use_metadata_identity_endpoint self.token = "id_token" - with mock.patch.multiple( - google.auth.compute_engine.IDTokenCredentials, - __init__=mock_init, - refresh=mock.Mock(), - ): - request = mock.Mock() - token = id_token.fetch_id_token(request, "https://pubsub.googleapis.com") - assert token == "id_token" + with mock.patch("google.auth.compute_engine._metadata.ping", return_value=True): + with mock.patch.multiple( + google.auth.compute_engine.IDTokenCredentials, + __init__=mock_init, + refresh=mock.Mock(), + ): + request = mock.Mock() + token = id_token.fetch_id_token(request, "https://pubsub.googleapis.com") + assert token == "id_token" -@mock.patch.object( - google.auth.compute_engine.IDTokenCredentials, - "__init__", - side_effect=exceptions.TransportError(), -) -def test_fetch_id_token_from_explicit_cred_json_file(mock_init, monkeypatch): +def test_fetch_id_token_from_explicit_cred_json_file(monkeypatch): monkeypatch.setenv(environment_vars.CREDENTIALS, SERVICE_ACCOUNT_FILE) def mock_refresh(self, request): self.token = "id_token" - with mock.patch.object( - google.oauth2.service_account.IDTokenCredentials, "refresh", mock_refresh - ): + with mock.patch.object(service_account.IDTokenCredentials, "refresh", mock_refresh): request = mock.Mock() token = id_token.fetch_id_token(request, "https://pubsub.googleapis.com") assert token == "id_token" -@mock.patch.object( - google.auth.compute_engine.IDTokenCredentials, - "__init__", - side_effect=exceptions.TransportError(), -) -def test_fetch_id_token_no_cred_json_file(mock_init, monkeypatch): +def test_fetch_id_token_no_cred_exists(monkeypatch): monkeypatch.delenv(environment_vars.CREDENTIALS, raising=False) - with pytest.raises(exceptions.DefaultCredentialsError): + with mock.patch( + "google.auth.compute_engine._metadata.ping", + side_effect=exceptions.TransportError(), + ): + with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: + request = mock.Mock() + id_token.fetch_id_token(request, "https://pubsub.googleapis.com") + assert excinfo.match( + r"Neither metadata server or valid service account credentials are found." + ) + + with mock.patch("google.auth.compute_engine._metadata.ping", return_value=False): + with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: + request = mock.Mock() + id_token.fetch_id_token(request, "https://pubsub.googleapis.com") + assert excinfo.match( + r"Neither metadata server or valid service account credentials are found." + ) + + +def test_fetch_id_token_invalid_cred_file_type(monkeypatch): + user_credentials_file = os.path.join( + os.path.dirname(__file__), "../data/authorized_user.json" + ) + monkeypatch.setenv(environment_vars.CREDENTIALS, user_credentials_file) + + with mock.patch("google.auth.compute_engine._metadata.ping", return_value=False): + with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: + request = mock.Mock() + id_token.fetch_id_token(request, "https://pubsub.googleapis.com") + assert excinfo.match( + r"Neither metadata server or valid service account credentials are found." + ) + + +def test_fetch_id_token_invalid_json(monkeypatch): + not_json_file = os.path.join(os.path.dirname(__file__), "../data/public_cert.pem") + monkeypatch.setenv(environment_vars.CREDENTIALS, not_json_file) + + with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: request = mock.Mock() id_token.fetch_id_token(request, "https://pubsub.googleapis.com") + assert excinfo.match( + r"GOOGLE_APPLICATION_CREDENTIALS is not valid service account credentials." + ) -@mock.patch.object( - google.auth.compute_engine.IDTokenCredentials, - "__init__", - side_effect=exceptions.TransportError(), -) -def test_fetch_id_token_invalid_cred_file(mock_init, monkeypatch): - not_json_file = os.path.join(os.path.dirname(__file__), "../data/public_cert.pem") +def test_fetch_id_token_invalid_cred_path(monkeypatch): + not_json_file = os.path.join(os.path.dirname(__file__), "../data/not_exists.json") monkeypatch.setenv(environment_vars.CREDENTIALS, not_json_file) - with pytest.raises(exceptions.DefaultCredentialsError): + with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: request = mock.Mock() id_token.fetch_id_token(request, "https://pubsub.googleapis.com") + assert excinfo.match( + r"GOOGLE_APPLICATION_CREDENTIALS path is either not found or invalid." + ) diff --git a/packages/google-auth/tests_async/oauth2/test_id_token.py b/packages/google-auth/tests_async/oauth2/test_id_token.py index a46bd615ecaf..1deb9efd676f 100644 --- a/packages/google-auth/tests_async/oauth2/test_id_token.py +++ b/packages/google-auth/tests_async/oauth2/test_id_token.py @@ -21,6 +21,7 @@ from google.auth import exceptions import google.auth.compute_engine._metadata from google.oauth2 import _id_token_async as id_token +from google.oauth2 import _service_account_async from google.oauth2 import id_token as sync_id_token from tests.oauth2 import test_id_token @@ -139,67 +140,106 @@ async def test_verify_firebase_token(verify_token): @pytest.mark.asyncio -async def test_fetch_id_token_from_metadata_server(): +async def test_fetch_id_token_from_metadata_server(monkeypatch): + monkeypatch.delenv(environment_vars.CREDENTIALS, raising=False) + def mock_init(self, request, audience, use_metadata_identity_endpoint): assert use_metadata_identity_endpoint self.token = "id_token" - with mock.patch.multiple( - google.auth.compute_engine.IDTokenCredentials, - __init__=mock_init, - refresh=mock.Mock(), - ): - request = mock.AsyncMock() - token = await id_token.fetch_id_token(request, "https://pubsub.googleapis.com") - assert token == "id_token" + with mock.patch("google.auth.compute_engine._metadata.ping", return_value=True): + with mock.patch.multiple( + google.auth.compute_engine.IDTokenCredentials, + __init__=mock_init, + refresh=mock.Mock(), + ): + request = mock.AsyncMock() + token = await id_token.fetch_id_token( + request, "https://pubsub.googleapis.com" + ) + assert token == "id_token" -@mock.patch.object( - google.auth.compute_engine.IDTokenCredentials, - "__init__", - side_effect=exceptions.TransportError(), -) @pytest.mark.asyncio -async def test_fetch_id_token_from_explicit_cred_json_file(mock_init, monkeypatch): +async def test_fetch_id_token_from_explicit_cred_json_file(monkeypatch): monkeypatch.setenv(environment_vars.CREDENTIALS, test_id_token.SERVICE_ACCOUNT_FILE) async def mock_refresh(self, request): self.token = "id_token" with mock.patch.object( - google.oauth2._service_account_async.IDTokenCredentials, "refresh", mock_refresh + _service_account_async.IDTokenCredentials, "refresh", mock_refresh ): request = mock.AsyncMock() token = await id_token.fetch_id_token(request, "https://pubsub.googleapis.com") assert token == "id_token" -@mock.patch.object( - google.auth.compute_engine.IDTokenCredentials, - "__init__", - side_effect=exceptions.TransportError(), -) @pytest.mark.asyncio -async def test_fetch_id_token_no_cred_json_file(mock_init, monkeypatch): +async def test_fetch_id_token_no_cred_exists(monkeypatch): monkeypatch.delenv(environment_vars.CREDENTIALS, raising=False) - with pytest.raises(exceptions.DefaultCredentialsError): + with mock.patch( + "google.auth.compute_engine._metadata.ping", + side_effect=exceptions.TransportError(), + ): + with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: + request = mock.AsyncMock() + await id_token.fetch_id_token(request, "https://pubsub.googleapis.com") + assert excinfo.match( + r"Neither metadata server or valid service account credentials are found." + ) + + with mock.patch("google.auth.compute_engine._metadata.ping", return_value=False): + with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: + request = mock.AsyncMock() + await id_token.fetch_id_token(request, "https://pubsub.googleapis.com") + assert excinfo.match( + r"Neither metadata server or valid service account credentials are found." + ) + + +@pytest.mark.asyncio +async def test_fetch_id_token_invalid_cred_file(monkeypatch): + not_json_file = os.path.join( + os.path.dirname(__file__), "../../tests/data/public_cert.pem" + ) + monkeypatch.setenv(environment_vars.CREDENTIALS, not_json_file) + + with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: request = mock.AsyncMock() await id_token.fetch_id_token(request, "https://pubsub.googleapis.com") + assert excinfo.match( + r"GOOGLE_APPLICATION_CREDENTIALS is not valid service account credentials." + ) -@mock.patch.object( - google.auth.compute_engine.IDTokenCredentials, - "__init__", - side_effect=exceptions.TransportError(), -) @pytest.mark.asyncio -async def test_fetch_id_token_invalid_cred_file(mock_init, monkeypatch): +async def test_fetch_id_token_invalid_cred_type(monkeypatch): + user_credentials_file = os.path.join( + os.path.dirname(__file__), "../../tests/data/authorized_user.json" + ) + monkeypatch.setenv(environment_vars.CREDENTIALS, user_credentials_file) + + with mock.patch("google.auth.compute_engine._metadata.ping", return_value=False): + with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: + request = mock.AsyncMock() + await id_token.fetch_id_token(request, "https://pubsub.googleapis.com") + assert excinfo.match( + r"Neither metadata server or valid service account credentials are found." + ) + + +@pytest.mark.asyncio +async def test_fetch_id_token_invalid_cred_path(monkeypatch): not_json_file = os.path.join( - os.path.dirname(__file__), "../../tests/data/public_cert.pem" + os.path.dirname(__file__), "../../tests/data/not_exists.json" ) monkeypatch.setenv(environment_vars.CREDENTIALS, not_json_file) - with pytest.raises(exceptions.DefaultCredentialsError): + with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: request = mock.AsyncMock() await id_token.fetch_id_token(request, "https://pubsub.googleapis.com") + assert excinfo.match( + r"GOOGLE_APPLICATION_CREDENTIALS path is either not found or invalid." + ) From 64efd43792a514ad74233f8c3c9a797ab34decb8 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Wed, 14 Jul 2021 14:27:19 -0600 Subject: [PATCH 436/966] chore: release 1.33.0 (#800) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 19 +++++++++++++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index d04dad5b0425..5ccadc42bf1a 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,25 @@ [1]: https://pypi.org/project/google-auth/#history +## [1.33.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.32.1...v1.33.0) (2021-07-14) + + +### Features + +* define `CredentialAccessBoundary` classes ([#793](https://www.github.com/googleapis/google-auth-library-python/issues/793)) ([d883921](https://www.github.com/googleapis/google-auth-library-python/commit/d883921ae8fdc92b2c2cf1b3a5cd389e1287eb60)) +* define `google.auth.downscoped.Credentials` class ([#801](https://www.github.com/googleapis/google-auth-library-python/issues/801)) ([2f5c3a6](https://www.github.com/googleapis/google-auth-library-python/commit/2f5c3a636192c20cf4c92c3831d1f485031d24d2)) +* service account is able to use a private token endpoint ([#784](https://www.github.com/googleapis/google-auth-library-python/issues/784)) ([0e26409](https://www.github.com/googleapis/google-auth-library-python/commit/0e264092e35ac02ad68d5d91424ecba5397daa41)) + + +### Bug Fixes + +* fix fetch_id_token credential lookup order to match adc ([#748](https://www.github.com/googleapis/google-auth-library-python/issues/748)) ([c34452e](https://www.github.com/googleapis/google-auth-library-python/commit/c34452ef450c42cfef37a1b0c548bb422302dd5d)) + + +### Documentation + +* fix code block formatting in 'user-guide.rst' ([#794](https://www.github.com/googleapis/google-auth-library-python/issues/794)) ([4fd84bd](https://www.github.com/googleapis/google-auth-library-python/commit/4fd84bdf43694af5107dc8c8b443c06ba2f61d2c)) + ### [1.32.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.32.0...v1.32.1) (2021-06-30) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 2ffa8a43a5d8..e74f1e70fc83 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "1.32.1" +__version__ = "1.33.0" From e1583ea42057257f69ce61612a7354f625e7a562 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Tue, 20 Jul 2021 11:17:37 -0600 Subject: [PATCH 437/966] chore: update user credential secret (#806) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10323 -> 10323 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index e416918d4df6a2fc2c634d6db858c4f5b5d46729..46b521c76e638564dd51363cbd18eab0bfcbdaba 100644 GIT binary patch literal 10323 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%0DW#ey5r0090R#F>~_7N+J zu#?>pDz%~{jpT()7ck3nW#0G-4s2JFZPDH$34evyx8uf?mkUepHv|-Pl-j9OR}L7Z zEIYI2oy%Aqybpgti%2`&8(V3FY?d+Y)Bb@WVBcGJ^&i=d43nJbZ|uIKT==5aQ@Guy zwHiiT%xU`N|1|vN(h9v5&3_28sONQsAj4nKBU!T5`?P}MZ&k9&Xhc_^^XojMzOXhZ zbpFk5KUchmXm_}d-?dETB0b=+mPo=JYH{e)4+u5s9LT(iy zU~gN~LA&f1A6A%a!OFF+LuDt%k(lAb(*a^~ZeoW`b zj7A=$+K3&rht|KT^=GwaaQ$-LO`3}AA1vutTybL?iV=gRcy!2QpgH>P0X|NnzNOu0 zdX3(lRj|NY&AW?$ykh%bCMAhpFn`nqE4EW(m=%&aU(BW!=`&&vm2mDfY8@URSDfwl zTTclWV9!l+I@H1&F{}e)x}w<4=_wvd)zXdyWJE=&ALWbp~{Zcyev;fq8^ z>Y}8aKAVnqJ{e$z;`L`Jb1I&KD%+2VNG4PXJ)1vYcy@5EfjiP4fjcG3+UU^o{7tMw zMjq}`OC?ti7mO9!d=3q=OX4TU(GTg#uj!S<*XEmu^>g8g@Dp4&OeKPlY?nqQqfH`! zk-?_{OAVIcAzhkEmf=A5?SMyd&)LiQX9qIoO;1B(%S;qLw3&pnB2wG)puEWZ&HIa9 zYkIo(S>{Ik`}?3MqjqIx8}@y%)yy-=ude{>!(*Kl*nX1GcB{q(Xc*(SgqvhlN~DBe zr_r)~FZ%7U@WfHN8^nNEes>?=#$EGhE<-$RG9~k_f7~b*OMUqc92gD|kmFV|7l|`ft$F z=F92N3=Tl?*@%t>CL&jN%ds&%c7phknUBTWaX{oiErj0rNqvvQ!*AQ3mMi$AA$NWs@_` zCKdPZfISOpDiSPA&P_KOo6US|+zqoN4}^(`tN<04r{0<$%Wti{8Q~w+Lh&uhHwzNe zow4LK3asEdjJAG>?pfPI9=7%_F5y#O=Hf?WoqLVMfEVlQy#=|P)Q#=={Rx<}=IIY% z0^+1VSk~1(VHQ~IUM1gO zcL;7#Ywme^^tDWKfDLz*ST)a5HvtMCGRNY6N3$3tF}~b4K#T>_5(3`QkGz0ec2@cx zQ3hF!HKF%ZUq5TqvBzwz%1}f|lTf%L^nij|Q6Q_TagF;}<9(c`*%9cg8bpC;Fa|VU zLVp1P(VHefM$P5gxG}huk`J~UzU*~fkPRJagd?`Pr09Z!5}B~K-o zEfR9(TXE}b_heQRj)hxzwE9r!1M09K%-U_E|mJBidQMI*

ri{f8pbZ@sJLkZP#k(D{yF_ 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 Date: Tue, 20 Jul 2021 10:43:13 -0700 Subject: [PATCH 438/966] fix: fallback to source creds expiration in downscoped tokens (#805) For downscoping CAB flow, the STS endpoint may not return the expiration field for certain source credentials. The generated downscoped token should always have the same expiration time as the source credentials. When no `expires_in` field is returned in the response, we can just get the expiration time from the source credentials. Co-authored-by: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> --- .../google-auth/google/auth/downscoped.py | 12 ++++- packages/google-auth/tests/test_downscoped.py | 46 ++++++++++++++++++- 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/packages/google-auth/google/auth/downscoped.py b/packages/google-auth/google/auth/downscoped.py index 800f2894c809..96a4e6547322 100644 --- a/packages/google-auth/google/auth/downscoped.py +++ b/packages/google-auth/google/auth/downscoped.py @@ -479,8 +479,16 @@ def refresh(self, request): additional_options=self._credential_access_boundary.to_json(), ) self.token = response_data.get("access_token") - lifetime = datetime.timedelta(seconds=response_data.get("expires_in")) - self.expiry = now + lifetime + # For downscoping CAB flow, the STS endpoint may not return the expiration + # field for some flows. The generated downscoped token should always have + # the same expiration time as the source credentials. When no expires_in + # field is returned in the response, we can just get the expiration time + # from the source credentials. + if response_data.get("expires_in"): + lifetime = datetime.timedelta(seconds=response_data.get("expires_in")) + self.expiry = now + lifetime + else: + self.expiry = self._source_credentials.expiry @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) def with_quota_project(self, quota_project_id): diff --git a/packages/google-auth/tests/test_downscoped.py b/packages/google-auth/tests/test_downscoped.py index ac60e5b00d56..795ec2942e08 100644 --- a/packages/google-auth/tests/test_downscoped.py +++ b/packages/google-auth/tests/test_downscoped.py @@ -80,10 +80,11 @@ class SourceCredentials(credentials.Credentials): - def __init__(self, raise_error=False): + def __init__(self, raise_error=False, expires_in=3600): super(SourceCredentials, self).__init__() self._counter = 0 self._raise_error = raise_error + self._expires_in = expires_in def refresh(self, request): if self._raise_error: @@ -93,7 +94,7 @@ def refresh(self, request): now = _helpers.utcnow() self._counter += 1 self.token = "ACCESS_TOKEN_{}".format(self._counter) - self.expiry = now + datetime.timedelta(seconds=3600) + self.expiry = now + datetime.timedelta(seconds=self._expires_in) def make_availability_condition(expression, title=None, description=None): @@ -539,6 +540,47 @@ def test_refresh(self, unused_utcnow): # Confirm source credentials called with the same request instance. wrapped_souce_cred_refresh.assert_called_with(request) + @mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) + def test_refresh_without_response_expires_in(self, unused_utcnow): + response = SUCCESS_RESPONSE.copy() + # Simulate the response is missing the expires_in field. + # The downscoped token expiration should match the source credentials + # expiration. + del response["expires_in"] + expected_expires_in = 1800 + # Simulate the source credentials generates a token with 1800 second + # expiration time. The generated downscoped token should have the same + # expiration time. + source_credentials = SourceCredentials(expires_in=expected_expires_in) + expected_expiry = datetime.datetime.min + datetime.timedelta( + seconds=expected_expires_in + ) + headers = {"Content-Type": "application/x-www-form-urlencoded"} + request_data = { + "grant_type": GRANT_TYPE, + "subject_token": "ACCESS_TOKEN_1", + "subject_token_type": SUBJECT_TOKEN_TYPE, + "requested_token_type": REQUESTED_TOKEN_TYPE, + "options": urllib.parse.quote(json.dumps(CREDENTIAL_ACCESS_BOUNDARY_JSON)), + } + request = self.make_mock_request(status=http_client.OK, data=response) + credentials = self.make_credentials(source_credentials=source_credentials) + + # Spy on calls to source credentials refresh to confirm the expected request + # instance is used. + with mock.patch.object( + source_credentials, "refresh", wraps=source_credentials.refresh + ) as wrapped_souce_cred_refresh: + credentials.refresh(request) + + self.assert_request_kwargs(request.call_args[1], headers, request_data) + assert credentials.valid + assert credentials.expiry == expected_expiry + assert not credentials.expired + assert credentials.token == response["access_token"] + # Confirm source credentials called with the same request instance. + wrapped_souce_cred_refresh.assert_called_with(request) + def test_refresh_token_exchange_error(self): request = self.make_mock_request( status=http_client.BAD_REQUEST, data=ERROR_RESPONSE From 1c44086ac6ffa5fe5c813e5df18c5fd4318d9918 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Tue, 20 Jul 2021 12:09:57 -0700 Subject: [PATCH 439/966] revert: revert "feat: service account is able to use a private token endpoint (#784)" (#808) revert "feat: service account is able to use a private token endpoint (#784)" until b/194191737 is fixed. This reverts commit 0e264092e35ac02ad68d5d91424ecba5397daa41. --- packages/google-auth/google/oauth2/service_account.py | 5 ++--- .../google-auth/tests/oauth2/test_service_account.py | 4 ++-- .../tests_async/oauth2/test_service_account_async.py | 10 ++-------- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/packages/google-auth/google/oauth2/service_account.py b/packages/google-auth/google/oauth2/service_account.py index 8f18f26ea17b..dd3658994336 100644 --- a/packages/google-auth/google/oauth2/service_account.py +++ b/packages/google-auth/google/oauth2/service_account.py @@ -80,7 +80,6 @@ from google.oauth2 import _client _DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds -_GOOGLE_OAUTH2_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token" class Credentials( @@ -383,7 +382,7 @@ def _make_authorization_grant_assertion(self): # The issuer must be the service account email. "iss": self._service_account_email, # The audience must be the auth token endpoint's URI - "aud": _GOOGLE_OAUTH2_TOKEN_ENDPOINT, + "aud": self._token_uri, "scope": _helpers.scopes_to_string(self._scopes or ()), } @@ -644,7 +643,7 @@ def _make_authorization_grant_assertion(self): # The issuer must be the service account email. "iss": self.service_account_email, # The audience must be the auth token endpoint's URI - "aud": _GOOGLE_OAUTH2_TOKEN_ENDPOINT, + "aud": self._token_uri, # The target audience specifies which service the ID token is # intended for. "target_audience": self._target_audience, diff --git a/packages/google-auth/tests/oauth2/test_service_account.py b/packages/google-auth/tests/oauth2/test_service_account.py index 370438f48d04..5852d37146b8 100644 --- a/packages/google-auth/tests/oauth2/test_service_account.py +++ b/packages/google-auth/tests/oauth2/test_service_account.py @@ -167,7 +167,7 @@ def test__make_authorization_grant_assertion(self): token = credentials._make_authorization_grant_assertion() payload = jwt.decode(token, PUBLIC_CERT_BYTES) assert payload["iss"] == self.SERVICE_ACCOUNT_EMAIL - assert payload["aud"] == service_account._GOOGLE_OAUTH2_TOKEN_ENDPOINT + assert payload["aud"] == self.TOKEN_URI def test__make_authorization_grant_assertion_scoped(self): credentials = self.make_credentials() @@ -440,7 +440,7 @@ def test__make_authorization_grant_assertion(self): token = credentials._make_authorization_grant_assertion() payload = jwt.decode(token, PUBLIC_CERT_BYTES) assert payload["iss"] == self.SERVICE_ACCOUNT_EMAIL - assert payload["aud"] == service_account._GOOGLE_OAUTH2_TOKEN_ENDPOINT + assert payload["aud"] == self.TOKEN_URI assert payload["target_audience"] == self.TARGET_AUDIENCE @mock.patch("google.oauth2._client.id_token_jwt_grant", autospec=True) diff --git a/packages/google-auth/tests_async/oauth2/test_service_account_async.py b/packages/google-auth/tests_async/oauth2/test_service_account_async.py index 3dce13d82b02..40794536cee9 100644 --- a/packages/google-auth/tests_async/oauth2/test_service_account_async.py +++ b/packages/google-auth/tests_async/oauth2/test_service_account_async.py @@ -152,10 +152,7 @@ def test__make_authorization_grant_assertion(self): token = credentials._make_authorization_grant_assertion() payload = jwt.decode(token, test_service_account.PUBLIC_CERT_BYTES) assert payload["iss"] == self.SERVICE_ACCOUNT_EMAIL - assert ( - payload["aud"] - == service_account.service_account._GOOGLE_OAUTH2_TOKEN_ENDPOINT - ) + assert payload["aud"] == self.TOKEN_URI def test__make_authorization_grant_assertion_scoped(self): credentials = self.make_credentials() @@ -314,10 +311,7 @@ def test__make_authorization_grant_assertion(self): token = credentials._make_authorization_grant_assertion() payload = jwt.decode(token, test_service_account.PUBLIC_CERT_BYTES) assert payload["iss"] == self.SERVICE_ACCOUNT_EMAIL - assert ( - payload["aud"] - == service_account.service_account._GOOGLE_OAUTH2_TOKEN_ENDPOINT - ) + assert payload["aud"] == self.TOKEN_URI assert payload["target_audience"] == self.TARGET_AUDIENCE @mock.patch("google.oauth2._client_async.id_token_jwt_grant", autospec=True) From 6227d8f1690ee94825c5fc7922848fdd7df6e241 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 20 Jul 2021 14:06:11 -0600 Subject: [PATCH 440/966] chore: release 1.33.1 (#809) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 12 ++++++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 5ccadc42bf1a..6b7abb3664eb 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,18 @@ [1]: https://pypi.org/project/google-auth/#history +### [1.33.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.33.0...v1.33.1) (2021-07-20) + + +### Bug Fixes + +* fallback to source creds expiration in downscoped tokens ([#805](https://www.github.com/googleapis/google-auth-library-python/issues/805)) ([dfad661](https://www.github.com/googleapis/google-auth-library-python/commit/dfad66128c6ee7513e5565d39bc7b002055dd0d5)) + + +### Reverts + +* revert "feat: service account is able to use a private token endpoint ([#784](https://www.github.com/googleapis/google-auth-library-python/issues/784))" ([#808](https://www.github.com/googleapis/google-auth-library-python/issues/808)) ([d94e65c](https://www.github.com/googleapis/google-auth-library-python/commit/d94e65c0e441183403608d762b92b30b77e21eeb)) + ## [1.33.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.32.1...v1.33.0) (2021-07-14) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index e74f1e70fc83..6327f8588401 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "1.33.0" +__version__ = "1.33.1" From a8ddc516ecd9d2ec03175f31afb35e2aa638ee16 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Wed, 21 Jul 2021 16:11:17 -0600 Subject: [PATCH 441/966] chore: add Sijun and Silvano as CODEOWNERS (#813) This ensures they are automatically tagged in PRs for reviews. --- packages/google-auth/.github/CODEOWNERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/.github/CODEOWNERS b/packages/google-auth/.github/CODEOWNERS index 30c3973aa372..f3c8219b8ae7 100644 --- a/packages/google-auth/.github/CODEOWNERS +++ b/packages/google-auth/.github/CODEOWNERS @@ -5,7 +5,7 @@ # https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners#codeowners-syntax # The @googleapis/yoshi-python is the default owner for changes in this repo -* @googleapis/yoshi-python +* @arithmetic1728 @silvolu @googleapis/yoshi-python # The python-samples-reviewers team is the default owner for samples changes -/samples/ @googleapis/python-samples-owners \ No newline at end of file +/samples/ @googleapis/python-samples-owners From 0c048b4f9a6b14b5a1e0ddefd87d65fae49acf4e Mon Sep 17 00:00:00 2001 From: bojeil-google Date: Thu, 22 Jul 2021 10:01:31 -0700 Subject: [PATCH 442/966] feat: support refresh callable on google.oauth2.credentials.Credentials (#812) This is an optional parameter that can be set via the constructor. It is used to provide the credentials with new tokens and their expiration time on `refresh()` call. ``` def refresh_handler(request, scopes): # Generate a new token for the requested scopes by calling # an external process. return ( "ACCESS_TOKEN", _helpers.utcnow() + datetime.timedelta(seconds=3600)) creds = google.oauth2.credentials.Credentials( scopes=scopes, refresh_handler=refresh_handler) creds.refresh(request) ``` It is useful in the following cases: - Useful in general when tokens are obtained by calling some external process on demand. - Useful in particular for retrieving downscoped tokens from a token broker. This should have no impact on existing behavior. Refresh tokens will still have higher priority over refresh handlers. A getter and setter is exposed to make it easy to set the callable on unpickled credentials as the callable may not be easily serialized. ``` unpickled = pickle.loads(pickle.dumps(oauth_creds)) unpickled.refresh_handler = refresh_handler ``` --- .../google-auth/google/oauth2/credentials.py | 71 ++++- .../tests/oauth2/test_credentials.py | 285 ++++++++++++++++++ 2 files changed, 353 insertions(+), 3 deletions(-) diff --git a/packages/google-auth/google/oauth2/credentials.py b/packages/google-auth/google/oauth2/credentials.py index dcfa5f91222d..158249ed5f60 100644 --- a/packages/google-auth/google/oauth2/credentials.py +++ b/packages/google-auth/google/oauth2/credentials.py @@ -74,6 +74,7 @@ def __init__( quota_project_id=None, expiry=None, rapt_token=None, + refresh_handler=None, ): """ Args: @@ -103,6 +104,13 @@ def __init__( This project may be different from the project used to create the credentials. rapt_token (Optional[str]): The reauth Proof Token. + refresh_handler (Optional[Callable[[google.auth.transport.Request, Sequence[str]], [str, datetime]]]): + A callable which takes in the HTTP request callable and the list of + OAuth scopes and when called returns an access token string for the + requested scopes and its expiry datetime. This is useful when no + refresh tokens are provided and tokens are obtained by calling + some external process on demand. It is particularly useful for + retrieving downscoped tokens from a token broker. """ super(Credentials, self).__init__() self.token = token @@ -116,13 +124,20 @@ def __init__( self._client_secret = client_secret self._quota_project_id = quota_project_id self._rapt_token = rapt_token + self.refresh_handler = refresh_handler def __getstate__(self): """A __getstate__ method must exist for the __setstate__ to be called This is identical to the default implementation. See https://docs.python.org/3.7/library/pickle.html#object.__setstate__ """ - return self.__dict__ + state_dict = self.__dict__.copy() + # Remove _refresh_handler function as there are limitations pickling and + # unpickling certain callables (lambda, functools.partial instances) + # because they need to be importable. + # Instead, the refresh_handler setter should be used to repopulate this. + del state_dict["_refresh_handler"] + return state_dict def __setstate__(self, d): """Credentials pickled with older versions of the class do not have @@ -138,6 +153,8 @@ def __setstate__(self, d): self._client_secret = d.get("_client_secret") self._quota_project_id = d.get("_quota_project_id") self._rapt_token = d.get("_rapt_token") + # The refresh_handler setter should be used to repopulate this. + self._refresh_handler = None @property def refresh_token(self): @@ -187,6 +204,31 @@ def rapt_token(self): """Optional[str]: The reauth Proof Token.""" return self._rapt_token + @property + def refresh_handler(self): + """Returns the refresh handler if available. + + Returns: + Optional[Callable[[google.auth.transport.Request, Sequence[str]], [str, datetime]]]: + The current refresh handler. + """ + return self._refresh_handler + + @refresh_handler.setter + def refresh_handler(self, value): + """Updates the current refresh handler. + + Args: + value (Optional[Callable[[google.auth.transport.Request, Sequence[str]], [str, datetime]]]): + The updated value of the refresh handler. + + Raises: + TypeError: If the value is not a callable or None. + """ + if not callable(value) and value is not None: + raise TypeError("The provided refresh_handler is not a callable or None.") + self._refresh_handler = value + @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) def with_quota_project(self, quota_project_id): @@ -205,6 +247,31 @@ def with_quota_project(self, quota_project_id): @_helpers.copy_docstring(credentials.Credentials) def refresh(self, request): + scopes = self._scopes if self._scopes is not None else self._default_scopes + # Use refresh handler if available and no refresh token is + # available. This is useful in general when tokens are obtained by calling + # some external process on demand. It is particularly useful for retrieving + # downscoped tokens from a token broker. + if self._refresh_token is None and self.refresh_handler: + token, expiry = self.refresh_handler(request, scopes=scopes) + # Validate returned data. + if not isinstance(token, str): + raise exceptions.RefreshError( + "The refresh_handler returned token is not a string." + ) + if not isinstance(expiry, datetime): + raise exceptions.RefreshError( + "The refresh_handler returned expiry is not a datetime object." + ) + if _helpers.utcnow() >= expiry - _helpers.CLOCK_SKEW: + raise exceptions.RefreshError( + "The credentials returned by the refresh_handler are " + "already expired." + ) + self.token = token + self.expiry = expiry + return + if ( self._refresh_token is None or self._token_uri is None @@ -217,8 +284,6 @@ def refresh(self, request): "token_uri, client_id, and client_secret." ) - scopes = self._scopes if self._scopes is not None else self._default_scopes - ( access_token, refresh_token, diff --git a/packages/google-auth/tests/oauth2/test_credentials.py b/packages/google-auth/tests/oauth2/test_credentials.py index 4a387a58e068..4a7f66e7f495 100644 --- a/packages/google-auth/tests/oauth2/test_credentials.py +++ b/packages/google-auth/tests/oauth2/test_credentials.py @@ -66,6 +66,50 @@ def test_default_state(self): assert credentials.client_id == self.CLIENT_ID assert credentials.client_secret == self.CLIENT_SECRET assert credentials.rapt_token == self.RAPT_TOKEN + assert credentials.refresh_handler is None + + def test_refresh_handler_setter_and_getter(self): + scopes = ["email", "profile"] + original_refresh_handler = mock.Mock(return_value=("ACCESS_TOKEN_1", None)) + updated_refresh_handler = mock.Mock(return_value=("ACCESS_TOKEN_2", None)) + creds = credentials.Credentials( + token=None, + refresh_token=None, + token_uri=None, + client_id=None, + client_secret=None, + rapt_token=None, + scopes=scopes, + default_scopes=None, + refresh_handler=original_refresh_handler, + ) + + assert creds.refresh_handler is original_refresh_handler + + creds.refresh_handler = updated_refresh_handler + + assert creds.refresh_handler is updated_refresh_handler + + creds.refresh_handler = None + + assert creds.refresh_handler is None + + def test_invalid_refresh_handler(self): + scopes = ["email", "profile"] + with pytest.raises(TypeError) as excinfo: + credentials.Credentials( + token=None, + refresh_token=None, + token_uri=None, + client_id=None, + client_secret=None, + rapt_token=None, + scopes=scopes, + default_scopes=None, + refresh_handler=object(), + ) + + assert excinfo.match("The provided refresh_handler is not a callable or None.") @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True) @mock.patch( @@ -126,6 +170,221 @@ def test_refresh_no_refresh_token(self): request.assert_not_called() + @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True) + @mock.patch( + "google.auth._helpers.utcnow", + return_value=datetime.datetime.min + _helpers.CLOCK_SKEW, + ) + def test_refresh_with_refresh_token_and_refresh_handler( + self, unused_utcnow, refresh_grant + ): + token = "token" + new_rapt_token = "new_rapt_token" + expiry = _helpers.utcnow() + datetime.timedelta(seconds=500) + grant_response = {"id_token": mock.sentinel.id_token} + refresh_grant.return_value = ( + # Access token + token, + # New refresh token + None, + # Expiry, + expiry, + # Extra data + grant_response, + # rapt_token + new_rapt_token, + ) + + refresh_handler = mock.Mock() + request = mock.create_autospec(transport.Request) + creds = credentials.Credentials( + token=None, + refresh_token=self.REFRESH_TOKEN, + token_uri=self.TOKEN_URI, + client_id=self.CLIENT_ID, + client_secret=self.CLIENT_SECRET, + rapt_token=self.RAPT_TOKEN, + refresh_handler=refresh_handler, + ) + + # Refresh credentials + creds.refresh(request) + + # Check jwt grant call. + refresh_grant.assert_called_with( + request, + self.TOKEN_URI, + self.REFRESH_TOKEN, + self.CLIENT_ID, + self.CLIENT_SECRET, + None, + self.RAPT_TOKEN, + ) + + # Check that the credentials have the token and expiry + assert creds.token == token + assert creds.expiry == expiry + assert creds.id_token == mock.sentinel.id_token + assert creds.rapt_token == new_rapt_token + + # Check that the credentials are valid (have a token and are not + # expired) + assert creds.valid + + # Assert refresh handler not called as the refresh token has + # higher priority. + refresh_handler.assert_not_called() + + @mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) + def test_refresh_with_refresh_handler_success_scopes(self, unused_utcnow): + expected_expiry = datetime.datetime.min + datetime.timedelta(seconds=2800) + refresh_handler = mock.Mock(return_value=("ACCESS_TOKEN", expected_expiry)) + scopes = ["email", "profile"] + default_scopes = ["https://www.googleapis.com/auth/cloud-platform"] + request = mock.create_autospec(transport.Request) + creds = credentials.Credentials( + token=None, + refresh_token=None, + token_uri=None, + client_id=None, + client_secret=None, + rapt_token=None, + scopes=scopes, + default_scopes=default_scopes, + refresh_handler=refresh_handler, + ) + + creds.refresh(request) + + assert creds.token == "ACCESS_TOKEN" + assert creds.expiry == expected_expiry + assert creds.valid + assert not creds.expired + # Confirm refresh handler called with the expected arguments. + refresh_handler.assert_called_with(request, scopes=scopes) + + @mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) + def test_refresh_with_refresh_handler_success_default_scopes(self, unused_utcnow): + expected_expiry = datetime.datetime.min + datetime.timedelta(seconds=2800) + original_refresh_handler = mock.Mock( + return_value=("UNUSED_TOKEN", expected_expiry) + ) + refresh_handler = mock.Mock(return_value=("ACCESS_TOKEN", expected_expiry)) + default_scopes = ["https://www.googleapis.com/auth/cloud-platform"] + request = mock.create_autospec(transport.Request) + creds = credentials.Credentials( + token=None, + refresh_token=None, + token_uri=None, + client_id=None, + client_secret=None, + rapt_token=None, + scopes=None, + default_scopes=default_scopes, + refresh_handler=original_refresh_handler, + ) + + # Test newly set refresh_handler is used instead of the original one. + creds.refresh_handler = refresh_handler + creds.refresh(request) + + assert creds.token == "ACCESS_TOKEN" + assert creds.expiry == expected_expiry + assert creds.valid + assert not creds.expired + # default_scopes should be used since no developer provided scopes + # are provided. + refresh_handler.assert_called_with(request, scopes=default_scopes) + + @mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) + def test_refresh_with_refresh_handler_invalid_token(self, unused_utcnow): + expected_expiry = datetime.datetime.min + datetime.timedelta(seconds=2800) + # Simulate refresh handler does not return a valid token. + refresh_handler = mock.Mock(return_value=(None, expected_expiry)) + scopes = ["email", "profile"] + default_scopes = ["https://www.googleapis.com/auth/cloud-platform"] + request = mock.create_autospec(transport.Request) + creds = credentials.Credentials( + token=None, + refresh_token=None, + token_uri=None, + client_id=None, + client_secret=None, + rapt_token=None, + scopes=scopes, + default_scopes=default_scopes, + refresh_handler=refresh_handler, + ) + + with pytest.raises( + exceptions.RefreshError, match="returned token is not a string" + ): + creds.refresh(request) + + assert creds.token is None + assert creds.expiry is None + assert not creds.valid + # Confirm refresh handler called with the expected arguments. + refresh_handler.assert_called_with(request, scopes=scopes) + + def test_refresh_with_refresh_handler_invalid_expiry(self): + # Simulate refresh handler returns expiration time in an invalid unit. + refresh_handler = mock.Mock(return_value=("TOKEN", 2800)) + scopes = ["email", "profile"] + default_scopes = ["https://www.googleapis.com/auth/cloud-platform"] + request = mock.create_autospec(transport.Request) + creds = credentials.Credentials( + token=None, + refresh_token=None, + token_uri=None, + client_id=None, + client_secret=None, + rapt_token=None, + scopes=scopes, + default_scopes=default_scopes, + refresh_handler=refresh_handler, + ) + + with pytest.raises( + exceptions.RefreshError, match="returned expiry is not a datetime object" + ): + creds.refresh(request) + + assert creds.token is None + assert creds.expiry is None + assert not creds.valid + # Confirm refresh handler called with the expected arguments. + refresh_handler.assert_called_with(request, scopes=scopes) + + @mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) + def test_refresh_with_refresh_handler_expired_token(self, unused_utcnow): + expected_expiry = datetime.datetime.min + _helpers.CLOCK_SKEW + # Simulate refresh handler returns an expired token. + refresh_handler = mock.Mock(return_value=("TOKEN", expected_expiry)) + scopes = ["email", "profile"] + default_scopes = ["https://www.googleapis.com/auth/cloud-platform"] + request = mock.create_autospec(transport.Request) + creds = credentials.Credentials( + token=None, + refresh_token=None, + token_uri=None, + client_id=None, + client_secret=None, + rapt_token=None, + scopes=scopes, + default_scopes=default_scopes, + refresh_handler=refresh_handler, + ) + + with pytest.raises(exceptions.RefreshError, match="already expired"): + creds.refresh(request) + + assert creds.token is None + assert creds.expiry is None + assert not creds.valid + # Confirm refresh handler called with the expected arguments. + refresh_handler.assert_called_with(request, scopes=scopes) + @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", @@ -527,6 +786,32 @@ def test_pickle_and_unpickle(self): for attr in list(creds.__dict__): assert getattr(creds, attr) == getattr(unpickled, attr) + def test_pickle_and_unpickle_with_refresh_handler(self): + expected_expiry = _helpers.utcnow() + datetime.timedelta(seconds=2800) + refresh_handler = mock.Mock(return_value=("TOKEN", expected_expiry)) + + creds = credentials.Credentials( + token=None, + refresh_token=None, + token_uri=None, + client_id=None, + client_secret=None, + rapt_token=None, + refresh_handler=refresh_handler, + ) + unpickled = pickle.loads(pickle.dumps(creds)) + + # make sure attributes aren't lost during pickling + assert list(creds.__dict__).sort() == list(unpickled.__dict__).sort() + + for attr in list(creds.__dict__): + # For the _refresh_handler property, the unpickled creds should be + # set to None. + if attr == "_refresh_handler": + assert getattr(unpickled, attr) is None + else: + assert getattr(creds, attr) == getattr(unpickled, attr) + def test_pickle_with_missing_attribute(self): creds = self.make_credentials() From bdc4d52c340534cf9288c641afc3e47b1167870d Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Fri, 23 Jul 2021 12:00:27 -0400 Subject: [PATCH 443/966] chore: fix kokoro config for samples (#817) Source-Link: https://github.com/googleapis/synthtool/commit/dd05f9d12f134871c9e45282349c9856fbebecdd Post-Processor: gcr.io/repo-automation-bots/owlbot-python:latest@sha256:aea14a583128771ae8aefa364e1652f3c56070168ef31beb203534222d842b8b Co-authored-by: Owl Bot --- packages/google-auth/.github/.OwlBot.lock.yaml | 2 +- .../google-auth/.kokoro/samples/python3.6/periodic-head.cfg | 2 +- .../google-auth/.kokoro/samples/python3.7/periodic-head.cfg | 2 +- .../google-auth/.kokoro/samples/python3.8/periodic-head.cfg | 2 +- .../google-auth/.kokoro/samples/python3.9/periodic-head.cfg | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/google-auth/.github/.OwlBot.lock.yaml b/packages/google-auth/.github/.OwlBot.lock.yaml index cb06536dab0b..9ee60f7e4850 100644 --- a/packages/google-auth/.github/.OwlBot.lock.yaml +++ b/packages/google-auth/.github/.OwlBot.lock.yaml @@ -1,3 +1,3 @@ docker: image: gcr.io/repo-automation-bots/owlbot-python:latest - digest: sha256:5ff7446edeaede81c3ed58b23a4e76a5403fba1350ce28478045657303b6479d + digest: sha256:aea14a583128771ae8aefa364e1652f3c56070168ef31beb203534222d842b8b diff --git a/packages/google-auth/.kokoro/samples/python3.6/periodic-head.cfg b/packages/google-auth/.kokoro/samples/python3.6/periodic-head.cfg index f9cfcd33e058..83eace873ccb 100644 --- a/packages/google-auth/.kokoro/samples/python3.6/periodic-head.cfg +++ b/packages/google-auth/.kokoro/samples/python3.6/periodic-head.cfg @@ -7,5 +7,5 @@ env_vars: { env_vars: { key: "TRAMPOLINE_BUILD_FILE" - value: "github/python-pubsub/.kokoro/test-samples-against-head.sh" + value: "github/google-auth-library-python/.kokoro/test-samples-against-head.sh" } diff --git a/packages/google-auth/.kokoro/samples/python3.7/periodic-head.cfg b/packages/google-auth/.kokoro/samples/python3.7/periodic-head.cfg index f9cfcd33e058..83eace873ccb 100644 --- a/packages/google-auth/.kokoro/samples/python3.7/periodic-head.cfg +++ b/packages/google-auth/.kokoro/samples/python3.7/periodic-head.cfg @@ -7,5 +7,5 @@ env_vars: { env_vars: { key: "TRAMPOLINE_BUILD_FILE" - value: "github/python-pubsub/.kokoro/test-samples-against-head.sh" + value: "github/google-auth-library-python/.kokoro/test-samples-against-head.sh" } diff --git a/packages/google-auth/.kokoro/samples/python3.8/periodic-head.cfg b/packages/google-auth/.kokoro/samples/python3.8/periodic-head.cfg index f9cfcd33e058..83eace873ccb 100644 --- a/packages/google-auth/.kokoro/samples/python3.8/periodic-head.cfg +++ b/packages/google-auth/.kokoro/samples/python3.8/periodic-head.cfg @@ -7,5 +7,5 @@ env_vars: { env_vars: { key: "TRAMPOLINE_BUILD_FILE" - value: "github/python-pubsub/.kokoro/test-samples-against-head.sh" + value: "github/google-auth-library-python/.kokoro/test-samples-against-head.sh" } diff --git a/packages/google-auth/.kokoro/samples/python3.9/periodic-head.cfg b/packages/google-auth/.kokoro/samples/python3.9/periodic-head.cfg index f9cfcd33e058..83eace873ccb 100644 --- a/packages/google-auth/.kokoro/samples/python3.9/periodic-head.cfg +++ b/packages/google-auth/.kokoro/samples/python3.9/periodic-head.cfg @@ -7,5 +7,5 @@ env_vars: { env_vars: { key: "TRAMPOLINE_BUILD_FILE" - value: "github/python-pubsub/.kokoro/test-samples-against-head.sh" + value: "github/google-auth-library-python/.kokoro/test-samples-against-head.sh" } From 40f19cc1b08bb062c33dde68990cd0db9e9d1835 Mon Sep 17 00:00:00 2001 From: Zev Goldstein Date: Fri, 23 Jul 2021 15:52:46 -0400 Subject: [PATCH 444/966] fix: do not use the GAE APIs on gen2+ runtimes (#807) * fix: do not use the GAE APIs on gen2+ runtimes Currently, this library uses the App Engine API in all environments if it can be imported successfully. This assumption made sense when the API was only available on gen1, but this is no longer the case. See https://github.com/GoogleCloudPlatform/appengine-python-standard In order to comply with AIP-4115, we must treat GAE gen2+ as a "compute engine equivalent environment" even if the GAE APIs are importable. In other words, google.auth.default() must never return an app_engine.Credental on GAE gen2+.Currently, this library uses the App Engine API in all environments if it can be imported successfully. This assumption made sense when the API was only available on gen1, but this is no longer the case. See https://github.com/GoogleCloudPlatform/appengine-python-standard In order to comply with AIP-4115, we must treat GAE gen2+ as a "compute engine equivalent environment" even if the GAE APIs are importable. In other words, google.auth.default() should not return an app_engine.Credental on GAE gen2+. * blacken Co-authored-by: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> --- packages/google-auth/google/auth/_default.py | 5 ++ .../google/auth/environment_vars.py | 6 ++ packages/google-auth/tests/test__default.py | 57 +++++++++++++++++-- packages/google-auth/tests/test_app_engine.py | 2 + .../tests_async/test__default_async.py | 57 +++++++++++++++++-- 5 files changed, 119 insertions(+), 8 deletions(-) diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index 4dc0725e7f03..f7e308f3e084 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -230,6 +230,11 @@ def _get_explicit_environ_credentials(): def _get_gae_credentials(): """Gets Google App Engine App Identity credentials and project ID.""" + # If not GAE gen1, prefer the metadata service even if the GAE APIs are + # available as per https://google.aip.dev/auth/4115. + if os.environ.get(environment_vars.LEGACY_APPENGINE_RUNTIME) != "python27": + return None, None + # While this library is normally bundled with app_engine, there are # some cases where it's not available, so we tolerate ImportError. try: diff --git a/packages/google-auth/google/auth/environment_vars.py b/packages/google-auth/google/auth/environment_vars.py index f02774181345..d36d6c4afad0 100644 --- a/packages/google-auth/google/auth/environment_vars.py +++ b/packages/google-auth/google/auth/environment_vars.py @@ -60,6 +60,12 @@ The default value is false. Users have to explicitly set this value to true in order to use client certificate to establish a mutual TLS channel.""" +LEGACY_APPENGINE_RUNTIME = "APPENGINE_RUNTIME" +"""Gen1 environment variable defining the App Engine Runtime. + +Used to distinguish between GAE gen1 and GAE gen2+. +""" + # AWS environment variables used with AWS workload identity pools to retrieve # AWS security credentials and the AWS region needed to create a serialized # signed requests to the AWS STS GetCalledIdentity API that can be exchanged diff --git a/packages/google-auth/tests/test__default.py b/packages/google-auth/tests/test__default.py index e1368962525d..a515f3813395 100644 --- a/packages/google-auth/tests/test__default.py +++ b/packages/google-auth/tests/test__default.py @@ -447,7 +447,9 @@ def app_identity(monkeypatch): yield app_identity_module -def test__get_gae_credentials(app_identity): +@mock.patch.dict(os.environ) +def test__get_gae_credentials_gen1(app_identity): + os.environ[environment_vars.LEGACY_APPENGINE_RUNTIME] = "python27" app_identity.get_application_id.return_value = mock.sentinel.project credentials, project_id = _default._get_gae_credentials() @@ -456,18 +458,65 @@ def test__get_gae_credentials(app_identity): assert project_id == mock.sentinel.project +@mock.patch.dict(os.environ) +def test__get_gae_credentials_gen2(): + os.environ["GAE_RUNTIME"] = "python37" + credentials, project_id = _default._get_gae_credentials() + assert credentials is None + assert project_id is None + + +@mock.patch.dict(os.environ) +def test__get_gae_credentials_gen2_backwards_compat(): + # compat helpers may copy GAE_RUNTIME to APPENGINE_RUNTIME + # for backwards compatibility with code that relies on it + os.environ[environment_vars.LEGACY_APPENGINE_RUNTIME] = "python37" + os.environ["GAE_RUNTIME"] = "python37" + credentials, project_id = _default._get_gae_credentials() + assert credentials is None + assert project_id is None + + +def test__get_gae_credentials_env_unset(): + assert environment_vars.LEGACY_APPENGINE_RUNTIME not in os.environ + assert "GAE_RUNTIME" not in os.environ + credentials, project_id = _default._get_gae_credentials() + assert credentials is None + assert project_id is None + + +@mock.patch.dict(os.environ) def test__get_gae_credentials_no_app_engine(): + # test both with and without LEGACY_APPENGINE_RUNTIME setting + assert environment_vars.LEGACY_APPENGINE_RUNTIME not in os.environ + import sys - with mock.patch.dict("sys.modules"): - sys.modules["google.auth.app_engine"] = None + with mock.patch.dict(sys.modules, {"google.auth.app_engine": None}): + credentials, project_id = _default._get_gae_credentials() + assert credentials is None + assert project_id is None + + os.environ[environment_vars.LEGACY_APPENGINE_RUNTIME] = "python27" credentials, project_id = _default._get_gae_credentials() assert credentials is None assert project_id is None +@mock.patch.dict(os.environ) +@mock.patch.object(app_engine, "app_identity", new=None) def test__get_gae_credentials_no_apis(): - assert _default._get_gae_credentials() == (None, None) + # test both with and without LEGACY_APPENGINE_RUNTIME setting + assert environment_vars.LEGACY_APPENGINE_RUNTIME not in os.environ + + credentials, project_id = _default._get_gae_credentials() + assert credentials is None + assert project_id is None + + os.environ[environment_vars.LEGACY_APPENGINE_RUNTIME] = "python27" + credentials, project_id = _default._get_gae_credentials() + assert credentials is None + assert project_id is None @mock.patch( diff --git a/packages/google-auth/tests/test_app_engine.py b/packages/google-auth/tests/test_app_engine.py index e335ff7ed2d8..6a788b9e9ab0 100644 --- a/packages/google-auth/tests/test_app_engine.py +++ b/packages/google-auth/tests/test_app_engine.py @@ -52,6 +52,7 @@ def test_get_project_id(app_identity): assert app_engine.get_project_id() == mock.sentinel.project +@mock.patch.object(app_engine, "app_identity", new=None) def test_get_project_id_missing_apis(): with pytest.raises(EnvironmentError) as excinfo: assert app_engine.get_project_id() @@ -86,6 +87,7 @@ def test_sign(self, app_identity): class TestCredentials(object): + @mock.patch.object(app_engine, "app_identity", new=None) def test_missing_apis(self): with pytest.raises(EnvironmentError) as excinfo: app_engine.Credentials() diff --git a/packages/google-auth/tests_async/test__default_async.py b/packages/google-auth/tests_async/test__default_async.py index 527a8da45674..b67230342d79 100644 --- a/packages/google-auth/tests_async/test__default_async.py +++ b/packages/google-auth/tests_async/test__default_async.py @@ -284,7 +284,9 @@ def app_identity(monkeypatch): yield app_identity_module -def test__get_gae_credentials(app_identity): +@mock.patch.dict(os.environ) +def test__get_gae_credentials_gen1(app_identity): + os.environ[environment_vars.LEGACY_APPENGINE_RUNTIME] = "python27" app_identity.get_application_id.return_value = mock.sentinel.project credentials, project_id = _default._get_gae_credentials() @@ -293,18 +295,65 @@ def test__get_gae_credentials(app_identity): assert project_id == mock.sentinel.project +@mock.patch.dict(os.environ) +def test__get_gae_credentials_gen2(): + os.environ["GAE_RUNTIME"] = "python37" + credentials, project_id = _default._get_gae_credentials() + assert credentials is None + assert project_id is None + + +@mock.patch.dict(os.environ) +def test__get_gae_credentials_gen2_backwards_compat(): + # compat helpers may copy GAE_RUNTIME to APPENGINE_RUNTIME + # for backwards compatibility with code that relies on it + os.environ[environment_vars.LEGACY_APPENGINE_RUNTIME] = "python37" + os.environ["GAE_RUNTIME"] = "python37" + credentials, project_id = _default._get_gae_credentials() + assert credentials is None + assert project_id is None + + +def test__get_gae_credentials_env_unset(): + assert environment_vars.LEGACY_APPENGINE_RUNTIME not in os.environ + assert "GAE_RUNTIME" not in os.environ + credentials, project_id = _default._get_gae_credentials() + assert credentials is None + assert project_id is None + + +@mock.patch.dict(os.environ) def test__get_gae_credentials_no_app_engine(): + # test both with and without LEGACY_APPENGINE_RUNTIME setting + assert environment_vars.LEGACY_APPENGINE_RUNTIME not in os.environ + import sys - with mock.patch.dict("sys.modules"): - sys.modules["google.auth.app_engine"] = None + with mock.patch.dict(sys.modules, {"google.auth.app_engine": None}): + credentials, project_id = _default._get_gae_credentials() + assert credentials is None + assert project_id is None + + os.environ[environment_vars.LEGACY_APPENGINE_RUNTIME] = "python27" credentials, project_id = _default._get_gae_credentials() assert credentials is None assert project_id is None +@mock.patch.dict(os.environ) +@mock.patch.object(app_engine, "app_identity", new=None) def test__get_gae_credentials_no_apis(): - assert _default._get_gae_credentials() == (None, None) + # test both with and without LEGACY_APPENGINE_RUNTIME setting + assert environment_vars.LEGACY_APPENGINE_RUNTIME not in os.environ + + credentials, project_id = _default._get_gae_credentials() + assert credentials is None + assert project_id is None + + os.environ[environment_vars.LEGACY_APPENGINE_RUNTIME] = "python27" + credentials, project_id = _default._get_gae_credentials() + assert credentials is None + assert project_id is None @mock.patch( From 6a8bd16f369048026195f4ffee7d881647e323ed Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 27 Jul 2021 12:13:19 -0700 Subject: [PATCH 445/966] chore: release 1.34.0 (#816) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 12 ++++++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 6b7abb3664eb..88fd400cbb78 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,18 @@ [1]: https://pypi.org/project/google-auth/#history +## [1.34.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.33.1...v1.34.0) (2021-07-23) + + +### Features + +* support refresh callable on google.oauth2.credentials.Credentials ([#812](https://www.github.com/googleapis/google-auth-library-python/issues/812)) ([ec2fb18](https://www.github.com/googleapis/google-auth-library-python/commit/ec2fb18e7f0f452fb20e43fd0bfbb788bcf7f46b)) + + +### Bug Fixes + +* do not use the GAE APIs on gen2+ runtimes ([#807](https://www.github.com/googleapis/google-auth-library-python/issues/807)) ([7f7d92d](https://www.github.com/googleapis/google-auth-library-python/commit/7f7d92d63ffee91859fc819416af78cef3baf574)) + ### [1.33.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.33.0...v1.33.1) (2021-07-20) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 6327f8588401..bd3c61a3dd33 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "1.33.1" +__version__ = "1.34.0" From 678712d4683f803794c1068f5b001ecbb4c071da Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Thu, 29 Jul 2021 14:26:30 -0600 Subject: [PATCH 446/966] chore: update authorized_user.json (#820) I've filed a question against internal the team that manages test accounts to ask what the recommended way to refresh these tokens are, but have not yet gotten a response. Manually refresh the token to unblock CI. --- .../google-auth/system_tests/secrets.tar.enc | Bin 10323 -> 10323 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 46b521c76e638564dd51363cbd18eab0bfcbdaba..e9d9a516fcc0f729fe436a301db834bb8cf0456f 100644 GIT binary patch literal 10323 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 literal 10323 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 Date: Tue, 3 Aug 2021 16:35:54 -0400 Subject: [PATCH 447/966] fix!: drop support for Python 2.7 (#778) Drop use of 'six' wrapper library. Drop 'u"' prefixes. Drop support for app_engine 'classic' mode (Python 2.7-only). Release-As: 2.0.0b1 Closes #777. --- packages/google-auth/CONTRIBUTING.rst | 4 +- packages/google-auth/README.rst | 12 +- .../google-auth/google/auth/_cloud_sdk.py | 4 +- .../google/auth/_credentials_async.py | 11 +- packages/google-auth/google/auth/_default.py | 8 +- .../google-auth/google/auth/_default_async.py | 8 +- packages/google-auth/google/auth/_helpers.py | 17 +-- .../google-auth/google/auth/_oauth2client.py | 6 +- .../google/auth/_service_account_info.py | 4 +- packages/google-auth/google/auth/aws.py | 9 +- .../google/auth/compute_engine/_metadata.py | 12 +- .../google/auth/compute_engine/credentials.py | 8 +- .../google-auth/google/auth/credentials.py | 11 +- .../google-auth/google/auth/crypt/__init__.py | 4 +- .../google/auth/crypt/_python_rsa.py | 9 +- .../google-auth/google/auth/crypt/base.py | 11 +- .../google/auth/external_account.py | 7 +- packages/google-auth/google/auth/iam.py | 5 +- .../google-auth/google/auth/identity_pool.py | 6 +- .../google/auth/impersonated_credentials.py | 8 +- packages/google-auth/google/auth/jwt.py | 28 ++-- .../google/auth/transport/__init__.py | 12 +- .../auth/transport/_aiohttp_requests.py | 5 +- .../google/auth/transport/_http_client.py | 12 +- .../google/auth/transport/_mtls_helper.py | 6 +- .../google-auth/google/auth/transport/grpc.py | 16 +-- .../google-auth/google/auth/transport/mtls.py | 6 +- .../google/auth/transport/requests.py | 19 +-- .../google/auth/transport/urllib3.py | 19 +-- packages/google-auth/google/oauth2/_client.py | 18 ++- .../google/oauth2/_client_async.py | 16 +-- .../google/oauth2/_id_token_async.py | 12 +- .../google/oauth2/_reauth_async.py | 2 - .../google-auth/google/oauth2/challenges.py | 5 +- .../google-auth/google/oauth2/credentials.py | 4 +- .../google-auth/google/oauth2/id_token.py | 12 +- packages/google-auth/google/oauth2/reauth.py | 2 - packages/google-auth/google/oauth2/sts.py | 7 +- packages/google-auth/google/oauth2/utils.py | 5 +- packages/google-auth/noxfile.py | 24 +--- packages/google-auth/setup.py | 25 ++-- packages/google-auth/system_tests/noxfile.py | 46 +----- .../app_engine_test_app/.gitignore | 1 - .../app_engine_test_app/app.yaml | 12 -- .../app_engine_test_app/appengine_config.py | 30 ---- .../app_engine_test_app/main.py | 133 ------------------ .../app_engine_test_app/requirements.txt | 3 - .../system_tests_sync/test_app_engine.py | 22 --- .../test_external_accounts.py | 14 +- .../google-auth/testing/constraints-3.6.txt | 1 - .../tests/compute_engine/test__metadata.py | 20 +-- .../tests/crypt/test__cryptography_rsa.py | 6 +- .../tests/crypt/test__python_rsa.py | 10 +- .../google-auth/tests/crypt/test_es256.py | 6 +- .../google-auth/tests/oauth2/test__client.py | 15 +- packages/google-auth/tests/oauth2/test_sts.py | 24 ++-- packages/google-auth/tests/test__helpers.py | 12 +- .../google-auth/tests/test__oauth2client.py | 8 +- .../tests/test__service_account_info.py | 3 +- packages/google-auth/tests/test_aws.py | 74 +++++----- .../tests/test_external_account.py | 66 ++++----- packages/google-auth/tests/test_iam.py | 6 +- .../google-auth/tests/test_identity_pool.py | 12 +- .../tests/test_impersonated_credentials.py | 34 ++--- packages/google-auth/tests/test_jwt.py | 8 +- .../google-auth/tests/transport/compliance.py | 14 +- .../tests/transport/test_requests.py | 22 +-- .../tests/transport/test_urllib3.py | 12 +- .../tests_async/oauth2/test__client_async.py | 15 +- .../tests_async/transport/async_compliance.py | 16 +-- 70 files changed, 350 insertions(+), 714 deletions(-) delete mode 100644 packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/.gitignore delete mode 100644 packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/app.yaml delete mode 100644 packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/appengine_config.py delete mode 100644 packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/main.py delete mode 100644 packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/requirements.txt delete mode 100644 packages/google-auth/system_tests/system_tests_sync/test_app_engine.py diff --git a/packages/google-auth/CONTRIBUTING.rst b/packages/google-auth/CONTRIBUTING.rst index 175e76634f01..84108995d194 100644 --- a/packages/google-auth/CONTRIBUTING.rst +++ b/packages/google-auth/CONTRIBUTING.rst @@ -18,8 +18,8 @@ A few notes on making changes to ``google-auth-library-python``. documentation (in ``docs/``). You can re-generate the reference documentation using ``nox -s docgen``. -- The change must work fully on the following CPython versions: 2.7, - 3.5, 3.6, 3.7, 3.8 across macOS, Linux, and Windows. +- The change must work fully on the following CPython versions: + 3.6, 3.7, 3.8, 3.9 across macOS, Linux, and Windows. - The codebase *must* have 100% test statement coverage after each commit. You can test coverage via ``nox -e cover``. diff --git a/packages/google-auth/README.rst b/packages/google-auth/README.rst index 10de0ac06f75..35ebe8bf078b 100644 --- a/packages/google-auth/README.rst +++ b/packages/google-auth/README.rst @@ -24,11 +24,15 @@ For more information on setting up your Python development environment, please r Supported Python Versions ^^^^^^^^^^^^^^^^^^^^^^^^^ -Python >= 3.5 +Python >= 3.6 -Deprecated Python Versions -^^^^^^^^^^^^^^^^^^^^^^^^^^ -Python == 2.7. Python 2.7 support will be removed on January 1, 2020. +Unsupported Python Versions +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +- Python == 2.7: The last version of this library with support for Python 2.7 + was `google.auth == 1.34.0`. + +- Python 3.5: The last version of this library with support for Python 3.5 + was `google.auth == 1.23.0`. Documentation ------------- diff --git a/packages/google-auth/google/auth/_cloud_sdk.py b/packages/google-auth/google/auth/_cloud_sdk.py index 40e6aec13a8e..1f13ad4207be 100644 --- a/packages/google-auth/google/auth/_cloud_sdk.py +++ b/packages/google-auth/google/auth/_cloud_sdk.py @@ -18,8 +18,6 @@ import os import subprocess -import six - from google.auth import environment_vars from google.auth import exceptions @@ -156,4 +154,4 @@ def get_auth_access_token(account=None): new_exc = exceptions.UserAccessTokenError( "Failed to obtain access token", caught_exc ) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc diff --git a/packages/google-auth/google/auth/_credentials_async.py b/packages/google-auth/google/auth/_credentials_async.py index d4d4e2c0e45f..760758d851b0 100644 --- a/packages/google-auth/google/auth/_credentials_async.py +++ b/packages/google-auth/google/auth/_credentials_async.py @@ -18,13 +18,10 @@ import abc import inspect -import six - from google.auth import credentials -@six.add_metaclass(abc.ABCMeta) -class Credentials(credentials.Credentials): +class Credentials(credentials.Credentials, metaclass=abc.ABCMeta): """Async inherited credentials class from google.auth.credentials. The added functionality is the before_request call which requires async/await syntax. @@ -84,8 +81,7 @@ class AnonymousCredentials(credentials.AnonymousCredentials, Credentials): """ -@six.add_metaclass(abc.ABCMeta) -class ReadOnlyScoped(credentials.ReadOnlyScoped): +class ReadOnlyScoped(credentials.ReadOnlyScoped, metaclass=abc.ABCMeta): """Interface for credentials whose scopes can be queried. OAuth 2.0-based credentials allow limiting access using scopes as described @@ -171,6 +167,5 @@ def with_scopes_if_required(credentials, scopes): return credentials -@six.add_metaclass(abc.ABCMeta) -class Signing(credentials.Signing): +class Signing(credentials.Signing, metaclass=abc.ABCMeta): """Interface for credentials that can cryptographically sign messages.""" diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index f7e308f3e084..7da77a28f7af 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -23,8 +23,6 @@ import os import warnings -import six - from google.auth import environment_vars from google.auth import exceptions import google.auth.transport._http_client @@ -115,7 +113,7 @@ def load_credentials_from_file( new_exc = exceptions.DefaultCredentialsError( "File {} is not a valid json file.".format(filename), caught_exc ) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc # The type key should indicate that the file is either a service account # credentials file or an authorized user credentials file. @@ -131,7 +129,7 @@ def load_credentials_from_file( except ValueError as caught_exc: msg = "Failed to load authorized user credentials from {}".format(filename) new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc if quota_project_id: credentials = credentials.with_quota_project(quota_project_id) if not credentials.quota_project_id: @@ -148,7 +146,7 @@ def load_credentials_from_file( except ValueError as caught_exc: msg = "Failed to load service account credentials from {}".format(filename) new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc if quota_project_id: credentials = credentials.with_quota_project(quota_project_id) return credentials, info.get("project_id") diff --git a/packages/google-auth/google/auth/_default_async.py b/packages/google-auth/google/auth/_default_async.py index d12a642afd3e..82e6c432d878 100644 --- a/packages/google-auth/google/auth/_default_async.py +++ b/packages/google-auth/google/auth/_default_async.py @@ -21,8 +21,6 @@ import json import os -import six - from google.auth import _default from google.auth import environment_vars from google.auth import exceptions @@ -63,7 +61,7 @@ def load_credentials_from_file(filename, scopes=None, quota_project_id=None): new_exc = exceptions.DefaultCredentialsError( "File {} is not a valid json file.".format(filename), caught_exc ) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc # The type key should indicate that the file is either a service account # credentials file or an authorized user credentials file. @@ -79,7 +77,7 @@ def load_credentials_from_file(filename, scopes=None, quota_project_id=None): except ValueError as caught_exc: msg = "Failed to load authorized user credentials from {}".format(filename) new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc if not credentials.quota_project_id: _default._warn_about_problematic_credentials(credentials) return credentials, None @@ -94,7 +92,7 @@ def load_credentials_from_file(filename, scopes=None, quota_project_id=None): except ValueError as caught_exc: msg = "Failed to load service account credentials from {}".format(filename) new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc return credentials, info.get("project_id") else: diff --git a/packages/google-auth/google/auth/_helpers.py b/packages/google-auth/google/auth/_helpers.py index 21c987a73277..09f32f84e38f 100644 --- a/packages/google-auth/google/auth/_helpers.py +++ b/packages/google-auth/google/auth/_helpers.py @@ -17,9 +17,7 @@ import base64 import calendar import datetime - -import six -from six.moves import urllib +import urllib CLOCK_SKEW_SECS = 10 # 10 seconds @@ -84,9 +82,6 @@ def datetime_to_secs(value): def to_bytes(value, encoding="utf-8"): """Converts a string value to bytes, if necessary. - Unfortunately, ``six.b`` is insufficient for this task since in - Python 2 because it does not modify ``unicode`` objects. - Args: value (Union[str, bytes]): The value to be converted. encoding (str): The encoding to use to convert unicode to bytes. @@ -99,8 +94,8 @@ def to_bytes(value, encoding="utf-8"): Raises: ValueError: If the value could not be converted to bytes. """ - result = value.encode(encoding) if isinstance(value, six.text_type) else value - if isinstance(result, six.binary_type): + result = value.encode(encoding) if isinstance(value, str) else value + if isinstance(result, bytes): return result else: raise ValueError("{0!r} could not be converted to bytes".format(value)) @@ -119,8 +114,8 @@ def from_bytes(value): Raises: ValueError: If the value could not be converted to unicode. """ - result = value.decode("utf-8") if isinstance(value, six.binary_type) else value - if isinstance(result, six.text_type): + result = value.decode("utf-8") if isinstance(value, bytes) else value + if isinstance(result, str): return result else: raise ValueError("{0!r} could not be converted to unicode".format(value)) @@ -162,7 +157,7 @@ def update_query(url, params, remove=None): query_params.update(params) # Remove any values specified in remove. query_params = { - key: value for key, value in six.iteritems(query_params) if key not in remove + key: value for key, value in query_params.items() if key not in remove } # Re-encoded the query string. new_query = urllib.parse.urlencode(query_params, doseq=True) diff --git a/packages/google-auth/google/auth/_oauth2client.py b/packages/google-auth/google/auth/_oauth2client.py index 95a9876f3151..3512e1d116f1 100644 --- a/packages/google-auth/google/auth/_oauth2client.py +++ b/packages/google-auth/google/auth/_oauth2client.py @@ -21,8 +21,6 @@ from __future__ import absolute_import -import six - from google.auth import _helpers import google.auth.app_engine import google.auth.compute_engine @@ -34,7 +32,7 @@ import oauth2client.contrib.gce import oauth2client.service_account except ImportError as caught_exc: - six.raise_from(ImportError("oauth2client is not installed."), caught_exc) + raise ImportError("oauth2client is not installed.") from caught_exc try: import oauth2client.contrib.appengine # pytype: disable=import-error @@ -166,4 +164,4 @@ def convert(credentials): return _CLASS_CONVERSION_MAP[credentials_class](credentials) except KeyError as caught_exc: new_exc = ValueError(_CONVERT_ERROR_TMPL.format(credentials_class)) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc diff --git a/packages/google-auth/google/auth/_service_account_info.py b/packages/google-auth/google/auth/_service_account_info.py index 3d340c78d40c..54a40e9daa0b 100644 --- a/packages/google-auth/google/auth/_service_account_info.py +++ b/packages/google-auth/google/auth/_service_account_info.py @@ -17,8 +17,6 @@ import io import json -import six - from google.auth import crypt @@ -43,7 +41,7 @@ def from_dict(data, require=None): """ keys_needed = set(require if require is not None else []) - missing = keys_needed.difference(six.iterkeys(data)) + missing = keys_needed.difference(data) if missing: raise ValueError( diff --git a/packages/google-auth/google/auth/aws.py b/packages/google-auth/google/auth/aws.py index c2b521c360f6..2f2a1359b141 100644 --- a/packages/google-auth/google/auth/aws.py +++ b/packages/google-auth/google/auth/aws.py @@ -39,13 +39,12 @@ import hashlib import hmac +import http.client import io import json import os import re - -from six.moves import http_client -from six.moves import urllib +import urllib from google.auth import _helpers from google.auth import environment_vars @@ -627,7 +626,7 @@ def _get_metadata_security_credentials(self, request, role_name): else response.data ) - if response.status != http_client.OK: + if response.status != http.client.OK: raise exceptions.RefreshError( "Unable to retrieve AWS security credentials", response_body ) @@ -666,7 +665,7 @@ def _get_metadata_role_name(self, request): else response.data ) - if response.status != http_client.OK: + if response.status != http.client.OK: raise exceptions.RefreshError( "Unable to retrieve AWS role name", response_body ) diff --git a/packages/google-auth/google/auth/compute_engine/_metadata.py b/packages/google-auth/google/auth/compute_engine/_metadata.py index 5687a42f965e..ee6fec623936 100644 --- a/packages/google-auth/google/auth/compute_engine/_metadata.py +++ b/packages/google-auth/google/auth/compute_engine/_metadata.py @@ -18,13 +18,11 @@ """ import datetime +import http.client import json import logging import os - -import six -from six.moves import http_client -from six.moves.urllib import parse as urlparse +from urllib import parse as urlparse from google.auth import _helpers from google.auth import environment_vars @@ -91,7 +89,7 @@ def ping(request, timeout=_METADATA_DEFAULT_TIMEOUT, retry_count=3): metadata_flavor = response.headers.get(_METADATA_FLAVOR_HEADER) return ( - response.status == http_client.OK + response.status == http.client.OK and metadata_flavor == _METADATA_FLAVOR_VALUE ) @@ -165,7 +163,7 @@ def get( "metadata service. Compute Engine Metadata server unavailable".format(url) ) - if response.status == http_client.OK: + if response.status == http.client.OK: content = _helpers.from_bytes(response.data) if response.headers["content-type"] == "application/json": try: @@ -175,7 +173,7 @@ def get( "Received invalid JSON from the Google Compute Engine" "metadata service: {:.20}".format(content) ) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc else: return content else: diff --git a/packages/google-auth/google/auth/compute_engine/credentials.py b/packages/google-auth/google/auth/compute_engine/credentials.py index 167165620068..cb4e0f0a0a7a 100644 --- a/packages/google-auth/google/auth/compute_engine/credentials.py +++ b/packages/google-auth/google/auth/compute_engine/credentials.py @@ -21,8 +21,6 @@ import datetime -import six - from google.auth import _helpers from google.auth import credentials from google.auth import exceptions @@ -38,7 +36,7 @@ class Credentials(credentials.Scoped, credentials.CredentialsWithQuotaProject): These credentials use the Google Compute Engine metadata server to obtain OAuth 2.0 access tokens associated with the instance's service account, and are also used for Cloud Run, Flex and App Engine (except for the Python - 2.7 runtime). + 2.7 runtime, which is supported only on older versions of this library). For more information about Compute Engine authentication, including how to configure scopes, see the `Compute Engine authentication @@ -114,7 +112,7 @@ def refresh(self, request): ) except exceptions.TransportError as caught_exc: new_exc = exceptions.RefreshError(caught_exc) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc @property def service_account_email(self): @@ -352,7 +350,7 @@ def _call_metadata_identity_endpoint(self, request): id_token = _metadata.get(request, path, params=params) except exceptions.TransportError as caught_exc: new_exc = exceptions.RefreshError(caught_exc) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc _, payload, _, _ = jwt._unverified_decode(id_token) return id_token, datetime.datetime.fromtimestamp(payload["exp"]) diff --git a/packages/google-auth/google/auth/credentials.py b/packages/google-auth/google/auth/credentials.py index 7d3c798b139d..6356f5434c13 100644 --- a/packages/google-auth/google/auth/credentials.py +++ b/packages/google-auth/google/auth/credentials.py @@ -17,13 +17,10 @@ import abc -import six - from google.auth import _helpers -@six.add_metaclass(abc.ABCMeta) -class Credentials(object): +class Credentials(object, metaclass=abc.ABCMeta): """Base class for all credentials. All credentials have a :attr:`token` that is used for authentication and @@ -187,8 +184,7 @@ def before_request(self, request, method, url, headers): """Anonymous credentials do nothing to the request.""" -@six.add_metaclass(abc.ABCMeta) -class ReadOnlyScoped(object): +class ReadOnlyScoped(object, metaclass=abc.ABCMeta): """Interface for credentials whose scopes can be queried. OAuth 2.0-based credentials allow limiting access using scopes as described @@ -329,8 +325,7 @@ def with_scopes_if_required(credentials, scopes, default_scopes=None): return credentials -@six.add_metaclass(abc.ABCMeta) -class Signing(object): +class Signing(object, metaclass=abc.ABCMeta): """Interface for credentials that can cryptographically sign messages.""" @abc.abstractmethod diff --git a/packages/google-auth/google/auth/crypt/__init__.py b/packages/google-auth/google/auth/crypt/__init__.py index 15ac95068625..97e9d81d590c 100644 --- a/packages/google-auth/google/auth/crypt/__init__.py +++ b/packages/google-auth/google/auth/crypt/__init__.py @@ -37,8 +37,6 @@ version is at least 1.4.0. """ -import six - from google.auth.crypt import base from google.auth.crypt import rsa @@ -90,7 +88,7 @@ class to use for verification. This can be used to select different Returns: bool: True if the signature is valid, otherwise False. """ - if isinstance(certs, (six.text_type, six.binary_type)): + if isinstance(certs, (str, bytes)): certs = [certs] for cert in certs: diff --git a/packages/google-auth/google/auth/crypt/_python_rsa.py b/packages/google-auth/google/auth/crypt/_python_rsa.py index ec30dd09a37e..1c4a9dab8a4d 100644 --- a/packages/google-auth/google/auth/crypt/_python_rsa.py +++ b/packages/google-auth/google/auth/crypt/_python_rsa.py @@ -21,12 +21,13 @@ from __future__ import absolute_import +import io + from pyasn1.codec.der import decoder from pyasn1_modules import pem from pyasn1_modules.rfc2459 import Certificate from pyasn1_modules.rfc5208 import PrivateKeyInfo import rsa -import six from google.auth import _helpers from google.auth.crypt import base @@ -52,9 +53,9 @@ def _bit_list_to_bytes(bit_list): """ num_bits = len(bit_list) byte_vals = bytearray() - for start in six.moves.xrange(0, num_bits, 8): + for start in range(0, num_bits, 8): curr_bits = bit_list[start : start + 8] - char_val = sum(val * digit for val, digit in six.moves.zip(_POW2, curr_bits)) + char_val = sum(val * digit for val, digit in zip(_POW2, curr_bits)) byte_vals.append(char_val) return bytes(byte_vals) @@ -152,7 +153,7 @@ def from_string(cls, key, key_id=None): """ key = _helpers.from_bytes(key) # PEM expects str in Python 3 marker_id, key_bytes = pem.readPemBlocksFromFile( - six.StringIO(key), _PKCS1_MARKER, _PKCS8_MARKER + io.StringIO(key), _PKCS1_MARKER, _PKCS8_MARKER ) # Key is in pkcs1 format. diff --git a/packages/google-auth/google/auth/crypt/base.py b/packages/google-auth/google/auth/crypt/base.py index c98d5bf64fc4..0bda9c34526d 100644 --- a/packages/google-auth/google/auth/crypt/base.py +++ b/packages/google-auth/google/auth/crypt/base.py @@ -18,15 +18,12 @@ import io import json -import six - _JSON_FILE_PRIVATE_KEY = "private_key" _JSON_FILE_PRIVATE_KEY_ID = "private_key_id" -@six.add_metaclass(abc.ABCMeta) -class Verifier(object): +class Verifier(object, metaclass=abc.ABCMeta): """Abstract base class for crytographic signature verifiers.""" @abc.abstractmethod @@ -46,8 +43,7 @@ def verify(self, message, signature): raise NotImplementedError("Verify must be implemented") -@six.add_metaclass(abc.ABCMeta) -class Signer(object): +class Signer(object, metaclass=abc.ABCMeta): """Abstract base class for cryptographic signers.""" @abc.abstractproperty @@ -70,8 +66,7 @@ def sign(self, message): raise NotImplementedError("Sign must be implemented") -@six.add_metaclass(abc.ABCMeta) -class FromServiceAccountMixin(object): +class FromServiceAccountMixin(object, metaclass=abc.ABCMeta): """Mix-in to enable factory constructors for a Signer.""" @abc.abstractmethod diff --git a/packages/google-auth/google/auth/external_account.py b/packages/google-auth/google/auth/external_account.py index 1f3034ac3533..24b93b423613 100644 --- a/packages/google-auth/google/auth/external_account.py +++ b/packages/google-auth/google/auth/external_account.py @@ -33,8 +33,6 @@ import json import re -import six - from google.auth import _helpers from google.auth import credentials from google.auth import exceptions @@ -52,8 +50,9 @@ _CLOUD_RESOURCE_MANAGER = "https://cloudresourcemanager.googleapis.com/v1/projects/" -@six.add_metaclass(abc.ABCMeta) -class Credentials(credentials.Scoped, credentials.CredentialsWithQuotaProject): +class Credentials( + credentials.Scoped, credentials.CredentialsWithQuotaProject, metaclass=abc.ABCMeta +): """Base class for all external account credentials. This is used to instantiate Credentials for exchanging external account diff --git a/packages/google-auth/google/auth/iam.py b/packages/google-auth/google/auth/iam.py index 5d63dc5d8a91..277f4b7f36bf 100644 --- a/packages/google-auth/google/auth/iam.py +++ b/packages/google-auth/google/auth/iam.py @@ -20,10 +20,9 @@ """ import base64 +import http.client import json -from six.moves import http_client - from google.auth import _helpers from google.auth import crypt from google.auth import exceptions @@ -77,7 +76,7 @@ def _make_signing_request(self, message): self._credentials.before_request(self._request, method, url, headers) response = self._request(url=url, method=method, body=body, headers=headers) - if response.status != http_client.OK: + if response.status != http.client.OK: raise exceptions.TransportError( "Error calling the IAM signBlob API: {}".format(response.data) ) diff --git a/packages/google-auth/google/auth/identity_pool.py b/packages/google-auth/google/auth/identity_pool.py index 53621995557f..c331e09217a9 100644 --- a/packages/google-auth/google/auth/identity_pool.py +++ b/packages/google-auth/google/auth/identity_pool.py @@ -33,11 +33,7 @@ access tokens. """ -try: - from collections.abc import Mapping -# Python 2.7 compatibility -except ImportError: # pragma: NO COVER - from collections import Mapping +from collections.abc import Mapping import io import json import os diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index b8a6c49a1eba..2704bfdd242c 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -28,11 +28,9 @@ import base64 import copy from datetime import datetime +import http.client import json -import six -from six.moves import http_client - from google.auth import _helpers from google.auth import credentials from google.auth import exceptions @@ -100,7 +98,7 @@ def _make_iam_token_request( else response.data ) - if response.status != http_client.OK: + if response.status != http.client.OK: exceptions.RefreshError(_REFRESH_ERROR, response_body) try: @@ -117,7 +115,7 @@ def _make_iam_token_request( ), response_body, ) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc class Credentials(credentials.CredentialsWithQuotaProject, credentials.Signing): diff --git a/packages/google-auth/google/auth/jwt.py b/packages/google-auth/google/auth/jwt.py index e9f4f69ca0a6..d931bf7b9f75 100644 --- a/packages/google-auth/google/auth/jwt.py +++ b/packages/google-auth/google/auth/jwt.py @@ -40,18 +40,13 @@ """ -try: - from collections.abc import Mapping -# Python 2.7 compatibility -except ImportError: # pragma: NO COVER - from collections import Mapping +from collections.abc import Mapping import copy import datetime import json +import urllib import cachetools -import six -from six.moves import urllib from google.auth import _helpers from google.auth import _service_account_info @@ -123,7 +118,7 @@ def _decode_jwt_segment(encoded_section): return json.loads(section_bytes.decode("utf-8")) except ValueError as caught_exc: new_exc = ValueError("Can't parse segment: {0}".format(section_bytes)) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc def _unverified_decode(token): @@ -241,19 +236,16 @@ def decode(token, certs=None, verify=True, audience=None): try: verifier_cls = _ALGORITHM_TO_VERIFIER_CLASS[key_alg] - except KeyError as exc: + except KeyError as caught_exc: if key_alg in _CRYPTOGRAPHY_BASED_ALGORITHMS: - six.raise_from( - ValueError( - "The key algorithm {} requires the cryptography package " - "to be installed.".format(key_alg) - ), - exc, + msg = ( + "The key algorithm {} requires the cryptography package " + "to be installed." ) else: - six.raise_from( - ValueError("Unsupported signature algorithm {}".format(key_alg)), exc - ) + msg = "Unsupported signature algorithm {}" + new_exc = ValueError(msg.format(key_alg)) + raise new_exc from caught_exc # If certs is specified as a dictionary of key IDs to certificates, then # use the certificate identified by the key ID in the token header. diff --git a/packages/google-auth/google/auth/transport/__init__.py b/packages/google-auth/google/auth/transport/__init__.py index 374e7b4d7228..d1b035df97ee 100644 --- a/packages/google-auth/google/auth/transport/__init__.py +++ b/packages/google-auth/google/auth/transport/__init__.py @@ -25,11 +25,9 @@ """ import abc +import http.client -import six -from six.moves import http_client - -DEFAULT_REFRESH_STATUS_CODES = (http_client.UNAUTHORIZED,) +DEFAULT_REFRESH_STATUS_CODES = (http.client.UNAUTHORIZED,) """Sequence[int]: Which HTTP status code indicate that credentials should be refreshed and a request should be retried. """ @@ -38,8 +36,7 @@ """int: How many times to refresh the credentials and retry a request.""" -@six.add_metaclass(abc.ABCMeta) -class Response(object): +class Response(object, metaclass=abc.ABCMeta): """HTTP Response data.""" @abc.abstractproperty @@ -58,8 +55,7 @@ def data(self): raise NotImplementedError("data must be implemented.") -@six.add_metaclass(abc.ABCMeta) -class Request(object): +class Request(object, metaclass=abc.ABCMeta): """Interface for a callable that makes HTTP requests. Specific transport implementations should provide an implementation of diff --git a/packages/google-auth/google/auth/transport/_aiohttp_requests.py b/packages/google-auth/google/auth/transport/_aiohttp_requests.py index ab7dfef67740..ee94043304df 100644 --- a/packages/google-auth/google/auth/transport/_aiohttp_requests.py +++ b/packages/google-auth/google/auth/transport/_aiohttp_requests.py @@ -24,7 +24,6 @@ import functools import aiohttp -import six import urllib3 from google.auth import exceptions @@ -191,11 +190,11 @@ async def __call__( except aiohttp.ClientError as caught_exc: new_exc = exceptions.TransportError(caught_exc) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc except asyncio.TimeoutError as caught_exc: new_exc = exceptions.TransportError(caught_exc) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc class AuthorizedSession(aiohttp.ClientSession): diff --git a/packages/google-auth/google/auth/transport/_http_client.py b/packages/google-auth/google/auth/transport/_http_client.py index c153763efa2c..679087f068a7 100644 --- a/packages/google-auth/google/auth/transport/_http_client.py +++ b/packages/google-auth/google/auth/transport/_http_client.py @@ -14,12 +14,10 @@ """Transport adapter for http.client, for internal use only.""" +import http.client import logging import socket - -import six -from six.moves import http_client -from six.moves import urllib +import urllib from google.auth import exceptions from google.auth import transport @@ -98,7 +96,7 @@ def __call__( "was specified".format(parts.scheme) ) - connection = http_client.HTTPConnection(parts.netloc, timeout=timeout) + connection = http.client.HTTPConnection(parts.netloc, timeout=timeout) try: _LOGGER.debug("Making request: %s %s", method, url) @@ -107,9 +105,9 @@ def __call__( response = connection.getresponse() return Response(response) - except (http_client.HTTPException, socket.error) as caught_exc: + except (http.client.HTTPException, socket.error) as caught_exc: new_exc = exceptions.TransportError(caught_exc) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc finally: connection.close() diff --git a/packages/google-auth/google/auth/transport/_mtls_helper.py b/packages/google-auth/google/auth/transport/_mtls_helper.py index 4dccb1062f88..1b9b9c285c6c 100644 --- a/packages/google-auth/google/auth/transport/_mtls_helper.py +++ b/packages/google-auth/google/auth/transport/_mtls_helper.py @@ -20,8 +20,6 @@ import re import subprocess -import six - from google.auth import exceptions CONTEXT_AWARE_METADATA_PATH = "~/.secureConnect/context_aware_metadata.json" @@ -82,7 +80,7 @@ def _read_dca_metadata_file(metadata_path): metadata = json.load(f) except ValueError as caught_exc: new_exc = exceptions.ClientCertError(caught_exc) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc return metadata @@ -110,7 +108,7 @@ def _run_cert_provider_command(command, expect_encrypted_key=False): stdout, stderr = process.communicate() except OSError as caught_exc: new_exc = exceptions.ClientCertError(caught_exc) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc # Check cert provider command execution error. if process.returncode != 0: diff --git a/packages/google-auth/google/auth/transport/grpc.py b/packages/google-auth/google/auth/transport/grpc.py index c47cb3ddafcd..160dc946b145 100644 --- a/packages/google-auth/google/auth/transport/grpc.py +++ b/packages/google-auth/google/auth/transport/grpc.py @@ -19,8 +19,6 @@ import logging import os -import six - from google.auth import environment_vars from google.auth import exceptions from google.auth.transport import _mtls_helper @@ -29,13 +27,11 @@ try: import grpc except ImportError as caught_exc: # pragma: NO COVER - six.raise_from( - ImportError( - "gRPC is not installed, please install the grpcio package " - "to use the gRPC transport." - ), - caught_exc, + new_exc = ImportError( + "gRPC is not installed, please install the grpcio package " + "to use the gRPC transport." ) + raise new_exc from caught_exc _LOGGER = logging.getLogger(__name__) @@ -88,7 +84,7 @@ def _get_authorization_headers(self, context): self._request, context.method_name, context.service_url, headers ) - return list(six.iteritems(headers)) + return list(headers.items()) def __call__(self, context, callback): """Passes authorization metadata into the given callback. @@ -337,7 +333,7 @@ def ssl_credentials(self): ) except exceptions.ClientCertError as caught_exc: new_exc = exceptions.MutualTLSChannelError(caught_exc) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc else: self._ssl_credentials = grpc.ssl_channel_credentials() diff --git a/packages/google-auth/google/auth/transport/mtls.py b/packages/google-auth/google/auth/transport/mtls.py index b40bfbedf97d..c5707617ff80 100644 --- a/packages/google-auth/google/auth/transport/mtls.py +++ b/packages/google-auth/google/auth/transport/mtls.py @@ -14,8 +14,6 @@ """Utilites for mutual TLS.""" -import six - from google.auth import exceptions from google.auth.transport import _mtls_helper @@ -53,7 +51,7 @@ def callback(): _, cert_bytes, key_bytes = _mtls_helper.get_client_cert_and_key() except (OSError, RuntimeError, ValueError) as caught_exc: new_exc = exceptions.MutualTLSChannelError(caught_exc) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc return cert_bytes, key_bytes @@ -98,7 +96,7 @@ def callback(): key_file.write(key_bytes) except (exceptions.ClientCertError, OSError) as caught_exc: new_exc = exceptions.MutualTLSChannelError(caught_exc) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc return cert_path, key_path, passphrase_bytes diff --git a/packages/google-auth/google/auth/transport/requests.py b/packages/google-auth/google/auth/transport/requests.py index 817176befa20..2cb6942476dd 100644 --- a/packages/google-auth/google/auth/transport/requests.py +++ b/packages/google-auth/google/auth/transport/requests.py @@ -25,21 +25,16 @@ try: import requests except ImportError as caught_exc: # pragma: NO COVER - import six - - six.raise_from( - ImportError( - "The requests library is not installed, please install the " - "requests package to use the requests transport." - ), - caught_exc, + new_exc = ImportError( + "The requests library is not installed, please install the " + "requests package to use the requests transport." ) + raise new_exc from caught_exc import requests.adapters # pylint: disable=ungrouped-imports import requests.exceptions # pylint: disable=ungrouped-imports from requests.packages.urllib3.util.ssl_ import ( create_urllib3_context, ) # pylint: disable=ungrouped-imports -import six # pylint: disable=ungrouped-imports from google.auth import environment_vars from google.auth import exceptions @@ -186,7 +181,7 @@ def __call__( return _Response(response) except requests.exceptions.RequestException as caught_exc: new_exc = exceptions.TransportError(caught_exc) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc class _MutualTlsAdapter(requests.adapters.HTTPAdapter): @@ -396,7 +391,7 @@ def configure_mtls_channel(self, client_cert_callback=None): import OpenSSL except ImportError as caught_exc: new_exc = exceptions.MutualTLSChannelError(caught_exc) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc try: ( @@ -416,7 +411,7 @@ def configure_mtls_channel(self, client_cert_callback=None): OpenSSL.crypto.Error, ) as caught_exc: new_exc = exceptions.MutualTLSChannelError(caught_exc) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc def request( self, diff --git a/packages/google-auth/google/auth/transport/urllib3.py b/packages/google-auth/google/auth/transport/urllib3.py index 6a2504d972ef..aa7188c559d7 100644 --- a/packages/google-auth/google/auth/transport/urllib3.py +++ b/packages/google-auth/google/auth/transport/urllib3.py @@ -34,16 +34,11 @@ try: import urllib3 except ImportError as caught_exc: # pragma: NO COVER - import six - - six.raise_from( - ImportError( - "The urllib3 library is not installed, please install the " - "urllib3 package to use the urllib3 transport." - ), - caught_exc, + new_exc = ImportError( + "The urllib3 library is not installed, please install the " + "urllib3 package to use the urllib3 transport." ) -import six + raise new_exc from caught_exc import urllib3.exceptions # pylint: disable=ungrouped-imports from google.auth import environment_vars @@ -142,7 +137,7 @@ def __call__( return _Response(response) except urllib3.exceptions.HTTPError as caught_exc: new_exc = exceptions.TransportError(caught_exc) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc def _make_default_http(): @@ -334,7 +329,7 @@ def configure_mtls_channel(self, client_cert_callback=None): import OpenSSL except ImportError as caught_exc: new_exc = exceptions.MutualTLSChannelError(caught_exc) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc try: found_cert_key, cert, key = transport._mtls_helper.get_client_cert_and_key( @@ -351,7 +346,7 @@ def configure_mtls_channel(self, client_cert_callback=None): OpenSSL.crypto.Error, ) as caught_exc: new_exc = exceptions.MutualTLSChannelError(caught_exc) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc if self._has_user_provided_http: self._has_user_provided_http = False diff --git a/packages/google-auth/google/oauth2/_client.py b/packages/google-auth/google/oauth2/_client.py index 2f4e8474b557..f819371af8de 100644 --- a/packages/google-auth/google/oauth2/_client.py +++ b/packages/google-auth/google/oauth2/_client.py @@ -24,11 +24,9 @@ """ import datetime +import http.client import json - -import six -from six.moves import http_client -from six.moves import urllib +import urllib from google.auth import _helpers from google.auth import exceptions @@ -120,7 +118,7 @@ def _token_endpoint_request_no_throw( ) response_data = json.loads(response_body) - if response.status == http_client.OK: + if response.status == http.client.OK: break else: error_desc = response_data.get("error_description") or "" @@ -131,9 +129,9 @@ def _token_endpoint_request_no_throw( ): retry += 1 continue - return response.status == http_client.OK, response_data + return response.status == http.client.OK, response_data - return response.status == http_client.OK, response_data + return response.status == http.client.OK, response_data def _token_endpoint_request( @@ -196,7 +194,7 @@ def jwt_grant(request, token_uri, assertion): access_token = response_data["access_token"] except KeyError as caught_exc: new_exc = exceptions.RefreshError("No access token in response.", response_data) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc expiry = _parse_expiry(response_data) @@ -236,7 +234,7 @@ def id_token_jwt_grant(request, token_uri, assertion): id_token = response_data["id_token"] except KeyError as caught_exc: new_exc = exceptions.RefreshError("No ID token in response.", response_data) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc payload = jwt.decode(id_token, verify=False) expiry = datetime.datetime.utcfromtimestamp(payload["exp"]) @@ -265,7 +263,7 @@ def _handle_refresh_grant_response(response_data, refresh_token): access_token = response_data["access_token"] except KeyError as caught_exc: new_exc = exceptions.RefreshError("No access token in response.", response_data) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc refresh_token = response_data.get("refresh_token", refresh_token) expiry = _parse_expiry(response_data) diff --git a/packages/google-auth/google/oauth2/_client_async.py b/packages/google-auth/google/oauth2/_client_async.py index cf51211379ee..8849023e7923 100644 --- a/packages/google-auth/google/oauth2/_client_async.py +++ b/packages/google-auth/google/oauth2/_client_async.py @@ -24,11 +24,9 @@ """ import datetime +import http.client import json - -import six -from six.moves import http_client -from six.moves import urllib +import urllib from google.auth import exceptions from google.auth import jwt @@ -85,7 +83,7 @@ async def _token_endpoint_request_no_throw( response_data = json.loads(response_body) - if response.status == http_client.OK: + if response.status == http.client.OK: break else: error_desc = response_data.get("error_description") or "" @@ -96,9 +94,9 @@ async def _token_endpoint_request_no_throw( ): retry += 1 continue - return response.status == http_client.OK, response_data + return response.status == http.client.OK, response_data - return response.status == http_client.OK, response_data + return response.status == http.client.OK, response_data async def _token_endpoint_request( @@ -161,7 +159,7 @@ async def jwt_grant(request, token_uri, assertion): access_token = response_data["access_token"] except KeyError as caught_exc: new_exc = exceptions.RefreshError("No access token in response.", response_data) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc expiry = client._parse_expiry(response_data) @@ -201,7 +199,7 @@ async def id_token_jwt_grant(request, token_uri, assertion): id_token = response_data["id_token"] except KeyError as caught_exc: new_exc = exceptions.RefreshError("No ID token in response.", response_data) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc payload = jwt.decode(id_token, verify=False) expiry = datetime.datetime.utcfromtimestamp(payload["exp"]) diff --git a/packages/google-auth/google/oauth2/_id_token_async.py b/packages/google-auth/google/oauth2/_id_token_async.py index ab681a9cb730..a4a526dc0e0a 100644 --- a/packages/google-auth/google/oauth2/_id_token_async.py +++ b/packages/google-auth/google/oauth2/_id_token_async.py @@ -58,12 +58,10 @@ .. _CacheControl: https://cachecontrol.readthedocs.io """ +import http.client import json import os -import six -from six.moves import http_client - from google.auth import environment_vars from google.auth import exceptions from google.auth import jwt @@ -88,7 +86,7 @@ async def _fetch_certs(request, certs_url): """ response = await request(certs_url, method="GET") - if response.status != http_client.OK: + if response.status != http.client.OK: raise exceptions.TransportError( "Could not fetch certificates at {}".format(certs_url) ) @@ -243,10 +241,10 @@ async def fetch_id_token(request, audience): "GOOGLE_APPLICATION_CREDENTIALS is not valid service account credentials.", caught_exc, ) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc - # 2. Try to fetch ID token from metada server if it exists. The code works for GAE and - # Cloud Run metadata server as well. + # 2. Try to fetch ID token from metada server if it exists. The code works + # for GAE and Cloud Run metadata server as well. try: from google.auth import compute_engine from google.auth.compute_engine import _metadata diff --git a/packages/google-auth/google/oauth2/_reauth_async.py b/packages/google-auth/google/oauth2/_reauth_async.py index 09e076090afb..510578bf7730 100644 --- a/packages/google-auth/google/oauth2/_reauth_async.py +++ b/packages/google-auth/google/oauth2/_reauth_async.py @@ -34,8 +34,6 @@ import sys -from six.moves import range - from google.auth import exceptions from google.oauth2 import _client from google.oauth2 import _client_async diff --git a/packages/google-auth/google/oauth2/challenges.py b/packages/google-auth/google/oauth2/challenges.py index d0b070eda670..7756a8057773 100644 --- a/packages/google-auth/google/oauth2/challenges.py +++ b/packages/google-auth/google/oauth2/challenges.py @@ -20,8 +20,6 @@ import getpass import sys -import six - from google.auth import _helpers from google.auth import exceptions @@ -44,8 +42,7 @@ def get_user_password(text): return getpass.getpass(text) -@six.add_metaclass(abc.ABCMeta) -class ReauthChallenge(object): +class ReauthChallenge(object, metaclass=abc.ABCMeta): """Base class for reauth challenges.""" @property diff --git a/packages/google-auth/google/oauth2/credentials.py b/packages/google-auth/google/oauth2/credentials.py index 158249ed5f60..98fd71b04a10 100644 --- a/packages/google-auth/google/oauth2/credentials.py +++ b/packages/google-auth/google/oauth2/credentials.py @@ -35,8 +35,6 @@ import io import json -import six - from google.auth import _cloud_sdk from google.auth import _helpers from google.auth import credentials @@ -336,7 +334,7 @@ def from_authorized_user_info(cls, info, scopes=None): ValueError: If the info is not in the expected format. """ keys_needed = set(("refresh_token", "client_id", "client_secret")) - missing = keys_needed.difference(six.iterkeys(info)) + missing = keys_needed.difference(info) if missing: raise ValueError( diff --git a/packages/google-auth/google/oauth2/id_token.py b/packages/google-auth/google/oauth2/id_token.py index 540ccd125124..25492ca6c198 100644 --- a/packages/google-auth/google/oauth2/id_token.py +++ b/packages/google-auth/google/oauth2/id_token.py @@ -55,12 +55,10 @@ .. _CacheControl: https://cachecontrol.readthedocs.io """ +import http.client import json import os -import six -from six.moves import http_client - from google.auth import environment_vars from google.auth import exceptions from google.auth import jwt @@ -97,7 +95,7 @@ def _fetch_certs(request, certs_url): """ response = request(certs_url, method="GET") - if response.status != http_client.OK: + if response.status != http.client.OK: raise exceptions.TransportError( "Could not fetch certificates at {}".format(certs_url) ) @@ -242,10 +240,10 @@ def fetch_id_token(request, audience): "GOOGLE_APPLICATION_CREDENTIALS is not valid service account credentials.", caught_exc, ) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc - # 2. Try to fetch ID token from metada server if it exists. The code works for GAE and - # Cloud Run metadata server as well. + # 2. Try to fetch ID token from metada server if it exists. The code + # works for GAE and Cloud Run metadata server as well. try: from google.auth import compute_engine from google.auth.compute_engine import _metadata diff --git a/packages/google-auth/google/oauth2/reauth.py b/packages/google-auth/google/oauth2/reauth.py index d914fe9a7ddb..fc2629e828c4 100644 --- a/packages/google-auth/google/oauth2/reauth.py +++ b/packages/google-auth/google/oauth2/reauth.py @@ -34,8 +34,6 @@ import sys -from six.moves import range - from google.auth import exceptions from google.oauth2 import _client from google.oauth2 import challenges diff --git a/packages/google-auth/google/oauth2/sts.py b/packages/google-auth/google/oauth2/sts.py index ae3c0146b114..9f2d68af3cab 100644 --- a/packages/google-auth/google/oauth2/sts.py +++ b/packages/google-auth/google/oauth2/sts.py @@ -31,10 +31,9 @@ .. _rfc8693 section 2.2.1: https://tools.ietf.org/html/rfc8693#section-2.2.1 """ +import http.client import json - -from six.moves import http_client -from six.moves import urllib +import urllib from google.oauth2 import utils @@ -146,7 +145,7 @@ def exchange_token( ) # If non-200 response received, translate to OAuthError exception. - if response.status != http_client.OK: + if response.status != http.client.OK: utils.handle_error_response(response_body) response_data = json.loads(response_body) diff --git a/packages/google-auth/google/oauth2/utils.py b/packages/google-auth/google/oauth2/utils.py index 593f03236ec8..c57833daf800 100644 --- a/packages/google-auth/google/oauth2/utils.py +++ b/packages/google-auth/google/oauth2/utils.py @@ -45,8 +45,6 @@ import enum import json -import six - from google.auth import exceptions @@ -77,8 +75,7 @@ def __init__(self, client_auth_type, client_id, client_secret=None): self.client_secret = client_secret -@six.add_metaclass(abc.ABCMeta) -class OAuthClientAuthHandler(object): +class OAuthClientAuthHandler(object, metaclass=abc.ABCMeta): """Abstract class for handling client authentication in OAuth-based operations. """ diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index 94661df319c7..d375b03d159e 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -58,7 +58,7 @@ @nox.session(python="3.7") def lint(session): session.install("flake8", "flake8-import-order", "docutils", BLACK_VERSION) - session.install(".") + session.install("-e", ".") session.run("black", "--check", *BLACK_PATHS) session.run( "flake8", @@ -94,7 +94,7 @@ def unit(session): add_constraints = ["-c", constraints_path] session.install(*(TEST_DEPENDENCIES + add_constraints)) session.install(*(ASYNC_DEPENDENCIES + add_constraints)) - session.install(".", *add_constraints) + session.install("-e", ".", *add_constraints) session.run( "pytest", f"--junitxml=unit_{session.python}_sponge_log.xml", @@ -107,25 +107,11 @@ def unit(session): ) -@nox.session(python=["2.7"]) -def unit_prev_versions(session): - session.install(".") - session.install(*TEST_DEPENDENCIES) - session.run( - "pytest", - f"--junitxml=unit_{session.python}_sponge_log.xml", - "--cov=google.auth", - "--cov=google.oauth2", - "--cov=tests", - "tests", - ) - - @nox.session(python="3.7") def cover(session): session.install(*TEST_DEPENDENCIES) session.install(*(ASYNC_DEPENDENCIES)) - session.install(".") + session.install("-e", ".") session.run( "pytest", "--cov=google.auth", @@ -144,7 +130,7 @@ def docgen(session): session.env["SPHINX_APIDOC_OPTIONS"] = "members,inherited-members,show-inheritance" session.install(*TEST_DEPENDENCIES) session.install("sphinx") - session.install(".") + session.install("-e", ".") session.run("rm", "-r", "docs/reference") session.run( "sphinx-apidoc", @@ -184,7 +170,7 @@ def docs(session): def pypy(session): session.install(*TEST_DEPENDENCIES) session.install(*ASYNC_DEPENDENCIES) - session.install(".") + session.install("-e", ".") session.run( "pytest", f"--junitxml=unit_{session.python}_sponge_log.xml", diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index a9bfc9738a20..54a2c900bef3 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -20,23 +20,16 @@ DEPENDENCIES = ( - "cachetools>=2.0.0,<5.0", - "pyasn1-modules>=0.2.1", - # rsa==4.5 is the last version to support 2.7 - # https://github.com/sybrenstuvel/python-rsa/issues/152#issuecomment-643470233 - 'rsa<4.6; python_version < "3.6"', - 'rsa>=3.1.4,<5; python_version >= "3.6"', - "setuptools>=40.3.0", - "six>=1.9.0", + "cachetools >= 2.0.0, < 5.0", + "pyasn1-modules >= 0.2.1", + "rsa >= 3.1.4, < 5", + "setuptools >= 40.3.0", ) extras = { - "aiohttp": [ - "aiohttp >= 3.6.2, < 4.0.0dev; python_version>='3.6'", - "requests >= 2.20.0, < 3.0.0dev", - ], - "pyopenssl": "pyopenssl>=20.0.0", - "reauth": "pyu2f>=0.1.5", + "aiohttp": ["aiohttp >= 3.6.2, < 4.0.0dev", "requests >= 2.20.0, < 3.0.0dev"], + "pyopenssl": "pyopenssl >= 20.0.0", + "reauth": "pyu2f >= 0.1.5", } with io.open("README.rst", "r") as fh: @@ -61,12 +54,10 @@ namespace_packages=("google",), install_requires=DEPENDENCIES, extras_require=extras, - python_requires=">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*", + python_requires=">= 3.6", license="Apache 2.0", keywords="google auth oauth client", classifiers=[ - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", diff --git a/packages/google-auth/system_tests/noxfile.py b/packages/google-auth/system_tests/noxfile.py index 11c398b97423..33e49c37fba8 100644 --- a/packages/google-auth/system_tests/noxfile.py +++ b/packages/google-auth/system_tests/noxfile.py @@ -171,7 +171,7 @@ def configure_cloud_sdk(session, application_default_credentials, project=False) TEST_DEPENDENCIES_ASYNC = ["aiohttp", "pytest-asyncio", "nest-asyncio"] TEST_DEPENDENCIES_SYNC = ["pytest", "requests", "mock"] PYTHON_VERSIONS_ASYNC = ["3.7"] -PYTHON_VERSIONS_SYNC = ["2.7", "3.7"] +PYTHON_VERSIONS_SYNC = ["3.7"] def default(session, *test_paths): @@ -287,50 +287,6 @@ def compute_engine(session): ) -@nox.session(python=["2.7"]) -def app_engine(session): - if SKIP_GAE_TEST_ENV in os.environ: - session.log("Skipping App Engine tests.") - return - - session.install(LIBRARY_DIR) - # Unlike the default tests above, the App Engine system test require a - # 'real' gcloud sdk installation that is configured to deploy to an - # app engine project. - # Grab the project ID from the cloud sdk. - project_id = ( - subprocess.check_output( - ["gcloud", "config", "list", "project", "--format", "value(core.project)"] - ) - .decode("utf-8") - .strip() - ) - - if not project_id: - session.error( - "The Cloud SDK must be installed and configured to deploy to App " "Engine." - ) - - application_url = GAE_APP_URL_TMPL.format(GAE_TEST_APP_SERVICE, project_id) - - # Vendor in the test application's dependencies - session.chdir(os.path.join(HERE, "system_tests_sync/app_engine_test_app")) - session.install(*TEST_DEPENDENCIES_SYNC) - session.run( - "pip", "install", "--target", "lib", "-r", "requirements.txt", silent=True - ) - - # Deploy the application. - session.run("gcloud", "app", "deploy", "-q", "app.yaml") - - # Run the tests - session.env["TEST_APP_URL"] = application_url - session.chdir(HERE) - default( - session, "system_tests_sync/test_app_engine.py", - ) - - @nox.session(python=PYTHON_VERSIONS_SYNC) def grpc(session): session.install(LIBRARY_DIR) diff --git a/packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/.gitignore b/packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/.gitignore deleted file mode 100644 index a65b41774ad5..000000000000 --- a/packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/.gitignore +++ /dev/null @@ -1 +0,0 @@ -lib diff --git a/packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/app.yaml b/packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/app.yaml deleted file mode 100644 index 872efb37b6c9..000000000000 --- a/packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/app.yaml +++ /dev/null @@ -1,12 +0,0 @@ -api_version: 1 -service: google-auth-system-tests -runtime: python27 -threadsafe: true - -handlers: -- url: .* - script: main.app - -libraries: -- name: ssl - version: 2.7.11 diff --git a/packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/appengine_config.py b/packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/appengine_config.py deleted file mode 100644 index 5a832ac6fd6f..000000000000 --- a/packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/appengine_config.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright 2016 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from google.appengine.ext import vendor - -# Add any libraries installed in the "lib" folder. -vendor.add("lib") - - -# Patch os.path.expanduser. This should be fixed in GAE -# versions released after Nov 2016. -import os.path - - -def patched_expanduser(path): - return path - - -os.path.expanduser = patched_expanduser diff --git a/packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/main.py b/packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/main.py deleted file mode 100644 index 33e61d07b532..000000000000 --- a/packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/main.py +++ /dev/null @@ -1,133 +0,0 @@ -# Copyright 2016 Google LLC All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""App Engine standard application that runs basic system tests for -google.auth.app_engine. - -This application has to run tests manually instead of using pytest because -pytest currently doesn't work on App Engine standard. -""" - -import contextlib -import json -import sys -from StringIO import StringIO -import traceback - -from google.appengine.api import app_identity -import google.auth -from google.auth import _helpers -from google.auth import app_engine -import google.auth.transport.urllib3 -import urllib3.contrib.appengine -import webapp2 - -FAILED_TEST_TMPL = """ -Test {} failed: {} - -Stacktrace: -{} - -Captured output: -{} -""" -TOKEN_INFO_URL = "https://www.googleapis.com/oauth2/v3/tokeninfo" -EMAIL_SCOPE = "https://www.googleapis.com/auth/userinfo.email" -HTTP = urllib3.contrib.appengine.AppEngineManager() -HTTP_REQUEST = google.auth.transport.urllib3.Request(HTTP) - - -def test_credentials(): - credentials = app_engine.Credentials() - scoped_credentials = credentials.with_scopes([EMAIL_SCOPE]) - - scoped_credentials.refresh(None) - - assert scoped_credentials.valid - assert scoped_credentials.token is not None - - # Get token info and verify scope - url = _helpers.update_query( - TOKEN_INFO_URL, {"access_token": scoped_credentials.token} - ) - response = HTTP_REQUEST(url=url, method="GET") - token_info = json.loads(response.data.decode("utf-8")) - - assert token_info["scope"] == EMAIL_SCOPE - - -def test_default(): - credentials, project_id = google.auth.default() - - assert isinstance(credentials, app_engine.Credentials) - assert project_id == app_identity.get_application_id() - - -@contextlib.contextmanager -def capture(): - """Context manager that captures stderr and stdout.""" - oldout, olderr = sys.stdout, sys.stderr - try: - out = StringIO() - sys.stdout, sys.stderr = out, out - yield out - finally: - sys.stdout, sys.stderr = oldout, olderr - - -def run_test_func(func): - with capture() as capsys: - try: - func() - return True, "" - except Exception as exc: - output = FAILED_TEST_TMPL.format( - func.func_name, exc, traceback.format_exc(), capsys.getvalue() - ) - return False, output - - -def run_tests(): - """Runs all tests. - - Returns: - Tuple[bool, str]: A tuple containing True if all tests pass, False - otherwise, and any captured output from the tests. - """ - status = True - output = "" - - tests = (test_credentials, test_default) - - for test in tests: - test_status, test_output = run_test_func(test) - status = status and test_status - output += test_output - - return status, output - - -class MainHandler(webapp2.RequestHandler): - def get(self): - self.response.headers["content-type"] = "text/plain" - - status, output = run_tests() - - if not status: - self.response.status = 500 - - self.response.write(output) - - -app = webapp2.WSGIApplication([("/", MainHandler)], debug=True) diff --git a/packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/requirements.txt b/packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/requirements.txt deleted file mode 100644 index bd5c476ab2b4..000000000000 --- a/packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -urllib3 -# Relative path to google-auth-python's source. -../../.. diff --git a/packages/google-auth/system_tests/system_tests_sync/test_app_engine.py b/packages/google-auth/system_tests/system_tests_sync/test_app_engine.py deleted file mode 100644 index 45a1989a4ad8..000000000000 --- a/packages/google-auth/system_tests/system_tests_sync/test_app_engine.py +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright 2016 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os - -TEST_APP_URL = os.environ["TEST_APP_URL"] - - -def test_live_application(http_request): - response = http_request(method="GET", url=TEST_APP_URL) - assert response.status == 200, response.data.decode("utf-8") diff --git a/packages/google-auth/system_tests/system_tests_sync/test_external_accounts.py b/packages/google-auth/system_tests/system_tests_sync/test_external_accounts.py index e24c7b40a54a..c2855a2c3832 100644 --- a/packages/google-auth/system_tests/system_tests_sync/test_external_accounts.py +++ b/packages/google-auth/system_tests/system_tests_sync/test_external_accounts.py @@ -32,19 +32,21 @@ # original service account key. +from http.server import BaseHTTPRequestHandler +from http.server import HTTPServer import json import os import socket +import sys from tempfile import NamedTemporaryFile import threading -import sys +import pytest +from mock import patch + import google.auth from googleapiclient import discovery -from six.moves import BaseHTTPServer from google.oauth2 import service_account -import pytest -from mock import patch # Populate values from the output of scripts/setup_external_accounts.sh. _AUDIENCE_OIDC = "//iam.googleapis.com/projects/79992041559/locations/global/workloadIdentityPools/pool-73wslmxn/providers/oidc-73wslmxn" @@ -175,7 +177,7 @@ def test_file_based_external_account( # This test makes sure that setting up an http server to provide credentials # works to allow access to Google resources. def test_url_based_external_account(dns_access, oidc_credentials, service_account_info): - class TestResponseHandler(BaseHTTPServer.BaseHTTPRequestHandler): + class TestResponseHandler(BaseHTTPRequestHandler): def do_GET(self): if self.headers["my-header"] != "expected-value": self.send_response(400) @@ -199,7 +201,7 @@ def do_GET(self): json.dumps({"access_token": oidc_credentials.token}).encode("utf-8") ) - class TestHTTPServer(BaseHTTPServer.HTTPServer, object): + class TestHTTPServer(HTTPServer, object): def __init__(self): self.port = self._find_open_port() super(TestHTTPServer, self).__init__(("", self.port), TestResponseHandler) diff --git a/packages/google-auth/testing/constraints-3.6.txt b/packages/google-auth/testing/constraints-3.6.txt index ad6f5984605b..6c4dd2e8cbc7 100644 --- a/packages/google-auth/testing/constraints-3.6.txt +++ b/packages/google-auth/testing/constraints-3.6.txt @@ -8,7 +8,6 @@ cachetools==2.0.0 pyasn1-modules==0.2.1 setuptools==40.3.0 -six==1.9.0 rsa==3.1.4 aiohttp==3.6.2 requests==2.20.0 diff --git a/packages/google-auth/tests/compute_engine/test__metadata.py b/packages/google-auth/tests/compute_engine/test__metadata.py index 852822dc0c44..0bb07b007af3 100644 --- a/packages/google-auth/tests/compute_engine/test__metadata.py +++ b/packages/google-auth/tests/compute_engine/test__metadata.py @@ -13,13 +13,13 @@ # limitations under the License. import datetime +import http.client +import importlib import json import os import mock import pytest -from six.moves import http_client -from six.moves import reload_module from google.auth import _helpers from google.auth import environment_vars @@ -30,7 +30,7 @@ PATH = "instance/service-accounts/default" -def make_request(data, status=http_client.OK, headers=None, retry=False): +def make_request(data, status=http.client.OK, headers=None, retry=False): response = mock.create_autospec(transport.Response, instance=True) response.status = status response.data = _helpers.to_bytes(data) @@ -90,13 +90,13 @@ def test_ping_success_custom_root(): fake_ip = "1.2.3.4" os.environ[environment_vars.GCE_METADATA_IP] = fake_ip - reload_module(_metadata) + importlib.reload(_metadata) try: assert _metadata.ping(request) finally: del os.environ[environment_vars.GCE_METADATA_IP] - reload_module(_metadata) + importlib.reload(_metadata) request.assert_called_once_with( method="GET", @@ -203,13 +203,13 @@ def test_get_success_custom_root_new_variable(): fake_root = "another.metadata.service" os.environ[environment_vars.GCE_METADATA_HOST] = fake_root - reload_module(_metadata) + importlib.reload(_metadata) try: _metadata.get(request, PATH) finally: del os.environ[environment_vars.GCE_METADATA_HOST] - reload_module(_metadata) + importlib.reload(_metadata) request.assert_called_once_with( method="GET", @@ -223,13 +223,13 @@ def test_get_success_custom_root_old_variable(): fake_root = "another.metadata.service" os.environ[environment_vars.GCE_METADATA_ROOT] = fake_root - reload_module(_metadata) + importlib.reload(_metadata) try: _metadata.get(request, PATH) finally: del os.environ[environment_vars.GCE_METADATA_ROOT] - reload_module(_metadata) + importlib.reload(_metadata) request.assert_called_once_with( method="GET", @@ -239,7 +239,7 @@ def test_get_success_custom_root_old_variable(): def test_get_failure(): - request = make_request("Metadata error", status=http_client.NOT_FOUND) + request = make_request("Metadata error", status=http.client.NOT_FOUND) with pytest.raises(exceptions.TransportError) as excinfo: _metadata.get(request, PATH) diff --git a/packages/google-auth/tests/crypt/test__cryptography_rsa.py b/packages/google-auth/tests/crypt/test__cryptography_rsa.py index dbf07c7805b3..41dfc3693362 100644 --- a/packages/google-auth/tests/crypt/test__cryptography_rsa.py +++ b/packages/google-auth/tests/crypt/test__cryptography_rsa.py @@ -60,7 +60,7 @@ class TestRSAVerifier(object): - def test_verify_success(self): + def test_verify_bytes_success(self): to_sign = b"foo" signer = _cryptography_rsa.RSASigner.from_string(PRIVATE_KEY_BYTES) actual_signature = signer.sign(to_sign) @@ -68,8 +68,8 @@ def test_verify_success(self): verifier = _cryptography_rsa.RSAVerifier.from_string(PUBLIC_KEY_BYTES) assert verifier.verify(to_sign, actual_signature) - def test_verify_unicode_success(self): - to_sign = u"foo" + def test_verify_text_success(self): + to_sign = "foo" signer = _cryptography_rsa.RSASigner.from_string(PRIVATE_KEY_BYTES) actual_signature = signer.sign(to_sign) diff --git a/packages/google-auth/tests/crypt/test__python_rsa.py b/packages/google-auth/tests/crypt/test__python_rsa.py index 886ee55a2388..9ef29ee15c1e 100644 --- a/packages/google-auth/tests/crypt/test__python_rsa.py +++ b/packages/google-auth/tests/crypt/test__python_rsa.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import io import json import os @@ -19,7 +20,6 @@ from pyasn1_modules import pem import pytest import rsa -import six from google.auth import _helpers from google.auth.crypt import _python_rsa @@ -63,7 +63,7 @@ class TestRSAVerifier(object): - def test_verify_success(self): + def test_verify_bytes_success(self): to_sign = b"foo" signer = _python_rsa.RSASigner.from_string(PRIVATE_KEY_BYTES) actual_signature = signer.sign(to_sign) @@ -71,8 +71,8 @@ def test_verify_success(self): verifier = _python_rsa.RSAVerifier.from_string(PUBLIC_KEY_BYTES) assert verifier.verify(to_sign, actual_signature) - def test_verify_unicode_success(self): - to_sign = u"foo" + def test_verify_text_success(self): + to_sign = "foo" signer = _python_rsa.RSASigner.from_string(PRIVATE_KEY_BYTES) actual_signature = signer.sign(to_sign) @@ -141,7 +141,7 @@ def test_from_string_pkcs8(self): def test_from_string_pkcs8_extra_bytes(self): key_bytes = PKCS8_KEY_BYTES _, pem_bytes = pem.readPemBlocksFromFile( - six.StringIO(_helpers.from_bytes(key_bytes)), _python_rsa._PKCS8_MARKER + io.StringIO(_helpers.from_bytes(key_bytes)), _python_rsa._PKCS8_MARKER ) key_info, remaining = None, "extra" diff --git a/packages/google-auth/tests/crypt/test_es256.py b/packages/google-auth/tests/crypt/test_es256.py index 5bb9050cd8d7..720f74ca2210 100644 --- a/packages/google-auth/tests/crypt/test_es256.py +++ b/packages/google-auth/tests/crypt/test_es256.py @@ -50,7 +50,7 @@ class TestES256Verifier(object): - def test_verify_success(self): + def test_verify_bytes_success(self): to_sign = b"foo" signer = es256.ES256Signer.from_string(PRIVATE_KEY_BYTES) actual_signature = signer.sign(to_sign) @@ -58,8 +58,8 @@ def test_verify_success(self): verifier = es256.ES256Verifier.from_string(PUBLIC_KEY_BYTES) assert verifier.verify(to_sign, actual_signature) - def test_verify_unicode_success(self): - to_sign = u"foo" + def test_verify_text_success(self): + to_sign = "foo" signer = es256.ES256Signer.from_string(PRIVATE_KEY_BYTES) actual_signature = signer.sign(to_sign) diff --git a/packages/google-auth/tests/oauth2/test__client.py b/packages/google-auth/tests/oauth2/test__client.py index 54686df594bd..690a87bc47e1 100644 --- a/packages/google-auth/tests/oauth2/test__client.py +++ b/packages/google-auth/tests/oauth2/test__client.py @@ -13,14 +13,13 @@ # limitations under the License. import datetime +import http.client import json import os +import urllib import mock import pytest -import six -from six.moves import http_client -from six.moves import urllib from google.auth import _helpers from google.auth import crypt @@ -75,7 +74,7 @@ def test__parse_expiry_none(): assert _client._parse_expiry({}) is None -def make_request(response_data, status=http_client.OK): +def make_request(response_data, status=http.client.OK): response = mock.create_autospec(transport.Response, instance=True) response.status = status response.data = json.dumps(response_data).encode("utf-8") @@ -130,7 +129,7 @@ def test__token_endpoint_request_use_json(): def test__token_endpoint_request_error(): - request = make_request({}, status=http_client.BAD_REQUEST) + request = make_request({}, status=http.client.BAD_REQUEST) with pytest.raises(exceptions.RefreshError): _client._token_endpoint_request(request, "http://example.com", {}) @@ -138,7 +137,7 @@ def test__token_endpoint_request_error(): def test__token_endpoint_request_internal_failure_error(): request = make_request( - {"error_description": "internal_failure"}, status=http_client.BAD_REQUEST + {"error_description": "internal_failure"}, status=http.client.BAD_REQUEST ) with pytest.raises(exceptions.RefreshError): @@ -147,7 +146,7 @@ def test__token_endpoint_request_internal_failure_error(): ) request = make_request( - {"error": "internal_failure"}, status=http_client.BAD_REQUEST + {"error": "internal_failure"}, status=http.client.BAD_REQUEST ) with pytest.raises(exceptions.RefreshError): @@ -160,7 +159,7 @@ def verify_request_params(request, params): request_body = request.call_args[1]["body"].decode("utf-8") request_params = urllib.parse.parse_qs(request_body) - for key, value in six.iteritems(params): + for key, value in params.items(): assert request_params[key][0] == value diff --git a/packages/google-auth/tests/oauth2/test_sts.py b/packages/google-auth/tests/oauth2/test_sts.py index e8e008df5da8..b516c8a5b547 100644 --- a/packages/google-auth/tests/oauth2/test_sts.py +++ b/packages/google-auth/tests/oauth2/test_sts.py @@ -12,12 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. +import http.client import json +import urllib import mock import pytest -from six.moves import http_client -from six.moves import urllib from google.auth import exceptions from google.auth import transport @@ -67,7 +67,7 @@ def make_client(cls, client_auth=None): return sts.Client(cls.TOKEN_EXCHANGE_ENDPOINT, client_auth) @classmethod - def make_mock_request(cls, data, status=http_client.OK): + def make_mock_request(cls, data, status=http.client.OK): response = mock.create_autospec(transport.Response, instance=True) response.status = status response.data = json.dumps(data).encode("utf-8") @@ -110,7 +110,7 @@ def test_exchange_token_full_success_without_auth(self): "options": urllib.parse.quote(json.dumps(self.ADDON_OPTIONS)), } request = self.make_mock_request( - status=http_client.OK, data=self.SUCCESS_RESPONSE + status=http.client.OK, data=self.SUCCESS_RESPONSE ) response = client.exchange_token( @@ -145,7 +145,7 @@ def test_exchange_token_partial_success_without_auth(self): "subject_token_type": self.SUBJECT_TOKEN_TYPE, } request = self.make_mock_request( - status=http_client.OK, data=self.SUCCESS_RESPONSE + status=http.client.OK, data=self.SUCCESS_RESPONSE ) response = client.exchange_token( @@ -165,7 +165,7 @@ def test_exchange_token_non200_without_auth(self): """ client = self.make_client() request = self.make_mock_request( - status=http_client.BAD_REQUEST, data=self.ERROR_RESPONSE + status=http.client.BAD_REQUEST, data=self.ERROR_RESPONSE ) with pytest.raises(exceptions.OAuthError) as excinfo: @@ -209,7 +209,7 @@ def test_exchange_token_full_success_with_basic_auth(self): "options": urllib.parse.quote(json.dumps(self.ADDON_OPTIONS)), } request = self.make_mock_request( - status=http_client.OK, data=self.SUCCESS_RESPONSE + status=http.client.OK, data=self.SUCCESS_RESPONSE ) response = client.exchange_token( @@ -247,7 +247,7 @@ def test_exchange_token_partial_success_with_basic_auth(self): "subject_token_type": self.SUBJECT_TOKEN_TYPE, } request = self.make_mock_request( - status=http_client.OK, data=self.SUCCESS_RESPONSE + status=http.client.OK, data=self.SUCCESS_RESPONSE ) response = client.exchange_token( @@ -268,7 +268,7 @@ def test_exchange_token_non200_with_basic_auth(self): """ client = self.make_client(self.CLIENT_AUTH_BASIC) request = self.make_mock_request( - status=http_client.BAD_REQUEST, data=self.ERROR_RESPONSE + status=http.client.BAD_REQUEST, data=self.ERROR_RESPONSE ) with pytest.raises(exceptions.OAuthError) as excinfo: @@ -313,7 +313,7 @@ def test_exchange_token_full_success_with_reqbody_auth(self): "client_secret": CLIENT_SECRET, } request = self.make_mock_request( - status=http_client.OK, data=self.SUCCESS_RESPONSE + status=http.client.OK, data=self.SUCCESS_RESPONSE ) response = client.exchange_token( @@ -350,7 +350,7 @@ def test_exchange_token_partial_success_with_reqbody_auth(self): "client_secret": CLIENT_SECRET, } request = self.make_mock_request( - status=http_client.OK, data=self.SUCCESS_RESPONSE + status=http.client.OK, data=self.SUCCESS_RESPONSE ) response = client.exchange_token( @@ -371,7 +371,7 @@ def test_exchange_token_non200_with_reqbody_auth(self): """ client = self.make_client(self.CLIENT_AUTH_REQUEST_BODY) request = self.make_mock_request( - status=http_client.BAD_REQUEST, data=self.ERROR_RESPONSE + status=http.client.BAD_REQUEST, data=self.ERROR_RESPONSE ) with pytest.raises(exceptions.OAuthError) as excinfo: diff --git a/packages/google-auth/tests/test__helpers.py b/packages/google-auth/tests/test__helpers.py index 0c0bad2d2fdc..906cf126ef95 100644 --- a/packages/google-auth/tests/test__helpers.py +++ b/packages/google-auth/tests/test__helpers.py @@ -13,9 +13,9 @@ # limitations under the License. import datetime +import urllib import pytest -from six.moves import urllib from google.auth import _helpers @@ -65,8 +65,8 @@ def test_to_bytes_with_bytes(): assert _helpers.to_bytes(value) == value -def test_to_bytes_with_unicode(): - value = u"string-val" +def test_to_bytes_with_text(): + value = "string-val" encoded_value = b"string-val" assert _helpers.to_bytes(value) == encoded_value @@ -76,14 +76,14 @@ def test_to_bytes_with_nonstring_type(): _helpers.to_bytes(object()) -def test_from_bytes_with_unicode(): - value = u"bytes-val" +def test_from_bytes_with_text(): + value = "bytes-val" assert _helpers.from_bytes(value) == value def test_from_bytes_with_bytes(): value = b"string-val" - decoded_value = u"string-val" + decoded_value = "string-val" assert _helpers.from_bytes(value) == decoded_value diff --git a/packages/google-auth/tests/test__oauth2client.py b/packages/google-auth/tests/test__oauth2client.py index 6b1112b50e75..aa06eced2f48 100644 --- a/packages/google-auth/tests/test__oauth2client.py +++ b/packages/google-auth/tests/test__oauth2client.py @@ -13,6 +13,7 @@ # limitations under the License. import datetime +import importlib import os import sys @@ -21,7 +22,6 @@ import oauth2client.contrib.gce import oauth2client.service_account import pytest -from six.moves import reload_module from google.auth import _oauth2client @@ -152,19 +152,19 @@ def test_convert_not_found(): @pytest.fixture def reset__oauth2client_module(): """Reloads the _oauth2client module after a test.""" - reload_module(_oauth2client) + importlib.reload(_oauth2client) def test_import_has_app_engine( mock_oauth2client_gae_imports, reset__oauth2client_module ): - reload_module(_oauth2client) + importlib.reload(_oauth2client) assert _oauth2client._HAS_APPENGINE def test_import_without_oauth2client(monkeypatch, reset__oauth2client_module): monkeypatch.setitem(sys.modules, "oauth2client", None) with pytest.raises(ImportError) as excinfo: - reload_module(_oauth2client) + importlib.reload(_oauth2client) assert excinfo.match("oauth2client") diff --git a/packages/google-auth/tests/test__service_account_info.py b/packages/google-auth/tests/test__service_account_info.py index 13b2f85a2944..fd2d8c8be4ef 100644 --- a/packages/google-auth/tests/test__service_account_info.py +++ b/packages/google-auth/tests/test__service_account_info.py @@ -16,7 +16,6 @@ import os import pytest -import six from google.auth import _service_account_info from google.auth import crypt @@ -55,7 +54,7 @@ def test_from_dict_bad_format(): def test_from_filename(): info, signer = _service_account_info.from_filename(SERVICE_ACCOUNT_JSON_FILE) - for key, value in six.iteritems(SERVICE_ACCOUNT_INFO): + for key, value in SERVICE_ACCOUNT_INFO.items(): assert info[key] == value assert isinstance(signer, crypt.RSASigner) diff --git a/packages/google-auth/tests/test_aws.py b/packages/google-auth/tests/test_aws.py index 9ca08d5b2cdc..86594376ef06 100644 --- a/packages/google-auth/tests/test_aws.py +++ b/packages/google-auth/tests/test_aws.py @@ -13,12 +13,12 @@ # limitations under the License. import datetime +import http.client import json +import urllib import mock import pytest -from six.moves import http_client -from six.moves import urllib from google.auth import _helpers from google.auth import aws @@ -952,11 +952,11 @@ def test_retrieve_subject_token_success_temp_creds_no_environment_vars( self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ" ) request = self.make_mock_request( - region_status=http_client.OK, + region_status=http.client.OK, region_name=self.AWS_REGION, - role_status=http_client.OK, + role_status=http.client.OK, role_name=self.AWS_ROLE, - security_credentials_status=http_client.OK, + security_credentials_status=http.client.OK, security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE, ) credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE) @@ -987,9 +987,9 @@ def test_retrieve_subject_token_success_temp_creds_no_environment_vars( # Retrieve subject_token again. Region should not be queried again. new_request = self.make_mock_request( - role_status=http_client.OK, + role_status=http.client.OK, role_name=self.AWS_ROLE, - security_credentials_status=http_client.OK, + security_credentials_status=http.client.OK, security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE, ) @@ -1020,11 +1020,11 @@ def test_retrieve_subject_token_success_permanent_creds_no_environment_vars( self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ" ) request = self.make_mock_request( - region_status=http_client.OK, + region_status=http.client.OK, region_name=self.AWS_REGION, - role_status=http_client.OK, + role_status=http.client.OK, role_name=self.AWS_ROLE, - security_credentials_status=http_client.OK, + security_credentials_status=http.client.OK, security_credentials_data=security_creds_response, ) credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE) @@ -1136,7 +1136,7 @@ def test_retrieve_subject_token_success_environment_vars_except_region( ) # Region will be queried since it is not found in envvars. request = self.make_mock_request( - region_status=http_client.OK, region_name=self.AWS_REGION + region_status=http.client.OK, region_name=self.AWS_REGION ) credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE) @@ -1152,7 +1152,7 @@ def test_retrieve_subject_token_success_environment_vars_except_region( def test_retrieve_subject_token_error_determining_aws_region(self): # Simulate error in retrieving the AWS region. - request = self.make_mock_request(region_status=http_client.BAD_REQUEST) + request = self.make_mock_request(region_status=http.client.BAD_REQUEST) credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE) with pytest.raises(exceptions.RefreshError) as excinfo: @@ -1163,9 +1163,9 @@ def test_retrieve_subject_token_error_determining_aws_region(self): def test_retrieve_subject_token_error_determining_aws_role(self): # Simulate error in retrieving the AWS role name. request = self.make_mock_request( - region_status=http_client.OK, + region_status=http.client.OK, region_name=self.AWS_REGION, - role_status=http_client.BAD_REQUEST, + role_status=http.client.BAD_REQUEST, ) credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE) @@ -1180,7 +1180,7 @@ def test_retrieve_subject_token_error_determining_security_creds_url(self): credential_source = self.CREDENTIAL_SOURCE.copy() credential_source.pop("url") request = self.make_mock_request( - region_status=http_client.OK, region_name=self.AWS_REGION + region_status=http.client.OK, region_name=self.AWS_REGION ) credentials = self.make_credentials(credential_source=credential_source) @@ -1194,11 +1194,11 @@ def test_retrieve_subject_token_error_determining_security_creds_url(self): def test_retrieve_subject_token_error_determining_aws_security_creds(self): # Simulate error in retrieving the AWS security credentials. request = self.make_mock_request( - region_status=http_client.OK, + region_status=http.client.OK, region_name=self.AWS_REGION, - role_status=http_client.OK, + role_status=http.client.OK, role_name=self.AWS_ROLE, - security_credentials_status=http_client.BAD_REQUEST, + security_credentials_status=http.client.BAD_REQUEST, ) credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE) @@ -1232,13 +1232,13 @@ def test_refresh_success_without_impersonation_ignore_default_scopes(self, utcno "subject_token_type": SUBJECT_TOKEN_TYPE, } request = self.make_mock_request( - region_status=http_client.OK, + region_status=http.client.OK, region_name=self.AWS_REGION, - role_status=http_client.OK, + role_status=http.client.OK, role_name=self.AWS_ROLE, - security_credentials_status=http_client.OK, + security_credentials_status=http.client.OK, security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE, - token_status=http_client.OK, + token_status=http.client.OK, token_data=self.SUCCESS_RESPONSE, ) credentials = self.make_credentials( @@ -1288,13 +1288,13 @@ def test_refresh_success_without_impersonation_use_default_scopes(self, utcnow): "subject_token_type": SUBJECT_TOKEN_TYPE, } request = self.make_mock_request( - region_status=http_client.OK, + region_status=http.client.OK, region_name=self.AWS_REGION, - role_status=http_client.OK, + role_status=http.client.OK, role_name=self.AWS_ROLE, - security_credentials_status=http_client.OK, + security_credentials_status=http.client.OK, security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE, - token_status=http_client.OK, + token_status=http.client.OK, token_data=self.SUCCESS_RESPONSE, ) credentials = self.make_credentials( @@ -1362,15 +1362,15 @@ def test_refresh_success_with_impersonation_ignore_default_scopes(self, utcnow): "lifetime": "3600s", } request = self.make_mock_request( - region_status=http_client.OK, + region_status=http.client.OK, region_name=self.AWS_REGION, - role_status=http_client.OK, + role_status=http.client.OK, role_name=self.AWS_ROLE, - security_credentials_status=http_client.OK, + security_credentials_status=http.client.OK, security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE, - token_status=http_client.OK, + token_status=http.client.OK, token_data=self.SUCCESS_RESPONSE, - impersonation_status=http_client.OK, + impersonation_status=http.client.OK, impersonation_data=impersonation_response, ) credentials = self.make_credentials( @@ -1446,15 +1446,15 @@ def test_refresh_success_with_impersonation_use_default_scopes(self, utcnow): "lifetime": "3600s", } request = self.make_mock_request( - region_status=http_client.OK, + region_status=http.client.OK, region_name=self.AWS_REGION, - role_status=http_client.OK, + role_status=http.client.OK, role_name=self.AWS_ROLE, - security_credentials_status=http_client.OK, + security_credentials_status=http.client.OK, security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE, - token_status=http_client.OK, + token_status=http.client.OK, token_data=self.SUCCESS_RESPONSE, - impersonation_status=http_client.OK, + impersonation_status=http.client.OK, impersonation_data=impersonation_response, ) credentials = self.make_credentials( @@ -1488,7 +1488,7 @@ def test_refresh_success_with_impersonation_use_default_scopes(self, utcnow): assert credentials.default_scopes == SCOPES def test_refresh_with_retrieve_subject_token_error(self): - request = self.make_mock_request(region_status=http_client.BAD_REQUEST) + request = self.make_mock_request(region_status=http.client.BAD_REQUEST) credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE) with pytest.raises(exceptions.RefreshError) as excinfo: diff --git a/packages/google-auth/tests/test_external_account.py b/packages/google-auth/tests/test_external_account.py index 7390fb980f2d..e8297dab62af 100644 --- a/packages/google-auth/tests/test_external_account.py +++ b/packages/google-auth/tests/test_external_account.py @@ -13,12 +13,12 @@ # limitations under the License. import datetime +import http.client import json +import urllib import mock import pytest -from six.moves import http_client -from six.moves import urllib from google.auth import _helpers from google.auth import exceptions @@ -149,7 +149,7 @@ def make_credentials( @classmethod def make_mock_request( cls, - status=http_client.OK, + status=http.client.OK, data=None, impersonation_status=None, impersonation_data=None, @@ -474,7 +474,7 @@ def test_refresh_without_client_auth_success(self, unused_utcnow): "subject_token": "subject_token_0", "subject_token_type": self.SUBJECT_TOKEN_TYPE, } - request = self.make_mock_request(status=http_client.OK, data=response) + request = self.make_mock_request(status=http.client.OK, data=response) credentials = self.make_credentials() credentials.refresh(request) @@ -519,9 +519,9 @@ def test_refresh_impersonation_without_client_auth_success(self): # Initialize mock request to handle token exchange and service account # impersonation request. request = self.make_mock_request( - status=http_client.OK, + status=http.client.OK, data=token_response, - impersonation_status=http_client.OK, + impersonation_status=http.client.OK, impersonation_data=impersonation_response, ) # Initialize credentials with service account impersonation. @@ -562,7 +562,7 @@ def test_refresh_without_client_auth_success_explicit_user_scopes_ignore_default "subject_token_type": self.SUBJECT_TOKEN_TYPE, } request = self.make_mock_request( - status=http_client.OK, data=self.SUCCESS_RESPONSE + status=http.client.OK, data=self.SUCCESS_RESPONSE ) credentials = self.make_credentials( scopes=["scope1", "scope2"], @@ -590,7 +590,7 @@ def test_refresh_without_client_auth_success_explicit_default_scopes_only(self): "subject_token_type": self.SUBJECT_TOKEN_TYPE, } request = self.make_mock_request( - status=http_client.OK, data=self.SUCCESS_RESPONSE + status=http.client.OK, data=self.SUCCESS_RESPONSE ) credentials = self.make_credentials( scopes=None, @@ -608,7 +608,7 @@ def test_refresh_without_client_auth_success_explicit_default_scopes_only(self): def test_refresh_without_client_auth_error(self): request = self.make_mock_request( - status=http_client.BAD_REQUEST, data=self.ERROR_RESPONSE + status=http.client.BAD_REQUEST, data=self.ERROR_RESPONSE ) credentials = self.make_credentials() @@ -623,9 +623,9 @@ def test_refresh_without_client_auth_error(self): def test_refresh_impersonation_without_client_auth_error(self): request = self.make_mock_request( - status=http_client.OK, + status=http.client.OK, data=self.SUCCESS_RESPONSE, - impersonation_status=http_client.BAD_REQUEST, + impersonation_status=http.client.BAD_REQUEST, impersonation_data=self.IMPERSONATION_ERROR_RESPONSE, ) credentials = self.make_credentials( @@ -653,7 +653,7 @@ def test_refresh_with_client_auth_success(self): "subject_token_type": self.SUBJECT_TOKEN_TYPE, } request = self.make_mock_request( - status=http_client.OK, data=self.SUCCESS_RESPONSE + status=http.client.OK, data=self.SUCCESS_RESPONSE ) credentials = self.make_credentials( client_id=CLIENT_ID, client_secret=CLIENT_SECRET @@ -703,9 +703,9 @@ def test_refresh_impersonation_with_client_auth_success_ignore_default_scopes(se # Initialize mock request to handle token exchange and service account # impersonation request. request = self.make_mock_request( - status=http_client.OK, + status=http.client.OK, data=token_response, - impersonation_status=http_client.OK, + impersonation_status=http.client.OK, impersonation_data=impersonation_response, ) # Initialize credentials with service account impersonation and basic auth. @@ -774,9 +774,9 @@ def test_refresh_impersonation_with_client_auth_success_use_default_scopes(self) # Initialize mock request to handle token exchange and service account # impersonation request. request = self.make_mock_request( - status=http_client.OK, + status=http.client.OK, data=token_response, - impersonation_status=http_client.OK, + impersonation_status=http.client.OK, impersonation_data=impersonation_response, ) # Initialize credentials with service account impersonation and basic auth. @@ -811,7 +811,7 @@ def test_refresh_impersonation_with_client_auth_success_use_default_scopes(self) def test_apply_without_quota_project_id(self): headers = {} request = self.make_mock_request( - status=http_client.OK, data=self.SUCCESS_RESPONSE + status=http.client.OK, data=self.SUCCESS_RESPONSE ) credentials = self.make_credentials() @@ -834,9 +834,9 @@ def test_apply_impersonation_without_quota_project_id(self): # Initialize mock request to handle token exchange and service account # impersonation request. request = self.make_mock_request( - status=http_client.OK, + status=http.client.OK, data=self.SUCCESS_RESPONSE.copy(), - impersonation_status=http_client.OK, + impersonation_status=http.client.OK, impersonation_data=impersonation_response, ) # Initialize credentials with service account impersonation. @@ -856,7 +856,7 @@ def test_apply_impersonation_without_quota_project_id(self): def test_apply_with_quota_project_id(self): headers = {"other": "header-value"} request = self.make_mock_request( - status=http_client.OK, data=self.SUCCESS_RESPONSE + status=http.client.OK, data=self.SUCCESS_RESPONSE ) credentials = self.make_credentials(quota_project_id=self.QUOTA_PROJECT_ID) @@ -881,9 +881,9 @@ def test_apply_impersonation_with_quota_project_id(self): # Initialize mock request to handle token exchange and service account # impersonation request. request = self.make_mock_request( - status=http_client.OK, + status=http.client.OK, data=self.SUCCESS_RESPONSE.copy(), - impersonation_status=http_client.OK, + impersonation_status=http.client.OK, impersonation_data=impersonation_response, ) # Initialize credentials with service account impersonation. @@ -906,7 +906,7 @@ def test_apply_impersonation_with_quota_project_id(self): def test_before_request(self): headers = {"other": "header-value"} request = self.make_mock_request( - status=http_client.OK, data=self.SUCCESS_RESPONSE + status=http.client.OK, data=self.SUCCESS_RESPONSE ) credentials = self.make_credentials() @@ -938,9 +938,9 @@ def test_before_request_impersonation(self): # Initialize mock request to handle token exchange and service account # impersonation request. request = self.make_mock_request( - status=http_client.OK, + status=http.client.OK, data=self.SUCCESS_RESPONSE.copy(), - impersonation_status=http_client.OK, + impersonation_status=http.client.OK, impersonation_data=impersonation_response, ) headers = {"other": "header-value"} @@ -968,7 +968,7 @@ def test_before_request_impersonation(self): def test_before_request_expired(self, utcnow): headers = {} request = self.make_mock_request( - status=http_client.OK, data=self.SUCCESS_RESPONSE + status=http.client.OK, data=self.SUCCESS_RESPONSE ) credentials = self.make_credentials() credentials.token = "token" @@ -1014,9 +1014,9 @@ def test_before_request_impersonation_expired(self, utcnow): # Initialize mock request to handle token exchange and service account # impersonation request. request = self.make_mock_request( - status=http_client.OK, + status=http.client.OK, data=self.SUCCESS_RESPONSE.copy(), - impersonation_status=http_client.OK, + impersonation_status=http.client.OK, impersonation_data=impersonation_response, ) credentials = self.make_credentials( @@ -1132,11 +1132,11 @@ def test_get_project_id_cloud_resource_manager_success(self): # Initialize mock request to handle token exchange, service account # impersonation and cloud resource manager request. request = self.make_mock_request( - status=http_client.OK, + status=http.client.OK, data=self.SUCCESS_RESPONSE.copy(), - impersonation_status=http_client.OK, + impersonation_status=http.client.OK, impersonation_data=impersonation_response, - cloud_resource_manager_status=http_client.OK, + cloud_resource_manager_status=http.client.OK, cloud_resource_manager_data=self.CLOUD_RESOURCE_MANAGER_SUCCESS_RESPONSE, ) credentials = self.make_credentials( @@ -1190,9 +1190,9 @@ def test_get_project_id_cloud_resource_manager_error(self): # Simulate resource doesn't have sufficient permissions to access # cloud resource manager. request = self.make_mock_request( - status=http_client.OK, + status=http.client.OK, data=self.SUCCESS_RESPONSE.copy(), - cloud_resource_manager_status=http_client.UNAUTHORIZED, + cloud_resource_manager_status=http.client.UNAUTHORIZED, ) credentials = self.make_credentials(scopes=self.SCOPES) diff --git a/packages/google-auth/tests/test_iam.py b/packages/google-auth/tests/test_iam.py index 382713b9b146..30ce2279f018 100644 --- a/packages/google-auth/tests/test_iam.py +++ b/packages/google-auth/tests/test_iam.py @@ -14,11 +14,11 @@ import base64 import datetime +import http.client import json import mock import pytest -from six.moves import http_client from google.auth import _helpers from google.auth import exceptions @@ -81,7 +81,7 @@ def test_key_id(self): def test_sign_bytes(self): signature = b"DEADBEEF" encoded_signature = base64.b64encode(signature).decode("utf-8") - request = make_request(http_client.OK, data={"signedBlob": encoded_signature}) + request = make_request(http.client.OK, data={"signedBlob": encoded_signature}) credentials = make_credentials() signer = iam.Signer(request, credentials, mock.sentinel.service_account_email) @@ -93,7 +93,7 @@ def test_sign_bytes(self): assert kwargs["headers"]["Content-Type"] == "application/json" def test_sign_bytes_failure(self): - request = make_request(http_client.UNAUTHORIZED) + request = make_request(http.client.UNAUTHORIZED) credentials = make_credentials() signer = iam.Signer(request, credentials, mock.sentinel.service_account_email) diff --git a/packages/google-auth/tests/test_identity_pool.py b/packages/google-auth/tests/test_identity_pool.py index b529268fb0ec..efe11b082320 100644 --- a/packages/google-auth/tests/test_identity_pool.py +++ b/packages/google-auth/tests/test_identity_pool.py @@ -13,13 +13,13 @@ # limitations under the License. import datetime +import http.client import json import os +import urllib import mock import pytest -from six.moves import http_client -from six.moves import urllib from google.auth import _helpers from google.auth import exceptions @@ -87,7 +87,7 @@ def make_mock_response(cls, status, data): @classmethod def make_mock_request( - cls, token_status=http_client.OK, token_data=None, *extra_requests + cls, token_status=http.client.OK, token_data=None, *extra_requests ): responses = [] responses.append(cls.make_mock_response(token_status, token_data)) @@ -208,14 +208,14 @@ def assert_underlying_credentials_refresh( # service account impersonation request. requests = [] if credential_data: - requests.append((http_client.OK, credential_data)) + requests.append((http.client.OK, credential_data)) token_request_index = len(requests) - requests.append((http_client.OK, token_response)) + requests.append((http.client.OK, token_response)) if service_account_impersonation_url: impersonation_request_index = len(requests) - requests.append((http_client.OK, impersonation_response)) + requests.append((http.client.OK, impersonation_response)) request = cls.make_mock_request(*[el for req in requests for el in req]) diff --git a/packages/google-auth/tests/test_impersonated_credentials.py b/packages/google-auth/tests/test_impersonated_credentials.py index 90de704a265c..126c4c344c93 100644 --- a/packages/google-auth/tests/test_impersonated_credentials.py +++ b/packages/google-auth/tests/test_impersonated_credentials.py @@ -13,12 +13,12 @@ # limitations under the License. import datetime +import http.client import json import os import mock import pytest -from six.moves import http_client from google.auth import _helpers from google.auth import crypt @@ -79,7 +79,7 @@ def mock_authorizedsession_sign(): "google.auth.transport.requests.AuthorizedSession.request", autospec=True ) as auth_session: data = {"keyId": "1", "signedBlob": "c2lnbmF0dXJl"} - auth_session.return_value = MockResponse(data, http_client.OK) + auth_session.return_value = MockResponse(data, http.client.OK) yield auth_session @@ -89,7 +89,7 @@ def mock_authorizedsession_idtoken(): "google.auth.transport.requests.AuthorizedSession.request", autospec=True ) as auth_session: data = {"token": ID_TOKEN_DATA} - auth_session.return_value = MockResponse(data, http_client.OK) + auth_session.return_value = MockResponse(data, http.client.OK) yield auth_session @@ -141,7 +141,7 @@ def test_default_state(self): def make_request( self, data, - status=http_client.OK, + status=http.client.OK, headers=None, side_effect=None, use_data_bytes=True, @@ -169,7 +169,7 @@ def test_refresh_success(self, use_data_bytes, mock_donor_credentials): request = self.make_request( data=json.dumps(response_body), - status=http_client.OK, + status=http.client.OK, use_data_bytes=use_data_bytes, ) @@ -194,7 +194,7 @@ def test_refresh_success_iam_endpoint_override( request = self.make_request( data=json.dumps(response_body), - status=http_client.OK, + status=http.client.OK, use_data_bytes=use_data_bytes, ) @@ -229,7 +229,7 @@ def test_refresh_source_credentials(self, time_skew): ).isoformat("T") + "Z" response_body = {"accessToken": "token", "expireTime": expire_time} request = self.make_request( - data=json.dumps(response_body), status=http_client.OK + data=json.dumps(response_body), status=http.client.OK ) credentials.refresh(request) @@ -254,7 +254,7 @@ def test_refresh_failure_malformed_expire_time(self, mock_donor_credentials): response_body = {"accessToken": token, "expireTime": expire_time} request = self.make_request( - data=json.dumps(response_body), status=http_client.OK + data=json.dumps(response_body), status=http.client.OK ) with pytest.raises(exceptions.RefreshError) as excinfo: @@ -277,7 +277,7 @@ def test_refresh_failure_unauthorzed(self, mock_donor_credentials): } request = self.make_request( - data=json.dumps(response_body), status=http_client.UNAUTHORIZED + data=json.dumps(response_body), status=http.client.UNAUTHORIZED ) with pytest.raises(exceptions.RefreshError) as excinfo: @@ -294,7 +294,7 @@ def test_refresh_failure_http_error(self, mock_donor_credentials): response_body = {} request = self.make_request( - data=json.dumps(response_body), status=http_client.HTTPException + data=json.dumps(response_body), status=http.client.HTTPException ) with pytest.raises(exceptions.RefreshError) as excinfo: @@ -331,7 +331,7 @@ def test_sign_bytes(self, mock_donor_credentials, mock_authorizedsession_sign): token_response_body = {"accessToken": token, "expireTime": expire_time} response = mock.create_autospec(transport.Response, instance=False) - response.status = http_client.OK + response.status = http.client.OK response.data = _helpers.to_bytes(json.dumps(token_response_body)) request = mock.create_autospec(transport.Request, instance=False) @@ -369,7 +369,7 @@ def test_with_quota_project_iam_endpoint_override( request = self.make_request( data=json.dumps(response_body), - status=http_client.OK, + status=http.client.OK, use_data_bytes=use_data_bytes, ) @@ -394,7 +394,7 @@ def test_id_token_success( response_body = {"accessToken": token, "expireTime": expire_time} request = self.make_request( - data=json.dumps(response_body), status=http_client.OK + data=json.dumps(response_body), status=http.client.OK ) credentials.refresh(request) @@ -423,7 +423,7 @@ def test_id_token_from_credential( response_body = {"accessToken": token, "expireTime": expire_time} request = self.make_request( - data=json.dumps(response_body), status=http_client.OK + data=json.dumps(response_body), status=http.client.OK ) credentials.refresh(request) @@ -453,7 +453,7 @@ def test_id_token_with_target_audience( response_body = {"accessToken": token, "expireTime": expire_time} request = self.make_request( - data=json.dumps(response_body), status=http_client.OK + data=json.dumps(response_body), status=http.client.OK ) credentials.refresh(request) @@ -494,7 +494,7 @@ def test_id_token_with_include_email( response_body = {"accessToken": token, "expireTime": expire_time} request = self.make_request( - data=json.dumps(response_body), status=http_client.OK + data=json.dumps(response_body), status=http.client.OK ) credentials.refresh(request) @@ -523,7 +523,7 @@ def test_id_token_with_quota_project( response_body = {"accessToken": token, "expireTime": expire_time} request = self.make_request( - data=json.dumps(response_body), status=http_client.OK + data=json.dumps(response_body), status=http.client.OK ) credentials.refresh(request) diff --git a/packages/google-auth/tests/test_jwt.py b/packages/google-auth/tests/test_jwt.py index 39c45bd236d5..0dd7fa968b75 100644 --- a/packages/google-auth/tests/test_jwt.py +++ b/packages/google-auth/tests/test_jwt.py @@ -258,9 +258,9 @@ def test_decode_no_key_id(token_factory): def test_decode_unknown_alg(): - headers = json.dumps({u"kid": u"1", u"alg": u"fakealg"}) + headers = json.dumps({"kid": "1", "alg": "fakealg"}) token = b".".join( - map(lambda seg: base64.b64encode(seg.encode("utf-8")), [headers, u"{}", u"sig"]) + map(lambda seg: base64.b64encode(seg.encode("utf-8")), [headers, "{}", "sig"]) ) with pytest.raises(ValueError) as excinfo: @@ -270,9 +270,9 @@ def test_decode_unknown_alg(): def test_decode_missing_crytography_alg(monkeypatch): monkeypatch.delitem(jwt._ALGORITHM_TO_VERIFIER_CLASS, "ES256") - headers = json.dumps({u"kid": u"1", u"alg": u"ES256"}) + headers = json.dumps({"kid": "1", "alg": "ES256"}) token = b".".join( - map(lambda seg: base64.b64encode(seg.encode("utf-8")), [headers, u"{}", u"sig"]) + map(lambda seg: base64.b64encode(seg.encode("utf-8")), [headers, "{}", "sig"]) ) with pytest.raises(ValueError) as excinfo: diff --git a/packages/google-auth/tests/transport/compliance.py b/packages/google-auth/tests/transport/compliance.py index e093d761df3d..a5cb678c31fd 100644 --- a/packages/google-auth/tests/transport/compliance.py +++ b/packages/google-auth/tests/transport/compliance.py @@ -12,12 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. +import http.client import time import flask import pytest from pytest_localserver.http import WSGIServer -from six.moves import http_client from google.auth import exceptions @@ -43,11 +43,11 @@ def server(self): def index(): header_value = flask.request.headers.get("x-test-header", "value") headers = {"X-Test-Header": header_value} - return "Basic Content", http_client.OK, headers + return "Basic Content", http.client.OK, headers @app.route("/server_error") def server_error(): - return "Error", http_client.INTERNAL_SERVER_ERROR + return "Error", http.client.INTERNAL_SERVER_ERROR @app.route("/wait") def wait(): @@ -65,7 +65,7 @@ def test_request_basic(self, server): request = self.make_request() response = request(url=server.url + "/basic", method="GET") - assert response.status == http_client.OK + assert response.status == http.client.OK assert response.headers["x-test-header"] == "value" assert response.data == b"Basic Content" @@ -73,7 +73,7 @@ def test_request_with_timeout_success(self, server): request = self.make_request() response = request(url=server.url + "/basic", method="GET", timeout=2) - assert response.status == http_client.OK + assert response.status == http.client.OK assert response.headers["x-test-header"] == "value" assert response.data == b"Basic Content" @@ -91,7 +91,7 @@ def test_request_headers(self, server): headers={"x-test-header": "hello world"}, ) - assert response.status == http_client.OK + assert response.status == http.client.OK assert response.headers["x-test-header"] == "hello world" assert response.data == b"Basic Content" @@ -99,7 +99,7 @@ def test_request_error(self, server): request = self.make_request() response = request(url=server.url + "/server_error", method="GET") - assert response.status == http_client.INTERNAL_SERVER_ERROR + assert response.status == http.client.INTERNAL_SERVER_ERROR assert response.data == b"Error" def test_connection_error(self): diff --git a/packages/google-auth/tests/transport/test_requests.py b/packages/google-auth/tests/transport/test_requests.py index ed9300d7684e..8b57e0b4e747 100644 --- a/packages/google-auth/tests/transport/test_requests.py +++ b/packages/google-auth/tests/transport/test_requests.py @@ -14,6 +14,7 @@ import datetime import functools +import http.client import os import sys @@ -23,7 +24,6 @@ import pytest import requests import requests.adapters -from six.moves import http_client from google.auth import environment_vars from google.auth import exceptions @@ -188,7 +188,7 @@ def test_import_error(self): ) -def make_response(status=http_client.OK, data=None): +def make_response(status=http.client.OK, data=None): response = requests.Response() response.status_code = status response._content = data @@ -249,10 +249,10 @@ def test_request_no_refresh(self): def test_request_refresh(self): credentials = mock.Mock(wraps=CredentialsStub()) - final_response = make_response(status=http_client.OK) + final_response = make_response(status=http.client.OK) # First request will 401, second request will succeed. adapter = AdapterStub( - [make_response(status=http_client.UNAUTHORIZED), final_response] + [make_response(status=http.client.UNAUTHORIZED), final_response] ) authed_session = google.auth.transport.requests.AuthorizedSession( @@ -282,7 +282,7 @@ def test_request_max_allowed_time_timeout_error(self, frozen_time): wraps=TimeTickCredentialsStub(time_tick=tick_one_second) ) adapter = TimeTickAdapterStub( - time_tick=tick_one_second, responses=[make_response(status=http_client.OK)] + time_tick=tick_one_second, responses=[make_response(status=http.client.OK)] ) authed_session = google.auth.transport.requests.AuthorizedSession(credentials) @@ -304,8 +304,8 @@ def test_request_max_allowed_time_w_transport_timeout_no_error(self, frozen_time adapter = TimeTickAdapterStub( time_tick=tick_one_second, responses=[ - make_response(status=http_client.UNAUTHORIZED), - make_response(status=http_client.OK), + make_response(status=http.client.UNAUTHORIZED), + make_response(status=http.client.OK), ], ) @@ -328,8 +328,8 @@ def test_request_max_allowed_time_w_refresh_timeout_no_error(self, frozen_time): adapter = TimeTickAdapterStub( time_tick=tick_one_second, responses=[ - make_response(status=http_client.UNAUTHORIZED), - make_response(status=http_client.OK), + make_response(status=http.client.UNAUTHORIZED), + make_response(status=http.client.OK), ], ) @@ -355,8 +355,8 @@ def test_request_timeout_w_refresh_timeout_timeout_error(self, frozen_time): adapter = TimeTickAdapterStub( time_tick=tick_one_second, responses=[ - make_response(status=http_client.UNAUTHORIZED), - make_response(status=http_client.OK), + make_response(status=http.client.UNAUTHORIZED), + make_response(status=http.client.OK), ], ) diff --git a/packages/google-auth/tests/transport/test_urllib3.py b/packages/google-auth/tests/transport/test_urllib3.py index e3848c177aad..995d3dccd09c 100644 --- a/packages/google-auth/tests/transport/test_urllib3.py +++ b/packages/google-auth/tests/transport/test_urllib3.py @@ -12,13 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. +import http.client import os import sys import mock import OpenSSL import pytest -from six.moves import http_client import urllib3 from google.auth import environment_vars @@ -84,7 +84,7 @@ def urlopen(self, method, url, body=None, headers=None, **kwargs): class ResponseStub(object): - def __init__(self, status=http_client.OK, data=None): + def __init__(self, status=http.client.OK, data=None): self.status = status self.data = data @@ -141,12 +141,12 @@ def test_urlopen_no_refresh(self): def test_urlopen_refresh(self): credentials = mock.Mock(wraps=CredentialsStub()) - final_response = ResponseStub(status=http_client.OK) + final_response = ResponseStub(status=http.client.OK) # First request will 401, second request will succeed. - http = HttpStub([ResponseStub(status=http_client.UNAUTHORIZED), final_response]) + stub = HttpStub([ResponseStub(status=http.client.UNAUTHORIZED), final_response]) authed_http = google.auth.transport.urllib3.AuthorizedHttp( - credentials, http=http + credentials, http=stub ) authed_http = authed_http.urlopen("GET", "http://example.com") @@ -154,7 +154,7 @@ def test_urlopen_refresh(self): assert authed_http == final_response assert credentials.before_request.call_count == 2 assert credentials.refresh.called - assert http.requests == [ + assert stub.requests == [ ("GET", self.TEST_URL, None, {"authorization": "token"}, {}), ("GET", self.TEST_URL, None, {"authorization": "token1"}, {}), ] diff --git a/packages/google-auth/tests_async/oauth2/test__client_async.py b/packages/google-auth/tests_async/oauth2/test__client_async.py index 6e48c4590fcb..66338d56c09b 100644 --- a/packages/google-auth/tests_async/oauth2/test__client_async.py +++ b/packages/google-auth/tests_async/oauth2/test__client_async.py @@ -13,13 +13,12 @@ # limitations under the License. import datetime +import http.client import json +import urllib import mock import pytest -import six -from six.moves import http_client -from six.moves import urllib from google.auth import _helpers from google.auth import _jwt_async as jwt @@ -29,7 +28,7 @@ from tests.oauth2 import test__client as test_client -def make_request(response_data, status=http_client.OK): +def make_request(response_data, status=http.client.OK): response = mock.AsyncMock(spec=["transport.Response"]) response.status = status data = json.dumps(response_data).encode("utf-8") @@ -93,7 +92,7 @@ async def test__token_endpoint_request_json(): @pytest.mark.asyncio async def test__token_endpoint_request_error(): - request = make_request({}, status=http_client.BAD_REQUEST) + request = make_request({}, status=http.client.BAD_REQUEST) with pytest.raises(exceptions.RefreshError): await _client._token_endpoint_request(request, "http://example.com", {}) @@ -102,7 +101,7 @@ async def test__token_endpoint_request_error(): @pytest.mark.asyncio async def test__token_endpoint_request_internal_failure_error(): request = make_request( - {"error_description": "internal_failure"}, status=http_client.BAD_REQUEST + {"error_description": "internal_failure"}, status=http.client.BAD_REQUEST ) with pytest.raises(exceptions.RefreshError): @@ -111,7 +110,7 @@ async def test__token_endpoint_request_internal_failure_error(): ) request = make_request( - {"error": "internal_failure"}, status=http_client.BAD_REQUEST + {"error": "internal_failure"}, status=http.client.BAD_REQUEST ) with pytest.raises(exceptions.RefreshError): @@ -124,7 +123,7 @@ def verify_request_params(request, params): request_body = request.call_args[1]["body"].decode("utf-8") request_params = urllib.parse.parse_qs(request_body) - for key, value in six.iteritems(params): + for key, value in params.items(): assert request_params[key][0] == value diff --git a/packages/google-auth/tests_async/transport/async_compliance.py b/packages/google-auth/tests_async/transport/async_compliance.py index 9c4b173c2341..385a9236a1f2 100644 --- a/packages/google-auth/tests_async/transport/async_compliance.py +++ b/packages/google-auth/tests_async/transport/async_compliance.py @@ -12,12 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. +import http.client import time import flask import pytest from pytest_localserver.http import WSGIServer -from six.moves import http_client from google.auth import exceptions from tests.transport import compliance @@ -41,11 +41,11 @@ def server(self): def index(): header_value = flask.request.headers.get("x-test-header", "value") headers = {"X-Test-Header": header_value} - return "Basic Content", http_client.OK, headers + return "Basic Content", http.client.OK, headers @app.route("/server_error") def server_error(): - return "Error", http_client.INTERNAL_SERVER_ERROR + return "Error", http.client.INTERNAL_SERVER_ERROR @app.route("/wait") def wait(): @@ -63,7 +63,7 @@ def wait(): async def test_request_basic(self, server): request = self.make_request() response = await request(url=server.url + "/basic", method="GET") - assert response.status == http_client.OK + assert response.status == http.client.OK assert response.headers["x-test-header"] == "value" # Use 13 as this is the length of the data written into the stream. @@ -75,7 +75,7 @@ async def test_request_basic(self, server): async def test_request_basic_with_http(self, server): request = self.make_with_parameter_request() response = await request(url=server.url + "/basic", method="GET") - assert response.status == http_client.OK + assert response.status == http.client.OK assert response.headers["x-test-header"] == "value" # Use 13 as this is the length of the data written into the stream. @@ -88,7 +88,7 @@ async def test_request_with_timeout_success(self, server): request = self.make_request() response = await request(url=server.url + "/basic", method="GET", timeout=2) - assert response.status == http_client.OK + assert response.status == http.client.OK assert response.headers["x-test-header"] == "value" data = await response.data.read(13) @@ -110,7 +110,7 @@ async def test_request_headers(self, server): headers={"x-test-header": "hello world"}, ) - assert response.status == http_client.OK + assert response.status == http.client.OK assert response.headers["x-test-header"] == "hello world" data = await response.data.read(13) @@ -121,7 +121,7 @@ async def test_request_error(self, server): request = self.make_request() response = await request(url=server.url + "/server_error", method="GET") - assert response.status == http_client.INTERNAL_SERVER_ERROR + assert response.status == http.client.INTERNAL_SERVER_ERROR data = await response.data.read(5) assert data == b"Error" From 2c18d91fa2cc4b41d91738087894acd7177269cb Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 3 Aug 2021 17:07:28 -0400 Subject: [PATCH 448/966] chore: release 2.0.0b1 (#823) --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 88fd400cbb78..78e0b508e5e3 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.0.0b1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.34.0...v2.0.0b1) (2021-08-03) + + +### ⚠ BREAKING CHANGES + +* drop support for Python 2.7 ([#778](https://www.github.com/googleapis/google-auth-library-python/issues/778)) ([560cf1e](https://www.github.com/googleapis/google-auth-library-python/commit/560cf1ed02a900436c5d9e0a0fb3f94b5fd98c55)) + ## [1.34.0](https://www.github.com/googleapis/google-auth-library-python/compare/v1.33.1...v1.34.0) (2021-07-23) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index bd3c61a3dd33..db6d3e9e76e1 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "1.34.0" +__version__ = "2.0.0b1" From 12d55454dd3a3f5c2776ab1d92472f75b8853b95 Mon Sep 17 00:00:00 2001 From: Goran Obradovic <62857127+gor-obr@users.noreply.github.com> Date: Tue, 3 Aug 2021 23:24:13 +0200 Subject: [PATCH 449/966] fix: Fix missing space in error message. (#821) Added missing space in the log message. --- packages/google-auth/google/auth/compute_engine/_metadata.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/google/auth/compute_engine/_metadata.py b/packages/google-auth/google/auth/compute_engine/_metadata.py index ee6fec623936..af0d84943111 100644 --- a/packages/google-auth/google/auth/compute_engine/_metadata.py +++ b/packages/google-auth/google/auth/compute_engine/_metadata.py @@ -95,7 +95,7 @@ def ping(request, timeout=_METADATA_DEFAULT_TIMEOUT, retry_count=3): except exceptions.TransportError as e: _LOGGER.warning( - "Compute Engine Metadata server unavailable on" + "Compute Engine Metadata server unavailable on " "attempt %s of %s. Reason: %s", retries + 1, retry_count, @@ -150,7 +150,7 @@ def get( except exceptions.TransportError as e: _LOGGER.warning( - "Compute Engine Metadata server unavailable on" + "Compute Engine Metadata server unavailable on " "attempt %s of %s. Reason: %s", retries + 1, retry_count, From ae9cd739cf4cc3b29c04cfdab8804ddefe360d62 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Fri, 6 Aug 2021 12:54:18 -0600 Subject: [PATCH 450/966] chore: update user secret file (#828) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10323 -> 10323 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index e9d9a516fcc0f729fe436a301db834bb8cf0456f..b47c478ffde3537047316379a31c034da7e8018e 100644 GIT binary patch literal 10323 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 literal 10323 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 From 808d696b898864bfe0e56d4c18523b75a2206c6d Mon Sep 17 00:00:00 2001 From: bojeil-google Date: Fri, 6 Aug 2021 12:51:22 -0700 Subject: [PATCH 451/966] docs: update user guide/references for downscoped creds (#827) Updates user guide to document using downscoping with Credential Access Boundaries. Regenerates references for all classes and utilities related to this feature. Co-authored-by: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> --- packages/google-auth/docs/index.rst | 1 + .../docs/reference/google.auth.downscoped.rst | 7 + .../docs/reference/google.auth.rst | 1 + packages/google-auth/docs/user-guide.rst | 132 ++++++++++++++++++ 4 files changed, 141 insertions(+) create mode 100644 packages/google-auth/docs/reference/google.auth.downscoped.rst diff --git a/packages/google-auth/docs/index.rst b/packages/google-auth/docs/index.rst index 17169109a4df..9544259818d2 100644 --- a/packages/google-auth/docs/index.rst +++ b/packages/google-auth/docs/index.rst @@ -22,6 +22,7 @@ also provides integration with several HTTP libraries. - Support for :mod:`Google App Engine standard credentials `. - Support for :mod:`Identity Pool credentials `. - Support for :mod:`AWS credentials `. +- Support for :mod:`Downscoping with Credential Access Boundaries credentials `. - Support for various transports, including :mod:`Requests `, :mod:`urllib3 `, and diff --git a/packages/google-auth/docs/reference/google.auth.downscoped.rst b/packages/google-auth/docs/reference/google.auth.downscoped.rst new file mode 100644 index 000000000000..79668f998cd5 --- /dev/null +++ b/packages/google-auth/docs/reference/google.auth.downscoped.rst @@ -0,0 +1,7 @@ +google.auth.downscoped module +============================= + +.. automodule:: google.auth.downscoped + :members: + :inherited-members: + :show-inheritance: diff --git a/packages/google-auth/docs/reference/google.auth.rst b/packages/google-auth/docs/reference/google.auth.rst index eb8328ae0c5a..06cc2676bee7 100644 --- a/packages/google-auth/docs/reference/google.auth.rst +++ b/packages/google-auth/docs/reference/google.auth.rst @@ -26,6 +26,7 @@ Submodules google.auth.aws google.auth.credentials google.auth._credentials_async + google.auth.downscoped google.auth.environment_vars google.auth.exceptions google.auth.external_account diff --git a/packages/google-auth/docs/user-guide.rst b/packages/google-auth/docs/user-guide.rst index 4a45cde9163e..de284b881628 100644 --- a/packages/google-auth/docs/user-guide.rst +++ b/packages/google-auth/docs/user-guide.rst @@ -417,6 +417,138 @@ In the example above `source_credentials` does not have direct access to list bu in the target project. Using `ImpersonatedCredentials` will allow the source_credentials to assume the identity of a target_principal that does have access. + +Downscoped credentials +++++++++++++++++++++++ + +`Downscoping with Credential Access Boundaries`_ is used to restrict the +Identity and Access Management (IAM) permissions that a short-lived credential +can use. + +To downscope permissions of a source credential, a `Credential Access Boundary` +that specifies which resources the new credential can access, as well as +an upper bound on the permissions that are available on each resource, has to +be defined. A downscoped credential can then be instantiated using the +`source_credential` and the `Credential Access Boundary`. + +The common pattern of usage is to have a token broker with elevated access +generate these downscoped credentials from higher access source credentials and +pass the downscoped short-lived access tokens to a token consumer via some +secure authenticated channel for limited access to Google Cloud Storage +resources. + +.. _Downscoping with Credential Access Boundaries: https://cloud.google.com/iam/docs/downscoping-short-lived-credentials + +Token broker :: + + import google.auth + + from google.auth import downscoped + from google.auth.transport import requests + + # Initialize the credential access boundary rules. + available_resource = '//storage.googleapis.com/projects/_/buckets/bucket-123' + available_permissions = ['inRole:roles/storage.objectViewer'] + availability_expression = ( + "resource.name.startsWith('projects/_/buckets/bucket-123/objects/customer-a')" + ) + + availability_condition = downscoped.AvailabilityCondition( + availability_expression) + rule = downscoped.AccessBoundaryRule( + available_resource=available_resource, + available_permissions=available_permissions, + availability_condition=availability_condition) + credential_access_boundary = downscoped.CredentialAccessBoundary( + rules=[rule]) + + # Retrieve the source credentials via ADC. + source_credentials, _ = google.auth.default() + + # Create the downscoped credentials. + downscoped_credentials = downscoped.Credentials( + source_credentials=source_credentials, + credential_access_boundary=credential_access_boundary) + + # Refresh the tokens. + downscoped_credentials.refresh(requests.Request()) + + # These values will need to be passed to the Token Consumer. + access_token = downscoped_credentials.token + expiry = downscoped_credentials.expiry + + +For example, a token broker can be set up on a server in a private network. +Various workloads (token consumers) in the same network will send authenticated +requests to that broker for downscoped tokens to access or modify specific google +cloud storage buckets. + +The broker will instantiate downscoped credentials instances that can be used to +generate short lived downscoped access tokens that can be passed to the token +consumer. These downscoped access tokens can be injected by the consumer into +`google.oauth2.Credentials` and used to initialize a storage client instance to +access Google Cloud Storage resources with restricted access. + +Token Consumer :: + + import google.oauth2 + + from google.auth.transport import requests + from google.cloud import storage + + # Downscoped token retrieved from token broker. + # The `get_token_from_broker` callable requests a token and an expiry + # from the token broker. + downscoped_token, expiry = get_token_from_broker( + requests.Request(), + scopes=['https://www.googleapis.com/auth/cloud-platform']) + + # Create the OAuth credentials from the downscoped token and pass a + # refresh handler to handle token expiration. Passing the original + # downscoped token or the expiry here is optional, as the refresh_handler + # will generate the downscoped token on demand. + credentials = google.oauth2.Credentials( + downscoped_token, + expiry=expiry, + scopes=['https://www.googleapis.com/auth/cloud-platform'], + refresh_handler=get_token_from_broker) + + # Initialize a storage client with the oauth2 credentials. + storage_client = storage.Client( + project='my_project_id', credentials=credentials) + # Call GCS APIs. + # The token broker has readonly access to objects starting with "customer-a" + # in bucket "bucket-123". + bucket = storage_client.bucket('bucket-123') + blob = bucket.blob('customer-a-data.txt') + print(blob.download_as_string()) + + +Another reason to use downscoped credentials is to ensure tokens in flight +always have the least privileges, e.g. Principle of Least Privilege. :: + + # Create the downscoped credentials. + downscoped_credentials = downscoped.Credentials( + # source_credentials have elevated access but only a subset of + # these permissions are needed here. + source_credentials=source_credentials, + credential_access_boundary=credential_access_boundary) + + # Pass the token directly. + storage_client = storage.Client( + project='my_project_id', credentials=downscoped_credentials) + # If the source credentials have elevated levels of access, the + # token in flight here will have limited readonly access to objects + # starting with "customer-a" in bucket "bucket-123". + bucket = storage_client.bucket('bucket-123') + blob = bucket.blob('customer-a-data.txt') + print(blob.download_as_string()) + + +Note: Only Cloud Storage supports Credential Access Boundaries. Other Google +Cloud services do not support this feature. + + Identity Tokens +++++++++++++++ From ff8ced7ca0aa807c81c04ed4f0dff080764aa87c Mon Sep 17 00:00:00 2001 From: bojeil-google Date: Mon, 9 Aug 2021 21:23:25 -0700 Subject: [PATCH 452/966] fix: downscoping documentation bugs (#830) Fixes the following issues: - Change `google.oauth2.Credentials` to `google.oauth2.credentials.Credentials` - Replace deprecated `blob.download_as_string()` with `blob.download_as_bytes()` --- packages/google-auth/docs/user-guide.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/docs/user-guide.rst b/packages/google-auth/docs/user-guide.rst index de284b881628..ccece5751548 100644 --- a/packages/google-auth/docs/user-guide.rst +++ b/packages/google-auth/docs/user-guide.rst @@ -507,7 +507,7 @@ Token Consumer :: # refresh handler to handle token expiration. Passing the original # downscoped token or the expiry here is optional, as the refresh_handler # will generate the downscoped token on demand. - credentials = google.oauth2.Credentials( + credentials = google.oauth2.credentials.Credentials( downscoped_token, expiry=expiry, scopes=['https://www.googleapis.com/auth/cloud-platform'], @@ -521,7 +521,7 @@ Token Consumer :: # in bucket "bucket-123". bucket = storage_client.bucket('bucket-123') blob = bucket.blob('customer-a-data.txt') - print(blob.download_as_string()) + print(blob.download_as_bytes().decode("utf-8")) Another reason to use downscoped credentials is to ensure tokens in flight From a3c5e0755977844cb25603b8a9757701b1e84984 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Fri, 13 Aug 2021 15:47:57 -0600 Subject: [PATCH 453/966] chore: update secrets (#834) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10323 -> 10323 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index b47c478ffde3537047316379a31c034da7e8018e..62ef5a717c4d9be19bf53d513bf3705ead4ff6db 100644 GIT binary patch literal 10323 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 literal 10323 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 From 22f78a783c2941d6e10485e771194d6f1bf010e5 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Fri, 13 Aug 2021 18:37:08 -0400 Subject: [PATCH 454/966] chore: omit 'docfx' from docs-presubmit build (#831) We don't have a 'docfx' session in our noxfile (see #822). Further tweak '.kokoro' configuration to get 'Kokoro docs-presubmit' running: - Set up additional 'gfile_resource' in '.kokoro/docs/common.cfg'. - Get 'gcloud' installed in '.kokoro/docker/docs/Dockerfile' - Run 'docs' session with Python 3.8. Closes #797. --- .../.kokoro/docker/docs/Dockerfile | 8 ++++++ packages/google-auth/.kokoro/docs/common.cfg | 5 +++- .../.kokoro/docs/docs-presubmit.cfg | 2 +- packages/google-auth/noxfile.py | 2 +- packages/google-auth/owlbot.py | 28 +++++++++++++++++++ 5 files changed, 42 insertions(+), 3 deletions(-) diff --git a/packages/google-auth/.kokoro/docker/docs/Dockerfile b/packages/google-auth/.kokoro/docker/docs/Dockerfile index 4e1b1fb8b5a5..a6bcfc80c9cf 100644 --- a/packages/google-auth/.kokoro/docker/docs/Dockerfile +++ b/packages/google-auth/.kokoro/docker/docs/Dockerfile @@ -64,4 +64,12 @@ RUN wget -O /tmp/get-pip.py 'https://bootstrap.pypa.io/get-pip.py' \ && python3.8 /tmp/get-pip.py \ && rm /tmp/get-pip.py +# Install gcloud SDK +RUN echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] http://packages.cloud.google.com/apt cloud-sdk main" | \ + tee -a /etc/apt/sources.list.d/google-cloud-sdk.list \ + && curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | \ + apt-key --keyring /usr/share/keyrings/cloud.google.gpg add - \ + && apt-get update -y \ + && apt-get install python2 google-cloud-sdk -y + CMD ["python3.8"] diff --git a/packages/google-auth/.kokoro/docs/common.cfg b/packages/google-auth/.kokoro/docs/common.cfg index 24c8c89dd4d8..2118d7def646 100644 --- a/packages/google-auth/.kokoro/docs/common.cfg +++ b/packages/google-auth/.kokoro/docs/common.cfg @@ -10,6 +10,9 @@ action { # Download trampoline resources. gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" +# Download resources for system tests (service account key, etc.) +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-auth-library-python" + # Use the trampoline script to run in docker. build_file: "google-auth-library-python/.kokoro/trampoline_v2.sh" @@ -62,4 +65,4 @@ before_action { keyname: "docuploader_service_account" } } -} \ No newline at end of file +} diff --git a/packages/google-auth/.kokoro/docs/docs-presubmit.cfg b/packages/google-auth/.kokoro/docs/docs-presubmit.cfg index d0f5783d5345..d3f0deae3c20 100644 --- a/packages/google-auth/.kokoro/docs/docs-presubmit.cfg +++ b/packages/google-auth/.kokoro/docs/docs-presubmit.cfg @@ -24,5 +24,5 @@ env_vars: { # Only run this nox session. env_vars: { key: "NOX_SESSION" - value: "docs docfx" + value: "docs" } diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index d375b03d159e..caeb2725a205 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -142,7 +142,7 @@ def docgen(session): ) -@nox.session(python="3.7") +@nox.session(python="3.8") def docs(session): """Build the docs for this library.""" diff --git a/packages/google-auth/owlbot.py b/packages/google-auth/owlbot.py index f692f7010623..fa03d69135a5 100644 --- a/packages/google-auth/owlbot.py +++ b/packages/google-auth/owlbot.py @@ -7,11 +7,39 @@ # Add templated files # ---------------------------------------------------------------------------- templated_files = common.py_library(unit_cov_level=100, cov_level=100) + + s.move( templated_files / ".kokoro", excludes=[ "continuous/common.cfg", + "docs/common.cfg", "presubmit/common.cfg", "build.sh", ], ) # just move kokoro configs + + +assert 1 == s.replace( + ".kokoro/docs/docs-presubmit.cfg", + 'value: "docs docfx"', + 'value: "docs"', +) + +assert 1 == s.replace( + ".kokoro/docker/docs/Dockerfile", + """\ +CMD \["python3\.8"\]""", + """\ +# Install gcloud SDK +RUN echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] http://packages.cloud.google.com/apt cloud-sdk main" | \\ + tee -a /etc/apt/sources.list.d/google-cloud-sdk.list \\ + && curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | \\ + apt-key --keyring /usr/share/keyrings/cloud.google.gpg add - \\ + && apt-get update -y \\ + && apt-get install python2 google-cloud-sdk -y + +CMD ["python3.8"]""", +) + +s.shell.run(["nox", "-s", "blacken"], hide_output=False) From 592c4b8d348aac2ed647b38437bb7d952a7fa991 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Mon, 16 Aug 2021 13:16:18 -0400 Subject: [PATCH 455/966] chore: split systests into separate kokoro build (#833) * chore: split systests into their own Kokoro build Closes #832. * chore: docs builds no longer need systest resource * chore: remove hacks to get 'gcloud'/'gfile_resource' installed for docs --- .../google-auth/.kokoro/build-systests.sh | 48 +++++++++++++++++++ packages/google-auth/.kokoro/build.sh | 23 --------- .../.kokoro/docker/docs/Dockerfile | 8 ---- packages/google-auth/.kokoro/docs/common.cfg | 5 +- .../.kokoro/presubmit/system-3.7.cfg | 5 ++ packages/google-auth/owlbot.py | 17 ------- 6 files changed, 54 insertions(+), 52 deletions(-) create mode 100755 packages/google-auth/.kokoro/build-systests.sh create mode 100644 packages/google-auth/.kokoro/presubmit/system-3.7.cfg diff --git a/packages/google-auth/.kokoro/build-systests.sh b/packages/google-auth/.kokoro/build-systests.sh new file mode 100755 index 000000000000..a2947c25a3fd --- /dev/null +++ b/packages/google-auth/.kokoro/build-systests.sh @@ -0,0 +1,48 @@ +#!/bin/bash +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -eo pipefail + +if [[ -z "${PROJECT_ROOT:-}" ]]; then + PROJECT_ROOT="github/google-auth-library-python" +fi + +cd "${PROJECT_ROOT}" + +# Disable buffering, so that the logs stream through. +export PYTHONUNBUFFERED=1 + +# Remove old nox +python3 -m pip uninstall --yes --quiet nox-automation + +# Install nox +python3 -m pip install --upgrade --quiet nox +python3 -m nox --version + +# Setup service account credentials. +export GOOGLE_APPLICATION_CREDENTIALS=${KOKORO_GFILE_DIR}/service-account.json + +# Setup project id. +export PROJECT_ID=$(cat "${KOKORO_GFILE_DIR}/project-id.txt") + +# Activate gcloud with service account credentials +gcloud auth activate-service-account --key-file=$GOOGLE_APPLICATION_CREDENTIALS +gcloud config set project ${PROJECT_ID} + +# Decrypt system test secrets +./scripts/decrypt-secrets.sh + +# Run system tests which use a different noxfile +python3 -m nox -f system_tests/noxfile.py diff --git a/packages/google-auth/.kokoro/build.sh b/packages/google-auth/.kokoro/build.sh index 1f96e21d7834..04ab45c19c9b 100755 --- a/packages/google-auth/.kokoro/build.sh +++ b/packages/google-auth/.kokoro/build.sh @@ -24,22 +24,6 @@ cd "${PROJECT_ROOT}" # Disable buffering, so that the logs stream through. export PYTHONUNBUFFERED=1 -# Debug: show build environment -env | grep KOKORO - -# Setup service account credentials. -export GOOGLE_APPLICATION_CREDENTIALS=${KOKORO_GFILE_DIR}/service-account.json - -# Setup project id. -export PROJECT_ID=$(cat "${KOKORO_GFILE_DIR}/project-id.txt") - -# Activate gcloud with service account credentials -gcloud auth activate-service-account --key-file=$GOOGLE_APPLICATION_CREDENTIALS -gcloud config set project ${PROJECT_ID} - -# Decrypt system test secrets -./scripts/decrypt-secrets.sh - # Remove old nox python3 -m pip uninstall --yes --quiet nox-automation @@ -54,10 +38,3 @@ if [[ -n "${NOX_SESSION:-}" ]]; then else python3 -m nox fi - - -# Decrypt system test secrets -./scripts/decrypt-secrets.sh - -# Run system tests which use a different noxfile -python3 -m nox -f system_tests/noxfile.py \ No newline at end of file diff --git a/packages/google-auth/.kokoro/docker/docs/Dockerfile b/packages/google-auth/.kokoro/docker/docs/Dockerfile index a6bcfc80c9cf..4e1b1fb8b5a5 100644 --- a/packages/google-auth/.kokoro/docker/docs/Dockerfile +++ b/packages/google-auth/.kokoro/docker/docs/Dockerfile @@ -64,12 +64,4 @@ RUN wget -O /tmp/get-pip.py 'https://bootstrap.pypa.io/get-pip.py' \ && python3.8 /tmp/get-pip.py \ && rm /tmp/get-pip.py -# Install gcloud SDK -RUN echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] http://packages.cloud.google.com/apt cloud-sdk main" | \ - tee -a /etc/apt/sources.list.d/google-cloud-sdk.list \ - && curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | \ - apt-key --keyring /usr/share/keyrings/cloud.google.gpg add - \ - && apt-get update -y \ - && apt-get install python2 google-cloud-sdk -y - CMD ["python3.8"] diff --git a/packages/google-auth/.kokoro/docs/common.cfg b/packages/google-auth/.kokoro/docs/common.cfg index 2118d7def646..24c8c89dd4d8 100644 --- a/packages/google-auth/.kokoro/docs/common.cfg +++ b/packages/google-auth/.kokoro/docs/common.cfg @@ -10,9 +10,6 @@ action { # Download trampoline resources. gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" -# Download resources for system tests (service account key, etc.) -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-auth-library-python" - # Use the trampoline script to run in docker. build_file: "google-auth-library-python/.kokoro/trampoline_v2.sh" @@ -65,4 +62,4 @@ before_action { keyname: "docuploader_service_account" } } -} +} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/presubmit/system-3.7.cfg b/packages/google-auth/.kokoro/presubmit/system-3.7.cfg new file mode 100644 index 000000000000..0393b98b13f3 --- /dev/null +++ b/packages/google-auth/.kokoro/presubmit/system-3.7.cfg @@ -0,0 +1,5 @@ +# Format: //devtools/kokoro/config/proto/build.proto +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/google-auth-library-python/.kokoro/build-systests.sh" +} diff --git a/packages/google-auth/owlbot.py b/packages/google-auth/owlbot.py index fa03d69135a5..58aa53a70f96 100644 --- a/packages/google-auth/owlbot.py +++ b/packages/google-auth/owlbot.py @@ -13,7 +13,6 @@ templated_files / ".kokoro", excludes=[ "continuous/common.cfg", - "docs/common.cfg", "presubmit/common.cfg", "build.sh", ], @@ -26,20 +25,4 @@ 'value: "docs"', ) -assert 1 == s.replace( - ".kokoro/docker/docs/Dockerfile", - """\ -CMD \["python3\.8"\]""", - """\ -# Install gcloud SDK -RUN echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] http://packages.cloud.google.com/apt cloud-sdk main" | \\ - tee -a /etc/apt/sources.list.d/google-cloud-sdk.list \\ - && curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | \\ - apt-key --keyring /usr/share/keyrings/cloud.google.gpg add - \\ - && apt-get update -y \\ - && apt-get install python2 google-cloud-sdk -y - -CMD ["python3.8"]""", -) - s.shell.run(["nox", "-s", "blacken"], hide_output=False) From 34ce9ada844590c70d9a899404e431713201046d Mon Sep 17 00:00:00 2001 From: bojeil-google Date: Mon, 16 Aug 2021 12:34:28 -0700 Subject: [PATCH 456/966] test: Add integration tests for downscoping (#837) This tests creates a temporary bucket and 2 objects in it. A downscoped token is then created to access only one of the objects (readonly). The test would then check: - Read access to accessible object is successful. - Write access to that object is unsuccessful. - Read access to the inaccessible object is not successful. --- packages/google-auth/CONTRIBUTING.rst | 1 + packages/google-auth/system_tests/noxfile.py | 14 ++ .../system_tests_sync/test_downscoping.py | 163 ++++++++++++++++++ 3 files changed, 178 insertions(+) create mode 100644 packages/google-auth/system_tests/system_tests_sync/test_downscoping.py diff --git a/packages/google-auth/CONTRIBUTING.rst b/packages/google-auth/CONTRIBUTING.rst index 84108995d194..0e784af48e71 100644 --- a/packages/google-auth/CONTRIBUTING.rst +++ b/packages/google-auth/CONTRIBUTING.rst @@ -97,6 +97,7 @@ Grant the account associated with ``service_account.json`` the following roles. - Pub/Sub Viewer (for gRPC tests) - Storage Object Viewer (for impersonated credentials tests) - DNS Viewer (for workload identity federation tests) +- GCE Storage Bucket Admin (for downscoping tests) ``impersonated_service_account.json`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/packages/google-auth/system_tests/noxfile.py b/packages/google-auth/system_tests/noxfile.py index 33e49c37fba8..540727e485a5 100644 --- a/packages/google-auth/system_tests/noxfile.py +++ b/packages/google-auth/system_tests/noxfile.py @@ -350,6 +350,20 @@ def external_accounts(session): ) +@nox.session(python=PYTHON_VERSIONS_SYNC) +def downscoping(session): + session.install( + *TEST_DEPENDENCIES_SYNC, + "google-auth", + "google-cloud-storage", + ) + default( + session, + "system_tests_sync/test_downscoping.py", + *session.posargs, + ) + + # ASYNC SYSTEM TESTS @nox.session(python=PYTHON_VERSIONS_ASYNC) diff --git a/packages/google-auth/system_tests/system_tests_sync/test_downscoping.py b/packages/google-auth/system_tests/system_tests_sync/test_downscoping.py new file mode 100644 index 000000000000..3e62aba9bb81 --- /dev/null +++ b/packages/google-auth/system_tests/system_tests_sync/test_downscoping.py @@ -0,0 +1,163 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import re +import uuid + +import google.auth + +from google.auth import downscoped +from google.auth.transport import requests +from google.cloud import exceptions +from google.cloud import storage +from google.oauth2 import credentials + +import pytest + + # The object prefix used to test access to files beginning with this prefix. +_OBJECT_PREFIX = "customer-a" +# The object name of the object inaccessible by the downscoped token. +_ACCESSIBLE_OBJECT_NAME = f"{_OBJECT_PREFIX}-data.txt" +# The content of the object accessible by the downscoped token. +_ACCESSIBLE_CONTENT = "hello world" +# The content of the object inaccessible by the downscoped token. +_INACCESSIBLE_CONTENT = "secret content" +# The object name of the object inaccessible by the downscoped token. +_INACCESSIBLE_OBJECT_NAME = "other-customer-data.txt" + + +@pytest.fixture +def temp_bucket(): + """Yields a bucket that is deleted after the test completes.""" + bucket = None + while bucket is None or bucket.exists(): + bucket_name = "bucket-downscoping-test-{}".format(uuid.uuid4()) + bucket = storage.Client().bucket(bucket_name) + bucket = storage.Client().create_bucket(bucket.name) + yield bucket + bucket.delete(force=True) + + +@pytest.fixture +def temp_blobs(temp_bucket): + """Yields a blob that is deleted after the test completes.""" + bucket = temp_bucket + # Downscoped tokens will have readonly access to this blob. + accessible_blob = bucket.blob(_ACCESSIBLE_OBJECT_NAME) + accessible_blob.upload_from_string(_ACCESSIBLE_CONTENT) + # Downscoped tokens will have no access to this blob. + inaccessible_blob = bucket.blob(_INACCESSIBLE_OBJECT_NAME) + inaccessible_blob.upload_from_string(_INACCESSIBLE_CONTENT) + yield (accessible_blob, inaccessible_blob) + + +def get_token_from_broker(bucket_name, object_prefix): + """Simulates token broker generating downscoped tokens for specified bucket. + + Args: + bucket_name (str): The name of the Cloud Storage bucket. + object_prefix (str): The prefix string of the object name. This is used + to ensure access is restricted to only objects starting with this + prefix string. + + Returns: + Tuple[str, datetime.datetime]: The downscoped access token and its expiry date. + """ + # Initialize the Credential Access Boundary rules. + available_resource = f"//storage.googleapis.com/projects/_/buckets/{bucket_name}" + # Downscoped credentials will have readonly access to the resource. + available_permissions = ["inRole:roles/storage.objectViewer"] + # Only objects starting with the specified prefix string in the object name + # will be allowed read access. + availability_expression = ( + "resource.name.startsWith('projects/_/buckets/{}/objects/{}')".format( + bucket_name, object_prefix + ) + ) + availability_condition = downscoped.AvailabilityCondition(availability_expression) + # Define the single access boundary rule using the above properties. + rule = downscoped.AccessBoundaryRule( + available_resource=available_resource, + available_permissions=available_permissions, + availability_condition=availability_condition, + ) + # Define the Credential Access Boundary with all the relevant rules. + credential_access_boundary = downscoped.CredentialAccessBoundary(rules=[rule]) + + # Retrieve the source credentials via ADC. + source_credentials, _ = google.auth.default() + if source_credentials.requires_scopes: + source_credentials = source_credentials.with_scopes( + ["https://www.googleapis.com/auth/cloud-platform"] + ) + + # Create the downscoped credentials. + downscoped_credentials = downscoped.Credentials( + source_credentials=source_credentials, + credential_access_boundary=credential_access_boundary, + ) + + # Refresh the tokens. + downscoped_credentials.refresh(requests.Request()) + + # These values will need to be passed to the token consumer. + access_token = downscoped_credentials.token + expiry = downscoped_credentials.expiry + return (access_token, expiry) + + +def test_downscoping(temp_blobs): + """Tests token consumer access to cloud storage using downscoped tokens. + + Args: + temp_blobs (Tuple[google.cloud.storage.blob.Blob, ...]): The temporarily + created test cloud storage blobs (one readonly accessible, the other + not). + """ + accessible_blob, inaccessible_blob = temp_blobs + bucket_name = accessible_blob.bucket.name + # Create the OAuth credentials from the downscoped token and pass a + # refresh handler to handle token expiration. We are passing a + # refresh_handler instead of a one-time access token/expiry pair. + # This will allow testing this on-demand method for getting access tokens. + def refresh_handler(request, scopes=None): + # Get readonly access tokens to objects with accessible prefix in + # the temporarily created bucket. + return get_token_from_broker(bucket_name, _OBJECT_PREFIX) + + creds = credentials.Credentials( + None, + scopes=["https://www.googleapis.com/auth/cloud-platform"], + refresh_handler=refresh_handler, + ) + + # Initialize a Cloud Storage client with the oauth2 credentials. + storage_client = storage.Client(credentials=creds) + + # Test read access succeeds to accessible blob. + bucket = storage_client.bucket(bucket_name) + blob = bucket.blob(accessible_blob.name) + assert blob.download_as_bytes().decode("utf-8") == _ACCESSIBLE_CONTENT + + # Test write access fails. + with pytest.raises(exceptions.GoogleCloudError) as excinfo: + blob.upload_from_string("Write operations are not allowed") + + assert excinfo.match(r"does not have storage.objects.create access") + + # Test read access fails to inaccessible blob. + with pytest.raises(exceptions.GoogleCloudError) as excinfo: + bucket.blob(inaccessible_blob.name).download_as_bytes() + + assert excinfo.match(r"does not have storage.objects.get access") From d37a8b5eb746e1f485ea1e1df605a69617fe575c Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Mon, 16 Aug 2021 12:57:09 -0700 Subject: [PATCH 457/966] feat: service account is able to use a private token endpoint (#835) Co-authored-by: Dan Lee <71398022+dandhlee@users.noreply.github.com> --- packages/google-auth/google/oauth2/service_account.py | 5 +++-- .../google-auth/tests/oauth2/test_service_account.py | 4 ++-- .../tests_async/oauth2/test_service_account_async.py | 10 ++++++++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/packages/google-auth/google/oauth2/service_account.py b/packages/google-auth/google/oauth2/service_account.py index dd3658994336..8f18f26ea17b 100644 --- a/packages/google-auth/google/oauth2/service_account.py +++ b/packages/google-auth/google/oauth2/service_account.py @@ -80,6 +80,7 @@ from google.oauth2 import _client _DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds +_GOOGLE_OAUTH2_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token" class Credentials( @@ -382,7 +383,7 @@ def _make_authorization_grant_assertion(self): # The issuer must be the service account email. "iss": self._service_account_email, # The audience must be the auth token endpoint's URI - "aud": self._token_uri, + "aud": _GOOGLE_OAUTH2_TOKEN_ENDPOINT, "scope": _helpers.scopes_to_string(self._scopes or ()), } @@ -643,7 +644,7 @@ def _make_authorization_grant_assertion(self): # The issuer must be the service account email. "iss": self.service_account_email, # The audience must be the auth token endpoint's URI - "aud": self._token_uri, + "aud": _GOOGLE_OAUTH2_TOKEN_ENDPOINT, # The target audience specifies which service the ID token is # intended for. "target_audience": self._target_audience, diff --git a/packages/google-auth/tests/oauth2/test_service_account.py b/packages/google-auth/tests/oauth2/test_service_account.py index 5852d37146b8..370438f48d04 100644 --- a/packages/google-auth/tests/oauth2/test_service_account.py +++ b/packages/google-auth/tests/oauth2/test_service_account.py @@ -167,7 +167,7 @@ def test__make_authorization_grant_assertion(self): token = credentials._make_authorization_grant_assertion() payload = jwt.decode(token, PUBLIC_CERT_BYTES) assert payload["iss"] == self.SERVICE_ACCOUNT_EMAIL - assert payload["aud"] == self.TOKEN_URI + assert payload["aud"] == service_account._GOOGLE_OAUTH2_TOKEN_ENDPOINT def test__make_authorization_grant_assertion_scoped(self): credentials = self.make_credentials() @@ -440,7 +440,7 @@ def test__make_authorization_grant_assertion(self): token = credentials._make_authorization_grant_assertion() payload = jwt.decode(token, PUBLIC_CERT_BYTES) assert payload["iss"] == self.SERVICE_ACCOUNT_EMAIL - assert payload["aud"] == self.TOKEN_URI + assert payload["aud"] == service_account._GOOGLE_OAUTH2_TOKEN_ENDPOINT assert payload["target_audience"] == self.TARGET_AUDIENCE @mock.patch("google.oauth2._client.id_token_jwt_grant", autospec=True) diff --git a/packages/google-auth/tests_async/oauth2/test_service_account_async.py b/packages/google-auth/tests_async/oauth2/test_service_account_async.py index 40794536cee9..3dce13d82b02 100644 --- a/packages/google-auth/tests_async/oauth2/test_service_account_async.py +++ b/packages/google-auth/tests_async/oauth2/test_service_account_async.py @@ -152,7 +152,10 @@ def test__make_authorization_grant_assertion(self): token = credentials._make_authorization_grant_assertion() payload = jwt.decode(token, test_service_account.PUBLIC_CERT_BYTES) assert payload["iss"] == self.SERVICE_ACCOUNT_EMAIL - assert payload["aud"] == self.TOKEN_URI + assert ( + payload["aud"] + == service_account.service_account._GOOGLE_OAUTH2_TOKEN_ENDPOINT + ) def test__make_authorization_grant_assertion_scoped(self): credentials = self.make_credentials() @@ -311,7 +314,10 @@ def test__make_authorization_grant_assertion(self): token = credentials._make_authorization_grant_assertion() payload = jwt.decode(token, test_service_account.PUBLIC_CERT_BYTES) assert payload["iss"] == self.SERVICE_ACCOUNT_EMAIL - assert payload["aud"] == self.TOKEN_URI + assert ( + payload["aud"] + == service_account.service_account._GOOGLE_OAUTH2_TOKEN_ENDPOINT + ) assert payload["target_audience"] == self.TARGET_AUDIENCE @mock.patch("google.oauth2._client_async.id_token_jwt_grant", autospec=True) From f552ca14fd6225b8b941d59f797068278cec101a Mon Sep 17 00:00:00 2001 From: bojeil-google Date: Mon, 16 Aug 2021 13:28:04 -0700 Subject: [PATCH 458/966] test: address comments for downscoping tests (#838) Addresses all comments from: https://github.com/googleapis/google-auth-library-python/pull/837 Co-authored-by: Tres Seaver --- .../system_tests_sync/test_downscoping.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/packages/google-auth/system_tests/system_tests_sync/test_downscoping.py b/packages/google-auth/system_tests/system_tests_sync/test_downscoping.py index 3e62aba9bb81..77224aeae0d1 100644 --- a/packages/google-auth/system_tests/system_tests_sync/test_downscoping.py +++ b/packages/google-auth/system_tests/system_tests_sync/test_downscoping.py @@ -37,21 +37,21 @@ _INACCESSIBLE_OBJECT_NAME = "other-customer-data.txt" -@pytest.fixture +@pytest.fixture(scope="module") def temp_bucket(): """Yields a bucket that is deleted after the test completes.""" bucket = None while bucket is None or bucket.exists(): - bucket_name = "bucket-downscoping-test-{}".format(uuid.uuid4()) + bucket_name = "auth-python-downscope-test-{}".format(uuid.uuid4()) bucket = storage.Client().bucket(bucket_name) bucket = storage.Client().create_bucket(bucket.name) yield bucket bucket.delete(force=True) -@pytest.fixture +@pytest.fixture(scope="module") def temp_blobs(temp_bucket): - """Yields a blob that is deleted after the test completes.""" + """Yields two blobs that are deleted after the test completes.""" bucket = temp_bucket # Downscoped tokens will have readonly access to this blob. accessible_blob = bucket.blob(_ACCESSIBLE_OBJECT_NAME) @@ -60,6 +60,7 @@ def temp_blobs(temp_bucket): inaccessible_blob = bucket.blob(_INACCESSIBLE_OBJECT_NAME) inaccessible_blob.upload_from_string(_INACCESSIBLE_CONTENT) yield (accessible_blob, inaccessible_blob) + bucket.delete_blobs([accessible_blob, inaccessible_blob]) def get_token_from_broker(bucket_name, object_prefix): @@ -81,9 +82,7 @@ def get_token_from_broker(bucket_name, object_prefix): # Only objects starting with the specified prefix string in the object name # will be allowed read access. availability_expression = ( - "resource.name.startsWith('projects/_/buckets/{}/objects/{}')".format( - bucket_name, object_prefix - ) + f"resource.name.startsWith('projects/_/buckets/{bucket_name}/objects/{object_prefix}')" ) availability_condition = downscoped.AvailabilityCondition(availability_expression) # Define the single access boundary rule using the above properties. @@ -151,13 +150,13 @@ def refresh_handler(request, scopes=None): assert blob.download_as_bytes().decode("utf-8") == _ACCESSIBLE_CONTENT # Test write access fails. - with pytest.raises(exceptions.GoogleCloudError) as excinfo: + with pytest.raises(exceptions.Forbidden) as excinfo: blob.upload_from_string("Write operations are not allowed") assert excinfo.match(r"does not have storage.objects.create access") # Test read access fails to inaccessible blob. - with pytest.raises(exceptions.GoogleCloudError) as excinfo: + with pytest.raises(exceptions.Forbidden) as excinfo: bucket.blob(inaccessible_blob.name).download_as_bytes() assert excinfo.match(r"does not have storage.objects.get access") From df3eed04f618121e7f18bce3744db08b32c671ff Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 17 Aug 2021 09:51:51 -0600 Subject: [PATCH 459/966] chore: release 2.0.0 (#829) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit :robot: I have created a release \*beep\* \*boop\* --- ## [2.0.0](https://www.github.com/googleapis/google-auth-library-python/compare/v2.0.0-b1...v2.0.0) (2021-08-16) ### ⚠ BREAKING CHANGES * drop support for Python 2.7 ([#778](https://www.github.com/googleapis/google-auth-library-python/issues/778)) ([560cf1e](https://www.github.com/googleapis/google-auth-library-python/commit/560cf1ed02a900436c5d9e0a0fb3f94b5fd98c55)) ### Features * service account is able to use a private token endpoint ([#835](https://www.github.com/googleapis/google-auth-library-python/issues/835)) ([20b817a](https://www.github.com/googleapis/google-auth-library-python/commit/20b817af8e202b0331998e5abde4e2a5aab51f9a)) ### Bug Fixes * downscoping documentation bugs ([#830](https://www.github.com/googleapis/google-auth-library-python/issues/830)) ([da8bb13](https://www.github.com/googleapis/google-auth-library-python/commit/da8bb13c1349e771ffc2e125256030495c53d956)) * Fix missing space in error message. ([#821](https://www.github.com/googleapis/google-auth-library-python/issues/821)) ([7b03988](https://www.github.com/googleapis/google-auth-library-python/commit/7b039888aeb6ec7691d91c9afce182b17f02b1a6)) ### Documentation * update user guide/references for downscoped creds ([#827](https://www.github.com/googleapis/google-auth-library-python/issues/827)) ([d1840dc](https://www.github.com/googleapis/google-auth-library-python/commit/d1840dcdcd03dfd7fdfa81d08da68402f6f8b658)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --- packages/google-auth/CHANGELOG.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 78e0b508e5e3..952355f4c17c 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,28 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.0.0](https://www.github.com/googleapis/google-auth-library-python/compare/v2.0.0-b1...v2.0.0) (2021-08-16) + + +### ⚠ BREAKING CHANGES +* drop support for Python 2.7 ([#778](https://www.github.com/googleapis/google-auth-library-python/issues/778)) ([560cf1e](https://www.github.com/googleapis/google-auth-library-python/commit/560cf1ed02a900436c5d9e0a0fb3f94b5fd98c55)) + + +### Features + +* service account is able to use a private token endpoint ([#835](https://www.github.com/googleapis/google-auth-library-python/issues/835)) ([20b817a](https://www.github.com/googleapis/google-auth-library-python/commit/20b817af8e202b0331998e5abde4e2a5aab51f9a)) + + +### Bug Fixes + +* downscoping documentation bugs ([#830](https://www.github.com/googleapis/google-auth-library-python/issues/830)) ([da8bb13](https://www.github.com/googleapis/google-auth-library-python/commit/da8bb13c1349e771ffc2e125256030495c53d956)) +* Fix missing space in error message. ([#821](https://www.github.com/googleapis/google-auth-library-python/issues/821)) ([7b03988](https://www.github.com/googleapis/google-auth-library-python/commit/7b039888aeb6ec7691d91c9afce182b17f02b1a6)) + + +### Documentation + +* update user guide/references for downscoped creds ([#827](https://www.github.com/googleapis/google-auth-library-python/issues/827)) ([d1840dc](https://www.github.com/googleapis/google-auth-library-python/commit/d1840dcdcd03dfd7fdfa81d08da68402f6f8b658)) + ## [2.0.0b1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.34.0...v2.0.0b1) (2021-08-03) From 1e20b352864d88f338e6d50f07c7c215e3e9a100 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Tue, 17 Aug 2021 10:58:28 -0600 Subject: [PATCH 460/966] chore: set release version to 2.0.0 (#840) * chore: release 2.0.0 * Update CHANGELOG.md * chore: set version number to 2.0.0 Follow up to #829 Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/google/auth/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index db6d3e9e76e1..2343e401843f 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.0.0b1" +__version__ = "2.0.0" From a82f313a325ab5afb4f296600c1365f178c62b1f Mon Sep 17 00:00:00 2001 From: bojeil-google Date: Tue, 17 Aug 2021 15:56:39 -0700 Subject: [PATCH 461/966] fix: aws path normalization in windows (#842) Path normalization for the canonical_uri was broken in windows. This is because we were using `os.path.normpath`. This normalizes "/" paths to "\\" in Windows OS. Confirmed the fix is working in Windows. --- packages/google-auth/google/auth/aws.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/aws.py b/packages/google-auth/google/auth/aws.py index 2f2a1359b141..c8deee7079f4 100644 --- a/packages/google-auth/google/auth/aws.py +++ b/packages/google-auth/google/auth/aws.py @@ -45,6 +45,7 @@ import os import re import urllib +from urllib.parse import urljoin from google.auth import _helpers from google.auth import environment_vars @@ -112,13 +113,17 @@ def get_request_options( additional_headers = additional_headers or {} uri = urllib.parse.urlparse(url) + # Normalize the URL path. This is needed for the canonical_uri. + # os.path.normpath can't be used since it normalizes "/" paths + # to "\\" in Windows OS. + normalized_uri = urllib.parse.urlparse(urljoin(url, uri.path)) # Validate provided URL. if not uri.hostname or uri.scheme != "https": raise ValueError("Invalid AWS service URL") header_map = _generate_authentication_header_map( host=uri.hostname, - canonical_uri=os.path.normpath(uri.path or "/"), + canonical_uri=normalized_uri.path or "/", canonical_querystring=_get_canonical_querystring(uri.query), method=method, region=self._region_name, From 8d2be4e9219967725075963dcadb01a8552737a5 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 19 Aug 2021 15:16:48 -0400 Subject: [PATCH 462/966] chore: release 2.0.1 (#845) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 952355f4c17c..82da8c6aa590 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +### [2.0.1](https://www.github.com/googleapis/google-auth-library-python/compare/v2.0.0...v2.0.1) (2021-08-17) + + +### Bug Fixes + +* normalize AWS paths correctly on windows ([#842](https://www.github.com/googleapis/google-auth-library-python/issues/842)) ([4e0fb1c](https://www.github.com/googleapis/google-auth-library-python/commit/4e0fb1cee78ee56b878b6e12be3b3c58df242b05)) + ## [2.0.0](https://www.github.com/googleapis/google-auth-library-python/compare/v2.0.0-b1...v2.0.0) (2021-08-16) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 2343e401843f..1a390124f88e 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.0.0" +__version__ = "2.0.1" From 6be6b4cf299536554c39ef093a6b87c291e4e158 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Fri, 20 Aug 2021 12:14:15 -0600 Subject: [PATCH 463/966] fix: use int.from_bytes (#846) * chore: release 2.0.1 (#845) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> * chore: update secrest Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- .../google-auth/google/auth/crypt/es256.py | 4 ++-- .../google-auth/system_tests/secrets.tar.enc | Bin 10323 -> 10322 bytes 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/google/auth/crypt/es256.py b/packages/google-auth/google/auth/crypt/es256.py index c6d617606728..7465cd607312 100644 --- a/packages/google-auth/google/auth/crypt/es256.py +++ b/packages/google-auth/google/auth/crypt/es256.py @@ -53,8 +53,8 @@ def verify(self, message, signature): sig_bytes = _helpers.to_bytes(signature) if len(sig_bytes) != 64: return False - r = utils.int_from_bytes(sig_bytes[:32], byteorder="big") - s = utils.int_from_bytes(sig_bytes[32:], byteorder="big") + r = int.from_bytes(sig_bytes[:32], byteorder="big") + s = int.from_bytes(sig_bytes[32:], byteorder="big") asn1_sig = encode_dss_signature(r, s) message = _helpers.to_bytes(message) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 62ef5a717c4d9be19bf53d513bf3705ead4ff6db..dab541c5d98934fb8494cf3519b5f75d033746c0 100644 GIT binary patch literal 10322 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 literal 10323 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 From b9e4a87c7c140ccbf1c97657df3c311b6dd6a513 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Mon, 23 Aug 2021 14:24:28 -0400 Subject: [PATCH 464/966] fix: use 'int.to_bytes' rather than deprecated crypto wrapper (#848) Follow-on to #773, #846. --- packages/google-auth/google/auth/crypt/es256.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/google-auth/google/auth/crypt/es256.py b/packages/google-auth/google/auth/crypt/es256.py index 7465cd607312..71dcbfcac097 100644 --- a/packages/google-auth/google/auth/crypt/es256.py +++ b/packages/google-auth/google/auth/crypt/es256.py @@ -15,7 +15,6 @@ """ECDSA (ES256) verifier and signer that use the ``cryptography`` library. """ -from cryptography import utils import cryptography.exceptions from cryptography.hazmat import backends from cryptography.hazmat.primitives import hashes @@ -121,7 +120,7 @@ def sign(self, message): # Convert ASN1 encoded signature to (r||s) raw signature. (r, s) = decode_dss_signature(asn1_signature) - return utils.int_to_bytes(r, 32) + utils.int_to_bytes(s, 32) + return r.to_bytes(32, byteorder="big") + s.to_bytes(32, byteorder="big") @classmethod def from_string(cls, key, key_id=None): From 3a331211d0b8781cb6d575673438a7c949dfed8d Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Wed, 25 Aug 2021 12:41:18 -0400 Subject: [PATCH 465/966] chore: migrate default branch from master to main (#849) --- .../google-auth/.kokoro/test-samples-impl.sh | 2 +- packages/google-auth/README.rst | 4 ++-- packages/google-auth/docs/conf.py | 16 +++++----------- packages/google-auth/docs/index.rst | 4 ++-- packages/google-auth/owlbot.py | 5 +++++ 5 files changed, 15 insertions(+), 16 deletions(-) diff --git a/packages/google-auth/.kokoro/test-samples-impl.sh b/packages/google-auth/.kokoro/test-samples-impl.sh index 311a8d54b9f1..8a324c9c7bc6 100755 --- a/packages/google-auth/.kokoro/test-samples-impl.sh +++ b/packages/google-auth/.kokoro/test-samples-impl.sh @@ -80,7 +80,7 @@ for file in samples/**/requirements.txt; do EXIT=$? # If this is a periodic build, send the test log to the FlakyBot. - # See https://github.com/googleapis/repo-automation-bots/tree/master/packages/flakybot. + # See https://github.com/googleapis/repo-automation-bots/tree/main/packages/flakybot. if [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"periodic"* ]]; then chmod +x $KOKORO_GFILE_DIR/linux_amd64/flakybot $KOKORO_GFILE_DIR/linux_amd64/flakybot diff --git a/packages/google-auth/README.rst b/packages/google-auth/README.rst index 35ebe8bf078b..6e6716173896 100644 --- a/packages/google-auth/README.rst +++ b/packages/google-auth/README.rst @@ -57,11 +57,11 @@ Contributions to this library are always welcome and highly encouraged. See `CONTRIBUTING.rst`_ for more information on how to get started. -.. _CONTRIBUTING.rst: https://github.com/googleapis/google-auth-library-python/blob/master/CONTRIBUTING.rst +.. _CONTRIBUTING.rst: https://github.com/googleapis/google-auth-library-python/blob/main/CONTRIBUTING.rst License ------- Apache 2.0 - See `the LICENSE`_ for more information. -.. _the LICENSE: https://github.com/googleapis/google-auth-library-python/blob/master/LICENSE +.. _the LICENSE: https://github.com/googleapis/google-auth-library-python/blob/main/LICENSE diff --git a/packages/google-auth/docs/conf.py b/packages/google-auth/docs/conf.py index b68467fabe57..58e5b9a99ef7 100644 --- a/packages/google-auth/docs/conf.py +++ b/packages/google-auth/docs/conf.py @@ -53,8 +53,8 @@ # # source_encoding = 'utf-8-sig' -# The master toctree document. -master_doc = "index" +# The root toctree document. +root_doc = "index" # General information about the project. project = "google-auth" @@ -277,13 +277,7 @@ # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - ( - master_doc, - "google-auth.tex", - "google-auth Documentation", - "Google, Inc.", - "manual", - ) + (root_doc, "google-auth.tex", "google-auth Documentation", "Google, Inc.", "manual") ] # The name of an image file (relative to this directory) to place at the top of @@ -323,7 +317,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [(master_doc, "google-auth", "google-auth Documentation", [author], 1)] +man_pages = [(root_doc, "google-auth", "google-auth Documentation", [author], 1)] # If true, show URL addresses after external links. # @@ -337,7 +331,7 @@ # dir menu entry, description, category) texinfo_documents = [ ( - master_doc, + root_doc, "google-auth", "google-auth Documentation", author, diff --git a/packages/google-auth/docs/index.rst b/packages/google-auth/docs/index.rst index 9544259818d2..8a5f13a6daf4 100644 --- a/packages/google-auth/docs/index.rst +++ b/packages/google-auth/docs/index.rst @@ -62,7 +62,7 @@ google-auth is made available under the Apache License, Version 2.0. For more details, see `LICENSE`_ .. _LICENSE: - https://github.com/GoogleCloudPlatform/google-auth-library-python/blob/master/LICENSE + https://github.com/GoogleCloudPlatform/google-auth-library-python/blob/main/LICENSE Contributing ------------ @@ -71,4 +71,4 @@ We happily welcome contributions, please see our `contributing`_ documentation for details. .. _contributing: - https://github.com/GoogleCloudPlatform/google-auth-library-python/blob/master/CONTRIBUTING.rst + https://github.com/GoogleCloudPlatform/google-auth-library-python/blob/main/CONTRIBUTING.rst diff --git a/packages/google-auth/owlbot.py b/packages/google-auth/owlbot.py index 58aa53a70f96..61cf1281a5cf 100644 --- a/packages/google-auth/owlbot.py +++ b/packages/google-auth/owlbot.py @@ -25,4 +25,9 @@ 'value: "docs"', ) +# Remove the replacement below once https://github.com/googleapis/synthtool/pull/1188 is merged + +# Update googleapis/repo-automation-bots repo to main in .kokoro/*.sh files +assert 1 == s.replace(".kokoro/*.sh", "repo-automation-bots/tree/master", "repo-automation-bots/tree/main") + s.shell.run(["nox", "-s", "blacken"], hide_output=False) From 3fe5fc17a17e5fbf2b9120405dcae9aba07e3fcc Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 31 Aug 2021 11:26:00 -0600 Subject: [PATCH 466/966] chore: release 2.0.2 (#847) * chore: release 2.0.2 * chore: update authorized_user.json Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> Co-authored-by: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Co-authored-by: Bu Sun Kim --- packages/google-auth/CHANGELOG.md | 8 ++++++++ packages/google-auth/google/auth/version.py | 2 +- .../google-auth/system_tests/secrets.tar.enc | Bin 10322 -> 10323 bytes 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 82da8c6aa590..68b388db9775 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,14 @@ [1]: https://pypi.org/project/google-auth/#history +### [2.0.2](https://www.github.com/googleapis/google-auth-library-python/compare/v2.0.1...v2.0.2) (2021-08-25) + + +### Bug Fixes + +* use 'int.to_bytes' rather than deprecated crypto wrapper ([#848](https://www.github.com/googleapis/google-auth-library-python/issues/848)) ([b79b554](https://www.github.com/googleapis/google-auth-library-python/commit/b79b55407b31933c9a8fe6de01478fa00a33fa2b)) +* use int.from_bytes ([#846](https://www.github.com/googleapis/google-auth-library-python/issues/846)) ([466aed9](https://www.github.com/googleapis/google-auth-library-python/commit/466aed99f5c2ba15d2036fa21cc83b3f0fc22639)) + ### [2.0.1](https://www.github.com/googleapis/google-auth-library-python/compare/v2.0.0...v2.0.1) (2021-08-17) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 1a390124f88e..46b4030bda34 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.0.1" +__version__ = "2.0.2" diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index dab541c5d98934fb8494cf3519b5f75d033746c0..8f0d88ac669140f69f65305fae23ca5de3265c36 100644 GIT binary patch literal 10323 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 literal 10322 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 From 4f723e569c7e53dba6e1d2e8fd24e368919c5b48 Mon Sep 17 00:00:00 2001 From: nicain Date: Wed, 1 Sep 2021 14:57:21 -0700 Subject: [PATCH 467/966] chore: removing owlbot directives for conversion to main (#855) * chore: removing owlbot directives for conversion to main * update lock file --- packages/google-auth/.github/.OwlBot.lock.yaml | 2 +- packages/google-auth/owlbot.py | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/google-auth/.github/.OwlBot.lock.yaml b/packages/google-auth/.github/.OwlBot.lock.yaml index 9ee60f7e4850..c07f148f0b0b 100644 --- a/packages/google-auth/.github/.OwlBot.lock.yaml +++ b/packages/google-auth/.github/.OwlBot.lock.yaml @@ -1,3 +1,3 @@ docker: image: gcr.io/repo-automation-bots/owlbot-python:latest - digest: sha256:aea14a583128771ae8aefa364e1652f3c56070168ef31beb203534222d842b8b + digest: sha256:0ffe3bdd6c7159692df5f7744da74e5ef19966288a6bf76023e8e04e0c424d7d diff --git a/packages/google-auth/owlbot.py b/packages/google-auth/owlbot.py index 61cf1281a5cf..58aa53a70f96 100644 --- a/packages/google-auth/owlbot.py +++ b/packages/google-auth/owlbot.py @@ -25,9 +25,4 @@ 'value: "docs"', ) -# Remove the replacement below once https://github.com/googleapis/synthtool/pull/1188 is merged - -# Update googleapis/repo-automation-bots repo to main in .kokoro/*.sh files -assert 1 == s.replace(".kokoro/*.sh", "repo-automation-bots/tree/master", "repo-automation-bots/tree/main") - s.shell.run(["nox", "-s", "blacken"], hide_output=False) From bec3c6160760fb6985300748cf75e92de33f4d6c Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Tue, 7 Sep 2021 15:27:42 -0600 Subject: [PATCH 468/966] chore: update authorized_user.json (#859) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10323 -> 10323 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 8f0d88ac669140f69f65305fae23ca5de3265c36..a44427604eb1d47c629fc0eff4e5869f7b68e861 100644 GIT binary patch literal 10323 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 literal 10323 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 From 951820a9babe737b6db67e32e68d662239aaf877 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Tue, 7 Sep 2021 14:38:49 -0700 Subject: [PATCH 469/966] fix: disable warning if quota project id provided to auth.default() (#856) * fix: disable warning if quota project id provided to auth.default() * add more tests --- packages/google-auth/google/auth/_default.py | 16 ++++--- .../google-auth/google/auth/_default_async.py | 20 ++++---- packages/google-auth/tests/test__default.py | 44 ++++++++++++++---- .../tests_async/test__default_async.py | 46 +++++++++++++++---- 4 files changed, 93 insertions(+), 33 deletions(-) diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index 7da77a28f7af..d4ccbc6ec530 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -172,7 +172,7 @@ def load_credentials_from_file( ) -def _get_gcloud_sdk_credentials(): +def _get_gcloud_sdk_credentials(quota_project_id=None): """Gets the credentials and project ID from the Cloud SDK.""" from google.auth import _cloud_sdk @@ -185,7 +185,9 @@ def _get_gcloud_sdk_credentials(): _LOGGER.debug("Cloud SDK credentials not found on disk; not using them") return None, None - credentials, project_id = load_credentials_from_file(credentials_filename) + credentials, project_id = load_credentials_from_file( + credentials_filename, quota_project_id=quota_project_id + ) if not project_id: project_id = _cloud_sdk.get_project_id() @@ -193,7 +195,7 @@ def _get_gcloud_sdk_credentials(): return credentials, project_id -def _get_explicit_environ_credentials(): +def _get_explicit_environ_credentials(quota_project_id=None): """Gets credentials from the GOOGLE_APPLICATION_CREDENTIALS environment variable.""" from google.auth import _cloud_sdk @@ -213,11 +215,11 @@ def _get_explicit_environ_credentials(): "Explicit credentials path %s is the same as Cloud SDK credentials path, fall back to Cloud SDK credentials flow...", explicit_file, ) - return _get_gcloud_sdk_credentials() + return _get_gcloud_sdk_credentials(quota_project_id=quota_project_id) if explicit_file is not None: credentials, project_id = load_credentials_from_file( - os.environ[environment_vars.CREDENTIALS] + os.environ[environment_vars.CREDENTIALS], quota_project_id=quota_project_id ) return credentials, project_id @@ -447,8 +449,8 @@ def default(scopes=None, request=None, quota_project_id=None, default_scopes=Non # with_scopes_if_required() below will ensure scopes/default scopes are # safely set on the returned credentials since requires_scopes will # guard against setting scopes on user credentials. - _get_explicit_environ_credentials, - _get_gcloud_sdk_credentials, + lambda: _get_explicit_environ_credentials(quota_project_id=quota_project_id), + lambda: _get_gcloud_sdk_credentials(quota_project_id=quota_project_id), _get_gae_credentials, lambda: _get_gce_credentials(request), ) diff --git a/packages/google-auth/google/auth/_default_async.py b/packages/google-auth/google/auth/_default_async.py index 82e6c432d878..3fa125b46243 100644 --- a/packages/google-auth/google/auth/_default_async.py +++ b/packages/google-auth/google/auth/_default_async.py @@ -73,11 +73,13 @@ def load_credentials_from_file(filename, scopes=None, quota_project_id=None): try: credentials = credentials.Credentials.from_authorized_user_info( info, scopes=scopes - ).with_quota_project(quota_project_id) + ) except ValueError as caught_exc: msg = "Failed to load authorized user credentials from {}".format(filename) new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) raise new_exc from caught_exc + if quota_project_id: + credentials = credentials.with_quota_project(quota_project_id) if not credentials.quota_project_id: _default._warn_about_problematic_credentials(credentials) return credentials, None @@ -104,7 +106,7 @@ def load_credentials_from_file(filename, scopes=None, quota_project_id=None): ) -def _get_gcloud_sdk_credentials(): +def _get_gcloud_sdk_credentials(quota_project_id=None): """Gets the credentials and project ID from the Cloud SDK.""" from google.auth import _cloud_sdk @@ -114,7 +116,9 @@ def _get_gcloud_sdk_credentials(): if not os.path.isfile(credentials_filename): return None, None - credentials, project_id = load_credentials_from_file(credentials_filename) + credentials, project_id = load_credentials_from_file( + credentials_filename, quota_project_id=quota_project_id + ) if not project_id: project_id = _cloud_sdk.get_project_id() @@ -122,7 +126,7 @@ def _get_gcloud_sdk_credentials(): return credentials, project_id -def _get_explicit_environ_credentials(): +def _get_explicit_environ_credentials(quota_project_id=None): """Gets credentials from the GOOGLE_APPLICATION_CREDENTIALS environment variable.""" from google.auth import _cloud_sdk @@ -134,11 +138,11 @@ def _get_explicit_environ_credentials(): # Cloud sdk flow calls gcloud to fetch project id, so if the explicit # file path is cloud sdk credentials path, then we should fall back # to cloud sdk flow, otherwise project id cannot be obtained. - return _get_gcloud_sdk_credentials() + return _get_gcloud_sdk_credentials(quota_project_id=quota_project_id) if explicit_file is not None: credentials, project_id = load_credentials_from_file( - os.environ[environment_vars.CREDENTIALS] + os.environ[environment_vars.CREDENTIALS], quota_project_id=quota_project_id ) return credentials, project_id @@ -250,8 +254,8 @@ def default_async(scopes=None, request=None, quota_project_id=None): ) checkers = ( - _get_explicit_environ_credentials, - _get_gcloud_sdk_credentials, + lambda: _get_explicit_environ_credentials(quota_project_id=quota_project_id), + lambda: _get_gcloud_sdk_credentials(quota_project_id=quota_project_id), _get_gae_credentials, lambda: _get_gce_credentials(request), ) diff --git a/packages/google-auth/tests/test__default.py b/packages/google-auth/tests/test__default.py index a515f3813395..c70ceaa57da3 100644 --- a/packages/google-auth/tests/test__default.py +++ b/packages/google-auth/tests/test__default.py @@ -328,15 +328,18 @@ def test__get_explicit_environ_credentials_no_env(): assert _default._get_explicit_environ_credentials() == (None, None) +@pytest.mark.parametrize("quota_project_id", [None, "project-foo"]) @LOAD_FILE_PATCH -def test__get_explicit_environ_credentials(load, monkeypatch): +def test__get_explicit_environ_credentials(load, quota_project_id, monkeypatch): monkeypatch.setenv(environment_vars.CREDENTIALS, "filename") - credentials, project_id = _default._get_explicit_environ_credentials() + credentials, project_id = _default._get_explicit_environ_credentials( + quota_project_id=quota_project_id + ) assert credentials is MOCK_CREDENTIALS assert project_id is mock.sentinel.project_id - load.assert_called_with("filename") + load.assert_called_with("filename", quota_project_id=quota_project_id) @LOAD_FILE_PATCH @@ -350,36 +353,40 @@ def test__get_explicit_environ_credentials_no_project_id(load, monkeypatch): assert project_id is None +@pytest.mark.parametrize("quota_project_id", [None, "project-foo"]) @mock.patch( "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True ) @mock.patch("google.auth._default._get_gcloud_sdk_credentials", autospec=True) def test__get_explicit_environ_credentials_fallback_to_gcloud( - get_gcloud_creds, get_adc_path, monkeypatch + get_gcloud_creds, get_adc_path, quota_project_id, monkeypatch ): # Set explicit credentials path to cloud sdk credentials path. get_adc_path.return_value = "filename" monkeypatch.setenv(environment_vars.CREDENTIALS, "filename") - _default._get_explicit_environ_credentials() + _default._get_explicit_environ_credentials(quota_project_id=quota_project_id) # Check we fall back to cloud sdk flow since explicit credentials path is # cloud sdk credentials path - get_gcloud_creds.assert_called_once() + get_gcloud_creds.assert_called_with(quota_project_id=quota_project_id) +@pytest.mark.parametrize("quota_project_id", [None, "project-foo"]) @LOAD_FILE_PATCH @mock.patch( "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True ) -def test__get_gcloud_sdk_credentials(get_adc_path, load): +def test__get_gcloud_sdk_credentials(get_adc_path, load, quota_project_id): get_adc_path.return_value = SERVICE_ACCOUNT_FILE - credentials, project_id = _default._get_gcloud_sdk_credentials() + credentials, project_id = _default._get_gcloud_sdk_credentials( + quota_project_id=quota_project_id + ) assert credentials is MOCK_CREDENTIALS assert project_id is mock.sentinel.project_id - load.assert_called_with(SERVICE_ACCOUNT_FILE) + load.assert_called_with(SERVICE_ACCOUNT_FILE, quota_project_id=quota_project_id) @mock.patch( @@ -779,3 +786,22 @@ def test_default_environ_external_credentials_bad_format(monkeypatch, tmpdir): assert excinfo.match( "Failed to load external account credentials from {}".format(str(filename)) ) + + +@mock.patch( + "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True +) +def test_default_warning_without_quota_project_id_for_user_creds(get_adc_path): + get_adc_path.return_value = AUTHORIZED_USER_CLOUD_SDK_FILE + + with pytest.warns(UserWarning, match="Cloud SDK"): + credentials, project_id = _default.default(quota_project_id=None) + + +@mock.patch( + "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True +) +def test_default_no_warning_with_quota_project_id_for_user_creds(get_adc_path): + get_adc_path.return_value = AUTHORIZED_USER_CLOUD_SDK_FILE + + credentials, project_id = _default.default(quota_project_id="project-foo") diff --git a/packages/google-auth/tests_async/test__default_async.py b/packages/google-auth/tests_async/test__default_async.py index b67230342d79..69a50d69aed5 100644 --- a/packages/google-auth/tests_async/test__default_async.py +++ b/packages/google-auth/tests_async/test__default_async.py @@ -165,15 +165,18 @@ def test__get_explicit_environ_credentials_no_env(): assert _default._get_explicit_environ_credentials() == (None, None) +@pytest.mark.parametrize("quota_project_id", [None, "project-foo"]) @LOAD_FILE_PATCH -def test__get_explicit_environ_credentials(load, monkeypatch): +def test__get_explicit_environ_credentials(load, quota_project_id, monkeypatch): monkeypatch.setenv(environment_vars.CREDENTIALS, "filename") - credentials, project_id = _default._get_explicit_environ_credentials() + credentials, project_id = _default._get_explicit_environ_credentials( + quota_project_id=quota_project_id + ) assert credentials is MOCK_CREDENTIALS assert project_id is mock.sentinel.project_id - load.assert_called_with("filename") + load.assert_called_with("filename", quota_project_id=quota_project_id) @LOAD_FILE_PATCH @@ -187,36 +190,42 @@ def test__get_explicit_environ_credentials_no_project_id(load, monkeypatch): assert project_id is None +@pytest.mark.parametrize("quota_project_id", [None, "project-foo"]) @mock.patch( "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True ) @mock.patch("google.auth._default_async._get_gcloud_sdk_credentials", autospec=True) def test__get_explicit_environ_credentials_fallback_to_gcloud( - get_gcloud_creds, get_adc_path, monkeypatch + get_gcloud_creds, get_adc_path, quota_project_id, monkeypatch ): # Set explicit credentials path to cloud sdk credentials path. get_adc_path.return_value = "filename" monkeypatch.setenv(environment_vars.CREDENTIALS, "filename") - _default._get_explicit_environ_credentials() + _default._get_explicit_environ_credentials(quota_project_id=quota_project_id) # Check we fall back to cloud sdk flow since explicit credentials path is # cloud sdk credentials path - get_gcloud_creds.assert_called_once() + get_gcloud_creds.assert_called_with(quota_project_id=quota_project_id) +@pytest.mark.parametrize("quota_project_id", [None, "project-foo"]) @LOAD_FILE_PATCH @mock.patch( "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True ) -def test__get_gcloud_sdk_credentials(get_adc_path, load): +def test__get_gcloud_sdk_credentials(get_adc_path, load, quota_project_id): get_adc_path.return_value = test_default.SERVICE_ACCOUNT_FILE - credentials, project_id = _default._get_gcloud_sdk_credentials() + credentials, project_id = _default._get_gcloud_sdk_credentials( + quota_project_id=quota_project_id + ) assert credentials is MOCK_CREDENTIALS assert project_id is mock.sentinel.project_id - load.assert_called_with(test_default.SERVICE_ACCOUNT_FILE) + load.assert_called_with( + test_default.SERVICE_ACCOUNT_FILE, quota_project_id=quota_project_id + ) @mock.patch( @@ -533,3 +542,22 @@ def test_default_no_app_engine_compute_engine_module(unused_get): sys.modules["google.auth.compute_engine"] = None sys.modules["google.auth.app_engine"] = None assert _default.default_async() == (MOCK_CREDENTIALS, mock.sentinel.project_id) + + +@mock.patch( + "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True +) +def test_default_warning_without_quota_project_id_for_user_creds(get_adc_path): + get_adc_path.return_value = test_default.AUTHORIZED_USER_CLOUD_SDK_FILE + + with pytest.warns(UserWarning, match="Cloud SDK"): + credentials, project_id = _default.default_async(quota_project_id=None) + + +@mock.patch( + "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True +) +def test_default_no_warning_with_quota_project_id_for_user_creds(get_adc_path): + get_adc_path.return_value = test_default.AUTHORIZED_USER_CLOUD_SDK_FILE + + credentials, project_id = _default.default_async(quota_project_id="project-foo") From 449ca2d67f97ae5188a2f021859adfd5cb4da0ad Mon Sep 17 00:00:00 2001 From: Liron Newman Date: Tue, 7 Sep 2021 23:07:07 +0100 Subject: [PATCH 470/966] feat: Improve handling of clock skew (#858) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Allow up to 60 seconds of skew * Add actionable/helpful error message text. * 🦉 Updates from OwlBot See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md Co-authored-by: Owl Bot Co-authored-by: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> --- packages/google-auth/google/auth/_helpers.py | 2 +- packages/google-auth/google/auth/jwt.py | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/google/auth/_helpers.py b/packages/google-auth/google/auth/_helpers.py index 09f32f84e38f..11c6b1adb41c 100644 --- a/packages/google-auth/google/auth/_helpers.py +++ b/packages/google-auth/google/auth/_helpers.py @@ -20,7 +20,7 @@ import urllib -CLOCK_SKEW_SECS = 10 # 10 seconds +CLOCK_SKEW_SECS = 60 # 60 seconds CLOCK_SKEW = datetime.timedelta(seconds=CLOCK_SKEW_SECS) diff --git a/packages/google-auth/google/auth/jwt.py b/packages/google-auth/google/auth/jwt.py index d931bf7b9f75..1bc7e5e7188a 100644 --- a/packages/google-auth/google/auth/jwt.py +++ b/packages/google-auth/google/auth/jwt.py @@ -190,7 +190,11 @@ def _verify_iat_and_exp(payload): # for clock skew. earliest = iat - _helpers.CLOCK_SKEW_SECS if now < earliest: - raise ValueError("Token used too early, {} < {}".format(now, iat)) + raise ValueError( + "Token used too early, {} < {}. Check that your computer's clock is set correctly.".format( + now, iat + ) + ) # Make sure the token wasn't issued in the past. exp = payload["exp"] From 0e20efd004cc56bab44b95910eaf8d4b4c0e77e3 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Tue, 7 Sep 2021 16:24:45 -0700 Subject: [PATCH 471/966] fix: add SAML challenge to reauth (#819) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: add SAML challenge to reauth * add enable_reauth_refresh flag * address comments * fix unit test * address comments * update * update * update * update * 🦉 Updates from OwlBot See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md Co-authored-by: Owl Bot Co-authored-by: Tres Seaver --- .../google-auth/google/auth/exceptions.py | 4 +++ .../google/oauth2/_credentials_async.py | 1 + .../google/oauth2/_reauth_async.py | 9 ++++++ .../google-auth/google/oauth2/challenges.py | 28 ++++++++++++++++++- .../google-auth/google/oauth2/credentials.py | 11 ++++++++ packages/google-auth/google/oauth2/reauth.py | 9 ++++++ .../data/authorized_user_with_rapt_token.json | 8 ++++++ .../tests/oauth2/test_challenges.py | 8 ++++++ .../tests/oauth2/test_credentials.py | 24 ++++++++++++++++ .../google-auth/tests/oauth2/test_reauth.py | 23 ++++++++++++++- .../oauth2/test_credentials_async.py | 5 ++++ .../tests_async/oauth2/test_reauth_async.py | 23 ++++++++++++++- 12 files changed, 150 insertions(+), 3 deletions(-) create mode 100644 packages/google-auth/tests/data/authorized_user_with_rapt_token.json diff --git a/packages/google-auth/google/auth/exceptions.py b/packages/google-auth/google/auth/exceptions.py index 57f181ea1a40..e9e737780a99 100644 --- a/packages/google-auth/google/auth/exceptions.py +++ b/packages/google-auth/google/auth/exceptions.py @@ -57,3 +57,7 @@ def __init__(self, message=None): super(ReauthFailError, self).__init__( "Reauthentication failed. {0}".format(message) ) + + +class ReauthSamlChallengeFailError(ReauthFailError): + """An exception for SAML reauth challenge failures.""" diff --git a/packages/google-auth/google/oauth2/_credentials_async.py b/packages/google-auth/google/oauth2/_credentials_async.py index b4878c5430f8..e7b9637c82f1 100644 --- a/packages/google-auth/google/oauth2/_credentials_async.py +++ b/packages/google-auth/google/oauth2/_credentials_async.py @@ -75,6 +75,7 @@ async def refresh(self, request): self._client_secret, scopes=self._scopes, rapt_token=self._rapt_token, + enable_reauth_refresh=self._enable_reauth_refresh, ) self.token = access_token diff --git a/packages/google-auth/google/oauth2/_reauth_async.py b/packages/google-auth/google/oauth2/_reauth_async.py index 510578bf7730..f74f50b43009 100644 --- a/packages/google-auth/google/oauth2/_reauth_async.py +++ b/packages/google-auth/google/oauth2/_reauth_async.py @@ -248,6 +248,7 @@ async def refresh_grant( client_secret, scopes=None, rapt_token=None, + enable_reauth_refresh=False, ): """Implements the reauthentication flow. @@ -265,6 +266,9 @@ async def refresh_grant( token has a wild card scope (e.g. 'https://www.googleapis.com/auth/any-api'). rapt_token (Optional(str)): The rapt token for reauth. + enable_reauth_refresh (Optional[bool]): Whether reauth refresh flow + should be used. The default value is False. This option is for + gcloud only, other users should use the default value. Returns: Tuple[str, Optional[str], Optional[datetime], Mapping[str, str], str]: The @@ -299,6 +303,11 @@ async def refresh_grant( == reauth._REAUTH_NEEDED_ERROR_RAPT_REQUIRED ) ): + if not enable_reauth_refresh: + raise exceptions.RefreshError( + "Reauthentication is needed. Please run `gcloud auth login --update-adc` to reauthenticate." + ) + rapt_token = await get_rapt_token( request, client_id, client_secret, refresh_token, token_uri, scopes=scopes ) diff --git a/packages/google-auth/google/oauth2/challenges.py b/packages/google-auth/google/oauth2/challenges.py index 7756a8057773..0baff62e054e 100644 --- a/packages/google-auth/google/oauth2/challenges.py +++ b/packages/google-auth/google/oauth2/challenges.py @@ -25,6 +25,9 @@ REAUTH_ORIGIN = "https://accounts.google.com" +SAML_CHALLENGE_MESSAGE = ( + "Please run `gcloud auth login` to complete reauthentication with SAML." +) def get_user_password(text): @@ -148,7 +151,30 @@ def obtain_challenge_input(self, metadata): return None +class SamlChallenge(ReauthChallenge): + """Challenge that asks the users to browse to their ID Providers. + + Currently SAML challenge is not supported. When obtaining the challenge + input, exception will be raised to instruct the users to run + `gcloud auth login` for reauthentication. + """ + + @property + def name(self): + return "SAML" + + @property + def is_locally_eligible(self): + return True + + def obtain_challenge_input(self, metadata): + # Magic Arch has not fully supported returning a proper dedirect URL + # for programmatic SAML users today. So we error our here and request + # users to use gcloud to complete a login. + raise exceptions.ReauthSamlChallengeFailError(SAML_CHALLENGE_MESSAGE) + + AVAILABLE_CHALLENGES = { challenge.name: challenge - for challenge in [SecurityKeyChallenge(), PasswordChallenge()] + for challenge in [SecurityKeyChallenge(), PasswordChallenge(), SamlChallenge()] } diff --git a/packages/google-auth/google/oauth2/credentials.py b/packages/google-auth/google/oauth2/credentials.py index 98fd71b04a10..e259f78256a8 100644 --- a/packages/google-auth/google/oauth2/credentials.py +++ b/packages/google-auth/google/oauth2/credentials.py @@ -54,6 +54,9 @@ class Credentials(credentials.ReadOnlyScoped, credentials.CredentialsWithQuotaPr credentials = credentials.with_quota_project('myproject-123) + Reauth is disabled by default. To enable reauth, set the + `enable_reauth_refresh` parameter to True in the constructor. Note that + reauth feature is intended for gcloud to use only. If reauth is enabled, `pyu2f` dependency has to be installed in order to use security key reauth feature. Dependency can be installed via `pip install pyu2f` or `pip install google-auth[reauth]`. @@ -73,6 +76,7 @@ def __init__( expiry=None, rapt_token=None, refresh_handler=None, + enable_reauth_refresh=False, ): """ Args: @@ -109,6 +113,8 @@ def __init__( refresh tokens are provided and tokens are obtained by calling some external process on demand. It is particularly useful for retrieving downscoped tokens from a token broker. + enable_reauth_refresh (Optional[bool]): Whether reauth refresh flow + should be used. This flag is for gcloud to use only. """ super(Credentials, self).__init__() self.token = token @@ -123,6 +129,7 @@ def __init__( self._quota_project_id = quota_project_id self._rapt_token = rapt_token self.refresh_handler = refresh_handler + self._enable_reauth_refresh = enable_reauth_refresh def __getstate__(self): """A __getstate__ method must exist for the __setstate__ to be called @@ -151,6 +158,7 @@ def __setstate__(self, d): self._client_secret = d.get("_client_secret") self._quota_project_id = d.get("_quota_project_id") self._rapt_token = d.get("_rapt_token") + self._enable_reauth_refresh = d.get("_enable_reauth_refresh") # The refresh_handler setter should be used to repopulate this. self._refresh_handler = None @@ -241,6 +249,7 @@ def with_quota_project(self, quota_project_id): default_scopes=self.default_scopes, quota_project_id=quota_project_id, rapt_token=self.rapt_token, + enable_reauth_refresh=self._enable_reauth_refresh, ) @_helpers.copy_docstring(credentials.Credentials) @@ -296,6 +305,7 @@ def refresh(self, request): self._client_secret, scopes=scopes, rapt_token=self._rapt_token, + enable_reauth_refresh=self._enable_reauth_refresh, ) self.token = access_token @@ -366,6 +376,7 @@ def from_authorized_user_info(cls, info, scopes=None): client_secret=info.get("client_secret"), quota_project_id=info.get("quota_project_id"), # may not exist expiry=expiry, + rapt_token=info.get("rapt_token"), # may not exist ) @classmethod diff --git a/packages/google-auth/google/oauth2/reauth.py b/packages/google-auth/google/oauth2/reauth.py index fc2629e828c4..1e496d12ec7a 100644 --- a/packages/google-auth/google/oauth2/reauth.py +++ b/packages/google-auth/google/oauth2/reauth.py @@ -275,6 +275,7 @@ def refresh_grant( client_secret, scopes=None, rapt_token=None, + enable_reauth_refresh=False, ): """Implements the reauthentication flow. @@ -292,6 +293,9 @@ def refresh_grant( token has a wild card scope (e.g. 'https://www.googleapis.com/auth/any-api'). rapt_token (Optional(str)): The rapt token for reauth. + enable_reauth_refresh (Optional[bool]): Whether reauth refresh flow + should be used. The default value is False. This option is for + gcloud only, other users should use the default value. Returns: Tuple[str, Optional[str], Optional[datetime], Mapping[str, str], str]: The @@ -324,6 +328,11 @@ def refresh_grant( or response_data.get("error_subtype") == _REAUTH_NEEDED_ERROR_RAPT_REQUIRED ) ): + if not enable_reauth_refresh: + raise exceptions.RefreshError( + "Reauthentication is needed. Please run `gcloud auth login --update-adc` to reauthenticate." + ) + rapt_token = get_rapt_token( request, client_id, client_secret, refresh_token, token_uri, scopes=scopes ) diff --git a/packages/google-auth/tests/data/authorized_user_with_rapt_token.json b/packages/google-auth/tests/data/authorized_user_with_rapt_token.json new file mode 100644 index 000000000000..64b161d42268 --- /dev/null +++ b/packages/google-auth/tests/data/authorized_user_with_rapt_token.json @@ -0,0 +1,8 @@ +{ + "client_id": "123", + "client_secret": "secret", + "refresh_token": "alabalaportocala", + "type": "authorized_user", + "rapt_token": "rapt" + } + \ No newline at end of file diff --git a/packages/google-auth/tests/oauth2/test_challenges.py b/packages/google-auth/tests/oauth2/test_challenges.py index 019b908dae37..412895adaf27 100644 --- a/packages/google-auth/tests/oauth2/test_challenges.py +++ b/packages/google-auth/tests/oauth2/test_challenges.py @@ -130,3 +130,11 @@ def test_password_challenge(getpass_mock): assert challenges.PasswordChallenge().obtain_challenge_input({}) == { "credential": " " } + + +def test_saml_challenge(): + challenge = challenges.SamlChallenge() + assert challenge.is_locally_eligible + assert challenge.name == "SAML" + with pytest.raises(exceptions.ReauthSamlChallengeFailError): + challenge.obtain_challenge_input(None) diff --git a/packages/google-auth/tests/oauth2/test_credentials.py b/packages/google-auth/tests/oauth2/test_credentials.py index 4a7f66e7f495..b6a80e3d071d 100644 --- a/packages/google-auth/tests/oauth2/test_credentials.py +++ b/packages/google-auth/tests/oauth2/test_credentials.py @@ -51,6 +51,7 @@ def make_credentials(cls): client_id=cls.CLIENT_ID, client_secret=cls.CLIENT_SECRET, rapt_token=cls.RAPT_TOKEN, + enable_reauth_refresh=True, ) def test_default_state(self): @@ -149,6 +150,7 @@ def test_refresh_success(self, unused_utcnow, refresh_grant): self.CLIENT_SECRET, None, self.RAPT_TOKEN, + True, ) # Check that the credentials have the token and expiry @@ -219,6 +221,7 @@ def test_refresh_with_refresh_token_and_refresh_handler( self.CLIENT_SECRET, None, self.RAPT_TOKEN, + False, ) # Check that the credentials have the token and expiry @@ -422,6 +425,7 @@ def test_credentials_with_scopes_requested_refresh_success( scopes=scopes, default_scopes=default_scopes, rapt_token=self.RAPT_TOKEN, + enable_reauth_refresh=True, ) # Refresh credentials @@ -436,6 +440,7 @@ def test_credentials_with_scopes_requested_refresh_success( self.CLIENT_SECRET, scopes, self.RAPT_TOKEN, + True, ) # Check that the credentials have the token and expiry @@ -484,6 +489,7 @@ def test_credentials_with_only_default_scopes_requested( client_secret=self.CLIENT_SECRET, default_scopes=default_scopes, rapt_token=self.RAPT_TOKEN, + enable_reauth_refresh=True, ) # Refresh credentials @@ -498,6 +504,7 @@ def test_credentials_with_only_default_scopes_requested( self.CLIENT_SECRET, default_scopes, self.RAPT_TOKEN, + True, ) # Check that the credentials have the token and expiry @@ -549,6 +556,7 @@ def test_credentials_with_scopes_returned_refresh_success( client_secret=self.CLIENT_SECRET, scopes=scopes, rapt_token=self.RAPT_TOKEN, + enable_reauth_refresh=True, ) # Refresh credentials @@ -563,6 +571,7 @@ def test_credentials_with_scopes_returned_refresh_success( self.CLIENT_SECRET, scopes, self.RAPT_TOKEN, + True, ) # Check that the credentials have the token and expiry @@ -615,6 +624,7 @@ def test_credentials_with_scopes_refresh_failure_raises_refresh_error( client_secret=self.CLIENT_SECRET, scopes=scopes, rapt_token=self.RAPT_TOKEN, + enable_reauth_refresh=True, ) # Refresh credentials @@ -632,6 +642,7 @@ def test_credentials_with_scopes_refresh_failure_raises_refresh_error( self.CLIENT_SECRET, scopes, self.RAPT_TOKEN, + True, ) # Check that the credentials have the token and expiry @@ -731,6 +742,7 @@ def test_from_authorized_user_file(self): assert creds.refresh_token == info["refresh_token"] assert creds.token_uri == credentials._GOOGLE_OAUTH2_TOKEN_ENDPOINT assert creds.scopes is None + assert creds.rapt_token is None scopes = ["email", "profile"] creds = credentials.Credentials.from_authorized_user_file( @@ -742,6 +754,18 @@ def test_from_authorized_user_file(self): assert creds.token_uri == credentials._GOOGLE_OAUTH2_TOKEN_ENDPOINT assert creds.scopes == scopes + def test_from_authorized_user_file_with_rapt_token(self): + info = AUTH_USER_INFO.copy() + file_path = os.path.join(DATA_DIR, "authorized_user_with_rapt_token.json") + + creds = credentials.Credentials.from_authorized_user_file(file_path) + assert creds.client_secret == info["client_secret"] + assert creds.client_id == info["client_id"] + assert creds.refresh_token == info["refresh_token"] + assert creds.token_uri == credentials._GOOGLE_OAUTH2_TOKEN_ENDPOINT + assert creds.scopes is None + assert creds.rapt_token == "rapt" + def test_to_json(self): info = AUTH_USER_INFO.copy() expiry = datetime.datetime(2020, 8, 14, 15, 54, 1) diff --git a/packages/google-auth/tests/oauth2/test_reauth.py b/packages/google-auth/tests/oauth2/test_reauth.py index e9ffa8a79e8f..58d649d839ac 100644 --- a/packages/google-auth/tests/oauth2/test_reauth.py +++ b/packages/google-auth/tests/oauth2/test_reauth.py @@ -270,6 +270,7 @@ def test_refresh_grant_failed(): "client_secret", scopes=["foo", "bar"], rapt_token="rapt_token", + enable_reauth_refresh=True, ) assert excinfo.match(r"Bad request") mock_token_request.assert_called_with( @@ -298,7 +299,12 @@ def test_refresh_grant_success(): "google.oauth2.reauth.get_rapt_token", return_value="new_rapt_token" ): assert reauth.refresh_grant( - MOCK_REQUEST, "token_uri", "refresh_token", "client_id", "client_secret" + MOCK_REQUEST, + "token_uri", + "refresh_token", + "client_id", + "client_secret", + enable_reauth_refresh=True, ) == ( "access_token", "refresh_token", @@ -306,3 +312,18 @@ def test_refresh_grant_success(): {"access_token": "access_token"}, "new_rapt_token", ) + + +def test_refresh_grant_reauth_refresh_disabled(): + with mock.patch( + "google.oauth2._client._token_endpoint_request_no_throw" + ) as mock_token_request: + mock_token_request.side_effect = [ + (False, {"error": "invalid_grant", "error_subtype": "rapt_required"}), + (True, {"access_token": "access_token"}), + ] + with pytest.raises(exceptions.RefreshError) as excinfo: + reauth.refresh_grant( + MOCK_REQUEST, "token_uri", "refresh_token", "client_id", "client_secret" + ) + assert excinfo.match(r"Reauthentication is needed") diff --git a/packages/google-auth/tests_async/oauth2/test_credentials_async.py b/packages/google-auth/tests_async/oauth2/test_credentials_async.py index 99cf16f80e49..bc89392ad801 100644 --- a/packages/google-auth/tests_async/oauth2/test_credentials_async.py +++ b/packages/google-auth/tests_async/oauth2/test_credentials_async.py @@ -43,6 +43,7 @@ def make_credentials(cls): token_uri=cls.TOKEN_URI, client_id=cls.CLIENT_ID, client_secret=cls.CLIENT_SECRET, + enable_reauth_refresh=True, ) def test_default_state(self): @@ -97,6 +98,7 @@ async def test_refresh_success(self, unused_utcnow, refresh_grant): self.CLIENT_SECRET, None, None, + True, ) # Check that the credentials have the token and expiry @@ -169,6 +171,7 @@ async def test_credentials_with_scopes_requested_refresh_success( self.CLIENT_SECRET, scopes, "old_rapt_token", + False, ) # Check that the credentials have the token and expiry @@ -231,6 +234,7 @@ async def test_credentials_with_scopes_returned_refresh_success( self.CLIENT_SECRET, scopes, None, + False, ) # Check that the credentials have the token and expiry @@ -301,6 +305,7 @@ async def test_credentials_with_scopes_refresh_failure_raises_refresh_error( self.CLIENT_SECRET, scopes, None, + False, ) # Check that the credentials have the token and expiry diff --git a/packages/google-auth/tests_async/oauth2/test_reauth_async.py b/packages/google-auth/tests_async/oauth2/test_reauth_async.py index f144d89f527f..d982e13a11c8 100644 --- a/packages/google-auth/tests_async/oauth2/test_reauth_async.py +++ b/packages/google-auth/tests_async/oauth2/test_reauth_async.py @@ -318,7 +318,12 @@ async def test_refresh_grant_success(): "google.oauth2._reauth_async.get_rapt_token", return_value="new_rapt_token" ): assert await _reauth_async.refresh_grant( - MOCK_REQUEST, "token_uri", "refresh_token", "client_id", "client_secret" + MOCK_REQUEST, + "token_uri", + "refresh_token", + "client_id", + "client_secret", + enable_reauth_refresh=True, ) == ( "access_token", "refresh_token", @@ -326,3 +331,19 @@ async def test_refresh_grant_success(): {"access_token": "access_token"}, "new_rapt_token", ) + + +@pytest.mark.asyncio +async def test_refresh_grant_reauth_refresh_disabled(): + with mock.patch( + "google.oauth2._client_async._token_endpoint_request_no_throw" + ) as mock_token_request: + mock_token_request.side_effect = [ + (False, {"error": "invalid_grant", "error_subtype": "rapt_required"}), + (True, {"access_token": "access_token"}), + ] + with pytest.raises(exceptions.RefreshError) as excinfo: + assert await _reauth_async.refresh_grant( + MOCK_REQUEST, "token_uri", "refresh_token", "client_id", "client_secret" + ) + assert excinfo.match(r"Reauthentication is needed") From 7a1f8f2e16d2ea5ee34e18943b1904491e3b536a Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Thu, 9 Sep 2021 17:09:54 -0700 Subject: [PATCH 472/966] fix: rename CLOCK_SKEW and separate client/server user case (#863) * fix: rename CLOCK_SKEW and separate client/server user case * update clock skew to 20s --- packages/google-auth/google/auth/_helpers.py | 7 ++-- .../google-auth/google/auth/credentials.py | 2 +- packages/google-auth/google/auth/jwt.py | 14 +++++--- .../google-auth/google/oauth2/credentials.py | 4 +-- .../tests/compute_engine/test_credentials.py | 4 +-- .../tests/oauth2/test_credentials.py | 14 ++++---- .../google-auth/tests/test_credentials.py | 4 ++- packages/google-auth/tests/test_downscoped.py | 4 ++- .../tests/test_external_account.py | 8 +++-- packages/google-auth/tests/test_iam.py | 2 +- .../tests/test_impersonated_credentials.py | 6 ++-- packages/google-auth/tests/test_jwt.py | 34 +++++++++++++++++-- .../google-auth/tests/transport/test_grpc.py | 2 +- .../oauth2/test_credentials_async.py | 8 ++--- .../tests_async/test_credentials_async.py | 4 ++- 15 files changed, 82 insertions(+), 35 deletions(-) diff --git a/packages/google-auth/google/auth/_helpers.py b/packages/google-auth/google/auth/_helpers.py index 11c6b1adb41c..55adf5bc3c7b 100644 --- a/packages/google-auth/google/auth/_helpers.py +++ b/packages/google-auth/google/auth/_helpers.py @@ -20,8 +20,11 @@ import urllib -CLOCK_SKEW_SECS = 60 # 60 seconds -CLOCK_SKEW = datetime.timedelta(seconds=CLOCK_SKEW_SECS) +# Token server doesn't provide a new a token when doing refresh unless the +# token is expiring within 30 seconds, so refresh threshold should not be +# more than 30 seconds. Otherwise auth lib will send tons of refresh requests +# until 30 seconds before the expiration, and cause a spike of CPU usage. +REFRESH_THRESHOLD = datetime.timedelta(seconds=20) def copy_docstring(source_class): diff --git a/packages/google-auth/google/auth/credentials.py b/packages/google-auth/google/auth/credentials.py index 6356f5434c13..8d9974ce1555 100644 --- a/packages/google-auth/google/auth/credentials.py +++ b/packages/google-auth/google/auth/credentials.py @@ -62,7 +62,7 @@ def expired(self): # Remove 10 seconds from expiry to err on the side of reporting # expiration early so that we avoid the 401-refresh-retry loop. - skewed_expiry = self.expiry - _helpers.CLOCK_SKEW + skewed_expiry = self.expiry - _helpers.REFRESH_THRESHOLD return _helpers.utcnow() >= skewed_expiry @property diff --git a/packages/google-auth/google/auth/jwt.py b/packages/google-auth/google/auth/jwt.py index 1bc7e5e7188a..bb9ffae83dc6 100644 --- a/packages/google-auth/google/auth/jwt.py +++ b/packages/google-auth/google/auth/jwt.py @@ -167,12 +167,14 @@ def decode_header(token): return header -def _verify_iat_and_exp(payload): +def _verify_iat_and_exp(payload, clock_skew_in_seconds=0): """Verifies the ``iat`` (Issued At) and ``exp`` (Expires) claims in a token payload. Args: payload (Mapping[str, str]): The JWT payload. + clock_skew_in_seconds (int): The clock skew used for `iat` and `exp` + validation. Raises: ValueError: if any checks failed. @@ -188,7 +190,7 @@ def _verify_iat_and_exp(payload): iat = payload["iat"] # Err on the side of accepting a token that is slightly early to account # for clock skew. - earliest = iat - _helpers.CLOCK_SKEW_SECS + earliest = iat - clock_skew_in_seconds if now < earliest: raise ValueError( "Token used too early, {} < {}. Check that your computer's clock is set correctly.".format( @@ -200,12 +202,12 @@ def _verify_iat_and_exp(payload): exp = payload["exp"] # Err on the side of accepting a token that is slightly out of date # to account for clow skew. - latest = exp + _helpers.CLOCK_SKEW_SECS + latest = exp + clock_skew_in_seconds if latest < now: raise ValueError("Token expired, {} < {}".format(latest, now)) -def decode(token, certs=None, verify=True, audience=None): +def decode(token, certs=None, verify=True, audience=None, clock_skew_in_seconds=0): """Decode and verify a JWT. Args: @@ -221,6 +223,8 @@ def decode(token, certs=None, verify=True, audience=None): audience (str or list): The audience claim, 'aud', that this JWT should contain. Or a list of audience claims. If None then the JWT's 'aud' parameter is not verified. + clock_skew_in_seconds (int): The clock skew used for `iat` and `exp` + validation. Returns: Mapping[str, str]: The deserialized JSON payload in the JWT. @@ -271,7 +275,7 @@ def decode(token, certs=None, verify=True, audience=None): raise ValueError("Could not verify token signature.") # Verify the issued at and created times in the payload. - _verify_iat_and_exp(payload) + _verify_iat_and_exp(payload, clock_skew_in_seconds) # Check audience. if audience is not None: diff --git a/packages/google-auth/google/oauth2/credentials.py b/packages/google-auth/google/oauth2/credentials.py index e259f78256a8..6d34edf045f0 100644 --- a/packages/google-auth/google/oauth2/credentials.py +++ b/packages/google-auth/google/oauth2/credentials.py @@ -270,7 +270,7 @@ def refresh(self, request): raise exceptions.RefreshError( "The refresh_handler returned expiry is not a datetime object." ) - if _helpers.utcnow() >= expiry - _helpers.CLOCK_SKEW: + if _helpers.utcnow() >= expiry - _helpers.REFRESH_THRESHOLD: raise exceptions.RefreshError( "The credentials returned by the refresh_handler are " "already expired." @@ -359,7 +359,7 @@ def from_authorized_user_info(cls, info, scopes=None): expiry.rstrip("Z").split(".")[0], "%Y-%m-%dT%H:%M:%S" ) else: - expiry = _helpers.utcnow() - _helpers.CLOCK_SKEW + expiry = _helpers.utcnow() - _helpers.REFRESH_THRESHOLD # process scopes, which needs to be a seq if scopes is None and "scopes" in info: diff --git a/packages/google-auth/tests/compute_engine/test_credentials.py b/packages/google-auth/tests/compute_engine/test_credentials.py index ebe9aa5ba300..81cc6db3130b 100644 --- a/packages/google-auth/tests/compute_engine/test_credentials.py +++ b/packages/google-auth/tests/compute_engine/test_credentials.py @@ -64,7 +64,7 @@ def test_default_state(self): @mock.patch( "google.auth._helpers.utcnow", - return_value=datetime.datetime.min + _helpers.CLOCK_SKEW, + return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD, ) @mock.patch("google.auth.compute_engine._metadata.get", autospec=True) def test_refresh_success(self, get, utcnow): @@ -98,7 +98,7 @@ def test_refresh_success(self, get, utcnow): @mock.patch( "google.auth._helpers.utcnow", - return_value=datetime.datetime.min + _helpers.CLOCK_SKEW, + return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD, ) @mock.patch("google.auth.compute_engine._metadata.get", autospec=True) def test_refresh_success_with_scopes(self, get, utcnow): diff --git a/packages/google-auth/tests/oauth2/test_credentials.py b/packages/google-auth/tests/oauth2/test_credentials.py index b6a80e3d071d..243f97de8d81 100644 --- a/packages/google-auth/tests/oauth2/test_credentials.py +++ b/packages/google-auth/tests/oauth2/test_credentials.py @@ -115,7 +115,7 @@ def test_invalid_refresh_handler(self): @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", - return_value=datetime.datetime.min + _helpers.CLOCK_SKEW, + return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD, ) def test_refresh_success(self, unused_utcnow, refresh_grant): token = "token" @@ -175,7 +175,7 @@ def test_refresh_no_refresh_token(self): @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", - return_value=datetime.datetime.min + _helpers.CLOCK_SKEW, + return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD, ) def test_refresh_with_refresh_token_and_refresh_handler( self, unused_utcnow, refresh_grant @@ -361,7 +361,7 @@ def test_refresh_with_refresh_handler_invalid_expiry(self): @mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) def test_refresh_with_refresh_handler_expired_token(self, unused_utcnow): - expected_expiry = datetime.datetime.min + _helpers.CLOCK_SKEW + expected_expiry = datetime.datetime.min + _helpers.REFRESH_THRESHOLD # Simulate refresh handler returns an expired token. refresh_handler = mock.Mock(return_value=("TOKEN", expected_expiry)) scopes = ["email", "profile"] @@ -391,7 +391,7 @@ def test_refresh_with_refresh_handler_expired_token(self, unused_utcnow): @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", - return_value=datetime.datetime.min + _helpers.CLOCK_SKEW, + return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD, ) def test_credentials_with_scopes_requested_refresh_success( self, unused_utcnow, refresh_grant @@ -457,7 +457,7 @@ def test_credentials_with_scopes_requested_refresh_success( @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", - return_value=datetime.datetime.min + _helpers.CLOCK_SKEW, + return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD, ) def test_credentials_with_only_default_scopes_requested( self, unused_utcnow, refresh_grant @@ -521,7 +521,7 @@ def test_credentials_with_only_default_scopes_requested( @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", - return_value=datetime.datetime.min + _helpers.CLOCK_SKEW, + return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD, ) def test_credentials_with_scopes_returned_refresh_success( self, unused_utcnow, refresh_grant @@ -588,7 +588,7 @@ def test_credentials_with_scopes_returned_refresh_success( @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", - return_value=datetime.datetime.min + _helpers.CLOCK_SKEW, + return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD, ) def test_credentials_with_scopes_refresh_failure_raises_refresh_error( self, unused_utcnow, refresh_grant diff --git a/packages/google-auth/tests/test_credentials.py b/packages/google-auth/tests/test_credentials.py index 0633b38c07ca..2de63884070c 100644 --- a/packages/google-auth/tests/test_credentials.py +++ b/packages/google-auth/tests/test_credentials.py @@ -46,7 +46,9 @@ def test_expired_and_valid(): # Set the expiration to one second more than now plus the clock skew # accomodation. These credentials should be valid. credentials.expiry = ( - datetime.datetime.utcnow() + _helpers.CLOCK_SKEW + datetime.timedelta(seconds=1) + datetime.datetime.utcnow() + + _helpers.REFRESH_THRESHOLD + + datetime.timedelta(seconds=1) ) assert credentials.valid diff --git a/packages/google-auth/tests/test_downscoped.py b/packages/google-auth/tests/test_downscoped.py index 795ec2942e08..9ca95f5aa856 100644 --- a/packages/google-auth/tests/test_downscoped.py +++ b/packages/google-auth/tests/test_downscoped.py @@ -669,7 +669,9 @@ def test_before_request_expired(self, utcnow): # Set the expiration to one second more than now plus the clock skew # accommodation. These credentials should be valid. credentials.expiry = ( - datetime.datetime.min + _helpers.CLOCK_SKEW + datetime.timedelta(seconds=1) + datetime.datetime.min + + _helpers.REFRESH_THRESHOLD + + datetime.timedelta(seconds=1) ) assert credentials.valid diff --git a/packages/google-auth/tests/test_external_account.py b/packages/google-auth/tests/test_external_account.py index e8297dab62af..df6174f176cd 100644 --- a/packages/google-auth/tests/test_external_account.py +++ b/packages/google-auth/tests/test_external_account.py @@ -976,7 +976,9 @@ def test_before_request_expired(self, utcnow): # Set the expiration to one second more than now plus the clock skew # accomodation. These credentials should be valid. credentials.expiry = ( - datetime.datetime.min + _helpers.CLOCK_SKEW + datetime.timedelta(seconds=1) + datetime.datetime.min + + _helpers.REFRESH_THRESHOLD + + datetime.timedelta(seconds=1) ) assert credentials.valid @@ -1027,7 +1029,9 @@ def test_before_request_impersonation_expired(self, utcnow): # Set the expiration to one second more than now plus the clock skew # accomodation. These credentials should be valid. credentials.expiry = ( - datetime.datetime.min + _helpers.CLOCK_SKEW + datetime.timedelta(seconds=1) + datetime.datetime.min + + _helpers.REFRESH_THRESHOLD + + datetime.timedelta(seconds=1) ) assert credentials.valid diff --git a/packages/google-auth/tests/test_iam.py b/packages/google-auth/tests/test_iam.py index 30ce2279f018..e9eca583cb9b 100644 --- a/packages/google-auth/tests/test_iam.py +++ b/packages/google-auth/tests/test_iam.py @@ -45,7 +45,7 @@ def __init__(self): super(CredentialsImpl, self).__init__() self.token = "token" # Force refresh - self.expiry = datetime.datetime.min + _helpers.CLOCK_SKEW + self.expiry = datetime.datetime.min + _helpers.REFRESH_THRESHOLD def refresh(self, request): pass diff --git a/packages/google-auth/tests/test_impersonated_credentials.py b/packages/google-auth/tests/test_impersonated_credentials.py index 126c4c344c93..3dbb6caa6544 100644 --- a/packages/google-auth/tests/test_impersonated_credentials.py +++ b/packages/google-auth/tests/test_impersonated_credentials.py @@ -211,11 +211,11 @@ def test_refresh_source_credentials(self, time_skew): credentials = self.make_credentials(lifetime=None) # Source credentials is refreshed only if it is expired within - # _helpers.CLOCK_SKEW from now. We add a time_skew to the expiry, so + # _helpers.REFRESH_THRESHOLD from now. We add a time_skew to the expiry, so # source credentials is refreshed only if time_skew <= 0. credentials._source_credentials.expiry = ( _helpers.utcnow() - + _helpers.CLOCK_SKEW + + _helpers.REFRESH_THRESHOLD + datetime.timedelta(seconds=time_skew) ) credentials._source_credentials.token = "Token" @@ -238,7 +238,7 @@ def test_refresh_source_credentials(self, time_skew): assert not credentials.expired # Source credentials is refreshed only if it is expired within - # _helpers.CLOCK_SKEW + # _helpers.REFRESH_THRESHOLD if time_skew > 0: source_cred_refresh.assert_not_called() else: diff --git a/packages/google-auth/tests/test_jwt.py b/packages/google-auth/tests/test_jwt.py index 0dd7fa968b75..ba7277cdcb6e 100644 --- a/packages/google-auth/tests/test_jwt.py +++ b/packages/google-auth/tests/test_jwt.py @@ -197,7 +197,7 @@ def test_decode_bad_token_too_early(token_factory): } ) with pytest.raises(ValueError) as excinfo: - jwt.decode(token, PUBLIC_CERT_BYTES) + jwt.decode(token, PUBLIC_CERT_BYTES, clock_skew_in_seconds=59) assert excinfo.match(r"Token used too early") @@ -210,10 +210,40 @@ def test_decode_bad_token_expired(token_factory): } ) with pytest.raises(ValueError) as excinfo: - jwt.decode(token, PUBLIC_CERT_BYTES) + jwt.decode(token, PUBLIC_CERT_BYTES, clock_skew_in_seconds=59) assert excinfo.match(r"Token expired") +def test_decode_success_with_no_clock_skew(token_factory): + token = token_factory( + claims={ + "exp": _helpers.datetime_to_secs( + _helpers.utcnow() + datetime.timedelta(seconds=1) + ), + "iat": _helpers.datetime_to_secs( + _helpers.utcnow() - datetime.timedelta(seconds=1) + ), + } + ) + + jwt.decode(token, PUBLIC_CERT_BYTES) + + +def test_decode_success_with_custom_clock_skew(token_factory): + token = token_factory( + claims={ + "exp": _helpers.datetime_to_secs( + _helpers.utcnow() + datetime.timedelta(seconds=2) + ), + "iat": _helpers.datetime_to_secs( + _helpers.utcnow() - datetime.timedelta(seconds=2) + ), + } + ) + + jwt.decode(token, PUBLIC_CERT_BYTES, clock_skew_in_seconds=1) + + def test_decode_bad_token_wrong_audience(token_factory): token = token_factory() audience = "audience2@example.com" diff --git a/packages/google-auth/tests/transport/test_grpc.py b/packages/google-auth/tests/transport/test_grpc.py index 926c1bc400bb..3437658a3738 100644 --- a/packages/google-auth/tests/transport/test_grpc.py +++ b/packages/google-auth/tests/transport/test_grpc.py @@ -80,7 +80,7 @@ def test_call_no_refresh(self): def test_call_refresh(self): credentials = CredentialsStub() - credentials.expiry = datetime.datetime.min + _helpers.CLOCK_SKEW + credentials.expiry = datetime.datetime.min + _helpers.REFRESH_THRESHOLD request = mock.create_autospec(transport.Request) plugin = google.auth.transport.grpc.AuthMetadataPlugin(credentials, request) diff --git a/packages/google-auth/tests_async/oauth2/test_credentials_async.py b/packages/google-auth/tests_async/oauth2/test_credentials_async.py index bc89392ad801..06c91419c67a 100644 --- a/packages/google-auth/tests_async/oauth2/test_credentials_async.py +++ b/packages/google-auth/tests_async/oauth2/test_credentials_async.py @@ -62,7 +62,7 @@ def test_default_state(self): @mock.patch("google.oauth2._reauth_async.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", - return_value=datetime.datetime.min + _helpers.CLOCK_SKEW, + return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD, ) @pytest.mark.asyncio async def test_refresh_success(self, unused_utcnow, refresh_grant): @@ -124,7 +124,7 @@ async def test_refresh_no_refresh_token(self): @mock.patch("google.oauth2._reauth_async.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", - return_value=datetime.datetime.min + _helpers.CLOCK_SKEW, + return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD, ) @pytest.mark.asyncio async def test_credentials_with_scopes_requested_refresh_success( @@ -188,7 +188,7 @@ async def test_credentials_with_scopes_requested_refresh_success( @mock.patch("google.oauth2._reauth_async.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", - return_value=datetime.datetime.min + _helpers.CLOCK_SKEW, + return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD, ) @pytest.mark.asyncio async def test_credentials_with_scopes_returned_refresh_success( @@ -251,7 +251,7 @@ async def test_credentials_with_scopes_returned_refresh_success( @mock.patch("google.oauth2._reauth_async.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", - return_value=datetime.datetime.min + _helpers.CLOCK_SKEW, + return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD, ) @pytest.mark.asyncio async def test_credentials_with_scopes_refresh_failure_raises_refresh_error( diff --git a/packages/google-auth/tests_async/test_credentials_async.py b/packages/google-auth/tests_async/test_credentials_async.py index 0a4890825f23..5315483da318 100644 --- a/packages/google-auth/tests_async/test_credentials_async.py +++ b/packages/google-auth/tests_async/test_credentials_async.py @@ -46,7 +46,9 @@ def test_expired_and_valid(): # Set the expiration to one second more than now plus the clock skew # accomodation. These credentials should be valid. credentials.expiry = ( - datetime.datetime.utcnow() + _helpers.CLOCK_SKEW + datetime.timedelta(seconds=1) + datetime.datetime.utcnow() + + _helpers.REFRESH_THRESHOLD + + datetime.timedelta(seconds=1) ) assert credentials.valid From 3781a1c6af4d22aa09911476ca170a3600743a8b Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 14 Sep 2021 10:26:53 -0700 Subject: [PATCH 473/966] chore: release 2.1.0 (#861) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 14 ++++++++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 68b388db9775..056aeac6e448 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,20 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.1.0](https://www.github.com/googleapis/google-auth-library-python/compare/v2.0.2...v2.1.0) (2021-09-10) + + +### Features + +* Improve handling of clock skew ([#858](https://www.github.com/googleapis/google-auth-library-python/issues/858)) ([45c4491](https://www.github.com/googleapis/google-auth-library-python/commit/45c4491fb971c9edf590b27b9e271b7a23a1bba6)) + + +### Bug Fixes + +* add SAML challenge to reauth ([#819](https://www.github.com/googleapis/google-auth-library-python/issues/819)) ([13aed5f](https://www.github.com/googleapis/google-auth-library-python/commit/13aed5ffe3ba435004ab48202462452f04d7cb29)) +* disable warning if quota project id provided to auth.default() ([#856](https://www.github.com/googleapis/google-auth-library-python/issues/856)) ([11ebaeb](https://www.github.com/googleapis/google-auth-library-python/commit/11ebaeb9d7c0862916154cfb810238574507629a)) +* rename CLOCK_SKEW and separate client/server user case ([#863](https://www.github.com/googleapis/google-auth-library-python/issues/863)) ([738611b](https://www.github.com/googleapis/google-auth-library-python/commit/738611bd2914f0fd5fa8b49b65f56ef321829c85)) + ### [2.0.2](https://www.github.com/googleapis/google-auth-library-python/compare/v2.0.1...v2.0.2) (2021-08-25) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 46b4030bda34..a02dc8aec8c7 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.0.2" +__version__ = "2.1.0" From 7f2479784d72579b4ecb4b49be7979de998ae4c8 Mon Sep 17 00:00:00 2001 From: Jeffrey Rennie Date: Tue, 21 Sep 2021 12:36:08 -0700 Subject: [PATCH 474/966] chore: relocate owl bot post processor (#869) chore: relocate owl bot post processor --- packages/google-auth/.github/.OwlBot.lock.yaml | 4 ++-- packages/google-auth/.github/.OwlBot.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/google-auth/.github/.OwlBot.lock.yaml b/packages/google-auth/.github/.OwlBot.lock.yaml index c07f148f0b0b..2567653c000d 100644 --- a/packages/google-auth/.github/.OwlBot.lock.yaml +++ b/packages/google-auth/.github/.OwlBot.lock.yaml @@ -1,3 +1,3 @@ docker: - image: gcr.io/repo-automation-bots/owlbot-python:latest - digest: sha256:0ffe3bdd6c7159692df5f7744da74e5ef19966288a6bf76023e8e04e0c424d7d + image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest + digest: sha256:87eee22d276554e4e52863ec9b1cb6a7245815dfae20439712bf644348215a5a diff --git a/packages/google-auth/.github/.OwlBot.yaml b/packages/google-auth/.github/.OwlBot.yaml index 892fbc2910bf..ed6155aab50f 100644 --- a/packages/google-auth/.github/.OwlBot.yaml +++ b/packages/google-auth/.github/.OwlBot.yaml @@ -13,6 +13,6 @@ # limitations under the License. docker: - image: gcr.io/repo-automation-bots/owlbot-python:latest + image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest begin-after-commit-hash: ee56c3493ec6aeb237ff515ecea949710944a20f From 18d7e754e4b93b7f7e42d1fa886bce4ede4b0d93 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Tue, 21 Sep 2021 14:42:13 -0600 Subject: [PATCH 475/966] chore: update token (#870) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10323 -> 10323 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index a44427604eb1d47c629fc0eff4e5869f7b68e861..ed178d8bd92b0f5633c3447df729de58d65408a9 100644 GIT binary patch literal 10323 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 From 1148c9af2dcb1fc2b354e7e26081b9a3c56eda2c Mon Sep 17 00:00:00 2001 From: bojeil-google Date: Tue, 21 Sep 2021 14:00:15 -0700 Subject: [PATCH 476/966] feat: add support for workforce pool credentials (#868) Workforce pools (external account credentials for non-Google users) are organization-level resources which means that issued workforce pool tokens will not have any client project ID on token exchange as currently designed. "To use a Google API, the client must identify the application to the server. If the API requires authentication, the client must also identify the principal running the application." The application here is the client project. The token will identify the user principal but not the application. This will result in APIs rejecting requests authenticated with these tokens. Note that passing a `x-goog-user-project` override header on API request is still not sufficient. The token is still expected to have a client project. As a result, we have extended the spec to support an additional `workforce_pool_user_project` for these credentials (workforce pools) which will be passed when exchanging an external token for a Google Access token. After the exchange, the issued access token will use the supplied project as the client project. The underlying principal must still have `serviceusage.services.use` IAM permission to use the project for billing/quota. This field is not needed for flows with basic client authentication (e.g. client ID is supplied). The client ID is sufficient to determine the client project and any additionally supplied `workforce_pool_user_project` value will be ignored. Note that this feature is not usable yet publicly. The additional field has been added to the abstract external account credentials `google.auth.external_account.Credentials` and the subclass `google.auth.identity_pool.Credentials`. --- .../google/auth/external_account.py | 67 ++- .../google-auth/google/auth/identity_pool.py | 8 + .../tests/test_external_account.py | 477 ++++++++++++++++-- .../google-auth/tests/test_identity_pool.py | 213 +++++++- 4 files changed, 723 insertions(+), 42 deletions(-) diff --git a/packages/google-auth/google/auth/external_account.py b/packages/google-auth/google/auth/external_account.py index 24b93b423613..f588981a0791 100644 --- a/packages/google-auth/google/auth/external_account.py +++ b/packages/google-auth/google/auth/external_account.py @@ -73,6 +73,7 @@ def __init__( quota_project_id=None, scopes=None, default_scopes=None, + workforce_pool_user_project=None, ): """Instantiates an external account credentials object. @@ -90,6 +91,11 @@ def __init__( authorization grant. default_scopes (Optional[Sequence[str]]): Default scopes passed by a Google client library. Use 'scopes' for user-defined scopes. + workforce_pool_user_project (Optona[str]): The optional workforce pool user + project number when the credential corresponds to a workforce pool and not + a workload identity pool. The underlying principal must still have + serviceusage.services.use IAM permission to use the project for + billing/quota. Raises: google.auth.exceptions.RefreshError: If the generateAccessToken endpoint returned an error. @@ -105,6 +111,7 @@ def __init__( self._quota_project_id = quota_project_id self._scopes = scopes self._default_scopes = default_scopes + self._workforce_pool_user_project = workforce_pool_user_project if self._client_id: self._client_auth = utils.ClientAuthentication( @@ -120,6 +127,13 @@ def __init__( self._impersonated_credentials = None self._project_id = None + if not self.is_workforce_pool and self._workforce_pool_user_project: + # Workload identity pools do not support workforce pool user projects. + raise ValueError( + "workforce_pool_user_project should not be set for non-workforce pool " + "credentials" + ) + @property def info(self): """Generates the dictionary representation of the current credentials. @@ -140,6 +154,7 @@ def info(self): "quota_project_id": self._quota_project_id, "client_id": self._client_id, "client_secret": self._client_secret, + "workforce_pool_user_project": self._workforce_pool_user_project, } return {key: value for key, value in config_info.items() if value is not None} @@ -178,12 +193,23 @@ def is_user(self): # service account. if self._service_account_impersonation_url: return False + return self.is_workforce_pool + + @property + def is_workforce_pool(self): + """Returns whether the credentials represent a workforce pool (True) or + workload (False) based on the credentials' audience. + + This will also return True for impersonated workforce pool credentials. + + Returns: + bool: True if the credentials represent a workforce pool. False if they + represent a workload. + """ # Workforce pools representing users have the following audience format: # //iam.googleapis.com/locations/$location/workforcePools/$poolId/providers/$providerId p = re.compile(r"//iam\.googleapis\.com/locations/[^/]+/workforcePools/") - if p.match(self._audience): - return True - return False + return p.match(self._audience or "") is not None @property def requires_scopes(self): @@ -210,7 +236,7 @@ def project_number(self): @_helpers.copy_docstring(credentials.Scoped) def with_scopes(self, scopes, default_scopes=None): - return self.__class__( + d = dict( audience=self._audience, subject_token_type=self._subject_token_type, token_url=self._token_url, @@ -221,7 +247,11 @@ def with_scopes(self, scopes, default_scopes=None): quota_project_id=self._quota_project_id, scopes=scopes, default_scopes=default_scopes, + workforce_pool_user_project=self._workforce_pool_user_project, ) + if not self.is_workforce_pool: + d.pop("workforce_pool_user_project") + return self.__class__(**d) @abc.abstractmethod def retrieve_subject_token(self, request): @@ -238,7 +268,9 @@ def retrieve_subject_token(self, request): raise NotImplementedError("retrieve_subject_token must be implemented") def get_project_id(self, request): - """Retrieves the project ID corresponding to the workload identity pool. + """Retrieves the project ID corresponding to the workload identity or workforce pool. + For workforce pool credentials, it returns the project ID corresponding to + the workforce_pool_user_project. When not determinable, None is returned. @@ -255,16 +287,17 @@ def get_project_id(self, request): HTTP requests. Returns: Optional[str]: The project ID corresponding to the workload identity pool - if determinable. + or workforce pool if determinable. """ if self._project_id: # If already retrieved, return the cached project ID value. return self._project_id scopes = self._scopes if self._scopes is not None else self._default_scopes # Scopes are required in order to retrieve a valid access token. - if self.project_number and scopes: + project_number = self.project_number or self._workforce_pool_user_project + if project_number and scopes: headers = {} - url = _CLOUD_RESOURCE_MANAGER + self.project_number + url = _CLOUD_RESOURCE_MANAGER + project_number self.before_request(request, "GET", url, headers) response = request(url=url, method="GET", headers=headers) @@ -291,6 +324,11 @@ def refresh(self, request): self.expiry = self._impersonated_credentials.expiry else: now = _helpers.utcnow() + additional_options = None + # Do not pass workforce_pool_user_project when client authentication + # is used. The client ID is sufficient for determining the user project. + if self._workforce_pool_user_project and not self._client_id: + additional_options = {"userProject": self._workforce_pool_user_project} response_data = self._sts_client.exchange_token( request=request, grant_type=_STS_GRANT_TYPE, @@ -299,6 +337,7 @@ def refresh(self, request): audience=self._audience, scopes=scopes, requested_token_type=_STS_REQUESTED_TOKEN_TYPE, + additional_options=additional_options, ) self.token = response_data.get("access_token") lifetime = datetime.timedelta(seconds=response_data.get("expires_in")) @@ -307,7 +346,7 @@ def refresh(self, request): @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) def with_quota_project(self, quota_project_id): # Return copy of instance with the provided quota project ID. - return self.__class__( + d = dict( audience=self._audience, subject_token_type=self._subject_token_type, token_url=self._token_url, @@ -318,7 +357,11 @@ def with_quota_project(self, quota_project_id): quota_project_id=quota_project_id, scopes=self._scopes, default_scopes=self._default_scopes, + workforce_pool_user_project=self._workforce_pool_user_project, ) + if not self.is_workforce_pool: + d.pop("workforce_pool_user_project") + return self.__class__(**d) def _initialize_impersonated_credentials(self): """Generates an impersonated credentials. @@ -336,7 +379,7 @@ def _initialize_impersonated_credentials(self): endpoint returned an error. """ # Return copy of instance with no service account impersonation. - source_credentials = self.__class__( + d = dict( audience=self._audience, subject_token_type=self._subject_token_type, token_url=self._token_url, @@ -347,7 +390,11 @@ def _initialize_impersonated_credentials(self): quota_project_id=self._quota_project_id, scopes=self._scopes, default_scopes=self._default_scopes, + workforce_pool_user_project=self._workforce_pool_user_project, ) + if not self.is_workforce_pool: + d.pop("workforce_pool_user_project") + source_credentials = self.__class__(**d) # Determine target_principal. target_principal = self.service_account_email diff --git a/packages/google-auth/google/auth/identity_pool.py b/packages/google-auth/google/auth/identity_pool.py index c331e09217a9..901fd62fb999 100644 --- a/packages/google-auth/google/auth/identity_pool.py +++ b/packages/google-auth/google/auth/identity_pool.py @@ -58,6 +58,7 @@ def __init__( quota_project_id=None, scopes=None, default_scopes=None, + workforce_pool_user_project=None, ): """Instantiates an external account credentials object from a file/URL. @@ -95,6 +96,11 @@ def __init__( authorization grant. default_scopes (Optional[Sequence[str]]): Default scopes passed by a Google client library. Use 'scopes' for user-defined scopes. + workforce_pool_user_project (Optona[str]): The optional workforce pool user + project number when the credential corresponds to a workforce pool and not + a workload identity pool. The underlying principal must still have + serviceusage.services.use IAM permission to use the project for + billing/quota. Raises: google.auth.exceptions.RefreshError: If an error is encountered during @@ -117,6 +123,7 @@ def __init__( quota_project_id=quota_project_id, scopes=scopes, default_scopes=default_scopes, + workforce_pool_user_project=workforce_pool_user_project, ) if not isinstance(credential_source, Mapping): self._credential_source_file = None @@ -255,6 +262,7 @@ def from_info(cls, info, **kwargs): client_secret=info.get("client_secret"), credential_source=info.get("credential_source"), quota_project_id=info.get("quota_project_id"), + workforce_pool_user_project=info.get("workforce_pool_user_project"), **kwargs ) diff --git a/packages/google-auth/tests/test_external_account.py b/packages/google-auth/tests/test_external_account.py index df6174f176cd..97f1564ef34f 100644 --- a/packages/google-auth/tests/test_external_account.py +++ b/packages/google-auth/tests/test_external_account.py @@ -37,6 +37,33 @@ "//iam.googleapis.com/locations/eu/workforcePools/pool-id/providers/provider-id", "//iam.googleapis.com/locations/eu/workforcePools/workloadIdentityPools/providers/provider-id", ] +# Workload identity pool audiences or invalid workforce pool audiences. +TEST_NON_USER_AUDIENCES = [ + # Legacy K8s audience format. + "identitynamespace:1f12345:my_provider", + ( + "//iam.googleapis.com/projects/123456/locations/" + "global/workloadIdentityPools/pool-id/providers/" + "provider-id" + ), + ( + "//iam.googleapis.com/projects/123456/locations/" + "eu/workloadIdentityPools/pool-id/providers/" + "provider-id" + ), + # Pool ID with workforcePools string. + ( + "//iam.googleapis.com/projects/123456/locations/" + "global/workloadIdentityPools/workforcePools/providers/" + "provider-id" + ), + # Unrealistic / incorrect workforce pool audiences. + "//iamgoogleapis.com/locations/eu/workforcePools/pool-id/providers/provider-id", + "//iam.googleapiscom/locations/eu/workforcePools/pool-id/providers/provider-id", + "//iam.googleapis.com/locations/workforcePools/pool-id/providers/provider-id", + "//iam.googleapis.com/locations/eu/workforcePool/pool-id/providers/provider-id", + "//iam.googleapis.com/locations//workforcePool/pool-id/providers/provider-id", +] class CredentialsImpl(external_account.Credentials): @@ -52,6 +79,7 @@ def __init__( quota_project_id=None, scopes=None, default_scopes=None, + workforce_pool_user_project=None, ): super(CredentialsImpl, self).__init__( audience=audience, @@ -64,6 +92,7 @@ def __init__( quota_project_id=quota_project_id, scopes=scopes, default_scopes=default_scopes, + workforce_pool_user_project=workforce_pool_user_project, ) self._counter = 0 @@ -83,7 +112,12 @@ class TestCredentials(object): "/locations/global/workloadIdentityPools/{}" "/providers/{}" ).format(PROJECT_NUMBER, POOL_ID, PROVIDER_ID) + WORKFORCE_AUDIENCE = ( + "//iam.googleapis.com/locations/global/workforcePools/{}/providers/{}" + ).format(POOL_ID, PROVIDER_ID) + WORKFORCE_POOL_USER_PROJECT = "WORKFORCE_POOL_USER_PROJECT_NUMBER" SUBJECT_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:jwt" + WORKFORCE_SUBJECT_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:id_token" CREDENTIAL_SOURCE = {"file": "/var/run/secrets/goog.id/token"} SUCCESS_RESPONSE = { "access_token": "ACCESS_TOKEN", @@ -146,6 +180,31 @@ def make_credentials( default_scopes=default_scopes, ) + @classmethod + def make_workforce_pool_credentials( + cls, + client_id=None, + client_secret=None, + quota_project_id=None, + scopes=None, + default_scopes=None, + service_account_impersonation_url=None, + workforce_pool_user_project=None, + ): + return CredentialsImpl( + audience=cls.WORKFORCE_AUDIENCE, + subject_token_type=cls.WORKFORCE_SUBJECT_TOKEN_TYPE, + token_url=cls.TOKEN_URL, + service_account_impersonation_url=service_account_impersonation_url, + credential_source=cls.CREDENTIAL_SOURCE, + client_id=client_id, + client_secret=client_secret, + quota_project_id=quota_project_id, + scopes=scopes, + default_scopes=default_scopes, + workforce_pool_user_project=workforce_pool_user_project, + ) + @classmethod def make_mock_request( cls, @@ -230,6 +289,21 @@ def test_default_state(self): assert credentials.requires_scopes assert not credentials.quota_project_id + def test_nonworkforce_with_workforce_pool_user_project(self): + with pytest.raises(ValueError) as excinfo: + CredentialsImpl( + audience=self.AUDIENCE, + subject_token_type=self.SUBJECT_TOKEN_TYPE, + token_url=self.TOKEN_URL, + credential_source=self.CREDENTIAL_SOURCE, + workforce_pool_user_project=self.WORKFORCE_POOL_USER_PROJECT, + ) + + assert excinfo.match( + "workforce_pool_user_project should not be set for non-workforce " + "pool credentials" + ) + def test_with_scopes(self): credentials = self.make_credentials() @@ -241,6 +315,23 @@ def test_with_scopes(self): assert scoped_credentials.has_scopes(["email"]) assert not scoped_credentials.requires_scopes + def test_with_scopes_workforce_pool(self): + credentials = self.make_workforce_pool_credentials( + workforce_pool_user_project=self.WORKFORCE_POOL_USER_PROJECT + ) + + assert not credentials.scopes + assert credentials.requires_scopes + + scoped_credentials = credentials.with_scopes(["email"]) + + assert scoped_credentials.has_scopes(["email"]) + assert not scoped_credentials.requires_scopes + assert ( + scoped_credentials.info.get("workforce_pool_user_project") + == self.WORKFORCE_POOL_USER_PROJECT + ) + def test_with_scopes_using_user_and_default_scopes(self): credentials = self.make_credentials() @@ -296,6 +387,7 @@ def test_with_scopes_full_options_propagated(self): quota_project_id=self.QUOTA_PROJECT_ID, scopes=["email"], default_scopes=["default2"], + workforce_pool_user_project=None, ) def test_with_quota_project(self): @@ -308,6 +400,22 @@ def test_with_quota_project(self): assert quota_project_creds.quota_project_id == "project-foo" + def test_with_quota_project_workforce_pool(self): + credentials = self.make_workforce_pool_credentials( + workforce_pool_user_project=self.WORKFORCE_POOL_USER_PROJECT + ) + + assert not credentials.scopes + assert not credentials.quota_project_id + + quota_project_creds = credentials.with_quota_project("project-foo") + + assert quota_project_creds.quota_project_id == "project-foo" + assert ( + quota_project_creds.info.get("workforce_pool_user_project") + == self.WORKFORCE_POOL_USER_PROJECT + ) + def test_with_quota_project_full_options_propagated(self): credentials = self.make_credentials( client_id=CLIENT_ID, @@ -336,6 +444,7 @@ def test_with_quota_project_full_options_propagated(self): quota_project_id="project-foo", scopes=self.SCOPES, default_scopes=["default1"], + workforce_pool_user_project=None, ) def test_with_invalid_impersonation_target_principal(self): @@ -359,6 +468,20 @@ def test_info(self): "credential_source": self.CREDENTIAL_SOURCE.copy(), } + def test_info_workforce_pool(self): + credentials = self.make_workforce_pool_credentials( + workforce_pool_user_project=self.WORKFORCE_POOL_USER_PROJECT + ) + + assert credentials.info == { + "type": "external_account", + "audience": self.WORKFORCE_AUDIENCE, + "subject_token_type": self.WORKFORCE_SUBJECT_TOKEN_TYPE, + "token_url": self.TOKEN_URL, + "credential_source": self.CREDENTIAL_SOURCE.copy(), + "workforce_pool_user_project": self.WORKFORCE_POOL_USER_PROJECT, + } + def test_info_with_full_options(self): credentials = self.make_credentials( client_id=CLIENT_ID, @@ -391,36 +514,7 @@ def test_service_account_email_with_impersonation(self): assert credentials.service_account_email == SERVICE_ACCOUNT_EMAIL - @pytest.mark.parametrize( - "audience", - # Workload identity pool audiences or invalid workforce pool audiences. - [ - # Legacy K8s audience format. - "identitynamespace:1f12345:my_provider", - ( - "//iam.googleapis.com/projects/123456/locations/" - "global/workloadIdentityPools/pool-id/providers/" - "provider-id" - ), - ( - "//iam.googleapis.com/projects/123456/locations/" - "eu/workloadIdentityPools/pool-id/providers/" - "provider-id" - ), - # Pool ID with workforcePools string. - ( - "//iam.googleapis.com/projects/123456/locations/" - "global/workloadIdentityPools/workforcePools/providers/" - "provider-id" - ), - # Unrealistic / incorrect workforce pool audiences. - "//iamgoogleapis.com/locations/eu/workforcePools/pool-id/providers/provider-id", - "//iam.googleapiscom/locations/eu/workforcePools/pool-id/providers/provider-id", - "//iam.googleapis.com/locations/workforcePools/pool-id/providers/provider-id", - "//iam.googleapis.com/locations/eu/workforcePool/pool-id/providers/provider-id", - "//iam.googleapis.com/locations//workforcePool/pool-id/providers/provider-id", - ], - ) + @pytest.mark.parametrize("audience", TEST_NON_USER_AUDIENCES) def test_is_user_with_non_users(self, audience): credentials = CredentialsImpl( audience=audience, @@ -458,6 +552,43 @@ def test_is_user_with_users_and_impersonation(self, audience): # not a user. assert credentials.is_user is False + @pytest.mark.parametrize("audience", TEST_NON_USER_AUDIENCES) + def test_is_workforce_pool_with_non_users(self, audience): + credentials = CredentialsImpl( + audience=audience, + subject_token_type=self.SUBJECT_TOKEN_TYPE, + token_url=self.TOKEN_URL, + credential_source=self.CREDENTIAL_SOURCE, + ) + + assert credentials.is_workforce_pool is False + + @pytest.mark.parametrize("audience", TEST_USER_AUDIENCES) + def test_is_workforce_pool_with_users(self, audience): + credentials = CredentialsImpl( + audience=audience, + subject_token_type=self.SUBJECT_TOKEN_TYPE, + token_url=self.TOKEN_URL, + credential_source=self.CREDENTIAL_SOURCE, + ) + + assert credentials.is_workforce_pool is True + + @pytest.mark.parametrize("audience", TEST_USER_AUDIENCES) + def test_is_workforce_pool_with_users_and_impersonation(self, audience): + # Initialize the credentials with workforce audience and service account + # impersonation. + credentials = CredentialsImpl( + audience=audience, + subject_token_type=self.SUBJECT_TOKEN_TYPE, + token_url=self.TOKEN_URL, + credential_source=self.CREDENTIAL_SOURCE, + service_account_impersonation_url=self.SERVICE_ACCOUNT_IMPERSONATION_URL, + ) + + # Even though impersonation is used, is_workforce_pool should still return True. + assert credentials.is_workforce_pool is True + @mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) def test_refresh_without_client_auth_success(self, unused_utcnow): response = self.SUCCESS_RESPONSE.copy() @@ -485,6 +616,110 @@ def test_refresh_without_client_auth_success(self, unused_utcnow): assert not credentials.expired assert credentials.token == response["access_token"] + @mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) + def test_refresh_workforce_without_client_auth_success(self, unused_utcnow): + response = self.SUCCESS_RESPONSE.copy() + # Test custom expiration to confirm expiry is set correctly. + response["expires_in"] = 2800 + expected_expiry = datetime.datetime.min + datetime.timedelta( + seconds=response["expires_in"] + ) + headers = {"Content-Type": "application/x-www-form-urlencoded"} + request_data = { + "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange", + "audience": self.WORKFORCE_AUDIENCE, + "requested_token_type": "urn:ietf:params:oauth:token-type:access_token", + "subject_token": "subject_token_0", + "subject_token_type": self.WORKFORCE_SUBJECT_TOKEN_TYPE, + "options": urllib.parse.quote( + json.dumps({"userProject": self.WORKFORCE_POOL_USER_PROJECT}) + ), + } + request = self.make_mock_request(status=http.client.OK, data=response) + credentials = self.make_workforce_pool_credentials( + workforce_pool_user_project=self.WORKFORCE_POOL_USER_PROJECT + ) + + credentials.refresh(request) + + self.assert_token_request_kwargs(request.call_args[1], headers, request_data) + assert credentials.valid + assert credentials.expiry == expected_expiry + assert not credentials.expired + assert credentials.token == response["access_token"] + + @mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) + def test_refresh_workforce_with_client_auth_success(self, unused_utcnow): + response = self.SUCCESS_RESPONSE.copy() + # Test custom expiration to confirm expiry is set correctly. + response["expires_in"] = 2800 + expected_expiry = datetime.datetime.min + datetime.timedelta( + seconds=response["expires_in"] + ) + headers = { + "Content-Type": "application/x-www-form-urlencoded", + "Authorization": "Basic {}".format(BASIC_AUTH_ENCODING), + } + request_data = { + "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange", + "audience": self.WORKFORCE_AUDIENCE, + "requested_token_type": "urn:ietf:params:oauth:token-type:access_token", + "subject_token": "subject_token_0", + "subject_token_type": self.WORKFORCE_SUBJECT_TOKEN_TYPE, + } + request = self.make_mock_request(status=http.client.OK, data=response) + # Client Auth will have higher priority over workforce_pool_user_project. + credentials = self.make_workforce_pool_credentials( + client_id=CLIENT_ID, + client_secret=CLIENT_SECRET, + workforce_pool_user_project=self.WORKFORCE_POOL_USER_PROJECT, + ) + + credentials.refresh(request) + + self.assert_token_request_kwargs(request.call_args[1], headers, request_data) + assert credentials.valid + assert credentials.expiry == expected_expiry + assert not credentials.expired + assert credentials.token == response["access_token"] + + @mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) + def test_refresh_workforce_with_client_auth_and_no_workforce_project_success( + self, unused_utcnow + ): + response = self.SUCCESS_RESPONSE.copy() + # Test custom expiration to confirm expiry is set correctly. + response["expires_in"] = 2800 + expected_expiry = datetime.datetime.min + datetime.timedelta( + seconds=response["expires_in"] + ) + headers = { + "Content-Type": "application/x-www-form-urlencoded", + "Authorization": "Basic {}".format(BASIC_AUTH_ENCODING), + } + request_data = { + "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange", + "audience": self.WORKFORCE_AUDIENCE, + "requested_token_type": "urn:ietf:params:oauth:token-type:access_token", + "subject_token": "subject_token_0", + "subject_token_type": self.WORKFORCE_SUBJECT_TOKEN_TYPE, + } + request = self.make_mock_request(status=http.client.OK, data=response) + # Client Auth will be sufficient for user project determination. + credentials = self.make_workforce_pool_credentials( + client_id=CLIENT_ID, + client_secret=CLIENT_SECRET, + workforce_pool_user_project=None, + ) + + credentials.refresh(request) + + self.assert_token_request_kwargs(request.call_args[1], headers, request_data) + assert credentials.valid + assert credentials.expiry == expected_expiry + assert not credentials.expired + assert credentials.token == response["access_token"] + def test_refresh_impersonation_without_client_auth_success(self): # Simulate service account access token expires in 2800 seconds. expire_time = ( @@ -549,6 +784,74 @@ def test_refresh_impersonation_without_client_auth_success(self): assert not credentials.expired assert credentials.token == impersonation_response["accessToken"] + def test_refresh_workforce_impersonation_without_client_auth_success(self): + # Simulate service account access token expires in 2800 seconds. + expire_time = ( + _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=2800) + ).isoformat("T") + "Z" + expected_expiry = datetime.datetime.strptime(expire_time, "%Y-%m-%dT%H:%M:%SZ") + # STS token exchange request/response. + token_response = self.SUCCESS_RESPONSE.copy() + token_headers = {"Content-Type": "application/x-www-form-urlencoded"} + token_request_data = { + "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange", + "audience": self.WORKFORCE_AUDIENCE, + "requested_token_type": "urn:ietf:params:oauth:token-type:access_token", + "subject_token": "subject_token_0", + "subject_token_type": self.WORKFORCE_SUBJECT_TOKEN_TYPE, + "scope": "https://www.googleapis.com/auth/iam", + "options": urllib.parse.quote( + json.dumps({"userProject": self.WORKFORCE_POOL_USER_PROJECT}) + ), + } + # Service account impersonation request/response. + impersonation_response = { + "accessToken": "SA_ACCESS_TOKEN", + "expireTime": expire_time, + } + impersonation_headers = { + "Content-Type": "application/json", + "authorization": "Bearer {}".format(token_response["access_token"]), + } + impersonation_request_data = { + "delegates": None, + "scope": self.SCOPES, + "lifetime": "3600s", + } + # Initialize mock request to handle token exchange and service account + # impersonation request. + request = self.make_mock_request( + status=http.client.OK, + data=token_response, + impersonation_status=http.client.OK, + impersonation_data=impersonation_response, + ) + # Initialize credentials with service account impersonation. + credentials = self.make_workforce_pool_credentials( + service_account_impersonation_url=self.SERVICE_ACCOUNT_IMPERSONATION_URL, + scopes=self.SCOPES, + workforce_pool_user_project=self.WORKFORCE_POOL_USER_PROJECT, + ) + + credentials.refresh(request) + + # Only 2 requests should be processed. + assert len(request.call_args_list) == 2 + # Verify token exchange request parameters. + self.assert_token_request_kwargs( + request.call_args_list[0][1], token_headers, token_request_data + ) + # Verify service account impersonation request parameters. + self.assert_impersonation_request_kwargs( + request.call_args_list[1][1], + impersonation_headers, + impersonation_request_data, + ) + assert credentials.valid + assert credentials.expiry == expected_expiry + assert not credentials.expired + assert credentials.token == impersonation_response["accessToken"] + def test_refresh_without_client_auth_success_explicit_user_scopes_ignore_default_scopes( self, ): @@ -822,6 +1125,22 @@ def test_apply_without_quota_project_id(self): "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]) } + def test_apply_workforce_without_quota_project_id(self): + headers = {} + request = self.make_mock_request( + status=http.client.OK, data=self.SUCCESS_RESPONSE + ) + credentials = self.make_workforce_pool_credentials( + workforce_pool_user_project=self.WORKFORCE_POOL_USER_PROJECT + ) + + credentials.refresh(request) + credentials.apply(headers) + + assert headers == { + "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]) + } + def test_apply_impersonation_without_quota_project_id(self): expire_time = ( _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=3600) @@ -926,6 +1245,31 @@ def test_before_request(self): "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]), } + def test_before_request_workforce(self): + headers = {"other": "header-value"} + request = self.make_mock_request( + status=http.client.OK, data=self.SUCCESS_RESPONSE + ) + credentials = self.make_workforce_pool_credentials( + workforce_pool_user_project=self.WORKFORCE_POOL_USER_PROJECT + ) + + # First call should call refresh, setting the token. + credentials.before_request(request, "POST", "https://example.com/api", headers) + + assert headers == { + "other": "header-value", + "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]), + } + + # Second call shouldn't call refresh. + credentials.before_request(request, "POST", "https://example.com/api", headers) + + assert headers == { + "other": "header-value", + "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]), + } + def test_before_request_impersonation(self): expire_time = ( _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=3600) @@ -1091,6 +1435,17 @@ def test_project_number_determinable(self): assert credentials.project_number == self.PROJECT_NUMBER + def test_project_number_workforce(self): + credentials = CredentialsImpl( + audience=self.WORKFORCE_AUDIENCE, + subject_token_type=self.WORKFORCE_SUBJECT_TOKEN_TYPE, + token_url=self.TOKEN_URL, + credential_source=self.CREDENTIAL_SOURCE, + workforce_pool_user_project=self.WORKFORCE_POOL_USER_PROJECT, + ) + + assert credentials.project_number is None + def test_project_id_without_scopes(self): # Initialize credentials with no scopes. credentials = CredentialsImpl( @@ -1190,6 +1545,68 @@ def test_get_project_id_cloud_resource_manager_success(self): # No additional requests. assert len(request.call_args_list) == 3 + def test_workforce_pool_get_project_id_cloud_resource_manager_success(self): + # STS token exchange request/response. + token_headers = {"Content-Type": "application/x-www-form-urlencoded"} + token_request_data = { + "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange", + "audience": self.WORKFORCE_AUDIENCE, + "requested_token_type": "urn:ietf:params:oauth:token-type:access_token", + "subject_token": "subject_token_0", + "subject_token_type": self.WORKFORCE_SUBJECT_TOKEN_TYPE, + "scope": "scope1 scope2", + "options": urllib.parse.quote( + json.dumps({"userProject": self.WORKFORCE_POOL_USER_PROJECT}) + ), + } + # Initialize mock request to handle token exchange and cloud resource + # manager request. + request = self.make_mock_request( + status=http.client.OK, + data=self.SUCCESS_RESPONSE.copy(), + cloud_resource_manager_status=http.client.OK, + cloud_resource_manager_data=self.CLOUD_RESOURCE_MANAGER_SUCCESS_RESPONSE, + ) + credentials = self.make_workforce_pool_credentials( + scopes=self.SCOPES, + quota_project_id=self.QUOTA_PROJECT_ID, + workforce_pool_user_project=self.WORKFORCE_POOL_USER_PROJECT, + ) + + # Expected project ID from cloud resource manager response should be returned. + project_id = credentials.get_project_id(request) + + assert project_id == self.PROJECT_ID + # 2 requests should be processed. + assert len(request.call_args_list) == 2 + # Verify token exchange request parameters. + self.assert_token_request_kwargs( + request.call_args_list[0][1], token_headers, token_request_data + ) + # In the process of getting project ID, an access token should be + # retrieved. + assert credentials.valid + assert not credentials.expired + assert credentials.token == self.SUCCESS_RESPONSE["access_token"] + # Verify cloud resource manager request parameters. + self.assert_resource_manager_request_kwargs( + request.call_args_list[1][1], + self.WORKFORCE_POOL_USER_PROJECT, + { + "x-goog-user-project": self.QUOTA_PROJECT_ID, + "authorization": "Bearer {}".format( + self.SUCCESS_RESPONSE["access_token"] + ), + }, + ) + + # Calling get_project_id again should return the cached project_id. + project_id = credentials.get_project_id(request) + + assert project_id == self.PROJECT_ID + # No additional requests. + assert len(request.call_args_list) == 2 + def test_get_project_id_cloud_resource_manager_error(self): # Simulate resource doesn't have sufficient permissions to access # cloud resource manager. diff --git a/packages/google-auth/tests/test_identity_pool.py b/packages/google-auth/tests/test_identity_pool.py index efe11b082320..e90e2880dfe2 100644 --- a/packages/google-auth/tests/test_identity_pool.py +++ b/packages/google-auth/tests/test_identity_pool.py @@ -53,6 +53,11 @@ TOKEN_URL = "https://sts.googleapis.com/v1/token" SUBJECT_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:jwt" AUDIENCE = "//iam.googleapis.com/projects/123456/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID" +WORKFORCE_AUDIENCE = ( + "//iam.googleapis.com/locations/global/workforcePools/POOL_ID/providers/PROVIDER_ID" +) +WORKFORCE_SUBJECT_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:id_token" +WORKFORCE_POOL_USER_PROJECT = "WORKFORCE_POOL_USER_PROJECT_NUMBER" class TestCredentials(object): @@ -158,6 +163,7 @@ def assert_underlying_credentials_refresh( credential_data=None, scopes=None, default_scopes=None, + workforce_pool_user_project=None, ): """Utility to assert that a credentials are initialized with the expected attributes by calling refresh functionality and confirming response matches @@ -183,6 +189,10 @@ def assert_underlying_credentials_refresh( "subject_token": subject_token, "subject_token_type": subject_token_type, } + if workforce_pool_user_project: + token_request_data["options"] = urllib.parse.quote( + json.dumps({"userProject": workforce_pool_user_project}) + ) if service_account_impersonation_url: # Service account impersonation request/response. @@ -250,6 +260,8 @@ def assert_underlying_credentials_refresh( @classmethod def make_credentials( cls, + audience=AUDIENCE, + subject_token_type=SUBJECT_TOKEN_TYPE, client_id=None, client_secret=None, quota_project_id=None, @@ -257,10 +269,11 @@ def make_credentials( default_scopes=None, service_account_impersonation_url=None, credential_source=None, + workforce_pool_user_project=None, ): return identity_pool.Credentials( - audience=AUDIENCE, - subject_token_type=SUBJECT_TOKEN_TYPE, + audience=audience, + subject_token_type=subject_token_type, token_url=TOKEN_URL, service_account_impersonation_url=service_account_impersonation_url, credential_source=credential_source, @@ -269,6 +282,7 @@ def make_credentials( quota_project_id=quota_project_id, scopes=scopes, default_scopes=default_scopes, + workforce_pool_user_project=workforce_pool_user_project, ) @mock.patch.object(identity_pool.Credentials, "__init__", return_value=None) @@ -297,6 +311,7 @@ def test_from_info_full_options(self, mock_init): client_secret=CLIENT_SECRET, credential_source=self.CREDENTIAL_SOURCE_TEXT, quota_project_id=QUOTA_PROJECT_ID, + workforce_pool_user_project=None, ) @mock.patch.object(identity_pool.Credentials, "__init__", return_value=None) @@ -321,6 +336,33 @@ def test_from_info_required_options_only(self, mock_init): client_secret=None, credential_source=self.CREDENTIAL_SOURCE_TEXT, quota_project_id=None, + workforce_pool_user_project=None, + ) + + @mock.patch.object(identity_pool.Credentials, "__init__", return_value=None) + def test_from_info_workforce_pool(self, mock_init): + credentials = identity_pool.Credentials.from_info( + { + "audience": WORKFORCE_AUDIENCE, + "subject_token_type": WORKFORCE_SUBJECT_TOKEN_TYPE, + "token_url": TOKEN_URL, + "credential_source": self.CREDENTIAL_SOURCE_TEXT, + "workforce_pool_user_project": WORKFORCE_POOL_USER_PROJECT, + } + ) + + # Confirm identity_pool.Credentials instantiated with expected attributes. + assert isinstance(credentials, identity_pool.Credentials) + mock_init.assert_called_once_with( + audience=WORKFORCE_AUDIENCE, + subject_token_type=WORKFORCE_SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + service_account_impersonation_url=None, + client_id=None, + client_secret=None, + credential_source=self.CREDENTIAL_SOURCE_TEXT, + quota_project_id=None, + workforce_pool_user_project=WORKFORCE_POOL_USER_PROJECT, ) @mock.patch.object(identity_pool.Credentials, "__init__", return_value=None) @@ -350,6 +392,7 @@ def test_from_file_full_options(self, mock_init, tmpdir): client_secret=CLIENT_SECRET, credential_source=self.CREDENTIAL_SOURCE_TEXT, quota_project_id=QUOTA_PROJECT_ID, + workforce_pool_user_project=None, ) @mock.patch.object(identity_pool.Credentials, "__init__", return_value=None) @@ -375,6 +418,46 @@ def test_from_file_required_options_only(self, mock_init, tmpdir): client_secret=None, credential_source=self.CREDENTIAL_SOURCE_TEXT, quota_project_id=None, + workforce_pool_user_project=None, + ) + + @mock.patch.object(identity_pool.Credentials, "__init__", return_value=None) + def test_from_file_workforce_pool(self, mock_init, tmpdir): + info = { + "audience": WORKFORCE_AUDIENCE, + "subject_token_type": WORKFORCE_SUBJECT_TOKEN_TYPE, + "token_url": TOKEN_URL, + "credential_source": self.CREDENTIAL_SOURCE_TEXT, + "workforce_pool_user_project": WORKFORCE_POOL_USER_PROJECT, + } + config_file = tmpdir.join("config.json") + config_file.write(json.dumps(info)) + credentials = identity_pool.Credentials.from_file(str(config_file)) + + # Confirm identity_pool.Credentials instantiated with expected attributes. + assert isinstance(credentials, identity_pool.Credentials) + mock_init.assert_called_once_with( + audience=WORKFORCE_AUDIENCE, + subject_token_type=WORKFORCE_SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + service_account_impersonation_url=None, + client_id=None, + client_secret=None, + credential_source=self.CREDENTIAL_SOURCE_TEXT, + quota_project_id=None, + workforce_pool_user_project=WORKFORCE_POOL_USER_PROJECT, + ) + + def test_constructor_nonworkforce_with_workforce_pool_user_project(self): + with pytest.raises(ValueError) as excinfo: + self.make_credentials( + audience=AUDIENCE, + workforce_pool_user_project=WORKFORCE_POOL_USER_PROJECT, + ) + + assert excinfo.match( + "workforce_pool_user_project should not be set for non-workforce " + "pool credentials" ) def test_constructor_invalid_options(self): @@ -430,6 +513,23 @@ def test_constructor_missing_subject_token_field_name(self): r"Missing subject_token_field_name for JSON credential_source format" ) + def test_info_with_workforce_pool_user_project(self): + credentials = self.make_credentials( + audience=WORKFORCE_AUDIENCE, + subject_token_type=WORKFORCE_SUBJECT_TOKEN_TYPE, + credential_source=self.CREDENTIAL_SOURCE_TEXT_URL.copy(), + workforce_pool_user_project=WORKFORCE_POOL_USER_PROJECT, + ) + + assert credentials.info == { + "type": "external_account", + "audience": WORKFORCE_AUDIENCE, + "subject_token_type": WORKFORCE_SUBJECT_TOKEN_TYPE, + "token_url": TOKEN_URL, + "credential_source": self.CREDENTIAL_SOURCE_TEXT_URL, + "workforce_pool_user_project": WORKFORCE_POOL_USER_PROJECT, + } + def test_info_with_file_credential_source(self): credentials = self.make_credentials( credential_source=self.CREDENTIAL_SOURCE_TEXT_URL.copy() @@ -557,6 +657,115 @@ def test_refresh_text_file_success_without_impersonation_ignore_default_scopes( default_scopes=["ignored"], ) + def test_refresh_workforce_success_with_client_auth_without_impersonation(self): + credentials = self.make_credentials( + audience=WORKFORCE_AUDIENCE, + subject_token_type=WORKFORCE_SUBJECT_TOKEN_TYPE, + client_id=CLIENT_ID, + client_secret=CLIENT_SECRET, + # Test with text format type. + credential_source=self.CREDENTIAL_SOURCE_TEXT, + scopes=SCOPES, + # This will be ignored in favor of client auth. + workforce_pool_user_project=WORKFORCE_POOL_USER_PROJECT, + ) + + self.assert_underlying_credentials_refresh( + credentials=credentials, + audience=WORKFORCE_AUDIENCE, + subject_token=TEXT_FILE_SUBJECT_TOKEN, + subject_token_type=WORKFORCE_SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + service_account_impersonation_url=None, + basic_auth_encoding=BASIC_AUTH_ENCODING, + quota_project_id=None, + used_scopes=SCOPES, + scopes=SCOPES, + workforce_pool_user_project=None, + ) + + def test_refresh_workforce_success_with_client_auth_and_no_workforce_project(self): + credentials = self.make_credentials( + audience=WORKFORCE_AUDIENCE, + subject_token_type=WORKFORCE_SUBJECT_TOKEN_TYPE, + client_id=CLIENT_ID, + client_secret=CLIENT_SECRET, + # Test with text format type. + credential_source=self.CREDENTIAL_SOURCE_TEXT, + scopes=SCOPES, + # This is not needed when client Auth is used. + workforce_pool_user_project=None, + ) + + self.assert_underlying_credentials_refresh( + credentials=credentials, + audience=WORKFORCE_AUDIENCE, + subject_token=TEXT_FILE_SUBJECT_TOKEN, + subject_token_type=WORKFORCE_SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + service_account_impersonation_url=None, + basic_auth_encoding=BASIC_AUTH_ENCODING, + quota_project_id=None, + used_scopes=SCOPES, + scopes=SCOPES, + workforce_pool_user_project=None, + ) + + def test_refresh_workforce_success_without_client_auth_without_impersonation(self): + credentials = self.make_credentials( + audience=WORKFORCE_AUDIENCE, + subject_token_type=WORKFORCE_SUBJECT_TOKEN_TYPE, + client_id=None, + client_secret=None, + # Test with text format type. + credential_source=self.CREDENTIAL_SOURCE_TEXT, + scopes=SCOPES, + # This will not be ignored as client auth is not used. + workforce_pool_user_project=WORKFORCE_POOL_USER_PROJECT, + ) + + self.assert_underlying_credentials_refresh( + credentials=credentials, + audience=WORKFORCE_AUDIENCE, + subject_token=TEXT_FILE_SUBJECT_TOKEN, + subject_token_type=WORKFORCE_SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + service_account_impersonation_url=None, + basic_auth_encoding=None, + quota_project_id=None, + used_scopes=SCOPES, + scopes=SCOPES, + workforce_pool_user_project=WORKFORCE_POOL_USER_PROJECT, + ) + + def test_refresh_workforce_success_without_client_auth_with_impersonation(self): + credentials = self.make_credentials( + audience=WORKFORCE_AUDIENCE, + subject_token_type=WORKFORCE_SUBJECT_TOKEN_TYPE, + client_id=None, + client_secret=None, + service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + # Test with text format type. + credential_source=self.CREDENTIAL_SOURCE_TEXT, + scopes=SCOPES, + # This will not be ignored as client auth is not used. + workforce_pool_user_project=WORKFORCE_POOL_USER_PROJECT, + ) + + self.assert_underlying_credentials_refresh( + credentials=credentials, + audience=WORKFORCE_AUDIENCE, + subject_token=TEXT_FILE_SUBJECT_TOKEN, + subject_token_type=WORKFORCE_SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + basic_auth_encoding=None, + quota_project_id=None, + used_scopes=SCOPES, + scopes=SCOPES, + workforce_pool_user_project=WORKFORCE_POOL_USER_PROJECT, + ) + def test_refresh_text_file_success_without_impersonation_use_default_scopes(self): credentials = self.make_credentials( client_id=CLIENT_ID, From 3d238cb0ffc599cdf40c722441d3a2bae1c69f15 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Tue, 21 Sep 2021 17:14:29 -0400 Subject: [PATCH 477/966] chore: remove 'six' (#871) --- packages/google-auth/tests/test_downscoped.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/google-auth/tests/test_downscoped.py b/packages/google-auth/tests/test_downscoped.py index 9ca95f5aa856..a686391c012d 100644 --- a/packages/google-auth/tests/test_downscoped.py +++ b/packages/google-auth/tests/test_downscoped.py @@ -13,12 +13,12 @@ # limitations under the License. import datetime +import http.client import json +import urllib import mock import pytest -from six.moves import http_client -from six.moves import urllib from google.auth import _helpers from google.auth import credentials @@ -461,7 +461,7 @@ def make_credentials(source_credentials=SourceCredentials(), quota_project_id=No ) @staticmethod - def make_mock_request(data, status=http_client.OK): + def make_mock_request(data, status=http.client.OK): response = mock.create_autospec(transport.Response, instance=True) response.status = status response.data = json.dumps(data).encode("utf-8") @@ -521,7 +521,7 @@ def test_refresh(self, unused_utcnow): "requested_token_type": REQUESTED_TOKEN_TYPE, "options": urllib.parse.quote(json.dumps(CREDENTIAL_ACCESS_BOUNDARY_JSON)), } - request = self.make_mock_request(status=http_client.OK, data=response) + request = self.make_mock_request(status=http.client.OK, data=response) source_credentials = SourceCredentials() credentials = self.make_credentials(source_credentials=source_credentials) @@ -563,7 +563,7 @@ def test_refresh_without_response_expires_in(self, unused_utcnow): "requested_token_type": REQUESTED_TOKEN_TYPE, "options": urllib.parse.quote(json.dumps(CREDENTIAL_ACCESS_BOUNDARY_JSON)), } - request = self.make_mock_request(status=http_client.OK, data=response) + request = self.make_mock_request(status=http.client.OK, data=response) credentials = self.make_credentials(source_credentials=source_credentials) # Spy on calls to source credentials refresh to confirm the expected request @@ -583,7 +583,7 @@ def test_refresh_without_response_expires_in(self, unused_utcnow): def test_refresh_token_exchange_error(self): request = self.make_mock_request( - status=http_client.BAD_REQUEST, data=ERROR_RESPONSE + status=http.client.BAD_REQUEST, data=ERROR_RESPONSE ) credentials = self.make_credentials() @@ -612,7 +612,7 @@ def test_refresh_source_credentials_refresh_error(self): def test_apply_without_quota_project_id(self): headers = {} - request = self.make_mock_request(status=http_client.OK, data=SUCCESS_RESPONSE) + request = self.make_mock_request(status=http.client.OK, data=SUCCESS_RESPONSE) credentials = self.make_credentials() credentials.refresh(request) @@ -624,7 +624,7 @@ def test_apply_without_quota_project_id(self): def test_apply_with_quota_project_id(self): headers = {"other": "header-value"} - request = self.make_mock_request(status=http_client.OK, data=SUCCESS_RESPONSE) + request = self.make_mock_request(status=http.client.OK, data=SUCCESS_RESPONSE) credentials = self.make_credentials(quota_project_id=QUOTA_PROJECT_ID) credentials.refresh(request) @@ -638,7 +638,7 @@ def test_apply_with_quota_project_id(self): def test_before_request(self): headers = {"other": "header-value"} - request = self.make_mock_request(status=http_client.OK, data=SUCCESS_RESPONSE) + request = self.make_mock_request(status=http.client.OK, data=SUCCESS_RESPONSE) credentials = self.make_credentials() # First call should call refresh, setting the token. @@ -662,7 +662,7 @@ def test_before_request(self): @mock.patch("google.auth._helpers.utcnow") def test_before_request_expired(self, utcnow): headers = {} - request = self.make_mock_request(status=http_client.OK, data=SUCCESS_RESPONSE) + request = self.make_mock_request(status=http.client.OK, data=SUCCESS_RESPONSE) credentials = self.make_credentials() credentials.token = "token" utcnow.return_value = datetime.datetime.min From 65c344204733a40309ffe4498a8a34ebd49cbd19 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 27 Sep 2021 16:02:19 +0000 Subject: [PATCH 478/966] chore: release 2.2.0 (#872) :robot: I have created a release \*beep\* \*boop\* --- ## [2.2.0](https://www.github.com/googleapis/google-auth-library-python/compare/v2.1.0...v2.2.0) (2021-09-21) ### Features * add support for workforce pool credentials ([#868](https://www.github.com/googleapis/google-auth-library-python/issues/868)) ([993bab2](https://www.github.com/googleapis/google-auth-library-python/commit/993bab2aaacf3034e09d9f0f25d36c0e815d3a29)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 056aeac6e448..3e5b77b0e9ff 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.2.0](https://www.github.com/googleapis/google-auth-library-python/compare/v2.1.0...v2.2.0) (2021-09-21) + + +### Features + +* add support for workforce pool credentials ([#868](https://www.github.com/googleapis/google-auth-library-python/issues/868)) ([993bab2](https://www.github.com/googleapis/google-auth-library-python/commit/993bab2aaacf3034e09d9f0f25d36c0e815d3a29)) + ## [2.1.0](https://www.github.com/googleapis/google-auth-library-python/compare/v2.0.2...v2.1.0) (2021-09-10) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index a02dc8aec8c7..b423306c097a 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.1.0" +__version__ = "2.2.0" From 96251b509067f1596d82062844428442ee8dffef Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Tue, 28 Sep 2021 13:40:43 -0700 Subject: [PATCH 479/966] fix: disable self signed jwt for domain wide delegation (#873) --- .../google/oauth2/service_account.py | 4 ++- .../tests/oauth2/test_service_account.py | 29 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/google/oauth2/service_account.py b/packages/google-auth/google/oauth2/service_account.py index 8f18f26ea17b..ecaac038c1e5 100644 --- a/packages/google-auth/google/oauth2/service_account.py +++ b/packages/google-auth/google/oauth2/service_account.py @@ -399,7 +399,9 @@ def _make_authorization_grant_assertion(self): @_helpers.copy_docstring(credentials.Credentials) def refresh(self, request): - if self._jwt_credentials is not None: + # Since domain wide delegation doesn't work with self signed JWT. If + # subject exists, then we should not use self signed JWT. + if self._subject is None and self._jwt_credentials is not None: self._jwt_credentials.refresh(request) self.token = self._jwt_credentials.token self.expiry = self._jwt_credentials.expiry diff --git a/packages/google-auth/tests/oauth2/test_service_account.py b/packages/google-auth/tests/oauth2/test_service_account.py index 370438f48d04..531fc4c9ed6c 100644 --- a/packages/google-auth/tests/oauth2/test_service_account.py +++ b/packages/google-auth/tests/oauth2/test_service_account.py @@ -371,6 +371,35 @@ def test_refresh_with_jwt_credentials(self, make_jwt): assert credentials.token == token assert credentials.expiry == expiry + @mock.patch("google.oauth2._client.jwt_grant", autospec=True) + @mock.patch("google.auth.jwt.Credentials.refresh", autospec=True) + def test_refresh_jwt_not_used_for_domain_wide_delegation( + self, self_signed_jwt_refresh, jwt_grant + ): + # Create a domain wide delegation credentials by setting the subject. + credentials = service_account.Credentials( + SIGNER, + self.SERVICE_ACCOUNT_EMAIL, + self.TOKEN_URI, + always_use_jwt_access=True, + subject="subject", + ) + credentials._create_self_signed_jwt("https://pubsub.googleapis.com") + jwt_grant.return_value = ( + "token", + _helpers.utcnow() + datetime.timedelta(seconds=500), + {}, + ) + request = mock.create_autospec(transport.Request, instance=True) + + # Refresh credentials + credentials.refresh(request) + + # Make sure we are using jwt_grant and not self signed JWT refresh + # method to obtain the token. + assert jwt_grant.called + assert not self_signed_jwt_refresh.called + class TestIDTokenCredentials(object): SERVICE_ACCOUNT_EMAIL = "service-account@example.com" From d84779f221f404d9d1efa57275f1e6399e931a18 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Tue, 28 Sep 2021 15:33:09 -0600 Subject: [PATCH 480/966] chore: update authorized_user.json (#876) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10323 -> 10323 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index ed178d8bd92b0f5633c3447df729de58d65408a9..187c95021317150814dde4f50b0cc35bcf520d80 100644 GIT binary patch literal 10323 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 literal 10323 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 Date: Tue, 28 Sep 2021 14:44:05 -0700 Subject: [PATCH 481/966] chore: release 2.2.1 (#875) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> Co-authored-by: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 3e5b77b0e9ff..997cac979049 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +### [2.2.1](https://www.github.com/googleapis/google-auth-library-python/compare/v2.2.0...v2.2.1) (2021-09-28) + + +### Bug Fixes + +* disable self signed jwt for domain wide delegation ([#873](https://www.github.com/googleapis/google-auth-library-python/issues/873)) ([0cd15e2](https://www.github.com/googleapis/google-auth-library-python/commit/0cd15e2ae20f7caddf9eb2d069064058d3c14ad7)) + ## [2.2.0](https://www.github.com/googleapis/google-auth-library-python/compare/v2.1.0...v2.2.0) (2021-09-21) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index b423306c097a..8cc169a2f48b 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.2.0" +__version__ = "2.2.1" From 65e5b34f9421bd846795a1fb4fe1989a13d3212f Mon Sep 17 00:00:00 2001 From: bojeil-google Date: Thu, 30 Sep 2021 23:19:51 -0700 Subject: [PATCH 482/966] fix: ADC with impersonated workforce pools (#877) While service account impersonation is uncommonly used with workforce pool external credentials, there is a bug where the following commands raise exceptions when impersonated workforce pools are used: - `google.auth.default()` - `google.auth.load_credentials_from_file()` The issue is due to `google.auth.aws.Credentials` not supporting the `workforce_pool_user_project` argument in the constructor, unlike `google.auth.identity_pool.Credentials`. This was indirectly passed here: https://github.com/googleapis/google-auth-library-python/blob/a37ff00d7afd6c7aac2d0fab29e05708bbc068be/google/auth/external_account.py#L395 Causing a TypeError to be raised (we only catch ValueError). Updated the credential determination logic to explicitly check the subject token type. This is a more reliable indicator instead of a try/catch. Increased unit test coverage in tests/test__default.py to cover these credentials. --- packages/google-auth/google/auth/_default.py | 7 +- packages/google-auth/tests/test__default.py | 191 ++++++++++++++++++- 2 files changed, 195 insertions(+), 3 deletions(-) diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index d4ccbc6ec530..8b0573bc8a55 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -54,6 +54,9 @@ added. Or you can use service accounts instead. For more information \ about service accounts, see https://cloud.google.com/docs/authentication/""" +# The subject token type used for AWS external_account credentials. +_AWS_SUBJECT_TOKEN_TYPE = "urn:ietf:params:aws:token-type:aws4_request" + def _warn_about_problematic_credentials(credentials): """Determines if the credentials are problematic. @@ -321,14 +324,14 @@ def _get_external_account_credentials( is in the wrong format or is missing required information. """ # There are currently 2 types of external_account credentials. - try: + if info.get("subject_token_type") == _AWS_SUBJECT_TOKEN_TYPE: # Check if configuration corresponds to an AWS credentials. from google.auth import aws credentials = aws.Credentials.from_info( info, scopes=scopes, default_scopes=default_scopes ) - except ValueError: + else: try: # Check if configuration corresponds to an Identity Pool credentials. from google.auth import identity_pool diff --git a/packages/google-auth/tests/test__default.py b/packages/google-auth/tests/test__default.py index c70ceaa57da3..1ce03cfe832a 100644 --- a/packages/google-auth/tests/test__default.py +++ b/packages/google-auth/tests/test__default.py @@ -55,6 +55,10 @@ SUBJECT_TOKEN_TEXT_FILE = os.path.join(DATA_DIR, "external_subject_token.txt") TOKEN_URL = "https://sts.googleapis.com/v1/token" AUDIENCE = "//iam.googleapis.com/projects/123456/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID" +WORKFORCE_AUDIENCE = ( + "//iam.googleapis.com/locations/global/workforcePools/POOL_ID/providers/PROVIDER_ID" +) +WORKFORCE_POOL_USER_PROJECT = "WORKFORCE_POOL_USER_PROJECT_NUMBER" REGION_URL = "http://169.254.169.254/latest/meta-data/placement/availability-zone" SECURITY_CREDS_URL = "http://169.254.169.254/latest/meta-data/iam/security-credentials" CRED_VERIFICATION_URL = ( @@ -79,6 +83,49 @@ "regional_cred_verification_url": CRED_VERIFICATION_URL, }, } +SERVICE_ACCOUNT_EMAIL = "service-1234@service-name.iam.gserviceaccount.com" +SERVICE_ACCOUNT_IMPERSONATION_URL = ( + "https://us-east1-iamcredentials.googleapis.com/v1/projects/-" + + "/serviceAccounts/{}:generateAccessToken".format(SERVICE_ACCOUNT_EMAIL) +) +IMPERSONATED_IDENTITY_POOL_DATA = { + "type": "external_account", + "audience": AUDIENCE, + "subject_token_type": "urn:ietf:params:oauth:token-type:jwt", + "token_url": TOKEN_URL, + "credential_source": {"file": SUBJECT_TOKEN_TEXT_FILE}, + "service_account_impersonation_url": SERVICE_ACCOUNT_IMPERSONATION_URL, +} +IMPERSONATED_AWS_DATA = { + "type": "external_account", + "audience": AUDIENCE, + "subject_token_type": "urn:ietf:params:aws:token-type:aws4_request", + "token_url": TOKEN_URL, + "credential_source": { + "environment_id": "aws1", + "region_url": REGION_URL, + "url": SECURITY_CREDS_URL, + "regional_cred_verification_url": CRED_VERIFICATION_URL, + }, + "service_account_impersonation_url": SERVICE_ACCOUNT_IMPERSONATION_URL, +} +IDENTITY_POOL_WORKFORCE_DATA = { + "type": "external_account", + "audience": WORKFORCE_AUDIENCE, + "subject_token_type": "urn:ietf:params:oauth:token-type:id_token", + "token_url": TOKEN_URL, + "credential_source": {"file": SUBJECT_TOKEN_TEXT_FILE}, + "workforce_pool_user_project": WORKFORCE_POOL_USER_PROJECT, +} +IMPERSONATED_IDENTITY_POOL_WORKFORCE_DATA = { + "type": "external_account", + "audience": WORKFORCE_AUDIENCE, + "subject_token_type": "urn:ietf:params:oauth:token-type:id_token", + "token_url": TOKEN_URL, + "credential_source": {"file": SUBJECT_TOKEN_TEXT_FILE}, + "service_account_impersonation_url": SERVICE_ACCOUNT_IMPERSONATION_URL, + "workforce_pool_user_project": WORKFORCE_POOL_USER_PROJECT, +} MOCK_CREDENTIALS = mock.Mock(spec=credentials.CredentialsWithQuotaProject) MOCK_CREDENTIALS.with_quota_project.return_value = MOCK_CREDENTIALS @@ -256,6 +303,68 @@ def test_load_credentials_from_file_external_account_aws(get_project_id, tmpdir) assert get_project_id.called +@EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH +def test_load_credentials_from_file_external_account_identity_pool_impersonated( + get_project_id, tmpdir +): + config_file = tmpdir.join("config.json") + config_file.write(json.dumps(IMPERSONATED_IDENTITY_POOL_DATA)) + credentials, project_id = _default.load_credentials_from_file(str(config_file)) + + assert isinstance(credentials, identity_pool.Credentials) + assert not credentials.is_user + assert not credentials.is_workforce_pool + # Since no scopes are specified, the project ID cannot be determined. + assert project_id is None + assert get_project_id.called + + +@EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH +def test_load_credentials_from_file_external_account_aws_impersonated( + get_project_id, tmpdir +): + config_file = tmpdir.join("config.json") + config_file.write(json.dumps(IMPERSONATED_AWS_DATA)) + credentials, project_id = _default.load_credentials_from_file(str(config_file)) + + assert isinstance(credentials, aws.Credentials) + assert not credentials.is_user + assert not credentials.is_workforce_pool + # Since no scopes are specified, the project ID cannot be determined. + assert project_id is None + assert get_project_id.called + + +@EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH +def test_load_credentials_from_file_external_account_workforce(get_project_id, tmpdir): + config_file = tmpdir.join("config.json") + config_file.write(json.dumps(IDENTITY_POOL_WORKFORCE_DATA)) + credentials, project_id = _default.load_credentials_from_file(str(config_file)) + + assert isinstance(credentials, identity_pool.Credentials) + assert credentials.is_user + assert credentials.is_workforce_pool + # Since no scopes are specified, the project ID cannot be determined. + assert project_id is None + assert get_project_id.called + + +@EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH +def test_load_credentials_from_file_external_account_workforce_impersonated( + get_project_id, tmpdir +): + config_file = tmpdir.join("config.json") + config_file.write(json.dumps(IMPERSONATED_IDENTITY_POOL_WORKFORCE_DATA)) + credentials, project_id = _default.load_credentials_from_file(str(config_file)) + + assert isinstance(credentials, identity_pool.Credentials) + assert not credentials.is_user + assert credentials.is_workforce_pool + # Since no scopes are specified, the project ID cannot be determined. + assert project_id is None + assert get_project_id.called + + @EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH def test_load_credentials_from_file_external_account_with_user_and_default_scopes( get_project_id, tmpdir @@ -718,7 +827,9 @@ def test_default_no_app_engine_compute_engine_module(unused_get): @EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH -def test_default_environ_external_credentials(get_project_id, monkeypatch, tmpdir): +def test_default_environ_external_credentials_identity_pool( + get_project_id, monkeypatch, tmpdir +): config_file = tmpdir.join("config.json") config_file.write(json.dumps(IDENTITY_POOL_DATA)) monkeypatch.setenv(environment_vars.CREDENTIALS, str(config_file)) @@ -726,10 +837,88 @@ def test_default_environ_external_credentials(get_project_id, monkeypatch, tmpdi credentials, project_id = _default.default() assert isinstance(credentials, identity_pool.Credentials) + assert not credentials.is_user + assert not credentials.is_workforce_pool # Without scopes, project ID cannot be determined. assert project_id is None +@EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH +def test_default_environ_external_credentials_identity_pool_impersonated( + get_project_id, monkeypatch, tmpdir +): + config_file = tmpdir.join("config.json") + config_file.write(json.dumps(IMPERSONATED_IDENTITY_POOL_DATA)) + monkeypatch.setenv(environment_vars.CREDENTIALS, str(config_file)) + + credentials, project_id = _default.default( + scopes=["https://www.google.com/calendar/feeds"] + ) + + assert isinstance(credentials, identity_pool.Credentials) + assert not credentials.is_user + assert not credentials.is_workforce_pool + assert project_id is mock.sentinel.project_id + assert credentials.scopes == ["https://www.google.com/calendar/feeds"] + + +@EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH +def test_default_environ_external_credentials_aws_impersonated( + get_project_id, monkeypatch, tmpdir +): + config_file = tmpdir.join("config.json") + config_file.write(json.dumps(IMPERSONATED_AWS_DATA)) + monkeypatch.setenv(environment_vars.CREDENTIALS, str(config_file)) + + credentials, project_id = _default.default( + scopes=["https://www.google.com/calendar/feeds"] + ) + + assert isinstance(credentials, aws.Credentials) + assert not credentials.is_user + assert not credentials.is_workforce_pool + assert project_id is mock.sentinel.project_id + assert credentials.scopes == ["https://www.google.com/calendar/feeds"] + + +@EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH +def test_default_environ_external_credentials_workforce( + get_project_id, monkeypatch, tmpdir +): + config_file = tmpdir.join("config.json") + config_file.write(json.dumps(IDENTITY_POOL_WORKFORCE_DATA)) + monkeypatch.setenv(environment_vars.CREDENTIALS, str(config_file)) + + credentials, project_id = _default.default( + scopes=["https://www.google.com/calendar/feeds"] + ) + + assert isinstance(credentials, identity_pool.Credentials) + assert credentials.is_user + assert credentials.is_workforce_pool + assert project_id is mock.sentinel.project_id + assert credentials.scopes == ["https://www.google.com/calendar/feeds"] + + +@EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH +def test_default_environ_external_credentials_workforce_impersonated( + get_project_id, monkeypatch, tmpdir +): + config_file = tmpdir.join("config.json") + config_file.write(json.dumps(IMPERSONATED_IDENTITY_POOL_WORKFORCE_DATA)) + monkeypatch.setenv(environment_vars.CREDENTIALS, str(config_file)) + + credentials, project_id = _default.default( + scopes=["https://www.google.com/calendar/feeds"] + ) + + assert isinstance(credentials, identity_pool.Credentials) + assert not credentials.is_user + assert credentials.is_workforce_pool + assert project_id is mock.sentinel.project_id + assert credentials.scopes == ["https://www.google.com/calendar/feeds"] + + @EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH def test_default_environ_external_credentials_with_user_and_default_scopes_and_quota_project_id( get_project_id, monkeypatch, tmpdir From 5c4ae640e7e0ad6a628348dbd4c9ccadb78fb40f Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Wed, 6 Oct 2021 11:08:22 -0400 Subject: [PATCH 483/966] build: use trampoline_v2 for python samples and allow custom dockerfile (#881) * build: use trampoline_v2 for python samples and allow custom dockerfile Source-Link: https://github.com/googleapis/synthtool/commit/a7ed11ec0863c422ba2e73aafa75eab22c32b33d Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:6e7328583be8edd3ba8f35311c76a1ecbc823010279ccb6ab46b7a76e25eafcc * chore: add trampolinerc Co-authored-by: Owl Bot Co-authored-by: Bu Sun Kim --- packages/google-auth/.github/.OwlBot.lock.yaml | 2 +- .../google-auth/.kokoro/samples/lint/common.cfg | 2 +- .../.kokoro/samples/python3.6/common.cfg | 2 +- .../.kokoro/samples/python3.6/periodic.cfg | 2 +- .../.kokoro/samples/python3.7/common.cfg | 2 +- .../.kokoro/samples/python3.7/periodic.cfg | 2 +- .../.kokoro/samples/python3.8/common.cfg | 2 +- .../.kokoro/samples/python3.8/periodic.cfg | 2 +- .../.kokoro/samples/python3.9/common.cfg | 2 +- .../.kokoro/samples/python3.9/periodic.cfg | 2 +- .../.kokoro/test-samples-against-head.sh | 2 -- packages/google-auth/.kokoro/test-samples.sh | 2 -- packages/google-auth/.trampolinerc | 17 ++++++++++++++--- packages/google-auth/owlbot.py | 4 ++++ 14 files changed, 28 insertions(+), 17 deletions(-) diff --git a/packages/google-auth/.github/.OwlBot.lock.yaml b/packages/google-auth/.github/.OwlBot.lock.yaml index 2567653c000d..ee94722ab57b 100644 --- a/packages/google-auth/.github/.OwlBot.lock.yaml +++ b/packages/google-auth/.github/.OwlBot.lock.yaml @@ -1,3 +1,3 @@ docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:87eee22d276554e4e52863ec9b1cb6a7245815dfae20439712bf644348215a5a + digest: sha256:6e7328583be8edd3ba8f35311c76a1ecbc823010279ccb6ab46b7a76e25eafcc diff --git a/packages/google-auth/.kokoro/samples/lint/common.cfg b/packages/google-auth/.kokoro/samples/lint/common.cfg index 61fa5217bb9d..f6b0c07c6533 100644 --- a/packages/google-auth/.kokoro/samples/lint/common.cfg +++ b/packages/google-auth/.kokoro/samples/lint/common.cfg @@ -31,4 +31,4 @@ gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/python-docs-samples" gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" # Use the trampoline script to run in docker. -build_file: "google-auth-library-python/.kokoro/trampoline.sh" \ No newline at end of file +build_file: "google-auth-library-python/.kokoro/trampoline_v2.sh" \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.6/common.cfg b/packages/google-auth/.kokoro/samples/python3.6/common.cfg index 4895c2bcf824..57feb84b3a2b 100644 --- a/packages/google-auth/.kokoro/samples/python3.6/common.cfg +++ b/packages/google-auth/.kokoro/samples/python3.6/common.cfg @@ -37,4 +37,4 @@ gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/python-docs-samples" gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" # Use the trampoline script to run in docker. -build_file: "google-auth-library-python/.kokoro/trampoline.sh" \ No newline at end of file +build_file: "google-auth-library-python/.kokoro/trampoline_v2.sh" \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.6/periodic.cfg b/packages/google-auth/.kokoro/samples/python3.6/periodic.cfg index 50fec9649732..71cd1e597e38 100644 --- a/packages/google-auth/.kokoro/samples/python3.6/periodic.cfg +++ b/packages/google-auth/.kokoro/samples/python3.6/periodic.cfg @@ -3,4 +3,4 @@ env_vars: { key: "INSTALL_LIBRARY_FROM_SOURCE" value: "False" -} \ No newline at end of file +} diff --git a/packages/google-auth/.kokoro/samples/python3.7/common.cfg b/packages/google-auth/.kokoro/samples/python3.7/common.cfg index 90aaef1b43f9..7ca2eb0eb6bd 100644 --- a/packages/google-auth/.kokoro/samples/python3.7/common.cfg +++ b/packages/google-auth/.kokoro/samples/python3.7/common.cfg @@ -37,4 +37,4 @@ gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/python-docs-samples" gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" # Use the trampoline script to run in docker. -build_file: "google-auth-library-python/.kokoro/trampoline.sh" \ No newline at end of file +build_file: "google-auth-library-python/.kokoro/trampoline_v2.sh" \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.7/periodic.cfg b/packages/google-auth/.kokoro/samples/python3.7/periodic.cfg index 50fec9649732..71cd1e597e38 100644 --- a/packages/google-auth/.kokoro/samples/python3.7/periodic.cfg +++ b/packages/google-auth/.kokoro/samples/python3.7/periodic.cfg @@ -3,4 +3,4 @@ env_vars: { key: "INSTALL_LIBRARY_FROM_SOURCE" value: "False" -} \ No newline at end of file +} diff --git a/packages/google-auth/.kokoro/samples/python3.8/common.cfg b/packages/google-auth/.kokoro/samples/python3.8/common.cfg index 78fd8c749639..fbd029e68fe1 100644 --- a/packages/google-auth/.kokoro/samples/python3.8/common.cfg +++ b/packages/google-auth/.kokoro/samples/python3.8/common.cfg @@ -37,4 +37,4 @@ gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/python-docs-samples" gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" # Use the trampoline script to run in docker. -build_file: "google-auth-library-python/.kokoro/trampoline.sh" \ No newline at end of file +build_file: "google-auth-library-python/.kokoro/trampoline_v2.sh" \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.8/periodic.cfg b/packages/google-auth/.kokoro/samples/python3.8/periodic.cfg index 50fec9649732..71cd1e597e38 100644 --- a/packages/google-auth/.kokoro/samples/python3.8/periodic.cfg +++ b/packages/google-auth/.kokoro/samples/python3.8/periodic.cfg @@ -3,4 +3,4 @@ env_vars: { key: "INSTALL_LIBRARY_FROM_SOURCE" value: "False" -} \ No newline at end of file +} diff --git a/packages/google-auth/.kokoro/samples/python3.9/common.cfg b/packages/google-auth/.kokoro/samples/python3.9/common.cfg index b1eacb8f6aaf..07cda0ab20e5 100644 --- a/packages/google-auth/.kokoro/samples/python3.9/common.cfg +++ b/packages/google-auth/.kokoro/samples/python3.9/common.cfg @@ -37,4 +37,4 @@ gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/python-docs-samples" gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" # Use the trampoline script to run in docker. -build_file: "google-auth-library-python/.kokoro/trampoline.sh" \ No newline at end of file +build_file: "google-auth-library-python/.kokoro/trampoline_v2.sh" \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.9/periodic.cfg b/packages/google-auth/.kokoro/samples/python3.9/periodic.cfg index 50fec9649732..71cd1e597e38 100644 --- a/packages/google-auth/.kokoro/samples/python3.9/periodic.cfg +++ b/packages/google-auth/.kokoro/samples/python3.9/periodic.cfg @@ -3,4 +3,4 @@ env_vars: { key: "INSTALL_LIBRARY_FROM_SOURCE" value: "False" -} \ No newline at end of file +} diff --git a/packages/google-auth/.kokoro/test-samples-against-head.sh b/packages/google-auth/.kokoro/test-samples-against-head.sh index e974d9d016ff..ba3a707b040c 100755 --- a/packages/google-auth/.kokoro/test-samples-against-head.sh +++ b/packages/google-auth/.kokoro/test-samples-against-head.sh @@ -23,6 +23,4 @@ set -eo pipefail # Enables `**` to include files nested inside sub-folders shopt -s globstar -cd github/google-auth-library-python - exec .kokoro/test-samples-impl.sh diff --git a/packages/google-auth/.kokoro/test-samples.sh b/packages/google-auth/.kokoro/test-samples.sh index 6cea6d4f3733..11c042d342d7 100755 --- a/packages/google-auth/.kokoro/test-samples.sh +++ b/packages/google-auth/.kokoro/test-samples.sh @@ -24,8 +24,6 @@ set -eo pipefail # Enables `**` to include files nested inside sub-folders shopt -s globstar -cd github/google-auth-library-python - # Run periodic samples tests at latest release if [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"periodic"* ]]; then # preserving the test runner implementation. diff --git a/packages/google-auth/.trampolinerc b/packages/google-auth/.trampolinerc index 383b6ec89fbc..0eee72ab62aa 100644 --- a/packages/google-auth/.trampolinerc +++ b/packages/google-auth/.trampolinerc @@ -16,15 +16,26 @@ # Add required env vars here. required_envvars+=( - "STAGING_BUCKET" - "V2_STAGING_BUCKET" ) # Add env vars which are passed down into the container here. pass_down_envvars+=( + "NOX_SESSION" + ############### + # Docs builds + ############### "STAGING_BUCKET" "V2_STAGING_BUCKET" - "NOX_SESSION" + ################## + # Samples builds + ################## + "INSTALL_LIBRARY_FROM_SOURCE" + "RUN_TESTS_SESSION" + "BUILD_SPECIFIC_GCLOUD_PROJECT" + # Target directories. + "RUN_TESTS_DIRS" + # The nox session to run. + "RUN_TESTS_SESSION" ) # Prevent unintentional override on the default image. diff --git a/packages/google-auth/owlbot.py b/packages/google-auth/owlbot.py index 58aa53a70f96..611ce920c527 100644 --- a/packages/google-auth/owlbot.py +++ b/packages/google-auth/owlbot.py @@ -17,6 +17,10 @@ "build.sh", ], ) # just move kokoro configs +s.move( + # needed by samples kokoro jobs + templated_files / ".trampolinerc" +) assert 1 == s.replace( From 1027507ea993dd4ac6dcc92531f56692bcf401c2 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Wed, 6 Oct 2021 10:24:47 -0600 Subject: [PATCH 484/966] chore: update authorized_user.json (#883) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10323 -> 10323 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 187c95021317150814dde4f50b0cc35bcf520d80..66c8ff4822121786cd4b4753aee3ca1cbb13c440 100644 GIT binary patch literal 10323 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 literal 10323 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 From 968306b7e0eb9e3ec26ad851d3436f04ae72e3be Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Thu, 7 Oct 2021 13:05:19 -0400 Subject: [PATCH 485/966] feat: add support for Python 3.10 (#882) --- packages/google-auth/CONTRIBUTING.rst | 2 +- packages/google-auth/noxfile.py | 2 +- packages/google-auth/setup.py | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/CONTRIBUTING.rst b/packages/google-auth/CONTRIBUTING.rst index 0e784af48e71..255f33c74ff2 100644 --- a/packages/google-auth/CONTRIBUTING.rst +++ b/packages/google-auth/CONTRIBUTING.rst @@ -19,7 +19,7 @@ A few notes on making changes to ``google-auth-library-python``. using ``nox -s docgen``. - The change must work fully on the following CPython versions: - 3.6, 3.7, 3.8, 3.9 across macOS, Linux, and Windows. + 3.6, 3.7, 3.8, 3.9, 3.10 across macOS, Linux, and Windows. - The codebase *must* have 100% test statement coverage after each commit. You can test coverage via ``nox -e cover``. diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index caeb2725a205..2e6e88971fba 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -86,7 +86,7 @@ def blacken(session): session.run("black", *BLACK_PATHS) -@nox.session(python=["3.6", "3.7", "3.8", "3.9"]) +@nox.session(python=["3.6", "3.7", "3.8", "3.9", "3.10"]) def unit(session): constraints_path = str( CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt" diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 54a2c900bef3..343e660f1502 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -63,6 +63,7 @@ "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", From 6b8bcfd277513127231eaa5e82fddb052d8b7c36 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 7 Oct 2021 12:11:17 -0600 Subject: [PATCH 486/966] chore: release 2.3.0 (#884) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 12 ++++++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 997cac979049..a8fddc9b9b42 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,18 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.3.0](https://www.github.com/googleapis/google-auth-library-python/compare/v2.2.1...v2.3.0) (2021-10-07) + + +### Features + +* add support for Python 3.10 ([#882](https://www.github.com/googleapis/google-auth-library-python/issues/882)) ([19d41f8](https://www.github.com/googleapis/google-auth-library-python/commit/19d41f8ec94ab0148d2f09a5d560ae237a87ffdb)) + + +### Bug Fixes + +* ADC with impersonated workforce pools ([#877](https://www.github.com/googleapis/google-auth-library-python/issues/877)) ([10bd9fb](https://www.github.com/googleapis/google-auth-library-python/commit/10bd9fbecd462435246afa46fd666a2836cd9e89)) + ### [2.2.1](https://www.github.com/googleapis/google-auth-library-python/compare/v2.2.0...v2.2.1) (2021-09-28) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 8cc169a2f48b..60805d9ca3ff 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.2.1" +__version__ = "2.3.0" From becaeae406c2adb8bd93e2353092cd94d51e975a Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Fri, 8 Oct 2021 17:16:35 +0000 Subject: [PATCH 487/966] chore(python): Add kokoro configs for python 3.10 samples testing (#886) --- .../google-auth/.github/.OwlBot.lock.yaml | 2 +- .../.kokoro/samples/python3.10/common.cfg | 40 +++++++++++++++++++ .../.kokoro/samples/python3.10/continuous.cfg | 6 +++ .../samples/python3.10/periodic-head.cfg | 11 +++++ .../.kokoro/samples/python3.10/periodic.cfg | 6 +++ .../.kokoro/samples/python3.10/presubmit.cfg | 6 +++ 6 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 packages/google-auth/.kokoro/samples/python3.10/common.cfg create mode 100644 packages/google-auth/.kokoro/samples/python3.10/continuous.cfg create mode 100644 packages/google-auth/.kokoro/samples/python3.10/periodic-head.cfg create mode 100644 packages/google-auth/.kokoro/samples/python3.10/periodic.cfg create mode 100644 packages/google-auth/.kokoro/samples/python3.10/presubmit.cfg diff --git a/packages/google-auth/.github/.OwlBot.lock.yaml b/packages/google-auth/.github/.OwlBot.lock.yaml index ee94722ab57b..7d98291cc35f 100644 --- a/packages/google-auth/.github/.OwlBot.lock.yaml +++ b/packages/google-auth/.github/.OwlBot.lock.yaml @@ -1,3 +1,3 @@ docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:6e7328583be8edd3ba8f35311c76a1ecbc823010279ccb6ab46b7a76e25eafcc + digest: sha256:58f73ba196b5414782605236dd0712a73541b44ff2ff4d3a36ec41092dd6fa5b diff --git a/packages/google-auth/.kokoro/samples/python3.10/common.cfg b/packages/google-auth/.kokoro/samples/python3.10/common.cfg new file mode 100644 index 000000000000..de052d35fc5e --- /dev/null +++ b/packages/google-auth/.kokoro/samples/python3.10/common.cfg @@ -0,0 +1,40 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Build logs will be here +action { + define_artifacts { + regex: "**/*sponge_log.xml" + } +} + +# Specify which tests to run +env_vars: { + key: "RUN_TESTS_SESSION" + value: "py-3.10" +} + +# Declare build specific Cloud project. +env_vars: { + key: "BUILD_SPECIFIC_GCLOUD_PROJECT" + value: "python-docs-samples-tests-310" +} + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/google-auth-library-python/.kokoro/test-samples.sh" +} + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/python-samples-testing-docker" +} + +# Download secrets for samples +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/python-docs-samples" + +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" + +# Use the trampoline script to run in docker. +build_file: "google-auth-library-python/.kokoro/trampoline_v2.sh" \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.10/continuous.cfg b/packages/google-auth/.kokoro/samples/python3.10/continuous.cfg new file mode 100644 index 000000000000..a1c8d9759c88 --- /dev/null +++ b/packages/google-auth/.kokoro/samples/python3.10/continuous.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.10/periodic-head.cfg b/packages/google-auth/.kokoro/samples/python3.10/periodic-head.cfg new file mode 100644 index 000000000000..83eace873ccb --- /dev/null +++ b/packages/google-auth/.kokoro/samples/python3.10/periodic-head.cfg @@ -0,0 +1,11 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/google-auth-library-python/.kokoro/test-samples-against-head.sh" +} diff --git a/packages/google-auth/.kokoro/samples/python3.10/periodic.cfg b/packages/google-auth/.kokoro/samples/python3.10/periodic.cfg new file mode 100644 index 000000000000..71cd1e597e38 --- /dev/null +++ b/packages/google-auth/.kokoro/samples/python3.10/periodic.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "False" +} diff --git a/packages/google-auth/.kokoro/samples/python3.10/presubmit.cfg b/packages/google-auth/.kokoro/samples/python3.10/presubmit.cfg new file mode 100644 index 000000000000..a1c8d9759c88 --- /dev/null +++ b/packages/google-auth/.kokoro/samples/python3.10/presubmit.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} \ No newline at end of file From 114dcfa380c14abb136a90a7dd83b4f080138e55 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Fri, 15 Oct 2021 11:26:39 -0600 Subject: [PATCH 488/966] test: list unit test dependencies in requirements.txt file (#888) * test: list unit test reqs in requirements.txt file * chore: update authorized_user.json --- packages/google-auth/noxfile.py | 38 +++--------------- .../google-auth/system_tests/secrets.tar.enc | Bin 10323 -> 10323 bytes packages/google-auth/testing/requirements.txt | 20 +++++++++ 3 files changed, 25 insertions(+), 33 deletions(-) create mode 100644 packages/google-auth/testing/requirements.txt diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index 2e6e88971fba..e238c973ce25 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -20,30 +20,6 @@ CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() -TEST_DEPENDENCIES = [ - "flask", - "freezegun", - "mock", - "oauth2client", - "pyopenssl", - "pytest", - "pytest-cov", - "pytest-localserver", - "pyu2f", - "requests", - "urllib3", - "cryptography", - "responses", - "grpcio", -] - -ASYNC_DEPENDENCIES = [ - "pytest-asyncio", - "aioresponses", - "asynctest", - "aiohttp!=3.7.4.post0", -] - BLACK_VERSION = "black==19.3b0" BLACK_PATHS = [ "google", @@ -91,10 +67,8 @@ def unit(session): constraints_path = str( CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt" ) - add_constraints = ["-c", constraints_path] - session.install(*(TEST_DEPENDENCIES + add_constraints)) - session.install(*(ASYNC_DEPENDENCIES + add_constraints)) - session.install("-e", ".", *add_constraints) + session.install("-r", "testing/requirements.txt", "-c", constraints_path) + session.install("-e", ".", "-c", constraints_path) session.run( "pytest", f"--junitxml=unit_{session.python}_sponge_log.xml", @@ -109,8 +83,7 @@ def unit(session): @nox.session(python="3.7") def cover(session): - session.install(*TEST_DEPENDENCIES) - session.install(*(ASYNC_DEPENDENCIES)) + session.install("-r", "testing/requirements.txt") session.install("-e", ".") session.run( "pytest", @@ -128,7 +101,7 @@ def cover(session): @nox.session(python="3.7") def docgen(session): session.env["SPHINX_APIDOC_OPTIONS"] = "members,inherited-members,show-inheritance" - session.install(*TEST_DEPENDENCIES) + session.install("-r", "testing/requirements.txt") session.install("sphinx") session.install("-e", ".") session.run("rm", "-r", "docs/reference") @@ -168,8 +141,7 @@ def docs(session): @nox.session(python="pypy") def pypy(session): - session.install(*TEST_DEPENDENCIES) - session.install(*ASYNC_DEPENDENCIES) + session.install("-r", "test/requirements.txt") session.install("-e", ".") session.run( "pytest", diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 66c8ff4822121786cd4b4753aee3ca1cbb13c440..646bb239447c711fcfd9afc1ff05d8770ec17798 100644 GIT binary patch literal 10323 zcmV-ZD6H2CBmnkJRTF@bTsYuJCkOE>Qp=M+PBKklom)stz?F#Ux}fDduTHk3s&B{0#ni$1(v{1-AIR%$D;BVK%xNM?!biIcK#%6l<1N->$ z&+MYn4)gkVcc~V+%uUiYQ^@$Z>ocL;LEJLO8m{X4lEo$M1#p!iD;0MQm~S*gVML;>Wu%P zJ3>yeioaE#cLS!hwUpCFhMp%mm?lVwWHlJz3|ECSY5pkbJ&yGuxAKytd#@=)1;9n| zUe64WPcRE-a@AGzQ!7zoOM{bv=sNKOtmofdegpkS@BS^>^OI#YDe!_BZ+jmS;r5P# z8seVtne|iJp!td_-zYl*lKQ{C6CkkW5|pw?sf=$xfAlQH&U7ZDVTM4q?K2VUXlGD` zSYZdb$iJ`Fdj(>a%|kYfOHmRL(1*8}CiiYBg1cPJ0GsSP4XnehbQ9uYN$@K=H7m;D zP7nxnpxZkJ=&EvU57Jn+t~*;+a8+-Tu_vt-T_S)o{0j4|D;w5J_M$okzg##OdSb#e z7%CGNiuo!nr*K*Zs*6GqAUK+&wwi`|cpOf9=QWbL{jXJWyfrmZ&FZ-G$W( zgBxN3vAoyAL>&VW%}$yVW>YmN4Px~8#*s`7w$!%xCBX%nbF^5M|L8%vt@@nk`Iw0f z#q)H9#G{aH%d5KSBvhvWpV31$L2}aBlUTWioXXd{WU+onY(_mnv&N??%8*3~tghRK zE-bqV7z{RiJ5y}3s>Hd!n8+3CGc%o_fA^zvoEWi;YKdhe(|Wjbkv+O3Q?G5%-5vjX z^h(zWgHv7gP3C$>z!a;&8b+nm4~#>Llr6G}-$|)XXPDZx!(@X;aI{a(s z5D|B81{ygGLbsqi@_o=8=N=y}tT4xURX=ncUTcOiD&Tk{9>F7A-G{+5Uw7D8ooYjo z8iSdsntmBFjj5nMWo#L{675t`9y;jxv5&sN9Q&#Dr`yg6mBMvRRf8_~_g{s2nIcL( zxX@MW6&{xslN@^*Avzhx_vvLbm7>wJEuAz1$+QjI{$7BmmAEPq4d^!HpP+*-P{UKz z(ugCq3s#>6 zoXf{PD~tuoTmO=xTeEUkv;!m5<{VXHvb?=Q^4_!GKsv&MVapGW6!%w_x4&T!Hl~C$ za||~>VHvh6pnLBAOppcm=s++h6YOH}geb-01wsQH>(udf zAH1B`j!olRMkEL02q!8n4aMzQM}5-@vQ6QHK7ScL36GQrV@pr%^JBZvT(wzL2eV#<@GP|^A;x5dx!!GN!NQ3 z!w}Bb+4y7DSc|7+wC!}t6F*IVvdBS(>y1x~!GyA@0NC-iI(nT%HPLghh(RQ%fDF%0 z{<#tJL{RKDUHJV|7IHZ|LR0z`bv*nq=jjez)_4S3e~JxW$CsP~Q632<8ryNX0bbV1 zQ@qxY%EmjpJNas!$0Omqqj}RoQyv9QwZm2jO8#PIB0=hDY&vR=^!5|}#A@hj*Uc-x z6))(E_GVc$L_V?CvwUu)46{#*Z0wv<*W-T?6R3R47`ng1s2cI}$;Z)qPrC^18 z`UISm_~Y`Q1cl0!K1sF(_GAXeHGJG0)UiKpEt;s@lmDmh||p2bOSl|M7f$EORL zC7u+@|^Jv{w4%2)y$*9 zr9zUu9_%GHRgoD7z(*9Av1d_hhd!Wwq>iWws466PDX8#>Rf!x_J(@8UV)2*VkAvuO z-lzilN6}A5CYTpR^DN-P39`9yr>{a8!!`B$sZ)|&zRt9I)s0PAB2jd0&Onzsj@h~U zR^t9alAYYL*V^WX3t^<%`g@(Yt>(3c-4qQ^g{IQfi=424A6=i7NAC!kD0+GLo5TFg zcio}C5GqNdxNGm|HgtRxh7^0w*>O1jY#Va4(}ZPjh3@%wcGqPImE+D5M5z<)>erLM_GY1J2OY^DsfTtC$cX<{j2lWHd{H1vrf!AU>0KwvY~ig0kIapB(xSV3 z#Y3B~hDQbXfM&}M$&Lyt+dhNmA#Wv2{m$}dTMJNFZR$cL5S~@uT3cYVZH)EG@JqF* zZLJ#KzX98ZVgCQ@?C_p1cQ{XK$YPtlY`&&52){t zOPNIFWfXehw)y+QRX9r3o7`wr9*(E}Ri~+jfQ#+cqxsF^Ad7VxLRI$ID6F9p)6k=E z!rlS8H|@AfXjr(6gDdpgiWh<%-jsD}ng4g@TjOzoUsr^R>P}{z``s5P7O%DTI5gf5 zl57s>Lvg1>;jFx2;RRD%ron+%;>Cc|kntd~rLxeUG!+hNwiA*Vd)CCbMYbi5j<-0t z3x&5~z<#4+U*wWSj6B8yn>kZ1>F8O7bwj^Q;UI4wsfUF>jP7Dpr?WyDd{r-XHF82K zHOGpgD;pblcgZ7$=Hev)@@&5qQt`jojUsHcQKQ5H?#$*O_e|moePot)=*aPiYD02b zoDT^#xWC!8Qyxu4p~*KDSlPMSza{5#FcGh{9VCU7DcB}#eGv5v(X)^E(X_UrokY)E zok0peb$K9ZV+;{c_5Bko$o96)Oed{a_E_rJK9X#|+&~%Yl24)4_rC~pEE+ee%GhjF zi!xmalQ2i7MTbr})-@~?bRY)T)C3Ug2_w(`RA*Wi#sYuHwZ;Qv78@UaPE_?aAn5z? z5%RTc@W~cC{?HknKQY5%$EgXojg6^ljsSMg7xp47l79X~QI5UqT)p)aY~v#$BlzFq zy3wd)+Xsfi(oQ^0NSBitH+{1gaHh%JJ!=y387mY#*(#|Jn~D{p?3Qn6Oe2IXJ5^o4 z!=WezSI|ZgtcmVEmp%7-fsNv%DU8(S3Z9B$jwSo{EyhWGhH2zDW=FpqtUOr$Uu(*m+}s}V$%{)4HQ+zcq#jF=49DTld%P7fTipcwkoke z9HB<4Z~Nq0xW+&HlfX>G9D`9W*-I>DEyr=~XfW|_NN1NoGI0Auy7i#xB_z-^!O!9l zlnl>n{$J{HUl;?$#@Uzep2z#n7O;;7kGTMkgt*-b5lI@Wz(`2C04a_6=6Fr-_hub- zZhURD9&>i+QstzsXWXkjZtP01O=mok3bJjZZKXY%Z)jdV=%sL2)wA&&GiVf!F;=-Q zT#=ip#HeN`AMsW9>z9;wnwHPxYX9d?dTZ8#a>Q}rJqiiuX7Jh>0+72pVa?L4)Rl}g zY;J-OBh#+ciud5)@kg8<$u0>~Q~=hYBH%3*;&%SsRgF5@gmfi_U?aUYEqiN*N-l4M zCBgr>OAtB=z9g9)B95diBc5??Bv%mHZR=#q1`N&H8{>b!%L zK`$9;w4$cgW=VyNusP{}FiEM0dhsEE7@)nxGq>Px-sEz3&*l#^w221eSew3UNw4p2{?CRI zQB4}naT#aRE0NM^C*Tu8c4-vRZ-?xQhQ^}<4qr91MqGb^o=MM5aOQNzCyT6d$;#}ue|S)52AUt0C_qJk zeAwG6+lrYGis-T0%?pG#vt(vdMEAwnCS$U^HY^?PLDApF)_9T#hBVRX9pbvh0Io4S zs$+J5x4m?BNo>54%;mhOtc)v+;lFz#n&hD1-*dfm(JwcDb3jwnZ0-}uUy1+$^Hj#P zk)G-8luIxFja}C%?|gPnY~bnh?jypJDxMv_vQ+^-Yy+;f?!Q%HCYh2l)Ky5)ha#xq-fG0-X1V6n08_ju>nbN@R=>v9wcW?luZZ2=sj9n`g@i;??b4*bp=29+ zyez;?86J`BE#EIrwksPoIzHjj?Co*H(M@lVWIL6h6X4-b@NFD;eQUaIQtLNxi;od?lIcIYb~ z(b-(#AUkLwou4uX1OWHjcAi}8TbdM#-O$}fd45z+Nx)+$+@*Qr!(nZVCN1~uJlxt( z@SA{}eZeUQm-v_RW1nrX&$^<19_2w|Rq-YT@f?i#@4!{YA&~b04UP*T_T7(#gn-1jjgBAb z9cbE$?#KfT%jETCTxhpmH6R`^lIr0`&P@Fi07M8zW1TlNe({ z(Z}#oioX>Y1K}e?>+@B68QHFoudy|?A)P%K55A5bY7z>Oh zR`oJm60xR33Pj&?z{%L=uA1K^^*?_QR%LOGV9;M>LE%*?9OBz z8t2Qk08Um$qf~iE2}(nzH$%O;TqA{wOagbi?_~>xj)vo{ANWfk*jBy6wae;Waw;)V zPn72HZkxFV9P7w8KP4jt2HnbrxNxf;AqZ6}T^({)^~lR%UgTbR!%S8zo}TfCJTN~E zMf#n9FlVrSq1u56hbxeG_SKf9+-z}l51^CHy-Kd=Wldp`B;E{N=KD;JZ!%_jzeslV6?^ulU#95^XWN8V>PsfMQpiq*a5}SSRfKHc$AZOz2aa`0RU8 z-ME&rSvEz;MZGA=D+L$x8~MH7o=yM3C6fSsg04VIQ}c|vxb$=wP(P>);JrM1WKNF^ z4aW}bJP4*#A>Cb_p?sX>$pP4#nP39^OgJv99Zi?iZ}AHo3c=*KSurlV{6o43MP%2G zTgPG;yQ%H4YmyGyux2IrX|45e29h^67^0VCgkJI1z#AAq$jPLfI2Zi@J|M$UcKCla z>)QyAjyXM@BE>wQL$Bq33Ynn{r)AMRsYY-Q)O{AQ0Byh>VA|nX8Q^z{9|Eh%8f3@o z_cOZzV0xUHt0&DO{&MrmUN_Mo-|U1|JjXeZc(423f@l_S0qoZ!7h7}s-uQg#lR<)K znA*>g6R2c+@;F_B4+YBm42JSP-dKQM3{r*9KSpx;9+}^Z@ZS@Ik(&J0P>ZqRWx9-$ zyr(98Vj0quZ;4+G3f_tINNn$t*!eAd?o)fmu3G zygTagk#*<(kd);ahLZy{PVBnL<>s4viwgudLs^;VWEt;bXmkfk``lmuY|)FM*WUiB z@bOaTn#DKO$>Vy8iD%&e;a4wAbd{>iNoCD(+{LrEzel%L~&o z&WcM?DFL--@V@lMn!ua@*8||0d3-9Vojscj2tVgJW($3w0F1Qj4b-Yy*MyB|I+mNZ zoz@M$a`Ztn$n&aFB&~~renVGZ6qX=ioKlndRN?GM&vQ;p$Lop}d0I~L{Jfi97a!+t z4jz9`=ze0Acx*@+d{R)4P^d|k*VTXAMw$uUu@}U(pB@kaU1Xi?v%6~p6I#@QU2LSi zSN83)mttj#pLo<>p%@G*E6lXfVUT>!_e$lq66P0%f8+Ym*eKTp20&woxq0gQ3ku=Xc-0_ z(+ZC1_Z=*@V@46T^o|=XFkQ>2;%I7|y86FzGY|`2)-`d0%#N8JCfm%_CV1OyMuIg- zqd_ZkTLat4-NF|ao|1Uq-_`kpZkDOosK)x;tVEr9LZ;rSpp*&rsi7Iy<@`CW6?Mdr z&D~;3Y<%(fc92Nv-j|*QU0-{Vr4Xw+xp{i)17=oT6IY~zOoz!83R%HVLHbz?YW=%O z0IZEzJEVIaRsBqHL*FbWqxb&&mm~;JPu=kJ?`hAM_$X+?V0+zuF!2nJGu9`k&E1BN zp39hJyHDJWtj84HFFGdqD9q5m`HA9(PewSQL4(e|H8&`RVs)AaMf6<{UiwAKyjiG{eoetMLe1!WEc0pJjGGwvV0QEIh6 zhssy48y|5e801-GiWh4kVztDFSs8LOs&($9QFbBPEs9&wt(9> zv-tnW^8L*fX(={KSQXiEyBm_DMIUtDRWHP;FHPdtc6T$4&W1fga%fWd3{uxt;PHXw z1~5OE_ZZ7t;e9$GO`xR^nyK_~LB2b_{NktISH|_;;!(JFZOOBWYrn9}T>AsK579YnMAL>Won*y$Gc?}O z997YtJ~)!Lu9e$!a`LqZE3qoK+qkuKH|g3+0-?1$KyVQF@TQt04HIc9dS`e>=*997 z%d@3A{xQ4xRp^$2(v8>*-hxSdlW|zVI3*ch2kP%BaeGTxgFV#QY%@k!DL2tavtL3{ z%G=lXAU?RQO@*AQY%_HX^-8@tTqi;{v~_$0z52q5ZZ3Yn4J z)uIjXt(Z^M@GEyZFh5j|0!;L*Z}>wTU_o*}o%Y_mn*zGL=o#)wec?GPH znodVk6b=)Yde+-1f?f7|c-REkh;g>piIO%Lcbm~__laYCwG%C}MC3xI2~6U}k!%Z5 zCk2Mf)6pedh|hT%Oh7Wk16dyaeoWNpj-@#mouRaKmrfmBO6HCMMB#RWAdO6)uiFC{ z6pzh_FtkQ&H)Jf!(j7_19V<|s{*u}7WP!VI7Znt0!(6;grFJyRffovY6Rp06$w+*; zruT2y;=42AHkJAI1j=M&Y=lmr>CiGMiRrk8GG|UvEe3Y1gJiGRQk7as`zAQt?@PoK z^?ycj&-1q}ZbfaX+t;V%!PIi6vCec|mJE0wK(>)v3TJ!H<1S$;Qi<=bv$zT`g9l?? z*lEaK3E34(5~n9X=Q>>-Gz@POZtqwx=B>k#QUvS+mH{8^^n@p+ReiX?hM_nkR-_fp zsVsk6?!&DnXdm7ZU8Cxo9+Zy>{U}#W`p*&?4_6*NSPP=3<3in#d|$P80SRz@#nz^ zIv+60U51}O^lvr?Hm6tP?}X|4FvL84n6zwEAc|fyI$MmvSho~nn-rP|l!nh>Vjsdy zfbg(qlaV`S#+<(hUsFfO_)UT@lnzO@OF=Woi4@+}f74iUW;wX>pC}YmXIY>5A(VoL)HmYTJ4zRjy2fGv4#WzN$=1{A=!B>#^leUCwL0?_n5Kr%8LAIWCDet~*wL&`!M@KAtCT*MlS?x2i`}@ZO#U7 zR3E&7!7D`*SnfI#i4(BE&L2+PFr)lpE|+~d(9uCYvuSH-N?+twKg8rN!lqkKb#Z1? zIqAw#7!wr;9*@;ht@qf4(wYbdJ~vTXs>>5?+9VP(v_&XhsWBk;cSF;aLF^^7K6?H9 zrf~d6?u(0$a=edHFJB+~bSTqaUvZdZRGJtTw_Jx4XPuJ76D&cOe#VX1OI-mYPHXum zc32}a2MkPI4*Gh<&5+>&o3+)-+29V%z_h$Y5yb!JC!G*NM zNB|$u&bQ&7IH=-!A6@}-PRP`_naW}>=#dlz#?0Vkvd+)kFmNHX4?!|P?4N2A{i|z2 z6_Maz0z%6^n>O`5is9`#jo086FNm?Wt+6_#TgW$VA2^y94MrJJJ^TnYP-YQKc1k~H ztf=j`hC81nPs4uPVj;>m9ZsNHDXwp#(gx)$&%nDcC}*gJENKlue~|x(2mc=CSdJ}uPZPW6G6<_pqSd#{5N3PW7PmQzRBFJZf;~pc)rt4^;0lpJl zH}NLQJ*6XTeGyLYSVSDjBsW!3zFFnrNiMOLXnGyjK{-shh;@&W_GJ;vjt#`$G}kGQ za=`?IwCU^-(#S}sdn#1RdOpLsE-QRt0{nX{yC#)>ItmN%RQtEUs@J zBwy%uU=OBMo*QzgS{%WhqAOfTj39y!BN;R^+P_w-xn z^L;iKy9M3??o=&%Z;Q!7`$|be=<;@0gnw`93K?sX^{hXa5yOEGkNV)^|$ysP(tZ7^!VsA~YJC`9ColifyGc>2G z0x@R7rL1eGlCE}Vvf|NLQZ!Q5h0_d6n<=FQ7cT(fP>Ee5FKq!i&v@+zlPU`>7=fDv zdW>=;me)l!mIen_Wi|agn@luYe%D(6InB(6(Qm6&za_Rnc^^d|bTY`$!5>$Bbo7!X zQ=`%7p6zsjjZ$e$2SnsN>s%o7$JwT@a2`l=v?W%g>s-k`?8#%LIPwxU24G|M+ zfiUw3ua0%uA1d)3{AP-X`pUvF7m;WRodPxhBx>KJF|;R(Mb~H&YHp_M;D9^Gpy^sr z0(|P-c?|=H3Ct)$YMYvG?G(QGptV3s7xF!qyIl+E7I|1`_iaE}aDAI->J&-jn@X>s zAqwdZhY`W+lavyql!$f27X39CP zQ=BGW9v6m@_;jt?O?&-ZDAl2~BLb^DLMQsNx6pV+=uK??4iYUeuay)55@}=|(U{L6 zqdB&jNdb)JnBsn**Vc{M;bdqWU?8H!)+h`P-!QF`gOt$|YwxNYIlzryLx4dG0q(@?PK6_~X*m72E`*|i1^oS2wd%e=-JO9+U z-|Az(hO`dKNi1{3^tMHO{E~oC+_;(}qxFVsCFByadA8g}H z3>u>qXw|Fz$bt9sqc4f*3DKo^ZJiS8rCR%A);;GFT?a>7j%_gTp9ZOV`H{AhjMc?K z>eRipUO&0(sIyQMqi%lNo#(s$32H>U;!uGmDPqG}TJ zB7&2(OVj&`j*gZ&{{QFV?GZ{2IPt%*T_~F>jch5(rNJ{FL-^rw0B0RFv;T}Fr2bF|9q+v zmQNUQ$D82iU2s?nx^`F{HsHCVUOQ%p+d@RYmLv?}I~DDyY?pP$w0!6Tm}-S)evykc zz~O=T&-=e5V_E;XU4ESX|NYMeGd@{()D`~{XBO#@L&{>zVHMWErtnl2yR91vl2+sJ zk(YR=Ke$OtS8;<_?DP zN!R8n?+P^!URy~b$O^a!5*bYap!o=)Tf=-)NSV+=qGlqq^IB;#Jo~GXT9uu(@PN2R zd@uU;kXl2QL(NfC$;^GmTO*%u`zCz>HBQbJ;G!Pjpit)@5^W0xmi0T(J~-yW*|;sO zx295pEpWnaJkM2LPt}7sG@Qf}1%KF}+GHW|0c$AZ#}Lh~j%^++S|5!Nw({KH+yR2ePHN8l#N1kqugCb4!YMhl$iV!8@}93c90kV#`4Xy70LRP_ zj-C?vR=Nh)w0EW@^D2OGqZe%pJH1)YEPJrglTY>!{yLpk)5z-qU+8WBX7Cqv0@WtX zrQv__$rjE&)%mJFpyO5v;Ygfp7MvZivwi4WR*AwpdQbnTbRyx?WZtf*m1z5~-wgxD zSpphuTwg1bGN~1=@Me8H1UVP0NxbeY@l(a68|C6uE>32gfG3X(cpKu1g(0kzCYc@t zZQ!RZcg?UoP6b;A(cddpy+7`wf*&4o_Z;O0lTB??rE#H-Z+|W_NBV)}eX+n(I#A5q zIxR&ZN5FgpZEE2Oi@W;i^yCJ3=rn#AFQ{=gbbd!1{DQddyDe^4K<>`uN#vX9Br z5q7@|jlRXLb@92LXEzAXvC zAv&f)t;8j}IeCUREMD?pSb$}HeRKP!uKR+k!)RW^<~mewfGbNCfD?708s*g+pj4oL z{xxnP8vkW@iIrH0_*5gi{4DWxgVF5cnH%{*WI13-+Np0Hqr_)TyZxxRREulPcbfzm zZ_&7{=PCfv+fA1LVvBfUNr{ZcGG>~z70Mq^b}+wg?#kWcOZ)q$zePO*00q);KVHdG zry!f89j9~C+{?5Ov0$7vc%sQ8K!hLAZOHZl4(^EB@!-Zb&Jx*rj9)-=gBi}(zF=}w zgXIY|-q?rWqpd55ymJYcqekms)CY>g1FuhVe*@hplb9D@S>pC)cd)OT?f5dBd*gu( zaTARZP4z*5ucwBLACJRrCDD&-edtiqmWSKioeuY(()eoS(S-KvY~PZbXv*NIQpn`u zMo+xrqRfoMT+$YCDwqcYSRLhD=n+-4UJ_T5GIalTR4*hduutD~1 zMdf{>059;@fePPHAO(Thn>HXJiHp4hPz_d?&>NR+u6-YCLOJ#8=?4&FK2pBC<_Sgh zT4{TU8j5@Ge=z<1VX@7e>ns3GWQ&)aqwhRA%zE`i@?M2gz8aHe^AMCZdA%ssC+nUL z|3EjQ6Wle{SeSPZ+~SkG1#cvHw`K9{QZx-?P7f@I^zZ_Th6h{_m@HZIwB^Gt4mBv@un@{rERt8985;gq ztM6I1BD_ijz6T~hCZ4`EJ1oE|&~$l!c%mgc9b-OPFk@$qmmH>x&jOL0+!2u=(|PE- z#Y!T!p%Jziio}e3xmC~D6q(po!rgO4f*WkxrUdqH6FcWqWdd9}QXd=zQu2T4{kx&o zE>MQf0ty`t4ua)*Pu zxE`0ha@i4%9mx$!252*DJ}>Q@$|It@mtO1c(Pfk+w}yN)8H&wxGP6w>jlT%`a)y3% zkvYVD_~(Uy-EzlJOy6 zNYaiTh`8V{)I+iOxaxz>fx64DE7&Ywo7(bAgg|mnzS2n++iDcFw)m?4Up>4AjK*jc ze~<3AmPZu$5^7U@i|^*(q)*Z3-S$>}tyG0ruuAXR#tJsl_b5)l3JekE0!uho35_P0 z7EO`cNB7DBmSQn$p58man?=(L{E@~Ev8dG=XP)I>5PUhZKqVFw;yFrfJz2r8^e>r44mx<~G0^Hu!%AD{P1;bOBp>xX(z z@D5*Cq;#M*o4y9Xf{!uNsS|J0g&d$(0~H51+@{@}j~6^4RC}!_kZf)Uj+xQ|iYiN9 z<73lE>o-cma&=5L2M*O=<#|tjr46euXQ8f^^1Db0l;DqBmA7R`k$_^W zxJd*Y0kw;c3A+_oDOL>r!vhOYia3IMWaSw^$9OhN#2?vV>Z8fwK7X>g{f+w>@dG$p zl#n$$<~C9-ln2}n1@F=P>cDcsXR_KiS@q1rP%hUrPuqZeKPEuF5BH2V4k7}p%`&aR zKix0E8~G9zh=j0VJn*KUDe%0q*%Ui7mw+ZW07r)<{N-FUbxc+D@H)q8XbxMYbq+S8 zceDG@isD=d5ed$XU$#O@l4R;{s?}(Nkw#7%-ocsUO=V`VkB%+vXeG1aAk6I+VB4IA zQ(}nga$kQlx}DHmVVwF*ML78Kzr){Ho#2W#65om4;;Bc5nleSE}k;R@9OvfL5kKu4m)f#Z! zr1hUiUKK=XZo_RN-4wFgYcF-3R_{qT`ft^=`oe*|58n|hWoolzug`wgqoe(QBk;QI zOJcTF?^wEC&MvITkvi<*?NjxOif-6megwQZPkfT@Lx&tw~lpx;#@gd|C$1eXllt zIttx$cwJJpsspd@oMIe8<;i{d%Zg?=Xu0-4=OcX20*pH#_(>C?gL1BUdMgAvWCHt| ziW~4WO_&OhZuxm*`Se?_+ycNL`_E6Jk4KAvfK=uhCt&=sWS;|iK)SBHxxDAHVbig; zGs0yZw#FD2NQg&t=;)Cx?wSLf`{-@OQc@&Z zJyRo5D-qm!RxcCgvY?~N-L}E*W+8c%Mw0>CS{NBt^$=z#-(e6YGpai~FHF+)xWXW% zQF>r#4N;ri#v5I!zF^9X5U+cL8bp=J6LigMi4DqwV7=wJ4Uy<;I}DH6aMqZlYhMtr z^+Sie9M{BL=zw=S(1`;5f-arC;hP^|{d9I5B2eFaT)oR~Et&yV>Nc(RZ#`rsvK@w3 zH48%{Hwe_Z(@E<+(o=#0CYS=rK9C*+xsm3Gbh}WMwHltcyg{IAR?l2}IOSj7Y9bEO z7D!v$7p|Yw%zeu_Owe7A$yw@J-RKeCEJ?ud4 zONf2BynF`S+}+<;1Q%FTbpkeW9&1fYa}^N?y?s&d7U{mzLrhn){&_${l%qL^4+Xsu zhdvIPlEyU`^PH0|IcJ@K9GZ*|Wcc6G1A6(zZ_Wf2OTX}HglFXGp9fwSiNQPQ)aXh! zxWR(nh$os6#tC2;wGq3n!#7(J8Y~=yAF>OObLX8kE2X2K79f#8nje%Kb5dZX%@kgJ z*fImEyFiCIcWdz8HmxTkVJVaMC|Us&FmV4UI&Yl>IP3`3;R4Q#zzLkUY=7e|1 zb}O@u_dsfa%yQIsW|lL%X;Ns40_eB75lJ{KW||ji1hzocE)-sN_OzmO+gJ$~EwdY} zLpFd;jlVK?_%oWH&NTG7CYsxyUd>B|QpEU9Il4DnlV|sf>za?Lv|l2Ph8S3{oi)kZ z{*d*eMXN;FgzU;RrlnuLS(j!%eT?hG_Hsrd`gyE=%4_ob8jDhDY_PNneA22+Y>O_2 zTAxJWLEavs>+Ei6AG`iL83`0fW>Vx3ulTIfqDy8TFBI^7iF*;o6o;JNAy-b)q7vn8 z$~Wtag|5T_m@JsZ_4>-FmKOroC=!T-O2bw(yd~BV7+TP=IZ6Lx=$Xm~Y@5t{8ptw_ z32?S_{omnSaCdt$*H`q2pXjSX*=&qhO15_dQH&1ZR|So22Y`zA-Nx!V|MD+NN%6P# z*%rVhfw(e6bfh)$lQO}Pl{njA4cvySnP@Qw9-j zxP0y+sxm7%URB0L`lqOm7G^ghYxgVfzm}SlPm(K#{xVD81p;3h*@Tc<`1u1HY@F<3 z3de&6uXlAT6~50OfY%$?M4aG*w3NIjj9L19^-=w8`YE)3G!j2~Tdl=0 zgq)}A0K2Xn{N!l=((2ml9;tR#X^QzOoWP(BGq|zEyTbDIk>i-qse~7~ycO|S zf%2@+gsb}w_^QG!2uwhsr>ua!06m@hEUDsj*4Lb4Fk&rWD@+TFVr(N21RD>B&~*;? zr-nKZK!_x%8{HTuX7>}5so`-{p2u+0EMDr3ErF_`Kg;zVF%ZQbRb-$XcEF&p>i<38W_!S9qrC!lIed zfrmY=S}YQD@U7t4aMnyo8^7oD)|U`oz3=U8g4YIL@$)twkOC0C_+eTH4#&Y4QqY;+ zl>rOrjLQjQBBhlUyfBJG9N*b;<+8&52ZvQ%o**#PB~nVr)f61d=bu5^{7T=$tOf?u z{z4l!RCY~00Y~|=c7GICqEVG^XCc3)Q^EyCc7qR-P zpBbU;{}ERsx6}N^O*3_)1**JClE9(yN<4H0*G4(;G+qt!#uomd`H=56IMhz7crzaK z;iiOiWPrdE*FF~DW|n{GbyX<}{j&F&uYtIk{_`cjJ$>2pw{PcdLS-mR>_3gD4LOro zrH>dX{@!b=$pV}M54_hM(Qhp!;mB0ZjAv_SJrqYjR^hTllU{1tcS7q>#}ALZ=^~O! zxa!P!(soUQ0~1c&gy4IX{j-+R8wGnpg~p#{O{{C!8oc9r91(S!vxxJrv)cOKLpthf9Pm9y9@ z)2b26nfO+=K=-t0c;ideM6A|BTUEAVM=W8Wid8iVRYRcU4)Pil6V9q{f^ko8tE}BS z0Nne#_BM)wELj=a8*53|J!_+fqu+Kvk43#1S&w!SFm3)Ja3whkH0L;@mK zv?kUbBORNkkbYjpi{P4V%cB5187i5E@OS1sK31vzD@d(UzdW>yaU<(NPBT_*$Si)@ z76oVIu0jRZ^8LpUFOHXcj_7%e&g*%*V8MwGyBJCe!Ll%;y})a|umO585V(Q4djoOvpX8J4g)Y#+DG4)odGRq*73)P z;(6`0M|&CQZb!!}hMHR!z#4o;fz=YdnqUz*ScYPhrCS=ePax-IZd^uvKJ@tmXo$kt zGKe#89`IcD1xJr3{|eh0SBo6qJ!P?e@WDsRH^B0OUzq;z(E@Yq=-WD5dd$hu{NRw2}U!FY-i>>WG> z#Z8gk7G>1|VmXK$A|hvIJ|Jc*k`BX9@`G(H$6?ripG^qKy*m(R5KoVZNQq6g?m-x* zg1l!LhgdRPD?E7)N}ZP51Dgod(;XBu-zTVL6!Rz62p$!Fz^rOEYZTfPO-keK$P$jv zwN?msPTkB1OiPxQ-TPZBamnq8SKYI1tpwHiQ7rdS;AsGp7egocAM&E*yGT+SEE6_$ z(PJH58RWog1n8gF{0@WSHmv42k}Z>-gxoj}3!x z(r=l#QQv*!zzVQJj+*aWjHQzPx3y|3xuQYDmh|0-DF6R2OLYfc_?x1YFHY<~>3DnU zuFV|f5{E-_u4MY^9Wa@O9qEl-2T<$GX4v|$=PdqW&A9XL>PXZSUen6Usj?nM-j#F~ptiG)?3a-04|u>D4bP zb3Y%!1)7lO(GpS^&5U<=KF7dAlc7v(cSZ&>n%YF^{ejI=B)M5I`158;5}2+~eIF;( z$Dza4y|{%q#iqu}8cdmEFUAQ8EO;I$zcXN6zgTI+Mk2 z&ze|jSjBFSBC)O&X%c7)T1gO{hQ+cVAbTJv#Fce6FmfIxDqqqTZRPH5WV$^kN3BZ{ zi!lO;__IaDybW$2!nOBUkD%e;*+6Si$8-9vF1l9mXkl5QI1q%{g5KnKp=>O126(Y$ zpzH_hqiWnUH$a3+1VHhYB#9s6xupsw24*ZUBdTnd3UI39J9@(`GlT>9@T#^B`+;hY zeRD(aoaZc;1@p8uEit z;J1zc>|FQqWn3t}JZIkv^9I{}culRNXuW<`* z@3d&F203}k&k1b#QBF>anzwC<`rUhIO|s(c?)rtl+&K}%(z7R)ZNbN(@n0sreOus- zzbSnmxwVf37+H9#SwF$!f@qD#1oQLkTJIGp8e=@^79y(vPA{uZ;qH>HtwGXzl#~j4 z-mr#KvbM>i%O2%OZx?lS^JTf+(^+|^Gc!@5H9J4Vw6ouycisY}R^CoCT04&WBopj> zh4vi_s@nhV;`OEWf{{33T(dtKna|ePp8_n74BP`b_;UYj0PGhiM5WJ(o3BZ|?PdA2aUK3M7p#QGap2v67o#xkx2>voaA46s zvl(wPl%x?94*`C~Ab=0oT1B@q&jXTDbz;8PxJO0F?vG&0nI$vimh}6bg677CQ12={ zyzZ_wEQb_i=8X(FW|eWaZb~6F`Q9e#p ze!~>O&83HB4C~dM-1MND*^0zm$Gty>v)__ik#1OR`JUp_!#{_`5z0=5PUYp-ArLfg z4FgjsY5W5Uj5_6K^h`s9aM_W|%vYLFPs~+|9W`5{wLaV;S%HS>2<($V2Q#p%nuUryuuXH>puy>1hDL zt%LK2wH{};@X3^hLV%$5#8)vsQt(t-YjtOe$`zS!Mc;~X#}9H;(us_SfIFM}!%74s z|AC(#s(SVbnZ0AC=`O3Q|1R=-N{`K>mnEY2-E@QhJa~SlD@`?5zpL0{z2-)Kqk+TQ zA?XFg>&O(b7{MYUfTVzj-jav`QQ1Bu3&> zf-W1%uL(zu;L6Ac&w`Mmy%#)18u4yO+C^W}OkJDYzi5+ff6{qMo&ZOqIbvV0JW)6F z4yMmcPr3xY0wr}Tfx0$yIEL5)d%AC~!ejzKr$=wN+fX)!)ABnmg1@oBR*^qs5R&@} zbR}~TC~z3Wx3+U~U)tqDkt17!Ny!8F=!gAijYMF(H{&3U+{#*shm)7tAfrX|4K_Fn zPLL~@!2CHR76jp9(?_YJ>sN60Lk4r4vI^x-%pf=3Vhe3yv8kh0+>BMiJ7!wx|WfL9T4BcKYER^6pE z*xF=xdt`G%1YDyUbw+@!exXrgpg0{&S`U`;3Uz>pe?noC9MMvlEp0-c$rE|d?OjDe z=8FO4;ii)hL2u%dCtdeTN(+Zzu&u(f}Db1aGcY zNoVq7HZE4&B4nY{{u47le~m)$lbYW_QWQiudi&eK?FXVqxIBfhVM29Dch2 zdg%39XaYd<4C1sR3+id(*4j3n?~UK2OKk$NOk&CfVOWzYUYl)yn1+5}A4PX;*gs;1 zSX!zt9Ymgd?3*Fdy5M(i51q(@6rmtgI9nIVXM>0LAm0FF+cw%oIbKTX?i;5{hz61) zxCCaiz2SHV8|ZJ=M+Ch32lEFVw;3ClVnwG*s2@f5z!Eq_wSDncLJ#!B!?0SCX_{foL~^v7-d>+!#(KCk3Q1 z$@0?Of*j-)Q@&$b#?4A@$aB`Zy@#kSYHO~>f6FYhYqJ|i#+szb+_eiEcCf?=Br*3M z>*98Nkq@w64}kat!PsJ7)9~9t?Gz;&-2iy@h&M`c3d+M9jXa~c7Q-gN<8*9|RQl}e z*&)=KpI@;9VhZnG&5%ne-y~D;uU$+7D`;T+v)o%;=QzGQdQxwg=LJm-IE7)RQ{0+9 zU^^*e@DVXZ?czIaIAnN_3bOJq3qbXCsfu{{ty zO1~k$pMnpnlJ?-sd7}38J6|r*a47&Rt~w6deqAQ?@IILsor!abv0;#()L~|gwEV^c z=Ii1u5Y<5Hb^~T4qhBOR9 z1w@HZ@bf7VAJPm%yF9*l3;7njoa1z>RF7QWFdA_HWkc;Tan>tDr0Kc@<+TAph_}s{ zDqC-=(wV0po298z1^5-*UURegZDJAy5Z6Xv`iV?X^B$Fg*$9k@YD%U?qLjG##ReoanFTiQ|>^_Wcw8^|$!aj!a9RuKcHQ{>jWm?sHd;MkF z!Cjgl8`ZM_L5GeQ3uhneUF%4^gJc(Kvx7XwDbdcdgr9#H1&vIumu%QwPh2V+m{Hy| zlR|(DMCi-A%%}HU;=5A;FTf(uuHq^$0K;hIB4-jKC6?u)Ord!|J${SsvmU|(RG%wq z7Cn#vxX*`rX0Br3Lw^rE;=7iG={TON_c7TR$IJqu5n;=3oLX6I+8`vnnVyHK%gMkZ` z#_cITC4L$%^;5DrZylq<7O+==FT+Aiqoi@7I-&X$LmDx$PF00TT2LZk7L-<=bAlf) zcv%*USgr@aAHjaMOoW*%tRSc<{uKW#;V$-+$hLF(g0X|D^h~;Y9e}weFk>E2JL(rJ zoXs*ACsf&0tt;;L>lTQ%YXqP0=X2D3XKPHZ)2`Y&5vPm8_HJ#lx?sv}AkOw4b;h zWD--^0NOtt&n7cL9rg^ac?8lN$n>`d%E=;YTlA8T(nUKVfKH2)F>bjJ`yw>_>`NDx zFzrwlE+HnL%^e`_A&m}CL_Ir#FkFgHJ-U8l?sAKN`!+H%2RobK()~r?fu8M~(>ic| z9ZRPkm8OfM#2zPHxMX3b7+Ju9t9A^9nOxvcuT2+1QFL(nMu?dHRF%aY0MW_O4J%#*YZnEc5D{TGQx0$WdTi^Q!tlC zaex5#34_^NF(mS2cO&@5no?uCA|C_&B6l4~`96_cZshyM9^>O?+E0>Qfo?&n4 z6_nK;YuA2saHQMF+N5V{%I)aFcgK3NMSCG!==$vwD1Sygi;_l2c~AGHmcFo5`|mh7(-nG){rM+)nF-zM+^;>9UY|UALYAF*kX-qCWWQe)lnCJM zBF=)#H29XH=AjxEL&zv0w9Lo-*p#YC^y+AAVgH;G(C8vgz1TS=1PGjwGBS$-1KMv!W#7t4-gPB z@R7^f#^g{lJ28@85f9unfJB%OUM#za2G>BEk1>ShSJRf<`Iq_6Y9v>JoOl_rUnxW` zgX>;^2m-%i11(`KYE=t=qry^FfrQV!M8TZwE+d|L-i#gkVtPd_O5y^qF*b3sqSulaT_Ze+y3S#mY{Z*CaE3l?(n3{U_7 diff --git a/packages/google-auth/testing/requirements.txt b/packages/google-auth/testing/requirements.txt new file mode 100644 index 000000000000..df20f96d65fe --- /dev/null +++ b/packages/google-auth/testing/requirements.txt @@ -0,0 +1,20 @@ +# Unit test requirements +flask +freezegun +mock +oauth2client +pyopenssl +pytest +pytest-cov +pytest-localserver +pyu2f +requests +urllib3 +cryptography +responses +grpcio +# Async Dependencies +pytest-asyncio; python_version > '3.0' +aioresponses; python_version > '3.0' +asynctest; python_version > '3.0' +aiohttp; python_version > '3.0' \ No newline at end of file From f5760ef2ad7a2d66f72c9588a0a4bbe86319ec44 Mon Sep 17 00:00:00 2001 From: Ian Rodney Date: Mon, 18 Oct 2021 01:52:00 -0700 Subject: [PATCH 489/966] docs: Fix formatting of `GCE_METADATA_HOST` (#890) * Clean docs up * Update google/auth/environment_vars.py Co-authored-by: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> --- packages/google-auth/google/auth/environment_vars.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/google-auth/google/auth/environment_vars.py b/packages/google-auth/google/auth/environment_vars.py index d36d6c4afad0..c076dc59da1c 100644 --- a/packages/google-auth/google/auth/environment_vars.py +++ b/packages/google-auth/google/auth/environment_vars.py @@ -41,15 +41,17 @@ # These two variables allow for customization of the addresses used when # contacting the GCE metadata service. GCE_METADATA_HOST = "GCE_METADATA_HOST" -GCE_METADATA_ROOT = "GCE_METADATA_ROOT" """Environment variable providing an alternate hostname or host:port to be used for GCE metadata requests. -This environment variable is originally named GCE_METADATA_ROOT. System will -check the new variable first; should there be no value present, -the system falls back to the old variable. +This environment variable was originally named GCE_METADATA_ROOT. The system will +check this environemnt variable first; should there be no value present, +the system will fall back to the old variable. """ +GCE_METADATA_ROOT = "GCE_METADATA_ROOT" +"""Old environment variable for GCE_METADATA_HOST.""" + GCE_METADATA_IP = "GCE_METADATA_IP" """Environment variable providing an alternate ip:port to be used for ip-only GCE metadata requests.""" From 3b8a681b4d1d550133b40be45836f68d8a5966e8 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Thu, 21 Oct 2021 15:25:46 -0700 Subject: [PATCH 490/966] fix: add back python 2.7 for gcloud usage only (#892) * fix: add back python 2.7 for gcloud * fix: fix setup and tests * fix: add enum34 for python 2.7 * fix: add app engine app and fix noxfile * fix: move test_app_engine.py * fix: fix downscoped * fix: fix downscoped * fix: remove py2 from classifiers --- .../google-auth/google/auth/_cloud_sdk.py | 4 +- .../google/auth/_credentials_async.py | 11 +- packages/google-auth/google/auth/_default.py | 8 +- .../google-auth/google/auth/_default_async.py | 8 +- packages/google-auth/google/auth/_helpers.py | 17 ++- .../google-auth/google/auth/_oauth2client.py | 6 +- .../google/auth/_service_account_info.py | 4 +- packages/google-auth/google/auth/aws.py | 21 ++- .../google/auth/compute_engine/_metadata.py | 12 +- .../google/auth/compute_engine/credentials.py | 6 +- .../google-auth/google/auth/credentials.py | 11 +- .../google-auth/google/auth/crypt/__init__.py | 4 +- .../google/auth/crypt/_python_rsa.py | 9 +- .../google-auth/google/auth/crypt/base.py | 11 +- .../google-auth/google/auth/crypt/es256.py | 7 +- .../google-auth/google/auth/downscoped.py | 12 +- .../google/auth/external_account.py | 7 +- packages/google-auth/google/auth/iam.py | 5 +- .../google-auth/google/auth/identity_pool.py | 6 +- .../google/auth/impersonated_credentials.py | 8 +- packages/google-auth/google/auth/jwt.py | 28 ++-- .../google/auth/transport/__init__.py | 12 +- .../auth/transport/_aiohttp_requests.py | 5 +- .../google/auth/transport/_http_client.py | 12 +- .../google/auth/transport/_mtls_helper.py | 6 +- .../google-auth/google/auth/transport/grpc.py | 16 ++- .../google-auth/google/auth/transport/mtls.py | 6 +- .../google/auth/transport/requests.py | 19 ++- .../google/auth/transport/urllib3.py | 19 ++- packages/google-auth/google/oauth2/_client.py | 18 +-- .../google/oauth2/_client_async.py | 16 ++- .../google/oauth2/_id_token_async.py | 8 +- .../google/oauth2/_reauth_async.py | 2 + .../google-auth/google/oauth2/challenges.py | 5 +- .../google-auth/google/oauth2/credentials.py | 8 +- .../google-auth/google/oauth2/id_token.py | 8 +- packages/google-auth/google/oauth2/reauth.py | 2 + packages/google-auth/google/oauth2/sts.py | 7 +- packages/google-auth/google/oauth2/utils.py | 5 +- packages/google-auth/noxfile.py | 17 +++ packages/google-auth/setup.py | 25 ++-- packages/google-auth/system_tests/noxfile.py | 51 ++++++- .../app_engine_test_app/.gitignore | 1 + .../app_engine_test_app/app.yaml | 12 ++ .../app_engine_test_app/appengine_config.py | 30 ++++ .../app_engine_test_app/main.py | 129 ++++++++++++++++++ .../app_engine_test_app/requirements.txt | 3 + .../system_tests_sync/test_app_engine.py | 22 +++ .../system_tests_sync/test_downscoping.py | 6 +- .../test_external_accounts.py | 14 +- .../google-auth/testing/constraints-2.7.txt | 1 + .../tests/compute_engine/test__metadata.py | 20 +-- .../tests/crypt/test__cryptography_rsa.py | 6 +- .../tests/crypt/test__python_rsa.py | 10 +- .../google-auth/tests/crypt/test_es256.py | 6 +- .../google-auth/tests/oauth2/test__client.py | 15 +- packages/google-auth/tests/oauth2/test_sts.py | 24 ++-- packages/google-auth/tests/test__helpers.py | 12 +- .../google-auth/tests/test__oauth2client.py | 8 +- .../tests/test__service_account_info.py | 3 +- packages/google-auth/tests/test_aws.py | 74 +++++----- packages/google-auth/tests/test_downscoped.py | 20 +-- .../tests/test_external_account.py | 84 ++++++------ packages/google-auth/tests/test_iam.py | 6 +- .../google-auth/tests/test_identity_pool.py | 12 +- .../tests/test_impersonated_credentials.py | 34 ++--- packages/google-auth/tests/test_jwt.py | 8 +- .../google-auth/tests/transport/compliance.py | 14 +- .../tests/transport/test_requests.py | 22 +-- .../tests/transport/test_urllib3.py | 12 +- .../tests_async/oauth2/test__client_async.py | 15 +- .../tests_async/transport/async_compliance.py | 16 +-- 72 files changed, 744 insertions(+), 367 deletions(-) create mode 100644 packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/.gitignore create mode 100644 packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/app.yaml create mode 100644 packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/appengine_config.py create mode 100644 packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/main.py create mode 100644 packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/requirements.txt create mode 100644 packages/google-auth/system_tests/system_tests_sync/test_app_engine.py create mode 100644 packages/google-auth/testing/constraints-2.7.txt diff --git a/packages/google-auth/google/auth/_cloud_sdk.py b/packages/google-auth/google/auth/_cloud_sdk.py index 1f13ad4207be..40e6aec13a8e 100644 --- a/packages/google-auth/google/auth/_cloud_sdk.py +++ b/packages/google-auth/google/auth/_cloud_sdk.py @@ -18,6 +18,8 @@ import os import subprocess +import six + from google.auth import environment_vars from google.auth import exceptions @@ -154,4 +156,4 @@ def get_auth_access_token(account=None): new_exc = exceptions.UserAccessTokenError( "Failed to obtain access token", caught_exc ) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) diff --git a/packages/google-auth/google/auth/_credentials_async.py b/packages/google-auth/google/auth/_credentials_async.py index 760758d851b0..d4d4e2c0e45f 100644 --- a/packages/google-auth/google/auth/_credentials_async.py +++ b/packages/google-auth/google/auth/_credentials_async.py @@ -18,10 +18,13 @@ import abc import inspect +import six + from google.auth import credentials -class Credentials(credentials.Credentials, metaclass=abc.ABCMeta): +@six.add_metaclass(abc.ABCMeta) +class Credentials(credentials.Credentials): """Async inherited credentials class from google.auth.credentials. The added functionality is the before_request call which requires async/await syntax. @@ -81,7 +84,8 @@ class AnonymousCredentials(credentials.AnonymousCredentials, Credentials): """ -class ReadOnlyScoped(credentials.ReadOnlyScoped, metaclass=abc.ABCMeta): +@six.add_metaclass(abc.ABCMeta) +class ReadOnlyScoped(credentials.ReadOnlyScoped): """Interface for credentials whose scopes can be queried. OAuth 2.0-based credentials allow limiting access using scopes as described @@ -167,5 +171,6 @@ def with_scopes_if_required(credentials, scopes): return credentials -class Signing(credentials.Signing, metaclass=abc.ABCMeta): +@six.add_metaclass(abc.ABCMeta) +class Signing(credentials.Signing): """Interface for credentials that can cryptographically sign messages.""" diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index 8b0573bc8a55..4ae7c8c06125 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -23,6 +23,8 @@ import os import warnings +import six + from google.auth import environment_vars from google.auth import exceptions import google.auth.transport._http_client @@ -116,7 +118,7 @@ def load_credentials_from_file( new_exc = exceptions.DefaultCredentialsError( "File {} is not a valid json file.".format(filename), caught_exc ) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) # The type key should indicate that the file is either a service account # credentials file or an authorized user credentials file. @@ -132,7 +134,7 @@ def load_credentials_from_file( except ValueError as caught_exc: msg = "Failed to load authorized user credentials from {}".format(filename) new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) if quota_project_id: credentials = credentials.with_quota_project(quota_project_id) if not credentials.quota_project_id: @@ -149,7 +151,7 @@ def load_credentials_from_file( except ValueError as caught_exc: msg = "Failed to load service account credentials from {}".format(filename) new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) if quota_project_id: credentials = credentials.with_quota_project(quota_project_id) return credentials, info.get("project_id") diff --git a/packages/google-auth/google/auth/_default_async.py b/packages/google-auth/google/auth/_default_async.py index 3fa125b46243..fb277c54e82e 100644 --- a/packages/google-auth/google/auth/_default_async.py +++ b/packages/google-auth/google/auth/_default_async.py @@ -21,6 +21,8 @@ import json import os +import six + from google.auth import _default from google.auth import environment_vars from google.auth import exceptions @@ -61,7 +63,7 @@ def load_credentials_from_file(filename, scopes=None, quota_project_id=None): new_exc = exceptions.DefaultCredentialsError( "File {} is not a valid json file.".format(filename), caught_exc ) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) # The type key should indicate that the file is either a service account # credentials file or an authorized user credentials file. @@ -77,7 +79,7 @@ def load_credentials_from_file(filename, scopes=None, quota_project_id=None): except ValueError as caught_exc: msg = "Failed to load authorized user credentials from {}".format(filename) new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) if quota_project_id: credentials = credentials.with_quota_project(quota_project_id) if not credentials.quota_project_id: @@ -94,7 +96,7 @@ def load_credentials_from_file(filename, scopes=None, quota_project_id=None): except ValueError as caught_exc: msg = "Failed to load service account credentials from {}".format(filename) new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) return credentials, info.get("project_id") else: diff --git a/packages/google-auth/google/auth/_helpers.py b/packages/google-auth/google/auth/_helpers.py index 55adf5bc3c7b..b239fcd4f4e5 100644 --- a/packages/google-auth/google/auth/_helpers.py +++ b/packages/google-auth/google/auth/_helpers.py @@ -17,7 +17,9 @@ import base64 import calendar import datetime -import urllib + +import six +from six.moves import urllib # Token server doesn't provide a new a token when doing refresh unless the @@ -85,6 +87,9 @@ def datetime_to_secs(value): def to_bytes(value, encoding="utf-8"): """Converts a string value to bytes, if necessary. + Unfortunately, ``six.b`` is insufficient for this task since in + Python 2 because it does not modify ``unicode`` objects. + Args: value (Union[str, bytes]): The value to be converted. encoding (str): The encoding to use to convert unicode to bytes. @@ -97,8 +102,8 @@ def to_bytes(value, encoding="utf-8"): Raises: ValueError: If the value could not be converted to bytes. """ - result = value.encode(encoding) if isinstance(value, str) else value - if isinstance(result, bytes): + result = value.encode(encoding) if isinstance(value, six.text_type) else value + if isinstance(result, six.binary_type): return result else: raise ValueError("{0!r} could not be converted to bytes".format(value)) @@ -117,8 +122,8 @@ def from_bytes(value): Raises: ValueError: If the value could not be converted to unicode. """ - result = value.decode("utf-8") if isinstance(value, bytes) else value - if isinstance(result, str): + result = value.decode("utf-8") if isinstance(value, six.binary_type) else value + if isinstance(result, six.text_type): return result else: raise ValueError("{0!r} could not be converted to unicode".format(value)) @@ -160,7 +165,7 @@ def update_query(url, params, remove=None): query_params.update(params) # Remove any values specified in remove. query_params = { - key: value for key, value in query_params.items() if key not in remove + key: value for key, value in six.iteritems(query_params) if key not in remove } # Re-encoded the query string. new_query = urllib.parse.urlencode(query_params, doseq=True) diff --git a/packages/google-auth/google/auth/_oauth2client.py b/packages/google-auth/google/auth/_oauth2client.py index 3512e1d116f1..95a9876f3151 100644 --- a/packages/google-auth/google/auth/_oauth2client.py +++ b/packages/google-auth/google/auth/_oauth2client.py @@ -21,6 +21,8 @@ from __future__ import absolute_import +import six + from google.auth import _helpers import google.auth.app_engine import google.auth.compute_engine @@ -32,7 +34,7 @@ import oauth2client.contrib.gce import oauth2client.service_account except ImportError as caught_exc: - raise ImportError("oauth2client is not installed.") from caught_exc + six.raise_from(ImportError("oauth2client is not installed."), caught_exc) try: import oauth2client.contrib.appengine # pytype: disable=import-error @@ -164,4 +166,4 @@ def convert(credentials): return _CLASS_CONVERSION_MAP[credentials_class](credentials) except KeyError as caught_exc: new_exc = ValueError(_CONVERT_ERROR_TMPL.format(credentials_class)) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) diff --git a/packages/google-auth/google/auth/_service_account_info.py b/packages/google-auth/google/auth/_service_account_info.py index 54a40e9daa0b..3d340c78d40c 100644 --- a/packages/google-auth/google/auth/_service_account_info.py +++ b/packages/google-auth/google/auth/_service_account_info.py @@ -17,6 +17,8 @@ import io import json +import six + from google.auth import crypt @@ -41,7 +43,7 @@ def from_dict(data, require=None): """ keys_needed = set(require if require is not None else []) - missing = keys_needed.difference(data) + missing = keys_needed.difference(six.iterkeys(data)) if missing: raise ValueError( diff --git a/packages/google-auth/google/auth/aws.py b/packages/google-auth/google/auth/aws.py index c8deee7079f4..925b1ddfbde1 100644 --- a/packages/google-auth/google/auth/aws.py +++ b/packages/google-auth/google/auth/aws.py @@ -39,13 +39,20 @@ import hashlib import hmac -import http.client import io import json import os +import posixpath import re -import urllib -from urllib.parse import urljoin + +try: + from urllib.parse import urljoin +# Python 2.7 compatibility +except ImportError: # pragma: NO COVER + from urlparse import urljoin + +from six.moves import http_client +from six.moves import urllib from google.auth import _helpers from google.auth import environment_vars @@ -116,7 +123,9 @@ def get_request_options( # Normalize the URL path. This is needed for the canonical_uri. # os.path.normpath can't be used since it normalizes "/" paths # to "\\" in Windows OS. - normalized_uri = urllib.parse.urlparse(urljoin(url, uri.path)) + normalized_uri = urllib.parse.urlparse( + urljoin(url, posixpath.normpath(uri.path)) + ) # Validate provided URL. if not uri.hostname or uri.scheme != "https": raise ValueError("Invalid AWS service URL") @@ -631,7 +640,7 @@ def _get_metadata_security_credentials(self, request, role_name): else response.data ) - if response.status != http.client.OK: + if response.status != http_client.OK: raise exceptions.RefreshError( "Unable to retrieve AWS security credentials", response_body ) @@ -670,7 +679,7 @@ def _get_metadata_role_name(self, request): else response.data ) - if response.status != http.client.OK: + if response.status != http_client.OK: raise exceptions.RefreshError( "Unable to retrieve AWS role name", response_body ) diff --git a/packages/google-auth/google/auth/compute_engine/_metadata.py b/packages/google-auth/google/auth/compute_engine/_metadata.py index af0d84943111..9db7bea92d49 100644 --- a/packages/google-auth/google/auth/compute_engine/_metadata.py +++ b/packages/google-auth/google/auth/compute_engine/_metadata.py @@ -18,11 +18,13 @@ """ import datetime -import http.client import json import logging import os -from urllib import parse as urlparse + +import six +from six.moves import http_client +from six.moves.urllib import parse as urlparse from google.auth import _helpers from google.auth import environment_vars @@ -89,7 +91,7 @@ def ping(request, timeout=_METADATA_DEFAULT_TIMEOUT, retry_count=3): metadata_flavor = response.headers.get(_METADATA_FLAVOR_HEADER) return ( - response.status == http.client.OK + response.status == http_client.OK and metadata_flavor == _METADATA_FLAVOR_VALUE ) @@ -163,7 +165,7 @@ def get( "metadata service. Compute Engine Metadata server unavailable".format(url) ) - if response.status == http.client.OK: + if response.status == http_client.OK: content = _helpers.from_bytes(response.data) if response.headers["content-type"] == "application/json": try: @@ -173,7 +175,7 @@ def get( "Received invalid JSON from the Google Compute Engine" "metadata service: {:.20}".format(content) ) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) else: return content else: diff --git a/packages/google-auth/google/auth/compute_engine/credentials.py b/packages/google-auth/google/auth/compute_engine/credentials.py index cb4e0f0a0a7a..b39ac50ae0ed 100644 --- a/packages/google-auth/google/auth/compute_engine/credentials.py +++ b/packages/google-auth/google/auth/compute_engine/credentials.py @@ -21,6 +21,8 @@ import datetime +import six + from google.auth import _helpers from google.auth import credentials from google.auth import exceptions @@ -112,7 +114,7 @@ def refresh(self, request): ) except exceptions.TransportError as caught_exc: new_exc = exceptions.RefreshError(caught_exc) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) @property def service_account_email(self): @@ -350,7 +352,7 @@ def _call_metadata_identity_endpoint(self, request): id_token = _metadata.get(request, path, params=params) except exceptions.TransportError as caught_exc: new_exc = exceptions.RefreshError(caught_exc) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) _, payload, _, _ = jwt._unverified_decode(id_token) return id_token, datetime.datetime.fromtimestamp(payload["exp"]) diff --git a/packages/google-auth/google/auth/credentials.py b/packages/google-auth/google/auth/credentials.py index 8d9974ce1555..ec21a27563b9 100644 --- a/packages/google-auth/google/auth/credentials.py +++ b/packages/google-auth/google/auth/credentials.py @@ -17,10 +17,13 @@ import abc +import six + from google.auth import _helpers -class Credentials(object, metaclass=abc.ABCMeta): +@six.add_metaclass(abc.ABCMeta) +class Credentials(object): """Base class for all credentials. All credentials have a :attr:`token` that is used for authentication and @@ -184,7 +187,8 @@ def before_request(self, request, method, url, headers): """Anonymous credentials do nothing to the request.""" -class ReadOnlyScoped(object, metaclass=abc.ABCMeta): +@six.add_metaclass(abc.ABCMeta) +class ReadOnlyScoped(object): """Interface for credentials whose scopes can be queried. OAuth 2.0-based credentials allow limiting access using scopes as described @@ -325,7 +329,8 @@ def with_scopes_if_required(credentials, scopes, default_scopes=None): return credentials -class Signing(object, metaclass=abc.ABCMeta): +@six.add_metaclass(abc.ABCMeta) +class Signing(object): """Interface for credentials that can cryptographically sign messages.""" @abc.abstractmethod diff --git a/packages/google-auth/google/auth/crypt/__init__.py b/packages/google-auth/google/auth/crypt/__init__.py index 97e9d81d590c..15ac95068625 100644 --- a/packages/google-auth/google/auth/crypt/__init__.py +++ b/packages/google-auth/google/auth/crypt/__init__.py @@ -37,6 +37,8 @@ version is at least 1.4.0. """ +import six + from google.auth.crypt import base from google.auth.crypt import rsa @@ -88,7 +90,7 @@ class to use for verification. This can be used to select different Returns: bool: True if the signature is valid, otherwise False. """ - if isinstance(certs, (str, bytes)): + if isinstance(certs, (six.text_type, six.binary_type)): certs = [certs] for cert in certs: diff --git a/packages/google-auth/google/auth/crypt/_python_rsa.py b/packages/google-auth/google/auth/crypt/_python_rsa.py index 1c4a9dab8a4d..ec30dd09a37e 100644 --- a/packages/google-auth/google/auth/crypt/_python_rsa.py +++ b/packages/google-auth/google/auth/crypt/_python_rsa.py @@ -21,13 +21,12 @@ from __future__ import absolute_import -import io - from pyasn1.codec.der import decoder from pyasn1_modules import pem from pyasn1_modules.rfc2459 import Certificate from pyasn1_modules.rfc5208 import PrivateKeyInfo import rsa +import six from google.auth import _helpers from google.auth.crypt import base @@ -53,9 +52,9 @@ def _bit_list_to_bytes(bit_list): """ num_bits = len(bit_list) byte_vals = bytearray() - for start in range(0, num_bits, 8): + for start in six.moves.xrange(0, num_bits, 8): curr_bits = bit_list[start : start + 8] - char_val = sum(val * digit for val, digit in zip(_POW2, curr_bits)) + char_val = sum(val * digit for val, digit in six.moves.zip(_POW2, curr_bits)) byte_vals.append(char_val) return bytes(byte_vals) @@ -153,7 +152,7 @@ def from_string(cls, key, key_id=None): """ key = _helpers.from_bytes(key) # PEM expects str in Python 3 marker_id, key_bytes = pem.readPemBlocksFromFile( - io.StringIO(key), _PKCS1_MARKER, _PKCS8_MARKER + six.StringIO(key), _PKCS1_MARKER, _PKCS8_MARKER ) # Key is in pkcs1 format. diff --git a/packages/google-auth/google/auth/crypt/base.py b/packages/google-auth/google/auth/crypt/base.py index 0bda9c34526d..c98d5bf64fc4 100644 --- a/packages/google-auth/google/auth/crypt/base.py +++ b/packages/google-auth/google/auth/crypt/base.py @@ -18,12 +18,15 @@ import io import json +import six + _JSON_FILE_PRIVATE_KEY = "private_key" _JSON_FILE_PRIVATE_KEY_ID = "private_key_id" -class Verifier(object, metaclass=abc.ABCMeta): +@six.add_metaclass(abc.ABCMeta) +class Verifier(object): """Abstract base class for crytographic signature verifiers.""" @abc.abstractmethod @@ -43,7 +46,8 @@ def verify(self, message, signature): raise NotImplementedError("Verify must be implemented") -class Signer(object, metaclass=abc.ABCMeta): +@six.add_metaclass(abc.ABCMeta) +class Signer(object): """Abstract base class for cryptographic signers.""" @abc.abstractproperty @@ -66,7 +70,8 @@ def sign(self, message): raise NotImplementedError("Sign must be implemented") -class FromServiceAccountMixin(object, metaclass=abc.ABCMeta): +@six.add_metaclass(abc.ABCMeta) +class FromServiceAccountMixin(object): """Mix-in to enable factory constructors for a Signer.""" @abc.abstractmethod diff --git a/packages/google-auth/google/auth/crypt/es256.py b/packages/google-auth/google/auth/crypt/es256.py index 71dcbfcac097..c6d617606728 100644 --- a/packages/google-auth/google/auth/crypt/es256.py +++ b/packages/google-auth/google/auth/crypt/es256.py @@ -15,6 +15,7 @@ """ECDSA (ES256) verifier and signer that use the ``cryptography`` library. """ +from cryptography import utils import cryptography.exceptions from cryptography.hazmat import backends from cryptography.hazmat.primitives import hashes @@ -52,8 +53,8 @@ def verify(self, message, signature): sig_bytes = _helpers.to_bytes(signature) if len(sig_bytes) != 64: return False - r = int.from_bytes(sig_bytes[:32], byteorder="big") - s = int.from_bytes(sig_bytes[32:], byteorder="big") + r = utils.int_from_bytes(sig_bytes[:32], byteorder="big") + s = utils.int_from_bytes(sig_bytes[32:], byteorder="big") asn1_sig = encode_dss_signature(r, s) message = _helpers.to_bytes(message) @@ -120,7 +121,7 @@ def sign(self, message): # Convert ASN1 encoded signature to (r||s) raw signature. (r, s) = decode_dss_signature(asn1_signature) - return r.to_bytes(32, byteorder="big") + s.to_bytes(32, byteorder="big") + return utils.int_to_bytes(r, 32) + utils.int_to_bytes(s, 32) @classmethod def from_string(cls, key, key_id=None): diff --git a/packages/google-auth/google/auth/downscoped.py b/packages/google-auth/google/auth/downscoped.py index 96a4e6547322..a1d7b6e46e39 100644 --- a/packages/google-auth/google/auth/downscoped.py +++ b/packages/google-auth/google/auth/downscoped.py @@ -50,6 +50,8 @@ import datetime +import six + from google.auth import _helpers from google.auth import credentials from google.oauth2 import sts @@ -221,7 +223,7 @@ def available_resource(self, value): Raises: TypeError: If the value is not a string. """ - if not isinstance(value, str): + if not isinstance(value, six.string_types): raise TypeError("The provided available_resource is not a string.") self._available_resource = value @@ -247,7 +249,7 @@ def available_permissions(self, value): ValueError: If the value is not valid. """ for available_permission in value: - if not isinstance(available_permission, str): + if not isinstance(available_permission, six.string_types): raise TypeError( "Provided available_permissions are not a list of strings." ) @@ -350,7 +352,7 @@ def expression(self, value): Raises: TypeError: If the value is not of type string. """ - if not isinstance(value, str): + if not isinstance(value, six.string_types): raise TypeError("The provided expression is not a string.") self._expression = value @@ -373,7 +375,7 @@ def title(self, value): Raises: TypeError: If the value is not of type string or None. """ - if not isinstance(value, str) and value is not None: + if not isinstance(value, six.string_types) and value is not None: raise TypeError("The provided title is not a string or None.") self._title = value @@ -396,7 +398,7 @@ def description(self, value): Raises: TypeError: If the value is not of type string or None. """ - if not isinstance(value, str) and value is not None: + if not isinstance(value, six.string_types) and value is not None: raise TypeError("The provided description is not a string or None.") self._description = value diff --git a/packages/google-auth/google/auth/external_account.py b/packages/google-auth/google/auth/external_account.py index f588981a0791..cbd0baf4eabd 100644 --- a/packages/google-auth/google/auth/external_account.py +++ b/packages/google-auth/google/auth/external_account.py @@ -33,6 +33,8 @@ import json import re +import six + from google.auth import _helpers from google.auth import credentials from google.auth import exceptions @@ -50,9 +52,8 @@ _CLOUD_RESOURCE_MANAGER = "https://cloudresourcemanager.googleapis.com/v1/projects/" -class Credentials( - credentials.Scoped, credentials.CredentialsWithQuotaProject, metaclass=abc.ABCMeta -): +@six.add_metaclass(abc.ABCMeta) +class Credentials(credentials.Scoped, credentials.CredentialsWithQuotaProject): """Base class for all external account credentials. This is used to instantiate Credentials for exchanging external account diff --git a/packages/google-auth/google/auth/iam.py b/packages/google-auth/google/auth/iam.py index 277f4b7f36bf..5d63dc5d8a91 100644 --- a/packages/google-auth/google/auth/iam.py +++ b/packages/google-auth/google/auth/iam.py @@ -20,9 +20,10 @@ """ import base64 -import http.client import json +from six.moves import http_client + from google.auth import _helpers from google.auth import crypt from google.auth import exceptions @@ -76,7 +77,7 @@ def _make_signing_request(self, message): self._credentials.before_request(self._request, method, url, headers) response = self._request(url=url, method=method, body=body, headers=headers) - if response.status != http.client.OK: + if response.status != http_client.OK: raise exceptions.TransportError( "Error calling the IAM signBlob API: {}".format(response.data) ) diff --git a/packages/google-auth/google/auth/identity_pool.py b/packages/google-auth/google/auth/identity_pool.py index 901fd62fb999..fb33d7726f54 100644 --- a/packages/google-auth/google/auth/identity_pool.py +++ b/packages/google-auth/google/auth/identity_pool.py @@ -33,7 +33,11 @@ access tokens. """ -from collections.abc import Mapping +try: + from collections.abc import Mapping +# Python 2.7 compatibility +except ImportError: # pragma: NO COVER + from collections import Mapping import io import json import os diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index 2704bfdd242c..b8a6c49a1eba 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -28,9 +28,11 @@ import base64 import copy from datetime import datetime -import http.client import json +import six +from six.moves import http_client + from google.auth import _helpers from google.auth import credentials from google.auth import exceptions @@ -98,7 +100,7 @@ def _make_iam_token_request( else response.data ) - if response.status != http.client.OK: + if response.status != http_client.OK: exceptions.RefreshError(_REFRESH_ERROR, response_body) try: @@ -115,7 +117,7 @@ def _make_iam_token_request( ), response_body, ) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) class Credentials(credentials.CredentialsWithQuotaProject, credentials.Signing): diff --git a/packages/google-auth/google/auth/jwt.py b/packages/google-auth/google/auth/jwt.py index bb9ffae83dc6..d56559510791 100644 --- a/packages/google-auth/google/auth/jwt.py +++ b/packages/google-auth/google/auth/jwt.py @@ -40,13 +40,18 @@ """ -from collections.abc import Mapping +try: + from collections.abc import Mapping +# Python 2.7 compatibility +except ImportError: # pragma: NO COVER + from collections import Mapping import copy import datetime import json -import urllib import cachetools +import six +from six.moves import urllib from google.auth import _helpers from google.auth import _service_account_info @@ -118,7 +123,7 @@ def _decode_jwt_segment(encoded_section): return json.loads(section_bytes.decode("utf-8")) except ValueError as caught_exc: new_exc = ValueError("Can't parse segment: {0}".format(section_bytes)) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) def _unverified_decode(token): @@ -244,16 +249,19 @@ def decode(token, certs=None, verify=True, audience=None, clock_skew_in_seconds= try: verifier_cls = _ALGORITHM_TO_VERIFIER_CLASS[key_alg] - except KeyError as caught_exc: + except KeyError as exc: if key_alg in _CRYPTOGRAPHY_BASED_ALGORITHMS: - msg = ( - "The key algorithm {} requires the cryptography package " - "to be installed." + six.raise_from( + ValueError( + "The key algorithm {} requires the cryptography package " + "to be installed.".format(key_alg) + ), + exc, ) else: - msg = "Unsupported signature algorithm {}" - new_exc = ValueError(msg.format(key_alg)) - raise new_exc from caught_exc + six.raise_from( + ValueError("Unsupported signature algorithm {}".format(key_alg)), exc + ) # If certs is specified as a dictionary of key IDs to certificates, then # use the certificate identified by the key ID in the token header. diff --git a/packages/google-auth/google/auth/transport/__init__.py b/packages/google-auth/google/auth/transport/__init__.py index d1b035df97ee..374e7b4d7228 100644 --- a/packages/google-auth/google/auth/transport/__init__.py +++ b/packages/google-auth/google/auth/transport/__init__.py @@ -25,9 +25,11 @@ """ import abc -import http.client -DEFAULT_REFRESH_STATUS_CODES = (http.client.UNAUTHORIZED,) +import six +from six.moves import http_client + +DEFAULT_REFRESH_STATUS_CODES = (http_client.UNAUTHORIZED,) """Sequence[int]: Which HTTP status code indicate that credentials should be refreshed and a request should be retried. """ @@ -36,7 +38,8 @@ """int: How many times to refresh the credentials and retry a request.""" -class Response(object, metaclass=abc.ABCMeta): +@six.add_metaclass(abc.ABCMeta) +class Response(object): """HTTP Response data.""" @abc.abstractproperty @@ -55,7 +58,8 @@ def data(self): raise NotImplementedError("data must be implemented.") -class Request(object, metaclass=abc.ABCMeta): +@six.add_metaclass(abc.ABCMeta) +class Request(object): """Interface for a callable that makes HTTP requests. Specific transport implementations should provide an implementation of diff --git a/packages/google-auth/google/auth/transport/_aiohttp_requests.py b/packages/google-auth/google/auth/transport/_aiohttp_requests.py index ee94043304df..ab7dfef67740 100644 --- a/packages/google-auth/google/auth/transport/_aiohttp_requests.py +++ b/packages/google-auth/google/auth/transport/_aiohttp_requests.py @@ -24,6 +24,7 @@ import functools import aiohttp +import six import urllib3 from google.auth import exceptions @@ -190,11 +191,11 @@ async def __call__( except aiohttp.ClientError as caught_exc: new_exc = exceptions.TransportError(caught_exc) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) except asyncio.TimeoutError as caught_exc: new_exc = exceptions.TransportError(caught_exc) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) class AuthorizedSession(aiohttp.ClientSession): diff --git a/packages/google-auth/google/auth/transport/_http_client.py b/packages/google-auth/google/auth/transport/_http_client.py index 679087f068a7..c153763efa2c 100644 --- a/packages/google-auth/google/auth/transport/_http_client.py +++ b/packages/google-auth/google/auth/transport/_http_client.py @@ -14,10 +14,12 @@ """Transport adapter for http.client, for internal use only.""" -import http.client import logging import socket -import urllib + +import six +from six.moves import http_client +from six.moves import urllib from google.auth import exceptions from google.auth import transport @@ -96,7 +98,7 @@ def __call__( "was specified".format(parts.scheme) ) - connection = http.client.HTTPConnection(parts.netloc, timeout=timeout) + connection = http_client.HTTPConnection(parts.netloc, timeout=timeout) try: _LOGGER.debug("Making request: %s %s", method, url) @@ -105,9 +107,9 @@ def __call__( response = connection.getresponse() return Response(response) - except (http.client.HTTPException, socket.error) as caught_exc: + except (http_client.HTTPException, socket.error) as caught_exc: new_exc = exceptions.TransportError(caught_exc) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) finally: connection.close() diff --git a/packages/google-auth/google/auth/transport/_mtls_helper.py b/packages/google-auth/google/auth/transport/_mtls_helper.py index 1b9b9c285c6c..4dccb1062f88 100644 --- a/packages/google-auth/google/auth/transport/_mtls_helper.py +++ b/packages/google-auth/google/auth/transport/_mtls_helper.py @@ -20,6 +20,8 @@ import re import subprocess +import six + from google.auth import exceptions CONTEXT_AWARE_METADATA_PATH = "~/.secureConnect/context_aware_metadata.json" @@ -80,7 +82,7 @@ def _read_dca_metadata_file(metadata_path): metadata = json.load(f) except ValueError as caught_exc: new_exc = exceptions.ClientCertError(caught_exc) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) return metadata @@ -108,7 +110,7 @@ def _run_cert_provider_command(command, expect_encrypted_key=False): stdout, stderr = process.communicate() except OSError as caught_exc: new_exc = exceptions.ClientCertError(caught_exc) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) # Check cert provider command execution error. if process.returncode != 0: diff --git a/packages/google-auth/google/auth/transport/grpc.py b/packages/google-auth/google/auth/transport/grpc.py index 160dc946b145..c47cb3ddafcd 100644 --- a/packages/google-auth/google/auth/transport/grpc.py +++ b/packages/google-auth/google/auth/transport/grpc.py @@ -19,6 +19,8 @@ import logging import os +import six + from google.auth import environment_vars from google.auth import exceptions from google.auth.transport import _mtls_helper @@ -27,11 +29,13 @@ try: import grpc except ImportError as caught_exc: # pragma: NO COVER - new_exc = ImportError( - "gRPC is not installed, please install the grpcio package " - "to use the gRPC transport." + six.raise_from( + ImportError( + "gRPC is not installed, please install the grpcio package " + "to use the gRPC transport." + ), + caught_exc, ) - raise new_exc from caught_exc _LOGGER = logging.getLogger(__name__) @@ -84,7 +88,7 @@ def _get_authorization_headers(self, context): self._request, context.method_name, context.service_url, headers ) - return list(headers.items()) + return list(six.iteritems(headers)) def __call__(self, context, callback): """Passes authorization metadata into the given callback. @@ -333,7 +337,7 @@ def ssl_credentials(self): ) except exceptions.ClientCertError as caught_exc: new_exc = exceptions.MutualTLSChannelError(caught_exc) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) else: self._ssl_credentials = grpc.ssl_channel_credentials() diff --git a/packages/google-auth/google/auth/transport/mtls.py b/packages/google-auth/google/auth/transport/mtls.py index c5707617ff80..b40bfbedf97d 100644 --- a/packages/google-auth/google/auth/transport/mtls.py +++ b/packages/google-auth/google/auth/transport/mtls.py @@ -14,6 +14,8 @@ """Utilites for mutual TLS.""" +import six + from google.auth import exceptions from google.auth.transport import _mtls_helper @@ -51,7 +53,7 @@ def callback(): _, cert_bytes, key_bytes = _mtls_helper.get_client_cert_and_key() except (OSError, RuntimeError, ValueError) as caught_exc: new_exc = exceptions.MutualTLSChannelError(caught_exc) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) return cert_bytes, key_bytes @@ -96,7 +98,7 @@ def callback(): key_file.write(key_bytes) except (exceptions.ClientCertError, OSError) as caught_exc: new_exc = exceptions.MutualTLSChannelError(caught_exc) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) return cert_path, key_path, passphrase_bytes diff --git a/packages/google-auth/google/auth/transport/requests.py b/packages/google-auth/google/auth/transport/requests.py index 2cb6942476dd..817176befa20 100644 --- a/packages/google-auth/google/auth/transport/requests.py +++ b/packages/google-auth/google/auth/transport/requests.py @@ -25,16 +25,21 @@ try: import requests except ImportError as caught_exc: # pragma: NO COVER - new_exc = ImportError( - "The requests library is not installed, please install the " - "requests package to use the requests transport." + import six + + six.raise_from( + ImportError( + "The requests library is not installed, please install the " + "requests package to use the requests transport." + ), + caught_exc, ) - raise new_exc from caught_exc import requests.adapters # pylint: disable=ungrouped-imports import requests.exceptions # pylint: disable=ungrouped-imports from requests.packages.urllib3.util.ssl_ import ( create_urllib3_context, ) # pylint: disable=ungrouped-imports +import six # pylint: disable=ungrouped-imports from google.auth import environment_vars from google.auth import exceptions @@ -181,7 +186,7 @@ def __call__( return _Response(response) except requests.exceptions.RequestException as caught_exc: new_exc = exceptions.TransportError(caught_exc) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) class _MutualTlsAdapter(requests.adapters.HTTPAdapter): @@ -391,7 +396,7 @@ def configure_mtls_channel(self, client_cert_callback=None): import OpenSSL except ImportError as caught_exc: new_exc = exceptions.MutualTLSChannelError(caught_exc) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) try: ( @@ -411,7 +416,7 @@ def configure_mtls_channel(self, client_cert_callback=None): OpenSSL.crypto.Error, ) as caught_exc: new_exc = exceptions.MutualTLSChannelError(caught_exc) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) def request( self, diff --git a/packages/google-auth/google/auth/transport/urllib3.py b/packages/google-auth/google/auth/transport/urllib3.py index aa7188c559d7..6a2504d972ef 100644 --- a/packages/google-auth/google/auth/transport/urllib3.py +++ b/packages/google-auth/google/auth/transport/urllib3.py @@ -34,11 +34,16 @@ try: import urllib3 except ImportError as caught_exc: # pragma: NO COVER - new_exc = ImportError( - "The urllib3 library is not installed, please install the " - "urllib3 package to use the urllib3 transport." + import six + + six.raise_from( + ImportError( + "The urllib3 library is not installed, please install the " + "urllib3 package to use the urllib3 transport." + ), + caught_exc, ) - raise new_exc from caught_exc +import six import urllib3.exceptions # pylint: disable=ungrouped-imports from google.auth import environment_vars @@ -137,7 +142,7 @@ def __call__( return _Response(response) except urllib3.exceptions.HTTPError as caught_exc: new_exc = exceptions.TransportError(caught_exc) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) def _make_default_http(): @@ -329,7 +334,7 @@ def configure_mtls_channel(self, client_cert_callback=None): import OpenSSL except ImportError as caught_exc: new_exc = exceptions.MutualTLSChannelError(caught_exc) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) try: found_cert_key, cert, key = transport._mtls_helper.get_client_cert_and_key( @@ -346,7 +351,7 @@ def configure_mtls_channel(self, client_cert_callback=None): OpenSSL.crypto.Error, ) as caught_exc: new_exc = exceptions.MutualTLSChannelError(caught_exc) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) if self._has_user_provided_http: self._has_user_provided_http = False diff --git a/packages/google-auth/google/oauth2/_client.py b/packages/google-auth/google/oauth2/_client.py index f819371af8de..2f4e8474b557 100644 --- a/packages/google-auth/google/oauth2/_client.py +++ b/packages/google-auth/google/oauth2/_client.py @@ -24,9 +24,11 @@ """ import datetime -import http.client import json -import urllib + +import six +from six.moves import http_client +from six.moves import urllib from google.auth import _helpers from google.auth import exceptions @@ -118,7 +120,7 @@ def _token_endpoint_request_no_throw( ) response_data = json.loads(response_body) - if response.status == http.client.OK: + if response.status == http_client.OK: break else: error_desc = response_data.get("error_description") or "" @@ -129,9 +131,9 @@ def _token_endpoint_request_no_throw( ): retry += 1 continue - return response.status == http.client.OK, response_data + return response.status == http_client.OK, response_data - return response.status == http.client.OK, response_data + return response.status == http_client.OK, response_data def _token_endpoint_request( @@ -194,7 +196,7 @@ def jwt_grant(request, token_uri, assertion): access_token = response_data["access_token"] except KeyError as caught_exc: new_exc = exceptions.RefreshError("No access token in response.", response_data) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) expiry = _parse_expiry(response_data) @@ -234,7 +236,7 @@ def id_token_jwt_grant(request, token_uri, assertion): id_token = response_data["id_token"] except KeyError as caught_exc: new_exc = exceptions.RefreshError("No ID token in response.", response_data) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) payload = jwt.decode(id_token, verify=False) expiry = datetime.datetime.utcfromtimestamp(payload["exp"]) @@ -263,7 +265,7 @@ def _handle_refresh_grant_response(response_data, refresh_token): access_token = response_data["access_token"] except KeyError as caught_exc: new_exc = exceptions.RefreshError("No access token in response.", response_data) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) refresh_token = response_data.get("refresh_token", refresh_token) expiry = _parse_expiry(response_data) diff --git a/packages/google-auth/google/oauth2/_client_async.py b/packages/google-auth/google/oauth2/_client_async.py index 8849023e7923..cf51211379ee 100644 --- a/packages/google-auth/google/oauth2/_client_async.py +++ b/packages/google-auth/google/oauth2/_client_async.py @@ -24,9 +24,11 @@ """ import datetime -import http.client import json -import urllib + +import six +from six.moves import http_client +from six.moves import urllib from google.auth import exceptions from google.auth import jwt @@ -83,7 +85,7 @@ async def _token_endpoint_request_no_throw( response_data = json.loads(response_body) - if response.status == http.client.OK: + if response.status == http_client.OK: break else: error_desc = response_data.get("error_description") or "" @@ -94,9 +96,9 @@ async def _token_endpoint_request_no_throw( ): retry += 1 continue - return response.status == http.client.OK, response_data + return response.status == http_client.OK, response_data - return response.status == http.client.OK, response_data + return response.status == http_client.OK, response_data async def _token_endpoint_request( @@ -159,7 +161,7 @@ async def jwt_grant(request, token_uri, assertion): access_token = response_data["access_token"] except KeyError as caught_exc: new_exc = exceptions.RefreshError("No access token in response.", response_data) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) expiry = client._parse_expiry(response_data) @@ -199,7 +201,7 @@ async def id_token_jwt_grant(request, token_uri, assertion): id_token = response_data["id_token"] except KeyError as caught_exc: new_exc = exceptions.RefreshError("No ID token in response.", response_data) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) payload = jwt.decode(id_token, verify=False) expiry = datetime.datetime.utcfromtimestamp(payload["exp"]) diff --git a/packages/google-auth/google/oauth2/_id_token_async.py b/packages/google-auth/google/oauth2/_id_token_async.py index a4a526dc0e0a..31fcbc6230a0 100644 --- a/packages/google-auth/google/oauth2/_id_token_async.py +++ b/packages/google-auth/google/oauth2/_id_token_async.py @@ -58,10 +58,12 @@ .. _CacheControl: https://cachecontrol.readthedocs.io """ -import http.client import json import os +import six +from six.moves import http_client + from google.auth import environment_vars from google.auth import exceptions from google.auth import jwt @@ -86,7 +88,7 @@ async def _fetch_certs(request, certs_url): """ response = await request(certs_url, method="GET") - if response.status != http.client.OK: + if response.status != http_client.OK: raise exceptions.TransportError( "Could not fetch certificates at {}".format(certs_url) ) @@ -241,7 +243,7 @@ async def fetch_id_token(request, audience): "GOOGLE_APPLICATION_CREDENTIALS is not valid service account credentials.", caught_exc, ) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) # 2. Try to fetch ID token from metada server if it exists. The code works # for GAE and Cloud Run metadata server as well. diff --git a/packages/google-auth/google/oauth2/_reauth_async.py b/packages/google-auth/google/oauth2/_reauth_async.py index f74f50b43009..0276ddd0b217 100644 --- a/packages/google-auth/google/oauth2/_reauth_async.py +++ b/packages/google-auth/google/oauth2/_reauth_async.py @@ -34,6 +34,8 @@ import sys +from six.moves import range + from google.auth import exceptions from google.oauth2 import _client from google.oauth2 import _client_async diff --git a/packages/google-auth/google/oauth2/challenges.py b/packages/google-auth/google/oauth2/challenges.py index 0baff62e054e..95e76cb3262b 100644 --- a/packages/google-auth/google/oauth2/challenges.py +++ b/packages/google-auth/google/oauth2/challenges.py @@ -20,6 +20,8 @@ import getpass import sys +import six + from google.auth import _helpers from google.auth import exceptions @@ -45,7 +47,8 @@ def get_user_password(text): return getpass.getpass(text) -class ReauthChallenge(object, metaclass=abc.ABCMeta): +@six.add_metaclass(abc.ABCMeta) +class ReauthChallenge(object): """Base class for reauth challenges.""" @property diff --git a/packages/google-auth/google/oauth2/credentials.py b/packages/google-auth/google/oauth2/credentials.py index 6d34edf045f0..9b59f8cf532f 100644 --- a/packages/google-auth/google/oauth2/credentials.py +++ b/packages/google-auth/google/oauth2/credentials.py @@ -35,6 +35,8 @@ import io import json +import six + from google.auth import _cloud_sdk from google.auth import _helpers from google.auth import credentials @@ -262,7 +264,7 @@ def refresh(self, request): if self._refresh_token is None and self.refresh_handler: token, expiry = self.refresh_handler(request, scopes=scopes) # Validate returned data. - if not isinstance(token, str): + if not isinstance(token, six.string_types): raise exceptions.RefreshError( "The refresh_handler returned token is not a string." ) @@ -344,7 +346,7 @@ def from_authorized_user_info(cls, info, scopes=None): ValueError: If the info is not in the expected format. """ keys_needed = set(("refresh_token", "client_id", "client_secret")) - missing = keys_needed.difference(info) + missing = keys_needed.difference(six.iterkeys(info)) if missing: raise ValueError( @@ -364,7 +366,7 @@ def from_authorized_user_info(cls, info, scopes=None): # process scopes, which needs to be a seq if scopes is None and "scopes" in info: scopes = info.get("scopes") - if isinstance(scopes, str): + if isinstance(scopes, six.string_types): scopes = scopes.split(" ") return cls( diff --git a/packages/google-auth/google/oauth2/id_token.py b/packages/google-auth/google/oauth2/id_token.py index 25492ca6c198..8d0f85a593f6 100644 --- a/packages/google-auth/google/oauth2/id_token.py +++ b/packages/google-auth/google/oauth2/id_token.py @@ -55,10 +55,12 @@ .. _CacheControl: https://cachecontrol.readthedocs.io """ -import http.client import json import os +import six +from six.moves import http_client + from google.auth import environment_vars from google.auth import exceptions from google.auth import jwt @@ -95,7 +97,7 @@ def _fetch_certs(request, certs_url): """ response = request(certs_url, method="GET") - if response.status != http.client.OK: + if response.status != http_client.OK: raise exceptions.TransportError( "Could not fetch certificates at {}".format(certs_url) ) @@ -240,7 +242,7 @@ def fetch_id_token(request, audience): "GOOGLE_APPLICATION_CREDENTIALS is not valid service account credentials.", caught_exc, ) - raise new_exc from caught_exc + six.raise_from(new_exc, caught_exc) # 2. Try to fetch ID token from metada server if it exists. The code # works for GAE and Cloud Run metadata server as well. diff --git a/packages/google-auth/google/oauth2/reauth.py b/packages/google-auth/google/oauth2/reauth.py index 1e496d12ec7a..cbf1d7f09394 100644 --- a/packages/google-auth/google/oauth2/reauth.py +++ b/packages/google-auth/google/oauth2/reauth.py @@ -34,6 +34,8 @@ import sys +from six.moves import range + from google.auth import exceptions from google.oauth2 import _client from google.oauth2 import challenges diff --git a/packages/google-auth/google/oauth2/sts.py b/packages/google-auth/google/oauth2/sts.py index 9f2d68af3cab..ae3c0146b114 100644 --- a/packages/google-auth/google/oauth2/sts.py +++ b/packages/google-auth/google/oauth2/sts.py @@ -31,9 +31,10 @@ .. _rfc8693 section 2.2.1: https://tools.ietf.org/html/rfc8693#section-2.2.1 """ -import http.client import json -import urllib + +from six.moves import http_client +from six.moves import urllib from google.oauth2 import utils @@ -145,7 +146,7 @@ def exchange_token( ) # If non-200 response received, translate to OAuthError exception. - if response.status != http.client.OK: + if response.status != http_client.OK: utils.handle_error_response(response_body) response_data = json.loads(response_body) diff --git a/packages/google-auth/google/oauth2/utils.py b/packages/google-auth/google/oauth2/utils.py index c57833daf800..593f03236ec8 100644 --- a/packages/google-auth/google/oauth2/utils.py +++ b/packages/google-auth/google/oauth2/utils.py @@ -45,6 +45,8 @@ import enum import json +import six + from google.auth import exceptions @@ -75,7 +77,8 @@ def __init__(self, client_auth_type, client_id, client_secret=None): self.client_secret = client_secret -class OAuthClientAuthHandler(object, metaclass=abc.ABCMeta): +@six.add_metaclass(abc.ABCMeta) +class OAuthClientAuthHandler(object): """Abstract class for handling client authentication in OAuth-based operations. """ diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index e238c973ce25..885dbd61aa97 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -81,6 +81,23 @@ def unit(session): ) +@nox.session(python=["2.7"]) +def unit_prev_versions(session): + constraints_path = str( + CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt" + ) + session.install("-r", "testing/requirements.txt", "-c", constraints_path) + session.install("-e", ".", "-c", constraints_path) + session.run( + "pytest", + f"--junitxml=unit_{session.python}_sponge_log.xml", + "--cov=google.auth", + "--cov=google.oauth2", + "--cov=tests", + "tests", + ) + + @nox.session(python="3.7") def cover(session): session.install("-r", "testing/requirements.txt") diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 343e660f1502..301e99643172 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -20,16 +20,25 @@ DEPENDENCIES = ( - "cachetools >= 2.0.0, < 5.0", - "pyasn1-modules >= 0.2.1", - "rsa >= 3.1.4, < 5", - "setuptools >= 40.3.0", + "cachetools>=2.0.0,<5.0", + "pyasn1-modules>=0.2.1", + # rsa==4.5 is the last version to support 2.7 + # https://github.com/sybrenstuvel/python-rsa/issues/152#issuecomment-643470233 + 'rsa<4.6; python_version < "3.6"', + 'rsa>=3.1.4,<5; python_version >= "3.6"', + # install enum34 to support 2.7. enum34 only works up to python version 3.3. + 'enum34>=1.1.10; python_version < "3.4"', + "setuptools>=40.3.0", + "six>=1.9.0", ) extras = { - "aiohttp": ["aiohttp >= 3.6.2, < 4.0.0dev", "requests >= 2.20.0, < 3.0.0dev"], - "pyopenssl": "pyopenssl >= 20.0.0", - "reauth": "pyu2f >= 0.1.5", + "aiohttp": [ + "aiohttp >= 3.6.2, < 4.0.0dev; python_version>='3.6'", + "requests >= 2.20.0, < 3.0.0dev", + ], + "pyopenssl": "pyopenssl>=20.0.0", + "reauth": "pyu2f>=0.1.5", } with io.open("README.rst", "r") as fh: @@ -54,7 +63,7 @@ namespace_packages=("google",), install_requires=DEPENDENCIES, extras_require=extras, - python_requires=">= 3.6", + python_requires=">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*", license="Apache 2.0", keywords="google auth oauth client", classifiers=[ diff --git a/packages/google-auth/system_tests/noxfile.py b/packages/google-auth/system_tests/noxfile.py index 540727e485a5..459b71c78f65 100644 --- a/packages/google-auth/system_tests/noxfile.py +++ b/packages/google-auth/system_tests/noxfile.py @@ -171,7 +171,7 @@ def configure_cloud_sdk(session, application_default_credentials, project=False) TEST_DEPENDENCIES_ASYNC = ["aiohttp", "pytest-asyncio", "nest-asyncio"] TEST_DEPENDENCIES_SYNC = ["pytest", "requests", "mock"] PYTHON_VERSIONS_ASYNC = ["3.7"] -PYTHON_VERSIONS_SYNC = ["3.7"] +PYTHON_VERSIONS_SYNC = ["2.7", "3.7"] def default(session, *test_paths): @@ -287,6 +287,50 @@ def compute_engine(session): ) +@nox.session(python=["2.7"]) +def app_engine(session): + if SKIP_GAE_TEST_ENV in os.environ: + session.log("Skipping App Engine tests.") + return + + session.install(LIBRARY_DIR) + # Unlike the default tests above, the App Engine system test require a + # 'real' gcloud sdk installation that is configured to deploy to an + # app engine project. + # Grab the project ID from the cloud sdk. + project_id = ( + subprocess.check_output( + ["gcloud", "config", "list", "project", "--format", "value(core.project)"] + ) + .decode("utf-8") + .strip() + ) + + if not project_id: + session.error( + "The Cloud SDK must be installed and configured to deploy to App " "Engine." + ) + + application_url = GAE_APP_URL_TMPL.format(GAE_TEST_APP_SERVICE, project_id) + + # Vendor in the test application's dependencies + session.chdir(os.path.join(HERE, "system_tests_sync/app_engine_test_app")) + session.install(*TEST_DEPENDENCIES_SYNC) + session.run( + "pip", "install", "--target", "lib", "-r", "requirements.txt", silent=True + ) + + # Deploy the application. + session.run("gcloud", "app", "deploy", "-q", "app.yaml") + + # Run the tests + session.env["TEST_APP_URL"] = application_url + session.chdir(HERE) + default( + session, "system_tests_sync/test_app_engine.py", + ) + + @nox.session(python=PYTHON_VERSIONS_SYNC) def grpc(session): session.install(LIBRARY_DIR) @@ -339,9 +383,8 @@ def mtls_http(session): def external_accounts(session): session.install( *TEST_DEPENDENCIES_SYNC, - "google-auth", + LIBRARY_DIR, "google-api-python-client", - "enum34", ) default( session, @@ -354,7 +397,7 @@ def external_accounts(session): def downscoping(session): session.install( *TEST_DEPENDENCIES_SYNC, - "google-auth", + LIBRARY_DIR, "google-cloud-storage", ) default( diff --git a/packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/.gitignore b/packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/.gitignore new file mode 100644 index 000000000000..7951405f85a5 --- /dev/null +++ b/packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/.gitignore @@ -0,0 +1 @@ +lib \ No newline at end of file diff --git a/packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/app.yaml b/packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/app.yaml new file mode 100644 index 000000000000..06f2270303e8 --- /dev/null +++ b/packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/app.yaml @@ -0,0 +1,12 @@ +api_version: 1 +service: google-auth-system-tests +runtime: python27 +threadsafe: true + +handlers: +- url: .* + script: main.app + +libraries: +- name: ssl + version: 2.7.11 \ No newline at end of file diff --git a/packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/appengine_config.py b/packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/appengine_config.py new file mode 100644 index 000000000000..1197ab526826 --- /dev/null +++ b/packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/appengine_config.py @@ -0,0 +1,30 @@ +# Copyright 2016 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from google.appengine.ext import vendor + +# Add any libraries installed in the "lib" folder. +vendor.add("lib") + + +# Patch os.path.expanduser. This should be fixed in GAE +# versions released after Nov 2016. +import os.path + + +def patched_expanduser(path): + return path + + +os.path.expanduser = patched_expanduser \ No newline at end of file diff --git a/packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/main.py b/packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/main.py new file mode 100644 index 000000000000..f44ed4c7972e --- /dev/null +++ b/packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/main.py @@ -0,0 +1,129 @@ +# Copyright 2016 Google LLC All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""App Engine standard application that runs basic system tests for +google.auth.app_engine. +This application has to run tests manually instead of using pytest because +pytest currently doesn't work on App Engine standard. +""" + +import contextlib +import json +import sys +from StringIO import StringIO +import traceback + +from google.appengine.api import app_identity +import google.auth +from google.auth import _helpers +from google.auth import app_engine +import google.auth.transport.urllib3 +import urllib3.contrib.appengine +import webapp2 + +FAILED_TEST_TMPL = """ +Test {} failed: {} +Stacktrace: +{} +Captured output: +{} +""" +TOKEN_INFO_URL = "https://www.googleapis.com/oauth2/v3/tokeninfo" +EMAIL_SCOPE = "https://www.googleapis.com/auth/userinfo.email" +HTTP = urllib3.contrib.appengine.AppEngineManager() +HTTP_REQUEST = google.auth.transport.urllib3.Request(HTTP) + + +def test_credentials(): + credentials = app_engine.Credentials() + scoped_credentials = credentials.with_scopes([EMAIL_SCOPE]) + + scoped_credentials.refresh(None) + + assert scoped_credentials.valid + assert scoped_credentials.token is not None + + # Get token info and verify scope + url = _helpers.update_query( + TOKEN_INFO_URL, {"access_token": scoped_credentials.token} + ) + response = HTTP_REQUEST(url=url, method="GET") + token_info = json.loads(response.data.decode("utf-8")) + + assert token_info["scope"] == EMAIL_SCOPE + + +def test_default(): + credentials, project_id = google.auth.default() + + assert isinstance(credentials, app_engine.Credentials) + assert project_id == app_identity.get_application_id() + + +@contextlib.contextmanager +def capture(): + """Context manager that captures stderr and stdout.""" + oldout, olderr = sys.stdout, sys.stderr + try: + out = StringIO() + sys.stdout, sys.stderr = out, out + yield out + finally: + sys.stdout, sys.stderr = oldout, olderr + + +def run_test_func(func): + with capture() as capsys: + try: + func() + return True, "" + except Exception as exc: + output = FAILED_TEST_TMPL.format( + func.func_name, exc, traceback.format_exc(), capsys.getvalue() + ) + return False, output + + +def run_tests(): + """Runs all tests. + Returns: + Tuple[bool, str]: A tuple containing True if all tests pass, False + otherwise, and any captured output from the tests. + """ + status = True + output = "" + + tests = (test_credentials, test_default) + + for test in tests: + test_status, test_output = run_test_func(test) + status = status and test_status + output += test_output + + return status, output + + +class MainHandler(webapp2.RequestHandler): + def get(self): + self.response.headers["content-type"] = "text/plain" + + status, output = run_tests() + + if not status: + self.response.status = 500 + + self.response.write(output) + + +app = webapp2.WSGIApplication([("/", MainHandler)], debug=True) \ No newline at end of file diff --git a/packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/requirements.txt b/packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/requirements.txt new file mode 100644 index 000000000000..cb8a38216afe --- /dev/null +++ b/packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/requirements.txt @@ -0,0 +1,3 @@ +urllib3 +# Relative path to google-auth-python's source. +../../.. \ No newline at end of file diff --git a/packages/google-auth/system_tests/system_tests_sync/test_app_engine.py b/packages/google-auth/system_tests/system_tests_sync/test_app_engine.py new file mode 100644 index 000000000000..79776ce27eac --- /dev/null +++ b/packages/google-auth/system_tests/system_tests_sync/test_app_engine.py @@ -0,0 +1,22 @@ +# Copyright 2016 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os + +TEST_APP_URL = os.environ["TEST_APP_URL"] + + +def test_live_application(http_request): + response = http_request(method="GET", url=TEST_APP_URL) + assert response.status == 200, response.data.decode("utf-8") \ No newline at end of file diff --git a/packages/google-auth/system_tests/system_tests_sync/test_downscoping.py b/packages/google-auth/system_tests/system_tests_sync/test_downscoping.py index 77224aeae0d1..fdb4efaed02e 100644 --- a/packages/google-auth/system_tests/system_tests_sync/test_downscoping.py +++ b/packages/google-auth/system_tests/system_tests_sync/test_downscoping.py @@ -28,7 +28,7 @@ # The object prefix used to test access to files beginning with this prefix. _OBJECT_PREFIX = "customer-a" # The object name of the object inaccessible by the downscoped token. -_ACCESSIBLE_OBJECT_NAME = f"{_OBJECT_PREFIX}-data.txt" +_ACCESSIBLE_OBJECT_NAME = "{0}-data.txt".format(_OBJECT_PREFIX) # The content of the object accessible by the downscoped token. _ACCESSIBLE_CONTENT = "hello world" # The content of the object inaccessible by the downscoped token. @@ -76,13 +76,13 @@ def get_token_from_broker(bucket_name, object_prefix): Tuple[str, datetime.datetime]: The downscoped access token and its expiry date. """ # Initialize the Credential Access Boundary rules. - available_resource = f"//storage.googleapis.com/projects/_/buckets/{bucket_name}" + available_resource = "//storage.googleapis.com/projects/_/buckets/{0}".format(bucket_name) # Downscoped credentials will have readonly access to the resource. available_permissions = ["inRole:roles/storage.objectViewer"] # Only objects starting with the specified prefix string in the object name # will be allowed read access. availability_expression = ( - f"resource.name.startsWith('projects/_/buckets/{bucket_name}/objects/{object_prefix}')" + "resource.name.startsWith('projects/_/buckets/{0}/objects/{1}')".format(bucket_name, object_prefix) ) availability_condition = downscoped.AvailabilityCondition(availability_expression) # Define the single access boundary rule using the above properties. diff --git a/packages/google-auth/system_tests/system_tests_sync/test_external_accounts.py b/packages/google-auth/system_tests/system_tests_sync/test_external_accounts.py index c2855a2c3832..e24c7b40a54a 100644 --- a/packages/google-auth/system_tests/system_tests_sync/test_external_accounts.py +++ b/packages/google-auth/system_tests/system_tests_sync/test_external_accounts.py @@ -32,21 +32,19 @@ # original service account key. -from http.server import BaseHTTPRequestHandler -from http.server import HTTPServer import json import os import socket -import sys from tempfile import NamedTemporaryFile import threading -import pytest -from mock import patch - +import sys import google.auth from googleapiclient import discovery +from six.moves import BaseHTTPServer from google.oauth2 import service_account +import pytest +from mock import patch # Populate values from the output of scripts/setup_external_accounts.sh. _AUDIENCE_OIDC = "//iam.googleapis.com/projects/79992041559/locations/global/workloadIdentityPools/pool-73wslmxn/providers/oidc-73wslmxn" @@ -177,7 +175,7 @@ def test_file_based_external_account( # This test makes sure that setting up an http server to provide credentials # works to allow access to Google resources. def test_url_based_external_account(dns_access, oidc_credentials, service_account_info): - class TestResponseHandler(BaseHTTPRequestHandler): + class TestResponseHandler(BaseHTTPServer.BaseHTTPRequestHandler): def do_GET(self): if self.headers["my-header"] != "expected-value": self.send_response(400) @@ -201,7 +199,7 @@ def do_GET(self): json.dumps({"access_token": oidc_credentials.token}).encode("utf-8") ) - class TestHTTPServer(HTTPServer, object): + class TestHTTPServer(BaseHTTPServer.HTTPServer, object): def __init__(self): self.port = self._find_open_port() super(TestHTTPServer, self).__init__(("", self.port), TestResponseHandler) diff --git a/packages/google-auth/testing/constraints-2.7.txt b/packages/google-auth/testing/constraints-2.7.txt new file mode 100644 index 000000000000..dcc09f75e032 --- /dev/null +++ b/packages/google-auth/testing/constraints-2.7.txt @@ -0,0 +1 @@ +rsa==3.1.4 \ No newline at end of file diff --git a/packages/google-auth/tests/compute_engine/test__metadata.py b/packages/google-auth/tests/compute_engine/test__metadata.py index 0bb07b007af3..852822dc0c44 100644 --- a/packages/google-auth/tests/compute_engine/test__metadata.py +++ b/packages/google-auth/tests/compute_engine/test__metadata.py @@ -13,13 +13,13 @@ # limitations under the License. import datetime -import http.client -import importlib import json import os import mock import pytest +from six.moves import http_client +from six.moves import reload_module from google.auth import _helpers from google.auth import environment_vars @@ -30,7 +30,7 @@ PATH = "instance/service-accounts/default" -def make_request(data, status=http.client.OK, headers=None, retry=False): +def make_request(data, status=http_client.OK, headers=None, retry=False): response = mock.create_autospec(transport.Response, instance=True) response.status = status response.data = _helpers.to_bytes(data) @@ -90,13 +90,13 @@ def test_ping_success_custom_root(): fake_ip = "1.2.3.4" os.environ[environment_vars.GCE_METADATA_IP] = fake_ip - importlib.reload(_metadata) + reload_module(_metadata) try: assert _metadata.ping(request) finally: del os.environ[environment_vars.GCE_METADATA_IP] - importlib.reload(_metadata) + reload_module(_metadata) request.assert_called_once_with( method="GET", @@ -203,13 +203,13 @@ def test_get_success_custom_root_new_variable(): fake_root = "another.metadata.service" os.environ[environment_vars.GCE_METADATA_HOST] = fake_root - importlib.reload(_metadata) + reload_module(_metadata) try: _metadata.get(request, PATH) finally: del os.environ[environment_vars.GCE_METADATA_HOST] - importlib.reload(_metadata) + reload_module(_metadata) request.assert_called_once_with( method="GET", @@ -223,13 +223,13 @@ def test_get_success_custom_root_old_variable(): fake_root = "another.metadata.service" os.environ[environment_vars.GCE_METADATA_ROOT] = fake_root - importlib.reload(_metadata) + reload_module(_metadata) try: _metadata.get(request, PATH) finally: del os.environ[environment_vars.GCE_METADATA_ROOT] - importlib.reload(_metadata) + reload_module(_metadata) request.assert_called_once_with( method="GET", @@ -239,7 +239,7 @@ def test_get_success_custom_root_old_variable(): def test_get_failure(): - request = make_request("Metadata error", status=http.client.NOT_FOUND) + request = make_request("Metadata error", status=http_client.NOT_FOUND) with pytest.raises(exceptions.TransportError) as excinfo: _metadata.get(request, PATH) diff --git a/packages/google-auth/tests/crypt/test__cryptography_rsa.py b/packages/google-auth/tests/crypt/test__cryptography_rsa.py index 41dfc3693362..dbf07c7805b3 100644 --- a/packages/google-auth/tests/crypt/test__cryptography_rsa.py +++ b/packages/google-auth/tests/crypt/test__cryptography_rsa.py @@ -60,7 +60,7 @@ class TestRSAVerifier(object): - def test_verify_bytes_success(self): + def test_verify_success(self): to_sign = b"foo" signer = _cryptography_rsa.RSASigner.from_string(PRIVATE_KEY_BYTES) actual_signature = signer.sign(to_sign) @@ -68,8 +68,8 @@ def test_verify_bytes_success(self): verifier = _cryptography_rsa.RSAVerifier.from_string(PUBLIC_KEY_BYTES) assert verifier.verify(to_sign, actual_signature) - def test_verify_text_success(self): - to_sign = "foo" + def test_verify_unicode_success(self): + to_sign = u"foo" signer = _cryptography_rsa.RSASigner.from_string(PRIVATE_KEY_BYTES) actual_signature = signer.sign(to_sign) diff --git a/packages/google-auth/tests/crypt/test__python_rsa.py b/packages/google-auth/tests/crypt/test__python_rsa.py index 9ef29ee15c1e..886ee55a2388 100644 --- a/packages/google-auth/tests/crypt/test__python_rsa.py +++ b/packages/google-auth/tests/crypt/test__python_rsa.py @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -import io import json import os @@ -20,6 +19,7 @@ from pyasn1_modules import pem import pytest import rsa +import six from google.auth import _helpers from google.auth.crypt import _python_rsa @@ -63,7 +63,7 @@ class TestRSAVerifier(object): - def test_verify_bytes_success(self): + def test_verify_success(self): to_sign = b"foo" signer = _python_rsa.RSASigner.from_string(PRIVATE_KEY_BYTES) actual_signature = signer.sign(to_sign) @@ -71,8 +71,8 @@ def test_verify_bytes_success(self): verifier = _python_rsa.RSAVerifier.from_string(PUBLIC_KEY_BYTES) assert verifier.verify(to_sign, actual_signature) - def test_verify_text_success(self): - to_sign = "foo" + def test_verify_unicode_success(self): + to_sign = u"foo" signer = _python_rsa.RSASigner.from_string(PRIVATE_KEY_BYTES) actual_signature = signer.sign(to_sign) @@ -141,7 +141,7 @@ def test_from_string_pkcs8(self): def test_from_string_pkcs8_extra_bytes(self): key_bytes = PKCS8_KEY_BYTES _, pem_bytes = pem.readPemBlocksFromFile( - io.StringIO(_helpers.from_bytes(key_bytes)), _python_rsa._PKCS8_MARKER + six.StringIO(_helpers.from_bytes(key_bytes)), _python_rsa._PKCS8_MARKER ) key_info, remaining = None, "extra" diff --git a/packages/google-auth/tests/crypt/test_es256.py b/packages/google-auth/tests/crypt/test_es256.py index 720f74ca2210..5bb9050cd8d7 100644 --- a/packages/google-auth/tests/crypt/test_es256.py +++ b/packages/google-auth/tests/crypt/test_es256.py @@ -50,7 +50,7 @@ class TestES256Verifier(object): - def test_verify_bytes_success(self): + def test_verify_success(self): to_sign = b"foo" signer = es256.ES256Signer.from_string(PRIVATE_KEY_BYTES) actual_signature = signer.sign(to_sign) @@ -58,8 +58,8 @@ def test_verify_bytes_success(self): verifier = es256.ES256Verifier.from_string(PUBLIC_KEY_BYTES) assert verifier.verify(to_sign, actual_signature) - def test_verify_text_success(self): - to_sign = "foo" + def test_verify_unicode_success(self): + to_sign = u"foo" signer = es256.ES256Signer.from_string(PRIVATE_KEY_BYTES) actual_signature = signer.sign(to_sign) diff --git a/packages/google-auth/tests/oauth2/test__client.py b/packages/google-auth/tests/oauth2/test__client.py index 690a87bc47e1..54686df594bd 100644 --- a/packages/google-auth/tests/oauth2/test__client.py +++ b/packages/google-auth/tests/oauth2/test__client.py @@ -13,13 +13,14 @@ # limitations under the License. import datetime -import http.client import json import os -import urllib import mock import pytest +import six +from six.moves import http_client +from six.moves import urllib from google.auth import _helpers from google.auth import crypt @@ -74,7 +75,7 @@ def test__parse_expiry_none(): assert _client._parse_expiry({}) is None -def make_request(response_data, status=http.client.OK): +def make_request(response_data, status=http_client.OK): response = mock.create_autospec(transport.Response, instance=True) response.status = status response.data = json.dumps(response_data).encode("utf-8") @@ -129,7 +130,7 @@ def test__token_endpoint_request_use_json(): def test__token_endpoint_request_error(): - request = make_request({}, status=http.client.BAD_REQUEST) + request = make_request({}, status=http_client.BAD_REQUEST) with pytest.raises(exceptions.RefreshError): _client._token_endpoint_request(request, "http://example.com", {}) @@ -137,7 +138,7 @@ def test__token_endpoint_request_error(): def test__token_endpoint_request_internal_failure_error(): request = make_request( - {"error_description": "internal_failure"}, status=http.client.BAD_REQUEST + {"error_description": "internal_failure"}, status=http_client.BAD_REQUEST ) with pytest.raises(exceptions.RefreshError): @@ -146,7 +147,7 @@ def test__token_endpoint_request_internal_failure_error(): ) request = make_request( - {"error": "internal_failure"}, status=http.client.BAD_REQUEST + {"error": "internal_failure"}, status=http_client.BAD_REQUEST ) with pytest.raises(exceptions.RefreshError): @@ -159,7 +160,7 @@ def verify_request_params(request, params): request_body = request.call_args[1]["body"].decode("utf-8") request_params = urllib.parse.parse_qs(request_body) - for key, value in params.items(): + for key, value in six.iteritems(params): assert request_params[key][0] == value diff --git a/packages/google-auth/tests/oauth2/test_sts.py b/packages/google-auth/tests/oauth2/test_sts.py index b516c8a5b547..e8e008df5da8 100644 --- a/packages/google-auth/tests/oauth2/test_sts.py +++ b/packages/google-auth/tests/oauth2/test_sts.py @@ -12,12 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -import http.client import json -import urllib import mock import pytest +from six.moves import http_client +from six.moves import urllib from google.auth import exceptions from google.auth import transport @@ -67,7 +67,7 @@ def make_client(cls, client_auth=None): return sts.Client(cls.TOKEN_EXCHANGE_ENDPOINT, client_auth) @classmethod - def make_mock_request(cls, data, status=http.client.OK): + def make_mock_request(cls, data, status=http_client.OK): response = mock.create_autospec(transport.Response, instance=True) response.status = status response.data = json.dumps(data).encode("utf-8") @@ -110,7 +110,7 @@ def test_exchange_token_full_success_without_auth(self): "options": urllib.parse.quote(json.dumps(self.ADDON_OPTIONS)), } request = self.make_mock_request( - status=http.client.OK, data=self.SUCCESS_RESPONSE + status=http_client.OK, data=self.SUCCESS_RESPONSE ) response = client.exchange_token( @@ -145,7 +145,7 @@ def test_exchange_token_partial_success_without_auth(self): "subject_token_type": self.SUBJECT_TOKEN_TYPE, } request = self.make_mock_request( - status=http.client.OK, data=self.SUCCESS_RESPONSE + status=http_client.OK, data=self.SUCCESS_RESPONSE ) response = client.exchange_token( @@ -165,7 +165,7 @@ def test_exchange_token_non200_without_auth(self): """ client = self.make_client() request = self.make_mock_request( - status=http.client.BAD_REQUEST, data=self.ERROR_RESPONSE + status=http_client.BAD_REQUEST, data=self.ERROR_RESPONSE ) with pytest.raises(exceptions.OAuthError) as excinfo: @@ -209,7 +209,7 @@ def test_exchange_token_full_success_with_basic_auth(self): "options": urllib.parse.quote(json.dumps(self.ADDON_OPTIONS)), } request = self.make_mock_request( - status=http.client.OK, data=self.SUCCESS_RESPONSE + status=http_client.OK, data=self.SUCCESS_RESPONSE ) response = client.exchange_token( @@ -247,7 +247,7 @@ def test_exchange_token_partial_success_with_basic_auth(self): "subject_token_type": self.SUBJECT_TOKEN_TYPE, } request = self.make_mock_request( - status=http.client.OK, data=self.SUCCESS_RESPONSE + status=http_client.OK, data=self.SUCCESS_RESPONSE ) response = client.exchange_token( @@ -268,7 +268,7 @@ def test_exchange_token_non200_with_basic_auth(self): """ client = self.make_client(self.CLIENT_AUTH_BASIC) request = self.make_mock_request( - status=http.client.BAD_REQUEST, data=self.ERROR_RESPONSE + status=http_client.BAD_REQUEST, data=self.ERROR_RESPONSE ) with pytest.raises(exceptions.OAuthError) as excinfo: @@ -313,7 +313,7 @@ def test_exchange_token_full_success_with_reqbody_auth(self): "client_secret": CLIENT_SECRET, } request = self.make_mock_request( - status=http.client.OK, data=self.SUCCESS_RESPONSE + status=http_client.OK, data=self.SUCCESS_RESPONSE ) response = client.exchange_token( @@ -350,7 +350,7 @@ def test_exchange_token_partial_success_with_reqbody_auth(self): "client_secret": CLIENT_SECRET, } request = self.make_mock_request( - status=http.client.OK, data=self.SUCCESS_RESPONSE + status=http_client.OK, data=self.SUCCESS_RESPONSE ) response = client.exchange_token( @@ -371,7 +371,7 @@ def test_exchange_token_non200_with_reqbody_auth(self): """ client = self.make_client(self.CLIENT_AUTH_REQUEST_BODY) request = self.make_mock_request( - status=http.client.BAD_REQUEST, data=self.ERROR_RESPONSE + status=http_client.BAD_REQUEST, data=self.ERROR_RESPONSE ) with pytest.raises(exceptions.OAuthError) as excinfo: diff --git a/packages/google-auth/tests/test__helpers.py b/packages/google-auth/tests/test__helpers.py index 906cf126ef95..0c0bad2d2fdc 100644 --- a/packages/google-auth/tests/test__helpers.py +++ b/packages/google-auth/tests/test__helpers.py @@ -13,9 +13,9 @@ # limitations under the License. import datetime -import urllib import pytest +from six.moves import urllib from google.auth import _helpers @@ -65,8 +65,8 @@ def test_to_bytes_with_bytes(): assert _helpers.to_bytes(value) == value -def test_to_bytes_with_text(): - value = "string-val" +def test_to_bytes_with_unicode(): + value = u"string-val" encoded_value = b"string-val" assert _helpers.to_bytes(value) == encoded_value @@ -76,14 +76,14 @@ def test_to_bytes_with_nonstring_type(): _helpers.to_bytes(object()) -def test_from_bytes_with_text(): - value = "bytes-val" +def test_from_bytes_with_unicode(): + value = u"bytes-val" assert _helpers.from_bytes(value) == value def test_from_bytes_with_bytes(): value = b"string-val" - decoded_value = "string-val" + decoded_value = u"string-val" assert _helpers.from_bytes(value) == decoded_value diff --git a/packages/google-auth/tests/test__oauth2client.py b/packages/google-auth/tests/test__oauth2client.py index aa06eced2f48..6b1112b50e75 100644 --- a/packages/google-auth/tests/test__oauth2client.py +++ b/packages/google-auth/tests/test__oauth2client.py @@ -13,7 +13,6 @@ # limitations under the License. import datetime -import importlib import os import sys @@ -22,6 +21,7 @@ import oauth2client.contrib.gce import oauth2client.service_account import pytest +from six.moves import reload_module from google.auth import _oauth2client @@ -152,19 +152,19 @@ def test_convert_not_found(): @pytest.fixture def reset__oauth2client_module(): """Reloads the _oauth2client module after a test.""" - importlib.reload(_oauth2client) + reload_module(_oauth2client) def test_import_has_app_engine( mock_oauth2client_gae_imports, reset__oauth2client_module ): - importlib.reload(_oauth2client) + reload_module(_oauth2client) assert _oauth2client._HAS_APPENGINE def test_import_without_oauth2client(monkeypatch, reset__oauth2client_module): monkeypatch.setitem(sys.modules, "oauth2client", None) with pytest.raises(ImportError) as excinfo: - importlib.reload(_oauth2client) + reload_module(_oauth2client) assert excinfo.match("oauth2client") diff --git a/packages/google-auth/tests/test__service_account_info.py b/packages/google-auth/tests/test__service_account_info.py index fd2d8c8be4ef..13b2f85a2944 100644 --- a/packages/google-auth/tests/test__service_account_info.py +++ b/packages/google-auth/tests/test__service_account_info.py @@ -16,6 +16,7 @@ import os import pytest +import six from google.auth import _service_account_info from google.auth import crypt @@ -54,7 +55,7 @@ def test_from_dict_bad_format(): def test_from_filename(): info, signer = _service_account_info.from_filename(SERVICE_ACCOUNT_JSON_FILE) - for key, value in SERVICE_ACCOUNT_INFO.items(): + for key, value in six.iteritems(SERVICE_ACCOUNT_INFO): assert info[key] == value assert isinstance(signer, crypt.RSASigner) diff --git a/packages/google-auth/tests/test_aws.py b/packages/google-auth/tests/test_aws.py index 86594376ef06..9ca08d5b2cdc 100644 --- a/packages/google-auth/tests/test_aws.py +++ b/packages/google-auth/tests/test_aws.py @@ -13,12 +13,12 @@ # limitations under the License. import datetime -import http.client import json -import urllib import mock import pytest +from six.moves import http_client +from six.moves import urllib from google.auth import _helpers from google.auth import aws @@ -952,11 +952,11 @@ def test_retrieve_subject_token_success_temp_creds_no_environment_vars( self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ" ) request = self.make_mock_request( - region_status=http.client.OK, + region_status=http_client.OK, region_name=self.AWS_REGION, - role_status=http.client.OK, + role_status=http_client.OK, role_name=self.AWS_ROLE, - security_credentials_status=http.client.OK, + security_credentials_status=http_client.OK, security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE, ) credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE) @@ -987,9 +987,9 @@ def test_retrieve_subject_token_success_temp_creds_no_environment_vars( # Retrieve subject_token again. Region should not be queried again. new_request = self.make_mock_request( - role_status=http.client.OK, + role_status=http_client.OK, role_name=self.AWS_ROLE, - security_credentials_status=http.client.OK, + security_credentials_status=http_client.OK, security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE, ) @@ -1020,11 +1020,11 @@ def test_retrieve_subject_token_success_permanent_creds_no_environment_vars( self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ" ) request = self.make_mock_request( - region_status=http.client.OK, + region_status=http_client.OK, region_name=self.AWS_REGION, - role_status=http.client.OK, + role_status=http_client.OK, role_name=self.AWS_ROLE, - security_credentials_status=http.client.OK, + security_credentials_status=http_client.OK, security_credentials_data=security_creds_response, ) credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE) @@ -1136,7 +1136,7 @@ def test_retrieve_subject_token_success_environment_vars_except_region( ) # Region will be queried since it is not found in envvars. request = self.make_mock_request( - region_status=http.client.OK, region_name=self.AWS_REGION + region_status=http_client.OK, region_name=self.AWS_REGION ) credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE) @@ -1152,7 +1152,7 @@ def test_retrieve_subject_token_success_environment_vars_except_region( def test_retrieve_subject_token_error_determining_aws_region(self): # Simulate error in retrieving the AWS region. - request = self.make_mock_request(region_status=http.client.BAD_REQUEST) + request = self.make_mock_request(region_status=http_client.BAD_REQUEST) credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE) with pytest.raises(exceptions.RefreshError) as excinfo: @@ -1163,9 +1163,9 @@ def test_retrieve_subject_token_error_determining_aws_region(self): def test_retrieve_subject_token_error_determining_aws_role(self): # Simulate error in retrieving the AWS role name. request = self.make_mock_request( - region_status=http.client.OK, + region_status=http_client.OK, region_name=self.AWS_REGION, - role_status=http.client.BAD_REQUEST, + role_status=http_client.BAD_REQUEST, ) credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE) @@ -1180,7 +1180,7 @@ def test_retrieve_subject_token_error_determining_security_creds_url(self): credential_source = self.CREDENTIAL_SOURCE.copy() credential_source.pop("url") request = self.make_mock_request( - region_status=http.client.OK, region_name=self.AWS_REGION + region_status=http_client.OK, region_name=self.AWS_REGION ) credentials = self.make_credentials(credential_source=credential_source) @@ -1194,11 +1194,11 @@ def test_retrieve_subject_token_error_determining_security_creds_url(self): def test_retrieve_subject_token_error_determining_aws_security_creds(self): # Simulate error in retrieving the AWS security credentials. request = self.make_mock_request( - region_status=http.client.OK, + region_status=http_client.OK, region_name=self.AWS_REGION, - role_status=http.client.OK, + role_status=http_client.OK, role_name=self.AWS_ROLE, - security_credentials_status=http.client.BAD_REQUEST, + security_credentials_status=http_client.BAD_REQUEST, ) credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE) @@ -1232,13 +1232,13 @@ def test_refresh_success_without_impersonation_ignore_default_scopes(self, utcno "subject_token_type": SUBJECT_TOKEN_TYPE, } request = self.make_mock_request( - region_status=http.client.OK, + region_status=http_client.OK, region_name=self.AWS_REGION, - role_status=http.client.OK, + role_status=http_client.OK, role_name=self.AWS_ROLE, - security_credentials_status=http.client.OK, + security_credentials_status=http_client.OK, security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE, - token_status=http.client.OK, + token_status=http_client.OK, token_data=self.SUCCESS_RESPONSE, ) credentials = self.make_credentials( @@ -1288,13 +1288,13 @@ def test_refresh_success_without_impersonation_use_default_scopes(self, utcnow): "subject_token_type": SUBJECT_TOKEN_TYPE, } request = self.make_mock_request( - region_status=http.client.OK, + region_status=http_client.OK, region_name=self.AWS_REGION, - role_status=http.client.OK, + role_status=http_client.OK, role_name=self.AWS_ROLE, - security_credentials_status=http.client.OK, + security_credentials_status=http_client.OK, security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE, - token_status=http.client.OK, + token_status=http_client.OK, token_data=self.SUCCESS_RESPONSE, ) credentials = self.make_credentials( @@ -1362,15 +1362,15 @@ def test_refresh_success_with_impersonation_ignore_default_scopes(self, utcnow): "lifetime": "3600s", } request = self.make_mock_request( - region_status=http.client.OK, + region_status=http_client.OK, region_name=self.AWS_REGION, - role_status=http.client.OK, + role_status=http_client.OK, role_name=self.AWS_ROLE, - security_credentials_status=http.client.OK, + security_credentials_status=http_client.OK, security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE, - token_status=http.client.OK, + token_status=http_client.OK, token_data=self.SUCCESS_RESPONSE, - impersonation_status=http.client.OK, + impersonation_status=http_client.OK, impersonation_data=impersonation_response, ) credentials = self.make_credentials( @@ -1446,15 +1446,15 @@ def test_refresh_success_with_impersonation_use_default_scopes(self, utcnow): "lifetime": "3600s", } request = self.make_mock_request( - region_status=http.client.OK, + region_status=http_client.OK, region_name=self.AWS_REGION, - role_status=http.client.OK, + role_status=http_client.OK, role_name=self.AWS_ROLE, - security_credentials_status=http.client.OK, + security_credentials_status=http_client.OK, security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE, - token_status=http.client.OK, + token_status=http_client.OK, token_data=self.SUCCESS_RESPONSE, - impersonation_status=http.client.OK, + impersonation_status=http_client.OK, impersonation_data=impersonation_response, ) credentials = self.make_credentials( @@ -1488,7 +1488,7 @@ def test_refresh_success_with_impersonation_use_default_scopes(self, utcnow): assert credentials.default_scopes == SCOPES def test_refresh_with_retrieve_subject_token_error(self): - request = self.make_mock_request(region_status=http.client.BAD_REQUEST) + request = self.make_mock_request(region_status=http_client.BAD_REQUEST) credentials = self.make_credentials(credential_source=self.CREDENTIAL_SOURCE) with pytest.raises(exceptions.RefreshError) as excinfo: diff --git a/packages/google-auth/tests/test_downscoped.py b/packages/google-auth/tests/test_downscoped.py index a686391c012d..9ca95f5aa856 100644 --- a/packages/google-auth/tests/test_downscoped.py +++ b/packages/google-auth/tests/test_downscoped.py @@ -13,12 +13,12 @@ # limitations under the License. import datetime -import http.client import json -import urllib import mock import pytest +from six.moves import http_client +from six.moves import urllib from google.auth import _helpers from google.auth import credentials @@ -461,7 +461,7 @@ def make_credentials(source_credentials=SourceCredentials(), quota_project_id=No ) @staticmethod - def make_mock_request(data, status=http.client.OK): + def make_mock_request(data, status=http_client.OK): response = mock.create_autospec(transport.Response, instance=True) response.status = status response.data = json.dumps(data).encode("utf-8") @@ -521,7 +521,7 @@ def test_refresh(self, unused_utcnow): "requested_token_type": REQUESTED_TOKEN_TYPE, "options": urllib.parse.quote(json.dumps(CREDENTIAL_ACCESS_BOUNDARY_JSON)), } - request = self.make_mock_request(status=http.client.OK, data=response) + request = self.make_mock_request(status=http_client.OK, data=response) source_credentials = SourceCredentials() credentials = self.make_credentials(source_credentials=source_credentials) @@ -563,7 +563,7 @@ def test_refresh_without_response_expires_in(self, unused_utcnow): "requested_token_type": REQUESTED_TOKEN_TYPE, "options": urllib.parse.quote(json.dumps(CREDENTIAL_ACCESS_BOUNDARY_JSON)), } - request = self.make_mock_request(status=http.client.OK, data=response) + request = self.make_mock_request(status=http_client.OK, data=response) credentials = self.make_credentials(source_credentials=source_credentials) # Spy on calls to source credentials refresh to confirm the expected request @@ -583,7 +583,7 @@ def test_refresh_without_response_expires_in(self, unused_utcnow): def test_refresh_token_exchange_error(self): request = self.make_mock_request( - status=http.client.BAD_REQUEST, data=ERROR_RESPONSE + status=http_client.BAD_REQUEST, data=ERROR_RESPONSE ) credentials = self.make_credentials() @@ -612,7 +612,7 @@ def test_refresh_source_credentials_refresh_error(self): def test_apply_without_quota_project_id(self): headers = {} - request = self.make_mock_request(status=http.client.OK, data=SUCCESS_RESPONSE) + request = self.make_mock_request(status=http_client.OK, data=SUCCESS_RESPONSE) credentials = self.make_credentials() credentials.refresh(request) @@ -624,7 +624,7 @@ def test_apply_without_quota_project_id(self): def test_apply_with_quota_project_id(self): headers = {"other": "header-value"} - request = self.make_mock_request(status=http.client.OK, data=SUCCESS_RESPONSE) + request = self.make_mock_request(status=http_client.OK, data=SUCCESS_RESPONSE) credentials = self.make_credentials(quota_project_id=QUOTA_PROJECT_ID) credentials.refresh(request) @@ -638,7 +638,7 @@ def test_apply_with_quota_project_id(self): def test_before_request(self): headers = {"other": "header-value"} - request = self.make_mock_request(status=http.client.OK, data=SUCCESS_RESPONSE) + request = self.make_mock_request(status=http_client.OK, data=SUCCESS_RESPONSE) credentials = self.make_credentials() # First call should call refresh, setting the token. @@ -662,7 +662,7 @@ def test_before_request(self): @mock.patch("google.auth._helpers.utcnow") def test_before_request_expired(self, utcnow): headers = {} - request = self.make_mock_request(status=http.client.OK, data=SUCCESS_RESPONSE) + request = self.make_mock_request(status=http_client.OK, data=SUCCESS_RESPONSE) credentials = self.make_credentials() credentials.token = "token" utcnow.return_value = datetime.datetime.min diff --git a/packages/google-auth/tests/test_external_account.py b/packages/google-auth/tests/test_external_account.py index 97f1564ef34f..3c34f998c9c8 100644 --- a/packages/google-auth/tests/test_external_account.py +++ b/packages/google-auth/tests/test_external_account.py @@ -13,12 +13,12 @@ # limitations under the License. import datetime -import http.client import json -import urllib import mock import pytest +from six.moves import http_client +from six.moves import urllib from google.auth import _helpers from google.auth import exceptions @@ -208,7 +208,7 @@ def make_workforce_pool_credentials( @classmethod def make_mock_request( cls, - status=http.client.OK, + status=http_client.OK, data=None, impersonation_status=None, impersonation_data=None, @@ -605,7 +605,7 @@ def test_refresh_without_client_auth_success(self, unused_utcnow): "subject_token": "subject_token_0", "subject_token_type": self.SUBJECT_TOKEN_TYPE, } - request = self.make_mock_request(status=http.client.OK, data=response) + request = self.make_mock_request(status=http_client.OK, data=response) credentials = self.make_credentials() credentials.refresh(request) @@ -635,7 +635,7 @@ def test_refresh_workforce_without_client_auth_success(self, unused_utcnow): json.dumps({"userProject": self.WORKFORCE_POOL_USER_PROJECT}) ), } - request = self.make_mock_request(status=http.client.OK, data=response) + request = self.make_mock_request(status=http_client.OK, data=response) credentials = self.make_workforce_pool_credentials( workforce_pool_user_project=self.WORKFORCE_POOL_USER_PROJECT ) @@ -667,7 +667,7 @@ def test_refresh_workforce_with_client_auth_success(self, unused_utcnow): "subject_token": "subject_token_0", "subject_token_type": self.WORKFORCE_SUBJECT_TOKEN_TYPE, } - request = self.make_mock_request(status=http.client.OK, data=response) + request = self.make_mock_request(status=http_client.OK, data=response) # Client Auth will have higher priority over workforce_pool_user_project. credentials = self.make_workforce_pool_credentials( client_id=CLIENT_ID, @@ -704,7 +704,7 @@ def test_refresh_workforce_with_client_auth_and_no_workforce_project_success( "subject_token": "subject_token_0", "subject_token_type": self.WORKFORCE_SUBJECT_TOKEN_TYPE, } - request = self.make_mock_request(status=http.client.OK, data=response) + request = self.make_mock_request(status=http_client.OK, data=response) # Client Auth will be sufficient for user project determination. credentials = self.make_workforce_pool_credentials( client_id=CLIENT_ID, @@ -754,9 +754,9 @@ def test_refresh_impersonation_without_client_auth_success(self): # Initialize mock request to handle token exchange and service account # impersonation request. request = self.make_mock_request( - status=http.client.OK, + status=http_client.OK, data=token_response, - impersonation_status=http.client.OK, + impersonation_status=http_client.OK, impersonation_data=impersonation_response, ) # Initialize credentials with service account impersonation. @@ -821,9 +821,9 @@ def test_refresh_workforce_impersonation_without_client_auth_success(self): # Initialize mock request to handle token exchange and service account # impersonation request. request = self.make_mock_request( - status=http.client.OK, + status=http_client.OK, data=token_response, - impersonation_status=http.client.OK, + impersonation_status=http_client.OK, impersonation_data=impersonation_response, ) # Initialize credentials with service account impersonation. @@ -865,7 +865,7 @@ def test_refresh_without_client_auth_success_explicit_user_scopes_ignore_default "subject_token_type": self.SUBJECT_TOKEN_TYPE, } request = self.make_mock_request( - status=http.client.OK, data=self.SUCCESS_RESPONSE + status=http_client.OK, data=self.SUCCESS_RESPONSE ) credentials = self.make_credentials( scopes=["scope1", "scope2"], @@ -893,7 +893,7 @@ def test_refresh_without_client_auth_success_explicit_default_scopes_only(self): "subject_token_type": self.SUBJECT_TOKEN_TYPE, } request = self.make_mock_request( - status=http.client.OK, data=self.SUCCESS_RESPONSE + status=http_client.OK, data=self.SUCCESS_RESPONSE ) credentials = self.make_credentials( scopes=None, @@ -911,7 +911,7 @@ def test_refresh_without_client_auth_success_explicit_default_scopes_only(self): def test_refresh_without_client_auth_error(self): request = self.make_mock_request( - status=http.client.BAD_REQUEST, data=self.ERROR_RESPONSE + status=http_client.BAD_REQUEST, data=self.ERROR_RESPONSE ) credentials = self.make_credentials() @@ -926,9 +926,9 @@ def test_refresh_without_client_auth_error(self): def test_refresh_impersonation_without_client_auth_error(self): request = self.make_mock_request( - status=http.client.OK, + status=http_client.OK, data=self.SUCCESS_RESPONSE, - impersonation_status=http.client.BAD_REQUEST, + impersonation_status=http_client.BAD_REQUEST, impersonation_data=self.IMPERSONATION_ERROR_RESPONSE, ) credentials = self.make_credentials( @@ -956,7 +956,7 @@ def test_refresh_with_client_auth_success(self): "subject_token_type": self.SUBJECT_TOKEN_TYPE, } request = self.make_mock_request( - status=http.client.OK, data=self.SUCCESS_RESPONSE + status=http_client.OK, data=self.SUCCESS_RESPONSE ) credentials = self.make_credentials( client_id=CLIENT_ID, client_secret=CLIENT_SECRET @@ -1006,9 +1006,9 @@ def test_refresh_impersonation_with_client_auth_success_ignore_default_scopes(se # Initialize mock request to handle token exchange and service account # impersonation request. request = self.make_mock_request( - status=http.client.OK, + status=http_client.OK, data=token_response, - impersonation_status=http.client.OK, + impersonation_status=http_client.OK, impersonation_data=impersonation_response, ) # Initialize credentials with service account impersonation and basic auth. @@ -1077,9 +1077,9 @@ def test_refresh_impersonation_with_client_auth_success_use_default_scopes(self) # Initialize mock request to handle token exchange and service account # impersonation request. request = self.make_mock_request( - status=http.client.OK, + status=http_client.OK, data=token_response, - impersonation_status=http.client.OK, + impersonation_status=http_client.OK, impersonation_data=impersonation_response, ) # Initialize credentials with service account impersonation and basic auth. @@ -1114,7 +1114,7 @@ def test_refresh_impersonation_with_client_auth_success_use_default_scopes(self) def test_apply_without_quota_project_id(self): headers = {} request = self.make_mock_request( - status=http.client.OK, data=self.SUCCESS_RESPONSE + status=http_client.OK, data=self.SUCCESS_RESPONSE ) credentials = self.make_credentials() @@ -1128,7 +1128,7 @@ def test_apply_without_quota_project_id(self): def test_apply_workforce_without_quota_project_id(self): headers = {} request = self.make_mock_request( - status=http.client.OK, data=self.SUCCESS_RESPONSE + status=http_client.OK, data=self.SUCCESS_RESPONSE ) credentials = self.make_workforce_pool_credentials( workforce_pool_user_project=self.WORKFORCE_POOL_USER_PROJECT @@ -1153,9 +1153,9 @@ def test_apply_impersonation_without_quota_project_id(self): # Initialize mock request to handle token exchange and service account # impersonation request. request = self.make_mock_request( - status=http.client.OK, + status=http_client.OK, data=self.SUCCESS_RESPONSE.copy(), - impersonation_status=http.client.OK, + impersonation_status=http_client.OK, impersonation_data=impersonation_response, ) # Initialize credentials with service account impersonation. @@ -1175,7 +1175,7 @@ def test_apply_impersonation_without_quota_project_id(self): def test_apply_with_quota_project_id(self): headers = {"other": "header-value"} request = self.make_mock_request( - status=http.client.OK, data=self.SUCCESS_RESPONSE + status=http_client.OK, data=self.SUCCESS_RESPONSE ) credentials = self.make_credentials(quota_project_id=self.QUOTA_PROJECT_ID) @@ -1200,9 +1200,9 @@ def test_apply_impersonation_with_quota_project_id(self): # Initialize mock request to handle token exchange and service account # impersonation request. request = self.make_mock_request( - status=http.client.OK, + status=http_client.OK, data=self.SUCCESS_RESPONSE.copy(), - impersonation_status=http.client.OK, + impersonation_status=http_client.OK, impersonation_data=impersonation_response, ) # Initialize credentials with service account impersonation. @@ -1225,7 +1225,7 @@ def test_apply_impersonation_with_quota_project_id(self): def test_before_request(self): headers = {"other": "header-value"} request = self.make_mock_request( - status=http.client.OK, data=self.SUCCESS_RESPONSE + status=http_client.OK, data=self.SUCCESS_RESPONSE ) credentials = self.make_credentials() @@ -1248,7 +1248,7 @@ def test_before_request(self): def test_before_request_workforce(self): headers = {"other": "header-value"} request = self.make_mock_request( - status=http.client.OK, data=self.SUCCESS_RESPONSE + status=http_client.OK, data=self.SUCCESS_RESPONSE ) credentials = self.make_workforce_pool_credentials( workforce_pool_user_project=self.WORKFORCE_POOL_USER_PROJECT @@ -1282,9 +1282,9 @@ def test_before_request_impersonation(self): # Initialize mock request to handle token exchange and service account # impersonation request. request = self.make_mock_request( - status=http.client.OK, + status=http_client.OK, data=self.SUCCESS_RESPONSE.copy(), - impersonation_status=http.client.OK, + impersonation_status=http_client.OK, impersonation_data=impersonation_response, ) headers = {"other": "header-value"} @@ -1312,7 +1312,7 @@ def test_before_request_impersonation(self): def test_before_request_expired(self, utcnow): headers = {} request = self.make_mock_request( - status=http.client.OK, data=self.SUCCESS_RESPONSE + status=http_client.OK, data=self.SUCCESS_RESPONSE ) credentials = self.make_credentials() credentials.token = "token" @@ -1360,9 +1360,9 @@ def test_before_request_impersonation_expired(self, utcnow): # Initialize mock request to handle token exchange and service account # impersonation request. request = self.make_mock_request( - status=http.client.OK, + status=http_client.OK, data=self.SUCCESS_RESPONSE.copy(), - impersonation_status=http.client.OK, + impersonation_status=http_client.OK, impersonation_data=impersonation_response, ) credentials = self.make_credentials( @@ -1491,11 +1491,11 @@ def test_get_project_id_cloud_resource_manager_success(self): # Initialize mock request to handle token exchange, service account # impersonation and cloud resource manager request. request = self.make_mock_request( - status=http.client.OK, + status=http_client.OK, data=self.SUCCESS_RESPONSE.copy(), - impersonation_status=http.client.OK, + impersonation_status=http_client.OK, impersonation_data=impersonation_response, - cloud_resource_manager_status=http.client.OK, + cloud_resource_manager_status=http_client.OK, cloud_resource_manager_data=self.CLOUD_RESOURCE_MANAGER_SUCCESS_RESPONSE, ) credentials = self.make_credentials( @@ -1562,9 +1562,9 @@ def test_workforce_pool_get_project_id_cloud_resource_manager_success(self): # Initialize mock request to handle token exchange and cloud resource # manager request. request = self.make_mock_request( - status=http.client.OK, + status=http_client.OK, data=self.SUCCESS_RESPONSE.copy(), - cloud_resource_manager_status=http.client.OK, + cloud_resource_manager_status=http_client.OK, cloud_resource_manager_data=self.CLOUD_RESOURCE_MANAGER_SUCCESS_RESPONSE, ) credentials = self.make_workforce_pool_credentials( @@ -1611,9 +1611,9 @@ def test_get_project_id_cloud_resource_manager_error(self): # Simulate resource doesn't have sufficient permissions to access # cloud resource manager. request = self.make_mock_request( - status=http.client.OK, + status=http_client.OK, data=self.SUCCESS_RESPONSE.copy(), - cloud_resource_manager_status=http.client.UNAUTHORIZED, + cloud_resource_manager_status=http_client.UNAUTHORIZED, ) credentials = self.make_credentials(scopes=self.SCOPES) diff --git a/packages/google-auth/tests/test_iam.py b/packages/google-auth/tests/test_iam.py index e9eca583cb9b..bc71225b101e 100644 --- a/packages/google-auth/tests/test_iam.py +++ b/packages/google-auth/tests/test_iam.py @@ -14,11 +14,11 @@ import base64 import datetime -import http.client import json import mock import pytest +from six.moves import http_client from google.auth import _helpers from google.auth import exceptions @@ -81,7 +81,7 @@ def test_key_id(self): def test_sign_bytes(self): signature = b"DEADBEEF" encoded_signature = base64.b64encode(signature).decode("utf-8") - request = make_request(http.client.OK, data={"signedBlob": encoded_signature}) + request = make_request(http_client.OK, data={"signedBlob": encoded_signature}) credentials = make_credentials() signer = iam.Signer(request, credentials, mock.sentinel.service_account_email) @@ -93,7 +93,7 @@ def test_sign_bytes(self): assert kwargs["headers"]["Content-Type"] == "application/json" def test_sign_bytes_failure(self): - request = make_request(http.client.UNAUTHORIZED) + request = make_request(http_client.UNAUTHORIZED) credentials = make_credentials() signer = iam.Signer(request, credentials, mock.sentinel.service_account_email) diff --git a/packages/google-auth/tests/test_identity_pool.py b/packages/google-auth/tests/test_identity_pool.py index e90e2880dfe2..87e343be4981 100644 --- a/packages/google-auth/tests/test_identity_pool.py +++ b/packages/google-auth/tests/test_identity_pool.py @@ -13,13 +13,13 @@ # limitations under the License. import datetime -import http.client import json import os -import urllib import mock import pytest +from six.moves import http_client +from six.moves import urllib from google.auth import _helpers from google.auth import exceptions @@ -92,7 +92,7 @@ def make_mock_response(cls, status, data): @classmethod def make_mock_request( - cls, token_status=http.client.OK, token_data=None, *extra_requests + cls, token_status=http_client.OK, token_data=None, *extra_requests ): responses = [] responses.append(cls.make_mock_response(token_status, token_data)) @@ -218,14 +218,14 @@ def assert_underlying_credentials_refresh( # service account impersonation request. requests = [] if credential_data: - requests.append((http.client.OK, credential_data)) + requests.append((http_client.OK, credential_data)) token_request_index = len(requests) - requests.append((http.client.OK, token_response)) + requests.append((http_client.OK, token_response)) if service_account_impersonation_url: impersonation_request_index = len(requests) - requests.append((http.client.OK, impersonation_response)) + requests.append((http_client.OK, impersonation_response)) request = cls.make_mock_request(*[el for req in requests for el in req]) diff --git a/packages/google-auth/tests/test_impersonated_credentials.py b/packages/google-auth/tests/test_impersonated_credentials.py index 3dbb6caa6544..bceaebaa5d66 100644 --- a/packages/google-auth/tests/test_impersonated_credentials.py +++ b/packages/google-auth/tests/test_impersonated_credentials.py @@ -13,12 +13,12 @@ # limitations under the License. import datetime -import http.client import json import os import mock import pytest +from six.moves import http_client from google.auth import _helpers from google.auth import crypt @@ -79,7 +79,7 @@ def mock_authorizedsession_sign(): "google.auth.transport.requests.AuthorizedSession.request", autospec=True ) as auth_session: data = {"keyId": "1", "signedBlob": "c2lnbmF0dXJl"} - auth_session.return_value = MockResponse(data, http.client.OK) + auth_session.return_value = MockResponse(data, http_client.OK) yield auth_session @@ -89,7 +89,7 @@ def mock_authorizedsession_idtoken(): "google.auth.transport.requests.AuthorizedSession.request", autospec=True ) as auth_session: data = {"token": ID_TOKEN_DATA} - auth_session.return_value = MockResponse(data, http.client.OK) + auth_session.return_value = MockResponse(data, http_client.OK) yield auth_session @@ -141,7 +141,7 @@ def test_default_state(self): def make_request( self, data, - status=http.client.OK, + status=http_client.OK, headers=None, side_effect=None, use_data_bytes=True, @@ -169,7 +169,7 @@ def test_refresh_success(self, use_data_bytes, mock_donor_credentials): request = self.make_request( data=json.dumps(response_body), - status=http.client.OK, + status=http_client.OK, use_data_bytes=use_data_bytes, ) @@ -194,7 +194,7 @@ def test_refresh_success_iam_endpoint_override( request = self.make_request( data=json.dumps(response_body), - status=http.client.OK, + status=http_client.OK, use_data_bytes=use_data_bytes, ) @@ -229,7 +229,7 @@ def test_refresh_source_credentials(self, time_skew): ).isoformat("T") + "Z" response_body = {"accessToken": "token", "expireTime": expire_time} request = self.make_request( - data=json.dumps(response_body), status=http.client.OK + data=json.dumps(response_body), status=http_client.OK ) credentials.refresh(request) @@ -254,7 +254,7 @@ def test_refresh_failure_malformed_expire_time(self, mock_donor_credentials): response_body = {"accessToken": token, "expireTime": expire_time} request = self.make_request( - data=json.dumps(response_body), status=http.client.OK + data=json.dumps(response_body), status=http_client.OK ) with pytest.raises(exceptions.RefreshError) as excinfo: @@ -277,7 +277,7 @@ def test_refresh_failure_unauthorzed(self, mock_donor_credentials): } request = self.make_request( - data=json.dumps(response_body), status=http.client.UNAUTHORIZED + data=json.dumps(response_body), status=http_client.UNAUTHORIZED ) with pytest.raises(exceptions.RefreshError) as excinfo: @@ -294,7 +294,7 @@ def test_refresh_failure_http_error(self, mock_donor_credentials): response_body = {} request = self.make_request( - data=json.dumps(response_body), status=http.client.HTTPException + data=json.dumps(response_body), status=http_client.HTTPException ) with pytest.raises(exceptions.RefreshError) as excinfo: @@ -331,7 +331,7 @@ def test_sign_bytes(self, mock_donor_credentials, mock_authorizedsession_sign): token_response_body = {"accessToken": token, "expireTime": expire_time} response = mock.create_autospec(transport.Response, instance=False) - response.status = http.client.OK + response.status = http_client.OK response.data = _helpers.to_bytes(json.dumps(token_response_body)) request = mock.create_autospec(transport.Request, instance=False) @@ -369,7 +369,7 @@ def test_with_quota_project_iam_endpoint_override( request = self.make_request( data=json.dumps(response_body), - status=http.client.OK, + status=http_client.OK, use_data_bytes=use_data_bytes, ) @@ -394,7 +394,7 @@ def test_id_token_success( response_body = {"accessToken": token, "expireTime": expire_time} request = self.make_request( - data=json.dumps(response_body), status=http.client.OK + data=json.dumps(response_body), status=http_client.OK ) credentials.refresh(request) @@ -423,7 +423,7 @@ def test_id_token_from_credential( response_body = {"accessToken": token, "expireTime": expire_time} request = self.make_request( - data=json.dumps(response_body), status=http.client.OK + data=json.dumps(response_body), status=http_client.OK ) credentials.refresh(request) @@ -453,7 +453,7 @@ def test_id_token_with_target_audience( response_body = {"accessToken": token, "expireTime": expire_time} request = self.make_request( - data=json.dumps(response_body), status=http.client.OK + data=json.dumps(response_body), status=http_client.OK ) credentials.refresh(request) @@ -494,7 +494,7 @@ def test_id_token_with_include_email( response_body = {"accessToken": token, "expireTime": expire_time} request = self.make_request( - data=json.dumps(response_body), status=http.client.OK + data=json.dumps(response_body), status=http_client.OK ) credentials.refresh(request) @@ -523,7 +523,7 @@ def test_id_token_with_quota_project( response_body = {"accessToken": token, "expireTime": expire_time} request = self.make_request( - data=json.dumps(response_body), status=http.client.OK + data=json.dumps(response_body), status=http_client.OK ) credentials.refresh(request) diff --git a/packages/google-auth/tests/test_jwt.py b/packages/google-auth/tests/test_jwt.py index ba7277cdcb6e..c0e1184dcb6d 100644 --- a/packages/google-auth/tests/test_jwt.py +++ b/packages/google-auth/tests/test_jwt.py @@ -288,9 +288,9 @@ def test_decode_no_key_id(token_factory): def test_decode_unknown_alg(): - headers = json.dumps({"kid": "1", "alg": "fakealg"}) + headers = json.dumps({u"kid": u"1", u"alg": u"fakealg"}) token = b".".join( - map(lambda seg: base64.b64encode(seg.encode("utf-8")), [headers, "{}", "sig"]) + map(lambda seg: base64.b64encode(seg.encode("utf-8")), [headers, u"{}", u"sig"]) ) with pytest.raises(ValueError) as excinfo: @@ -300,9 +300,9 @@ def test_decode_unknown_alg(): def test_decode_missing_crytography_alg(monkeypatch): monkeypatch.delitem(jwt._ALGORITHM_TO_VERIFIER_CLASS, "ES256") - headers = json.dumps({"kid": "1", "alg": "ES256"}) + headers = json.dumps({u"kid": u"1", u"alg": u"ES256"}) token = b".".join( - map(lambda seg: base64.b64encode(seg.encode("utf-8")), [headers, "{}", "sig"]) + map(lambda seg: base64.b64encode(seg.encode("utf-8")), [headers, u"{}", u"sig"]) ) with pytest.raises(ValueError) as excinfo: diff --git a/packages/google-auth/tests/transport/compliance.py b/packages/google-auth/tests/transport/compliance.py index a5cb678c31fd..e093d761df3d 100644 --- a/packages/google-auth/tests/transport/compliance.py +++ b/packages/google-auth/tests/transport/compliance.py @@ -12,12 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -import http.client import time import flask import pytest from pytest_localserver.http import WSGIServer +from six.moves import http_client from google.auth import exceptions @@ -43,11 +43,11 @@ def server(self): def index(): header_value = flask.request.headers.get("x-test-header", "value") headers = {"X-Test-Header": header_value} - return "Basic Content", http.client.OK, headers + return "Basic Content", http_client.OK, headers @app.route("/server_error") def server_error(): - return "Error", http.client.INTERNAL_SERVER_ERROR + return "Error", http_client.INTERNAL_SERVER_ERROR @app.route("/wait") def wait(): @@ -65,7 +65,7 @@ def test_request_basic(self, server): request = self.make_request() response = request(url=server.url + "/basic", method="GET") - assert response.status == http.client.OK + assert response.status == http_client.OK assert response.headers["x-test-header"] == "value" assert response.data == b"Basic Content" @@ -73,7 +73,7 @@ def test_request_with_timeout_success(self, server): request = self.make_request() response = request(url=server.url + "/basic", method="GET", timeout=2) - assert response.status == http.client.OK + assert response.status == http_client.OK assert response.headers["x-test-header"] == "value" assert response.data == b"Basic Content" @@ -91,7 +91,7 @@ def test_request_headers(self, server): headers={"x-test-header": "hello world"}, ) - assert response.status == http.client.OK + assert response.status == http_client.OK assert response.headers["x-test-header"] == "hello world" assert response.data == b"Basic Content" @@ -99,7 +99,7 @@ def test_request_error(self, server): request = self.make_request() response = request(url=server.url + "/server_error", method="GET") - assert response.status == http.client.INTERNAL_SERVER_ERROR + assert response.status == http_client.INTERNAL_SERVER_ERROR assert response.data == b"Error" def test_connection_error(self): diff --git a/packages/google-auth/tests/transport/test_requests.py b/packages/google-auth/tests/transport/test_requests.py index 8b57e0b4e747..ed9300d7684e 100644 --- a/packages/google-auth/tests/transport/test_requests.py +++ b/packages/google-auth/tests/transport/test_requests.py @@ -14,7 +14,6 @@ import datetime import functools -import http.client import os import sys @@ -24,6 +23,7 @@ import pytest import requests import requests.adapters +from six.moves import http_client from google.auth import environment_vars from google.auth import exceptions @@ -188,7 +188,7 @@ def test_import_error(self): ) -def make_response(status=http.client.OK, data=None): +def make_response(status=http_client.OK, data=None): response = requests.Response() response.status_code = status response._content = data @@ -249,10 +249,10 @@ def test_request_no_refresh(self): def test_request_refresh(self): credentials = mock.Mock(wraps=CredentialsStub()) - final_response = make_response(status=http.client.OK) + final_response = make_response(status=http_client.OK) # First request will 401, second request will succeed. adapter = AdapterStub( - [make_response(status=http.client.UNAUTHORIZED), final_response] + [make_response(status=http_client.UNAUTHORIZED), final_response] ) authed_session = google.auth.transport.requests.AuthorizedSession( @@ -282,7 +282,7 @@ def test_request_max_allowed_time_timeout_error(self, frozen_time): wraps=TimeTickCredentialsStub(time_tick=tick_one_second) ) adapter = TimeTickAdapterStub( - time_tick=tick_one_second, responses=[make_response(status=http.client.OK)] + time_tick=tick_one_second, responses=[make_response(status=http_client.OK)] ) authed_session = google.auth.transport.requests.AuthorizedSession(credentials) @@ -304,8 +304,8 @@ def test_request_max_allowed_time_w_transport_timeout_no_error(self, frozen_time adapter = TimeTickAdapterStub( time_tick=tick_one_second, responses=[ - make_response(status=http.client.UNAUTHORIZED), - make_response(status=http.client.OK), + make_response(status=http_client.UNAUTHORIZED), + make_response(status=http_client.OK), ], ) @@ -328,8 +328,8 @@ def test_request_max_allowed_time_w_refresh_timeout_no_error(self, frozen_time): adapter = TimeTickAdapterStub( time_tick=tick_one_second, responses=[ - make_response(status=http.client.UNAUTHORIZED), - make_response(status=http.client.OK), + make_response(status=http_client.UNAUTHORIZED), + make_response(status=http_client.OK), ], ) @@ -355,8 +355,8 @@ def test_request_timeout_w_refresh_timeout_timeout_error(self, frozen_time): adapter = TimeTickAdapterStub( time_tick=tick_one_second, responses=[ - make_response(status=http.client.UNAUTHORIZED), - make_response(status=http.client.OK), + make_response(status=http_client.UNAUTHORIZED), + make_response(status=http_client.OK), ], ) diff --git a/packages/google-auth/tests/transport/test_urllib3.py b/packages/google-auth/tests/transport/test_urllib3.py index 995d3dccd09c..e3848c177aad 100644 --- a/packages/google-auth/tests/transport/test_urllib3.py +++ b/packages/google-auth/tests/transport/test_urllib3.py @@ -12,13 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -import http.client import os import sys import mock import OpenSSL import pytest +from six.moves import http_client import urllib3 from google.auth import environment_vars @@ -84,7 +84,7 @@ def urlopen(self, method, url, body=None, headers=None, **kwargs): class ResponseStub(object): - def __init__(self, status=http.client.OK, data=None): + def __init__(self, status=http_client.OK, data=None): self.status = status self.data = data @@ -141,12 +141,12 @@ def test_urlopen_no_refresh(self): def test_urlopen_refresh(self): credentials = mock.Mock(wraps=CredentialsStub()) - final_response = ResponseStub(status=http.client.OK) + final_response = ResponseStub(status=http_client.OK) # First request will 401, second request will succeed. - stub = HttpStub([ResponseStub(status=http.client.UNAUTHORIZED), final_response]) + http = HttpStub([ResponseStub(status=http_client.UNAUTHORIZED), final_response]) authed_http = google.auth.transport.urllib3.AuthorizedHttp( - credentials, http=stub + credentials, http=http ) authed_http = authed_http.urlopen("GET", "http://example.com") @@ -154,7 +154,7 @@ def test_urlopen_refresh(self): assert authed_http == final_response assert credentials.before_request.call_count == 2 assert credentials.refresh.called - assert stub.requests == [ + assert http.requests == [ ("GET", self.TEST_URL, None, {"authorization": "token"}, {}), ("GET", self.TEST_URL, None, {"authorization": "token1"}, {}), ] diff --git a/packages/google-auth/tests_async/oauth2/test__client_async.py b/packages/google-auth/tests_async/oauth2/test__client_async.py index 66338d56c09b..6e48c4590fcb 100644 --- a/packages/google-auth/tests_async/oauth2/test__client_async.py +++ b/packages/google-auth/tests_async/oauth2/test__client_async.py @@ -13,12 +13,13 @@ # limitations under the License. import datetime -import http.client import json -import urllib import mock import pytest +import six +from six.moves import http_client +from six.moves import urllib from google.auth import _helpers from google.auth import _jwt_async as jwt @@ -28,7 +29,7 @@ from tests.oauth2 import test__client as test_client -def make_request(response_data, status=http.client.OK): +def make_request(response_data, status=http_client.OK): response = mock.AsyncMock(spec=["transport.Response"]) response.status = status data = json.dumps(response_data).encode("utf-8") @@ -92,7 +93,7 @@ async def test__token_endpoint_request_json(): @pytest.mark.asyncio async def test__token_endpoint_request_error(): - request = make_request({}, status=http.client.BAD_REQUEST) + request = make_request({}, status=http_client.BAD_REQUEST) with pytest.raises(exceptions.RefreshError): await _client._token_endpoint_request(request, "http://example.com", {}) @@ -101,7 +102,7 @@ async def test__token_endpoint_request_error(): @pytest.mark.asyncio async def test__token_endpoint_request_internal_failure_error(): request = make_request( - {"error_description": "internal_failure"}, status=http.client.BAD_REQUEST + {"error_description": "internal_failure"}, status=http_client.BAD_REQUEST ) with pytest.raises(exceptions.RefreshError): @@ -110,7 +111,7 @@ async def test__token_endpoint_request_internal_failure_error(): ) request = make_request( - {"error": "internal_failure"}, status=http.client.BAD_REQUEST + {"error": "internal_failure"}, status=http_client.BAD_REQUEST ) with pytest.raises(exceptions.RefreshError): @@ -123,7 +124,7 @@ def verify_request_params(request, params): request_body = request.call_args[1]["body"].decode("utf-8") request_params = urllib.parse.parse_qs(request_body) - for key, value in params.items(): + for key, value in six.iteritems(params): assert request_params[key][0] == value diff --git a/packages/google-auth/tests_async/transport/async_compliance.py b/packages/google-auth/tests_async/transport/async_compliance.py index 385a9236a1f2..9c4b173c2341 100644 --- a/packages/google-auth/tests_async/transport/async_compliance.py +++ b/packages/google-auth/tests_async/transport/async_compliance.py @@ -12,12 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -import http.client import time import flask import pytest from pytest_localserver.http import WSGIServer +from six.moves import http_client from google.auth import exceptions from tests.transport import compliance @@ -41,11 +41,11 @@ def server(self): def index(): header_value = flask.request.headers.get("x-test-header", "value") headers = {"X-Test-Header": header_value} - return "Basic Content", http.client.OK, headers + return "Basic Content", http_client.OK, headers @app.route("/server_error") def server_error(): - return "Error", http.client.INTERNAL_SERVER_ERROR + return "Error", http_client.INTERNAL_SERVER_ERROR @app.route("/wait") def wait(): @@ -63,7 +63,7 @@ def wait(): async def test_request_basic(self, server): request = self.make_request() response = await request(url=server.url + "/basic", method="GET") - assert response.status == http.client.OK + assert response.status == http_client.OK assert response.headers["x-test-header"] == "value" # Use 13 as this is the length of the data written into the stream. @@ -75,7 +75,7 @@ async def test_request_basic(self, server): async def test_request_basic_with_http(self, server): request = self.make_with_parameter_request() response = await request(url=server.url + "/basic", method="GET") - assert response.status == http.client.OK + assert response.status == http_client.OK assert response.headers["x-test-header"] == "value" # Use 13 as this is the length of the data written into the stream. @@ -88,7 +88,7 @@ async def test_request_with_timeout_success(self, server): request = self.make_request() response = await request(url=server.url + "/basic", method="GET", timeout=2) - assert response.status == http.client.OK + assert response.status == http_client.OK assert response.headers["x-test-header"] == "value" data = await response.data.read(13) @@ -110,7 +110,7 @@ async def test_request_headers(self, server): headers={"x-test-header": "hello world"}, ) - assert response.status == http.client.OK + assert response.status == http_client.OK assert response.headers["x-test-header"] == "hello world" data = await response.data.read(13) @@ -121,7 +121,7 @@ async def test_request_error(self, server): request = self.make_request() response = await request(url=server.url + "/server_error", method="GET") - assert response.status == http.client.INTERNAL_SERVER_ERROR + assert response.status == http_client.INTERNAL_SERVER_ERROR data = await response.data.read(5) assert data == b"Error" From 2077a49d6d0dd2fbd89ce8e6dfb653a85143f230 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 25 Oct 2021 10:32:06 -0700 Subject: [PATCH 491/966] chore: release 2.3.1 (#891) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 12 ++++++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index a8fddc9b9b42..310f6afd18af 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,18 @@ [1]: https://pypi.org/project/google-auth/#history +### [2.3.1](https://www.github.com/googleapis/google-auth-library-python/compare/v2.3.0...v2.3.1) (2021-10-21) + + +### Bug Fixes + +* add back python 2.7 for gcloud usage only ([#892](https://www.github.com/googleapis/google-auth-library-python/issues/892)) ([5bd5ccf](https://www.github.com/googleapis/google-auth-library-python/commit/5bd5ccf7cf229f033c7152ce0b650a40feb25f81)) + + +### Documentation + +* Fix formatting of `GCE_METADATA_HOST` ([#890](https://www.github.com/googleapis/google-auth-library-python/issues/890)) ([e2b3c98](https://www.github.com/googleapis/google-auth-library-python/commit/e2b3c98cd8c67b702be1b711c06ee7b9bbedb8ba)) + ## [2.3.0](https://www.github.com/googleapis/google-auth-library-python/compare/v2.2.1...v2.3.0) (2021-10-07) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 60805d9ca3ff..7ca77790d5f9 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.3.0" +__version__ = "2.3.1" From d1c63938f60461210e570b5bc96b2cb60432a10a Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Mon, 25 Oct 2021 16:31:47 -0700 Subject: [PATCH 492/966] fix: add clock_skew_in_seconds to verify_token functions (#894) --- .../google/oauth2/_id_token_async.py | 29 ++++++-- .../google-auth/google/oauth2/id_token.py | 37 ++++++++-- .../google-auth/tests/oauth2/test_id_token.py | 68 +++++++++++++++++- .../tests_async/oauth2/test_id_token.py | 69 ++++++++++++++++++- 4 files changed, 191 insertions(+), 12 deletions(-) diff --git a/packages/google-auth/google/oauth2/_id_token_async.py b/packages/google-auth/google/oauth2/_id_token_async.py index 31fcbc6230a0..20630e0d4a91 100644 --- a/packages/google-auth/google/oauth2/_id_token_async.py +++ b/packages/google-auth/google/oauth2/_id_token_async.py @@ -99,7 +99,11 @@ async def _fetch_certs(request, certs_url): async def verify_token( - id_token, request, audience=None, certs_url=sync_id_token._GOOGLE_OAUTH2_CERTS_URL + id_token, + request, + audience=None, + certs_url=sync_id_token._GOOGLE_OAUTH2_CERTS_URL, + clock_skew_in_seconds=0, ): """Verifies an ID token and returns the decoded token. @@ -112,16 +116,25 @@ async def verify_token( certs_url (str): The URL that specifies the certificates to use to verify the token. This URL should return JSON in the format of ``{'key id': 'x509 certificate'}``. + clock_skew_in_seconds (int): The clock skew used for `iat` and `exp` + validation. Returns: Mapping[str, Any]: The decoded token. """ certs = await _fetch_certs(request, certs_url) - return jwt.decode(id_token, certs=certs, audience=audience) + return jwt.decode( + id_token, + certs=certs, + audience=audience, + clock_skew_in_seconds=clock_skew_in_seconds, + ) -async def verify_oauth2_token(id_token, request, audience=None): +async def verify_oauth2_token( + id_token, request, audience=None, clock_skew_in_seconds=0 +): """Verifies an ID Token issued by Google's OAuth 2.0 authorization server. Args: @@ -131,6 +144,8 @@ async def verify_oauth2_token(id_token, request, audience=None): audience (str): The audience that this token is intended for. This is typically your application's OAuth 2.0 client ID. If None then the audience is not verified. + clock_skew_in_seconds (int): The clock skew used for `iat` and `exp` + validation. Returns: Mapping[str, Any]: The decoded token. @@ -143,6 +158,7 @@ async def verify_oauth2_token(id_token, request, audience=None): request, audience=audience, certs_url=sync_id_token._GOOGLE_OAUTH2_CERTS_URL, + clock_skew_in_seconds=clock_skew_in_seconds, ) if idinfo["iss"] not in sync_id_token._GOOGLE_ISSUERS: @@ -155,7 +171,9 @@ async def verify_oauth2_token(id_token, request, audience=None): return idinfo -async def verify_firebase_token(id_token, request, audience=None): +async def verify_firebase_token( + id_token, request, audience=None, clock_skew_in_seconds=0 +): """Verifies an ID Token issued by Firebase Authentication. Args: @@ -165,6 +183,8 @@ async def verify_firebase_token(id_token, request, audience=None): audience (str): The audience that this token is intended for. This is typically your Firebase application ID. If None then the audience is not verified. + clock_skew_in_seconds (int): The clock skew used for `iat` and `exp` + validation. Returns: Mapping[str, Any]: The decoded token. @@ -174,6 +194,7 @@ async def verify_firebase_token(id_token, request, audience=None): request, audience=audience, certs_url=sync_id_token._GOOGLE_APIS_CERTS_URL, + clock_skew_in_seconds=clock_skew_in_seconds, ) diff --git a/packages/google-auth/google/oauth2/id_token.py b/packages/google-auth/google/oauth2/id_token.py index 8d0f85a593f6..20d3ac1aff7f 100644 --- a/packages/google-auth/google/oauth2/id_token.py +++ b/packages/google-auth/google/oauth2/id_token.py @@ -105,7 +105,13 @@ def _fetch_certs(request, certs_url): return json.loads(response.data.decode("utf-8")) -def verify_token(id_token, request, audience=None, certs_url=_GOOGLE_OAUTH2_CERTS_URL): +def verify_token( + id_token, + request, + audience=None, + certs_url=_GOOGLE_OAUTH2_CERTS_URL, + clock_skew_in_seconds=0, +): """Verifies an ID token and returns the decoded token. Args: @@ -117,16 +123,23 @@ def verify_token(id_token, request, audience=None, certs_url=_GOOGLE_OAUTH2_CERT certs_url (str): The URL that specifies the certificates to use to verify the token. This URL should return JSON in the format of ``{'key id': 'x509 certificate'}``. + clock_skew_in_seconds (int): The clock skew used for `iat` and `exp` + validation. Returns: Mapping[str, Any]: The decoded token. """ certs = _fetch_certs(request, certs_url) - return jwt.decode(id_token, certs=certs, audience=audience) + return jwt.decode( + id_token, + certs=certs, + audience=audience, + clock_skew_in_seconds=clock_skew_in_seconds, + ) -def verify_oauth2_token(id_token, request, audience=None): +def verify_oauth2_token(id_token, request, audience=None, clock_skew_in_seconds=0): """Verifies an ID Token issued by Google's OAuth 2.0 authorization server. Args: @@ -136,6 +149,8 @@ def verify_oauth2_token(id_token, request, audience=None): audience (str): The audience that this token is intended for. This is typically your application's OAuth 2.0 client ID. If None then the audience is not verified. + clock_skew_in_seconds (int): The clock skew used for `iat` and `exp` + validation. Returns: Mapping[str, Any]: The decoded token. @@ -144,7 +159,11 @@ def verify_oauth2_token(id_token, request, audience=None): exceptions.GoogleAuthError: If the issuer is invalid. """ idinfo = verify_token( - id_token, request, audience=audience, certs_url=_GOOGLE_OAUTH2_CERTS_URL + id_token, + request, + audience=audience, + certs_url=_GOOGLE_OAUTH2_CERTS_URL, + clock_skew_in_seconds=clock_skew_in_seconds, ) if idinfo["iss"] not in _GOOGLE_ISSUERS: @@ -157,7 +176,7 @@ def verify_oauth2_token(id_token, request, audience=None): return idinfo -def verify_firebase_token(id_token, request, audience=None): +def verify_firebase_token(id_token, request, audience=None, clock_skew_in_seconds=0): """Verifies an ID Token issued by Firebase Authentication. Args: @@ -167,12 +186,18 @@ def verify_firebase_token(id_token, request, audience=None): audience (str): The audience that this token is intended for. This is typically your Firebase application ID. If None then the audience is not verified. + clock_skew_in_seconds (int): The clock skew used for `iat` and `exp` + validation. Returns: Mapping[str, Any]: The decoded token. """ return verify_token( - id_token, request, audience=audience, certs_url=_GOOGLE_APIS_CERTS_URL + id_token, + request, + audience=audience, + certs_url=_GOOGLE_APIS_CERTS_URL, + clock_skew_in_seconds=clock_skew_in_seconds, ) diff --git a/packages/google-auth/tests/oauth2/test_id_token.py b/packages/google-auth/tests/oauth2/test_id_token.py index ab6774355c37..a612c58fecb3 100644 --- a/packages/google-auth/tests/oauth2/test_id_token.py +++ b/packages/google-auth/tests/oauth2/test_id_token.py @@ -71,7 +71,10 @@ def test_verify_token(_fetch_certs, decode): mock.sentinel.request, id_token._GOOGLE_OAUTH2_CERTS_URL ) decode.assert_called_once_with( - mock.sentinel.token, certs=_fetch_certs.return_value, audience=None + mock.sentinel.token, + certs=_fetch_certs.return_value, + audience=None, + clock_skew_in_seconds=0, ) @@ -91,6 +94,28 @@ def test_verify_token_args(_fetch_certs, decode): mock.sentinel.token, certs=_fetch_certs.return_value, audience=mock.sentinel.audience, + clock_skew_in_seconds=0, + ) + + +@mock.patch("google.auth.jwt.decode", autospec=True) +@mock.patch("google.oauth2.id_token._fetch_certs", autospec=True) +def test_verify_token_clock_skew(_fetch_certs, decode): + result = id_token.verify_token( + mock.sentinel.token, + mock.sentinel.request, + audience=mock.sentinel.audience, + certs_url=mock.sentinel.certs_url, + clock_skew_in_seconds=10, + ) + + assert result == decode.return_value + _fetch_certs.assert_called_once_with(mock.sentinel.request, mock.sentinel.certs_url) + decode.assert_called_once_with( + mock.sentinel.token, + certs=_fetch_certs.return_value, + audience=mock.sentinel.audience, + clock_skew_in_seconds=10, ) @@ -107,6 +132,27 @@ def test_verify_oauth2_token(verify_token): mock.sentinel.request, audience=mock.sentinel.audience, certs_url=id_token._GOOGLE_OAUTH2_CERTS_URL, + clock_skew_in_seconds=0, + ) + + +@mock.patch("google.oauth2.id_token.verify_token", autospec=True) +def test_verify_oauth2_token_clock_skew(verify_token): + verify_token.return_value = {"iss": "accounts.google.com"} + result = id_token.verify_oauth2_token( + mock.sentinel.token, + mock.sentinel.request, + audience=mock.sentinel.audience, + clock_skew_in_seconds=10, + ) + + assert result == verify_token.return_value + verify_token.assert_called_once_with( + mock.sentinel.token, + mock.sentinel.request, + audience=mock.sentinel.audience, + certs_url=id_token._GOOGLE_OAUTH2_CERTS_URL, + clock_skew_in_seconds=10, ) @@ -132,6 +178,26 @@ def test_verify_firebase_token(verify_token): mock.sentinel.request, audience=mock.sentinel.audience, certs_url=id_token._GOOGLE_APIS_CERTS_URL, + clock_skew_in_seconds=0, + ) + + +@mock.patch("google.oauth2.id_token.verify_token", autospec=True) +def test_verify_firebase_token_clock_skew(verify_token): + result = id_token.verify_firebase_token( + mock.sentinel.token, + mock.sentinel.request, + audience=mock.sentinel.audience, + clock_skew_in_seconds=10, + ) + + assert result == verify_token.return_value + verify_token.assert_called_once_with( + mock.sentinel.token, + mock.sentinel.request, + audience=mock.sentinel.audience, + certs_url=id_token._GOOGLE_APIS_CERTS_URL, + clock_skew_in_seconds=10, ) diff --git a/packages/google-auth/tests_async/oauth2/test_id_token.py b/packages/google-auth/tests_async/oauth2/test_id_token.py index 1deb9efd676f..2aee7676bfb2 100644 --- a/packages/google-auth/tests_async/oauth2/test_id_token.py +++ b/packages/google-auth/tests_async/oauth2/test_id_token.py @@ -71,7 +71,30 @@ async def test_verify_token(_fetch_certs, decode): mock.sentinel.request, sync_id_token._GOOGLE_OAUTH2_CERTS_URL ) decode.assert_called_once_with( - mock.sentinel.token, certs=_fetch_certs.return_value, audience=None + mock.sentinel.token, + certs=_fetch_certs.return_value, + audience=None, + clock_skew_in_seconds=0, + ) + + +@mock.patch("google.auth.jwt.decode", autospec=True) +@mock.patch("google.oauth2._id_token_async._fetch_certs", autospec=True) +@pytest.mark.asyncio +async def test_verify_token_clock_skew(_fetch_certs, decode): + result = await id_token.verify_token( + mock.sentinel.token, mock.sentinel.request, clock_skew_in_seconds=10 + ) + + assert result == decode.return_value + _fetch_certs.assert_called_once_with( + mock.sentinel.request, sync_id_token._GOOGLE_OAUTH2_CERTS_URL + ) + decode.assert_called_once_with( + mock.sentinel.token, + certs=_fetch_certs.return_value, + audience=None, + clock_skew_in_seconds=10, ) @@ -92,6 +115,7 @@ async def test_verify_token_args(_fetch_certs, decode): mock.sentinel.token, certs=_fetch_certs.return_value, audience=mock.sentinel.audience, + clock_skew_in_seconds=0, ) @@ -109,6 +133,28 @@ async def test_verify_oauth2_token(verify_token): mock.sentinel.request, audience=mock.sentinel.audience, certs_url=sync_id_token._GOOGLE_OAUTH2_CERTS_URL, + clock_skew_in_seconds=0, + ) + + +@mock.patch("google.oauth2._id_token_async.verify_token", autospec=True) +@pytest.mark.asyncio +async def test_verify_oauth2_token_clock_skew(verify_token): + verify_token.return_value = {"iss": "accounts.google.com"} + result = await id_token.verify_oauth2_token( + mock.sentinel.token, + mock.sentinel.request, + audience=mock.sentinel.audience, + clock_skew_in_seconds=10, + ) + + assert result == verify_token.return_value + verify_token.assert_called_once_with( + mock.sentinel.token, + mock.sentinel.request, + audience=mock.sentinel.audience, + certs_url=sync_id_token._GOOGLE_OAUTH2_CERTS_URL, + clock_skew_in_seconds=10, ) @@ -136,6 +182,27 @@ async def test_verify_firebase_token(verify_token): mock.sentinel.request, audience=mock.sentinel.audience, certs_url=sync_id_token._GOOGLE_APIS_CERTS_URL, + clock_skew_in_seconds=0, + ) + + +@mock.patch("google.oauth2._id_token_async.verify_token", autospec=True) +@pytest.mark.asyncio +async def test_verify_firebase_token_clock_skew(verify_token): + result = await id_token.verify_firebase_token( + mock.sentinel.token, + mock.sentinel.request, + audience=mock.sentinel.audience, + clock_skew_in_seconds=10, + ) + + assert result == verify_token.return_value + verify_token.assert_called_once_with( + mock.sentinel.token, + mock.sentinel.request, + audience=mock.sentinel.audience, + certs_url=sync_id_token._GOOGLE_APIS_CERTS_URL, + clock_skew_in_seconds=10, ) From eaccde51ae899eebb647084ec7bc563a633d927f Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Mon, 25 Oct 2021 17:52:12 -0600 Subject: [PATCH 493/966] chore: update authorized_user.json (#896) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10323 -> 10323 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 646bb239447c711fcfd9afc1ff05d8770ec17798..58c59eda8443c6776e4a9a9e697e578db45f60de 100644 GIT binary patch literal 10323 zcmV-ZD6H2CBmnkJRTIQi-bEyFwp+bqGc@7kfM-BvGKhz%nsjg--YGs~DiW$t0LRP_ zj)Bi*dN^Fa;de*`S)4`vV|a(A$)%IxW#Go|rbd0aS0E|V>zOF`Xy=lfddnV)hS#UN z&sCcZq%5-SwW;G$r%Wh}@n+JuvaH+4W4Oj`^�PXxI>;sy0AEOFB;eY>s^{*1t2{ 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 Date: Mon, 25 Oct 2021 21:35:51 -0400 Subject: [PATCH 494/966] chore(python): block pushing non-cloud library docs to Cloud RAD (#898) Source-Link: https://github.com/googleapis/synthtool/commit/694118b039b09551fb5d445fceb361a7dbb06400 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:ec49167c606648a063d1222220b48119c912562849a0528f35bfb592a9f72737 Co-authored-by: Owl Bot --- packages/google-auth/.github/.OwlBot.lock.yaml | 2 +- packages/google-auth/.kokoro/docs/common.cfg | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/.github/.OwlBot.lock.yaml b/packages/google-auth/.github/.OwlBot.lock.yaml index 7d98291cc35f..cb89b2e326b7 100644 --- a/packages/google-auth/.github/.OwlBot.lock.yaml +++ b/packages/google-auth/.github/.OwlBot.lock.yaml @@ -1,3 +1,3 @@ docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:58f73ba196b5414782605236dd0712a73541b44ff2ff4d3a36ec41092dd6fa5b + digest: sha256:ec49167c606648a063d1222220b48119c912562849a0528f35bfb592a9f72737 diff --git a/packages/google-auth/.kokoro/docs/common.cfg b/packages/google-auth/.kokoro/docs/common.cfg index 24c8c89dd4d8..980bff5db259 100644 --- a/packages/google-auth/.kokoro/docs/common.cfg +++ b/packages/google-auth/.kokoro/docs/common.cfg @@ -30,7 +30,9 @@ env_vars: { env_vars: { key: "V2_STAGING_BUCKET" - value: "docs-staging-v2" + # Push non-cloud library docs to `docs-staging-v2-staging` instead of the + # Cloud RAD bucket `docs-staging-v2` + value: "docs-staging-v2-staging" } # It will upload the docker image after successful builds. From 727c7514436b4315f3397a68f3671e45fb41ae2e Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Tue, 26 Oct 2021 10:02:29 -0600 Subject: [PATCH 495/966] chore: update authorized_user.json and use latest sphinx (#900) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: update authorized_user.json * chore: use latest sphinx * 🦉 Updates from OwlBot See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md Co-authored-by: Owl Bot --- packages/google-auth/noxfile.py | 4 +--- .../google-auth/system_tests/secrets.tar.enc | Bin 10323 -> 10323 bytes 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index 885dbd61aa97..efb367e83891 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -137,9 +137,7 @@ def docs(session): """Build the docs for this library.""" session.install("-e", ".[aiohttp]") - session.install( - "sphinx<3.0.0", "alabaster", "recommonmark", "sphinx-docstring-typing" - ) + session.install("sphinx", "alabaster", "recommonmark", "sphinx-docstring-typing") shutil.rmtree(os.path.join("docs", "_build"), ignore_errors=True) session.run( diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 58c59eda8443c6776e4a9a9e697e578db45f60de..3f42f879f93d60a1afd83a5af45a11fb4833f5e5 100644 GIT binary patch literal 10323 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 literal 10323 zcmV-ZD6H2CBmnkJRTIQi-bEyFwp+bqGc@7kfM-BvGKhz%nsjg--YGs~DiW$t0LRP_ zj)Bi*dN^Fa;de*`S)4`vV|a(A$)%IxW#Go|rbd0aS0E|V>zOF`Xy=lfddnV)hS#UN z&sCcZq%5-SwW;G$r%Wh}@n+JuvaH+4W4Oj`^�PXxI>;sy0AEOFB;eY>s^{*1t2{ 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 Date: Tue, 26 Oct 2021 11:04:38 -0700 Subject: [PATCH 496/966] chore: release 2.3.2 (#897) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> Co-authored-by: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 310f6afd18af..bcda51152628 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +### [2.3.2](https://www.github.com/googleapis/google-auth-library-python/compare/v2.3.1...v2.3.2) (2021-10-26) + + +### Bug Fixes + +* add clock_skew_in_seconds to verify_token functions ([#894](https://www.github.com/googleapis/google-auth-library-python/issues/894)) ([8e95c1e](https://www.github.com/googleapis/google-auth-library-python/commit/8e95c1e458793593972b6b05a355aaeaecd31670)) + ### [2.3.1](https://www.github.com/googleapis/google-auth-library-python/compare/v2.3.0...v2.3.1) (2021-10-21) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 7ca77790d5f9..cd24dc54a35b 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.3.1" +__version__ = "2.3.2" From 16def815f4630315b044d830673afdc2005c8d20 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Fri, 29 Oct 2021 11:59:01 -0700 Subject: [PATCH 497/966] fix: add fetch_id_token_credentials (#866) --- .../google-auth/google/oauth2/id_token.py | 75 +++++++++++++--- .../google-auth/tests/oauth2/test_id_token.py | 88 +++++++++++-------- 2 files changed, 116 insertions(+), 47 deletions(-) diff --git a/packages/google-auth/google/oauth2/id_token.py b/packages/google-auth/google/oauth2/id_token.py index 20d3ac1aff7f..74899ae55382 100644 --- a/packages/google-auth/google/oauth2/id_token.py +++ b/packages/google-auth/google/oauth2/id_token.py @@ -64,6 +64,7 @@ from google.auth import environment_vars from google.auth import exceptions from google.auth import jwt +import google.auth.transport.requests # The URL that provides public certificates for verifying ID tokens issued @@ -201,8 +202,8 @@ def verify_firebase_token(id_token, request, audience=None, clock_skew_in_second ) -def fetch_id_token(request, audience): - """Fetch the ID Token from the current environment. +def fetch_id_token_credentials(audience, request=None): + """Create the ID Token credentials from the current environment. This function acquires ID token from the environment in the following order. See https://google.aip.dev/auth/4110. @@ -224,15 +225,22 @@ def fetch_id_token(request, audience): request = google.auth.transport.requests.Request() target_audience = "https://pubsub.googleapis.com" - id_token = google.oauth2.id_token.fetch_id_token(request, target_audience) + # Create ID token credentials. + credentials = google.oauth2.id_token.fetch_id_token_credentials(target_audience, request=request) + + # Refresh the credential to obtain an ID token. + credentials.refresh(request) + + id_token = credentials.token + id_token_expiry = credentials.expiry Args: - request (google.auth.transport.Request): A callable used to make - HTTP requests. audience (str): The audience that this ID token is intended for. + request (Optional[google.auth.transport.Request]): A callable used to make + HTTP requests. A request object will be created if not provided. Returns: - str: The ID token. + google.auth.credentials.Credentials: The ID token credentials. Raises: ~google.auth.exceptions.DefaultCredentialsError: @@ -257,11 +265,9 @@ def fetch_id_token(request, audience): info = json.load(f) if info.get("type") == "service_account": - credentials = service_account.IDTokenCredentials.from_service_account_info( + return service_account.IDTokenCredentials.from_service_account_info( info, target_audience=audience ) - credentials.refresh(request) - return credentials.token except ValueError as caught_exc: new_exc = exceptions.DefaultCredentialsError( "GOOGLE_APPLICATION_CREDENTIALS is not valid service account credentials.", @@ -275,15 +281,60 @@ def fetch_id_token(request, audience): from google.auth import compute_engine from google.auth.compute_engine import _metadata + # Create a request object if not provided. + if not request: + request = google.auth.transport.requests.Request() + if _metadata.ping(request): - credentials = compute_engine.IDTokenCredentials( + return compute_engine.IDTokenCredentials( request, audience, use_metadata_identity_endpoint=True ) - credentials.refresh(request) - return credentials.token except (ImportError, exceptions.TransportError): pass raise exceptions.DefaultCredentialsError( "Neither metadata server or valid service account credentials are found." ) + + +def fetch_id_token(request, audience): + """Fetch the ID Token from the current environment. + + This function acquires ID token from the environment in the following order. + See https://google.aip.dev/auth/4110. + + 1. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set + to the path of a valid service account JSON file, then ID token is + acquired using this service account credentials. + 2. If the application is running in Compute Engine, App Engine or Cloud Run, + then the ID token are obtained from the metadata server. + 3. If metadata server doesn't exist and no valid service account credentials + are found, :class:`~google.auth.exceptions.DefaultCredentialsError` will + be raised. + + Example:: + + import google.oauth2.id_token + import google.auth.transport.requests + + request = google.auth.transport.requests.Request() + target_audience = "https://pubsub.googleapis.com" + + id_token = google.oauth2.id_token.fetch_id_token(request, target_audience) + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + audience (str): The audience that this ID token is intended for. + + Returns: + str: The ID token. + + Raises: + ~google.auth.exceptions.DefaultCredentialsError: + If metadata server doesn't exist and no valid service account + credentials are found. + """ + id_token_credentials = fetch_id_token_credentials(audience, request=request) + id_token_credentials.refresh(request) + return id_token_credentials.token diff --git a/packages/google-auth/tests/oauth2/test_id_token.py b/packages/google-auth/tests/oauth2/test_id_token.py index a612c58fecb3..ccfaaaf8cfb5 100644 --- a/packages/google-auth/tests/oauth2/test_id_token.py +++ b/packages/google-auth/tests/oauth2/test_id_token.py @@ -21,13 +21,13 @@ from google.auth import environment_vars from google.auth import exceptions from google.auth import transport -import google.auth.compute_engine._metadata from google.oauth2 import id_token from google.oauth2 import service_account SERVICE_ACCOUNT_FILE = os.path.join( os.path.dirname(__file__), "../data/service_account.json" ) +ID_TOKEN_AUDIENCE = "https://pubsub.googleapis.com" def make_request(status, data=None): @@ -201,37 +201,45 @@ def test_verify_firebase_token_clock_skew(verify_token): ) -def test_fetch_id_token_from_metadata_server(monkeypatch): +def test_fetch_id_token_credentials_optional_request(monkeypatch): monkeypatch.delenv(environment_vars.CREDENTIALS, raising=False) - def mock_init(self, request, audience, use_metadata_identity_endpoint): - assert use_metadata_identity_endpoint - self.token = "id_token" - + # Test a request object is created if not provided with mock.patch("google.auth.compute_engine._metadata.ping", return_value=True): - with mock.patch.multiple( - google.auth.compute_engine.IDTokenCredentials, - __init__=mock_init, - refresh=mock.Mock(), + with mock.patch( + "google.auth.compute_engine.IDTokenCredentials.__init__", return_value=None ): - request = mock.Mock() - token = id_token.fetch_id_token(request, "https://pubsub.googleapis.com") - assert token == "id_token" + with mock.patch( + "google.auth.transport.requests.Request.__init__", return_value=None + ) as mock_request: + id_token.fetch_id_token_credentials(ID_TOKEN_AUDIENCE) + mock_request.assert_called() -def test_fetch_id_token_from_explicit_cred_json_file(monkeypatch): - monkeypatch.setenv(environment_vars.CREDENTIALS, SERVICE_ACCOUNT_FILE) +def test_fetch_id_token_credentials_from_metadata_server(monkeypatch): + monkeypatch.delenv(environment_vars.CREDENTIALS, raising=False) + + mock_req = mock.Mock() + + with mock.patch("google.auth.compute_engine._metadata.ping", return_value=True): + with mock.patch( + "google.auth.compute_engine.IDTokenCredentials.__init__", return_value=None + ) as mock_init: + id_token.fetch_id_token_credentials(ID_TOKEN_AUDIENCE, request=mock_req) + mock_init.assert_called_once_with( + mock_req, ID_TOKEN_AUDIENCE, use_metadata_identity_endpoint=True + ) - def mock_refresh(self, request): - self.token = "id_token" - with mock.patch.object(service_account.IDTokenCredentials, "refresh", mock_refresh): - request = mock.Mock() - token = id_token.fetch_id_token(request, "https://pubsub.googleapis.com") - assert token == "id_token" +def test_fetch_id_token_credentials_from_explicit_cred_json_file(monkeypatch): + monkeypatch.setenv(environment_vars.CREDENTIALS, SERVICE_ACCOUNT_FILE) + + cred = id_token.fetch_id_token_credentials(ID_TOKEN_AUDIENCE) + assert isinstance(cred, service_account.IDTokenCredentials) + assert cred._target_audience == ID_TOKEN_AUDIENCE -def test_fetch_id_token_no_cred_exists(monkeypatch): +def test_fetch_id_token_credentials_no_cred_exists(monkeypatch): monkeypatch.delenv(environment_vars.CREDENTIALS, raising=False) with mock.patch( @@ -239,22 +247,20 @@ def test_fetch_id_token_no_cred_exists(monkeypatch): side_effect=exceptions.TransportError(), ): with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: - request = mock.Mock() - id_token.fetch_id_token(request, "https://pubsub.googleapis.com") + id_token.fetch_id_token_credentials(ID_TOKEN_AUDIENCE) assert excinfo.match( r"Neither metadata server or valid service account credentials are found." ) with mock.patch("google.auth.compute_engine._metadata.ping", return_value=False): with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: - request = mock.Mock() - id_token.fetch_id_token(request, "https://pubsub.googleapis.com") + id_token.fetch_id_token_credentials(ID_TOKEN_AUDIENCE) assert excinfo.match( r"Neither metadata server or valid service account credentials are found." ) -def test_fetch_id_token_invalid_cred_file_type(monkeypatch): +def test_fetch_id_token_credentials_invalid_cred_file_type(monkeypatch): user_credentials_file = os.path.join( os.path.dirname(__file__), "../data/authorized_user.json" ) @@ -262,32 +268,44 @@ def test_fetch_id_token_invalid_cred_file_type(monkeypatch): with mock.patch("google.auth.compute_engine._metadata.ping", return_value=False): with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: - request = mock.Mock() - id_token.fetch_id_token(request, "https://pubsub.googleapis.com") + id_token.fetch_id_token_credentials(ID_TOKEN_AUDIENCE) assert excinfo.match( r"Neither metadata server or valid service account credentials are found." ) -def test_fetch_id_token_invalid_json(monkeypatch): +def test_fetch_id_token_credentials_invalid_json(monkeypatch): not_json_file = os.path.join(os.path.dirname(__file__), "../data/public_cert.pem") monkeypatch.setenv(environment_vars.CREDENTIALS, not_json_file) with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: - request = mock.Mock() - id_token.fetch_id_token(request, "https://pubsub.googleapis.com") + id_token.fetch_id_token_credentials(ID_TOKEN_AUDIENCE) assert excinfo.match( r"GOOGLE_APPLICATION_CREDENTIALS is not valid service account credentials." ) -def test_fetch_id_token_invalid_cred_path(monkeypatch): +def test_fetch_id_token_credentials_invalid_cred_path(monkeypatch): not_json_file = os.path.join(os.path.dirname(__file__), "../data/not_exists.json") monkeypatch.setenv(environment_vars.CREDENTIALS, not_json_file) with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: - request = mock.Mock() - id_token.fetch_id_token(request, "https://pubsub.googleapis.com") + id_token.fetch_id_token_credentials(ID_TOKEN_AUDIENCE) assert excinfo.match( r"GOOGLE_APPLICATION_CREDENTIALS path is either not found or invalid." ) + + +def test_fetch_id_token(monkeypatch): + mock_cred = mock.MagicMock() + mock_cred.token = "token" + + mock_req = mock.Mock() + + with mock.patch( + "google.oauth2.id_token.fetch_id_token_credentials", return_value=mock_cred + ) as mock_fetch: + token = id_token.fetch_id_token(mock_req, ID_TOKEN_AUDIENCE) + mock_fetch.assert_called_once_with(ID_TOKEN_AUDIENCE, request=mock_req) + mock_cred.refresh.assert_called_once_with(mock_req) + assert token == "token" From 0be9e34ae818fa3d3d3a266d965d6cdaae404509 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Mon, 1 Nov 2021 10:51:09 -0600 Subject: [PATCH 498/966] chore: update authorized_user.json (#906) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10323 -> 10323 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 3f42f879f93d60a1afd83a5af45a11fb4833f5e5..5f20b1e4ccdcbe19f2cc6aff26157fee83079100 100644 GIT binary patch literal 10323 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 literal 10323 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 From f04173562403661f6ae12645f71ec5792770d5c7 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Mon, 1 Nov 2021 10:22:26 -0700 Subject: [PATCH 499/966] fix: use 'int.to_bytes' and 'int.from_bytes' for py3 (#904) --- packages/google-auth/google/auth/_helpers.py | 10 ++++++++++ .../google-auth/google/auth/crypt/es256.py | 18 +++++++++++++++--- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/packages/google-auth/google/auth/_helpers.py b/packages/google-auth/google/auth/_helpers.py index b239fcd4f4e5..1b08ab87f83c 100644 --- a/packages/google-auth/google/auth/_helpers.py +++ b/packages/google-auth/google/auth/_helpers.py @@ -17,6 +17,7 @@ import base64 import calendar import datetime +import sys import six from six.moves import urllib @@ -233,3 +234,12 @@ def unpadded_urlsafe_b64encode(value): Union[str|bytes]: The encoded value """ return base64.urlsafe_b64encode(value).rstrip(b"=") + + +def is_python_3(): + """Check if the Python interpreter is Python 2 or 3. + + Returns: + bool: True if the Python interpreter is Python 3 and False otherwise. + """ + return sys.version_info > (3, 0) diff --git a/packages/google-auth/google/auth/crypt/es256.py b/packages/google-auth/google/auth/crypt/es256.py index c6d617606728..42823a7a5ae2 100644 --- a/packages/google-auth/google/auth/crypt/es256.py +++ b/packages/google-auth/google/auth/crypt/es256.py @@ -53,8 +53,16 @@ def verify(self, message, signature): sig_bytes = _helpers.to_bytes(signature) if len(sig_bytes) != 64: return False - r = utils.int_from_bytes(sig_bytes[:32], byteorder="big") - s = utils.int_from_bytes(sig_bytes[32:], byteorder="big") + r = ( + int.from_bytes(sig_bytes[:32], byteorder="big") + if _helpers.is_python_3() + else utils.int_from_bytes(sig_bytes[:32], byteorder="big") + ) + s = ( + int.from_bytes(sig_bytes[32:], byteorder="big") + if _helpers.is_python_3() + else utils.int_from_bytes(sig_bytes[32:], byteorder="big") + ) asn1_sig = encode_dss_signature(r, s) message = _helpers.to_bytes(message) @@ -121,7 +129,11 @@ def sign(self, message): # Convert ASN1 encoded signature to (r||s) raw signature. (r, s) = decode_dss_signature(asn1_signature) - return utils.int_to_bytes(r, 32) + utils.int_to_bytes(s, 32) + return ( + (r.to_bytes(32, byteorder="big") + s.to_bytes(32, byteorder="big")) + if _helpers.is_python_3() + else (utils.int_to_bytes(r, 32) + utils.int_to_bytes(s, 32)) + ) @classmethod def from_string(cls, key, key_id=None): From de9764c07c43c5890e588252c3f4415ac1c5cd82 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Mon, 1 Nov 2021 13:10:17 -0700 Subject: [PATCH 500/966] fix: fix error in sign_bytes (#905) * fix: fix error in sign_bytes * fix test --- packages/google-auth/.coveragerc | 1 + .../google/auth/impersonated_credentials.py | 5 +++++ .../tests/test_impersonated_credentials.py | 13 +++++++++++++ 3 files changed, 19 insertions(+) diff --git a/packages/google-auth/.coveragerc b/packages/google-auth/.coveragerc index 494c03f07220..9ba3d3fe66a0 100644 --- a/packages/google-auth/.coveragerc +++ b/packages/google-auth/.coveragerc @@ -5,6 +5,7 @@ branch = True omit = */samples/* */conftest.py + */google-cloud-sdk/lib/* exclude_lines = # Re-enable the standard pragma pragma: NO COVER diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index b8a6c49a1eba..80d6fdfdcd13 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -290,6 +290,11 @@ def sign_bytes(self, message): url=iam_sign_endpoint, headers=headers, json=body ) + if response.status_code != http_client.OK: + raise exceptions.TransportError( + "Error calling sign_bytes: {}".format(response.json()) + ) + return base64.b64decode(response.json()["signedBlob"]) @property diff --git a/packages/google-auth/tests/test_impersonated_credentials.py b/packages/google-auth/tests/test_impersonated_credentials.py index bceaebaa5d66..bc404e36b78e 100644 --- a/packages/google-auth/tests/test_impersonated_credentials.py +++ b/packages/google-auth/tests/test_impersonated_credentials.py @@ -345,6 +345,19 @@ def test_sign_bytes(self, mock_donor_credentials, mock_authorizedsession_sign): signature = credentials.sign_bytes(b"signed bytes") assert signature == b"signature" + def test_sign_bytes_failure(self): + credentials = self.make_credentials(lifetime=None) + + with mock.patch( + "google.auth.transport.requests.AuthorizedSession.request", autospec=True + ) as auth_session: + data = {"error": {"code": 403, "message": "unauthorized"}} + auth_session.return_value = MockResponse(data, http_client.FORBIDDEN) + + with pytest.raises(exceptions.TransportError) as excinfo: + credentials.sign_bytes(b"foo") + assert excinfo.match("'code': 403") + def test_with_quota_project(self): credentials = self.make_credentials() From c4fc33a23ccf316ec0a112372595ad810f80decd Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 1 Nov 2021 20:30:12 +0000 Subject: [PATCH 501/966] chore: release 2.3.3 (#903) :robot: I have created a release \*beep\* \*boop\* --- ### [2.3.3](https://www.github.com/googleapis/google-auth-library-python/compare/v2.3.2...v2.3.3) (2021-11-01) ### Bug Fixes * add fetch_id_token_credentials ([#866](https://www.github.com/googleapis/google-auth-library-python/issues/866)) ([8f1e9cf](https://www.github.com/googleapis/google-auth-library-python/commit/8f1e9cfd56dbaae0dff64499e1d0cf55abc5b97e)) * fix error in sign_bytes ([#905](https://www.github.com/googleapis/google-auth-library-python/issues/905)) ([ef31284](https://www.github.com/googleapis/google-auth-library-python/commit/ef3128474431b07d1d519209ea61622bc245ce91)) * use 'int.to_bytes' and 'int.from_bytes' for py3 ([#904](https://www.github.com/googleapis/google-auth-library-python/issues/904)) ([bd0ccc5](https://www.github.com/googleapis/google-auth-library-python/commit/bd0ccc5fe77d55f7a19f5278d6b60587c393ee3c)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --- packages/google-auth/CHANGELOG.md | 9 +++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index bcda51152628..73440f7e38e8 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,15 @@ [1]: https://pypi.org/project/google-auth/#history +### [2.3.3](https://www.github.com/googleapis/google-auth-library-python/compare/v2.3.2...v2.3.3) (2021-11-01) + + +### Bug Fixes + +* add fetch_id_token_credentials ([#866](https://www.github.com/googleapis/google-auth-library-python/issues/866)) ([8f1e9cf](https://www.github.com/googleapis/google-auth-library-python/commit/8f1e9cfd56dbaae0dff64499e1d0cf55abc5b97e)) +* fix error in sign_bytes ([#905](https://www.github.com/googleapis/google-auth-library-python/issues/905)) ([ef31284](https://www.github.com/googleapis/google-auth-library-python/commit/ef3128474431b07d1d519209ea61622bc245ce91)) +* use 'int.to_bytes' and 'int.from_bytes' for py3 ([#904](https://www.github.com/googleapis/google-auth-library-python/issues/904)) ([bd0ccc5](https://www.github.com/googleapis/google-auth-library-python/commit/bd0ccc5fe77d55f7a19f5278d6b60587c393ee3c)) + ### [2.3.2](https://www.github.com/googleapis/google-auth-library-python/compare/v2.3.1...v2.3.2) (2021-10-26) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index cd24dc54a35b..ad9a0c7a4f75 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.3.2" +__version__ = "2.3.3" From 971e93867a95a97380dcb2a79ad9a63116411954 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Tue, 9 Nov 2021 09:44:50 -0700 Subject: [PATCH 502/966] chore: refresh user token (#915) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10323 -> 10323 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 5f20b1e4ccdcbe19f2cc6aff26157fee83079100..9dae4398fbf3723433b9e899fa53ffa21fc5c947 100644 GIT binary patch literal 10323 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 literal 10323 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 From e4062fd78b093e4407b566a844130eeb30d6759a Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Tue, 9 Nov 2021 12:07:48 -0500 Subject: [PATCH 503/966] chore: drop 'setuptools' dependency (#913) Closes #595. --- packages/google-auth/setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 301e99643172..44c5121281e7 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -28,7 +28,6 @@ 'rsa>=3.1.4,<5; python_version >= "3.6"', # install enum34 to support 2.7. enum34 only works up to python version 3.3. 'enum34>=1.1.10; python_version < "3.4"', - "setuptools>=40.3.0", "six>=1.9.0", ) From a319e08d03c2cdf87fa7045ade7a181fcd1298a6 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Tue, 9 Nov 2021 12:28:20 -0500 Subject: [PATCH 504/966] chore: drop 'docgen' nox session (#914) No longer run by default, and leaves the environment fouled. Closes #636. --- packages/google-auth/noxfile.py | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index efb367e83891..8c22b7a778dd 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -115,23 +115,6 @@ def cover(session): session.run("coverage", "report", "--show-missing", "--fail-under=100") -@nox.session(python="3.7") -def docgen(session): - session.env["SPHINX_APIDOC_OPTIONS"] = "members,inherited-members,show-inheritance" - session.install("-r", "testing/requirements.txt") - session.install("sphinx") - session.install("-e", ".") - session.run("rm", "-r", "docs/reference") - session.run( - "sphinx-apidoc", - "--output-dir", - "docs/reference", - "--separate", - "--module-first", - "google", - ) - - @nox.session(python="3.8") def docs(session): """Build the docs for this library.""" From 1c7041ee7062a72343f9894f762d34fb484d1011 Mon Sep 17 00:00:00 2001 From: Hao Xin Date: Wed, 10 Nov 2021 23:58:17 +0800 Subject: [PATCH 505/966] fix: fix the message format for metadata server exception (#916) ``` RefreshError: ("Failed to ... from the Google Compute Enginemetadata service. Status: 403 ...) ``` --- .../google-auth/google/auth/compute_engine/_metadata.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/google-auth/google/auth/compute_engine/_metadata.py b/packages/google-auth/google/auth/compute_engine/_metadata.py index 9db7bea92d49..d57c22a15de0 100644 --- a/packages/google-auth/google/auth/compute_engine/_metadata.py +++ b/packages/google-auth/google/auth/compute_engine/_metadata.py @@ -161,7 +161,7 @@ def get( retries += 1 else: raise exceptions.TransportError( - "Failed to retrieve {} from the Google Compute Engine" + "Failed to retrieve {} from the Google Compute Engine " "metadata service. Compute Engine Metadata server unavailable".format(url) ) @@ -172,7 +172,7 @@ def get( return json.loads(content) except ValueError as caught_exc: new_exc = exceptions.TransportError( - "Received invalid JSON from the Google Compute Engine" + "Received invalid JSON from the Google Compute Engine " "metadata service: {:.20}".format(content) ) six.raise_from(new_exc, caught_exc) @@ -180,7 +180,7 @@ def get( return content else: raise exceptions.TransportError( - "Failed to retrieve {} from the Google Compute Engine" + "Failed to retrieve {} from the Google Compute Engine " "metadata service. Status: {} Response:\n{}".format( url, response.status, response.data ), From 19b5b73861afc3d4c9f36a31a1f2b2617d6d10cc Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Thu, 18 Nov 2021 10:10:18 -0500 Subject: [PATCH 506/966] docs: fix intersphinx link for 'requests-oauthlib' (#921) Closes #920. --- packages/google-auth/docs/conf.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/docs/conf.py b/packages/google-auth/docs/conf.py index 58e5b9a99ef7..652d808bdc7c 100644 --- a/packages/google-auth/docs/conf.py +++ b/packages/google-auth/docs/conf.py @@ -363,7 +363,10 @@ "python": ("https://docs.python.org/3.5", None), "urllib3": ("https://urllib3.readthedocs.io/en/stable", None), "requests": ("https://requests.kennethreitz.org/en/master/", None), - "requests-oauthlib": ("https://requests-oauthlib.readthedocs.io/en/stable/", None), + "requests-oauthlib": ( + "https://requests-oauthlib.readthedocs.io/en/v1.3.0-docs/", + None, + ), } # Autodoc config From f97a5c28594157d5cd12df032f979d10ee62c08c Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Thu, 18 Nov 2021 13:08:43 -0700 Subject: [PATCH 507/966] chore: update refresh token (#923) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10323 -> 10323 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 9dae4398fbf3723433b9e899fa53ffa21fc5c947..ebd9e696a1ceac2ef15016f1f038ea16d84fcdec 100644 GIT binary patch literal 10323 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 literal 10323 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 From e673b825166fe2b5f8a444541792e7ab569e94c0 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Tue, 28 Dec 2021 18:47:38 -0500 Subject: [PATCH 508/966] chore: update release_level in repo-metadata.json (#933) * chore: update .repo-metadata.json * remove api_shortname * revert --- packages/google-auth/.repo-metadata.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/google-auth/.repo-metadata.json b/packages/google-auth/.repo-metadata.json index 9d799aa38aff..f6a3d96a0c97 100644 --- a/packages/google-auth/.repo-metadata.json +++ b/packages/google-auth/.repo-metadata.json @@ -1,11 +1,11 @@ { - "name": "google-auth", - "name_pretty": "Google Auth Python Library", - "client_documentation": "https://googleapis.dev/python/google-auth/latest", - "issue_tracker": "https://github.com/googleapis/google-auth-library-python/issues", - "release_level": "ga", - "language": "python", - "library_type": "AUTH", - "repo": "googleapis/google-auth-library-python", - "distribution_name": "google-auth" + "name": "google-auth", + "name_pretty": "Google Auth Python Library", + "client_documentation": "https://googleapis.dev/python/google-auth/latest", + "issue_tracker": "https://github.com/googleapis/google-auth-library-python/issues", + "release_level": "stable", + "language": "python", + "library_type": "AUTH", + "repo": "googleapis/google-auth-library-python", + "distribution_name": "google-auth" } From 6c2530a69303276c08a7ad4a291dde284acab388 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Thu, 6 Jan 2022 03:09:50 -0800 Subject: [PATCH 509/966] chore: update system test user creds (#938) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10323 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index ebd9e696a1ceac2ef15016f1f038ea16d84fcdec..5df7af8cf8c9cb34326c284db0d98d431446f524 100644 GIT binary patch literal 10324 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 literal 10323 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 From 1e4a8e142c5f1fda2f06742f5632177173e37c55 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Thu, 6 Jan 2022 17:15:03 -0500 Subject: [PATCH 510/966] feat: add 'py.typed' declaration (#919) Test types under CI using new 'mypy' nox session. Co-authored-by: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> --- packages/google-auth/google/__init__.py | 2 +- packages/google-auth/google/auth/_jwt_async.py | 10 +++------- .../google-auth/google/auth/_oauth2client.py | 8 ++++---- packages/google-auth/google/auth/app_engine.py | 8 ++++---- packages/google-auth/google/auth/aws.py | 7 +------ .../google/auth/compute_engine/credentials.py | 2 +- .../google-auth/google/auth/crypt/__init__.py | 2 +- .../google/auth/crypt/_cryptography_rsa.py | 2 +- .../google/auth/crypt/_python_rsa.py | 12 ++++++------ .../google-auth/google/auth/crypt/es256.py | 4 ++-- packages/google-auth/google/auth/crypt/rsa.py | 4 ++-- packages/google-auth/google/auth/jwt.py | 14 +++++++------- packages/google-auth/google/auth/py.typed | 2 ++ .../google/auth/transport/_aiohttp_requests.py | 4 ++-- .../google-auth/google/auth/transport/grpc.py | 2 +- .../google/auth/transport/requests.py | 2 +- .../google/auth/transport/urllib3.py | 13 ++++++------- .../google-auth/google/oauth2/challenges.py | 6 +++--- packages/google-auth/google/oauth2/py.typed | 2 ++ .../google/oauth2/service_account.py | 8 ++++---- packages/google-auth/mypy.ini | 3 +++ packages/google-auth/noxfile.py | 18 ++++++++++++++++++ .../tests/compute_engine/test__metadata.py | 2 +- .../tests/compute_engine/test_credentials.py | 4 ++-- packages/google-auth/tests/conftest.py | 2 +- .../tests/crypt/test__cryptography_rsa.py | 4 ++-- .../tests/crypt/test__python_rsa.py | 8 ++++---- packages/google-auth/tests/crypt/test_es256.py | 4 ++-- .../google-auth/tests/oauth2/test__client.py | 2 +- .../tests/oauth2/test_challenges.py | 4 ++-- .../tests/oauth2/test_credentials.py | 2 +- .../google-auth/tests/oauth2/test_id_token.py | 2 +- .../google-auth/tests/oauth2/test_reauth.py | 2 +- .../tests/oauth2/test_service_account.py | 2 +- packages/google-auth/tests/oauth2/test_sts.py | 2 +- .../google-auth/tests/oauth2/test_utils.py | 2 +- packages/google-auth/tests/test__cloud_sdk.py | 8 ++++---- packages/google-auth/tests/test__default.py | 2 +- packages/google-auth/tests/test__helpers.py | 2 +- .../google-auth/tests/test__oauth2client.py | 10 +++++----- .../tests/test__service_account_info.py | 2 +- packages/google-auth/tests/test_app_engine.py | 2 +- packages/google-auth/tests/test_aws.py | 2 +- packages/google-auth/tests/test_credentials.py | 2 +- packages/google-auth/tests/test_downscoped.py | 2 +- .../google-auth/tests/test_external_account.py | 2 +- packages/google-auth/tests/test_iam.py | 2 +- .../google-auth/tests/test_identity_pool.py | 2 +- .../tests/test_impersonated_credentials.py | 11 ++++++++--- packages/google-auth/tests/test_jwt.py | 4 ++-- .../google-auth/tests/transport/compliance.py | 6 +++--- .../tests/transport/test__http_client.py | 2 +- .../tests/transport/test__mtls_helper.py | 4 +--- .../google-auth/tests/transport/test_grpc.py | 4 ++-- .../google-auth/tests/transport/test_mtls.py | 2 +- .../tests/transport/test_requests.py | 2 +- .../tests/transport/test_urllib3.py | 4 ++-- packages/google-auth/tests_async/conftest.py | 2 +- .../tests_async/oauth2/test__client_async.py | 2 +- .../oauth2/test_credentials_async.py | 2 +- .../tests_async/oauth2/test_id_token.py | 2 +- .../tests_async/oauth2/test_reauth_async.py | 2 +- .../oauth2/test_service_account_async.py | 2 +- .../tests_async/test__default_async.py | 2 +- .../tests_async/test_credentials_async.py | 2 +- .../google-auth/tests_async/test_jwt_async.py | 2 +- .../tests_async/transport/async_compliance.py | 6 +++--- .../transport/test_aiohttp_requests.py | 6 +++--- 68 files changed, 151 insertions(+), 133 deletions(-) create mode 100644 packages/google-auth/google/auth/py.typed create mode 100644 packages/google-auth/google/oauth2/py.typed create mode 100644 packages/google-auth/mypy.ini diff --git a/packages/google-auth/google/__init__.py b/packages/google-auth/google/__init__.py index 0d0a4c3ab273..70a7bd995f51 100644 --- a/packages/google-auth/google/__init__.py +++ b/packages/google-auth/google/__init__.py @@ -21,4 +21,4 @@ except ImportError: import pkgutil - __path__ = pkgutil.extend_path(__path__, __name__) + __path__ = pkgutil.extend_path(__path__, __name__) # type: ignore diff --git a/packages/google-auth/google/auth/_jwt_async.py b/packages/google-auth/google/auth/_jwt_async.py index 49e3026e5315..3a1abc5b85c9 100644 --- a/packages/google-auth/google/auth/_jwt_async.py +++ b/packages/google-auth/google/auth/_jwt_async.py @@ -43,7 +43,7 @@ change in minor releases. """ -import google.auth +from google.auth import _credentials_async from google.auth import jwt @@ -91,9 +91,7 @@ def decode(token, certs=None, verify=True, audience=None): class Credentials( - jwt.Credentials, - google.auth._credentials_async.Signing, - google.auth._credentials_async.Credentials, + jwt.Credentials, _credentials_async.Signing, _credentials_async.Credentials ): """Credentials that use a JWT as the bearer token. @@ -146,9 +144,7 @@ class Credentials( class OnDemandCredentials( - jwt.OnDemandCredentials, - google.auth._credentials_async.Signing, - google.auth._credentials_async.Credentials, + jwt.OnDemandCredentials, _credentials_async.Signing, _credentials_async.Credentials ): """On-demand JWT credentials. diff --git a/packages/google-auth/google/auth/_oauth2client.py b/packages/google-auth/google/auth/_oauth2client.py index 95a9876f3151..a86ba8dd69f2 100644 --- a/packages/google-auth/google/auth/_oauth2client.py +++ b/packages/google-auth/google/auth/_oauth2client.py @@ -30,14 +30,14 @@ import google.oauth2.service_account try: - import oauth2client.client - import oauth2client.contrib.gce - import oauth2client.service_account + import oauth2client.client # type: ignore + import oauth2client.contrib.gce # type: ignore + import oauth2client.service_account # type: ignore except ImportError as caught_exc: six.raise_from(ImportError("oauth2client is not installed."), caught_exc) try: - import oauth2client.contrib.appengine # pytype: disable=import-error + import oauth2client.contrib.appengine # type: ignore _HAS_APPENGINE = True except ImportError: diff --git a/packages/google-auth/google/auth/app_engine.py b/packages/google-auth/google/auth/app_engine.py index 81aef73b4506..1460a7d1ae0c 100644 --- a/packages/google-auth/google/auth/app_engine.py +++ b/packages/google-auth/google/auth/app_engine.py @@ -30,9 +30,9 @@ # pytype: disable=import-error try: - from google.appengine.api import app_identity + from google.appengine.api import app_identity # type: ignore except ImportError: - app_identity = None + app_identity = None # type: ignore # pytype: enable=import-error @@ -168,12 +168,12 @@ def with_quota_project(self, quota_project_id): def sign_bytes(self, message): return self._signer.sign(message) - @property + @property # type: ignore @_helpers.copy_docstring(credentials.Signing) def signer_email(self): return self.service_account_email - @property + @property # type: ignore @_helpers.copy_docstring(credentials.Signing) def signer(self): return self._signer diff --git a/packages/google-auth/google/auth/aws.py b/packages/google-auth/google/auth/aws.py index 925b1ddfbde1..2fd96d058ded 100644 --- a/packages/google-auth/google/auth/aws.py +++ b/packages/google-auth/google/auth/aws.py @@ -45,14 +45,9 @@ import posixpath import re -try: - from urllib.parse import urljoin -# Python 2.7 compatibility -except ImportError: # pragma: NO COVER - from urlparse import urljoin - from six.moves import http_client from six.moves import urllib +from six.moves.urllib.parse import urljoin from google.auth import _helpers from google.auth import environment_vars diff --git a/packages/google-auth/google/auth/compute_engine/credentials.py b/packages/google-auth/google/auth/compute_engine/credentials.py index b39ac50ae0ed..59b48dae68c9 100644 --- a/packages/google-auth/google/auth/compute_engine/credentials.py +++ b/packages/google-auth/google/auth/compute_engine/credentials.py @@ -379,7 +379,7 @@ def refresh(self, request): self.token = access_token self.expiry = expiry - @property + @property # type: ignore @_helpers.copy_docstring(credentials.Signing) def signer(self): return self._signer diff --git a/packages/google-auth/google/auth/crypt/__init__.py b/packages/google-auth/google/auth/crypt/__init__.py index 15ac95068625..9f91f0d0ba67 100644 --- a/packages/google-auth/google/auth/crypt/__init__.py +++ b/packages/google-auth/google/auth/crypt/__init__.py @@ -45,7 +45,7 @@ try: from google.auth.crypt import es256 except ImportError: # pragma: NO COVER - es256 = None + es256 = None # type: ignore if es256 is not None: # pragma: NO COVER __all__ = [ diff --git a/packages/google-auth/google/auth/crypt/_cryptography_rsa.py b/packages/google-auth/google/auth/crypt/_cryptography_rsa.py index 916c9d80a8ed..4f2d61166658 100644 --- a/packages/google-auth/google/auth/crypt/_cryptography_rsa.py +++ b/packages/google-auth/google/auth/crypt/_cryptography_rsa.py @@ -101,7 +101,7 @@ def __init__(self, private_key, key_id=None): self._key = private_key self._key_id = key_id - @property + @property # type: ignore @_helpers.copy_docstring(base.Signer) def key_id(self): return self._key_id diff --git a/packages/google-auth/google/auth/crypt/_python_rsa.py b/packages/google-auth/google/auth/crypt/_python_rsa.py index ec30dd09a37e..797a2592b84d 100644 --- a/packages/google-auth/google/auth/crypt/_python_rsa.py +++ b/packages/google-auth/google/auth/crypt/_python_rsa.py @@ -21,11 +21,11 @@ from __future__ import absolute_import -from pyasn1.codec.der import decoder -from pyasn1_modules import pem -from pyasn1_modules.rfc2459 import Certificate -from pyasn1_modules.rfc5208 import PrivateKeyInfo -import rsa +from pyasn1.codec.der import decoder # type: ignore +from pyasn1_modules import pem # type: ignore +from pyasn1_modules.rfc2459 import Certificate # type: ignore +from pyasn1_modules.rfc5208 import PrivateKeyInfo # type: ignore +import rsa # type: ignore import six from google.auth import _helpers @@ -125,7 +125,7 @@ def __init__(self, private_key, key_id=None): self._key = private_key self._key_id = key_id - @property + @property # type: ignore @_helpers.copy_docstring(base.Signer) def key_id(self): return self._key_id diff --git a/packages/google-auth/google/auth/crypt/es256.py b/packages/google-auth/google/auth/crypt/es256.py index 42823a7a5ae2..7920cc7ffba5 100644 --- a/packages/google-auth/google/auth/crypt/es256.py +++ b/packages/google-auth/google/auth/crypt/es256.py @@ -15,7 +15,7 @@ """ECDSA (ES256) verifier and signer that use the ``cryptography`` library. """ -from cryptography import utils +from cryptography import utils # type: ignore import cryptography.exceptions from cryptography.hazmat import backends from cryptography.hazmat.primitives import hashes @@ -117,7 +117,7 @@ def __init__(self, private_key, key_id=None): self._key = private_key self._key_id = key_id - @property + @property # type: ignore @_helpers.copy_docstring(base.Signer) def key_id(self): return self._key_id diff --git a/packages/google-auth/google/auth/crypt/rsa.py b/packages/google-auth/google/auth/crypt/rsa.py index 8b2d64c1034a..ed842d1eb8ef 100644 --- a/packages/google-auth/google/auth/crypt/rsa.py +++ b/packages/google-auth/google/auth/crypt/rsa.py @@ -26,5 +26,5 @@ # unavailable. from google.auth.crypt import _python_rsa - RSASigner = _python_rsa.RSASigner - RSAVerifier = _python_rsa.RSAVerifier + RSASigner = _python_rsa.RSASigner # type: ignore + RSAVerifier = _python_rsa.RSAVerifier # type: ignore diff --git a/packages/google-auth/google/auth/jwt.py b/packages/google-auth/google/auth/jwt.py index d56559510791..9d7b76e7370a 100644 --- a/packages/google-auth/google/auth/jwt.py +++ b/packages/google-auth/google/auth/jwt.py @@ -44,7 +44,7 @@ from collections.abc import Mapping # Python 2.7 compatibility except ImportError: # pragma: NO COVER - from collections import Mapping + from collections import Mapping # type: ignore import copy import datetime import json @@ -62,7 +62,7 @@ try: from google.auth.crypt import es256 except ImportError: # pragma: NO COVER - es256 = None + es256 = None # type: ignore _DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds _DEFAULT_MAX_CACHE_SIZE = 10 @@ -70,7 +70,7 @@ _CRYPTOGRAPHY_BASED_ALGORITHMS = frozenset(["ES256"]) if es256 is not None: # pragma: NO COVER - _ALGORITHM_TO_VERIFIER_CLASS["ES256"] = es256.ES256Verifier + _ALGORITHM_TO_VERIFIER_CLASS["ES256"] = es256.ES256Verifier # type: ignore def encode(signer, payload, header=None, key_id=None): @@ -557,12 +557,12 @@ def refresh(self, request): def sign_bytes(self, message): return self._signer.sign(message) - @property + @property # type: ignore @_helpers.copy_docstring(google.auth.credentials.Signing) def signer_email(self): return self._issuer - @property + @property # type: ignore @_helpers.copy_docstring(google.auth.credentials.Signing) def signer(self): return self._signer @@ -846,12 +846,12 @@ def before_request(self, request, method, url, headers): def sign_bytes(self, message): return self._signer.sign(message) - @property + @property # type: ignore @_helpers.copy_docstring(google.auth.credentials.Signing) def signer_email(self): return self._issuer - @property + @property # type: ignore @_helpers.copy_docstring(google.auth.credentials.Signing) def signer(self): return self._signer diff --git a/packages/google-auth/google/auth/py.typed b/packages/google-auth/google/auth/py.typed new file mode 100644 index 000000000000..aa7b68923f2b --- /dev/null +++ b/packages/google-auth/google/auth/py.typed @@ -0,0 +1,2 @@ +# Marker file for PEP 561. +# The google-auth package uses inline types. diff --git a/packages/google-auth/google/auth/transport/_aiohttp_requests.py b/packages/google-auth/google/auth/transport/_aiohttp_requests.py index ab7dfef67740..8a20e7abe1cc 100644 --- a/packages/google-auth/google/auth/transport/_aiohttp_requests.py +++ b/packages/google-auth/google/auth/transport/_aiohttp_requests.py @@ -23,9 +23,9 @@ import asyncio import functools -import aiohttp +import aiohttp # type: ignore import six -import urllib3 +import urllib3 # type: ignore from google.auth import exceptions from google.auth import transport diff --git a/packages/google-auth/google/auth/transport/grpc.py b/packages/google-auth/google/auth/transport/grpc.py index c47cb3ddafcd..87fa5042fdac 100644 --- a/packages/google-auth/google/auth/transport/grpc.py +++ b/packages/google-auth/google/auth/transport/grpc.py @@ -27,7 +27,7 @@ from google.oauth2 import service_account try: - import grpc + import grpc # type: ignore except ImportError as caught_exc: # pragma: NO COVER six.raise_from( ImportError( diff --git a/packages/google-auth/google/auth/transport/requests.py b/packages/google-auth/google/auth/transport/requests.py index 817176befa20..9c6f6c888be9 100644 --- a/packages/google-auth/google/auth/transport/requests.py +++ b/packages/google-auth/google/auth/transport/requests.py @@ -205,7 +205,7 @@ class _MutualTlsAdapter(requests.adapters.HTTPAdapter): def __init__(self, cert, key): import certifi from OpenSSL import crypto - import urllib3.contrib.pyopenssl + import urllib3.contrib.pyopenssl # type: ignore urllib3.contrib.pyopenssl.inject_into_urllib3() diff --git a/packages/google-auth/google/auth/transport/urllib3.py b/packages/google-auth/google/auth/transport/urllib3.py index 6a2504d972ef..ad67327a4870 100644 --- a/packages/google-auth/google/auth/transport/urllib3.py +++ b/packages/google-auth/google/auth/transport/urllib3.py @@ -29,13 +29,14 @@ try: import certifi except ImportError: # pragma: NO COVER - certifi = None + certifi = None # type: ignore + +import six try: - import urllib3 + import urllib3 # type: ignore + import urllib3.exceptions # type: ignore except ImportError as caught_exc: # pragma: NO COVER - import six - six.raise_from( ImportError( "The urllib3 library is not installed, please install the " @@ -43,8 +44,6 @@ ), caught_exc, ) -import six -import urllib3.exceptions # pylint: disable=ungrouped-imports from google.auth import environment_vars from google.auth import exceptions @@ -169,7 +168,7 @@ def _make_mutual_tls_http(cert, key): """ import certifi from OpenSSL import crypto - import urllib3.contrib.pyopenssl + import urllib3.contrib.pyopenssl # type: ignore urllib3.contrib.pyopenssl.inject_into_urllib3() ctx = urllib3.util.ssl_.create_urllib3_context() diff --git a/packages/google-auth/google/oauth2/challenges.py b/packages/google-auth/google/oauth2/challenges.py index 95e76cb3262b..bb523e6cafe3 100644 --- a/packages/google-auth/google/oauth2/challenges.py +++ b/packages/google-auth/google/oauth2/challenges.py @@ -114,9 +114,9 @@ def is_locally_eligible(self): @_helpers.copy_docstring(ReauthChallenge) def obtain_challenge_input(self, metadata): try: - import pyu2f.convenience.authenticator - import pyu2f.errors - import pyu2f.model + import pyu2f.convenience.authenticator # type: ignore + import pyu2f.errors # type: ignore + import pyu2f.model # type: ignore except ImportError: raise exceptions.ReauthFailError( "pyu2f dependency is required to use Security key reauth feature. " diff --git a/packages/google-auth/google/oauth2/py.typed b/packages/google-auth/google/oauth2/py.typed new file mode 100644 index 000000000000..d82ed62c2f05 --- /dev/null +++ b/packages/google-auth/google/oauth2/py.typed @@ -0,0 +1,2 @@ +# Marker file for PEP 561. +# The google-oauth2 package uses inline types. diff --git a/packages/google-auth/google/oauth2/service_account.py b/packages/google-auth/google/oauth2/service_account.py index ecaac038c1e5..5c4f340fa0a9 100644 --- a/packages/google-auth/google/oauth2/service_account.py +++ b/packages/google-auth/google/oauth2/service_account.py @@ -444,12 +444,12 @@ def _create_self_signed_jwt(self, audience): def sign_bytes(self, message): return self._signer.sign(message) - @property + @property # type: ignore @_helpers.copy_docstring(credentials.Signing) def signer(self): return self._signer - @property + @property # type: ignore @_helpers.copy_docstring(credentials.Signing) def signer_email(self): return self._service_account_email @@ -676,12 +676,12 @@ def service_account_email(self): def sign_bytes(self, message): return self._signer.sign(message) - @property + @property # type: ignore @_helpers.copy_docstring(credentials.Signing) def signer(self): return self._signer - @property + @property # type: ignore @_helpers.copy_docstring(credentials.Signing) def signer_email(self): return self._service_account_email diff --git a/packages/google-auth/mypy.ini b/packages/google-auth/mypy.ini new file mode 100644 index 000000000000..4505b485436b --- /dev/null +++ b/packages/google-auth/mypy.ini @@ -0,0 +1,3 @@ +[mypy] +python_version = 3.6 +namespace_packages = True diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index 8c22b7a778dd..1032be641e1f 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -62,6 +62,24 @@ def blacken(session): session.run("black", *BLACK_PATHS) +@nox.session(python="3.6") +def mypy(session): + """Verify type hints are mypy compatible.""" + session.install("-e", ".") + session.install( + "mypy", + "types-cachetools", + "types-certifi", + "types-freezegun", + "types-pyOpenSSL", + "types-requests", + "types-setuptools", + "types-six", + "types-mock", + ) + session.run("mypy", "google/", "tests/", "tests_async/") + + @nox.session(python=["3.6", "3.7", "3.8", "3.9", "3.10"]) def unit(session): constraints_path = str( diff --git a/packages/google-auth/tests/compute_engine/test__metadata.py b/packages/google-auth/tests/compute_engine/test__metadata.py index 852822dc0c44..568812056a9f 100644 --- a/packages/google-auth/tests/compute_engine/test__metadata.py +++ b/packages/google-auth/tests/compute_engine/test__metadata.py @@ -17,7 +17,7 @@ import os import mock -import pytest +import pytest # type: ignore from six.moves import http_client from six.moves import reload_module diff --git a/packages/google-auth/tests/compute_engine/test_credentials.py b/packages/google-auth/tests/compute_engine/test_credentials.py index 81cc6db3130b..ff01720c460d 100644 --- a/packages/google-auth/tests/compute_engine/test_credentials.py +++ b/packages/google-auth/tests/compute_engine/test_credentials.py @@ -15,8 +15,8 @@ import datetime import mock -import pytest -import responses +import pytest # type: ignore +import responses # type: ignore from google.auth import _helpers from google.auth import exceptions diff --git a/packages/google-auth/tests/conftest.py b/packages/google-auth/tests/conftest.py index cf8a0f9e552c..8080ec3fa26a 100644 --- a/packages/google-auth/tests/conftest.py +++ b/packages/google-auth/tests/conftest.py @@ -16,7 +16,7 @@ import sys import mock -import pytest +import pytest # type: ignore def pytest_configure(): diff --git a/packages/google-auth/tests/crypt/test__cryptography_rsa.py b/packages/google-auth/tests/crypt/test__cryptography_rsa.py index dbf07c7805b3..99d8fc37c5d5 100644 --- a/packages/google-auth/tests/crypt/test__cryptography_rsa.py +++ b/packages/google-auth/tests/crypt/test__cryptography_rsa.py @@ -16,7 +16,7 @@ import os from cryptography.hazmat.primitives.asymmetric import rsa -import pytest +import pytest # type: ignore from google.auth import _helpers from google.auth.crypt import _cryptography_rsa @@ -55,7 +55,7 @@ # The service account JSON file can be generated from the Google Cloud Console. SERVICE_ACCOUNT_JSON_FILE = os.path.join(DATA_DIR, "service_account.json") -with open(SERVICE_ACCOUNT_JSON_FILE, "r") as fh: +with open(SERVICE_ACCOUNT_JSON_FILE, "rb") as fh: SERVICE_ACCOUNT_INFO = json.load(fh) diff --git a/packages/google-auth/tests/crypt/test__python_rsa.py b/packages/google-auth/tests/crypt/test__python_rsa.py index 886ee55a2388..9d832f044f73 100644 --- a/packages/google-auth/tests/crypt/test__python_rsa.py +++ b/packages/google-auth/tests/crypt/test__python_rsa.py @@ -16,9 +16,9 @@ import os import mock -from pyasn1_modules import pem -import pytest -import rsa +from pyasn1_modules import pem # type: ignore +import pytest # type: ignore +import rsa # type: ignore import six from google.auth import _helpers @@ -58,7 +58,7 @@ # The service account JSON file can be generated from the Google Cloud Console. SERVICE_ACCOUNT_JSON_FILE = os.path.join(DATA_DIR, "service_account.json") -with open(SERVICE_ACCOUNT_JSON_FILE, "r") as fh: +with open(SERVICE_ACCOUNT_JSON_FILE, "rb") as fh: SERVICE_ACCOUNT_INFO = json.load(fh) diff --git a/packages/google-auth/tests/crypt/test_es256.py b/packages/google-auth/tests/crypt/test_es256.py index 5bb9050cd8d7..33465ce6d11d 100644 --- a/packages/google-auth/tests/crypt/test_es256.py +++ b/packages/google-auth/tests/crypt/test_es256.py @@ -17,7 +17,7 @@ import os from cryptography.hazmat.primitives.asymmetric import ec -import pytest +import pytest # type: ignore from google.auth import _helpers from google.auth.crypt import base @@ -45,7 +45,7 @@ SERVICE_ACCOUNT_JSON_FILE = os.path.join(DATA_DIR, "es256_service_account.json") -with open(SERVICE_ACCOUNT_JSON_FILE, "r") as fh: +with open(SERVICE_ACCOUNT_JSON_FILE, "rb") as fh: SERVICE_ACCOUNT_INFO = json.load(fh) diff --git a/packages/google-auth/tests/oauth2/test__client.py b/packages/google-auth/tests/oauth2/test__client.py index 54686df594bd..5485bed84e56 100644 --- a/packages/google-auth/tests/oauth2/test__client.py +++ b/packages/google-auth/tests/oauth2/test__client.py @@ -17,7 +17,7 @@ import os import mock -import pytest +import pytest # type: ignore import six from six.moves import http_client from six.moves import urllib diff --git a/packages/google-auth/tests/oauth2/test_challenges.py b/packages/google-auth/tests/oauth2/test_challenges.py index 412895adaf27..9e35d88afecf 100644 --- a/packages/google-auth/tests/oauth2/test_challenges.py +++ b/packages/google-auth/tests/oauth2/test_challenges.py @@ -18,8 +18,8 @@ import sys import mock -import pytest -import pyu2f +import pytest # type: ignore +import pyu2f # type: ignore from google.auth import exceptions from google.oauth2 import challenges diff --git a/packages/google-auth/tests/oauth2/test_credentials.py b/packages/google-auth/tests/oauth2/test_credentials.py index 243f97de8d81..e5f71def04cb 100644 --- a/packages/google-auth/tests/oauth2/test_credentials.py +++ b/packages/google-auth/tests/oauth2/test_credentials.py @@ -19,7 +19,7 @@ import sys import mock -import pytest +import pytest # type: ignore from google.auth import _helpers from google.auth import exceptions diff --git a/packages/google-auth/tests/oauth2/test_id_token.py b/packages/google-auth/tests/oauth2/test_id_token.py index ccfaaaf8cfb5..40204f9d4eb8 100644 --- a/packages/google-auth/tests/oauth2/test_id_token.py +++ b/packages/google-auth/tests/oauth2/test_id_token.py @@ -16,7 +16,7 @@ import os import mock -import pytest +import pytest # type: ignore from google.auth import environment_vars from google.auth import exceptions diff --git a/packages/google-auth/tests/oauth2/test_reauth.py b/packages/google-auth/tests/oauth2/test_reauth.py index 58d649d839ac..ae64be009371 100644 --- a/packages/google-auth/tests/oauth2/test_reauth.py +++ b/packages/google-auth/tests/oauth2/test_reauth.py @@ -15,7 +15,7 @@ import copy import mock -import pytest +import pytest # type: ignore from google.auth import exceptions from google.oauth2 import reauth diff --git a/packages/google-auth/tests/oauth2/test_service_account.py b/packages/google-auth/tests/oauth2/test_service_account.py index 531fc4c9ed6c..1d14384859cd 100644 --- a/packages/google-auth/tests/oauth2/test_service_account.py +++ b/packages/google-auth/tests/oauth2/test_service_account.py @@ -38,7 +38,7 @@ SERVICE_ACCOUNT_JSON_FILE = os.path.join(DATA_DIR, "service_account.json") -with open(SERVICE_ACCOUNT_JSON_FILE, "r") as fh: +with open(SERVICE_ACCOUNT_JSON_FILE, "rb") as fh: SERVICE_ACCOUNT_INFO = json.load(fh) SIGNER = crypt.RSASigner.from_string(PRIVATE_KEY_BYTES, "1") diff --git a/packages/google-auth/tests/oauth2/test_sts.py b/packages/google-auth/tests/oauth2/test_sts.py index e8e008df5da8..f61a1d33853a 100644 --- a/packages/google-auth/tests/oauth2/test_sts.py +++ b/packages/google-auth/tests/oauth2/test_sts.py @@ -15,7 +15,7 @@ import json import mock -import pytest +import pytest # type: ignore from six.moves import http_client from six.moves import urllib diff --git a/packages/google-auth/tests/oauth2/test_utils.py b/packages/google-auth/tests/oauth2/test_utils.py index 6de9ff533793..543a693a98bf 100644 --- a/packages/google-auth/tests/oauth2/test_utils.py +++ b/packages/google-auth/tests/oauth2/test_utils.py @@ -14,7 +14,7 @@ import json -import pytest +import pytest # type: ignore from google.auth import exceptions from google.oauth2 import utils diff --git a/packages/google-auth/tests/test__cloud_sdk.py b/packages/google-auth/tests/test__cloud_sdk.py index 31cb6c22c427..c05c443201d0 100644 --- a/packages/google-auth/tests/test__cloud_sdk.py +++ b/packages/google-auth/tests/test__cloud_sdk.py @@ -18,7 +18,7 @@ import subprocess import mock -import pytest +import pytest # type: ignore from google.auth import _cloud_sdk from google.auth import environment_vars @@ -28,12 +28,12 @@ DATA_DIR = os.path.join(os.path.dirname(__file__), "data") AUTHORIZED_USER_FILE = os.path.join(DATA_DIR, "authorized_user.json") -with io.open(AUTHORIZED_USER_FILE) as fh: +with io.open(AUTHORIZED_USER_FILE, "rb") as fh: AUTHORIZED_USER_FILE_DATA = json.load(fh) SERVICE_ACCOUNT_FILE = os.path.join(DATA_DIR, "service_account.json") -with io.open(SERVICE_ACCOUNT_FILE) as fh: +with io.open(SERVICE_ACCOUNT_FILE, "rb") as fh: SERVICE_ACCOUNT_FILE_DATA = json.load(fh) with io.open(os.path.join(DATA_DIR, "cloud_sdk_config.json"), "rb") as fh: @@ -63,7 +63,7 @@ def test_get_project_id(data, expected_project_id): @mock.patch( "subprocess.check_output", autospec=True, - side_effect=subprocess.CalledProcessError(-1, None), + side_effect=subprocess.CalledProcessError(-1, "testing"), ) def test_get_project_id_call_error(check_output): project_id = _cloud_sdk.get_project_id() diff --git a/packages/google-auth/tests/test__default.py b/packages/google-auth/tests/test__default.py index 1ce03cfe832a..fe5710d3de6d 100644 --- a/packages/google-auth/tests/test__default.py +++ b/packages/google-auth/tests/test__default.py @@ -16,7 +16,7 @@ import os import mock -import pytest +import pytest # type: ignore from google.auth import _default from google.auth import app_engine diff --git a/packages/google-auth/tests/test__helpers.py b/packages/google-auth/tests/test__helpers.py index 0c0bad2d2fdc..8c71f3e51f4b 100644 --- a/packages/google-auth/tests/test__helpers.py +++ b/packages/google-auth/tests/test__helpers.py @@ -14,7 +14,7 @@ import datetime -import pytest +import pytest # type: ignore from six.moves import urllib from google.auth import _helpers diff --git a/packages/google-auth/tests/test__oauth2client.py b/packages/google-auth/tests/test__oauth2client.py index 6b1112b50e75..f1fac511b410 100644 --- a/packages/google-auth/tests/test__oauth2client.py +++ b/packages/google-auth/tests/test__oauth2client.py @@ -17,10 +17,10 @@ import sys import mock -import oauth2client.client -import oauth2client.contrib.gce -import oauth2client.service_account -import pytest +import oauth2client.client # type: ignore +import oauth2client.contrib.gce # type: ignore +import oauth2client.service_account # type: ignore +import pytest # type: ignore from six.moves import reload_module from google.auth import _oauth2client @@ -109,7 +109,7 @@ def test__convert_appengine_app_assertion_credentials( app_identity, mock_oauth2client_gae_imports ): - import oauth2client.contrib.appengine + import oauth2client.contrib.appengine # type: ignore service_account_id = "service_account_id" old_credentials = oauth2client.contrib.appengine.AppAssertionCredentials( diff --git a/packages/google-auth/tests/test__service_account_info.py b/packages/google-auth/tests/test__service_account_info.py index 13b2f85a2944..d5529bcce87f 100644 --- a/packages/google-auth/tests/test__service_account_info.py +++ b/packages/google-auth/tests/test__service_account_info.py @@ -15,7 +15,7 @@ import json import os -import pytest +import pytest # type: ignore import six from google.auth import _service_account_info diff --git a/packages/google-auth/tests/test_app_engine.py b/packages/google-auth/tests/test_app_engine.py index 6a788b9e9ab0..ca085bd69854 100644 --- a/packages/google-auth/tests/test_app_engine.py +++ b/packages/google-auth/tests/test_app_engine.py @@ -15,7 +15,7 @@ import datetime import mock -import pytest +import pytest # type: ignore from google.auth import app_engine diff --git a/packages/google-auth/tests/test_aws.py b/packages/google-auth/tests/test_aws.py index 9ca08d5b2cdc..d37131afb0ae 100644 --- a/packages/google-auth/tests/test_aws.py +++ b/packages/google-auth/tests/test_aws.py @@ -16,7 +16,7 @@ import json import mock -import pytest +import pytest # type: ignore from six.moves import http_client from six.moves import urllib diff --git a/packages/google-auth/tests/test_credentials.py b/packages/google-auth/tests/test_credentials.py index 2de63884070c..da074143a00d 100644 --- a/packages/google-auth/tests/test_credentials.py +++ b/packages/google-auth/tests/test_credentials.py @@ -14,7 +14,7 @@ import datetime -import pytest +import pytest # type: ignore from google.auth import _helpers from google.auth import credentials diff --git a/packages/google-auth/tests/test_downscoped.py b/packages/google-auth/tests/test_downscoped.py index 9ca95f5aa856..7d0768a18086 100644 --- a/packages/google-auth/tests/test_downscoped.py +++ b/packages/google-auth/tests/test_downscoped.py @@ -16,7 +16,7 @@ import json import mock -import pytest +import pytest # type: ignore from six.moves import http_client from six.moves import urllib diff --git a/packages/google-auth/tests/test_external_account.py b/packages/google-auth/tests/test_external_account.py index 3c34f998c9c8..3897aef15e8c 100644 --- a/packages/google-auth/tests/test_external_account.py +++ b/packages/google-auth/tests/test_external_account.py @@ -16,7 +16,7 @@ import json import mock -import pytest +import pytest # type: ignore from six.moves import http_client from six.moves import urllib diff --git a/packages/google-auth/tests/test_iam.py b/packages/google-auth/tests/test_iam.py index bc71225b101e..ae482765b475 100644 --- a/packages/google-auth/tests/test_iam.py +++ b/packages/google-auth/tests/test_iam.py @@ -17,7 +17,7 @@ import json import mock -import pytest +import pytest # type: ignore from six.moves import http_client from google.auth import _helpers diff --git a/packages/google-auth/tests/test_identity_pool.py b/packages/google-auth/tests/test_identity_pool.py index 87e343be4981..664c317d05b6 100644 --- a/packages/google-auth/tests/test_identity_pool.py +++ b/packages/google-auth/tests/test_identity_pool.py @@ -17,7 +17,7 @@ import os import mock -import pytest +import pytest # type: ignore from six.moves import http_client from six.moves import urllib diff --git a/packages/google-auth/tests/test_impersonated_credentials.py b/packages/google-auth/tests/test_impersonated_credentials.py index bc404e36b78e..58d159a59fbb 100644 --- a/packages/google-auth/tests/test_impersonated_credentials.py +++ b/packages/google-auth/tests/test_impersonated_credentials.py @@ -16,8 +16,11 @@ import json import os +# Because Python 2.7 +# from typing import List + import mock -import pytest +import pytest # type: ignore from six.moves import http_client from google.auth import _helpers @@ -46,7 +49,7 @@ ) ID_TOKEN_EXPIRY = 1564475051 -with open(SERVICE_ACCOUNT_JSON_FILE, "r") as fh: +with open(SERVICE_ACCOUNT_JSON_FILE, "rb") as fh: SERVICE_ACCOUNT_INFO = json.load(fh) SIGNER = crypt.RSASigner.from_string(PRIVATE_KEY_BYTES, "1") @@ -98,7 +101,9 @@ class TestImpersonatedCredentials(object): SERVICE_ACCOUNT_EMAIL = "service-account@example.com" TARGET_PRINCIPAL = "impersonated@project.iam.gserviceaccount.com" TARGET_SCOPES = ["https://www.googleapis.com/auth/devstorage.read_only"] - DELEGATES = [] + # DELEGATES: List[str] = [] + # Because Python 2.7: + DELEGATES = [] # type: ignore LIFETIME = 3600 SOURCE_CREDENTIALS = service_account.Credentials( SIGNER, SERVICE_ACCOUNT_EMAIL, TOKEN_URI diff --git a/packages/google-auth/tests/test_jwt.py b/packages/google-auth/tests/test_jwt.py index c0e1184dcb6d..bc01ebfc760a 100644 --- a/packages/google-auth/tests/test_jwt.py +++ b/packages/google-auth/tests/test_jwt.py @@ -18,7 +18,7 @@ import os import mock -import pytest +import pytest # type: ignore from google.auth import _helpers from google.auth import crypt @@ -45,7 +45,7 @@ SERVICE_ACCOUNT_JSON_FILE = os.path.join(DATA_DIR, "service_account.json") -with open(SERVICE_ACCOUNT_JSON_FILE, "r") as fh: +with open(SERVICE_ACCOUNT_JSON_FILE, "rb") as fh: SERVICE_ACCOUNT_INFO = json.load(fh) diff --git a/packages/google-auth/tests/transport/compliance.py b/packages/google-auth/tests/transport/compliance.py index e093d761df3d..faf39b9bacc0 100644 --- a/packages/google-auth/tests/transport/compliance.py +++ b/packages/google-auth/tests/transport/compliance.py @@ -14,9 +14,9 @@ import time -import flask -import pytest -from pytest_localserver.http import WSGIServer +import flask # type: ignore +import pytest # type: ignore +from pytest_localserver.http import WSGIServer # type: ignore from six.moves import http_client from google.auth import exceptions diff --git a/packages/google-auth/tests/transport/test__http_client.py b/packages/google-auth/tests/transport/test__http_client.py index c176cb2f4c69..202276323c66 100644 --- a/packages/google-auth/tests/transport/test__http_client.py +++ b/packages/google-auth/tests/transport/test__http_client.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import pytest +import pytest # type: ignore from google.auth import exceptions import google.auth.transport._http_client diff --git a/packages/google-auth/tests/transport/test__mtls_helper.py b/packages/google-auth/tests/transport/test__mtls_helper.py index 3b6349a1dcad..1621a0530210 100644 --- a/packages/google-auth/tests/transport/test__mtls_helper.py +++ b/packages/google-auth/tests/transport/test__mtls_helper.py @@ -17,15 +17,13 @@ import mock from OpenSSL import crypto -import pytest +import pytest # type: ignore from google.auth import exceptions from google.auth.transport import _mtls_helper CONTEXT_AWARE_METADATA = {"cert_provider_command": ["some command"]} -CONTEXT_AWARE_METADATA_NO_CERT_PROVIDER_COMMAND = {} - ENCRYPTED_EC_PRIVATE_KEY = b"""-----BEGIN ENCRYPTED PRIVATE KEY----- MIHkME8GCSqGSIb3DQEFDTBCMCkGCSqGSIb3DQEFDDAcBAgl2/yVgs1h3QICCAAw DAYIKoZIhvcNAgkFADAVBgkrBgEEAZdVAQIECJk2GRrvxOaJBIGQXIBnMU4wmciT diff --git a/packages/google-auth/tests/transport/test_grpc.py b/packages/google-auth/tests/transport/test_grpc.py index 3437658a3738..f62ab0eae760 100644 --- a/packages/google-auth/tests/transport/test_grpc.py +++ b/packages/google-auth/tests/transport/test_grpc.py @@ -17,7 +17,7 @@ import time import mock -import pytest +import pytest # type: ignore from google.auth import _helpers from google.auth import credentials @@ -28,7 +28,7 @@ try: # pylint: disable=ungrouped-imports - import grpc + import grpc # type: ignore import google.auth.transport.grpc HAS_GRPC = True diff --git a/packages/google-auth/tests/transport/test_mtls.py b/packages/google-auth/tests/transport/test_mtls.py index ff70bb3c2290..b62063e47975 100644 --- a/packages/google-auth/tests/transport/test_mtls.py +++ b/packages/google-auth/tests/transport/test_mtls.py @@ -13,7 +13,7 @@ # limitations under the License. import mock -import pytest +import pytest # type: ignore from google.auth import exceptions from google.auth.transport import mtls diff --git a/packages/google-auth/tests/transport/test_requests.py b/packages/google-auth/tests/transport/test_requests.py index ed9300d7684e..60d44a5f4e0b 100644 --- a/packages/google-auth/tests/transport/test_requests.py +++ b/packages/google-auth/tests/transport/test_requests.py @@ -20,7 +20,7 @@ import freezegun import mock import OpenSSL -import pytest +import pytest # type: ignore import requests import requests.adapters from six.moves import http_client diff --git a/packages/google-auth/tests/transport/test_urllib3.py b/packages/google-auth/tests/transport/test_urllib3.py index e3848c177aad..396961c394fd 100644 --- a/packages/google-auth/tests/transport/test_urllib3.py +++ b/packages/google-auth/tests/transport/test_urllib3.py @@ -17,9 +17,9 @@ import mock import OpenSSL -import pytest +import pytest # type: ignore from six.moves import http_client -import urllib3 +import urllib3 # type: ignore from google.auth import environment_vars from google.auth import exceptions diff --git a/packages/google-auth/tests_async/conftest.py b/packages/google-auth/tests_async/conftest.py index b4e90f0e8c34..f13dec0e4f00 100644 --- a/packages/google-auth/tests_async/conftest.py +++ b/packages/google-auth/tests_async/conftest.py @@ -16,7 +16,7 @@ import sys import mock -import pytest +import pytest # type: ignore def pytest_configure(): diff --git a/packages/google-auth/tests_async/oauth2/test__client_async.py b/packages/google-auth/tests_async/oauth2/test__client_async.py index 6e48c4590fcb..91874cdd4987 100644 --- a/packages/google-auth/tests_async/oauth2/test__client_async.py +++ b/packages/google-auth/tests_async/oauth2/test__client_async.py @@ -16,7 +16,7 @@ import json import mock -import pytest +import pytest # type: ignore import six from six.moves import http_client from six.moves import urllib diff --git a/packages/google-auth/tests_async/oauth2/test_credentials_async.py b/packages/google-auth/tests_async/oauth2/test_credentials_async.py index 06c91419c67a..a328cc3cba6d 100644 --- a/packages/google-auth/tests_async/oauth2/test_credentials_async.py +++ b/packages/google-auth/tests_async/oauth2/test_credentials_async.py @@ -19,7 +19,7 @@ import sys import mock -import pytest +import pytest # type: ignore from google.auth import _helpers from google.auth import exceptions diff --git a/packages/google-auth/tests_async/oauth2/test_id_token.py b/packages/google-auth/tests_async/oauth2/test_id_token.py index 2aee7676bfb2..b84e74db2ee0 100644 --- a/packages/google-auth/tests_async/oauth2/test_id_token.py +++ b/packages/google-auth/tests_async/oauth2/test_id_token.py @@ -15,7 +15,7 @@ import os import mock -import pytest +import pytest # type: ignore from google.auth import environment_vars from google.auth import exceptions diff --git a/packages/google-auth/tests_async/oauth2/test_reauth_async.py b/packages/google-auth/tests_async/oauth2/test_reauth_async.py index d982e13a11c8..8f51bd3a7729 100644 --- a/packages/google-auth/tests_async/oauth2/test_reauth_async.py +++ b/packages/google-auth/tests_async/oauth2/test_reauth_async.py @@ -15,7 +15,7 @@ import copy import mock -import pytest +import pytest # type: ignore from google.auth import exceptions from google.oauth2 import _reauth_async diff --git a/packages/google-auth/tests_async/oauth2/test_service_account_async.py b/packages/google-auth/tests_async/oauth2/test_service_account_async.py index 3dce13d82b02..176992f7714f 100644 --- a/packages/google-auth/tests_async/oauth2/test_service_account_async.py +++ b/packages/google-auth/tests_async/oauth2/test_service_account_async.py @@ -15,7 +15,7 @@ import datetime import mock -import pytest +import pytest # type: ignore from google.auth import _helpers from google.auth import crypt diff --git a/packages/google-auth/tests_async/test__default_async.py b/packages/google-auth/tests_async/test__default_async.py index 69a50d69aed5..bf1a129a8216 100644 --- a/packages/google-auth/tests_async/test__default_async.py +++ b/packages/google-auth/tests_async/test__default_async.py @@ -16,7 +16,7 @@ import os import mock -import pytest +import pytest # type: ignore from google.auth import _credentials_async as credentials from google.auth import _default_async as _default diff --git a/packages/google-auth/tests_async/test_credentials_async.py b/packages/google-auth/tests_async/test_credentials_async.py index 5315483da318..9db5fc9ae754 100644 --- a/packages/google-auth/tests_async/test_credentials_async.py +++ b/packages/google-auth/tests_async/test_credentials_async.py @@ -14,7 +14,7 @@ import datetime -import pytest +import pytest # type: ignore from google.auth import _credentials_async as credentials from google.auth import _helpers diff --git a/packages/google-auth/tests_async/test_jwt_async.py b/packages/google-auth/tests_async/test_jwt_async.py index a35b837b7f4f..f24a0a99d224 100644 --- a/packages/google-auth/tests_async/test_jwt_async.py +++ b/packages/google-auth/tests_async/test_jwt_async.py @@ -16,7 +16,7 @@ import json import mock -import pytest +import pytest # type: ignore from google.auth import _jwt_async as jwt_async from google.auth import crypt diff --git a/packages/google-auth/tests_async/transport/async_compliance.py b/packages/google-auth/tests_async/transport/async_compliance.py index 9c4b173c2341..36fe7a3015cc 100644 --- a/packages/google-auth/tests_async/transport/async_compliance.py +++ b/packages/google-auth/tests_async/transport/async_compliance.py @@ -14,9 +14,9 @@ import time -import flask -import pytest -from pytest_localserver.http import WSGIServer +import flask # type: ignore +import pytest # type: ignore +from pytest_localserver.http import WSGIServer # type: ignore from six.moves import http_client from google.auth import exceptions diff --git a/packages/google-auth/tests_async/transport/test_aiohttp_requests.py b/packages/google-auth/tests_async/transport/test_aiohttp_requests.py index a64a4eec9aec..d00955a7debb 100644 --- a/packages/google-auth/tests_async/transport/test_aiohttp_requests.py +++ b/packages/google-auth/tests_async/transport/test_aiohttp_requests.py @@ -12,10 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -import aiohttp -from aioresponses import aioresponses, core +import aiohttp # type: ignore +from aioresponses import aioresponses, core # type: ignore import mock -import pytest +import pytest # type: ignore from tests_async.transport import async_compliance import google.auth._credentials_async From 3bb56400344fa37524eb8de1c488d7358851c5d7 Mon Sep 17 00:00:00 2001 From: Matthew Hughes <34972397+matthewhughes934@users.noreply.github.com> Date: Fri, 7 Jan 2022 15:54:58 +1100 Subject: [PATCH 511/966] Docs: note ValueError in `verify_oauth2_token` (#928) These `ValueError`s can be raised by `google.auth.jwt.decode` e.g.: * https://github.com/googleapis/google-auth-library-python/blob/8f1e9cfd56dbaae0dff64499e1d0cf55abc5b97e/google/oauth2/id_token.py#L162 * https://github.com/googleapis/google-auth-library-python/blob/8f1e9cfd56dbaae0dff64499e1d0cf55abc5b97e/google/oauth2/id_token.py#L135 * https://github.com/googleapis/google-auth-library-python/blob/8f1e9cfd56dbaae0dff64499e1d0cf55abc5b97e/google/auth/jwt.py#L255 Handling of this is noted in the user docs [1] [1] https://developers.google.com/identity/sign-in/web/backend-auth Co-authored-by: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> --- packages/google-auth/google/oauth2/id_token.py | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/google-auth/google/oauth2/id_token.py b/packages/google-auth/google/oauth2/id_token.py index 74899ae55382..48f5b0a59655 100644 --- a/packages/google-auth/google/oauth2/id_token.py +++ b/packages/google-auth/google/oauth2/id_token.py @@ -158,6 +158,7 @@ def verify_oauth2_token(id_token, request, audience=None, clock_skew_in_seconds= Raises: exceptions.GoogleAuthError: If the issuer is invalid. + ValueError: If token verification fails """ idinfo = verify_token( id_token, From 4eb359c8f8aee16790572e82407222bff8065a86 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Fri, 7 Jan 2022 03:12:55 -0800 Subject: [PATCH 512/966] chore: update user cred for system tests (#940) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 5df7af8cf8c9cb34326c284db0d98d431446f524..2a742194cbf2154cf0f12b11f34f6b61afb5cf50 100644 GIT binary patch literal 10324 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 From 00c2d73056e3dc9a8419d8bfa7649039232d7d8e Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Tue, 18 Jan 2022 16:10:34 -0800 Subject: [PATCH 513/966] chore: update user creds for system test (#948) --- .../google/auth/transport/requests.py | 4 ++-- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/google/auth/transport/requests.py b/packages/google-auth/google/auth/transport/requests.py index 9c6f6c888be9..46ca669ffdcc 100644 --- a/packages/google-auth/google/auth/transport/requests.py +++ b/packages/google-auth/google/auth/transport/requests.py @@ -36,10 +36,10 @@ ) import requests.adapters # pylint: disable=ungrouped-imports import requests.exceptions # pylint: disable=ungrouped-imports -from requests.packages.urllib3.util.ssl_ import ( +import six # pylint: disable=ungrouped-imports +from urllib3.util.ssl_ import ( create_urllib3_context, ) # pylint: disable=ungrouped-imports -import six # pylint: disable=ungrouped-imports from google.auth import environment_vars from google.auth import exceptions diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 2a742194cbf2154cf0f12b11f34f6b61afb5cf50..25940c0846bc204630645fef878de47d0ffac16e 100644 GIT binary patch literal 10324 zcmV-aD67{BB>?tKRTGV;|7ua1P&p(Y0$&@_9a1mcICd+Q)+#=h5kU5@g2WQ4PyiA0 z;H4twb(yc~jrBL#f4Y`te3v)H$CJiLXFKBG7HUX(Vf~c{1h%Rj1>q=p>Ylb;{ibN@ z|8!zNbK%p_HobLuoTnL|t);=TA>04q_$9uaNQbW9WY2xPgl%6GR^^Imj>4oo zEl2qXYl7IqI=w20M0&3}W|O6KW4G%Y%5irl*%+l-K!(Sp2LuUlIe6Io9EypoZAM!G zJ`hB*`J`JU*6fRZde&wV-&&qyO+>2v2asZPYG$bj!9mRO>&5T+e+PucFG_FY9>Q@G z4)MO(lWPhEZ-&pRHJ%5?mE>Ol4_P9P!_{WI2Q9je%8LvtH|~0-@n)idtwcOfW$nqK zrP6U-lP~ zt#^|khEN)7b;zz&NZBHA8{Tf3g}#iswhsA#KcZ~4c_JUlSp7%r42 zq697^$;T#L{0>j1m{w0}N{!o}jjx#McXr}VO=mEAbJIS?!8YyG+K`KCoav2g46neQ z#}JZYpm`Acc%?g@uURD2|{+Rj^hq25?jR{jFq55gLpm^1M zW4}@h8{-qy=%Kxj%}OFTKf7oX9agCO9Rlu)w^j#fVZhMYDh&OaZH6Cn1@R_1y#O3{ zE2^Sh?|C60LuWO5&ri*7I$@}C+~D}bB2>))#nH+3 zjB!nVV@rSW4?t&xZ~k&?CoVc}d&-OJKRSl3`h z(>7l%8-4#Zp~4pJ**QiwCV}yCxSC_MEd2#20!$pt>vz~XtifJL;HW<7(q7WdScMTK zl~x2sr%p@(pJx7ZWf#Jrrh>EN-ia1eaaNJkg{6KCw){-SxsTPDd z`Zr9fWe%hHnAyp?gu?~H`gxvy4;xp8P^f@oi*sQouhXCR()tTnaHGrJeM&!|y?#1E zXA*>%Q#3v}&FQxL5G^xm`AfG9M3jZ?vv6cpFW?Sk;+3SfXZ2-@={8M)wHx-k#Tfyw zZl92U2n|t-`+I!12Jl0;1kg8E)*79Q)48euc1^6v`Ry^ofuNW2!(WahoXQ|e@369O zU3%z)HgF`$keDkICh*z6W&8r@4Kj{{D4Abu3>$}kt{hPhG<1Ok%RdaW;~c3U{_GNg z=Z;Nh6E*1{VY!J>wikgL(201#i=CqY_4PKowAM4`6@HVkbZub1#F(|DfqW%6C3I7lUPhx&@=lmal&b2f|t)ok-6U#WCl!#;|lK#tBd0Gt*Z` zr+c}V17q5&fF;|H#TOq&Z=YSEP&VM)Lp6eQuB)(MX-Ovxp@}bd6V9I8lDVM%XS{BJ z>b9df!OP?13J19vzDL#r)15}CH^mvRlTu)@w2X|>&Bs=TAPI=cW4?Q!J0qm|7HK)uLb6JB#S)|u} zxi*$6<2g_q@!URvc^v6DYL(Xe2DaJ|A9+>fhYxm7&5L}dLp6B z>pT^lHV(P@Ttm{_?-Kxm>Wg?>ER>@5`)`Y2clNf>GXkIib5i|MI+`EH>${ibcFy*M zb$_a|q&zp?iUeU`mY_=firSx9-rwyVCZt)_nEb>u$*|?t)5Adcel3HJhBq3ez}f>p zoh3Z%zR7Su$bmLb6RWjpsYf&^8VbwD#K6qLDtVRK#StY7)ydMo~jySnx=F*V&z%^#bw9<_D|NWfd#d>?nS1?LY8G%g`^q z?}c#E;t$(9E?=8ij|l+JlWKg7Xb>ojyBuCM3Z`FJ6c)~AH9Pzw9VJi95OypRoE2}0 z(;E2w5rVc$7T6_|xZ*gi)fDP&1&ZbHW{GWyQL|P1Gg{LSxI(t(|2sp~n*<@(piiO7g})obSmZ(L3=MiQ7QBUI>S^^XqC1mifd=E4 zuH}tkbejs`W_jPl2r(Uuubv@MYMj_i8?EL|Wf)dGAq;P$2u4P&7Mcy;zXa5#PF4`4Y(+onMf7fKbFqq!Fiq zGD<`AgtS=kwS=H#(f7c5c^+#p;?(V7m|_`&DG=BjtRjOG<^AqIf{mHi%x;Atg;X^99a2`)`=O|39;pi*!5g6OyoXZW$*(4|_LH=YtCcFDi%#2R+N3iV_*ka*X8D zwpqHq)y4kkJdLM7^>r!nG} zRkakP7a^LBoUla-pbAG;WS$|V)#_L1zFw=%U0#Bk1Qo~boiH=Jp-)A5uCyw@fJpr@vH^&P~);Rn5mrqaZ7HL3yMp8 zGin*yO$O-=4y1{Nb1R~cNgBb$9$1YP$`~SAAScKzR*=$hEIGjQapsqzBZrqyAWrvH z*G*)ga}V67m<0n1iD5)ASy8s8&UG}4`3(}}pqokw_$5gBBrj=k^qtaVag<?U80eu z!GlM7&=-~vc*5AaFu%p=#cxea7mC1ddgQ8vJxX9o_QuXJZHp{2kP6eVobG%3tI2P4 z&`?9%VzFX=JYxRWwCT|4WYDCNxy2+0u=uR`Jkh+Bx#NMz95@a*iEA+xllkSNkHe9e zK~L#^HXEal?Jo#Y1du&farG}G-$8LmllV3lnEx#2?9bMViYQ<=h*oY!WIgOf{eD0`L4g~@@_8ret^nH1Mp0G+qF zC4c2u$%yXLtgd&_f*q&rszEDG8)#}%zDtm|(H8Fwv|oa}FNtcJ<}h+)p~4ZX3c0if zU){$GxJ^;AbBg8iB)DTm>C%2~1NM>i9;`;?6_aQw(>qlyjy#w!Ee?Q%64ac{wx$TG ztW?gArR0C?G@_x2<{+$3eP{r*r`tIgZIpF-=TNjP&tuG@77XU%d;2p#PlWw$@J6Gv z=o?uv+b9NQJ^)QeWJ;zrFNU2?28U|MJ1CxRp={!jr>xNf9$0LL1Nt{Yw4EF44^aw< z8h=8tynrfAhioAOi`pRoxEYhUgBRbaS}L_^uQ&Ml^63C5oJ!m3i7(*6cWwS|vcFDe zFKaNH!y@v#i0&?%k{IWAZyNe=*}8K&sB9K^E*LaUSuvkaI~uZ87_}A(^mu*bQYkhK z<2fRa$hV#aYm$?%V?HTb%rWFGIL03Y7F6i)gpG2}PWfLe)ng=T$$#{8cESc5@Bpy> z);u=#7O6k>KUz%;bA6BSMZHezSc44(9Fydxo*iJAuOUQxe3Z?W6k~N9|LLpfBASzS zam3rXh~?5V>UMDQzW)YOvAdroFU}UudMBS2eSgs=z{pV6BB&^FH|Ywo zP&+Qv;DMk}#HGbRaLq(ym{4P7DX*8Blb>=jtp2>JzmJq8(cd6#Gdg1LBM6kr%i2WV zPdEh)T1Uk$5t!*ReiP<{7e>B+rA4X~B-}?qP}xMeFqMB9x?(rp??mb@*I}PA1oGvy zh}T4L#zY~$(z`-q-macIOpJ^MK2yV^;@?y0SrQ)9MtE%zpk$@6ZTljfLS_s4&$<^XiEiwt zO!BlQ2TdG+3VFPa_(j>jMfC>EYkGuHc}Ye+YHQf2;q*#=oj*q<)hxDck+}m@Yg6#*uLZR(_+)bA0Z(HPgB1S- zN4jN@8`epg&98355^~s(b_;csTTnE|=XYjlvsG<}AVXQFN7W*ls0=C-{ux&-rw{u{ zkS3ms*bU_&44`^J>PRY=7h^Jj7(YN(NU@EBzpS=0TCrPS3fJC!)GJB~!6-tP@LTyi zNmb;;f5)u}iyqqcv3LGQnRPUEiDjgmy%%I$dYpuZONYcys$}0cCfpYktJ@fHzB3PT zGjnJmBGZ5(X}x`7%FC{(c3 zIA3IRitQsB+0{mZN*~OfK=9l`-u8w4OnVM84gX{dl1Tft$x$o~=Qxs%*I&GM`JwFO zbmIH6kbqO#Gdbru+O~GEG1)bHf0NxPA4+tV(6*vapdM9-=)6X9sL2@5=3Iz1*4SOq z)0k1}v!$Z@yQr#Um@dAeDNR%>$+Cu#q4dpFtJRo_*-*`!QH76Qsybb%4byaqz8^r= zC!+YOTnZwIzORqxzWUzIp&r{8#J>DF=qM06QYj|Gn zokj$GkxsPi-bhLtcJy2 z>u0%=cB<=BZ@F4;1DRu}&nQ%43H0)n1)Ytodk$lmPcLYrTD%x8kAOyb-2@$Es3#(4 z^LLsLg@yWcgX2?w|6u-7oySK!Tg6*WIWXyLOW2oK;pgM3<>-sPXaCQH^rWe1h0%%s zq%Lfd9Qz+luqfH`k}rO9Q|`=FP*UD4`S<)k&-;sgO|O7^++Uh)mI6HJ#3_rqx$U7a znE!+MDR$6sRtd@%x`c%UcPkF%d;is|3pUer0fClgP!|KVVW?hWPss?g!?E@gsh9-3 z5X#+1es@g|H;Ajx>xPf&yd+g?!nxZ?HRUD#l7|t9@+?LLxUDuZI7|?hOckPbAKt=h zqx=-NYVt~ft_iq0mFq$NLt$Cy%nEs42PU;Pg{bLhxs<$kI-a9=g^zbf-NqKjAl}m* zj}{U{5x;c_PyX5D3R7honyI2^^BLZ7>FNK6mJ^xo$SNZ*`7lpAY_Fc6+&rPq-+pP) zTa6g?j9m_=R^SC=>4MXpP<0}X%;>C?<~Z5+O$`3^!VH@P=qO`wW;LTIODZPQfy7?I zt|xyo;yJT=n4~nTB@=0TwoOI~>935t?0aQQnu88JZ{DsFwVe}}uT@*@g0VBKvEUA2& z3A1~%R>?4T0eQ3VDlk5o$fZXmF|mR0eyFe2N{j|ZUONCkHG1(fsX@nC|V9o+wp3_uuq2Nnv*1Og`;m&(ECJ}+4M;FV6J*~q~wb4>HCSPRpzJz*{9js?>GP_&G4Jp zev?D3-OP$kK9Ndpy#T$+$4)~iWe@_Dv9DLHn{%20Z^H!v!`v^dP#G*Y02T28^~YDb z$N^Oss)Af7>SDYz)=n)RzDSrZ&I#a;bhys%ax5Ww1_-JE12YjE{;T94N~%X!7>yUZ zF`#W>qt^i35xB_oA#x*{BOvTj?p+|m5>Y+KccvoYHHDqDaZ{qS3&d&%=*Y3QNipf+ zMQF}GYJqR(?`8@FN7cV5lV+MDe&vhE?rQBm@*fNrclxGxFfaQJm^;0t1YTau$zzd& zKU^TX10X68{@BC(%I&8i;iWNaL`dIJK+?~)TtS_(&MDwUM7G}LWd9XRH%vVZ9Z@la z=tJQ-Y_ptaGS{4sNwKuBKxK(rKvt1~mh9CvEMN>v+C4siK#$a`nz8RnT+gVuq4@x$7I|&Lx<015q)gmJ{lr z5U{SQEGuiu`879FQp4WVq&6s!D~Y^X3Y0dDGtPS=z6|Vnq;?r; zte9s6jbEyOB!T70qK~w=chy{=zTIfV$TVZo8buq{SH(98!>TYUy9LFIYKn~Ya(`>> z*r=Gup2YoC$4#6VV?UA3Vc zn(R{K)y5h!9~&z4P{Nv>;Y1<0haSEhTQ})6z~jDjir=rCMTnL_i7^S`hx09QN5Z00 zn^`2KHnxYu@X~r|<{eq-jD8b=zZnVjv$gE+#|eCx5Fus0KY|04l>*|^^^J5fB^F}| zvJqpW-J{MUB8&^x5wbeHlTyrHxX1asD7V5Y|ARbbOf1EUeLp6_YW+Idq3fQsnp^C3 z@=12Q^tv>yL5^L(t!-!c{bGK3i|60)Xr|JHFk&(zpFZWjMlaGjHp~uVR zunrNbA!xy9E+DV8Ffe**8?&?dd`*S#KfwoJA=!NaboJRG4%x?cfyBFhdySA{!>Gy1 zDBgw@%0&WCBQ5-VNZq6lRWvd%8Ebmqe|axp2OM53S$ zbiL68$yVB!$Y{|;`j}^2EjP?gyYAu}8CtHMdqWr$#fx!@`y1pAi7c%4Za7>r7IsNa z4w~TrHu37#QP^9NR$!OGC#MlkT)OrPnx%S~%FhgDHWa0wF-53Z-0F#d0Zf`}{~gp- zELHD5bLF$o^!xZic`F{M9yf^S&)(zJC}5uCUPD>?qJKxVEknaJq-0Q2T_fXszs>Cq|fau6?l=9c%bX z*A797vh)0RItzEvA|&AdThj*a!7Lupha*3SG~l;{+hVk~=sdzn>T32g(wnxOFTnfhk{m2S z8&s5`jf@S)xo}p-&#^xt(56voP+KhFrDk;>(v-I6vvNq&Mickc&FeW1mDhLt8z?|R z55Ltmo%H+~luwk|&sGWK7ZrT$tUbZ(Bj_ysiE=ju{md7c{=Yko7mn@WTl4|ze>uec z7I&Rp7OkFa%KFa~d%hG5!Z=0x+|&4CcSlg5vsB*az~9hRMTUQ*h56b3ocO2Riu_r3 z8!N`Lgh-2i9&2(iqbPU$P@#k2F6wL$-p@wRW>r(Q?zvbv%?2&#ZfvZ$$@Bg&U@#D> zCcz?Rt2I0tX8|=YeGnH2(^>QAX|*vK*#h6VwaHa_7y?PcKQ<~vABQ_r4$3=muM2jE zY*$ka=s`Z8h5))HE_kG=Qv^HSc3XCwguibHxABn_9&yr&0@$=sG9XNaxORDB1d_Zu zk&B^u3zNzqM?2IC0twl0?6Tl{m)6cMhGUU*tFmpr%B-}ZR!#977X#o&&KY0UA1XN z>_FqO%@-#q+qGV5E|hBVKL{2uBI7m+qH$1iqkBOBl1Gh%Hr+ z=Uaw1xb6ctWs>-Ya(PgsG|q_}R75M+=UIp9(f)H=)P%tjF`fOy zO30Sq-w6c!OpWVWvBjk#>KlYJ?n#iQu8Kl1uSl9`@+SC1BVr?4BVc75xPAU((CMdBq(`K&Gx5KdrP=mj&EK*9 zdud4(xU(;@5b0eqo#~lHaffw8Fqjf~KO zst&0>)VYt~-UFuv4j5>hrHd*d;eoMu#JUOb6k8Mwvx2e2_ru0}ZV8szs;OmZznh%b zNcet;6nX&GB$!FX<}}i{IRODQs=M64o%pp~m)~`bllX+bAD?Bi3$mbTF6wswF({y& zY`SPr;KoJytc==EYlG>C$PG&QHEk~o7zGHHKBI?bay{y1Xt9k{%ubMBheU8qSxU6)M#>s{qd8ukHQ=Wv29o2;9#V4Tr&)`5 znRwZ2hT0E+{{cDj_9v&@jEs)x8$FRpfZ-C`;G^9Mq94s%?mZG(wXF~19V zjIq4P;^}nqb()uvmZ^Gn1jgW2$05T{d=CbY#}p-UOen`nVkPYc`>u{h>dzt9mM(&& zJ1YOnypk41nry}#B{hfYI(_a5aMBf}xay?Yo5A?` z3zgS9$s02YwlPWI7Vf7IJZ!r{tR@1ogxAiWLZbcMm`G`HUq6*ztgw5`K6mH=dFnz| zULHl4i@DHhR`YATpsknYXv291Z^urz(Mux<&k}!6s*IaSv5Z41m zEHeuMml!{L*Cjv=Hl`5%2^O>-Mq&f^{@Eq}-Z7%}j%HavO^73@6e5jTkg}P?f-(P% z-!p6RcyO{#Lbjw~>%p6>By};Sa6Z7z^&b+ynJ^CZ1A*{=9TKREv!33|2kf950FS(u zl7D>`WhQ`cw2WCJCr(B1h7V&*{0SX8bm|F}Gg0kn-2`z^P}rtwrfD7M6MUW$#t)sl zew0)sR6hHK?e5CA;WngNJ1lOJo6hI^6RK z5l(Y8Zhk)%o~Cc4l%?w)<{+u?g3QFXdoyl;f&4>?3O;B z8NxQ)t&fN#JiO)MW8n5c#y+^sqM<0`cn|@E)G(Mc!+tM7`3%1FL`KhXb)nE8605f>D%AKE1}y|T|8H|hLj4S&ERBUC*?1*mfp z4a(ezI6;^AC;q|6VT1jqSP%P z^3WwSAUorRFKj%xC+c5|>Ml{sUL;PXD`9AmfbjX7sBiz zI`#d@i(mTS_TyX(uaT6AVkzTO&ueODfwwyqrTyrZPLpPZ+cqxelVvh46JBcaND0&_ zuO959no#y%B3HBQoFFZq_SZ67teI+9^we2ulx7jUqOqk_loa6M$xGXy2`Uay zy(jh6EDat3JGP-El^|EPP_cA76nof5-1?@>p+pn0OTbl6g@laKgLY%wkFjSmU?p7YNhU3SQE7^w`=vFaLS49ohn{ErrKF0HAMp>SD*335I#dPkk%b(|RBc$GlaKrCq~r)!Q-luCuBb25 zBl*NyZN&bDU_agp0vBheTWX0%o>#b!sENII$%vH=w^ZT*>m5J+LK<5K9SdW+^vp*Qp&Q!|m_cPk~;!s3X_cfZXAf=`y|~&6>VR-o>M#otn2u z@FWku`z`dL0%uqkeLF#-b4vK($eY3pDzV5~&bv-jhjmOqPc+L^`DEYGZG_X5wsZyJ z!%3l9LM%y_;*woVd*1>&xxR^lzO4 m%J^Q2NXci+r)TY(=-CyY+LPzbcv^B1Si+h&IqdH=dWieN;wSh3 literal 10324 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 Date: Tue, 18 Jan 2022 19:49:15 -0500 Subject: [PATCH 514/966] chore(python): update release.sh to use keystore (#943) * chore(python): update release.sh to use keystore Source-Link: https://github.com/googleapis/synthtool/commit/69fda12e2994f0b595a397e8bb6e3e9f380524eb Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:ae600f36b6bc972b368367b6f83a1d91ec2c82a4a116b383d67d547c56fe6de3 * use urllib3 instead of requests.packages.urllib3 * lint Co-authored-by: Owl Bot Co-authored-by: Anthonios Partheniou --- packages/google-auth/.github/.OwlBot.lock.yaml | 2 +- packages/google-auth/.kokoro/release.sh | 2 +- packages/google-auth/.kokoro/release/common.cfg | 12 +++++++++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/google-auth/.github/.OwlBot.lock.yaml b/packages/google-auth/.github/.OwlBot.lock.yaml index cb89b2e326b7..eecb84c21b27 100644 --- a/packages/google-auth/.github/.OwlBot.lock.yaml +++ b/packages/google-auth/.github/.OwlBot.lock.yaml @@ -1,3 +1,3 @@ docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:ec49167c606648a063d1222220b48119c912562849a0528f35bfb592a9f72737 + digest: sha256:ae600f36b6bc972b368367b6f83a1d91ec2c82a4a116b383d67d547c56fe6de3 diff --git a/packages/google-auth/.kokoro/release.sh b/packages/google-auth/.kokoro/release.sh index 967bc917e8f3..638176950271 100755 --- a/packages/google-auth/.kokoro/release.sh +++ b/packages/google-auth/.kokoro/release.sh @@ -26,7 +26,7 @@ python3 -m pip install --upgrade twine wheel setuptools export PYTHONUNBUFFERED=1 # Move into the package, build the distribution and upload. -TWINE_PASSWORD=$(cat "${KOKORO_GFILE_DIR}/secret_manager/google-cloud-pypi-token") +TWINE_PASSWORD=$(cat "${KOKORO_KEYSTORE_DIR}/73713_google-cloud-pypi-token-keystore-1") cd github/google-auth-library-python python3 setup.py sdist bdist_wheel twine upload --username __token__ --password "${TWINE_PASSWORD}" dist/* diff --git a/packages/google-auth/.kokoro/release/common.cfg b/packages/google-auth/.kokoro/release/common.cfg index 07334fd501eb..9ec8d102fa51 100644 --- a/packages/google-auth/.kokoro/release/common.cfg +++ b/packages/google-auth/.kokoro/release/common.cfg @@ -23,8 +23,18 @@ env_vars: { value: "github/google-auth-library-python/.kokoro/release.sh" } +# Fetch PyPI password +before_action { + fetch_keystore { + keystore_resource { + keystore_config_id: 73713 + keyname: "google-cloud-pypi-token-keystore-1" + } + } +} + # Tokens needed to report release status back to GitHub env_vars: { key: "SECRET_MANAGER_KEYS" - value: "releasetool-publish-reporter-app,releasetool-publish-reporter-googleapis-installation,releasetool-publish-reporter-pem,google-cloud-pypi-token" + value: "releasetool-publish-reporter-app,releasetool-publish-reporter-googleapis-installation,releasetool-publish-reporter-pem" } From ff87f109a012b258b4684648d3e334fa82a15c63 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Wed, 19 Jan 2022 14:55:25 -0800 Subject: [PATCH 515/966] feat: add api key support (#826) * feat: add api key support * chore: update * Update google/auth/_default.py Co-authored-by: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Co-authored-by: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> --- packages/google-auth/google/auth/_default.py | 41 ++++++++- .../google-auth/google/auth/_default_async.py | 45 +++++++++- packages/google-auth/google/auth/api_key.py | 83 +++++++++++++++++++ .../google/auth/environment_vars.py | 3 + packages/google-auth/tests/test__default.py | 44 ++++++++++ packages/google-auth/tests/test_api_key.py | 45 ++++++++++ .../tests_async/test__default_async.py | 44 ++++++++++ 7 files changed, 299 insertions(+), 6 deletions(-) create mode 100644 packages/google-auth/google/auth/api_key.py create mode 100644 packages/google-auth/tests/test_api_key.py diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index 4ae7c8c06125..54d6561649e8 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -353,6 +353,24 @@ def _get_external_account_credentials( return credentials, credentials.get_project_id(request=request) +def _get_api_key_credentials(quota_project_id=None): + """Gets API key credentials and project ID.""" + from google.auth import api_key + + api_key_value = os.environ.get(environment_vars.API_KEY) + if api_key_value: + return api_key.Credentials(api_key_value), quota_project_id + else: + return None, None + + +def get_api_key_credentials(api_key_value): + """Gets API key credentials using the given api key value.""" + from google.auth import api_key + + return api_key.Credentials(api_key_value) + + def default(scopes=None, request=None, quota_project_id=None, default_scopes=None): """Gets the default credentials for the current environment. @@ -361,7 +379,14 @@ def default(scopes=None, request=None, quota_project_id=None, default_scopes=Non This function acquires credentials from the environment in the following order: - 1. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set + 1. If both ``GOOGLE_API_KEY`` and ``GOOGLE_APPLICATION_CREDENTIALS`` + environment variables are set, throw an exception. + + If ``GOOGLE_API_KEY`` is set, an `API Key`_ credentials will be returned. + The project ID returned is the one defined by ``GOOGLE_CLOUD_PROJECT`` or + ``GCLOUD_PROJECT`` environment variables. + + If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set to the path of a valid service account JSON private key file, then it is loaded and returned. The project ID returned is the project ID defined in the service account file if available (some older files do not @@ -409,6 +434,7 @@ def default(scopes=None, request=None, quota_project_id=None, default_scopes=Non .. _Metadata Service: https://cloud.google.com/compute/docs\ /storing-retrieving-metadata .. _Cloud Run: https://cloud.google.com/run + .. _API Key: https://cloud.google.com/docs/authentication/api-keys Example:: @@ -444,16 +470,25 @@ def default(scopes=None, request=None, quota_project_id=None, default_scopes=Non invalid. """ from google.auth.credentials import with_scopes_if_required + from google.auth.credentials import CredentialsWithQuotaProject explicit_project_id = os.environ.get( environment_vars.PROJECT, os.environ.get(environment_vars.LEGACY_PROJECT) ) + if os.environ.get(environment_vars.API_KEY) and os.environ.get( + environment_vars.CREDENTIALS + ): + raise exceptions.DefaultCredentialsError( + "Environment variables GOOGLE_API_KEY and GOOGLE_APPLICATION_CREDENTIALS are mutually exclusive" + ) + checkers = ( # Avoid passing scopes here to prevent passing scopes to user credentials. # with_scopes_if_required() below will ensure scopes/default scopes are # safely set on the returned credentials since requires_scopes will # guard against setting scopes on user credentials. + lambda: _get_api_key_credentials(quota_project_id=quota_project_id), lambda: _get_explicit_environ_credentials(quota_project_id=quota_project_id), lambda: _get_gcloud_sdk_credentials(quota_project_id=quota_project_id), _get_gae_credentials, @@ -477,7 +512,9 @@ def default(scopes=None, request=None, quota_project_id=None, default_scopes=Non request = google.auth.transport.requests.Request() project_id = credentials.get_project_id(request=request) - if quota_project_id: + if quota_project_id and isinstance( + credentials, CredentialsWithQuotaProject + ): credentials = credentials.with_quota_project(quota_project_id) effective_project_id = explicit_project_id or project_id diff --git a/packages/google-auth/google/auth/_default_async.py b/packages/google-auth/google/auth/_default_async.py index fb277c54e82e..a6f7d777775f 100644 --- a/packages/google-auth/google/auth/_default_async.py +++ b/packages/google-auth/google/auth/_default_async.py @@ -161,6 +161,24 @@ def _get_gae_credentials(): return _default._get_gae_credentials() +def _get_api_key_credentials(quota_project_id=None): + """Gets API key credentials and project ID.""" + from google.auth import api_key + + api_key_value = os.environ.get(environment_vars.API_KEY) + if api_key_value: + return api_key.Credentials(api_key_value), quota_project_id + else: + return None, None + + +def get_api_key_credentials(api_key_value): + """Gets API key credentials using the given api key value.""" + from google.auth import api_key + + return api_key.Credentials(api_key_value) + + def _get_gce_credentials(request=None): """Gets credentials and project ID from the GCE Metadata Service.""" # Ping requires a transport, but we want application default credentials @@ -182,7 +200,14 @@ def default_async(scopes=None, request=None, quota_project_id=None): This function acquires credentials from the environment in the following order: - 1. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set + 1. If both ``GOOGLE_API_KEY`` and ``GOOGLE_APPLICATION_CREDENTIALS`` + environment variables are set, throw an exception. + + If ``GOOGLE_API_KEY`` is set, an `API Key`_ credentials will be returned. + The project ID returned is the one defined by ``GOOGLE_CLOUD_PROJECT`` or + ``GCLOUD_PROJECT`` environment variables. + + If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set to the path of a valid service account JSON private key file, then it is loaded and returned. The project ID returned is the project ID defined in the service account file if available (some older files do not @@ -221,6 +246,7 @@ def default_async(scopes=None, request=None, quota_project_id=None): .. _Metadata Service: https://cloud.google.com/compute/docs\ /storing-retrieving-metadata .. _Cloud Run: https://cloud.google.com/run + .. _API Key: https://cloud.google.com/docs/authentication/api-keys Example:: @@ -250,12 +276,21 @@ def default_async(scopes=None, request=None, quota_project_id=None): invalid. """ from google.auth._credentials_async import with_scopes_if_required + from google.auth.credentials import CredentialsWithQuotaProject explicit_project_id = os.environ.get( environment_vars.PROJECT, os.environ.get(environment_vars.LEGACY_PROJECT) ) + if os.environ.get(environment_vars.API_KEY) and os.environ.get( + environment_vars.CREDENTIALS + ): + raise exceptions.DefaultCredentialsError( + "GOOGLE_API_KEY and GOOGLE_APPLICATION_CREDENTIALS are mutually exclusive" + ) + checkers = ( + lambda: _get_api_key_credentials(quota_project_id=quota_project_id), lambda: _get_explicit_environ_credentials(quota_project_id=quota_project_id), lambda: _get_gcloud_sdk_credentials(quota_project_id=quota_project_id), _get_gae_credentials, @@ -265,9 +300,11 @@ def default_async(scopes=None, request=None, quota_project_id=None): for checker in checkers: credentials, project_id = checker() if credentials is not None: - credentials = with_scopes_if_required( - credentials, scopes - ).with_quota_project(quota_project_id) + credentials = with_scopes_if_required(credentials, scopes) + if quota_project_id and isinstance( + credentials, CredentialsWithQuotaProject + ): + credentials = credentials.with_quota_project(quota_project_id) effective_project_id = explicit_project_id or project_id if not effective_project_id: _default._LOGGER.warning( diff --git a/packages/google-auth/google/auth/api_key.py b/packages/google-auth/google/auth/api_key.py new file mode 100644 index 000000000000..7cdef429c8a5 --- /dev/null +++ b/packages/google-auth/google/auth/api_key.py @@ -0,0 +1,83 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google API key support. + +This module provides authentication using the `API key`_. + + +.. _API key: + https://cloud.google.com/docs/authentication/api-keys/ +""" + +from google.auth import _helpers +from google.auth import credentials + + +class Credentials(credentials.Credentials): + """API key credentials. + + These credentials use API key to provide authorization to applications. + """ + + def __init__(self, token): + """ + Args: + token (str): API key string + + Raises: + ValueError: If the provided API key is not a non-empty string. + """ + if not token: + raise ValueError("Token must be a non-empty API key string") + super(Credentials, self).__init__() + self.token = token + + @property + def expired(self): + return False + + @property + def valid(self): + return True + + @_helpers.copy_docstring(credentials.Credentials) + def refresh(self, request): + return + + def apply(self, headers, token=None): + """Apply the API key token to the x-goog-api-key header. + + Args: + headers (Mapping): The HTTP request headers. + token (Optional[str]): If specified, overrides the current access + token. + """ + headers["x-goog-api-key"] = token or self.token + + def before_request(self, request, method, url, headers): + """Performs credential-specific before request logic. + + Refreshes the credentials if necessary, then calls :meth:`apply` to + apply the token to the x-goog-api-key header. + + Args: + request (google.auth.transport.Request): The object used to make + HTTP requests. + method (str): The request's HTTP method or the RPC method being + invoked. + url (str): The request's URI or the RPC service's URI. + headers (Mapping): The request's headers. + """ + self.apply(headers) diff --git a/packages/google-auth/google/auth/environment_vars.py b/packages/google-auth/google/auth/environment_vars.py index c076dc59da1c..d872c95f9a82 100644 --- a/packages/google-auth/google/auth/environment_vars.py +++ b/packages/google-auth/google/auth/environment_vars.py @@ -33,6 +33,9 @@ """Environment variable defining the location of Google application default credentials.""" +API_KEY = "GOOGLE_API_KEY" +"""Environment variable defining the API key value.""" + # The environment variable name which can replace ~/.config if set. CLOUD_SDK_CONFIG_DIR = "CLOUDSDK_CONFIG" """Environment variable defines the location of Google Cloud SDK's config diff --git a/packages/google-auth/tests/test__default.py b/packages/google-auth/tests/test__default.py index fe5710d3de6d..67795ef02979 100644 --- a/packages/google-auth/tests/test__default.py +++ b/packages/google-auth/tests/test__default.py @@ -19,6 +19,7 @@ import pytest # type: ignore from google.auth import _default +from google.auth import api_key from google.auth import app_engine from google.auth import aws from google.auth import compute_engine @@ -994,3 +995,46 @@ def test_default_no_warning_with_quota_project_id_for_user_creds(get_adc_path): get_adc_path.return_value = AUTHORIZED_USER_CLOUD_SDK_FILE credentials, project_id = _default.default(quota_project_id="project-foo") + + +def test__get_api_key_credentials_no_env_var(): + cred, project_id = _default._get_api_key_credentials(quota_project_id="project-foo") + assert cred is None + assert project_id is None + + +def test__get_api_key_credentials_from_env_var(): + with mock.patch.dict(os.environ, {environment_vars.API_KEY: "api-key"}): + cred, project_id = _default._get_api_key_credentials( + quota_project_id="project-foo" + ) + assert isinstance(cred, api_key.Credentials) + assert cred.token == "api-key" + assert project_id == "project-foo" + + +def test_exception_with_api_key_and_adc_env_var(): + with mock.patch.dict(os.environ, {environment_vars.API_KEY: "api-key"}): + with mock.patch.dict( + os.environ, {environment_vars.CREDENTIALS: "/path/to/json"} + ): + with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: + _default.default() + + assert excinfo.match( + r"GOOGLE_API_KEY and GOOGLE_APPLICATION_CREDENTIALS are mutually exclusive" + ) + + +def test_default_api_key_from_env_var(): + with mock.patch.dict(os.environ, {environment_vars.API_KEY: "api-key"}): + cred, project_id = _default.default() + assert isinstance(cred, api_key.Credentials) + assert cred.token == "api-key" + assert project_id is None + + +def test_get_api_key_credentials(): + cred = _default.get_api_key_credentials("api-key") + assert isinstance(cred, api_key.Credentials) + assert cred.token == "api-key" diff --git a/packages/google-auth/tests/test_api_key.py b/packages/google-auth/tests/test_api_key.py new file mode 100644 index 000000000000..9721731be3a3 --- /dev/null +++ b/packages/google-auth/tests/test_api_key.py @@ -0,0 +1,45 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest # type: ignore + +from google.auth import api_key + + +def test_credentials_constructor(): + with pytest.raises(ValueError) as excinfo: + api_key.Credentials("") + + assert excinfo.match(r"Token must be a non-empty API key string") + + +def test_expired_and_valid(): + credentials = api_key.Credentials("api-key") + + assert credentials.valid + assert credentials.token == "api-key" + assert not credentials.expired + + credentials.refresh(None) + assert credentials.valid + assert credentials.token == "api-key" + assert not credentials.expired + + +def test_before_request(): + credentials = api_key.Credentials("api-key") + headers = {} + + credentials.before_request(None, "http://example.com", "GET", headers) + assert headers["x-goog-api-key"] == "api-key" diff --git a/packages/google-auth/tests_async/test__default_async.py b/packages/google-auth/tests_async/test__default_async.py index bf1a129a8216..2a1921081470 100644 --- a/packages/google-auth/tests_async/test__default_async.py +++ b/packages/google-auth/tests_async/test__default_async.py @@ -20,6 +20,7 @@ from google.auth import _credentials_async as credentials from google.auth import _default_async as _default +from google.auth import api_key from google.auth import app_engine from google.auth import compute_engine from google.auth import environment_vars @@ -561,3 +562,46 @@ def test_default_no_warning_with_quota_project_id_for_user_creds(get_adc_path): get_adc_path.return_value = test_default.AUTHORIZED_USER_CLOUD_SDK_FILE credentials, project_id = _default.default_async(quota_project_id="project-foo") + + +def test__get_api_key_credentials_no_env_var(): + cred, project_id = _default._get_api_key_credentials(quota_project_id="project-foo") + assert cred is None + assert project_id is None + + +def test__get_api_key_credentials_from_env_var(): + with mock.patch.dict(os.environ, {environment_vars.API_KEY: "api-key"}): + cred, project_id = _default._get_api_key_credentials( + quota_project_id="project-foo" + ) + assert isinstance(cred, api_key.Credentials) + assert cred.token == "api-key" + assert project_id == "project-foo" + + +def test_exception_with_api_key_and_adc_env_var(): + with mock.patch.dict(os.environ, {environment_vars.API_KEY: "api-key"}): + with mock.patch.dict( + os.environ, {environment_vars.CREDENTIALS: "/path/to/json"} + ): + with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: + _default.default_async() + + assert excinfo.match( + r"GOOGLE_API_KEY and GOOGLE_APPLICATION_CREDENTIALS are mutually exclusive" + ) + + +def test_default_api_key_from_env_var(): + with mock.patch.dict(os.environ, {environment_vars.API_KEY: "api-key"}): + cred, project_id = _default.default_async() + assert isinstance(cred, api_key.Credentials) + assert cred.token == "api-key" + assert project_id is None + + +def test_get_api_key_credentials(): + cred = _default.get_api_key_credentials("api-key") + assert isinstance(cred, api_key.Credentials) + assert cred.token == "api-key" From dfc0e2164cfcda78fa836622e6160e5396cfa29e Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Thu, 20 Jan 2022 10:21:37 -0800 Subject: [PATCH 516/966] chore: update user creds for system tests (#951) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 25940c0846bc204630645fef878de47d0ffac16e..550f89847f0bc39aa547564d83f593267259fa66 100644 GIT binary patch literal 10324 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

S>RSh754;C(FDufKr=J4%VT=p_3Px-=YQNGC3y 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 From 1fe6ff514716f48dece47696d9d4594f5d47cd16 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Thu, 20 Jan 2022 13:46:18 -0700 Subject: [PATCH 517/966] fix(deps): allow cachetools 5.0 for python 3.7+ (#937) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(deps): allow cachetools 5.0 for python 3.7+ Changes listed https://github.com/tkem/cachetools/blob/master/CHANGELOG.rst#v500-2021-12-21 * chore: remove split pin * Update setup.py * 🦉 Updates from OwlBot See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md Co-authored-by: Owl Bot Co-authored-by: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> --- packages/google-auth/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 44c5121281e7..22f627b99139 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -20,7 +20,7 @@ DEPENDENCIES = ( - "cachetools>=2.0.0,<5.0", + "cachetools>=2.0.0,<6.0", "pyasn1-modules>=0.2.1", # rsa==4.5 is the last version to support 2.7 # https://github.com/sybrenstuvel/python-rsa/issues/152#issuecomment-643470233 From 3209283396ed6d9447f4fe2ac5ee6024a46625da Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 20 Jan 2022 21:06:35 +0000 Subject: [PATCH 518/966] chore(main): release 2.4.0 (#949) :robot: I have created a release *beep* *boop* --- ## [2.4.0](https://github.com/googleapis/google-auth-library-python/compare/v2.3.3...v2.4.0) (2022-01-20) ### Features * add 'py.typed' declaration ([#919](https://github.com/googleapis/google-auth-library-python/issues/919)) ([c993504](https://github.com/googleapis/google-auth-library-python/commit/c99350455d0f7fd3aab950ac47b43000c73dd312)) * add api key support ([#826](https://github.com/googleapis/google-auth-library-python/issues/826)) ([3b15092](https://github.com/googleapis/google-auth-library-python/commit/3b15092b3461278400e4683060f64a96d50587c4)) ### Bug Fixes * **deps:** allow cachetools 5.0 for python 3.7+ ([#937](https://github.com/googleapis/google-auth-library-python/issues/937)) ([1eae37d](https://github.com/googleapis/google-auth-library-python/commit/1eae37db7f6fceb32d6ef0041962ce1755d2116c)) * fix the message format for metadata server exception ([#916](https://github.com/googleapis/google-auth-library-python/issues/916)) ([e756f08](https://github.com/googleapis/google-auth-library-python/commit/e756f08dc78616040ab8fbd7db20903137ccf0c7)) ### Documentation * fix intersphinx link for 'requests-oauthlib' ([#921](https://github.com/googleapis/google-auth-library-python/issues/921)) ([967be4f](https://github.com/googleapis/google-auth-library-python/commit/967be4f4e2a43ba7e240d7acb01b6b992d40e6ec)) * note ValueError in `verify_oauth2_token` ([#928](https://github.com/googleapis/google-auth-library-python/issues/928)) ([82bc5f0](https://github.com/googleapis/google-auth-library-python/commit/82bc5f08111de78a2b475b0310d3f35470680dbe)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --- packages/google-auth/CHANGELOG.md | 20 ++++++++++++++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 73440f7e38e8..43376ec7a012 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,26 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.4.0](https://github.com/googleapis/google-auth-library-python/compare/v2.3.3...v2.4.0) (2022-01-20) + + +### Features + +* add 'py.typed' declaration ([#919](https://github.com/googleapis/google-auth-library-python/issues/919)) ([c993504](https://github.com/googleapis/google-auth-library-python/commit/c99350455d0f7fd3aab950ac47b43000c73dd312)) +* add api key support ([#826](https://github.com/googleapis/google-auth-library-python/issues/826)) ([3b15092](https://github.com/googleapis/google-auth-library-python/commit/3b15092b3461278400e4683060f64a96d50587c4)) + + +### Bug Fixes + +* **deps:** allow cachetools 5.0 for python 3.7+ ([#937](https://github.com/googleapis/google-auth-library-python/issues/937)) ([1eae37d](https://github.com/googleapis/google-auth-library-python/commit/1eae37db7f6fceb32d6ef0041962ce1755d2116c)) +* fix the message format for metadata server exception ([#916](https://github.com/googleapis/google-auth-library-python/issues/916)) ([e756f08](https://github.com/googleapis/google-auth-library-python/commit/e756f08dc78616040ab8fbd7db20903137ccf0c7)) + + +### Documentation + +* fix intersphinx link for 'requests-oauthlib' ([#921](https://github.com/googleapis/google-auth-library-python/issues/921)) ([967be4f](https://github.com/googleapis/google-auth-library-python/commit/967be4f4e2a43ba7e240d7acb01b6b992d40e6ec)) +* note ValueError in `verify_oauth2_token` ([#928](https://github.com/googleapis/google-auth-library-python/issues/928)) ([82bc5f0](https://github.com/googleapis/google-auth-library-python/commit/82bc5f08111de78a2b475b0310d3f35470680dbe)) + ### [2.3.3](https://www.github.com/googleapis/google-auth-library-python/compare/v2.3.2...v2.3.3) (2021-11-01) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index ad9a0c7a4f75..562d21480bf1 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.3.3" +__version__ = "2.4.0" From a80396643e338b46079ae0532ecf2f903cf9043b Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Thu, 20 Jan 2022 16:38:04 -0800 Subject: [PATCH 519/966] fix: urllib3 import (#953) * fix: urllib3 import fix #952 * chore: disable mypy for requests.packages.urllib3.util.ssl_ import * chore: update system test user creds --- .../google/auth/transport/requests.py | 4 ++-- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/google/auth/transport/requests.py b/packages/google-auth/google/auth/transport/requests.py index 46ca669ffdcc..a55b5f57b6e0 100644 --- a/packages/google-auth/google/auth/transport/requests.py +++ b/packages/google-auth/google/auth/transport/requests.py @@ -36,10 +36,10 @@ ) import requests.adapters # pylint: disable=ungrouped-imports import requests.exceptions # pylint: disable=ungrouped-imports -import six # pylint: disable=ungrouped-imports -from urllib3.util.ssl_ import ( +from requests.packages.urllib3.util.ssl_ import ( # type: ignore create_urllib3_context, ) # pylint: disable=ungrouped-imports +import six # pylint: disable=ungrouped-imports from google.auth import environment_vars from google.auth import exceptions diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 550f89847f0bc39aa547564d83f593267259fa66..f0df346746ed938164c53d3c2b6229137951f350 100644 GIT binary patch literal 10324 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
S>RSh754;C(FDufKr=J4%VT=p_3Px-=YQNGC3y zkI-q!pUjbozPDs^(=E^t>K}GjncoN>7&>s^*~)of2;`&1d>No;_lZ!X;rE`Tp9;nD zT;|Ve-y5#d7e~>lD>(eHz(U;^wZ`FlP=(cG!wdnfZA#6w%co;SIhH7mNp!{o(&G%{j`-Mxqc8u6-2#(C8SC)z z#xR*Y%28}5H_r(nuD&EpNQ?4 zlzzZiGABq48HV|~1Ntk0B}%2I3k=GpEBXHZ9(Gf$Qwb`dxym)3-4rHmt4xK+a%$Y+ ze#0(NIWht>p4y}>f595fQuy-*Bpk|Mbsm~Rt@Q#CNrfXgn+yEN_eJISt0kowu=<@? z@DotV+~;l%$mDx%N>040B1+FA0K~N|gbUFaKPFdd_tI)&IoCYhE+Zf$UK~huHj(Od zk?U%_+py|pp14X24jETfxm0u~8a0nTMk2JQkM_XE5*S-p7z5m6-x!=tu!W=)=Ogpl z^|w5OH2{VHy(Jo&);=FL^i%3y#%L=EL0WE^K>PwP8L-MYy%p1# z{B&wDFHJ_4RgYfMbX6}}Ntp{2?q9{Vf^p9Qf))7OxO1uwH2Jh|A$G}DvbiX7V{g|Q zs%krG?b=ZY1HWV=d%KObGvaO3AN~$~&JY|)mT;{{HIn4y#4pfgXXf)oI zkJ4zNbzRGfpo^WnVCF5*DKFX{mxsiI;%uegD6fQ5o(2rQmN^)aKb#2ZrfxnNWkm<0 zLA;wZ{=tbtm{w@z60f0jW~lL|NU8f|g{BY0!in^lr++%1#GQxdw5rp+B-5|>lyI(g zL+)oF?l6nHyW_gEU1&v)HB}a8v=GF|hO<_#rJtG6J5N4eel4=>ZjSpE*t|6TsXmUyP=sBuCQ1?tiQ;)yNv+JohDUZ zY-#mc6B++aD|g3rK~984IQV1s#HUF4pxgVex#w!Zdh)+2B+Bk1QT^(~*?!C)GI69eM{lF@0<$Y+52x2kWi5#-fw!o+{6UUuei z;5LBHzkc}Ap*ecyQcRqUjkeTjx!wVKL&wTmcD7+h_c+Q#^g0*oO9&E_fK)Xk11E-!~ zI@cPC_{gxR4_HBQnh?=wf+BgT^M}khc%T{A5Wl#C()lq*yGez#*=nV%Z>~X#6sd`+*DNhz@pTw z-bwA$t=X{T)IMdsblbA2I);T|#YbzIalXvNK_neA= zOE2mpKY}E%vuBMomMYKvn52t}Xu!+p_J-ds*(LJV57iRsi!gK>Q zxoU{7Uh6$UUMFklj4peSB>I;$Ugy_ySA=bm6V}TK-Q_reD~(J=N=W}KL=6uPQwnqt zt-5~c%XjDFgf}cn5LCNtDjXT%ueoFeKzErqeRdtKf}JBXUlP-?`HtxdZ4+&*{Inp_0Gd0ZQrjot2x3UWJ8#{Bv7ai*BqS6q@h0?1;k3Cp)7%E;DVWxR zzU_1Ps?Xh-yw3mtwe;gdi#9jiVdf?kt{zdhcAKuO6-X{Wh^ZUzk_@f&A`iVaX%x~T zERP^9#xm8r8We6z2u#9As;&|enZ|8AkQno`fsv?auFB4%I;@CS!*%mcLg+c|{ur!= zqViv`Xs-ZrUdV2O28^HRo-Oq1{OWt=6!}TMR8F*G8`JA`*0?uo8mKsV%iw)Tq73Sr z485>*Ej6EuWqfSIj0+-~_=LvXhC|Zj#*osJa@Y*fe`7QT&&0xcguYZ= z5mMbMgxLa;qH?=fkLIM&3ISy2=?hS~dwZY*UmQIzk=NA6+X&zJ7r?ZC4YmvYqww);)g20f4~ysP zam1F3W*nBsEz52EkR8p+4*lYw>24wU%F_XFtbM$x004rga+!x>T3wJ5_8*^a(ytH#OH5U;lzZ-fm%Lu0t9=_@ot zV3MDdNFJx~Pq$k<+Bm#3xxx?$ehl z>)%w26M7w%$F}N3etx)pvAf=qzxv`kNTqX$MdkTeg}dSlq+3hYkKiD>RZPxr^c39P z)O1_PZjj*u@Wes%qlFDLwHp{ErmcIVVC-SoE0`4On=GG~Z&K01h{=FY*Ts z-iDK1FS=SC16Wn`G$?)_6?8rt{Co;>3fRCPK3?rmB8= z14f+&`!@Ig%MzpOLDPophz_<}DT0ZovAH_%Sw7poV*~iMJ$3RR#MT1| z%s;6!L>TD^bkNhxXVDk{kL$=`b{`n-G$P=4(M0ge$cE8HXq8@FfJ zZm_RV$l}Qquu+2YmWH7bXP(ye4^=%_8b2~s{tgN)$wul__jb^+at5)GJ8{ze@F=#W z{nYdPNuYz0i>d{8;@9nkxeBlVLLcp)wKSj3({eF0WB`?-cVR>I^}?)1n@Mg53K_1jx@w9!`? zC8zJMOL7`#j6YdJW88EqdaWwp4f9D$nnM-##Y}T{LcnYW?8ffcJ0sVH{I_TTt&hkf zPn!=o;!y+oGzBJ39lsZZpKx%YjraPanylqAJ1AJY1;vL>LThX3kv;9*N8Xjuk)zhW zbM^8xQ3)^KC(=gjNro@4x*3%yeX>ld68im$dn9lqG9>T`dp0Lp-b!MV z=~gnHY-PLPij#mu3HoQKR}=m4gsiBPW!zHGfwMob>KmHg zi*G?8qlFuLoEq3FQt{Xh@aGXxu$J{<99-2`3H^3oSW{V;^_r=UVa6Yi7PqT|8qE_x zb*$62tSz81GS1gRer-fG!AzD39-xwnqv2#&VZUR+@ZQ>bn&-lswPz7FwYi-|V|R9& z;x%05q}opT)>10C3SLLdT}^Ivt$m$lDA8}*5$>be&=2!LgIn(#_<+@ke5PA1NAj{TJXMFUFVE=bee$LfPUg4R;wJs!0)VZ@ z!k*ilOUcD7sW&yj?%8F*hp@04dmr~9%Q7G%rNFWgB2)eq=dg1@bS0hL80yr z23obl>&Yxfvd+10Eh(!G5FQIrX6m9rRhy5e#~rgEE0X-`wP*#)({y?Uxs+cZ+MVC& z)qznlM;cTK$s6Q$mt}@x5`BImY@sPNFFSG#k_y964ldPVtHn%#c}s6kz62h6^}Ek& zNY?3)OI{;5J3$U}7_)`E23b7H*3-xAu`+a2;`~K-g#>DV6vIVjDj Date: Mon, 24 Jan 2022 10:20:37 -0800 Subject: [PATCH 520/966] chore(main): release 2.4.1 (#955) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 43376ec7a012..a93eece7d16a 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +### [2.4.1](https://github.com/googleapis/google-auth-library-python/compare/v2.4.0...v2.4.1) (2022-01-21) + + +### Bug Fixes + +* urllib3 import ([#953](https://github.com/googleapis/google-auth-library-python/issues/953)) ([c8b5cae](https://github.com/googleapis/google-auth-library-python/commit/c8b5cae3da5eb9d40067d38dac51a4a8c1e0763e)) + ## [2.4.0](https://github.com/googleapis/google-auth-library-python/compare/v2.3.3...v2.4.0) (2022-01-20) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 562d21480bf1..9ccf875c040a 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.4.0" +__version__ = "2.4.1" From 716512135105901d6e4502b466235754669ac66a Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Tue, 25 Jan 2022 03:21:25 -0800 Subject: [PATCH 521/966] chore: update user cred for system test (#957) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index f0df346746ed938164c53d3c2b6229137951f350..77e60a3091201b58e1e56dd722425a57acd72775 100644 GIT binary patch literal 10324 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 mZa5AE>1)MOG-%qDIgvIBk+gnOfM< Date: Tue, 25 Jan 2022 15:36:43 -0500 Subject: [PATCH 522/966] feat: ADC can load an impersonated service account credentials. (#956) * Make code changes in _default * Add unit tests. * Fix docstring. Co-authored-by: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> --- packages/google-auth/google/auth/_default.py | 151 +++++++++++++----- ...ervice_account_authorized_user_source.json | 13 ++ ...ervice_account_service_account_source.json | 17 ++ ...ed_service_account_with_quota_project.json | 14 ++ packages/google-auth/tests/test__default.py | 92 +++++++++++ 5 files changed, 251 insertions(+), 36 deletions(-) create mode 100644 packages/google-auth/tests/data/impersonated_service_account_authorized_user_source.json create mode 100644 packages/google-auth/tests/data/impersonated_service_account_service_account_source.json create mode 100644 packages/google-auth/tests/data/impersonated_service_account_with_quota_project.json diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index 54d6561649e8..c70dccfaa1ff 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -35,7 +35,13 @@ _AUTHORIZED_USER_TYPE = "authorized_user" _SERVICE_ACCOUNT_TYPE = "service_account" _EXTERNAL_ACCOUNT_TYPE = "external_account" -_VALID_TYPES = (_AUTHORIZED_USER_TYPE, _SERVICE_ACCOUNT_TYPE, _EXTERNAL_ACCOUNT_TYPE) +_IMPERSONATED_SERVICE_ACCOUNT_TYPE = "impersonated_service_account" +_VALID_TYPES = ( + _AUTHORIZED_USER_TYPE, + _SERVICE_ACCOUNT_TYPE, + _EXTERNAL_ACCOUNT_TYPE, + _IMPERSONATED_SERVICE_ACCOUNT_TYPE, +) # Help message when no credentials can be found. _HELP_MESSAGE = """\ @@ -79,7 +85,8 @@ def load_credentials_from_file( """Loads Google credentials from a file. The credentials file must be a service account key, stored authorized - user credentials or external account credentials. + user credentials, external account credentials, or impersonated service + account credentials. Args: filename (str): The full path to the credentials file. @@ -119,42 +126,25 @@ def load_credentials_from_file( "File {} is not a valid json file.".format(filename), caught_exc ) six.raise_from(new_exc, caught_exc) + return _load_credentials_from_info( + filename, info, scopes, default_scopes, quota_project_id, request + ) + - # The type key should indicate that the file is either a service account - # credentials file or an authorized user credentials file. +def _load_credentials_from_info( + filename, info, scopes, default_scopes, quota_project_id, request +): credential_type = info.get("type") if credential_type == _AUTHORIZED_USER_TYPE: - from google.oauth2 import credentials - - try: - credentials = credentials.Credentials.from_authorized_user_info( - info, scopes=scopes - ) - except ValueError as caught_exc: - msg = "Failed to load authorized user credentials from {}".format(filename) - new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) - six.raise_from(new_exc, caught_exc) - if quota_project_id: - credentials = credentials.with_quota_project(quota_project_id) - if not credentials.quota_project_id: - _warn_about_problematic_credentials(credentials) - return credentials, None + credentials, project_id = _get_authorized_user_credentials( + filename, info, scopes + ) elif credential_type == _SERVICE_ACCOUNT_TYPE: - from google.oauth2 import service_account - - try: - credentials = service_account.Credentials.from_service_account_info( - info, scopes=scopes, default_scopes=default_scopes - ) - except ValueError as caught_exc: - msg = "Failed to load service account credentials from {}".format(filename) - new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) - six.raise_from(new_exc, caught_exc) - if quota_project_id: - credentials = credentials.with_quota_project(quota_project_id) - return credentials, info.get("project_id") + credentials, project_id = _get_service_account_credentials( + filename, info, scopes, default_scopes + ) elif credential_type == _EXTERNAL_ACCOUNT_TYPE: credentials, project_id = _get_external_account_credentials( @@ -164,10 +154,10 @@ def load_credentials_from_file( default_scopes=default_scopes, request=request, ) - if quota_project_id: - credentials = credentials.with_quota_project(quota_project_id) - return credentials, project_id - + elif credential_type == _IMPERSONATED_SERVICE_ACCOUNT_TYPE: + credentials, project_id = _get_impersonated_service_account_credentials( + filename, info, scopes + ) else: raise exceptions.DefaultCredentialsError( "The file {file} does not have a valid type. " @@ -175,6 +165,8 @@ def load_credentials_from_file( file=filename, type=credential_type, valid_types=_VALID_TYPES ) ) + credentials = _apply_quota_project_id(credentials, quota_project_id) + return credentials, project_id def _get_gcloud_sdk_credentials(quota_project_id=None): @@ -371,6 +363,93 @@ def get_api_key_credentials(api_key_value): return api_key.Credentials(api_key_value) +def _get_authorized_user_credentials(filename, info, scopes=None): + from google.oauth2 import credentials + + try: + credentials = credentials.Credentials.from_authorized_user_info( + info, scopes=scopes + ) + except ValueError as caught_exc: + msg = "Failed to load authorized user credentials from {}".format(filename) + new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) + six.raise_from(new_exc, caught_exc) + return credentials, None + + +def _get_service_account_credentials(filename, info, scopes=None, default_scopes=None): + from google.oauth2 import service_account + + try: + credentials = service_account.Credentials.from_service_account_info( + info, scopes=scopes, default_scopes=default_scopes + ) + except ValueError as caught_exc: + msg = "Failed to load service account credentials from {}".format(filename) + new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) + six.raise_from(new_exc, caught_exc) + return credentials, info.get("project_id") + + +def _get_impersonated_service_account_credentials(filename, info, scopes): + from google.auth import impersonated_credentials + + try: + source_credentials_info = info.get("source_credentials") + source_credentials_type = source_credentials_info.get("type") + if source_credentials_type == _AUTHORIZED_USER_TYPE: + source_credentials, _ = _get_authorized_user_credentials( + filename, source_credentials_info + ) + elif source_credentials_type == _SERVICE_ACCOUNT_TYPE: + source_credentials, _ = _get_service_account_credentials( + filename, source_credentials_info + ) + else: + raise ValueError( + "source credential of type {} is not supported.".format( + source_credentials_type + ) + ) + impersonation_url = info.get("service_account_impersonation_url") + start_index = impersonation_url.rfind("/") + end_index = impersonation_url.find(":generateAccessToken") + if start_index == -1 or end_index == -1 or start_index > end_index: + raise ValueError( + "Cannot extract target principal from {}".format(impersonation_url) + ) + target_principal = impersonation_url[start_index + 1 : end_index] + delegates = info.get("delegates") + quota_project_id = info.get("quota_project_id") + credentials = impersonated_credentials.Credentials( + source_credentials, + target_principal, + scopes, + delegates, + quota_project_id=quota_project_id, + ) + except ValueError as caught_exc: + msg = "Failed to load impersonated service account credentials from {}".format( + filename + ) + new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) + six.raise_from(new_exc, caught_exc) + return credentials, None + + +def _apply_quota_project_id(credentials, quota_project_id): + if quota_project_id: + credentials = credentials.with_quota_project(quota_project_id) + + from google.oauth2 import credentials as authorized_user_credentials + + if isinstance(credentials, authorized_user_credentials.Credentials) and ( + not credentials.quota_project_id + ): + _warn_about_problematic_credentials(credentials) + return credentials + + def default(scopes=None, request=None, quota_project_id=None, default_scopes=None): """Gets the default credentials for the current environment. diff --git a/packages/google-auth/tests/data/impersonated_service_account_authorized_user_source.json b/packages/google-auth/tests/data/impersonated_service_account_authorized_user_source.json new file mode 100644 index 000000000000..0e545392ccff --- /dev/null +++ b/packages/google-auth/tests/data/impersonated_service_account_authorized_user_source.json @@ -0,0 +1,13 @@ +{ + "delegates": [ + "service-account-delegate@example.com" + ], + "service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/service-account-target@example.com:generateAccessToken", + "source_credentials": { + "client_id": "123", + "client_secret": "secret", + "refresh_token": "alabalaportocala", + "type": "authorized_user" + }, + "type": "impersonated_service_account" +} \ No newline at end of file diff --git a/packages/google-auth/tests/data/impersonated_service_account_service_account_source.json b/packages/google-auth/tests/data/impersonated_service_account_service_account_source.json new file mode 100644 index 000000000000..e1ff8e81f7c1 --- /dev/null +++ b/packages/google-auth/tests/data/impersonated_service_account_service_account_source.json @@ -0,0 +1,17 @@ +{ + "delegates": [ + "service-account-delegate@example.com" + ], + "service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/service-account-target@example.com:generateAccessToken", + "source_credentials": { + "type": "service_account", + "project_id": "example-project", + "private_key_id": "1", + "private_key": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEA4ej0p7bQ7L/r4rVGUz9RN4VQWoej1Bg1mYWIDYslvKrk1gpj\n7wZgkdmM7oVK2OfgrSj/FCTkInKPqaCR0gD7K80q+mLBrN3PUkDrJQZpvRZIff3/\nxmVU1WeruQLFJjnFb2dqu0s/FY/2kWiJtBCakXvXEOb7zfbINuayL+MSsCGSdVYs\nSliS5qQpgyDap+8b5fpXZVJkq92hrcNtbkg7hCYUJczt8n9hcCTJCfUpApvaFQ18\npe+zpyl4+WzkP66I28hniMQyUlA1hBiskT7qiouq0m8IOodhv2fagSZKjOTTU2xk\nSBc//fy3ZpsL7WqgsZS7Q+0VRK8gKfqkxg5OYQIDAQABAoIBAQDGGHzQxGKX+ANk\nnQi53v/c6632dJKYXVJC+PDAz4+bzU800Y+n/bOYsWf/kCp94XcG4Lgsdd0Gx+Zq\nHD9CI1IcqqBRR2AFscsmmX6YzPLTuEKBGMW8twaYy3utlFxElMwoUEsrSWRcCA1y\nnHSDzTt871c7nxCXHxuZ6Nm/XCL7Bg8uidRTSC1sQrQyKgTPhtQdYrPQ4WZ1A4J9\nIisyDYmZodSNZe5P+LTJ6M1SCgH8KH9ZGIxv3diMwzNNpk3kxJc9yCnja4mjiGE2\nYCNusSycU5IhZwVeCTlhQGcNeV/skfg64xkiJE34c2y2ttFbdwBTPixStGaF09nU\nZ422D40BAoGBAPvVyRRsC3BF+qZdaSMFwI1yiXY7vQw5+JZh01tD28NuYdRFzjcJ\nvzT2n8LFpj5ZfZFvSMLMVEFVMgQvWnN0O6xdXvGov6qlRUSGaH9u+TCPNnIldjMP\nB8+xTwFMqI7uQr54wBB+Poq7dVRP+0oHb0NYAwUBXoEuvYo3c/nDoRcZAoGBAOWl\naLHjMv4CJbArzT8sPfic/8waSiLV9Ixs3Re5YREUTtnLq7LoymqB57UXJB3BNz/2\neCueuW71avlWlRtE/wXASj5jx6y5mIrlV4nZbVuyYff0QlcG+fgb6pcJQuO9DxMI\naqFGrWP3zye+LK87a6iR76dS9vRU+bHZpSVvGMKJAoGAFGt3TIKeQtJJyqeUWNSk\nklORNdcOMymYMIlqG+JatXQD1rR6ThgqOt8sgRyJqFCVT++YFMOAqXOBBLnaObZZ\nCFbh1fJ66BlSjoXff0W+SuOx5HuJJAa5+WtFHrPajwxeuRcNa8jwxUsB7n41wADu\nUqWWSRedVBg4Ijbw3nWwYDECgYB0pLew4z4bVuvdt+HgnJA9n0EuYowVdadpTEJg\nsoBjNHV4msLzdNqbjrAqgz6M/n8Ztg8D2PNHMNDNJPVHjJwcR7duSTA6w2p/4k28\nbvvk/45Ta3XmzlxZcZSOct3O31Cw0i2XDVc018IY5be8qendDYM08icNo7vQYkRH\n504kQQKBgQDjx60zpz8ozvm1XAj0wVhi7GwXe+5lTxiLi9Fxq721WDxPMiHDW2XL\nYXfFVy/9/GIMvEiGYdmarK1NW+VhWl1DC5xhDg0kvMfxplt4tynoq1uTsQTY31Mx\nBeF5CT/JuNYk3bEBF0H/Q3VGO1/ggVS+YezdFbLWIRoMnLj6XCFEGg==\n-----END RSA PRIVATE KEY-----\n", + "client_email": "service-account@example.com", + "client_id": "1234", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://accounts.google.com/o/oauth2/token" + }, + "type": "impersonated_service_account" +} \ No newline at end of file diff --git a/packages/google-auth/tests/data/impersonated_service_account_with_quota_project.json b/packages/google-auth/tests/data/impersonated_service_account_with_quota_project.json new file mode 100644 index 000000000000..89db9617c467 --- /dev/null +++ b/packages/google-auth/tests/data/impersonated_service_account_with_quota_project.json @@ -0,0 +1,14 @@ +{ + "delegates": [ + "service-account-delegate@example.com" + ], + "quota_project_id": "quota_project", + "service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/service-account-target@example.com:generateAccessToken", + "source_credentials": { + "client_id": "123", + "client_secret": "secret", + "refresh_token": "alabalaportocala", + "type": "authorized_user" + }, + "type": "impersonated_service_account" +} \ No newline at end of file diff --git a/packages/google-auth/tests/test__default.py b/packages/google-auth/tests/test__default.py index 67795ef02979..1c27ac11c63f 100644 --- a/packages/google-auth/tests/test__default.py +++ b/packages/google-auth/tests/test__default.py @@ -28,6 +28,7 @@ from google.auth import exceptions from google.auth import external_account from google.auth import identity_pool +from google.auth import impersonated_credentials from google.oauth2 import service_account import google.oauth2.credentials @@ -128,6 +129,19 @@ "workforce_pool_user_project": WORKFORCE_POOL_USER_PROJECT, } +IMPERSONATED_SERVICE_ACCOUNT_AUTHORIZED_USER_SOURCE_FILE = os.path.join( + DATA_DIR, "impersonated_service_account_authorized_user_source.json" +) + +IMPERSONATED_SERVICE_ACCOUNT_WITH_QUOTA_PROJECT_FILE = os.path.join( + DATA_DIR, "impersonated_service_account_with_quota_project.json" +) + +IMPERSONATED_SERVICE_ACCOUNT_SERVICE_ACCOUNT_SOURCE_FILE = os.path.join( + DATA_DIR, "impersonated_service_account_service_account_source.json" +) + + MOCK_CREDENTIALS = mock.Mock(spec=credentials.CredentialsWithQuotaProject) MOCK_CREDENTIALS.with_quota_project.return_value = MOCK_CREDENTIALS @@ -278,6 +292,84 @@ def test_load_credentials_from_file_service_account_bad_format(tmpdir): assert excinfo.match(r"missing fields") +def test_load_credentials_from_file_impersonated_with_authorized_user_source(): + credentials, project_id = _default.load_credentials_from_file( + IMPERSONATED_SERVICE_ACCOUNT_AUTHORIZED_USER_SOURCE_FILE + ) + assert isinstance(credentials, impersonated_credentials.Credentials) + assert isinstance( + credentials._source_credentials, google.oauth2.credentials.Credentials + ) + assert credentials.service_account_email == "service-account-target@example.com" + assert credentials._delegates == ["service-account-delegate@example.com"] + assert not credentials._quota_project_id + assert not credentials._target_scopes + assert project_id is None + + +def test_load_credentials_from_file_impersonated_with_quota_project(): + credentials, _ = _default.load_credentials_from_file( + IMPERSONATED_SERVICE_ACCOUNT_WITH_QUOTA_PROJECT_FILE + ) + assert isinstance(credentials, impersonated_credentials.Credentials) + assert credentials._quota_project_id == "quota_project" + + +def test_load_credentials_from_file_impersonated_with_service_account_source(): + credentials, _ = _default.load_credentials_from_file( + IMPERSONATED_SERVICE_ACCOUNT_SERVICE_ACCOUNT_SOURCE_FILE + ) + assert isinstance(credentials, impersonated_credentials.Credentials) + assert isinstance(credentials._source_credentials, service_account.Credentials) + assert not credentials._quota_project_id + + +def test_load_credentials_from_file_impersonated_passing_quota_project(): + credentials, _ = _default.load_credentials_from_file( + IMPERSONATED_SERVICE_ACCOUNT_SERVICE_ACCOUNT_SOURCE_FILE, + quota_project_id="new_quota_project", + ) + assert credentials._quota_project_id == "new_quota_project" + + +def test_load_credentials_from_file_impersonated_passing_scopes(): + credentials, _ = _default.load_credentials_from_file( + IMPERSONATED_SERVICE_ACCOUNT_SERVICE_ACCOUNT_SOURCE_FILE, + scopes=["scope1", "scope2"], + ) + assert credentials._target_scopes == ["scope1", "scope2"] + + +def test_load_credentials_from_file_impersonated_wrong_target_principal(tmpdir): + + with open(IMPERSONATED_SERVICE_ACCOUNT_AUTHORIZED_USER_SOURCE_FILE) as fh: + impersonated_credentials_info = json.load(fh) + impersonated_credentials_info[ + "service_account_impersonation_url" + ] = "something_wrong" + + jsonfile = tmpdir.join("invalid.json") + jsonfile.write(json.dumps(impersonated_credentials_info)) + with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: + _default.load_credentials_from_file(str(jsonfile)) + + assert excinfo.match(r"Cannot extract target principal") + + +def test_load_credentials_from_file_impersonated_wrong_source_type(tmpdir): + + with open(IMPERSONATED_SERVICE_ACCOUNT_AUTHORIZED_USER_SOURCE_FILE) as fh: + impersonated_credentials_info = json.load(fh) + impersonated_credentials_info["source_credentials"]["type"] = "external_account" + + jsonfile = tmpdir.join("invalid.json") + jsonfile.write(json.dumps(impersonated_credentials_info)) + with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: + _default.load_credentials_from_file(str(jsonfile)) + + assert excinfo.match(r"source credential of type external_account is not supported") + + @EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH def test_load_credentials_from_file_external_account_identity_pool( get_project_id, tmpdir From 18d145fdabb50cb8c82d92ae62b998dccdb60043 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 25 Jan 2022 13:24:19 -0800 Subject: [PATCH 523/966] chore(main): release 2.5.0 (#960) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index a93eece7d16a..9249639b0a36 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.5.0](https://github.com/googleapis/google-auth-library-python/compare/v2.4.1...v2.5.0) (2022-01-25) + + +### Features + +* ADC can load an impersonated service account credentials. ([#956](https://github.com/googleapis/google-auth-library-python/issues/956)) ([a8eb4c8](https://github.com/googleapis/google-auth-library-python/commit/a8eb4c8693055a3420cfe9c3420aae2bc8cd465a)) + ### [2.4.1](https://github.com/googleapis/google-auth-library-python/compare/v2.4.0...v2.4.1) (2022-01-21) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 9ccf875c040a..e6c150fc94cb 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.4.1" +__version__ = "2.5.0" From 8082f771bbe873d8dff7ada7f143c7889d97aa4b Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Fri, 28 Jan 2022 16:47:19 -0800 Subject: [PATCH 524/966] chore: update user creds for system test (#963) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 77e60a3091201b58e1e56dd722425a57acd72775..c8678e3ee8cc6ce72cacd17119f4ad882da890be 100644 GIT binary patch literal 10324 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 literal 10324 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 Date: Fri, 28 Jan 2022 20:10:48 -0500 Subject: [PATCH 525/966] feat: ADC can load an impersonated service account credentials. (#962) * Make the impersonated credentials to be Scoped so default func will add scopes. * Add tests for impersonated service account credentials. Co-authored-by: Anthonios Partheniou --- .../google/auth/impersonated_credentials.py | 20 ++++++- packages/google-auth/tests/test__default.py | 54 +++++++++++++++++++ .../tests/test_impersonated_credentials.py | 16 ++++++ 3 files changed, 89 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index 80d6fdfdcd13..48acd1bb31e9 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -120,7 +120,9 @@ def _make_iam_token_request( six.raise_from(new_exc, caught_exc) -class Credentials(credentials.CredentialsWithQuotaProject, credentials.Signing): +class Credentials( + credentials.Scoped, credentials.CredentialsWithQuotaProject, credentials.Signing +): """This module defines impersonated credentials which are essentially impersonated identities. @@ -309,6 +311,10 @@ def service_account_email(self): def signer(self): return self + @property + def requires_scopes(self): + return not self._target_scopes + @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) def with_quota_project(self, quota_project_id): return self.__class__( @@ -321,6 +327,18 @@ def with_quota_project(self, quota_project_id): iam_endpoint_override=self._iam_endpoint_override, ) + @_helpers.copy_docstring(credentials.Scoped) + def with_scopes(self, scopes, default_scopes=None): + return self.__class__( + self._source_credentials, + target_principal=self._target_principal, + target_scopes=scopes or default_scopes, + delegates=self._delegates, + lifetime=self._lifetime, + quota_project_id=self._quota_project_id, + iam_endpoint_override=self._iam_endpoint_override, + ) + class IDTokenCredentials(credentials.CredentialsWithQuotaProject): """Open ID Connect ID Token-based service account credentials. diff --git a/packages/google-auth/tests/test__default.py b/packages/google-auth/tests/test__default.py index 1c27ac11c63f..35a5dc02101d 100644 --- a/packages/google-auth/tests/test__default.py +++ b/packages/google-auth/tests/test__default.py @@ -1089,6 +1089,60 @@ def test_default_no_warning_with_quota_project_id_for_user_creds(get_adc_path): credentials, project_id = _default.default(quota_project_id="project-foo") +@mock.patch( + "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True +) +def test_default_impersonated_service_account(get_adc_path): + get_adc_path.return_value = IMPERSONATED_SERVICE_ACCOUNT_AUTHORIZED_USER_SOURCE_FILE + + credentials, _ = _default.default() + + assert isinstance(credentials, impersonated_credentials.Credentials) + assert isinstance( + credentials._source_credentials, google.oauth2.credentials.Credentials + ) + assert credentials.service_account_email == "service-account-target@example.com" + assert credentials._delegates == ["service-account-delegate@example.com"] + assert not credentials._quota_project_id + assert not credentials._target_scopes + + +@mock.patch( + "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True +) +def test_default_impersonated_service_account_set_scopes(get_adc_path): + get_adc_path.return_value = IMPERSONATED_SERVICE_ACCOUNT_AUTHORIZED_USER_SOURCE_FILE + scopes = ["scope1", "scope2"] + + credentials, _ = _default.default(scopes=scopes) + assert credentials._target_scopes == scopes + + +@mock.patch( + "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True +) +def test_default_impersonated_service_account_set_default_scopes(get_adc_path): + get_adc_path.return_value = IMPERSONATED_SERVICE_ACCOUNT_AUTHORIZED_USER_SOURCE_FILE + default_scopes = ["scope1", "scope2"] + + credentials, _ = _default.default(default_scopes=default_scopes) + assert credentials._target_scopes == default_scopes + + +@mock.patch( + "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True +) +def test_default_impersonated_service_account_set_both_scopes_and_default_scopes( + get_adc_path +): + get_adc_path.return_value = IMPERSONATED_SERVICE_ACCOUNT_AUTHORIZED_USER_SOURCE_FILE + scopes = ["scope1", "scope2"] + default_scopes = ["scope3", "scope4"] + + credentials, _ = _default.default(scopes=scopes, default_scopes=default_scopes) + assert credentials._target_scopes == scopes + + def test__get_api_key_credentials_no_env_var(): cred, project_id = _default._get_api_key_credentials(quota_project_id="project-foo") assert cred is None diff --git a/packages/google-auth/tests/test_impersonated_credentials.py b/packages/google-auth/tests/test_impersonated_credentials.py index 58d159a59fbb..f65fb754149e 100644 --- a/packages/google-auth/tests/test_impersonated_credentials.py +++ b/packages/google-auth/tests/test_impersonated_credentials.py @@ -399,6 +399,22 @@ def test_with_quota_project_iam_endpoint_override( request_kwargs = request.call_args[1] assert request_kwargs["url"] == self.IAM_ENDPOINT_OVERRIDE + def test_with_scopes(self): + credentials = self.make_credentials() + credentials._target_scopes = [] + assert credentials.requires_scopes is True + credentials = credentials.with_scopes(["fake_scope1", "fake_scope2"]) + assert credentials.requires_scopes is False + assert credentials._target_scopes == ["fake_scope1", "fake_scope2"] + + def test_with_scopes_provide_default_scopes(self): + credentials = self.make_credentials() + credentials._target_scopes = [] + credentials = credentials.with_scopes( + ["fake_scope1"], default_scopes=["fake_scope2"] + ) + assert credentials._target_scopes == ["fake_scope1"] + def test_id_token_success( self, mock_donor_credentials, mock_authorizedsession_idtoken ): From 07fc1602a6b2607ef5a8c46160d31bc1361affbf Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Mon, 31 Jan 2022 11:33:20 -0800 Subject: [PATCH 526/966] chore: update user cred for system test (#966) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index c8678e3ee8cc6ce72cacd17119f4ad882da890be..ab9084e82c8b526bec4de7e8dc8a3bb01f7b730d 100644 GIT binary patch literal 10324 zcmV-aD67{BB>?tKRTCKUd$A_yEN?$b;UH5OM8rU%Uv?==KwmRm%si+9yUP-)PyiA0 z;H8|79u=Ww$tT+&xSS@DX{za^roPhWz_p356pp=g5kEIa!S@|0cgFt{D(H}pSUFQJ zW+*GzNir>TO7wds40xt6WkfJ@K_Z9w?P($#A;{rvjG_I!@W9DWQ@)wdbB$w zIyJrYD!v9uw|r#H)5e<%t9+|0v$ikO+C9B_iC*)9x5i+bkcI4|O0?HvC;H?J5< zX{N2&hp|BfC7{NLy~2Db9E7qJ*sH3!s=nfSuIEg0RjQI0ldM%<7C2YI9my|+DYAhF<`Y0%S+`Y zBPlt#SVao%76lhGht^*6ZhVV}-NqifBcWV}{;l9$kFN#2j(r84z|(O2COF6(1E5WZI z#4CqtOt{Pf(jeFS7U3Xm1?O&f z)Mn7ph_;os)wQPe7PdV1_$<^gEfm z-Qx%$foyz367w+UIhCN6yMx`2ET33Q;usExEtC;{9sO8Lz|k_&dnwIBInko0 zs^USe^eU!Y%0wIW>7gftp>l=370~-l<*GSkf+^3YK-UaigSkPIcjM)cokWMg zBUUNFo{L8I{lytT5q?nZtsj4c*oWgJQG5rh*@T?+qU|i4B;fx|eM-9;2aS7fVcYb+)+5?#jHXQ3sMA@*hW4S#RV^w~p}P_W_|uPq_zv%1 zyGVo5XOO))YT+%VBaJf<58P0+^V0{7aQ2};!4fNYm6X$F7lt5ZGoA+U|CbUWup~c0 zJBJp{a{4Z>!D5`dT$SS~JF<$MqE7_$1iv;^@>I71kZ~}MoQZ+jI}e^485vV%c9id5 zz~g5UMYk^tw_X)8`E98vkq_%9ZPMy4zrDRxP+dD?4G4mM({gd{xa!X+`zA-BiL03Q zMdf=NOB2?JKmrWh3^cF#t^c#T`?Au6a{A?4#W0Lv65U`F;xIRAsV@oQq7};cAnE0A zTt{Z5R+Z8Aah^JKkbI_Mtza_v88)hPDSDsgZ2EgyTwg7XY-`$u3$|6d!b~z|NIU## zocso@l~1-IeLsLwMD&e!uEnoJMj$Fn;0a%I>+h9RHn~I9B-+3Zt{*xK8c|pXV`1A< z3F?&92HQ#L9g_ES^UXa*{xNwAlAtx^gx04&2}DVNH)K2GoLbSOCDlQGbno!H^&B*g zp}>*x0)@PCWrMJ?46(!4+^egm@8O=VbC-hY3+;bSbr96qWakI+yal<0m*PNI@AGs?Z2H zoFeaTak2t--R8h)aMGbzH_aM;B(dMTvB(ey`y0S=_v2y$Yemb*!X8HhHEMHCYMo4a28fqV48B1uo1T_%mjT_@|@6Lu%+*MO0qS^fLOl|mrSQVN6u`7iau^CWZyU-+hbuja+{CI=FxtB)XOf7)WJa3Q zCuaU&$B>Um2NSUjqI~vOjI`-3Xcw0*6T^$7Sm(aXx2tm|$i9F4-Wf>iL4o3XY$uW51FFZ54;2Is12 zJ_4@y7|l)Mw=WE*V08cq%G-$zmT4i5Ibv*#iZznY7^n5vt~Bw29m0U0IZprv!1z8X zGR;t0u{3^C@wsxk%EdoxoO3uamMM@HX``ix0{a%hlh)N38HT+bqq@_+((HaplnmRE zz4BgOAp3*jyGt}y0HvWgJ~?-`GNm9(4Ov(pa14GyEMmaTIZQ#$n_}39rmBMLTW7a#qd5A|K=~d4lOaMomb>RLl zeexdfhulh?pt#$l>cxGbbq7|VT%&3Q!{iw(^1<`>!A5;Atl*DYsb#iiEwCzj*Q|Do z=RQ+1lKNgUJ@)pBt}gD5PMKy5DQS*84`)_|3GFuW?ojbReGix&hn@hF%3R`u!7+F_ zaNwO~=V`6876&IlLGSSPiYtOoZZ;00YoPz)?ltkgObpOch+-q8tH`+&FvWwT{p73q zxh#AG3&FE{TUD6jPYpQ8tUB1L3prf%twYeLS?f^Kh!Xn3g@L6L%YeB>RyQ~(hG47mg!p&i1PfVU zrQ!(#0eQzaDKF9;2N2;L)Kk33T29c70n&h;ajZv}7tY$yH*o^9+LdzqustEG*QClp z)WDkn57R7W^@tQtDb!`z^cX$}=2^Sa7qAum_MndjjMYFrgscfNgPnGF9vA#KBSs{ymZD}Opo zK5CA&HXDuloX!8v&0LG^hIn@=ec4uY%7j65j)<}^zkn*rlA%N zo(iK7&}|`ftwR);E;&oQi5b8$)uCm`0V@r>9Tfe0$o$>SaMebBX5|jO&_5DFNAXM~ zgc-lEAed1R_J`3NPdDX3?F1v5jWldTnL+zJ)?M3u90RADW^zH~bJr4E<1RkU!h;GA zq_wD7)H8sj5{AJjj_^Wg?SD_|4zx=QRK3h)M)$t0bZ?ldaG}4`u!eK(#{J_@nwG7p zT;zFlg#F{b`>`ZqFg7E&Vypmn)7EJ}bV5~(>H|DBG|D<iShoFxfpi^=L<_KD52e5LOq*K`R_$X_y`7FTH`Xl` zvnMdU9|;N3zcMiANOH4tG4-wcJ{PTgSoWVlt^YUi=k{HcUG8OF-~tF|=NW_9Rv)O5 zvHWy(2@%wA)bn&>pBrg@4q{Ys!;VevNI>mCz7y zr0=_)X(=>G(`^cDz>l9G!(u)Vg$wI~ccd6D`a-F~LH`^z%0>h;?$?Yu&c(ZU4E3Lo z%Ks8U>vqPJUAdDC0_jxHl2N3w5hKMG#47KJ_3GR>yY~}+GZc{M3izr@k=IS7JgdVG zS-xS+valY*426e9*!j=b9zNwFjdTJnH4mF>IzTic9xo{Y?SH%rla&%+IH~QWfxEgW zyl!DuD+)R1AmF8j!~)IR%;`M#V_gQddV;zX0M2Os-Kx>NuiR>Xt7;{CBc`Y|7iq$| zZ0)59G59v28)|@E5@ZB5I7m;Na*Nv!7NNUFIe$AkDo6&zsjYHv zrP6!;UAiDZFyJfr#3n=rKWqfJ*Kdyq7t=LNZ`FpPg*)lCAM+ODZ=lHU5A@H#y(0lS&-E%bpd2mNTGF8obwe7k|1 z2#xlcgqDjW!z$KkYj9NG(>uF0uV?WHtUJlU(*bSZQMxWE120fmVs3<5%$H!u_Hksy zJ4Ow8c#R)UhsF;Lbd|F3FbPTS;BpoS7GQ@rFC@QeD_mya%j-N&lFTWJybtwZnAcY0 zVrFbCGsttN2tu0kR8;PKknXZj@e@uiIdL0qxr) zC`n0H6Q_d}vI}x2*mi#-oMtOkEEW>~DfNd6_+G5>aNlfA0b|Z=3e?H-OBy*XR0h|! zbt|aDVb*10b*xrz^OyxN6Yx3XQu=qELSINJcxS2awYQ^7#;tjjycUz6*Q7EC-g`bT zKfzLL1a7ac$+0!l#{suuxSXR`9AZ`UokCs&`r>T>DU&#fHt72>%@2GPlW^r=W71h$ zcIX}f8p&$T89Q>ts>UGaVdJKeWeem#pZKn2Gt8(sHYcJl4OIq9C9pL3Uh``K%*A#H(dl@84ZYi!@7*4?HU2;z8LctjwB%bL#UkYj@P%Gi9c- z?9ie4yu|R@>#bwj0;Ty60gL@7$^C)JrAm+0imgk?!~U%@UMUFy3@>}qv&U~;u#w)& z54B$hn>n0{1dIp0CVs@<3ycJzRJ1s9O(*L)_j>3^U~v`bxPv7q0YuYef&5%RQPdi6 zS8M|@KXxVK@S9R;(pEh=j_*p_FGKc}A{*vghW4b+(ygGJcT<4dRqw&UI{k$tyM^yX^32zPTqeq!wdCW$vgL4VBn+Yo`?IW! z6Ptxxv?gkRx^S)B!(Z2P!*QgSCdkY`%N{FMJ%#_F_!8~(%_3KZM$U%zk=R@0laM2g zIzk#yYJb+?2sxljbxQR>Nr5%Ay;%#k18jO68Y)+DalZ3IibDFv-MDy4#FpW()N#yd z*PqIYV;GwQU6uYJ@Iqi}>U^3b(l{A*WCzz9|JC9$K4UocFWGM{`a5$HNJ00ZW1@RQ z0vG7uw|o|PBA0m<5V;GTg{b)oxrdBqDHrbrUD!{Rdyc zDS#oCB^m z6wa^&$L^@=FytAg!(E$p_i&{fWc(!Q<@#f$&Nb)eliZ|;Ex*piY(sZM-F>gEQGJ`{ zBjw!Ke>p#kYbYhTzHaVbKk(dJ@XmZvC1Jz~;NF*Y$TLdCZw;;W%gD>ra_5=TX3n#I zjr65kOk{RLIe4%9sabGDsC-4#=krxpqJ5Hs2|9>0Bu=x%teXuF!Fq$4s?D%0Scx6p zF?>}^{fn^6&qwf;sl$lpUQQtvV)DR}U%WEc940aA)|cHn z&3A)!SBd(Or?L6Q)@l_ic&N>3EaBN-yNr)YC_ez=y$VvUS=v<6U`?tj#rDw#+tc(d zcZF=im*NYns4Y`4k%b0E``qoKQ%{Np(`^Q!HSgfsWX3&f@d+iku6nmJ+n zCRaYs?#VK?$auA4qr5o+u-iB(b?_6Wixi&G%t4~S9OF}2Y%V?)v6PG-YQLZmX)49j z3H5oeE`sWL`gM?H98^Md%tWC&8DQHCQjnF4x60BQv3_4%<(ao{p+_q{YTe`-S0^>k z2k+i-42ZIfl!Dwpz!>eO{4^q{1-)$QY1Y5zLm4z?=oI&so>m`*(n zuUgfN%LJKyfr-Khv9Ksg{2eOVFFk?uVcbFL{Onaw`WwM1PImlhJ{RXetAn$^k|O@s zi=dqB6sr<@R=xhYsni*IkoJi(q4Cfs6+0c-m%z6Nz*`|@$nmz_LR_t>JqlR{dc2lt zpvUFQY-78?4=DD1i2{Nu+I$oIgX{gSGtj<3Z+7KNx)N6`g&=BQMDp8PU~ICP067q8 z;JmaSx^4+Bn(TB~YKGtzQrw$Oa_2s^s44afW~cLhV*|FJppo6CCfGmPwosCBw!4; z16MAQ@~R#rxTtV#nAODBj`8=SsVAy(wKhIAt~8Up@5o?T!O=a$0;ornYF*T9Cc8)O zc1DrGl{7QTqvd^vFMOy8#UU%%n`xUiD610pytQaSOwlSA#SQ|@d?LSF{0kz~L}*9t z(gBSF-5RBzOp9BQH7=5J1DfcnltNln`*qEq+re!&ZqTn3?+uYr_NJcgz=GXU0Y25$0~X6QAM#39PO~T5%Mm%kI?1gW#Dr%FK`V`s zddwI(K5ds|n>0>UHy~nBIuR&#Bie&Jg$rb_$njlds2O}rw*?Ro72GyxSU{iG>_Kyn zs{xpx;tXOt*O3?Ay9Y$>s-f@2(?rgqplHM!Nkipkbha)ugKTu1>`(AUwtn2I>f&q8 zv!4fzo%krb-7pqV|Lj|qKszeK%FNuZV7f>&|LHZT8&km}wXf4_u1}=FsFnvB?gNrH zdynCNQgddmA*JJN>+35N{hewOg0zvrXi0rmR0u$<W?4si-!n2oP}6u zM0mVe{&6hyc0OKeebR84CSk%C$va!JyAUpWj(4gW-AXp4mabRVXnT*HAjqF3sjjAMf5scGZ%j z4lbdY)b1$N!AmoED8Obz>0^tCbjrF>5AnB`$D zZ*ZYf$hr6rQSS1+M%h|lumy=Icv(I1e*>REZVd8*Qx<_wN?>ri{#Jq?@Q;xlu0xb%76?eRspg6-SfVX_`&c++8ZDGqZ!-Lc7x?(AMpfHxHnub^|)TMO&(}>y=GWZEy5q zglOm(qz~?D>lw~D&PtB|7?UZyDGubHQUEqtAvcHtGu#ZNt0C&H_8PWul5;$A`XJUq zLb_VRKlMsX5D=blXR8vwHKs75tFm-UN zfyVm7h>&wEZVC@H<@EiCf5tWhhK8QHii|7coN6lf5$}Z~(xgz={{m0ZA^qX02|n~L zv7{h$neEBc zj5#}}bxXdRZ&Y-~2{0ua2RG_iN{ttTp&Lq*3E#HeY{h`rUx4By!pD;(zxzh3#KVdj zjY11Ao-(!&EL)U?hcCCX(2%R#A9}a^Gk^r1;)c7VA!Qf6YVm1i>)XUy%?fDoF8o!V zs;?P#MMWM@r|~AL@DAXFtR=1aNR3a*hoL;BJEj45H`?e;%v5W)75M08(*@GHG2x**0hw>>Vet2Y>C-oe zy1aCSBR|YNr9yMQZJu?+-SNJ0I0!$4Gn-kXC8h_>QiC8?(GeyJt0-RI^#4=Ef+;@v zb^0^62l%l@D(kOaE#8wUkFs#vgWAm^Y52yIwQdXSoPh0y<5fUaq6DWiA98!2Byi-B z0OXpyTsul%#n+9nQ=@{{h3{__8uj+&sGDIDgt>DitoUU^5w=md>|}8~R!n9$8GH6@ znWB65*MTOR7sCyWbAa%%W$&eryHdWU{5?*UucX?A|B!J{y7ph)KG%{yqOZwwAL*Z3 z`J%s-rm-r9pvOrIXr$f+`Fn!5ku85=H1&O@?HqoKqYRwtXfP8ObbZp*A-#Z99oQwI z98xDfFuvMnT+=F9`mUaKeK5V0X8+skg7_r2d{bnalK2EO^g0M?eGyFw3XPS%<5K-g7PN&%4*KDdTcgy?uQc{1H7Cb$?gC2o z{^G(Mn7)9Jl?PayzbB)DYtMLIeVN0dkPbljQ(WqK(}d0HAp)*f#8< z!Kj;?IhcK2VkDea7;QUS$)S&`w?I3yXEYa~6E?fm?y=e2rjDUnn~iJY_TEc6@jHAs zz#vP~ELZ7awB-mDA%rvd*j5Elf3NQ?-wZgi*b zwpsO=JwDx0QQ%>KMY)7-H#5Q%Sa%eb4!fei#A zpQdpO|DTJ%#pD+qk%K8bEx`NMc2_nzDo05U<#|_j*OMRb&NE&e7{r26l4lp!LMh@<<~ZHIe}NXi&E_6uIV41~r~((0I?^xg3rawqZ;fF7EWp(Tl9w6?*J0dMuOt!dbg zU!tIns?%Xx(&Xeq4RmO?1ub&qJ$)8P;rrj|9nYY5@|tgj~FqP@A4s6#^1$u z*~AV-uS_O$3sGz^A&+x3#HQB(R>xdc*8Bc}^EHbNF6@>#W#l_cd~HvMjRi~;EJ97W zAxImY3riNaVo?^5NbVJ?=8VZ;aGWbrx4kxoe3TrfWb|zm)b_XlM3p%D;qn$$-S1QP zfJ;394I6}TJMDph5!V&D-?9ZLSkU5c!)hvk@INXTa_Fli= zG)$tL)8XyZ{Za(CER>CL3y!Lm0pV=_d^hs~&D=qI7wgBx66@=rWuF*n0NT@JBt9N= z_&_lbk<|^o;4iU&n4!SW#a^AA(1GMv?QeAJw0rfEZRUB-*yBCBP|x0qvV^~1$JKH{hQY#cyPx8Dq} zlQ@3o{65uzIkdq4zgJXoBh@`yKBa`*0uaQmR%w?5mcx8OgJvAhLo^1S;u0VLtKJ=6 zVj*w~bFd^ek!Afm5w>+y>ai9c2nH834n~i7%eusJm{SM mD{wA}>vAXYsVB_2g+<>u{+|0FR5G{f=Dn1LSwgzSU7T?xMee2m literal 10324 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 From b28224bbd94773ae2c9d80be86ac0496f8578e5a Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Mon, 31 Jan 2022 13:51:40 -0800 Subject: [PATCH 527/966] fix: revert "feat: add api key support (#826)" (#964) This reverts commit 3b15092b3461278400e4683060f64a96d50587c4. Co-authored-by: Anthonios Partheniou --- packages/google-auth/google/auth/_default.py | 36 +------- .../google-auth/google/auth/_default_async.py | 36 +------- packages/google-auth/google/auth/api_key.py | 83 ------------------- .../google/auth/environment_vars.py | 3 - packages/google-auth/tests/test__default.py | 44 ---------- packages/google-auth/tests/test_api_key.py | 45 ---------- .../tests_async/test__default_async.py | 44 ---------- 7 files changed, 2 insertions(+), 289 deletions(-) delete mode 100644 packages/google-auth/google/auth/api_key.py delete mode 100644 packages/google-auth/tests/test_api_key.py diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index c70dccfaa1ff..34edda046f63 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -345,24 +345,6 @@ def _get_external_account_credentials( return credentials, credentials.get_project_id(request=request) -def _get_api_key_credentials(quota_project_id=None): - """Gets API key credentials and project ID.""" - from google.auth import api_key - - api_key_value = os.environ.get(environment_vars.API_KEY) - if api_key_value: - return api_key.Credentials(api_key_value), quota_project_id - else: - return None, None - - -def get_api_key_credentials(api_key_value): - """Gets API key credentials using the given api key value.""" - from google.auth import api_key - - return api_key.Credentials(api_key_value) - - def _get_authorized_user_credentials(filename, info, scopes=None): from google.oauth2 import credentials @@ -458,14 +440,7 @@ def default(scopes=None, request=None, quota_project_id=None, default_scopes=Non This function acquires credentials from the environment in the following order: - 1. If both ``GOOGLE_API_KEY`` and ``GOOGLE_APPLICATION_CREDENTIALS`` - environment variables are set, throw an exception. - - If ``GOOGLE_API_KEY`` is set, an `API Key`_ credentials will be returned. - The project ID returned is the one defined by ``GOOGLE_CLOUD_PROJECT`` or - ``GCLOUD_PROJECT`` environment variables. - - If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set + 1. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set to the path of a valid service account JSON private key file, then it is loaded and returned. The project ID returned is the project ID defined in the service account file if available (some older files do not @@ -513,7 +488,6 @@ def default(scopes=None, request=None, quota_project_id=None, default_scopes=Non .. _Metadata Service: https://cloud.google.com/compute/docs\ /storing-retrieving-metadata .. _Cloud Run: https://cloud.google.com/run - .. _API Key: https://cloud.google.com/docs/authentication/api-keys Example:: @@ -555,19 +529,11 @@ def default(scopes=None, request=None, quota_project_id=None, default_scopes=Non environment_vars.PROJECT, os.environ.get(environment_vars.LEGACY_PROJECT) ) - if os.environ.get(environment_vars.API_KEY) and os.environ.get( - environment_vars.CREDENTIALS - ): - raise exceptions.DefaultCredentialsError( - "Environment variables GOOGLE_API_KEY and GOOGLE_APPLICATION_CREDENTIALS are mutually exclusive" - ) - checkers = ( # Avoid passing scopes here to prevent passing scopes to user credentials. # with_scopes_if_required() below will ensure scopes/default scopes are # safely set on the returned credentials since requires_scopes will # guard against setting scopes on user credentials. - lambda: _get_api_key_credentials(quota_project_id=quota_project_id), lambda: _get_explicit_environ_credentials(quota_project_id=quota_project_id), lambda: _get_gcloud_sdk_credentials(quota_project_id=quota_project_id), _get_gae_credentials, diff --git a/packages/google-auth/google/auth/_default_async.py b/packages/google-auth/google/auth/_default_async.py index a6f7d777775f..5a41f2a6ea16 100644 --- a/packages/google-auth/google/auth/_default_async.py +++ b/packages/google-auth/google/auth/_default_async.py @@ -161,24 +161,6 @@ def _get_gae_credentials(): return _default._get_gae_credentials() -def _get_api_key_credentials(quota_project_id=None): - """Gets API key credentials and project ID.""" - from google.auth import api_key - - api_key_value = os.environ.get(environment_vars.API_KEY) - if api_key_value: - return api_key.Credentials(api_key_value), quota_project_id - else: - return None, None - - -def get_api_key_credentials(api_key_value): - """Gets API key credentials using the given api key value.""" - from google.auth import api_key - - return api_key.Credentials(api_key_value) - - def _get_gce_credentials(request=None): """Gets credentials and project ID from the GCE Metadata Service.""" # Ping requires a transport, but we want application default credentials @@ -200,14 +182,7 @@ def default_async(scopes=None, request=None, quota_project_id=None): This function acquires credentials from the environment in the following order: - 1. If both ``GOOGLE_API_KEY`` and ``GOOGLE_APPLICATION_CREDENTIALS`` - environment variables are set, throw an exception. - - If ``GOOGLE_API_KEY`` is set, an `API Key`_ credentials will be returned. - The project ID returned is the one defined by ``GOOGLE_CLOUD_PROJECT`` or - ``GCLOUD_PROJECT`` environment variables. - - If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set + 1. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set to the path of a valid service account JSON private key file, then it is loaded and returned. The project ID returned is the project ID defined in the service account file if available (some older files do not @@ -246,7 +221,6 @@ def default_async(scopes=None, request=None, quota_project_id=None): .. _Metadata Service: https://cloud.google.com/compute/docs\ /storing-retrieving-metadata .. _Cloud Run: https://cloud.google.com/run - .. _API Key: https://cloud.google.com/docs/authentication/api-keys Example:: @@ -282,15 +256,7 @@ def default_async(scopes=None, request=None, quota_project_id=None): environment_vars.PROJECT, os.environ.get(environment_vars.LEGACY_PROJECT) ) - if os.environ.get(environment_vars.API_KEY) and os.environ.get( - environment_vars.CREDENTIALS - ): - raise exceptions.DefaultCredentialsError( - "GOOGLE_API_KEY and GOOGLE_APPLICATION_CREDENTIALS are mutually exclusive" - ) - checkers = ( - lambda: _get_api_key_credentials(quota_project_id=quota_project_id), lambda: _get_explicit_environ_credentials(quota_project_id=quota_project_id), lambda: _get_gcloud_sdk_credentials(quota_project_id=quota_project_id), _get_gae_credentials, diff --git a/packages/google-auth/google/auth/api_key.py b/packages/google-auth/google/auth/api_key.py deleted file mode 100644 index 7cdef429c8a5..000000000000 --- a/packages/google-auth/google/auth/api_key.py +++ /dev/null @@ -1,83 +0,0 @@ -# Copyright 2021 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Google API key support. - -This module provides authentication using the `API key`_. - - -.. _API key: - https://cloud.google.com/docs/authentication/api-keys/ -""" - -from google.auth import _helpers -from google.auth import credentials - - -class Credentials(credentials.Credentials): - """API key credentials. - - These credentials use API key to provide authorization to applications. - """ - - def __init__(self, token): - """ - Args: - token (str): API key string - - Raises: - ValueError: If the provided API key is not a non-empty string. - """ - if not token: - raise ValueError("Token must be a non-empty API key string") - super(Credentials, self).__init__() - self.token = token - - @property - def expired(self): - return False - - @property - def valid(self): - return True - - @_helpers.copy_docstring(credentials.Credentials) - def refresh(self, request): - return - - def apply(self, headers, token=None): - """Apply the API key token to the x-goog-api-key header. - - Args: - headers (Mapping): The HTTP request headers. - token (Optional[str]): If specified, overrides the current access - token. - """ - headers["x-goog-api-key"] = token or self.token - - def before_request(self, request, method, url, headers): - """Performs credential-specific before request logic. - - Refreshes the credentials if necessary, then calls :meth:`apply` to - apply the token to the x-goog-api-key header. - - Args: - request (google.auth.transport.Request): The object used to make - HTTP requests. - method (str): The request's HTTP method or the RPC method being - invoked. - url (str): The request's URI or the RPC service's URI. - headers (Mapping): The request's headers. - """ - self.apply(headers) diff --git a/packages/google-auth/google/auth/environment_vars.py b/packages/google-auth/google/auth/environment_vars.py index d872c95f9a82..c076dc59da1c 100644 --- a/packages/google-auth/google/auth/environment_vars.py +++ b/packages/google-auth/google/auth/environment_vars.py @@ -33,9 +33,6 @@ """Environment variable defining the location of Google application default credentials.""" -API_KEY = "GOOGLE_API_KEY" -"""Environment variable defining the API key value.""" - # The environment variable name which can replace ~/.config if set. CLOUD_SDK_CONFIG_DIR = "CLOUDSDK_CONFIG" """Environment variable defines the location of Google Cloud SDK's config diff --git a/packages/google-auth/tests/test__default.py b/packages/google-auth/tests/test__default.py index 35a5dc02101d..ed64bc723552 100644 --- a/packages/google-auth/tests/test__default.py +++ b/packages/google-auth/tests/test__default.py @@ -19,7 +19,6 @@ import pytest # type: ignore from google.auth import _default -from google.auth import api_key from google.auth import app_engine from google.auth import aws from google.auth import compute_engine @@ -1141,46 +1140,3 @@ def test_default_impersonated_service_account_set_both_scopes_and_default_scopes credentials, _ = _default.default(scopes=scopes, default_scopes=default_scopes) assert credentials._target_scopes == scopes - - -def test__get_api_key_credentials_no_env_var(): - cred, project_id = _default._get_api_key_credentials(quota_project_id="project-foo") - assert cred is None - assert project_id is None - - -def test__get_api_key_credentials_from_env_var(): - with mock.patch.dict(os.environ, {environment_vars.API_KEY: "api-key"}): - cred, project_id = _default._get_api_key_credentials( - quota_project_id="project-foo" - ) - assert isinstance(cred, api_key.Credentials) - assert cred.token == "api-key" - assert project_id == "project-foo" - - -def test_exception_with_api_key_and_adc_env_var(): - with mock.patch.dict(os.environ, {environment_vars.API_KEY: "api-key"}): - with mock.patch.dict( - os.environ, {environment_vars.CREDENTIALS: "/path/to/json"} - ): - with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: - _default.default() - - assert excinfo.match( - r"GOOGLE_API_KEY and GOOGLE_APPLICATION_CREDENTIALS are mutually exclusive" - ) - - -def test_default_api_key_from_env_var(): - with mock.patch.dict(os.environ, {environment_vars.API_KEY: "api-key"}): - cred, project_id = _default.default() - assert isinstance(cred, api_key.Credentials) - assert cred.token == "api-key" - assert project_id is None - - -def test_get_api_key_credentials(): - cred = _default.get_api_key_credentials("api-key") - assert isinstance(cred, api_key.Credentials) - assert cred.token == "api-key" diff --git a/packages/google-auth/tests/test_api_key.py b/packages/google-auth/tests/test_api_key.py deleted file mode 100644 index 9721731be3a3..000000000000 --- a/packages/google-auth/tests/test_api_key.py +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright 2021 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pytest # type: ignore - -from google.auth import api_key - - -def test_credentials_constructor(): - with pytest.raises(ValueError) as excinfo: - api_key.Credentials("") - - assert excinfo.match(r"Token must be a non-empty API key string") - - -def test_expired_and_valid(): - credentials = api_key.Credentials("api-key") - - assert credentials.valid - assert credentials.token == "api-key" - assert not credentials.expired - - credentials.refresh(None) - assert credentials.valid - assert credentials.token == "api-key" - assert not credentials.expired - - -def test_before_request(): - credentials = api_key.Credentials("api-key") - headers = {} - - credentials.before_request(None, "http://example.com", "GET", headers) - assert headers["x-goog-api-key"] == "api-key" diff --git a/packages/google-auth/tests_async/test__default_async.py b/packages/google-auth/tests_async/test__default_async.py index 2a1921081470..bf1a129a8216 100644 --- a/packages/google-auth/tests_async/test__default_async.py +++ b/packages/google-auth/tests_async/test__default_async.py @@ -20,7 +20,6 @@ from google.auth import _credentials_async as credentials from google.auth import _default_async as _default -from google.auth import api_key from google.auth import app_engine from google.auth import compute_engine from google.auth import environment_vars @@ -562,46 +561,3 @@ def test_default_no_warning_with_quota_project_id_for_user_creds(get_adc_path): get_adc_path.return_value = test_default.AUTHORIZED_USER_CLOUD_SDK_FILE credentials, project_id = _default.default_async(quota_project_id="project-foo") - - -def test__get_api_key_credentials_no_env_var(): - cred, project_id = _default._get_api_key_credentials(quota_project_id="project-foo") - assert cred is None - assert project_id is None - - -def test__get_api_key_credentials_from_env_var(): - with mock.patch.dict(os.environ, {environment_vars.API_KEY: "api-key"}): - cred, project_id = _default._get_api_key_credentials( - quota_project_id="project-foo" - ) - assert isinstance(cred, api_key.Credentials) - assert cred.token == "api-key" - assert project_id == "project-foo" - - -def test_exception_with_api_key_and_adc_env_var(): - with mock.patch.dict(os.environ, {environment_vars.API_KEY: "api-key"}): - with mock.patch.dict( - os.environ, {environment_vars.CREDENTIALS: "/path/to/json"} - ): - with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: - _default.default_async() - - assert excinfo.match( - r"GOOGLE_API_KEY and GOOGLE_APPLICATION_CREDENTIALS are mutually exclusive" - ) - - -def test_default_api_key_from_env_var(): - with mock.patch.dict(os.environ, {environment_vars.API_KEY: "api-key"}): - cred, project_id = _default.default_async() - assert isinstance(cred, api_key.Credentials) - assert cred.token == "api-key" - assert project_id is None - - -def test_get_api_key_credentials(): - cred = _default.get_api_key_credentials("api-key") - assert isinstance(cred, api_key.Credentials) - assert cred.token == "api-key" From 1ef1bb93b94702f21497bce4af08a002b5731339 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 31 Jan 2022 14:12:40 -0800 Subject: [PATCH 528/966] chore(main): release 2.6.0 (#965) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 12 ++++++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 9249639b0a36..377f02d3fdfe 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,18 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.6.0](https://github.com/googleapis/google-auth-library-python/compare/v2.5.0...v2.6.0) (2022-01-31) + + +### Features + +* ADC can load an impersonated service account credentials. ([#962](https://github.com/googleapis/google-auth-library-python/issues/962)) ([52c8ef9](https://github.com/googleapis/google-auth-library-python/commit/52c8ef90058120d7d04d3d201adc111664be526c)) + + +### Bug Fixes + +* revert "feat: add api key support ([#826](https://github.com/googleapis/google-auth-library-python/issues/826))" ([#964](https://github.com/googleapis/google-auth-library-python/issues/964)) ([f9f23f4](https://github.com/googleapis/google-auth-library-python/commit/f9f23f4370f2a7a5b2c66ee56a5e700ef03b5b06)) + ## [2.5.0](https://github.com/googleapis/google-auth-library-python/compare/v2.4.1...v2.5.0) (2022-01-25) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index e6c150fc94cb..23427179edb9 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.5.0" +__version__ = "2.6.0" From d0ebd0769633beacbf0100c83f8ce51f008559f1 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Mon, 31 Jan 2022 17:54:25 -0800 Subject: [PATCH 529/966] chore: remove silvano from code owners (#967) remove xoogler --- packages/google-auth/.github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/.github/CODEOWNERS b/packages/google-auth/.github/CODEOWNERS index f3c8219b8ae7..8eba06fddcf0 100644 --- a/packages/google-auth/.github/CODEOWNERS +++ b/packages/google-auth/.github/CODEOWNERS @@ -5,7 +5,7 @@ # https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners#codeowners-syntax # The @googleapis/yoshi-python is the default owner for changes in this repo -* @arithmetic1728 @silvolu @googleapis/yoshi-python +* @arithmetic1728 @googleapis/yoshi-python # The python-samples-reviewers team is the default owner for samples changes /samples/ @googleapis/python-samples-owners From 6db374c3093efed0958048ed4cad3bff97981c56 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Wed, 2 Feb 2022 15:09:04 -0800 Subject: [PATCH 530/966] chore: update user creds for system test (#970) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index ab9084e82c8b526bec4de7e8dc8a3bb01f7b730d..16fb66cd4adc38427ad8ba3ada7f98d114edb086 100644 GIT binary patch literal 10324 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 literal 10324 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 From 038296dbb78f9a37d49bd37b6fe5bfea0b8fce52 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Wed, 2 Feb 2022 16:14:28 -0800 Subject: [PATCH 531/966] chore: add Sai as a code owner (#969) Co-authored-by: Anthonios Partheniou --- packages/google-auth/.github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/.github/CODEOWNERS b/packages/google-auth/.github/CODEOWNERS index 8eba06fddcf0..4381bcf23253 100644 --- a/packages/google-auth/.github/CODEOWNERS +++ b/packages/google-auth/.github/CODEOWNERS @@ -5,7 +5,7 @@ # https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners#codeowners-syntax # The @googleapis/yoshi-python is the default owner for changes in this repo -* @arithmetic1728 @googleapis/yoshi-python +* @arithmetic1728 @sai-sunder-s @googleapis/yoshi-python # The python-samples-reviewers team is the default owner for samples changes /samples/ @googleapis/python-samples-owners From 3ac9c15faf9c2e45394b4e9b2c8559e4e5d9ad5c Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Thu, 3 Feb 2022 16:32:56 -0800 Subject: [PATCH 532/966] chore: update user creds for sys test (#972) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 16fb66cd4adc38427ad8ba3ada7f98d114edb086..c576d1e51da34d9986e6a161bd5d7aab8255d62d 100644 GIT binary patch literal 10324 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 From 83d0ace4256c69357476b2fce6a693a839b39221 Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Sat, 5 Feb 2022 00:11:49 +0000 Subject: [PATCH 533/966] fix: Add AWS session token to metadata requests (#958) * fix: add aws session token to metadata requests * Fix tests * update config name * Add test for session token * add coverage * add coverage * Run blacken and lint --- packages/google-auth/google/auth/aws.py | 65 +++++++++-- packages/google-auth/tests/test_aws.py | 147 +++++++++++++++++++++++- 2 files changed, 198 insertions(+), 14 deletions(-) diff --git a/packages/google-auth/google/auth/aws.py b/packages/google-auth/google/auth/aws.py index 2fd96d058ded..358a1cf9608c 100644 --- a/packages/google-auth/google/auth/aws.py +++ b/packages/google-auth/google/auth/aws.py @@ -406,6 +406,7 @@ def __init__( self._cred_verification_url = credential_source.get( "regional_cred_verification_url" ) + self._aws_session_token_url = credential_source.get("aws_session_token_url") self._region = None self._request_signer = None self._target_resource = audience @@ -458,15 +459,34 @@ def retrieve_subject_token(self, request): Returns: str: The retrieved subject token. """ + # Fetch the session token required to make meta data endpoint calls to aws + if request is not None and self._aws_session_token_url is not None: + headers = {"X-aws-ec2-metadata-token-ttl-seconds": "21600"} + + session_token_response = request( + url=self._aws_session_token_url, method="PUT", headers=headers + ) + + if session_token_response.status != 200: + raise exceptions.RefreshError( + "Unable to retrieve AWS Session Token", session_token_response.data + ) + + session_token = session_token_response.data + else: + session_token = None + # Initialize the request signer if not yet initialized after determining # the current AWS region. if self._request_signer is None: - self._region = self._get_region(request, self._region_url) + self._region = self._get_region(request, self._region_url, session_token) self._request_signer = RequestSigner(self._region) # Retrieve the AWS security credentials needed to generate the signed # request. - aws_security_credentials = self._get_security_credentials(request) + aws_security_credentials = self._get_security_credentials( + request, session_token + ) # Generate the signed request to AWS STS GetCallerIdentity API. # Use the required regional endpoint. Otherwise, the request will fail. request_options = self._request_signer.get_request_options( @@ -511,7 +531,7 @@ def retrieve_subject_token(self, request): json.dumps(aws_signed_req, separators=(",", ":"), sort_keys=True) ) - def _get_region(self, request, url): + def _get_region(self, request, url, session_token): """Retrieves the current AWS region from either the AWS_REGION or AWS_DEFAULT_REGION environment variable or from the AWS metadata server. @@ -519,6 +539,8 @@ def _get_region(self, request, url): request (google.auth.transport.Request): A callable used to make HTTP requests. url (str): The AWS metadata server region URL. + session_token (str): The AWS session token to be added as a + header in the requests to AWS metadata endpoint. Returns: str: The current AWS region. @@ -540,7 +562,12 @@ def _get_region(self, request, url): if not self._region_url: raise exceptions.RefreshError("Unable to determine AWS region") - response = request(url=self._region_url, method="GET") + + headers = None + if session_token is not None: + headers = {"X-aws-ec2-metadata-token": session_token} + + response = request(url=self._region_url, method="GET", headers=headers) # Support both string and bytes type response.data. response_body = ( @@ -558,7 +585,7 @@ def _get_region(self, request, url): # Only the us-east-2 part should be used. return response_body[:-1] - def _get_security_credentials(self, request): + def _get_security_credentials(self, request, session_token): """Retrieves the AWS security credentials required for signing AWS requests from either the AWS security credentials environment variables or from the AWS metadata server. @@ -566,6 +593,8 @@ def _get_security_credentials(self, request): Args: request (google.auth.transport.Request): A callable used to make HTTP requests. + session_token (str): The AWS session token to be added as a + header in the requests to AWS metadata endpoint. Returns: Mapping[str, str]: The AWS security credentials dictionary object. @@ -591,10 +620,12 @@ def _get_security_credentials(self, request): } # Get role name. - role_name = self._get_metadata_role_name(request) + role_name = self._get_metadata_role_name(request, session_token) # Get security credentials. - credentials = self._get_metadata_security_credentials(request, role_name) + credentials = self._get_metadata_security_credentials( + request, role_name, session_token + ) return { "access_key_id": credentials.get("AccessKeyId"), @@ -602,7 +633,7 @@ def _get_security_credentials(self, request): "security_token": credentials.get("Token"), } - def _get_metadata_security_credentials(self, request, role_name): + def _get_metadata_security_credentials(self, request, role_name, session_token): """Retrieves the AWS security credentials required for signing AWS requests from the AWS metadata server. @@ -612,6 +643,8 @@ def _get_metadata_security_credentials(self, request, role_name): role_name (str): The AWS role name required by the AWS metadata server security_credentials endpoint in order to return the credentials. + session_token (str): The AWS session token to be added as a + header in the requests to AWS metadata endpoint. Returns: Mapping[str, str]: The AWS metadata server security credentials @@ -622,6 +655,9 @@ def _get_metadata_security_credentials(self, request, role_name): retrieving the AWS security credentials. """ headers = {"Content-Type": "application/json"} + if session_token is not None: + headers["X-aws-ec2-metadata-token"] = session_token + response = request( url="{}/{}".format(self._security_credentials_url, role_name), method="GET", @@ -644,7 +680,7 @@ def _get_metadata_security_credentials(self, request, role_name): return credentials_response - def _get_metadata_role_name(self, request): + def _get_metadata_role_name(self, request, session_token): """Retrieves the AWS role currently attached to the current AWS workload by querying the AWS metadata server. This is needed for the AWS metadata server security credentials endpoint in order to retrieve @@ -653,6 +689,8 @@ def _get_metadata_role_name(self, request): Args: request (google.auth.transport.Request): A callable used to make HTTP requests. + session_token (str): The AWS session token to be added as a + header in the requests to AWS metadata endpoint. Returns: str: The AWS role name. @@ -665,7 +703,14 @@ def _get_metadata_role_name(self, request): raise exceptions.RefreshError( "Unable to determine the AWS metadata server security credentials endpoint" ) - response = request(url=self._security_credentials_url, method="GET") + + headers = None + if session_token is not None: + headers = {"X-aws-ec2-metadata-token": session_token} + + response = request( + url=self._security_credentials_url, method="GET", headers=headers + ) # support both string and bytes type response.data response_body = ( diff --git a/packages/google-auth/tests/test_aws.py b/packages/google-auth/tests/test_aws.py index d37131afb0ae..2bace5d0783e 100644 --- a/packages/google-auth/tests/test_aws.py +++ b/packages/google-auth/tests/test_aws.py @@ -42,6 +42,7 @@ SUBJECT_TOKEN_TYPE = "urn:ietf:params:aws:token-type:aws4_request" AUDIENCE = "//iam.googleapis.com/projects/123456/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID" REGION_URL = "http://169.254.169.254/latest/meta-data/placement/availability-zone" +AWS_SESSION_TOKEN_URL = "http://169.254.169.254/latest/api/token" SECURITY_CREDS_URL = "http://169.254.169.254/latest/meta-data/iam/security-credentials" CRED_VERIFICATION_URL = ( "https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15" @@ -578,6 +579,7 @@ class TestCredentials(object): "SecretAccessKey": SECRET_ACCESS_KEY, "Token": TOKEN, } + AWS_SESSION_TOKEN = "awssessiontoken" AWS_SIGNATURE_TIME = "2020-08-11T06:55:22Z" CREDENTIAL_SOURCE = { "environment_id": "aws1", @@ -654,6 +656,8 @@ def make_mock_request( token_data=None, impersonation_status=None, impersonation_data=None, + session_token_status=None, + session_token_data=None, ): """Utility function to generate a mock HTTP request object. This will facilitate testing various edge cases by specify how the @@ -661,6 +665,13 @@ def make_mock_request( in an AWS environment. """ responses = [] + if session_token_status: + # AWS session token request + session_response = mock.create_autospec(transport.Response, instance=True) + session_response.status = session_token_status + session_response.data = session_token_data + responses.append(session_response) + if region_status: # AWS region request. region_response = mock.create_autospec(transport.Response, instance=True) @@ -735,14 +746,16 @@ def make_credentials( ) @classmethod - def assert_aws_metadata_request_kwargs(cls, request_kwargs, url, headers=None): + def assert_aws_metadata_request_kwargs( + cls, request_kwargs, url, headers=None, method="GET" + ): assert request_kwargs["url"] == url # All used AWS metadata server endpoints use GET HTTP method. - assert request_kwargs["method"] == "GET" + assert request_kwargs["method"] == method if headers: assert request_kwargs["headers"] == headers else: - assert "headers" not in request_kwargs + assert "headers" not in request_kwargs or request_kwargs["headers"] is None # None of the endpoints used require any data in request. assert "body" not in request_kwargs @@ -995,7 +1008,7 @@ def test_retrieve_subject_token_success_temp_creds_no_environment_vars( credentials.retrieve_subject_token(new_request) - # Only 2 requests should be sent as the region is cached. + # Only 3 requests should be sent as the region is cached. assert len(new_request.call_args_list) == 2 # Assert role request. self.assert_aws_metadata_request_kwargs( @@ -1008,6 +1021,132 @@ def test_retrieve_subject_token_success_temp_creds_no_environment_vars( {"Content-Type": "application/json"}, ) + @mock.patch("google.auth._helpers.utcnow") + def test_retrieve_subject_token_success_temp_creds_no_environment_vars_idmsv2( + self, utcnow + ): + utcnow.return_value = datetime.datetime.strptime( + self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ" + ) + request = self.make_mock_request( + region_status=http_client.OK, + region_name=self.AWS_REGION, + role_status=http_client.OK, + role_name=self.AWS_ROLE, + security_credentials_status=http_client.OK, + security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE, + session_token_status=http_client.OK, + session_token_data=self.AWS_SESSION_TOKEN, + ) + credential_source_token_url = self.CREDENTIAL_SOURCE.copy() + credential_source_token_url["aws_session_token_url"] = AWS_SESSION_TOKEN_URL + credentials = self.make_credentials( + credential_source=credential_source_token_url + ) + + subject_token = credentials.retrieve_subject_token(request) + + assert subject_token == self.make_serialized_aws_signed_request( + { + "access_key_id": ACCESS_KEY_ID, + "secret_access_key": SECRET_ACCESS_KEY, + "security_token": TOKEN, + } + ) + # Assert session token request + self.assert_aws_metadata_request_kwargs( + request.call_args_list[0][1], + AWS_SESSION_TOKEN_URL, + {"X-aws-ec2-metadata-token-ttl-seconds": "21600"}, + "PUT", + ) + # Assert region request. + self.assert_aws_metadata_request_kwargs( + request.call_args_list[1][1], + REGION_URL, + {"X-aws-ec2-metadata-token": self.AWS_SESSION_TOKEN}, + ) + # Assert role request. + self.assert_aws_metadata_request_kwargs( + request.call_args_list[2][1], + SECURITY_CREDS_URL, + {"X-aws-ec2-metadata-token": self.AWS_SESSION_TOKEN}, + ) + # Assert security credentials request. + self.assert_aws_metadata_request_kwargs( + request.call_args_list[3][1], + "{}/{}".format(SECURITY_CREDS_URL, self.AWS_ROLE), + { + "Content-Type": "application/json", + "X-aws-ec2-metadata-token": self.AWS_SESSION_TOKEN, + }, + ) + + # Retrieve subject_token again. Region should not be queried again. + new_request = self.make_mock_request( + role_status=http_client.OK, + role_name=self.AWS_ROLE, + security_credentials_status=http_client.OK, + security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE, + session_token_status=http_client.OK, + session_token_data=self.AWS_SESSION_TOKEN, + ) + + credentials.retrieve_subject_token(new_request) + + # Only 3 requests should be sent as the region is cached. + assert len(new_request.call_args_list) == 3 + # Assert session token request + self.assert_aws_metadata_request_kwargs( + request.call_args_list[0][1], + AWS_SESSION_TOKEN_URL, + {"X-aws-ec2-metadata-token-ttl-seconds": "21600"}, + "PUT", + ) + # Assert role request. + self.assert_aws_metadata_request_kwargs( + new_request.call_args_list[1][1], + SECURITY_CREDS_URL, + {"X-aws-ec2-metadata-token": self.AWS_SESSION_TOKEN}, + ) + # Assert security credentials request. + self.assert_aws_metadata_request_kwargs( + new_request.call_args_list[2][1], + "{}/{}".format(SECURITY_CREDS_URL, self.AWS_ROLE), + { + "Content-Type": "application/json", + "X-aws-ec2-metadata-token": self.AWS_SESSION_TOKEN, + }, + ) + + @mock.patch("google.auth._helpers.utcnow") + def test_retrieve_subject_token_session_error_idmsv2(self, utcnow): + utcnow.return_value = datetime.datetime.strptime( + self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ" + ) + request = self.make_mock_request( + session_token_status=http_client.UNAUTHORIZED, + session_token_data="unauthorized", + ) + credential_source_token_url = self.CREDENTIAL_SOURCE.copy() + credential_source_token_url["aws_session_token_url"] = AWS_SESSION_TOKEN_URL + credentials = self.make_credentials( + credential_source=credential_source_token_url + ) + + with pytest.raises(exceptions.RefreshError) as excinfo: + credentials.retrieve_subject_token(request) + + assert excinfo.match(r"Unable to retrieve AWS Session Token") + + # Assert session token request + self.assert_aws_metadata_request_kwargs( + request.call_args_list[0][1], + AWS_SESSION_TOKEN_URL, + {"X-aws-ec2-metadata-token-ttl-seconds": "21600"}, + "PUT", + ) + @mock.patch("google.auth._helpers.utcnow") def test_retrieve_subject_token_success_permanent_creds_no_environment_vars( self, utcnow From 0663c4d17b6af11d4f985b244d7f1062dfdb8fb9 Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Wed, 9 Feb 2022 16:44:14 +0000 Subject: [PATCH 534/966] chore: update user creds for sys test (#975) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10323 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index c576d1e51da34d9986e6a161bd5d7aab8255d62d..d4decbfe633a8944dd35e16fbe6f5c4c305242f8 100644 GIT binary patch literal 10323 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 literal 10324 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 Date: Wed, 9 Feb 2022 12:09:05 -0500 Subject: [PATCH 535/966] chore: add custom sync repo settings (#978) --- .../.github/sync-repo-settings.yaml | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 packages/google-auth/.github/sync-repo-settings.yaml diff --git a/packages/google-auth/.github/sync-repo-settings.yaml b/packages/google-auth/.github/sync-repo-settings.yaml new file mode 100644 index 000000000000..675cd7c93097 --- /dev/null +++ b/packages/google-auth/.github/sync-repo-settings.yaml @@ -0,0 +1,20 @@ +# https://github.com/googleapis/repo-automation-bots/tree/main/packages/sync-repo-settings +# Rules for main branch protection +branchProtectionRules: +# Identifies the protection rule pattern. Name of the branch to be protected. +# Defaults to `main` +- pattern: main + requiresCodeOwnerReviews: true + requiresStrictStatusChecks: true + requiredStatusCheckContexts: + - 'cla/google' + - 'OwlBot Post Processor' + - 'Kokoro system-3.7' + - 'Kokoro' +permissionRules: + - team: actools-python + permission: admin + - team: actools + permission: admin + - team: yoshi-python + permission: push From f405d94b0030d1e97f249fe7c78a354e350b7208 Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Wed, 9 Feb 2022 18:28:23 +0000 Subject: [PATCH 536/966] doc: Update user-guide.rst for AWS IDMSv2 (#974) Co-authored-by: Anthonios Partheniou --- packages/google-auth/docs/user-guide.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/google-auth/docs/user-guide.rst b/packages/google-auth/docs/user-guide.rst index ccece5751548..303cc5cd79d3 100644 --- a/packages/google-auth/docs/user-guide.rst +++ b/packages/google-auth/docs/user-guide.rst @@ -259,6 +259,9 @@ following requirements are needed: credential files, the generated credential configuration file will only contain non-sensitive metadata to instruct the library on how to retrieve external subject tokens and exchange them for service account access tokens. +- If you want to use IDMSv2, then below field needs to be added to credential_source + section of credential configuration. + "aws_session_token_url": "http://169.254.169.254/latest/api/token" Follow the detailed instructions on how to `Configure Workload Identity Federation from AWS`_. From a386f334667ea0ea24c56ed8b3e4628e9b40dab7 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Wed, 9 Feb 2022 11:46:05 -0800 Subject: [PATCH 537/966] chore(main): release 2.6.1 (#973) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 377f02d3fdfe..7bfa22dd8239 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +### [2.6.1](https://github.com/googleapis/google-auth-library-python/compare/v2.6.0...v2.6.1) (2022-02-09) + + +### Bug Fixes + +* Add AWS session token to metadata requests ([#958](https://github.com/googleapis/google-auth-library-python/issues/958)) ([5c7f734](https://github.com/googleapis/google-auth-library-python/commit/5c7f7342179d007e9e779ffe8734d540cdf36fde)) + ## [2.6.0](https://github.com/googleapis/google-auth-library-python/compare/v2.5.0...v2.6.0) (2022-01-31) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 23427179edb9..02ca20699fda 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.6.0" +__version__ = "2.6.1" From 0afc53c6a4ccd26d00375ea35f5227702f323008 Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Sun, 20 Feb 2022 00:11:48 +0000 Subject: [PATCH 538/966] chore: update refresh token for system tests (#983) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10323 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index d4decbfe633a8944dd35e16fbe6f5c4c305242f8..f41b2a6f5b7bb7f9b96f512ac551f44ac79d8b5a 100644 GIT binary patch literal 10324 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 From 6e459afd7abbf58a09031fde124f5b6717cdf1cd Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Tue, 22 Feb 2022 21:12:22 +0000 Subject: [PATCH 539/966] chore: update refresh token for system tests (#985) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index f41b2a6f5b7bb7f9b96f512ac551f44ac79d8b5a..1b54a369cfee985df6243b6e2223fddfa6906489 100644 GIT binary patch literal 10324 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 m<%Ac_kFJTudk0FNz;D0)q5c Date: Tue, 22 Feb 2022 21:41:45 +0000 Subject: [PATCH 540/966] fix: Rename aws imdsv2 url field and update token lifetime (#982) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: Rename aws imdsv2 url field and update token lifetime * update readme * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md Co-authored-by: Owl Bot --- packages/google-auth/docs/user-guide.rst | 2 +- packages/google-auth/google/auth/aws.py | 61 ++++++++++++----------- packages/google-auth/tests/test_aws.py | 62 +++++++++++++----------- 3 files changed, 69 insertions(+), 56 deletions(-) diff --git a/packages/google-auth/docs/user-guide.rst b/packages/google-auth/docs/user-guide.rst index 303cc5cd79d3..239b5a6d7176 100644 --- a/packages/google-auth/docs/user-guide.rst +++ b/packages/google-auth/docs/user-guide.rst @@ -261,7 +261,7 @@ following requirements are needed: external subject tokens and exchange them for service account access tokens. - If you want to use IDMSv2, then below field needs to be added to credential_source section of credential configuration. - "aws_session_token_url": "http://169.254.169.254/latest/api/token" + "imdsv2_session_token_url": "http://169.254.169.254/latest/api/token" Follow the detailed instructions on how to `Configure Workload Identity Federation from AWS`_. diff --git a/packages/google-auth/google/auth/aws.py b/packages/google-auth/google/auth/aws.py index 358a1cf9608c..9df2d35e3723 100644 --- a/packages/google-auth/google/auth/aws.py +++ b/packages/google-auth/google/auth/aws.py @@ -406,7 +406,9 @@ def __init__( self._cred_verification_url = credential_source.get( "regional_cred_verification_url" ) - self._aws_session_token_url = credential_source.get("aws_session_token_url") + self._imdsv2_session_token_url = credential_source.get( + "imdsv2_session_token_url" + ) self._region = None self._request_signer = None self._target_resource = audience @@ -460,32 +462,35 @@ def retrieve_subject_token(self, request): str: The retrieved subject token. """ # Fetch the session token required to make meta data endpoint calls to aws - if request is not None and self._aws_session_token_url is not None: - headers = {"X-aws-ec2-metadata-token-ttl-seconds": "21600"} + if request is not None and self._imdsv2_session_token_url is not None: + headers = {"X-aws-ec2-metadata-token-ttl-seconds": "300"} - session_token_response = request( - url=self._aws_session_token_url, method="PUT", headers=headers + imdsv2_session_token_response = request( + url=self._imdsv2_session_token_url, method="PUT", headers=headers ) - if session_token_response.status != 200: + if imdsv2_session_token_response.status != 200: raise exceptions.RefreshError( - "Unable to retrieve AWS Session Token", session_token_response.data + "Unable to retrieve AWS Session Token", + imdsv2_session_token_response.data, ) - session_token = session_token_response.data + imdsv2_session_token = imdsv2_session_token_response.data else: - session_token = None + imdsv2_session_token = None # Initialize the request signer if not yet initialized after determining # the current AWS region. if self._request_signer is None: - self._region = self._get_region(request, self._region_url, session_token) + self._region = self._get_region( + request, self._region_url, imdsv2_session_token + ) self._request_signer = RequestSigner(self._region) # Retrieve the AWS security credentials needed to generate the signed # request. aws_security_credentials = self._get_security_credentials( - request, session_token + request, imdsv2_session_token ) # Generate the signed request to AWS STS GetCallerIdentity API. # Use the required regional endpoint. Otherwise, the request will fail. @@ -531,7 +536,7 @@ def retrieve_subject_token(self, request): json.dumps(aws_signed_req, separators=(",", ":"), sort_keys=True) ) - def _get_region(self, request, url, session_token): + def _get_region(self, request, url, imdsv2_session_token): """Retrieves the current AWS region from either the AWS_REGION or AWS_DEFAULT_REGION environment variable or from the AWS metadata server. @@ -539,7 +544,7 @@ def _get_region(self, request, url, session_token): request (google.auth.transport.Request): A callable used to make HTTP requests. url (str): The AWS metadata server region URL. - session_token (str): The AWS session token to be added as a + imdsv2_session_token (str): The AWS IMDSv2 session token to be added as a header in the requests to AWS metadata endpoint. Returns: @@ -564,8 +569,8 @@ def _get_region(self, request, url, session_token): raise exceptions.RefreshError("Unable to determine AWS region") headers = None - if session_token is not None: - headers = {"X-aws-ec2-metadata-token": session_token} + if imdsv2_session_token is not None: + headers = {"X-aws-ec2-metadata-token": imdsv2_session_token} response = request(url=self._region_url, method="GET", headers=headers) @@ -585,7 +590,7 @@ def _get_region(self, request, url, session_token): # Only the us-east-2 part should be used. return response_body[:-1] - def _get_security_credentials(self, request, session_token): + def _get_security_credentials(self, request, imdsv2_session_token): """Retrieves the AWS security credentials required for signing AWS requests from either the AWS security credentials environment variables or from the AWS metadata server. @@ -593,7 +598,7 @@ def _get_security_credentials(self, request, session_token): Args: request (google.auth.transport.Request): A callable used to make HTTP requests. - session_token (str): The AWS session token to be added as a + imdsv2_session_token (str): The AWS IMDSv2 session token to be added as a header in the requests to AWS metadata endpoint. Returns: @@ -620,11 +625,11 @@ def _get_security_credentials(self, request, session_token): } # Get role name. - role_name = self._get_metadata_role_name(request, session_token) + role_name = self._get_metadata_role_name(request, imdsv2_session_token) # Get security credentials. credentials = self._get_metadata_security_credentials( - request, role_name, session_token + request, role_name, imdsv2_session_token ) return { @@ -633,7 +638,9 @@ def _get_security_credentials(self, request, session_token): "security_token": credentials.get("Token"), } - def _get_metadata_security_credentials(self, request, role_name, session_token): + def _get_metadata_security_credentials( + self, request, role_name, imdsv2_session_token + ): """Retrieves the AWS security credentials required for signing AWS requests from the AWS metadata server. @@ -643,7 +650,7 @@ def _get_metadata_security_credentials(self, request, role_name, session_token): role_name (str): The AWS role name required by the AWS metadata server security_credentials endpoint in order to return the credentials. - session_token (str): The AWS session token to be added as a + imdsv2_session_token (str): The AWS IMDSv2 session token to be added as a header in the requests to AWS metadata endpoint. Returns: @@ -655,8 +662,8 @@ def _get_metadata_security_credentials(self, request, role_name, session_token): retrieving the AWS security credentials. """ headers = {"Content-Type": "application/json"} - if session_token is not None: - headers["X-aws-ec2-metadata-token"] = session_token + if imdsv2_session_token is not None: + headers["X-aws-ec2-metadata-token"] = imdsv2_session_token response = request( url="{}/{}".format(self._security_credentials_url, role_name), @@ -680,7 +687,7 @@ def _get_metadata_security_credentials(self, request, role_name, session_token): return credentials_response - def _get_metadata_role_name(self, request, session_token): + def _get_metadata_role_name(self, request, imdsv2_session_token): """Retrieves the AWS role currently attached to the current AWS workload by querying the AWS metadata server. This is needed for the AWS metadata server security credentials endpoint in order to retrieve @@ -689,7 +696,7 @@ def _get_metadata_role_name(self, request, session_token): Args: request (google.auth.transport.Request): A callable used to make HTTP requests. - session_token (str): The AWS session token to be added as a + imdsv2_session_token (str): The AWS IMDSv2 session token to be added as a header in the requests to AWS metadata endpoint. Returns: @@ -705,8 +712,8 @@ def _get_metadata_role_name(self, request, session_token): ) headers = None - if session_token is not None: - headers = {"X-aws-ec2-metadata-token": session_token} + if imdsv2_session_token is not None: + headers = {"X-aws-ec2-metadata-token": imdsv2_session_token} response = request( url=self._security_credentials_url, method="GET", headers=headers diff --git a/packages/google-auth/tests/test_aws.py b/packages/google-auth/tests/test_aws.py index 2bace5d0783e..d55afa6a8450 100644 --- a/packages/google-auth/tests/test_aws.py +++ b/packages/google-auth/tests/test_aws.py @@ -42,7 +42,7 @@ SUBJECT_TOKEN_TYPE = "urn:ietf:params:aws:token-type:aws4_request" AUDIENCE = "//iam.googleapis.com/projects/123456/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID" REGION_URL = "http://169.254.169.254/latest/meta-data/placement/availability-zone" -AWS_SESSION_TOKEN_URL = "http://169.254.169.254/latest/api/token" +IMDSV2_SESSION_TOKEN_URL = "http://169.254.169.254/latest/api/token" SECURITY_CREDS_URL = "http://169.254.169.254/latest/meta-data/iam/security-credentials" CRED_VERIFICATION_URL = ( "https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15" @@ -579,7 +579,7 @@ class TestCredentials(object): "SecretAccessKey": SECRET_ACCESS_KEY, "Token": TOKEN, } - AWS_SESSION_TOKEN = "awssessiontoken" + AWS_IMDSV2_SESSION_TOKEN = "awsimdsv2sessiontoken" AWS_SIGNATURE_TIME = "2020-08-11T06:55:22Z" CREDENTIAL_SOURCE = { "environment_id": "aws1", @@ -656,8 +656,8 @@ def make_mock_request( token_data=None, impersonation_status=None, impersonation_data=None, - session_token_status=None, - session_token_data=None, + imdsv2_session_token_status=None, + imdsv2_session_token_data=None, ): """Utility function to generate a mock HTTP request object. This will facilitate testing various edge cases by specify how the @@ -665,12 +665,14 @@ def make_mock_request( in an AWS environment. """ responses = [] - if session_token_status: + if imdsv2_session_token_status: # AWS session token request - session_response = mock.create_autospec(transport.Response, instance=True) - session_response.status = session_token_status - session_response.data = session_token_data - responses.append(session_response) + imdsv2_session_response = mock.create_autospec( + transport.Response, instance=True + ) + imdsv2_session_response.status = imdsv2_session_token_status + imdsv2_session_response.data = imdsv2_session_token_data + responses.append(imdsv2_session_response) if region_status: # AWS region request. @@ -1035,11 +1037,13 @@ def test_retrieve_subject_token_success_temp_creds_no_environment_vars_idmsv2( role_name=self.AWS_ROLE, security_credentials_status=http_client.OK, security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE, - session_token_status=http_client.OK, - session_token_data=self.AWS_SESSION_TOKEN, + imdsv2_session_token_status=http_client.OK, + imdsv2_session_token_data=self.AWS_IMDSV2_SESSION_TOKEN, ) credential_source_token_url = self.CREDENTIAL_SOURCE.copy() - credential_source_token_url["aws_session_token_url"] = AWS_SESSION_TOKEN_URL + credential_source_token_url[ + "imdsv2_session_token_url" + ] = IMDSV2_SESSION_TOKEN_URL credentials = self.make_credentials( credential_source=credential_source_token_url ) @@ -1056,21 +1060,21 @@ def test_retrieve_subject_token_success_temp_creds_no_environment_vars_idmsv2( # Assert session token request self.assert_aws_metadata_request_kwargs( request.call_args_list[0][1], - AWS_SESSION_TOKEN_URL, - {"X-aws-ec2-metadata-token-ttl-seconds": "21600"}, + IMDSV2_SESSION_TOKEN_URL, + {"X-aws-ec2-metadata-token-ttl-seconds": "300"}, "PUT", ) # Assert region request. self.assert_aws_metadata_request_kwargs( request.call_args_list[1][1], REGION_URL, - {"X-aws-ec2-metadata-token": self.AWS_SESSION_TOKEN}, + {"X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN}, ) # Assert role request. self.assert_aws_metadata_request_kwargs( request.call_args_list[2][1], SECURITY_CREDS_URL, - {"X-aws-ec2-metadata-token": self.AWS_SESSION_TOKEN}, + {"X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN}, ) # Assert security credentials request. self.assert_aws_metadata_request_kwargs( @@ -1078,7 +1082,7 @@ def test_retrieve_subject_token_success_temp_creds_no_environment_vars_idmsv2( "{}/{}".format(SECURITY_CREDS_URL, self.AWS_ROLE), { "Content-Type": "application/json", - "X-aws-ec2-metadata-token": self.AWS_SESSION_TOKEN, + "X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN, }, ) @@ -1088,8 +1092,8 @@ def test_retrieve_subject_token_success_temp_creds_no_environment_vars_idmsv2( role_name=self.AWS_ROLE, security_credentials_status=http_client.OK, security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE, - session_token_status=http_client.OK, - session_token_data=self.AWS_SESSION_TOKEN, + imdsv2_session_token_status=http_client.OK, + imdsv2_session_token_data=self.AWS_IMDSV2_SESSION_TOKEN, ) credentials.retrieve_subject_token(new_request) @@ -1099,15 +1103,15 @@ def test_retrieve_subject_token_success_temp_creds_no_environment_vars_idmsv2( # Assert session token request self.assert_aws_metadata_request_kwargs( request.call_args_list[0][1], - AWS_SESSION_TOKEN_URL, - {"X-aws-ec2-metadata-token-ttl-seconds": "21600"}, + IMDSV2_SESSION_TOKEN_URL, + {"X-aws-ec2-metadata-token-ttl-seconds": "300"}, "PUT", ) # Assert role request. self.assert_aws_metadata_request_kwargs( new_request.call_args_list[1][1], SECURITY_CREDS_URL, - {"X-aws-ec2-metadata-token": self.AWS_SESSION_TOKEN}, + {"X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN}, ) # Assert security credentials request. self.assert_aws_metadata_request_kwargs( @@ -1115,7 +1119,7 @@ def test_retrieve_subject_token_success_temp_creds_no_environment_vars_idmsv2( "{}/{}".format(SECURITY_CREDS_URL, self.AWS_ROLE), { "Content-Type": "application/json", - "X-aws-ec2-metadata-token": self.AWS_SESSION_TOKEN, + "X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN, }, ) @@ -1125,11 +1129,13 @@ def test_retrieve_subject_token_session_error_idmsv2(self, utcnow): self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ" ) request = self.make_mock_request( - session_token_status=http_client.UNAUTHORIZED, - session_token_data="unauthorized", + imdsv2_session_token_status=http_client.UNAUTHORIZED, + imdsv2_session_token_data="unauthorized", ) credential_source_token_url = self.CREDENTIAL_SOURCE.copy() - credential_source_token_url["aws_session_token_url"] = AWS_SESSION_TOKEN_URL + credential_source_token_url[ + "imdsv2_session_token_url" + ] = IMDSV2_SESSION_TOKEN_URL credentials = self.make_credentials( credential_source=credential_source_token_url ) @@ -1142,8 +1148,8 @@ def test_retrieve_subject_token_session_error_idmsv2(self, utcnow): # Assert session token request self.assert_aws_metadata_request_kwargs( request.call_args_list[0][1], - AWS_SESSION_TOKEN_URL, - {"X-aws-ec2-metadata-token-ttl-seconds": "21600"}, + IMDSV2_SESSION_TOKEN_URL, + {"X-aws-ec2-metadata-token-ttl-seconds": "300"}, "PUT", ) From 4dddf44c2b14b43fef8664a915ec41d36903350b Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Tue, 15 Mar 2022 16:56:16 -0600 Subject: [PATCH 541/966] chore: let release-please finish the release (#991) Release-As: 2.6.2 --- packages/google-auth/.github/release-please.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/google-auth/.github/release-please.yml b/packages/google-auth/.github/release-please.yml index 4507ad0598a5..466597e5b196 100644 --- a/packages/google-auth/.github/release-please.yml +++ b/packages/google-auth/.github/release-please.yml @@ -1 +1,2 @@ releaseType: python +handleGHRelease: true From c1dc419ae4bbe0f5871f574c567d497e58d9120e Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Wed, 16 Mar 2022 10:45:53 +0000 Subject: [PATCH 542/966] chore: refresh system test creds (#994) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 1b54a369cfee985df6243b6e2223fddfa6906489..2e502601892ea1a36f7c39febdd8f4ea86cad1b4 100644 GIT binary patch literal 10324 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 mX{SKpR{4~~mg4lICi2mp(0 Date: Wed, 16 Mar 2022 11:59:23 -0700 Subject: [PATCH 543/966] chore(main): release 2.6.2 (#992) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 12 ++++++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 7bfa22dd8239..73076fca406c 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,18 @@ [1]: https://pypi.org/project/google-auth/#history +### [2.6.2](https://github.com/googleapis/google-auth-library-python/compare/v2.6.1...v2.6.2) (2022-03-16) + + +### Bug Fixes + +* Rename aws imdsv2 url field and update token lifetime ([#982](https://github.com/googleapis/google-auth-library-python/issues/982)) ([818e6d2](https://github.com/googleapis/google-auth-library-python/commit/818e6d2e63e58601499f0eaac1dd160345d9d6e4)) + + +### Miscellaneous Chores + +* let release-please finish the release ([#991](https://github.com/googleapis/google-auth-library-python/issues/991)) ([d2bdc9a](https://github.com/googleapis/google-auth-library-python/commit/d2bdc9a8a23930a01e5b8445e869a135511977cf)) + ### [2.6.1](https://github.com/googleapis/google-auth-library-python/compare/v2.6.0...v2.6.1) (2022-02-09) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 02ca20699fda..f250531a097f 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.6.1" +__version__ = "2.6.2" From ebfeb6c341fe5805bf9e52c671d9fe8a78243a70 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Tue, 5 Apr 2022 15:43:44 -0700 Subject: [PATCH 544/966] fix: pin click version and update sys test creds (#1008) --- packages/google-auth/noxfile.py | 9 +++++++-- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index 1032be641e1f..552f0019269e 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -20,6 +20,9 @@ CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() +# https://github.com/psf/black/issues/2964, pin click version to 8.0.4 to +# avoid incompatiblity with black. +CLICK_VERSION = "click==8.0.4" BLACK_VERSION = "black==19.3b0" BLACK_PATHS = [ "google", @@ -33,7 +36,9 @@ @nox.session(python="3.7") def lint(session): - session.install("flake8", "flake8-import-order", "docutils", BLACK_VERSION) + session.install( + "flake8", "flake8-import-order", "docutils", CLICK_VERSION, BLACK_VERSION + ) session.install("-e", ".") session.run("black", "--check", *BLACK_PATHS) session.run( @@ -58,7 +63,7 @@ def blacken(session): https://github.com/googleapis/synthtool/blob/master/docker/owlbot/python/Dockerfile """ - session.install(BLACK_VERSION) + session.install(CLICK_VERSION, BLACK_VERSION) session.run("black", *BLACK_PATHS) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 2e502601892ea1a36f7c39febdd8f4ea86cad1b4..9d569ccfbf0c70dc6bc20bb922c40e46338aa93a 100644 GIT binary patch literal 10324 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%0Do;SOgK}%iQmQa2*T*M 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 m){os8=&LqAbGrsVnPd_(z Date: Tue, 5 Apr 2022 22:17:54 -0700 Subject: [PATCH 545/966] fix: clean up HTTP session and pool during tear down phase (#1007) * Add unit tests for the change * Fix the unittest to test on the correct class * Make linter happy Co-authored-by: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> --- .../google-auth/google/auth/transport/requests.py | 4 ++++ .../google-auth/google/auth/transport/urllib3.py | 4 ++++ .../google-auth/tests/transport/test_requests.py | 6 ++++++ packages/google-auth/tests/transport/test_urllib3.py | 12 ++++++++++++ 4 files changed, 26 insertions(+) diff --git a/packages/google-auth/google/auth/transport/requests.py b/packages/google-auth/google/auth/transport/requests.py index a55b5f57b6e0..cf5f0b1a03e1 100644 --- a/packages/google-auth/google/auth/transport/requests.py +++ b/packages/google-auth/google/auth/transport/requests.py @@ -149,6 +149,10 @@ def __init__(self, session=None): self.session = session + def __del__(self): + if hasattr(self, "session") and self.session is not None: + self.session.close() + def __call__( self, url, diff --git a/packages/google-auth/google/auth/transport/urllib3.py b/packages/google-auth/google/auth/transport/urllib3.py index ad67327a4870..4abc26b52278 100644 --- a/packages/google-auth/google/auth/transport/urllib3.py +++ b/packages/google-auth/google/auth/transport/urllib3.py @@ -427,6 +427,10 @@ def __exit__(self, exc_type, exc_val, exc_tb): """Proxy to ``self.http``.""" return self.http.__exit__(exc_type, exc_val, exc_tb) + def __del__(self): + if hasattr(self, "http") and self.http is not None: + self.http.clear() + @property def headers(self): """Proxy to ``self.http``.""" diff --git a/packages/google-auth/tests/transport/test_requests.py b/packages/google-auth/tests/transport/test_requests.py index 60d44a5f4e0b..9018e5c8d497 100644 --- a/packages/google-auth/tests/transport/test_requests.py +++ b/packages/google-auth/tests/transport/test_requests.py @@ -51,6 +51,12 @@ def test_timeout(self): assert http.request.call_args[1]["timeout"] == 5 + def test_session_closed_on_del(self): + http = mock.create_autospec(requests.Session, instance=True) + request = google.auth.transport.requests.Request(http) + request.__del__() + http.close.assert_called_with() + class TestTimeoutGuard(object): def make_guard(self, *args, **kwargs): diff --git a/packages/google-auth/tests/transport/test_urllib3.py b/packages/google-auth/tests/transport/test_urllib3.py index 396961c394fd..eb82b7744fce 100644 --- a/packages/google-auth/tests/transport/test_urllib3.py +++ b/packages/google-auth/tests/transport/test_urllib3.py @@ -305,3 +305,15 @@ def test_configure_mtls_channel_without_client_cert_env( is_mtls = authed_http.configure_mtls_channel(callback) assert not is_mtls get_client_cert_and_key.assert_not_called() + + def test_clear_pool_on_del(self): + http = mock.create_autospec(urllib3.PoolManager) + authed_http = google.auth.transport.urllib3.AuthorizedHttp( + mock.sentinel.credentials, http=http + ) + authed_http.__del__() + http.clear.assert_called_with() + + authed_http.http = None + authed_http.__del__() + # Expect it to not crash From ccbf1b09d608fcc526e675456d3a7863cbb21615 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Wed, 6 Apr 2022 15:36:03 -0700 Subject: [PATCH 546/966] fix: change requests lib import place (#1010) --- packages/google-auth/google/auth/impersonated_credentials.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index 48acd1bb31e9..00cabac3a26d 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -37,7 +37,6 @@ from google.auth import credentials from google.auth import exceptions from google.auth import jwt -from google.auth.transport.requests import AuthorizedSession _DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds @@ -276,6 +275,7 @@ def _update_token(self, request): ) def sign_bytes(self, message): + from google.auth.transport.requests import AuthorizedSession iam_sign_endpoint = _IAM_SIGN_ENDPOINT.format(self._target_principal) @@ -407,6 +407,7 @@ def with_quota_project(self, quota_project_id): @_helpers.copy_docstring(credentials.Credentials) def refresh(self, request): + from google.auth.transport.requests import AuthorizedSession iam_sign_endpoint = _IAM_IDTOKEN_ENDPOINT.format( self._target_credentials.signer_email From d754dc1e6f0ff5441f83b70889684a21f9a35313 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Wed, 6 Apr 2022 16:51:59 -0700 Subject: [PATCH 547/966] test: update sys test cred (#1011) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 9d569ccfbf0c70dc6bc20bb922c40e46338aa93a..62fadae136530a131370e444accbeb7a6e436831 100644 GIT binary patch literal 10324 zcmV-aD67{BB>?tKRTH<{1M9LQ$Ft_~HKOt93IP&O9wsFl7Ew&jd91O~>G2Y(PyjE^ zFChHu#>Il|45H?5MqqI(781+9dxKH&r*<-}2*zIp*PsSKTWV(Sm8W2~IXMM4_sjK7 zlJs%AEv5=Kpzf92j0(v<3&-9{Y9>xnSNuZkH}a{zFQC_!B*23=QWGC?L=e`32-EAQ z>x5vl7U>502=e(^2*!r19$Ta86W6;~&zn9mBRtgP#5lvCIA!j#2#FxWChi@VZElAr z^b3D>4to1t3Tb@6Id?+R`5Y@WCl#iU(S?y4G%@%3z;I+1=%K zM$o`4`kmDzzRQ{A^6K3C2F5&rsWX<$O3!|lhgsq@c*6=`7*b;4o^e2(Qh!hQ}kuK#8>OrZF zd(&FC8$u|WWcqIY840~m$g1geG`f!sJqwdbOlIc7lEqR)r?F*j?_@d_7yV06qAh5? zY@XN2IU_H-QYOZ(op4rh=mXDN8uX;ehVL>BpNBX_+l5Cm&#ylFaEy+wJ`k3GVK4^jyfYKTPSqWdFcUOX^j}h~4MJymZ(1;EuQIgdPmQaOcP` z@qrkYspb)59-F4=?0;!5_Ovvf7^dl?4%r--3WAsa(d8mMWO%F{IT5g?i@DA-xuG$m z+IBeaGBiZPv#CA@U<0mW1QUU^8H}5`s2oOcai%o-WiM0?VEzNMz@oOC!jf1ni%>^J zx-=cU;z72Rh>S`}(e!V=iiW}Als^J*S1+c^g!>zS(xcKccds4&|2^aTOqzcPKQafo zt*{4VW6G=Tqcly=^;jH@xU81a2FGd*qxf;-IQN^~{v#uOovk=Qh2ha6nI2E=lVcq( z(Ln9`b^v;(Tn@s)^BECr9+s(v&Eg1Dxk83E3;#=XF{^>P+ei?e5;psi(0wBKGdtCh zsrq5}u{Xgo3+yrQpJ?g?A>MuIJW0OzbngunEO-AypFv5t0B( z(=+U-bJ-kcMBH5+^t0OD6?dy5dg2T+;7~pQ7a-P^rBN<^9z^WnA0pbSw=4|#!QP9Hb!~T)$B^Nn~Al+7sww&|On-klDf;Q{a?AwJlMy*PAtVAH{i~e? zn@+nU7x-PVb=uf6ammipw}41(KvH)bn$&zgucsh`>W~09a}210G&b7|kOZh-?IU-{ zJP#+a)JW%LoFKRS$(Whh2yF#ecP0B9z<^@`-1dO{%KO_s#CM42P(7e;M0$KPh-9qF zt(q~)Bg42>$TjbeQ~(Snq7mgD1HHWiMpvF5^0Rc(IB*uwmsYn>jb?W_uT;(ayom>P za>u#S!EajRZ3hT-t6OP=SNoNodH$L_h+)Ct%~PQMSD0RkE-d;7uU2e>oXTbdO5q?M zN9-;1IPpw2&s@K2lB}fZZxzqBZNgWn0Krz;^jFNuSgF?L=Z1=0Z;r(Vr@#_7A!q8g zl`#_Cv1Ytd3+0dC!>8Rv{m(WtY`iV~5a=+BC|1#nZDX2TnK!>b!^2_P58N{Z+p!KU zd|NsVP1#H7*%y{NOK~Y4M=6Bt3kH9jLT<00mtq35FEdbMGq;|>k-a~y69C*6mrOK2 zj8{SscZI0p8{43by9p5;VeUOwS)43K6NirJVoq?){2Chr^2zM11;h_1XQCK<)4I@@JFqE?pOrk`0GmC-X!#E=ZNf_M z&p>RP3tUc5f9c>~@l2AvbDo4FcxG(_OeOcLJ@rN@c`v8e-slx>!!DMJYBh=4T-q1jE@s)|c@(zVFT`7j{N4|CGJKbSZ3} zLBYi>m+#x_8-O0U%BR-CKcmXYkE0z6iAp-h!8rd5xp)g|PM zfrx!OzjRvL@Ue=1>gKd{I*UQMmsC}8I;$vz9#*fD11a*chfe6hvm@415PXl5oy6&u z9Cd65$*X?dQQGN#ymDZq!++vr7h2{S@6abGKYwnZv*>r}E}e}Dc9zc;u!A_kBq|9g zWKjTk4EBZXx_bDzE+jLhnB?f_ovS5I6R0oe>VeIfKsmEczqx$*^59L@Rzs;zJ;7fg zMuSGKdLEgiv#Fi?3@BIZZDGQjp}@4aHJGVBG92d#7yY$82G8pR16N!TbhlR#M;)^! zAUnJuT|)eCU9J2F_cZth8sjm5T-o!VEit$@R9da!@gIsoE2u;gqHq1nM&aVKQ1VY2 zca>BlU=9umW&?NB4&DgRn9hTtU{Clc9_bd*7OI$h3lU2pNa`yR-R!+xK2VI0c50T= z3sw7Ayae|tPjt>6ODW&sv1%3TOTUl-k>TS<-pdotdFzzTr6S9dw(RAcZ5MPgVw6U= zCSCZ&Qn{6Dd{oWw4 zGI2iaj^P)w{rp!&xsJK5;UYU=&=Ol-N~Drjgl#{#t9`by_m-1Jopm-r0JC#wLFY^i0;Z| z8sum;>W)-DgV^zSgPXK8PMa(B81I0@q8gb@=U`Ckgcak<){{{=ouIv=vpLqsEt=cM z>Of05Ig78(u3z9*O_J%TD z{N4T*7d|&M5%`_>=jA*Ms{QCtk6{1DjP{c1Jg2(fM%_dFQsMLpFjPD6<^-9>xbtg< zr2PREtBR2qi+GnVl)d$f$2W6-PK_S7bE?7{e!E_j)DD-+PU4`j6T zPZdN+isf>)`o023M@Jn|@X|f1e6@jz9Yd3jEP_1aDfj5>@;CvS0a`8z5smZzKNXXZCWc}QYWQSEPn3X+r&Itkt4Gh)@J3XchFUg3fwRbrE3R3 z)rX{`%)c{V5r7%p8c3Ch;~GAhCk@x^IO)MF4c&Q+pOTYbHu#x3qlB~<-y=rBL&+(` z)Ru_=U%X?btk(tFs}s~)_uXOCq;=`w1Hb^3E*l4O; z%){6JoH94XNHS<28ROrt98rCy1V;@bc@y7O@uUO6uM*9-j*|VqbR3_DA8G7{%1lO$ zvZ2O!+C~TaaafEB;Uslox6kgJ?bfkz5OaW90Kle$N5TP`T%{g0S=w)V(d5kQqBKgx z>>LQ2&ql>~4E|;uD2eL@3Zboo7Eu0|lE(^XmvfZxx1;tpfMG~b*r%uOuW-O!|2QO! zOqm6~I1S$539W8qXS{kG%6BhWX-e|U)E@K0gvhn0mopW(OTU63yK79opaCt02Fqy} z=Ch;z;RMl`Y$AF4>1b|%&vZ@f0z^Nj=7DAk=Ggs=B>f_hhimV2u{^tjaJvtD>a6cQ zH)}8Q>by(@2NZ&0^K=XQ5p3j*N26A9M%+JaN%vl$iz@s|F*Q;1bSyV>A!O=shCy0y z1FpdR{X`n#ZCk45znOjg5D4{vhbDvk6BvHBS;0sewjK6$GYOZ)-; z@9)tQ4H{y>y&_HYD;-C#_KD;1sR6f3j<<40jO}QH#YU{ZlCj?g`f!#MV-XK}?&p$W zA6L3R>SLBf?|nWP9qy`Cj5BFO5E_$}Te44lv|@+xW0@x*a7bB#c(6%C=rL2YgOxPH zyk)R9_RpLS;I0oMD9tP<+fjRS8V%SoZYC%w?cB5$-kiPra7(sSf^&nJFA!rn;UL81 zNyVGq>CaUE>wAWM3pE>Kc9YCxvwyC#xk$ViuMow2>A6pWt#ZY~5j&>N93=*=v}R z@7db?DQY|w6-?roZt>Az6_qEhGD}S+gJ7xbkt6JV{ zu`hGhm{bAqPDMtJT^@T5<&fQ{Z^hWbw5&tV;o5=Oh5yk9#kY&Wzc)oItp63QQ}e_} z9bo zpLg<2g63;ZL(fC93F0e$hec6cR!R(6E{wlQV`I$|!jIAueROOxn|DLG@;lZNJYAHa zQR?SB zj74=_@eqFu5zbP1U<_vOo8EU`-h3D_V6^c0#kO47W~Os(*DO$dVi_u#2}^`(af0Jz z;EGsx^rurq&egA40{Hc6yz*2pggII0*uWqlLQJklG;pIr)tEFpWeOXEw;%~IOwGFO zM@!$ZF8xY3$vcQ2*}w;McMcpr?5KsNHBQoz6C{OCm9Z6IhY=m?q)?8laS8Yk?B3c+ z|J$~M65ju-VQ1k`yN_!y7L{ZxY<-r}lS;Kt*qbH8-!%}0GdlNd1KEdUk=+*$k*TG| zahav|0-!ogCo(gu;NvzI;8we4zy2gO+UH7T8GEz_K7Irw=4y=X@b%LnIQL&q{-I_QcT2fhQicsvts zHAOV?j~ri@07Ykx)i~HfG=fM^R4<4ae-2BP_Af;_go)*tXEFpFg%YY8+uQ!= zNI0oD5b)_`DmN5rK0Fqz$!b?+;C5~KAhWS2;P8g&XEan5Bt?Mn;Vwigp+8xk73!X~ z0|}+No%^55sB}`STEbRK0!-BdTlmL+ z9<0gi!7k-Twr|!lcGU*a>vxywjI5h6-)TIB+>0rVM4YjA4RzD&m)dMSGt6M=lI{oa z#$$TjN4hYV7vT!IZ8DExj(Tx8RDtdYc0Ukp8Ly+u8zsv4m6yeEe;@WLn29i!l_9vn zhizw*o7Khjmw?SJSl@o*FO&&?oM_YMzt{%aUY#^EA%{pS93b}(zD$jH&1OiJD(f0q zh{*XWc$)?V@@2j_;Q`}?v?2m^bz3flee5=Gm0M&bONKxMb}J@+f`uqjk^BhdRKqkc z~q4&vt>8P<4hejl#~N_$$v&-CSzSE!ir*0$0AB$`cPvPb5fEH z_n930p1v@7UPPl4D#u6yy#|VrI=Yi@f*n|m76a*RdB=&SB|$QVg+7OSe5`%`SQZ2# ztPu*NqO=d;{2$)y#284zKI)+-jZX8H6Ia`e31)mYPDEk|b}jQ=BB7(U!ewkNp2k3; zMOp`IjB8-TYFgT~I~|b0{J2SKR_*X@KS`KWEe?82$V1$k6(wl^KjtwAc{`DZ+xLKgKrc7G(sW-$Qmok;CA}abFU=lXL9;8Ox9V zWhzcO;AeI63wuvIbTAkn9LsLr^-K3(0pOIBHE>ZG!^G_aR8p?Fp3hsDjf5Pz4ltru z_O$kQgV>=U45PLc=gyAI%sWdVXc@{G&K(&}xW z0q(afmo1_XZ1*;4JKWPPO3X7$SGP>*B0kv9HY_9(e;+mLH6z70eOZ~9#C^4q+Ia-T z>i*j(MS9+F~v+%`Xg zE?38}dq40w^X<-9SJ=k-WFqh#hMTi8Dv}+)=zeIz`jeq(6o=#PE<18?NGli_t4K9D zGwX^Uhtis|Ql`1OGNV`0dSePCt=8RvPb6|s1?~DT7M12Zbp7=f1l#+1=W;52E5&z* z=0HsJAY*F(o$cr5!TDJ5GN^3+S*fkP5;c~4@0S&L4wM7AdUBB$3rrB{GE5QMQcQtM z`hgX_C<1A$8SNxTUNun-Bt4K3?J_UZISs*a z^|hQ1MizOroOonW_I_u-QPlPCJnqSap?qN!c0wyBWXG#W`kdoEpVQgiGW@Mx^ePT7 ze(HuKK{R?{#dbdGH0CweAZ4E{)r+MZ($+nC<*E|!cfAl|wt;r;8nN8>{xQ=_fu05^ zl!g=0^^>#O9;)>Yn*>Mnz87R^4^TAYFsUBO{Ko(mBgOGSW-Ytw zB7z_YtH4$o(Y1*YYy&`%mVNPjOtAtfK|qNK-8WA__>-4M-N9itL45e>VWVY<CK=nobTO6g%%Dg zkHhV;&f?^uUv*Md@LxHHLBLtvsvsNS;>?y8ixnnxxa+8K7=Mk#w{bsjv8iPPTcIVJ z9*zI$=lncQ=4)5j%8s@Zp!)KNEHFhPNZb`hNe&z**(PQ`1$(=BE6%%b{9Hva^&RsC z{`lW;n(w>cq$|)vCy-ns{NMf6NSd8nyq5FaS?6ElN3P?cQZxFxda1wFeuqh@^dt}H zPrC+CZBB=oV?8H=5Wb=(4DSSe&*Y}i-3_)b?3y8iWvmAeBb%C3`bkLSuv zl?@4l#c6+dMmGCEJi5z zXf?=bihlnC4<<)@WsMK)&p$0iAKb3CnPf?vaEY$v-x!WhGu~AmwiRB)d#sE;p`%zb zig$S2XE_XoG)l)PGH2KSep5*%ZTN31jOJ;O*Q*nfwdNab!2ia{e&xdYlC)z&T5QdO z9Xs+pUGhKgAgaOp;a)6P{8+#F!Tm=kGGK;L-50L|dEI|+a7;7Lo2SD_jtRK}!hj6! z0meyAyTLK7BfRIfRm?bHhbG#F0<1i^NLFBXkwMFAZxqqWKtTumMtW>1Fnws%yRGd! z&OJc@sA2XMkj!T25kHG{=~3taIh?wBOVFyjz-mqFjUr_B0#)D{p!B~ZQ%jZ(QM1xt z9P#%vxLR${9}ngdEZ^sL)%Pf2=A`I{#yhetcJW=~@!>aGG03%h?nZAq-zY(-sTe)u zCSZBPo=MU6yoshvyf%1ex>Ec|y!KCmnBI43$5LNfCs`LM?~=xJ6xoA$>f4JW6O{uL|6G~q;ag6qTAOnoL2)QDJ7w5;*y(JxJ(4#;N&pA1MS zuS;wdug4j;;e>R8U+|omaAnVGf#*`-*V3Or3)0cI_WjD9Pgelx+*?a&J?yD^z!W#W zJYRp-fBEU!SygFcYS%(52T|3Hb|Jk;%n;T}$+#>a(BLyQnao9h_4SL3wKp{SqkH`P z;VC*RPnVN#*gGUN8GD!BW%sMyvVV+k*k!MTTf?G-LnMYeW7J7ie^6wn@w4e1_btwH z#qqnaYrznN(j=HAMQm#xC0eyvJopAt9=l}Ln9lto$CbK<2lA11c(DBF7K3NeD5Q_Q zb17qq+V@7O7IBZxmNb9mIn+LyPY`n-(oNFTvhK;Ie3(VRR3V8>rkTK(*{NLD$Q9IRLHaMYQG zYK*G7G11pvZcWYQ%%Ld<`i#Pgf)0dZLSi$=>XkxHeYRU zpt#Y)OD(@Ba#azv-cAs!UL=H6EP~+-v*jV`O3c-KTt99a84hR|olu^;+<@kFx#&PR z>hA&FNJn?o1n*jbwZU~`5s~#DbxsTWk{Zjs^BIfZ6Xg7MKI1=B*5Z|VpsUFVgo*8i zTpsR8c?d?#({Be$l-qDMKQvwrcppp@z-3@p`9n>m7JRM#2T5*4ZZ2+#u539EE<3Z@ zE^M^-h(5hI$xxsNRcgcQu7hIG4giLu*64<%qq`5aI?4si@0E&gr<@o~v^P(VL9)0a zM9%ed%%rYcZz2rI{gAL2BFWKMdRuN}gZ#Ic6wK^O#b*`6<(h{>jOnW=Eg9dV3d}fz z<~u=~TU-+mT%mU#($@*SSwutwPK{xPMkqJ;vMG*;n*!P zeA50Z$xN^j@JKk9;JDo`zc)Z}rX@@t+H2SMr=n5lVx>k_BJ;<1_7TBR47+t{g$vWq zZYm~%zVM$LH_jBh-p2sppu+?~wvkjKpsG*3>eKIp_))qHcW}zcCc_eQ004{8Q~M@B zb;>?(E=Lz~j_@8<(OXRD4Hx0UDenfLO&H0pO;AK_iq^jOX)xm8zhPDBQ)41~sAs(5m{_sEx-&z{LtHMmEK{H~ zhh=}CppJHE;Xj!-e(PMb{LAS}LzrEeQmF5l;kwclKKp?swb2vb2n&Xi5T7uVcNk!N z)Wj(;GSf{=X*4*vChr)2oxM}a6dWg1PQIMR&#vFGYmD@Sh!!Q1_Y8E#{QJf8>k7MK1Xer()WgK8JbS& z3MPTyA+~0e@njj!6=0FvkLC}IhYc0&%gC36h5AfUbk-+OHDmS!m>In_t7c}|c$f+o|z=om~cCF<~lz=`C~?XNBOFb*oLiC=9@ z9rpMux}<@tIKiqY?D$C?bu2CG)Q?oMFTZKGxS>MIzB56S;BY@b1zl_z#OV$5iv=ty zK-$ZEJb8^~fIz(t<@f@q;n4~iYEZ=S^}H#~eao1!bDp-BzL(t7g;}cSD<2+Mb)KcL z^%E%ay9K9MW2ZROvhz7|moe1(#>&5JV!TbI!uP-ER$QkH7g0x0)dp~Wi--wmjcOp zR?x#UJg%<#g6>Wbmp5&}x`;IFGd&cmzMx~!3C&Ve;TgkYy5y0z`Al%#-LOD963(v$MhxFs@gLdU7@`XdyQ zT*(fzc)nu!#ra8H@=8F^2%^ebDoWQ6!3_N-0=GBXaS#ewVGSUO*P+QyuV9%WJVu1J zQsucc8}QV6o^IfM;l0C3;bGt+@lhRd@BUYv1(fM(Z@V8SoWIv0CqCI5-p|;t4YIlF zS=$3g1@W1F`~&tpm@3(7Yjcyj9lC?MO$uf=xKq=cR^!tk%oHMJ z%N~^G#v`=)z-f?q$QhhSwzOu-;CjIoJnx@lM9{vVASz6OU$_O%<2uTk+2&O858dsD<4;x9DtPgAm(T1ImU`}v*mrXSzO zss(zKS-&22fKiSsNa5zdaB!=!dgt&@=gPYcH%t#EE2H@$lo zOIgAKx2(r)N%l*a)=p8oeIx#8icQ-N18%t=_s$YE^-!*!`Xm60C3x@gN%AP8%7Nt5Yx; zc{l2IvXAr0>&cl+O6jJ{98z^LfflGfeJC{J8ZIzcK3Smc3J`yI1y~aQUoPOcFj#ya zM~Z{QGg)j8fm71ez`233-i8JNpf#Me@a<6?DNJm2$}xupk$GwtUY3L87T{p9g;)(U z;qn(7TslO&B^t4>4X@t2WE4tD1F>HZHRnDkDh$SYJ_`SsVB3Ie3%J^44KjGyjP)d{=^qq0gjk+v?c7;S0vp2)w@!NwScc^;C zPsnn+`3rrjE?~Ve{w)Z?^IF(4e%zAyghZOFhgN$`XiH85MysOnLQNav17CE@Hm$HS zxe6UY2f?JS#!z*f0~E7{t`6sIK@S5DVARIQDUf>+d#%&{>ho~p5B_^gWp8!o`)UOr z7qdvp6JL!Ju9U=MR0;GX{nKsCN+W>}+Fp%#XLL2&;O2H7?v5jLiwZv(n2nncbl+Sq moG?iA?tX+^J}pj{7;JjY^JV?zvG49*2jP@*-Xp##vtU!_o)uUC literal 10324 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%0Do;SOgK}%iQmQa2*T*M zb>4#i->#WTplnfc)XEvo`@-o!@iXz?+L}(sGekB3(MLWD-yVBEB^DaZt(JYO4h-L6!BDS+y7G0xW>gBrTo$Z+5 z&j8^_fJ;DHwGLhYD1#!q6S@B`o%jL#c!5E|%n&R5tO_OEU61>%>ltsUIu|OxZN+h% zZ1I+ij_Rx6He&wf;c&V<8$G4rmi8Nsn#te}%*<6fo+Osr#Cmhyh+o9=almR+9ZNIY zBK7+mA3k~}AcKsl*_JoC6nr}4$nKU9RQy7_&V1;V(atd)t!cFJswmNQx)!)UtN5;2 zY~VR&-{nsSMbGNUR9J

(M=2Ejdn%@0eSojr_hwW8)xNf*I_FGYXw*qWxOTDPE;& zseR;Jl2MTIj8=)`9IZ&~n~a;n8k}g@Q}Xk&b0m!#j$aQ-wJnyg>E=-`QFb7#C=S}p z7R2nLu846MuwvBgr&ixz5z=z<*Mbgz|Ci-Axy%8NW@ff8DD&JhUmDVS6ia(UTG!u$ zlpV_3#K^<_@;L7f_P47iO}l{eu32)8kykiQ7yq!@xWa~l=t1%jDtuC475g3m^_m>P zMwnz9iV+>e%(9ijeZo+g<4$GI5f}X0!R4zzLTD(`@T%Lw^qFkp8%D)xWR!kHN=+RB zvRf^Hfi6ITeP*pwRcVWH&+|hi z(qJ_F!kSx`dQs8%bgQggDygXi3Z2cnN`=D|3SI)&N$5N{26 zd4|(ZSsccmLO4`=Sax$9TW|)yI4l%;)W*gVWKB-TVVYnT*7PtPEN=*(C&D;P9C1b1 z7yK6)@6NsjgzOc9jpm@=BUPuj)v{eXfUO+gGaZD(FlOYO%#5iwB zI7U7F?g4wy;wW39e>IoP69LCWY;~z+-5rV82Nr@+FEc(4XJ0aDOZjTt!b56s!qRnJ zb*EnBY;!rI>i8_hTV*@VF5VQ`lMzGrMby@jg98EN@vrdFs?BesAn$YZWG9cWNM|M%OM%A{l=^Tfow7C+kyLH7=_YD*IPzGJ>JWA2LOqef8vX)DIvr z=8_+$Ewu98uBJ!I_Zpwf|NnU8o}rjp-jyM92!E)TYDNEu!S8MB?34(E{7$sM=vDGx z6=O)LWZ?D9z4+PL;*EhPav*Ie8$rYykcy*@&WDAEly_6e7T+`4_XD~&|M5z|=0vxV zaoi2cKm9~?o2LzaKwH$(C z={cRCg8wg>wr7=@_z_2PX$|dty4`R9ejP-WAIw8Y1#<50lqod-+-_>1rkI4)QKaf< z%B}eB>SsMddU8$C70P7|`TcgnQI_^}Sw}EkN4PgYKaQ}d23YG7(>ewfHN@PJE9f9W m4TL*sg5Nb Date: Wed, 6 Apr 2022 19:23:47 -0700 Subject: [PATCH 548/966] chore(main): release 2.6.3 (#1009) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> Co-authored-by: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 9 +++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 73076fca406c..3093803be656 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,15 @@ [1]: https://pypi.org/project/google-auth/#history +### [2.6.3](https://github.com/googleapis/google-auth-library-python/compare/v2.6.2...v2.6.3) (2022-04-06) + + +### Bug Fixes + +* change requests lib import place ([#1010](https://github.com/googleapis/google-auth-library-python/issues/1010)) ([c753c08](https://github.com/googleapis/google-auth-library-python/commit/c753c08d2c78295173bb1160e8a74e819a352c33)) +* clean up HTTP session and pool during tear down phase ([#1007](https://github.com/googleapis/google-auth-library-python/issues/1007)) ([d057376](https://github.com/googleapis/google-auth-library-python/commit/d057376245283402fe0b772ca138091c05864e5d)) +* pin click version and update sys test creds ([#1008](https://github.com/googleapis/google-auth-library-python/issues/1008)) ([ae2804b](https://github.com/googleapis/google-auth-library-python/commit/ae2804bf292b5c8e6f935d2d0751db8fbe95a7b3)) + ### [2.6.2](https://github.com/googleapis/google-auth-library-python/compare/v2.6.1...v2.6.2) (2022-03-16) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index f250531a097f..d82792a04f44 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.6.2" +__version__ = "2.6.3" From 3a587f0ab35d57a8e9d521c8aa4327ab2e4ab565 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Tue, 12 Apr 2022 11:31:47 -0700 Subject: [PATCH 549/966] fix: fix missing import in _default.py (#1015) * fix: missing import in _default.py * chore: update sys test creds --- packages/google-auth/google/auth/_default.py | 2 ++ .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 2 files changed, 2 insertions(+) diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index 34edda046f63..5730e58d8a2b 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -554,6 +554,8 @@ def default(scopes=None, request=None, quota_project_id=None, default_scopes=Non getattr(credentials, "get_project_id", None) ): if request is None: + import google.auth.transport.requests + request = google.auth.transport.requests.Request() project_id = credentials.get_project_id(request=request) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 62fadae136530a131370e444accbeb7a6e436831..cc1ce181a40a0fb71640e354e019fd3e99f560f4 100644 GIT binary patch literal 10324 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 From 59cf2b12b429ba79d6644223f824d82fc0c84501 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 12 Apr 2022 12:01:43 -0700 Subject: [PATCH 550/966] chore(main): release 2.6.4 (#1017) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 3093803be656..ba24f33b4b27 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +### [2.6.4](https://github.com/googleapis/google-auth-library-python/compare/v2.6.3...v2.6.4) (2022-04-12) + + +### Bug Fixes + +* fix missing import in _default.py ([#1015](https://github.com/googleapis/google-auth-library-python/issues/1015)) ([63f4e38](https://github.com/googleapis/google-auth-library-python/commit/63f4e38153ded9fe9b51b83a1de74f3b71f73abc)) + ### [2.6.3](https://github.com/googleapis/google-auth-library-python/compare/v2.6.2...v2.6.3) (2022-04-06) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index d82792a04f44..ff2b406f06ee 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.6.3" +__version__ = "2.6.4" From 150cd2aa16214ec460049bf3611fbb7d0c8b220e Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Thu, 14 Apr 2022 12:13:46 -0700 Subject: [PATCH 551/966] chore: update system test creds (#1021) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index cc1ce181a40a0fb71640e354e019fd3e99f560f4..2c5988ffd8a9d2a33b49c18d48731a4821f5a04b 100644 GIT binary patch literal 10324 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 Date: Thu, 14 Apr 2022 23:26:08 +0200 Subject: [PATCH 552/966] fix: add additional missing import in _default.py (#1018) Co-authored-by: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> --- packages/google-auth/google/auth/_default.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index 5730e58d8a2b..d038438d5be5 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -340,6 +340,8 @@ def _get_external_account_credentials( "Failed to load external account credentials from {}".format(filename) ) if request is None: + import google.auth.transport.requests + request = google.auth.transport.requests.Request() return credentials, credentials.get_project_id(request=request) From 4f53f1f225a7a56313cf385af491da4b88c282b7 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Thu, 14 Apr 2022 17:49:09 -0400 Subject: [PATCH 553/966] build: switch to release-please for tagging (#1019) Co-authored-by: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> --- packages/google-auth/.github/release-trigger.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 packages/google-auth/.github/release-trigger.yml diff --git a/packages/google-auth/.github/release-trigger.yml b/packages/google-auth/.github/release-trigger.yml new file mode 100644 index 000000000000..d4ca94189e16 --- /dev/null +++ b/packages/google-auth/.github/release-trigger.yml @@ -0,0 +1 @@ +enabled: true From 2bec7a898006f7b8bdc73f56cc7e8c05c3c9c660 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 14 Apr 2022 15:23:37 -0700 Subject: [PATCH 554/966] chore(main): release 2.6.5 (#1023) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> Co-authored-by: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index ba24f33b4b27..b173df31825a 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +### [2.6.5](https://github.com/googleapis/google-auth-library-python/compare/v2.6.4...v2.6.5) (2022-04-14) + + +### Bug Fixes + +* add additional missing import in _default.py ([#1018](https://github.com/googleapis/google-auth-library-python/issues/1018)) ([638331b](https://github.com/googleapis/google-auth-library-python/commit/638331b5b89c807b40c23c1e6333845d9b7e169a)) + ### [2.6.4](https://github.com/googleapis/google-auth-library-python/compare/v2.6.3...v2.6.4) (2022-04-12) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index ff2b406f06ee..b5f48f8f9071 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.6.4" +__version__ = "2.6.5" From 50936ce200864db76be950043bed9092d51fa4b7 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Thu, 21 Apr 2022 14:16:27 -0700 Subject: [PATCH 555/966] chore: update system test creds (#1028) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 2c5988ffd8a9d2a33b49c18d48731a4821f5a04b..e790a926fd6b6128592c92db2f3b64ba4bfdd7bc 100644 GIT binary patch literal 10324 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 Date: Thu, 21 Apr 2022 14:37:06 -0700 Subject: [PATCH 556/966] chore(python): use ubuntu 22.04 in docs image (#1026) Source-Link: https://github.com/googleapis/synthtool/commit/f15cc72fb401b4861cedebb10af74afe428fb1f8 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:bc5eed3804aec2f05fad42aacf973821d9500c174015341f721a984a0825b6fd Co-authored-by: Owl Bot Co-authored-by: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> --- .../google-auth/.github/.OwlBot.lock.yaml | 16 ++++++++++++++- .../.kokoro/docker/docs/Dockerfile | 20 +++++++++++++++++-- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/packages/google-auth/.github/.OwlBot.lock.yaml b/packages/google-auth/.github/.OwlBot.lock.yaml index eecb84c21b27..64f82d6bf4bc 100644 --- a/packages/google-auth/.github/.OwlBot.lock.yaml +++ b/packages/google-auth/.github/.OwlBot.lock.yaml @@ -1,3 +1,17 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:ae600f36b6bc972b368367b6f83a1d91ec2c82a4a116b383d67d547c56fe6de3 + digest: sha256:bc5eed3804aec2f05fad42aacf973821d9500c174015341f721a984a0825b6fd +# created: 2022-04-21T15:43:16.246106921Z diff --git a/packages/google-auth/.kokoro/docker/docs/Dockerfile b/packages/google-auth/.kokoro/docker/docs/Dockerfile index 4e1b1fb8b5a5..238b87b9d1c9 100644 --- a/packages/google-auth/.kokoro/docker/docs/Dockerfile +++ b/packages/google-auth/.kokoro/docker/docs/Dockerfile @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from ubuntu:20.04 +from ubuntu:22.04 ENV DEBIAN_FRONTEND noninteractive @@ -60,8 +60,24 @@ RUN apt-get update \ && rm -rf /var/lib/apt/lists/* \ && rm -f /var/cache/apt/archives/*.deb +###################### Install python 3.8.11 + +# Download python 3.8.11 +RUN wget https://www.python.org/ftp/python/3.8.11/Python-3.8.11.tgz + +# Extract files +RUN tar -xvf Python-3.8.11.tgz + +# Install python 3.8.11 +RUN ./Python-3.8.11/configure --enable-optimizations +RUN make altinstall + +###################### Install pip RUN wget -O /tmp/get-pip.py 'https://bootstrap.pypa.io/get-pip.py' \ - && python3.8 /tmp/get-pip.py \ + && python3 /tmp/get-pip.py \ && rm /tmp/get-pip.py +# Test pip +RUN python3 -m pip + CMD ["python3.8"] From 8a9c6a7eb08b55a2eaba871f0695f5b57d44a093 Mon Sep 17 00:00:00 2001 From: Lidi Zheng Date: Thu, 21 Apr 2022 16:03:51 -0700 Subject: [PATCH 557/966] fix: silence TypeError during tear down stage (#1027) Co-authored-by: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> --- packages/google-auth/google/auth/transport/requests.py | 10 ++++++++-- packages/google-auth/tests/transport/test_requests.py | 6 ++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/google/auth/transport/requests.py b/packages/google-auth/google/auth/transport/requests.py index cf5f0b1a03e1..f4a7c65d7571 100644 --- a/packages/google-auth/google/auth/transport/requests.py +++ b/packages/google-auth/google/auth/transport/requests.py @@ -150,8 +150,14 @@ def __init__(self, session=None): self.session = session def __del__(self): - if hasattr(self, "session") and self.session is not None: - self.session.close() + try: + if hasattr(self, "session") and self.session is not None: + self.session.close() + except TypeError: + # NOTE: For certain Python binary built, the queue.Empty exception + # might not be considered a normal Python exception causing + # TypeError. + pass def __call__( self, diff --git a/packages/google-auth/tests/transport/test_requests.py b/packages/google-auth/tests/transport/test_requests.py index 9018e5c8d497..7899419bee19 100644 --- a/packages/google-auth/tests/transport/test_requests.py +++ b/packages/google-auth/tests/transport/test_requests.py @@ -57,6 +57,12 @@ def test_session_closed_on_del(self): request.__del__() http.close.assert_called_with() + http = mock.create_autospec(requests.Session, instance=True) + http.close.side_effect = TypeError("test injected TypeError") + request = google.auth.transport.requests.Request(http) + request.__del__() + http.close.assert_called_with() + class TestTimeoutGuard(object): def make_guard(self, *args, **kwargs): From 35de4513b1a194c8819751e39a62549ea77de71b Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 21 Apr 2022 16:26:25 -0700 Subject: [PATCH 558/966] chore(main): release 2.6.6 (#1029) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index b173df31825a..712b84126708 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +### [2.6.6](https://github.com/googleapis/google-auth-library-python/compare/v2.6.5...v2.6.6) (2022-04-21) + + +### Bug Fixes + +* silence TypeError during tear down stage ([#1027](https://github.com/googleapis/google-auth-library-python/issues/1027)) ([952a6aa](https://github.com/googleapis/google-auth-library-python/commit/952a6aad888140c13815aada95f33792e414e061)) + ### [2.6.5](https://github.com/googleapis/google-auth-library-python/compare/v2.6.4...v2.6.5) (2022-04-14) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index b5f48f8f9071..6091bbe19874 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.6.5" +__version__ = "2.6.6" From deddf4188b8081c33d3ea61c7f319a51b0e0ea1a Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Thu, 28 Apr 2022 12:09:42 -0700 Subject: [PATCH 559/966] chore: update sys test creds (#1033) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index e790a926fd6b6128592c92db2f3b64ba4bfdd7bc..623fdd64434d29243c00dd62e04ff8d58f196467 100644 GIT binary patch literal 10324 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 literal 10324 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 Date: Fri, 29 Apr 2022 01:13:15 +0300 Subject: [PATCH 560/966] chore(python): update creds expiration threshold comment (#1032) Co-authored-by: Anthonios Partheniou --- packages/google-auth/google/auth/credentials.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/credentials.py b/packages/google-auth/google/auth/credentials.py index ec21a27563b9..004fde9c2f37 100644 --- a/packages/google-auth/google/auth/credentials.py +++ b/packages/google-auth/google/auth/credentials.py @@ -63,7 +63,7 @@ def expired(self): if not self.expiry: return False - # Remove 10 seconds from expiry to err on the side of reporting + # Remove some threshold from expiry to err on the side of reporting # expiration early so that we avoid the 401-refresh-retry loop. skewed_expiry = self.expiry - _helpers.REFRESH_THRESHOLD return _helpers.utcnow() >= skewed_expiry From 939c958cd1104ae7e79962911f967d899aec8a86 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Tue, 3 May 2022 16:13:31 -0700 Subject: [PATCH 561/966] chore: update sys test creds (#1035) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 623fdd64434d29243c00dd62e04ff8d58f196467..d0377369455ab98c1e12dc6bf506ab72a56bb37b 100644 GIT binary patch literal 10324 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 literal 10324 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 From 44d34c818999f4e7d43097d78def5f3fa1796546 Mon Sep 17 00:00:00 2001 From: Jin Date: Tue, 3 May 2022 16:39:26 -0700 Subject: [PATCH 562/966] fix: validate urls for external accounts (#1031) * [Bug 193694420] adding validation of token url and service account impersonation url (#1) [b-193694420] adding url validation for token url and service account impersonation url * adding coverage of empty hostname to invalid token url * Add more tests to enlarge the coverage Co-authored-by: Jin Qin * adding coverage for service account impersonate url validation * using urllib3 instead of urllib since it's the project test requirements included * fix format * addressing comments * remove redundant * fix tests since python 3.6 cannot have same string constants interpret as 3.7+ * fix lint and fix the flaky test in an alternate way * revert the debugging output * fix lint Co-authored-by: Leo <39062083+lsirac@users.noreply.github.com> Co-authored-by: Jin Qin Co-authored-by: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> --- .../google/auth/external_account.py | 55 ++++++++ .../tests/test_external_account.py | 128 +++++++++++++++++- 2 files changed, 182 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/external_account.py b/packages/google-auth/google/auth/external_account.py index cbd0baf4eabd..97aca108988c 100644 --- a/packages/google-auth/google/auth/external_account.py +++ b/packages/google-auth/google/auth/external_account.py @@ -34,6 +34,7 @@ import re import six +from urllib3.util import parse_url from google.auth import _helpers from google.auth import credentials @@ -114,6 +115,12 @@ def __init__( self._default_scopes = default_scopes self._workforce_pool_user_project = workforce_pool_user_project + Credentials.validate_token_url(token_url) + if service_account_impersonation_url: + Credentials.validate_service_account_impersonation_url( + service_account_impersonation_url + ) + if self._client_id: self._client_auth = utils.ClientAuthentication( utils.ClientAuthType.basic, self._client_id, self._client_secret @@ -413,3 +420,51 @@ def _initialize_impersonated_credentials(self): quota_project_id=self._quota_project_id, iam_endpoint_override=self._service_account_impersonation_url, ) + + @staticmethod + def validate_token_url(token_url): + _TOKEN_URL_PATTERNS = [ + "^[^\\.\\s\\/\\\\]+\\.sts\\.googleapis\\.com$", + "^sts\\.googleapis\\.com$", + "^sts\\.[^\\.\\s\\/\\\\]+\\.googleapis\\.com$", + "^[^\\.\\s\\/\\\\]+\\-sts\\.googleapis\\.com$", + ] + + if not Credentials.is_valid_url(_TOKEN_URL_PATTERNS, token_url): + raise ValueError("The provided token URL is invalid.") + + @staticmethod + def validate_service_account_impersonation_url(url): + _SERVICE_ACCOUNT_IMPERSONATION_URL_PATTERNS = [ + "^[^\\.\\s\\/\\\\]+\\.iamcredentials\\.googleapis\\.com$", + "^iamcredentials\\.googleapis\\.com$", + "^iamcredentials\\.[^\\.\\s\\/\\\\]+\\.googleapis\\.com$", + "^[^\\.\\s\\/\\\\]+\\-iamcredentials\\.googleapis\\.com$", + ] + + if not Credentials.is_valid_url( + _SERVICE_ACCOUNT_IMPERSONATION_URL_PATTERNS, url + ): + raise ValueError( + "The provided service account impersonation URL is invalid." + ) + + @staticmethod + def is_valid_url(patterns, url): + """ + Returns True if the provided URL's scheme is HTTPS and the host comforms to at least one of the provided patterns. + """ + # Check specifically for whitespcaces: + # Some python3.6 will parse the space character into %20 and pass the regex check which shouldn't be passed + if not url or len(str(url).split()) > 1: + return False + + try: + uri = parse_url(url) + except Exception: + return False + + if not uri.scheme or uri.scheme != "https" or not uri.hostname: + return False + + return any(re.compile(p).match(uri.hostname.lower()) for p in patterns) diff --git a/packages/google-auth/tests/test_external_account.py b/packages/google-auth/tests/test_external_account.py index 3897aef15e8c..067fb59b6656 100644 --- a/packages/google-auth/tests/test_external_account.py +++ b/packages/google-auth/tests/test_external_account.py @@ -275,9 +275,110 @@ def assert_resource_manager_request_kwargs( assert request_kwargs["headers"] == headers assert "body" not in request_kwargs + def test_valid_token_url_shall_pass_validation(self): + valid_urls = [ + "https://sts.googleapis.com", + "https://us-east-1.sts.googleapis.com", + "https://US-EAST-1.sts.googleapis.com", + "https://sts.us-east-1.googleapis.com", + "https://sts.US-WEST-1.googleapis.com", + "https://us-east-1-sts.googleapis.com", + "https://US-WEST-1-sts.googleapis.com", + "https://us-west-1-sts.googleapis.com/path?query", + ] + + for url in valid_urls: + # A valid url shouldn't throw exception and a None value should be returned + external_account.Credentials.validate_token_url(url) + + def test_invalid_token_url_shall_throw_exceptions(self): + invalid_urls = [ + "https://iamcredentials.googleapis.com", + "sts.googleapis.com", + "https://", + "http://sts.googleapis.com", + "https://st.s.googleapis.com", + "https://us-eas\t-1.sts.googleapis.com", + "https:/us-east-1.sts.googleapis.com", + "https://US-WE/ST-1-sts.googleapis.com", + "https://sts-us-east-1.googleapis.com", + "https://sts-US-WEST-1.googleapis.com", + "testhttps://us-east-1.sts.googleapis.com", + "https://us-east-1.sts.googleapis.comevil.com", + "https://us-east-1.us-east-1.sts.googleapis.com", + "https://us-ea.s.t.sts.googleapis.com", + "https://sts.googleapis.comevil.com", + "hhttps://us-east-1.sts.googleapis.com", + "https://us- -1.sts.googleapis.com", + "https://-sts.googleapis.com", + "https://us-east-1.sts.googleapis.com.evil.com", + ] + + for url in invalid_urls: + # An invalid url should throw a ValueError exception + with pytest.raises(ValueError) as excinfo: + external_account.Credentials.validate_token_url(url) + + assert excinfo.match("The provided token URL is invalid.") + + def test_valid_service_account_impersonation_url_shall_pass_validation(self): + valid_urls = [ + "https://iamcredentials.googleapis.com", + "https://us-east-1.iamcredentials.googleapis.com", + "https://US-EAST-1.iamcredentials.googleapis.com", + "https://iamcredentials.us-east-1.googleapis.com", + "https://iamcredentials.US-WEST-1.googleapis.com", + "https://us-east-1-iamcredentials.googleapis.com", + "https://US-WEST-1-iamcredentials.googleapis.com", + "https://us-west-1-iamcredentials.googleapis.com/path?query", + ] + + for url in valid_urls: + # A valid url shouldn't throw exception and a None value should be returned + external_account.Credentials.validate_service_account_impersonation_url(url) + + def test_invalid_service_account_impersonate_url_shall_throw_exceptions(self): + invalid_urls = [ + "https://sts.googleapis.com", + "iamcredentials.googleapis.com", + "https://", + "http://iamcredentials.googleapis.com", + "https://iamcre.dentials.googleapis.com", + "https://us-eas\t-1.iamcredentials.googleapis.com", + "https:/us-east-1.iamcredentials.googleapis.com", + "https://US-WE/ST-1-iamcredentials.googleapis.com", + "https://iamcredentials-us-east-1.googleapis.com", + "https://iamcredentials-US-WEST-1.googleapis.com", + "testhttps://us-east-1.iamcredentials.googleapis.com", + "https://us-east-1.iamcredentials.googleapis.comevil.com", + "https://us-east-1.us-east-1.iamcredentials.googleapis.com", + "https://us-ea.s.t.iamcredentials.googleapis.com", + "https://iamcredentials.googleapis.comevil.com", + "hhttps://us-east-1.iamcredentials.googleapis.com", + "https://us- -1.iamcredentials.googleapis.com", + "https://-iamcredentials.googleapis.com", + "https://us-east-1.iamcredentials.googleapis.com.evil.com", + ] + + for url in invalid_urls: + # An invalid url should throw a ValueError exception + with pytest.raises(ValueError) as excinfo: + external_account.Credentials.validate_service_account_impersonation_url( + url + ) + + assert excinfo.match( + "The provided service account impersonation URL is invalid." + ) + def test_default_state(self): - credentials = self.make_credentials() + credentials = self.make_credentials( + service_account_impersonation_url=self.SERVICE_ACCOUNT_IMPERSONATION_URL + ) + # Token url and service account impersonation url should be set + assert credentials._token_url + assert credentials._service_account_impersonation_url # Not token acquired yet assert not credentials.token assert not credentials.valid @@ -289,6 +390,31 @@ def test_default_state(self): assert credentials.requires_scopes assert not credentials.quota_project_id + def test_invalid_token_url(self): + with pytest.raises(ValueError) as excinfo: + CredentialsImpl( + audience=self.AUDIENCE, + subject_token_type=self.SUBJECT_TOKEN_TYPE, + token_url="https:///v1/token", + credential_source=self.CREDENTIAL_SOURCE, + ) + + assert excinfo.match("The provided token URL is invalid.") + + def test_invalid_service_account_impersonate_url(self): + with pytest.raises(ValueError) as excinfo: + CredentialsImpl( + audience=self.AUDIENCE, + subject_token_type=self.SUBJECT_TOKEN_TYPE, + token_url=self.TOKEN_URL, + credential_source=self.CREDENTIAL_SOURCE, + service_account_impersonation_url=12345, # create an exception by sending to parse url + ) + + assert excinfo.match( + "The provided service account impersonation URL is invalid." + ) + def test_nonworkforce_with_workforce_pool_user_project(self): with pytest.raises(ValueError) as excinfo: CredentialsImpl( From 5df116116e1f649e770bb7787269ad8bb34520e8 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Wed, 4 May 2022 12:11:32 -0700 Subject: [PATCH 563/966] chore: move lint,cover,mypy session to use py3.8 (#1037) --- packages/google-auth/noxfile.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index 552f0019269e..937d35d6982e 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -34,7 +34,7 @@ ] -@nox.session(python="3.7") +@nox.session(python="3.8") def lint(session): session.install( "flake8", "flake8-import-order", "docutils", CLICK_VERSION, BLACK_VERSION @@ -67,7 +67,7 @@ def blacken(session): session.run("black", *BLACK_PATHS) -@nox.session(python="3.6") +@nox.session(python="3.8") def mypy(session): """Verify type hints are mypy compatible.""" session.install("-e", ".") @@ -121,7 +121,7 @@ def unit_prev_versions(session): ) -@nox.session(python="3.7") +@nox.session(python="3.8") def cover(session): session.install("-r", "testing/requirements.txt") session.install("-e", ".") From 6646855968a8d8acd780da39ccd6b01dec3cbdc1 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Tue, 10 May 2022 11:09:30 -0700 Subject: [PATCH 564/966] chore: update sys test creds (#1038) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10323 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index d0377369455ab98c1e12dc6bf506ab72a56bb37b..91f6f45702822fe53e24ec255c572ac6d3a7aba3 100644 GIT binary patch literal 10323 zcmV-ZD6H2CB>?tKRTF!}KY{c}^}eKmKeGLJnGlRA|0v4}KwoM8VTURU=n)dBPyjE^ zFCYj7AaKTTI9-ur>AP@BO_yJuFDw^9`OOyG{GmRGch9x$I9W98IQkDfBoePEWTsB=z*y=F>jjWD_Xzh_&tQ9D;92ru3Gxus5-~n|Gwi za+S#bg<%*NnKDLXkowcyyJJjYp!(S$Cke2bjE|o4i5prA`5*EzBh{1OJsjhC=}DR0 zM@*3jIk_yEjV{(UkHqRW9yL9cSUP~|>(%I?)wCnyHiLMyw+4=`hrP=iUWg`GNsmiJztPkdRFs`^Gl6$vW$hC{qo;=2iXO+HMY;@L!&#tM zL7~#i0E;EDnN^5|RQsSP63UncME*W8x(%al3g02e-*8oT0;JM&@1UH>@5{H+QNg#0 z*CCgncUX?;kB=vb{b?3Qx3A**!=Nbi=psZSu?UjASycS^{ALjMzR(qhkgNLBMN|hB zr?-W9qLON)a)WalstmW-q}P1@01JNju|rad33(_Ap@I!V%s@@;Ac|xD6nE%F|KtEa z7+<-wL2qJp4_{y)mvQMs1%+FPL(l%-go2C51>!bw5^RPi=g$P13b}OM+9h{C92ykX zx0hW40Ex8m@$@Nu!7GKmb9B&l(1gO8hpAVELspWa)G+KGlJ8MW5YVA8`v2JDe^IwI=*&)

)bIsLe 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 literal 10324 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 From 97bde1697bbd901d3d5ec436a92b13086842f05c Mon Sep 17 00:00:00 2001 From: Chuan Ren Date: Tue, 10 May 2022 11:30:09 -0700 Subject: [PATCH 565/966] feat: Pluggable auth support (#995) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Add Pluggable auth support (#988) * Port identity pool credentials * access_token retrieved * -> pluggable * Update pluggable.py * Create test_pluggable.py * Unit tests * Address pr issues * feat: Add file caching (#990) * Add file cache * feat: add output file cache support * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * Update pluggable.py * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * Update pluggable.py * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * Update setup.py * Update setup.py * Update setup.py * pytest_subprocess * timeout * Update pluggable.py * env * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * Update _default.py * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * Update requirements.txt * Update _default.py * Update pluggable.py * Update pluggable.py * Update pluggable.py * Update test_pluggable.py * format validations * Update _default.py * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * Update requirements.txt * Revert "Update requirements.txt" This reverts commit 1c9b6db25c683663ed4b71ab0ab39946fce8f6eb. * Revert "Update _default.py" This reverts commit ac6c36072084a440c234a9465b35462bd52378b3. * Revert "Revert "Update _default.py"" This reverts commit 1c08483586007e4caf1a36f2c9cbf2a45d403ee0. * Raise output format error but retry parsing token if `success` is 0 * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * Update requirements.txt * Delete test_pluggable.py * Revert "Delete test_pluggable.py" This reverts commit 74beba9405564a5b764af8718c49e640d9b84c5f. * Update pluggable.py * Update pluggable.py * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * pytest-subprocess * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * lint * Update pluggable.py * nox cover nox cover * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * lint * Update test_pluggable.py * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * Update test_pluggable.py Co-authored-by: Owl Bot Co-authored-by: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> --- packages/google-auth/google/auth/_default.py | 11 +- packages/google-auth/google/auth/pluggable.py | 349 +++++++++ packages/google-auth/tests/test__default.py | 20 + packages/google-auth/tests/test_pluggable.py | 692 ++++++++++++++++++ 4 files changed, 1071 insertions(+), 1 deletion(-) create mode 100644 packages/google-auth/google/auth/pluggable.py create mode 100644 packages/google-auth/tests/test_pluggable.py diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index d038438d5be5..dc54c44b6fc8 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -317,7 +317,7 @@ def _get_external_account_credentials( google.auth.exceptions.DefaultCredentialsError: if the info dictionary is in the wrong format or is missing required information. """ - # There are currently 2 types of external_account credentials. + # There are currently 3 types of external_account credentials. if info.get("subject_token_type") == _AWS_SUBJECT_TOKEN_TYPE: # Check if configuration corresponds to an AWS credentials. from google.auth import aws @@ -325,6 +325,15 @@ def _get_external_account_credentials( credentials = aws.Credentials.from_info( info, scopes=scopes, default_scopes=default_scopes ) + elif ( + info.get("credential_source") is not None + and info.get("credential_source").get("executable") is not None + ): + from google.auth import pluggable + + credentials = pluggable.Credentials.from_info( + info, scopes=scopes, default_scopes=default_scopes + ) else: try: # Check if configuration corresponds to an Identity Pool credentials. diff --git a/packages/google-auth/google/auth/pluggable.py b/packages/google-auth/google/auth/pluggable.py new file mode 100644 index 000000000000..ebfb2c585ff6 --- /dev/null +++ b/packages/google-auth/google/auth/pluggable.py @@ -0,0 +1,349 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Pluggable Credentials. +Pluggable Credentials are initialized using external_account arguments which +are typically loaded from third-party executables. Unlike other +credentials that can be initialized with a list of explicit arguments, secrets +or credentials, external account clients use the environment and hints/guidelines +provided by the external_account JSON file to retrieve credentials and exchange +them for Google access tokens. + +Example credential_source for pluggable credential: + + { + "executable": { + "command": "/path/to/get/credentials.sh --arg1=value1 --arg2=value2", + "timeout_millis": 5000, + "output_file": "/path/to/generated/cached/credentials" + } + } +""" + +try: + from collections.abc import Mapping +# Python 2.7 compatibility +except ImportError: # pragma: NO COVER + from collections import Mapping +import io +import json +import os +import subprocess +import time + +from google.auth import _helpers +from google.auth import exceptions +from google.auth import external_account + +# The max supported executable spec version. +EXECUTABLE_SUPPORTED_MAX_VERSION = 1 + + +class Credentials(external_account.Credentials): + """External account credentials sourced from executables.""" + + def __init__( + self, + audience, + subject_token_type, + token_url, + credential_source, + service_account_impersonation_url=None, + client_id=None, + client_secret=None, + quota_project_id=None, + scopes=None, + default_scopes=None, + workforce_pool_user_project=None, + ): + """Instantiates an external account credentials object from a executables. + + Args: + audience (str): The STS audience field. + subject_token_type (str): The subject token type. + token_url (str): The STS endpoint URL. + credential_source (Mapping): The credential source dictionary used to + provide instructions on how to retrieve external credential to be + exchanged for Google access tokens. + + Example credential_source for pluggable credential: + + { + "executable": { + "command": "/path/to/get/credentials.sh --arg1=value1 --arg2=value2", + "timeout_millis": 5000, + "output_file": "/path/to/generated/cached/credentials" + } + } + + service_account_impersonation_url (Optional[str]): The optional service account + impersonation getAccessToken URL. + client_id (Optional[str]): The optional client ID. + client_secret (Optional[str]): The optional client secret. + quota_project_id (Optional[str]): The optional quota project ID. + scopes (Optional[Sequence[str]]): Optional scopes to request during the + authorization grant. + default_scopes (Optional[Sequence[str]]): Default scopes passed by a + Google client library. Use 'scopes' for user-defined scopes. + workforce_pool_user_project (Optona[str]): The optional workforce pool user + project number when the credential corresponds to a workforce pool and not + a workload Pluggable. The underlying principal must still have + serviceusage.services.use IAM permission to use the project for + billing/quota. + + Raises: + google.auth.exceptions.RefreshError: If an error is encountered during + access token retrieval logic. + ValueError: For invalid parameters. + + .. note:: Typically one of the helper constructors + :meth:`from_file` or + :meth:`from_info` are used instead of calling the constructor directly. + """ + + super(Credentials, self).__init__( + audience=audience, + subject_token_type=subject_token_type, + token_url=token_url, + credential_source=credential_source, + service_account_impersonation_url=service_account_impersonation_url, + client_id=client_id, + client_secret=client_secret, + quota_project_id=quota_project_id, + scopes=scopes, + default_scopes=default_scopes, + workforce_pool_user_project=workforce_pool_user_project, + ) + if not isinstance(credential_source, Mapping): + self._credential_source_executable = None + raise ValueError( + "Missing credential_source. The credential_source is not a dict." + ) + self._credential_source_executable = credential_source.get("executable") + if not self._credential_source_executable: + raise ValueError( + "Missing credential_source. An 'executable' must be provided." + ) + self._credential_source_executable_command = self._credential_source_executable.get( + "command" + ) + self._credential_source_executable_timeout_millis = self._credential_source_executable.get( + "timeout_millis" + ) + self._credential_source_executable_output_file = self._credential_source_executable.get( + "output_file" + ) + + if not self._credential_source_executable_command: + raise ValueError( + "Missing command field. Executable command must be provided." + ) + if not self._credential_source_executable_timeout_millis: + self._credential_source_executable_timeout_millis = 30 * 1000 + elif ( + self._credential_source_executable_timeout_millis < 5 * 1000 + or self._credential_source_executable_timeout_millis > 120 * 1000 + ): + raise ValueError("Timeout must be between 5 and 120 seconds.") + + @_helpers.copy_docstring(external_account.Credentials) + def retrieve_subject_token(self, request): + env_allow_executables = os.environ.get( + "GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES" + ) + if env_allow_executables != "1": + raise ValueError( + "Executables need to be explicitly allowed (set GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES to '1') to run." + ) + + # Check output file. + if self._credential_source_executable_output_file is not None: + try: + with open( + self._credential_source_executable_output_file + ) as output_file: + response = json.load(output_file) + except Exception: + pass + else: + try: + # If the cached response is expired, _parse_subject_token will raise an error which will be ignored and we will call the executable again. + subject_token = self._parse_subject_token(response) + except ValueError: + raise + except exceptions.RefreshError: + pass + else: + return subject_token + + # Inject env vars. + original_audience = os.getenv("GOOGLE_EXTERNAL_ACCOUNT_AUDIENCE") + os.environ["GOOGLE_EXTERNAL_ACCOUNT_AUDIENCE"] = self._audience + original_subject_token_type = os.getenv("GOOGLE_EXTERNAL_ACCOUNT_TOKEN_TYPE") + os.environ["GOOGLE_EXTERNAL_ACCOUNT_TOKEN_TYPE"] = self._subject_token_type + original_interactive = os.getenv("GOOGLE_EXTERNAL_ACCOUNT_INTERACTIVE") + os.environ[ + "GOOGLE_EXTERNAL_ACCOUNT_INTERACTIVE" + ] = "0" # Always set to 0 until interactive mode is implemented. + original_service_account_impersonation_url = os.getenv( + "GOOGLE_EXTERNAL_ACCOUNT_IMPERSONATED_EMAIL" + ) + if self._service_account_impersonation_url is not None: + os.environ[ + "GOOGLE_EXTERNAL_ACCOUNT_IMPERSONATED_EMAIL" + ] = self.service_account_email + original_credential_source_executable_output_file = os.getenv( + "GOOGLE_EXTERNAL_ACCOUNT_OUTPUT_FILE" + ) + if self._credential_source_executable_output_file is not None: + os.environ[ + "GOOGLE_EXTERNAL_ACCOUNT_OUTPUT_FILE" + ] = self._credential_source_executable_output_file + + try: + result = subprocess.check_output( + self._credential_source_executable_command.split(), + stderr=subprocess.STDOUT, + ) + except subprocess.CalledProcessError as e: + raise exceptions.RefreshError( + "Executable exited with non-zero return code {}. Error: {}".format( + e.returncode, e.output + ) + ) + else: + try: + data = result.decode("utf-8") + response = json.loads(data) + subject_token = self._parse_subject_token(response) + except Exception: + raise + + # Reset env vars. + if original_audience is not None: + os.environ["GOOGLE_EXTERNAL_ACCOUNT_AUDIENCE"] = original_audience + else: + del os.environ["GOOGLE_EXTERNAL_ACCOUNT_AUDIENCE"] + if original_subject_token_type is not None: + os.environ[ + "GOOGLE_EXTERNAL_ACCOUNT_TOKEN_TYPE" + ] = original_subject_token_type + else: + del os.environ["GOOGLE_EXTERNAL_ACCOUNT_TOKEN_TYPE"] + if original_interactive is not None: + os.environ["GOOGLE_EXTERNAL_ACCOUNT_INTERACTIVE"] = original_interactive + else: + del os.environ["GOOGLE_EXTERNAL_ACCOUNT_INTERACTIVE"] + if original_service_account_impersonation_url is not None: + os.environ[ + "GOOGLE_EXTERNAL_ACCOUNT_IMPERSONATED_EMAIL" + ] = original_service_account_impersonation_url + elif os.getenv("GOOGLE_EXTERNAL_ACCOUNT_IMPERSONATED_EMAIL") is not None: + del os.environ["GOOGLE_EXTERNAL_ACCOUNT_IMPERSONATED_EMAIL"] + if original_credential_source_executable_output_file is not None: + os.environ[ + "GOOGLE_EXTERNAL_ACCOUNT_OUTPUT_FILE" + ] = original_credential_source_executable_output_file + elif os.getenv("GOOGLE_EXTERNAL_ACCOUNT_OUTPUT_FILE") is not None: + del os.environ["GOOGLE_EXTERNAL_ACCOUNT_OUTPUT_FILE"] + + return subject_token + + @classmethod + def from_info(cls, info, **kwargs): + """Creates a Pluggable Credentials instance from parsed external account info. + + Args: + info (Mapping[str, str]): The Pluggable external account info in Google + format. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.pluggable.Credentials: The constructed + credentials. + + Raises: + ValueError: For invalid parameters. + """ + return cls( + audience=info.get("audience"), + subject_token_type=info.get("subject_token_type"), + token_url=info.get("token_url"), + service_account_impersonation_url=info.get( + "service_account_impersonation_url" + ), + client_id=info.get("client_id"), + client_secret=info.get("client_secret"), + credential_source=info.get("credential_source"), + quota_project_id=info.get("quota_project_id"), + workforce_pool_user_project=info.get("workforce_pool_user_project"), + **kwargs + ) + + @classmethod + def from_file(cls, filename, **kwargs): + """Creates an Pluggable Credentials instance from an external account json file. + + Args: + filename (str): The path to the Pluggable external account json file. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.pluggable.Credentials: The constructed + credentials. + """ + with io.open(filename, "r", encoding="utf-8") as json_file: + data = json.load(json_file) + return cls.from_info(data, **kwargs) + + def _parse_subject_token(self, response): + if "version" not in response: + raise ValueError("The executable response is missing the version field.") + if response["version"] > EXECUTABLE_SUPPORTED_MAX_VERSION: + raise exceptions.RefreshError( + "Executable returned unsupported version {}.".format( + response["version"] + ) + ) + if "success" not in response: + raise ValueError("The executable response is missing the success field.") + if not response["success"]: + if "code" not in response or "message" not in response: + raise ValueError( + "Error code and message fields are required in the response." + ) + raise exceptions.RefreshError( + "Executable returned unsuccessful response: code: {}, message: {}.".format( + response["code"], response["message"] + ) + ) + if "expiration_time" not in response: + raise ValueError( + "The executable response is missing the expiration_time field." + ) + if response["expiration_time"] < time.time(): + raise exceptions.RefreshError( + "The token returned by the executable is expired." + ) + if "token_type" not in response: + raise ValueError("The executable response is missing the token_type field.") + if ( + response["token_type"] == "urn:ietf:params:oauth:token-type:jwt" + or response["token_type"] == "urn:ietf:params:oauth:token-type:id_token" + ): # OIDC + return response["id_token"] + elif response["token_type"] == "urn:ietf:params:oauth:token-type:saml2": # SAML + return response["saml_response"] + else: + raise exceptions.RefreshError("Executable returned unsupported token type.") diff --git a/packages/google-auth/tests/test__default.py b/packages/google-auth/tests/test__default.py index ed64bc723552..4e7eeb84e178 100644 --- a/packages/google-auth/tests/test__default.py +++ b/packages/google-auth/tests/test__default.py @@ -28,6 +28,7 @@ from google.auth import external_account from google.auth import identity_pool from google.auth import impersonated_credentials +from google.auth import pluggable from google.oauth2 import service_account import google.oauth2.credentials @@ -72,6 +73,13 @@ "token_url": TOKEN_URL, "credential_source": {"file": SUBJECT_TOKEN_TEXT_FILE}, } +PLUGGABLE_DATA = { + "type": "external_account", + "audience": AUDIENCE, + "subject_token_type": "urn:ietf:params:oauth:token-type:jwt", + "token_url": TOKEN_URL, + "credential_source": {"executable": {"command": "command"}}, +} AWS_DATA = { "type": "external_account", "audience": AUDIENCE, @@ -1140,3 +1148,15 @@ def test_default_impersonated_service_account_set_both_scopes_and_default_scopes credentials, _ = _default.default(scopes=scopes, default_scopes=default_scopes) assert credentials._target_scopes == scopes + + +@EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH +def test_load_credentials_from_external_account_pluggable(get_project_id, tmpdir): + config_file = tmpdir.join("config.json") + config_file.write(json.dumps(PLUGGABLE_DATA)) + credentials, project_id = _default.load_credentials_from_file(str(config_file)) + + assert isinstance(credentials, pluggable.Credentials) + # Since no scopes are specified, the project ID cannot be determined. + assert project_id is None + assert get_project_id.called diff --git a/packages/google-auth/tests/test_pluggable.py b/packages/google-auth/tests/test_pluggable.py new file mode 100644 index 000000000000..2580fd1d435a --- /dev/null +++ b/packages/google-auth/tests/test_pluggable.py @@ -0,0 +1,692 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# import datetime +import json +import os +import subprocess + +import mock +import pytest # type: ignore + +# from six.moves import http_client +# from six.moves import urllib + +# from google.auth import _helpers +from google.auth import exceptions +from google.auth import pluggable + +# from google.auth import transport + + +CLIENT_ID = "username" +CLIENT_SECRET = "password" +# Base64 encoding of "username:password". +BASIC_AUTH_ENCODING = "dXNlcm5hbWU6cGFzc3dvcmQ=" +SERVICE_ACCOUNT_EMAIL = "service-1234@service-name.iam.gserviceaccount.com" +SERVICE_ACCOUNT_IMPERSONATION_URL = ( + "https://us-east1-iamcredentials.googleapis.com/v1/projects/-" + + "/serviceAccounts/{}:generateAccessToken".format(SERVICE_ACCOUNT_EMAIL) +) +QUOTA_PROJECT_ID = "QUOTA_PROJECT_ID" +SCOPES = ["scope1", "scope2"] +SUBJECT_TOKEN_FIELD_NAME = "access_token" + +TOKEN_URL = "https://sts.googleapis.com/v1/token" +SERVICE_ACCOUNT_IMPERSONATION_URL = "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/byoid-test@cicpclientproj.iam.gserviceaccount.com:generateAccessToken" +SUBJECT_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:jwt" +AUDIENCE = "//iam.googleapis.com/projects/123456/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID" + + +class TestCredentials(object): + CREDENTIAL_SOURCE_EXECUTABLE_COMMAND = ( + "/fake/external/excutable --arg1=value1 --arg2=value2" + ) + CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE = "fake_output_file" + CREDENTIAL_SOURCE_EXECUTABLE = { + "command": CREDENTIAL_SOURCE_EXECUTABLE_COMMAND, + "timeout_millis": 30000, + "output_file": CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, + } + CREDENTIAL_SOURCE = {"executable": CREDENTIAL_SOURCE_EXECUTABLE} + EXECUTABLE_OIDC_TOKEN = "FAKE_ID_TOKEN" + EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_ID_TOKEN = { + "version": 1, + "success": True, + "token_type": "urn:ietf:params:oauth:token-type:id_token", + "id_token": EXECUTABLE_OIDC_TOKEN, + "expiration_time": 9999999999, + } + EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_JWT = { + "version": 1, + "success": True, + "token_type": "urn:ietf:params:oauth:token-type:jwt", + "id_token": EXECUTABLE_OIDC_TOKEN, + "expiration_time": 9999999999, + } + EXECUTABLE_SAML_TOKEN = "FAKE_SAML_RESPONSE" + EXECUTABLE_SUCCESSFUL_SAML_RESPONSE = { + "version": 1, + "success": True, + "token_type": "urn:ietf:params:oauth:token-type:saml2", + "saml_response": EXECUTABLE_SAML_TOKEN, + "expiration_time": 9999999999, + } + EXECUTABLE_FAILED_RESPONSE = { + "version": 1, + "success": False, + "code": "401", + "message": "Permission denied. Caller not authorized", + } + CREDENTIAL_URL = "http://fakeurl.com" + + @classmethod + def make_pluggable( + cls, + audience=AUDIENCE, + subject_token_type=SUBJECT_TOKEN_TYPE, + client_id=None, + client_secret=None, + quota_project_id=None, + scopes=None, + default_scopes=None, + service_account_impersonation_url=None, + credential_source=None, + workforce_pool_user_project=None, + ): + return pluggable.Credentials( + audience=audience, + subject_token_type=subject_token_type, + token_url=TOKEN_URL, + service_account_impersonation_url=service_account_impersonation_url, + credential_source=credential_source, + client_id=client_id, + client_secret=client_secret, + quota_project_id=quota_project_id, + scopes=scopes, + default_scopes=default_scopes, + workforce_pool_user_project=workforce_pool_user_project, + ) + + @mock.patch.object(pluggable.Credentials, "__init__", return_value=None) + def test_from_info_full_options(self, mock_init): + credentials = pluggable.Credentials.from_info( + { + "audience": AUDIENCE, + "subject_token_type": SUBJECT_TOKEN_TYPE, + "token_url": TOKEN_URL, + "service_account_impersonation_url": SERVICE_ACCOUNT_IMPERSONATION_URL, + "client_id": CLIENT_ID, + "client_secret": CLIENT_SECRET, + "quota_project_id": QUOTA_PROJECT_ID, + "credential_source": self.CREDENTIAL_SOURCE, + } + ) + + # Confirm pluggable.Credentials instantiated with expected attributes. + assert isinstance(credentials, pluggable.Credentials) + mock_init.assert_called_once_with( + audience=AUDIENCE, + subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + client_id=CLIENT_ID, + client_secret=CLIENT_SECRET, + credential_source=self.CREDENTIAL_SOURCE, + quota_project_id=QUOTA_PROJECT_ID, + workforce_pool_user_project=None, + ) + + @mock.patch.object(pluggable.Credentials, "__init__", return_value=None) + def test_from_info_required_options_only(self, mock_init): + credentials = pluggable.Credentials.from_info( + { + "audience": AUDIENCE, + "subject_token_type": SUBJECT_TOKEN_TYPE, + "token_url": TOKEN_URL, + "credential_source": self.CREDENTIAL_SOURCE, + } + ) + + # Confirm pluggable.Credentials instantiated with expected attributes. + assert isinstance(credentials, pluggable.Credentials) + mock_init.assert_called_once_with( + audience=AUDIENCE, + subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + service_account_impersonation_url=None, + client_id=None, + client_secret=None, + credential_source=self.CREDENTIAL_SOURCE, + quota_project_id=None, + workforce_pool_user_project=None, + ) + + @mock.patch.object(pluggable.Credentials, "__init__", return_value=None) + def test_from_file_full_options(self, mock_init, tmpdir): + info = { + "audience": AUDIENCE, + "subject_token_type": SUBJECT_TOKEN_TYPE, + "token_url": TOKEN_URL, + "service_account_impersonation_url": SERVICE_ACCOUNT_IMPERSONATION_URL, + "client_id": CLIENT_ID, + "client_secret": CLIENT_SECRET, + "quota_project_id": QUOTA_PROJECT_ID, + "credential_source": self.CREDENTIAL_SOURCE, + } + config_file = tmpdir.join("config.json") + config_file.write(json.dumps(info)) + credentials = pluggable.Credentials.from_file(str(config_file)) + + # Confirm pluggable.Credentials instantiated with expected attributes. + assert isinstance(credentials, pluggable.Credentials) + mock_init.assert_called_once_with( + audience=AUDIENCE, + subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + client_id=CLIENT_ID, + client_secret=CLIENT_SECRET, + credential_source=self.CREDENTIAL_SOURCE, + quota_project_id=QUOTA_PROJECT_ID, + workforce_pool_user_project=None, + ) + + @mock.patch.object(pluggable.Credentials, "__init__", return_value=None) + def test_from_file_required_options_only(self, mock_init, tmpdir): + info = { + "audience": AUDIENCE, + "subject_token_type": SUBJECT_TOKEN_TYPE, + "token_url": TOKEN_URL, + "credential_source": self.CREDENTIAL_SOURCE, + } + config_file = tmpdir.join("config.json") + config_file.write(json.dumps(info)) + credentials = pluggable.Credentials.from_file(str(config_file)) + + # Confirm pluggable.Credentials instantiated with expected attributes. + assert isinstance(credentials, pluggable.Credentials) + mock_init.assert_called_once_with( + audience=AUDIENCE, + subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + service_account_impersonation_url=None, + client_id=None, + client_secret=None, + credential_source=self.CREDENTIAL_SOURCE, + quota_project_id=None, + workforce_pool_user_project=None, + ) + + def test_constructor_invalid_options(self): + credential_source = {"unsupported": "value"} + + with pytest.raises(ValueError) as excinfo: + self.make_pluggable(credential_source=credential_source) + + assert excinfo.match(r"Missing credential_source") + + def test_constructor_invalid_credential_source(self): + with pytest.raises(ValueError) as excinfo: + self.make_pluggable(credential_source="non-dict") + + assert excinfo.match(r"Missing credential_source") + + def test_info_with_credential_source(self): + credentials = self.make_pluggable( + credential_source=self.CREDENTIAL_SOURCE.copy() + ) + + assert credentials.info == { + "type": "external_account", + "audience": AUDIENCE, + "subject_token_type": SUBJECT_TOKEN_TYPE, + "token_url": TOKEN_URL, + "credential_source": self.CREDENTIAL_SOURCE, + } + + @mock.patch.dict( + os.environ, + { + "GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1", + "GOOGLE_EXTERNAL_ACCOUNT_AUDIENCE": "original_audience", + "GOOGLE_EXTERNAL_ACCOUNT_TOKEN_TYPE": "original_token_type", + "GOOGLE_EXTERNAL_ACCOUNT_INTERACTIVE": "0", + "GOOGLE_EXTERNAL_ACCOUNT_IMPERSONATED_EMAIL": "original_impersonated_email", + "GOOGLE_EXTERNAL_ACCOUNT_OUTPUT_FILE": "original_output_file", + }, + ) + def test_retrieve_subject_token_oidc_id_token(self): + with mock.patch( + "subprocess.check_output", + return_value=json.dumps( + self.EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_ID_TOKEN + ).encode("UTF-8"), + ): + credentials = self.make_pluggable( + audience=AUDIENCE, + service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + credential_source=self.CREDENTIAL_SOURCE, + ) + + subject_token = credentials.retrieve_subject_token(None) + + assert subject_token == self.EXECUTABLE_OIDC_TOKEN + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_retrieve_subject_token_oidc_jwt(self): + with mock.patch( + "subprocess.check_output", + return_value=json.dumps( + self.EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_JWT + ).encode("UTF-8"), + ): + credentials = self.make_pluggable( + audience=AUDIENCE, + service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + credential_source=self.CREDENTIAL_SOURCE, + ) + + subject_token = credentials.retrieve_subject_token(None) + + assert subject_token == self.EXECUTABLE_OIDC_TOKEN + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_retrieve_subject_token_saml(self): + with mock.patch( + "subprocess.check_output", + return_value=json.dumps(self.EXECUTABLE_SUCCESSFUL_SAML_RESPONSE).encode( + "UTF-8" + ), + ): + credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE) + + subject_token = credentials.retrieve_subject_token(None) + + assert subject_token == self.EXECUTABLE_SAML_TOKEN + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_retrieve_subject_token_failed(self): + with mock.patch( + "subprocess.check_output", + return_value=json.dumps(self.EXECUTABLE_FAILED_RESPONSE).encode("UTF-8"), + ): + credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE) + + with pytest.raises(exceptions.RefreshError) as excinfo: + _ = credentials.retrieve_subject_token(None) + + assert excinfo.match( + r"Executable returned unsuccessful response: code: 401, message: Permission denied. Caller not authorized." + ) + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "0"}) + def test_retrieve_subject_token_not_allowd(self): + with mock.patch( + "subprocess.check_output", + return_value=json.dumps( + self.EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_ID_TOKEN + ).encode("UTF-8"), + ): + credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE) + + with pytest.raises(ValueError) as excinfo: + _ = credentials.retrieve_subject_token(None) + + assert excinfo.match(r"Executables need to be explicitly allowed") + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_retrieve_subject_token_invalid_version(self): + EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_VERSION_2 = { + "version": 2, + "success": True, + "token_type": "urn:ietf:params:oauth:token-type:id_token", + "id_token": self.EXECUTABLE_OIDC_TOKEN, + "expiration_time": 9999999999, + } + + with mock.patch( + "subprocess.check_output", + return_value=json.dumps( + EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_VERSION_2 + ).encode("UTF-8"), + ): + credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE) + + with pytest.raises(exceptions.RefreshError) as excinfo: + _ = credentials.retrieve_subject_token(None) + + assert excinfo.match(r"Executable returned unsupported version.") + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_retrieve_subject_token_expired_token(self): + EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_EXPIRED = { + "version": 1, + "success": True, + "token_type": "urn:ietf:params:oauth:token-type:id_token", + "id_token": self.EXECUTABLE_OIDC_TOKEN, + "expiration_time": 0, + } + + with mock.patch( + "subprocess.check_output", + return_value=json.dumps(EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_EXPIRED).encode( + "UTF-8" + ), + ): + credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE) + + with pytest.raises(exceptions.RefreshError) as excinfo: + _ = credentials.retrieve_subject_token(None) + + assert excinfo.match(r"The token returned by the executable is expired.") + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_retrieve_subject_token_file_cache(self): + ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE = "actual_output_file" + ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE = { + "command": "command", + "timeout_millis": 30000, + "output_file": ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, + } + ACTUAL_CREDENTIAL_SOURCE = {"executable": ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE} + with open(ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, "w") as output_file: + json.dump(self.EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_ID_TOKEN, output_file) + + credentials = self.make_pluggable(credential_source=ACTUAL_CREDENTIAL_SOURCE) + + subject_token = credentials.retrieve_subject_token(None) + assert subject_token == self.EXECUTABLE_OIDC_TOKEN + + os.remove(ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE) + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_retrieve_subject_token_no_file_cache(self): + ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE = { + "command": "command", + "timeout_millis": 30000, + } + ACTUAL_CREDENTIAL_SOURCE = {"executable": ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE} + + with mock.patch( + "subprocess.check_output", + return_value=json.dumps( + self.EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_ID_TOKEN + ).encode("UTF-8"), + ): + credentials = self.make_pluggable( + credential_source=ACTUAL_CREDENTIAL_SOURCE + ) + + subject_token = credentials.retrieve_subject_token(None) + + assert subject_token == self.EXECUTABLE_OIDC_TOKEN + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_retrieve_subject_token_file_cache_value_error_report(self): + ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE = "actual_output_file" + ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE = { + "command": "command", + "timeout_millis": 30000, + "output_file": ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, + } + ACTUAL_CREDENTIAL_SOURCE = {"executable": ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE} + ACTUAL_EXECUTABLE_RESPONSE = { + "success": True, + "token_type": "urn:ietf:params:oauth:token-type:id_token", + "id_token": self.EXECUTABLE_OIDC_TOKEN, + "expiration_time": 9999999999, + } + with open(ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, "w") as output_file: + json.dump(ACTUAL_EXECUTABLE_RESPONSE, output_file) + + credentials = self.make_pluggable(credential_source=ACTUAL_CREDENTIAL_SOURCE) + + with pytest.raises(ValueError) as excinfo: + _ = credentials.retrieve_subject_token(None) + + assert excinfo.match(r"The executable response is missing the version field.") + + os.remove(ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE) + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_retrieve_subject_token_file_cache_refresh_error_retry(self): + ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE = "actual_output_file" + ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE = { + "command": "command", + "timeout_millis": 30000, + "output_file": ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, + } + ACTUAL_CREDENTIAL_SOURCE = {"executable": ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE} + ACTUAL_EXECUTABLE_RESPONSE = { + "version": 2, + "success": True, + "token_type": "urn:ietf:params:oauth:token-type:id_token", + "id_token": self.EXECUTABLE_OIDC_TOKEN, + "expiration_time": 9999999999, + } + with open(ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, "w") as output_file: + json.dump(ACTUAL_EXECUTABLE_RESPONSE, output_file) + + with mock.patch( + "subprocess.check_output", + return_value=json.dumps( + self.EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_ID_TOKEN + ).encode("UTF-8"), + ): + credentials = self.make_pluggable( + credential_source=ACTUAL_CREDENTIAL_SOURCE + ) + + subject_token = credentials.retrieve_subject_token(None) + + assert subject_token == self.EXECUTABLE_OIDC_TOKEN + + os.remove(ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE) + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_retrieve_subject_token_unsupported_token_type(self): + EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE = { + "version": 1, + "success": True, + "token_type": "unsupported_token_type", + "id_token": self.EXECUTABLE_OIDC_TOKEN, + "expiration_time": 9999999999, + } + + with mock.patch( + "subprocess.check_output", + return_value=json.dumps(EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE).encode( + "UTF-8" + ), + ): + credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE) + + with pytest.raises(exceptions.RefreshError) as excinfo: + _ = credentials.retrieve_subject_token(None) + + assert excinfo.match(r"Executable returned unsupported token type.") + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_retrieve_subject_token_missing_version(self): + EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE = { + "success": True, + "token_type": "urn:ietf:params:oauth:token-type:id_token", + "id_token": self.EXECUTABLE_OIDC_TOKEN, + "expiration_time": 9999999999, + } + + with mock.patch( + "subprocess.check_output", + return_value=json.dumps(EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE).encode( + "UTF-8" + ), + ): + credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE) + + with pytest.raises(ValueError) as excinfo: + _ = credentials.retrieve_subject_token(None) + + assert excinfo.match( + r"The executable response is missing the version field." + ) + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_retrieve_subject_token_missing_success(self): + EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE = { + "version": 1, + "token_type": "urn:ietf:params:oauth:token-type:id_token", + "id_token": self.EXECUTABLE_OIDC_TOKEN, + "expiration_time": 9999999999, + } + + with mock.patch( + "subprocess.check_output", + return_value=json.dumps(EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE).encode( + "UTF-8" + ), + ): + credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE) + + with pytest.raises(ValueError) as excinfo: + _ = credentials.retrieve_subject_token(None) + + assert excinfo.match( + r"The executable response is missing the success field." + ) + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_retrieve_subject_token_missing_error_code_message(self): + EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE = {"version": 1, "success": False} + + with mock.patch( + "subprocess.check_output", + return_value=json.dumps(EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE).encode( + "UTF-8" + ), + ): + credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE) + + with pytest.raises(ValueError) as excinfo: + _ = credentials.retrieve_subject_token(None) + + assert excinfo.match( + r"Error code and message fields are required in the response." + ) + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_retrieve_subject_token_missing_expiration_time(self): + EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE = { + "version": 1, + "success": True, + "token_type": "urn:ietf:params:oauth:token-type:id_token", + "id_token": self.EXECUTABLE_OIDC_TOKEN, + } + + with mock.patch( + "subprocess.check_output", + return_value=json.dumps(EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE).encode( + "UTF-8" + ), + ): + credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE) + + with pytest.raises(ValueError) as excinfo: + _ = credentials.retrieve_subject_token(None) + + assert excinfo.match( + r"The executable response is missing the expiration_time field." + ) + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_retrieve_subject_token_missing_token_type(self): + EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE = { + "version": 1, + "success": True, + "id_token": self.EXECUTABLE_OIDC_TOKEN, + "expiration_time": 9999999999, + } + + with mock.patch( + "subprocess.check_output", + return_value=json.dumps(EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE).encode( + "UTF-8" + ), + ): + credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE) + + with pytest.raises(ValueError) as excinfo: + _ = credentials.retrieve_subject_token(None) + + assert excinfo.match( + r"The executable response is missing the token_type field." + ) + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_credential_source_missing_command(self): + with pytest.raises(ValueError) as excinfo: + CREDENTIAL_SOURCE = { + "executable": { + "timeout_millis": 30000, + "output_file": self.CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, + } + } + _ = self.make_pluggable(credential_source=CREDENTIAL_SOURCE) + + assert excinfo.match( + r"Missing command field. Executable command must be provided." + ) + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_credential_source_timeout_small(self): + with pytest.raises(ValueError) as excinfo: + CREDENTIAL_SOURCE = { + "executable": { + "command": self.CREDENTIAL_SOURCE_EXECUTABLE_COMMAND, + "timeout_millis": 5000 - 1, + "output_file": self.CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, + } + } + _ = self.make_pluggable(credential_source=CREDENTIAL_SOURCE) + + assert excinfo.match(r"Timeout must be between 5 and 120 seconds.") + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_credential_source_timeout_large(self): + with pytest.raises(ValueError) as excinfo: + CREDENTIAL_SOURCE = { + "executable": { + "command": self.CREDENTIAL_SOURCE_EXECUTABLE_COMMAND, + "timeout_millis": 120000 + 1, + "output_file": self.CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, + } + } + _ = self.make_pluggable(credential_source=CREDENTIAL_SOURCE) + + assert excinfo.match(r"Timeout must be between 5 and 120 seconds.") + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_retrieve_subject_token_executable_fail(self): + with mock.patch("subprocess.check_output") as subprocess_mock: + subprocess_mock.side_effect = subprocess.CalledProcessError( + returncode=1, cmd="" + ) + credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE) + + with pytest.raises(exceptions.RefreshError) as excinfo: + _ = credentials.retrieve_subject_token(None) + + assert excinfo.match( + r"Executable exited with non-zero return code 1. Error: None" + ) From 913b16604d0c5247df3d703b62f0fb1d12d53e5a Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Tue, 10 May 2022 13:10:01 -0700 Subject: [PATCH 566/966] feat: add experimental GDCH support (#1022) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add experimental GDCH support * address comments * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * remove quota project id Co-authored-by: Owl Bot --- packages/google-auth/google/auth/_default.py | 46 ++++- packages/google-auth/google/oauth2/_client.py | 84 ++++++-- .../google/oauth2/gdch_credentials.py | 194 ++++++++++++++++++ .../tests/data/gdch_service_account.json | 10 + .../google-auth/tests/oauth2/test__client.py | 40 +++- .../tests/oauth2/test_gdch_credentials.py | 180 ++++++++++++++++ packages/google-auth/tests/test__default.py | 44 ++++ 7 files changed, 578 insertions(+), 20 deletions(-) create mode 100644 packages/google-auth/google/oauth2/gdch_credentials.py create mode 100644 packages/google-auth/tests/data/gdch_service_account.json create mode 100644 packages/google-auth/tests/oauth2/test_gdch_credentials.py diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index dc54c44b6fc8..15799cea78c4 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -36,11 +36,13 @@ _SERVICE_ACCOUNT_TYPE = "service_account" _EXTERNAL_ACCOUNT_TYPE = "external_account" _IMPERSONATED_SERVICE_ACCOUNT_TYPE = "impersonated_service_account" +_GDCH_SERVICE_ACCOUNT_TYPE = "gdch_service_account" _VALID_TYPES = ( _AUTHORIZED_USER_TYPE, _SERVICE_ACCOUNT_TYPE, _EXTERNAL_ACCOUNT_TYPE, _IMPERSONATED_SERVICE_ACCOUNT_TYPE, + _GDCH_SERVICE_ACCOUNT_TYPE, ) # Help message when no credentials can be found. @@ -134,6 +136,8 @@ def load_credentials_from_file( def _load_credentials_from_info( filename, info, scopes, default_scopes, quota_project_id, request ): + from google.auth.credentials import CredentialsWithQuotaProject + credential_type = info.get("type") if credential_type == _AUTHORIZED_USER_TYPE: @@ -158,6 +162,8 @@ def _load_credentials_from_info( credentials, project_id = _get_impersonated_service_account_credentials( filename, info, scopes ) + elif credential_type == _GDCH_SERVICE_ACCOUNT_TYPE: + credentials, project_id = _get_gdch_service_account_credentials(info) else: raise exceptions.DefaultCredentialsError( "The file {file} does not have a valid type. " @@ -165,7 +171,8 @@ def _load_credentials_from_info( file=filename, type=credential_type, valid_types=_VALID_TYPES ) ) - credentials = _apply_quota_project_id(credentials, quota_project_id) + if isinstance(credentials, CredentialsWithQuotaProject): + credentials = _apply_quota_project_id(credentials, quota_project_id) return credentials, project_id @@ -430,6 +437,36 @@ def _get_impersonated_service_account_credentials(filename, info, scopes): return credentials, None +def _get_gdch_service_account_credentials(info): + from google.oauth2 import gdch_credentials + + k8s_ca_cert_path = info.get("k8s_ca_cert_path") + k8s_cert_path = info.get("k8s_cert_path") + k8s_key_path = info.get("k8s_key_path") + k8s_token_endpoint = info.get("k8s_token_endpoint") + ais_ca_cert_path = info.get("ais_ca_cert_path") + ais_token_endpoint = info.get("ais_token_endpoint") + + format_version = info.get("format_version") + if format_version != "v1": + raise exceptions.DefaultCredentialsError( + "format_version is not provided or unsupported. Supported version is: v1" + ) + + return ( + gdch_credentials.ServiceAccountCredentials( + k8s_ca_cert_path, + k8s_cert_path, + k8s_key_path, + k8s_token_endpoint, + ais_ca_cert_path, + ais_token_endpoint, + None, + ), + None, + ) + + def _apply_quota_project_id(credentials, quota_project_id): if quota_project_id: credentials = credentials.with_quota_project(quota_project_id) @@ -465,6 +502,11 @@ def default(scopes=None, request=None, quota_project_id=None, default_scopes=Non endpoint. The project ID returned in this case is the one corresponding to the underlying workload identity pool resource if determinable. + + If the environment variable is set to the path of a valid GDCH service + account JSON file (`Google Distributed Cloud Hosted`_), then a GDCH + credential will be returned. The project ID returned is None unless it + is set via `GOOGLE_CLOUD_PROJECT` environment variable. 2. If the `Google Cloud SDK`_ is installed and has application default credentials set they are loaded and returned. @@ -499,6 +541,8 @@ def default(scopes=None, request=None, quota_project_id=None, default_scopes=Non .. _Metadata Service: https://cloud.google.com/compute/docs\ /storing-retrieving-metadata .. _Cloud Run: https://cloud.google.com/run + .. _Google Distributed Cloud Hosted: https://cloud.google.com/blog/topics\ + /hybrid-cloud/announcing-google-distributed-cloud-edge-and-hosted Example:: diff --git a/packages/google-auth/google/oauth2/_client.py b/packages/google-auth/google/oauth2/_client.py index 2f4e8474b557..8831baf27a47 100644 --- a/packages/google-auth/google/oauth2/_client.py +++ b/packages/google-auth/google/oauth2/_client.py @@ -44,11 +44,13 @@ def _handle_error_response(response_data): """Translates an error response into an exception. Args: - response_data (Mapping): The decoded response data. + response_data (Mapping | str): The decoded response data. Raises: google.auth.exceptions.RefreshError: The errors contained in response_data. """ + if isinstance(response_data, six.string_types): + raise exceptions.RefreshError(response_data) try: error_details = "{}: {}".format( response_data["error"], response_data.get("error_description") @@ -79,7 +81,13 @@ def _parse_expiry(response_data): def _token_endpoint_request_no_throw( - request, token_uri, body, access_token=None, use_json=False + request, + token_uri, + body, + access_token=None, + use_json=False, + expected_status_code=http_client.OK, + **kwargs ): """Makes a request to the OAuth 2.0 authorization server's token endpoint. This function doesn't throw on response errors. @@ -93,6 +101,16 @@ def _token_endpoint_request_no_throw( access_token (Optional(str)): The access token needed to make the request. use_json (Optional(bool)): Use urlencoded format or json format for the content type. The default value is False. + expected_status_code (Optional(int)): The expected the status code of + the token response. The default value is 200. We may expect other + status code like 201 for GDCH credentials. + kwargs: Additional arguments passed on to the request method. The + kwargs will be passed to `requests.request` method, see: + https://docs.python-requests.org/en/latest/api/#requests.request. + For example, you can use `cert=("cert_pem_path", "key_pem_path")` + to set up client side SSL certificate, and use + `verify="ca_bundle_path"` to set up the CA certificates for sever + side SSL certificate verification. Returns: Tuple(bool, Mapping[str, str]): A boolean indicating if the request is @@ -112,32 +130,46 @@ def _token_endpoint_request_no_throw( # retry to fetch token for maximum of two times if any internal failure # occurs. while True: - response = request(method="POST", url=token_uri, headers=headers, body=body) + response = request( + method="POST", url=token_uri, headers=headers, body=body, **kwargs + ) response_body = ( response.data.decode("utf-8") if hasattr(response.data, "decode") else response.data ) - response_data = json.loads(response_body) - if response.status == http_client.OK: + if response.status == expected_status_code: + # response_body should be a JSON + response_data = json.loads(response_body) break else: - error_desc = response_data.get("error_description") or "" - error_code = response_data.get("error") or "" - if ( - any(e == "internal_failure" for e in (error_code, error_desc)) - and retry < 1 - ): - retry += 1 - continue - return response.status == http_client.OK, response_data - - return response.status == http_client.OK, response_data + # For a failed response, response_body could be a string + try: + response_data = json.loads(response_body) + error_desc = response_data.get("error_description") or "" + error_code = response_data.get("error") or "" + if ( + any(e == "internal_failure" for e in (error_code, error_desc)) + and retry < 1 + ): + retry += 1 + continue + except ValueError: + response_data = response_body + return False, response_data + + return response.status == expected_status_code, response_data def _token_endpoint_request( - request, token_uri, body, access_token=None, use_json=False + request, + token_uri, + body, + access_token=None, + use_json=False, + expected_status_code=http_client.OK, + **kwargs ): """Makes a request to the OAuth 2.0 authorization server's token endpoint. @@ -150,6 +182,16 @@ def _token_endpoint_request( access_token (Optional(str)): The access token needed to make the request. use_json (Optional(bool)): Use urlencoded format or json format for the content type. The default value is False. + expected_status_code (Optional(int)): The expected the status code of + the token response. The default value is 200. We may expect other + status code like 201 for GDCH credentials. + kwargs: Additional arguments passed on to the request method. The + kwargs will be passed to `requests.request` method, see: + https://docs.python-requests.org/en/latest/api/#requests.request. + For example, you can use `cert=("cert_pem_path", "key_pem_path")` + to set up client side SSL certificate, and use + `verify="ca_bundle_path"` to set up the CA certificates for sever + side SSL certificate verification. Returns: Mapping[str, str]: The JSON-decoded response data. @@ -159,7 +201,13 @@ def _token_endpoint_request( an error. """ response_status_ok, response_data = _token_endpoint_request_no_throw( - request, token_uri, body, access_token=access_token, use_json=use_json + request, + token_uri, + body, + access_token=access_token, + use_json=use_json, + expected_status_code=expected_status_code, + **kwargs ) if not response_status_ok: _handle_error_response(response_data) diff --git a/packages/google-auth/google/oauth2/gdch_credentials.py b/packages/google-auth/google/oauth2/gdch_credentials.py new file mode 100644 index 000000000000..e0edbf039856 --- /dev/null +++ b/packages/google-auth/google/oauth2/gdch_credentials.py @@ -0,0 +1,194 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Experimental GDCH credentials support. +""" + +import six +from six.moves import http_client + +from google.auth import _helpers +from google.auth import credentials +from google.auth import exceptions +from google.oauth2 import _client + + +TOKEN_EXCHANGE_TYPE = "urn:ietf:params:oauth:token-type:token-exchange" +ACCESS_TOKEN_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token" +JWT_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:jwt" +SERVICE_ACCOUNT_TOKEN_TYPE = "urn:k8s:params:oauth:token-type:serviceaccount" + + +class ServiceAccountCredentials(credentials.Credentials): + """Credentials for GDCH (`Google Distributed Cloud Hosted`_) for service + account users. + + .. _Google Distributed Cloud Hosted: + https://cloud.google.com/blog/topics/hybrid-cloud/\ + announcing-google-distributed-cloud-edge-and-hosted + + Besides the constructor, a GDCH credential can be created via application + default credentials. + + To do so, user first creates a JSON file of the + following format:: + + { + "type":"gdch_service_account", + "format_version":"v1", + "k8s_ca_cert_path":"", + "k8s_cert_path":"", + "k8s_key_path":"", + "k8s_token_endpoint":"", + "ais_ca_cert_path":"", + "ais_token_endpoint":"" + } + + Here "k8s_*" files are used to request a k8s token from k8s token endpoint + using mutual TLS connection. The k8s token is then sent to AIS token endpoint + to exchange for an AIS token. The AIS token will be used to talk to Google + API services. + + "k8s_ca_cert_path" field is not needed if the k8s server uses well known CA. + "ais_ca_cert_path" field is not needed if the AIS server uses well known CA. + These two fields can be used for testing environments. + + The "format_version" field stands for the format of the JSON file. For now + it is always "v1". + + After the JSON file is created, set `GOOGLE_APPLICATION_CREDENTIALS` environment + variable to the JSON file path, then use the following code to create the + credential:: + + import google.auth + + credential, _ = google.auth.default() + credential = credential.with_audience("") + + The audience denotes the scope the AIS token is requested, for example, it + could be either a k8s cluster or API service. + """ + + def __init__( + self, + k8s_ca_cert_path, + k8s_cert_path, + k8s_key_path, + k8s_token_endpoint, + ais_ca_cert_path, + ais_token_endpoint, + audience, + ): + """ + Args: + k8s_ca_cert_path (str): CA cert path for k8s calls. This field is + useful if the specific k8s server doesn't use well known CA, + for instance, a testing k8s server. If the CA is well known, + you can pass `None` for this parameter. + k8s_cert_path (str): Certificate path for k8s calls + k8s_key_path (str): Key path for k8s calls + k8s_token_endpoint (str): k8s token endpoint url + ais_ca_cert_path (str): CA cert path for AIS token endpoint calls. + This field is useful if the specific AIS token server doesn't + uses well known CA, for instance, a testing AIS server. If the + CA is well known, you can pass `None` for this parameter. + ais_token_endpoint (str): AIS token endpoint url + audience (str): The audience for the requested AIS token. For + example, it could be a k8s cluster or API service. + """ + super(ServiceAccountCredentials, self).__init__() + self._k8s_ca_cert_path = k8s_ca_cert_path + self._k8s_cert_path = k8s_cert_path + self._k8s_key_path = k8s_key_path + self._k8s_token_endpoint = k8s_token_endpoint + self._ais_ca_cert_path = ais_ca_cert_path + self._ais_token_endpoint = ais_token_endpoint + self._audience = audience + + def _make_k8s_token_request(self, request): + k8s_request_body = { + "kind": "TokenRequest", + "apiVersion": "authentication.k8s.io/v1", + "spec": {"audiences": [self._ais_token_endpoint]}, + } + # mTLS connection to k8s token endpoint to get a k8s token. + k8s_response_data = _client._token_endpoint_request( + request, + self._k8s_token_endpoint, + k8s_request_body, + access_token=None, + use_json=True, + expected_status_code=http_client.CREATED, + cert=(self._k8s_cert_path, self._k8s_key_path), + verify=self._k8s_ca_cert_path, + ) + + try: + k8s_token = k8s_response_data["status"]["token"] + return k8s_token + except KeyError as caught_exc: + new_exc = exceptions.RefreshError( + "No access token in k8s token response.", k8s_response_data + ) + six.raise_from(new_exc, caught_exc) + + def _make_ais_token_request(self, k8s_token, request): + # send a request to AIS token point with the k8s token + ais_request_body = { + "grant_type": TOKEN_EXCHANGE_TYPE, + "audience": self._audience, + "requested_token_type": ACCESS_TOKEN_TOKEN_TYPE, + "subject_token": k8s_token, + "subject_token_type": SERVICE_ACCOUNT_TOKEN_TYPE, + } + ais_response_data = _client._token_endpoint_request( + request, + self._ais_token_endpoint, + ais_request_body, + access_token=None, + use_json=True, + verify=self._ais_ca_cert_path, + ) + ais_token, _, ais_expiry, _ = _client._handle_refresh_grant_response( + ais_response_data, None + ) + return ais_token, ais_expiry + + @_helpers.copy_docstring(credentials.Credentials) + def refresh(self, request): + import google.auth.transport.requests + + if not isinstance(request, google.auth.transport.requests.Request): + raise exceptions.RefreshError( + "For GDCH service account credentials, request must be a google.auth.transport.requests.Request object" + ) + + k8s_token = self._make_k8s_token_request(request) + self.token, self.expiry = self._make_ais_token_request(k8s_token, request) + + def with_audience(self, audience): + """Create a copy of GDCH credentials with the specified audience. + + Args: + audience (str): The intended audience for GDCH credentials. + """ + return self.__class__( + self._k8s_ca_cert_path, + self._k8s_cert_path, + self._k8s_key_path, + self._k8s_token_endpoint, + self._ais_ca_cert_path, + self._ais_token_endpoint, + audience, + ) diff --git a/packages/google-auth/tests/data/gdch_service_account.json b/packages/google-auth/tests/data/gdch_service_account.json new file mode 100644 index 000000000000..c6c441bfdd35 --- /dev/null +++ b/packages/google-auth/tests/data/gdch_service_account.json @@ -0,0 +1,10 @@ +{ + "type":"gdch_service_account", + "format_version": "v1", + "k8s_ca_cert_path":"./k8s_ca_cert.pem", + "k8s_cert_path":"./k8s_cert.pem", + "k8s_key_path":"./k8s_key.pem", + "k8s_token_endpoint":"https://k8s_endpoint/api/v1/namespaces/sa-token-test/serviceaccounts/sa-token-user/token", + "ais_ca_cert_path":"./ais_ca_cert.pem", + "ais_token_endpoint":"https://ais_endpoint/sts/v1beta/token" +} diff --git a/packages/google-auth/tests/oauth2/test__client.py b/packages/google-auth/tests/oauth2/test__client.py index 5485bed84e56..400582fc305f 100644 --- a/packages/google-auth/tests/oauth2/test__client.py +++ b/packages/google-auth/tests/oauth2/test__client.py @@ -56,7 +56,7 @@ def test__handle_error_response(): assert excinfo.match(r"help: I\'m alive") -def test__handle_error_response_non_json(): +def test__handle_error_response_no_error(): response_data = {"foo": "bar"} with pytest.raises(exceptions.RefreshError) as excinfo: @@ -65,6 +65,15 @@ def test__handle_error_response_non_json(): assert excinfo.match(r"{\"foo\": \"bar\"}") +def test__handle_error_response_not_json(): + response_data = "this is an error message" + + with pytest.raises(exceptions.RefreshError) as excinfo: + _client._handle_error_response(response_data) + + assert excinfo.match(response_data) + + @mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) def test__parse_expiry(unused_utcnow): result = _client._parse_expiry({"expires_in": 500}) @@ -145,6 +154,8 @@ def test__token_endpoint_request_internal_failure_error(): _client._token_endpoint_request( request, "http://example.com", {"error_description": "internal_failure"} ) + # request should be called twice due to the retry + assert request.call_count == 2 request = make_request( {"error": "internal_failure"}, status=http_client.BAD_REQUEST @@ -154,6 +165,33 @@ def test__token_endpoint_request_internal_failure_error(): _client._token_endpoint_request( request, "http://example.com", {"error": "internal_failure"} ) + # request should be called twice due to the retry + assert request.call_count == 2 + + +def test__token_endpoint_request_string_error(): + response = mock.create_autospec(transport.Response, instance=True) + response.status = http_client.BAD_REQUEST + response.data = "this is an error message" + request = mock.create_autospec(transport.Request) + request.return_value = response + + with pytest.raises(exceptions.RefreshError) as excinfo: + _client._token_endpoint_request(request, "http://example.com", {}) + assert excinfo.match("this is an error message") + + +def test__token_endpoint_request_expected_status_code(): + request = make_request({}, status=http_client.CREATED) + + # It doesn't throw if the response code is the expected one. + _client._token_endpoint_request( + request, "http://example.com", {}, expected_status_code=http_client.CREATED + ) + + # It throws since the default status code is 200 OK, but we are expecting 201 CREATED. + with pytest.raises(exceptions.RefreshError): + _client._token_endpoint_request(request, "http://example.com", {}) def verify_request_params(request, params): diff --git a/packages/google-auth/tests/oauth2/test_gdch_credentials.py b/packages/google-auth/tests/oauth2/test_gdch_credentials.py new file mode 100644 index 000000000000..41aa399af9fe --- /dev/null +++ b/packages/google-auth/tests/oauth2/test_gdch_credentials.py @@ -0,0 +1,180 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import datetime + +import mock +import pytest # type: ignore +from six.moves import http_client + +from google.auth import exceptions +from google.auth.transport import requests +from google.oauth2 import gdch_credentials + + +class TestCredentials(object): + K8S_CA_CERT_PATH = "./k8s_ca_cert.pem" + K8S_CERT_PATH = "./k8s_cert.pem" + K8S_KEY_PATH = "./k8s_key.pem" + K8S_TOKEN = "k8s_token" + K8S_TOKEN_ENDPOINT = "https://k8s_endpoint/v1/token" + AIS_CA_CERT_PATH = "./ais_ca_cert.pem" + AIS_TOKEN_ENDPOINT = "https://k8s_endpoint/v1/token" + AUDIENCE = "audience_foo" + + @classmethod + def make_credentials(cls): + return gdch_credentials.ServiceAccountCredentials( + cls.K8S_CA_CERT_PATH, + cls.K8S_CERT_PATH, + cls.K8S_KEY_PATH, + cls.K8S_TOKEN_ENDPOINT, + cls.AIS_CA_CERT_PATH, + cls.AIS_TOKEN_ENDPOINT, + cls.AUDIENCE, + ) + + def test_with_audience(self): + creds = self.make_credentials() + assert creds._audience == self.AUDIENCE + + new_creds = creds.with_audience("bar") + assert new_creds._audience == "bar" + + @mock.patch("google.oauth2._client._token_endpoint_request", autospec=True) + def test__make_k8s_token_request(self, token_endpoint_request): + creds = self.make_credentials() + req = requests.Request() + + token_endpoint_request.return_value = { + "status": { + "token": self.K8S_TOKEN, + "expirationTimestamp": "2022-02-22T06:51:46Z", + } + } + assert creds._make_k8s_token_request(req) == self.K8S_TOKEN + token_endpoint_request.assert_called_with( + req, + creds._k8s_token_endpoint, + { + "kind": "TokenRequest", + "apiVersion": "authentication.k8s.io/v1", + "spec": {"audiences": [creds._ais_token_endpoint]}, + }, + None, + True, + http_client.CREATED, + cert=(creds._k8s_cert_path, creds._k8s_key_path), + verify=creds._k8s_ca_cert_path, + ) + + @mock.patch("google.oauth2._client._token_endpoint_request", autospec=True) + def test__make_k8s_token_request_no_token(self, token_endpoint_request): + creds = self.make_credentials() + req = requests.Request() + + token_endpoint_request.return_value = { + "status": {"expirationTimestamp": "2022-02-22T06:51:46Z"} + } + + with pytest.raises(exceptions.RefreshError) as excinfo: + creds._make_k8s_token_request(req) + assert excinfo.match("No access token in k8s token response") + + @mock.patch("google.oauth2._client._token_endpoint_request", autospec=True) + @mock.patch("google.auth._helpers.utcnow", autospec=True) + def test__make_ais_token_request(self, utcnow, token_endpoint_request): + creds = self.make_credentials() + req = requests.Request() + + issue_time = datetime.datetime(2022, 1, 1, 0, 0, 0) + utcnow.return_value = issue_time + expires_in_seconds = 3599 + + token_endpoint_request.return_value = { + "access_token": "ais_token", + "expires_in": expires_in_seconds, + "issued_token_type": "urn:ietf:params:oauth:token-type:access_token", + "token_type": "Bearer", + } + + k8s_token = self.K8S_TOKEN + ais_token, ais_expiry = creds._make_ais_token_request(k8s_token, req) + assert ais_token == "ais_token" + assert ais_expiry == issue_time + datetime.timedelta(seconds=expires_in_seconds) + token_endpoint_request.assert_called_with( + req, + creds._ais_token_endpoint, + { + "grant_type": gdch_credentials.TOKEN_EXCHANGE_TYPE, + "audience": creds._audience, + "requested_token_type": gdch_credentials.ACCESS_TOKEN_TOKEN_TYPE, + "subject_token": k8s_token, + "subject_token_type": gdch_credentials.SERVICE_ACCOUNT_TOKEN_TYPE, + }, + None, + True, + verify=creds._ais_ca_cert_path, + ) + + @mock.patch( + "google.oauth2.gdch_credentials.ServiceAccountCredentials._make_k8s_token_request", + autospec=True, + ) + @mock.patch( + "google.oauth2.gdch_credentials.ServiceAccountCredentials._make_ais_token_request", + autospec=True, + ) + def test_refresh(self, ais_token_request, k8s_token_request): + k8s_token_request.return_value = self.K8S_TOKEN + mock_expiry = mock.Mock() + ais_token_request.return_value = ("ais_token", mock_expiry) + + creds = self.make_credentials() + req = requests.Request() + creds.refresh(req) + + k8s_token_request.assert_called_with(creds, req) + ais_token_request.assert_called_with(creds, self.K8S_TOKEN, req) + assert creds.token == "ais_token" + assert creds.expiry == mock_expiry + + def test_refresh_request_not_requests_type(self): + creds = self.make_credentials() + req = mock.Mock() + + with pytest.raises(exceptions.RefreshError) as excinfo: + creds.refresh(req) + assert excinfo.match( + "request must be a google.auth.transport.requests.Request object" + ) + + @mock.patch( + "google.oauth2.gdch_credentials.ServiceAccountCredentials._make_k8s_token_request", + autospec=True, + ) + @mock.patch( + "google.oauth2.gdch_credentials.ServiceAccountCredentials._make_ais_token_request", + autospec=True, + ) + def test_before_request(self, ais_token_request, k8s_token_request): + ais_token_request.return_value = ("ais_token", mock.Mock()) + + cred = self.make_credentials() + headers = {} + + cred.before_request(requests.Request(), "GET", "https://example.com", headers) + k8s_token_request.assert_called() + ais_token_request.assert_called() + assert headers["authorization"] == "Bearer ais_token" diff --git a/packages/google-auth/tests/test__default.py b/packages/google-auth/tests/test__default.py index 4e7eeb84e178..e92166811b61 100644 --- a/packages/google-auth/tests/test__default.py +++ b/packages/google-auth/tests/test__default.py @@ -29,6 +29,7 @@ from google.auth import identity_pool from google.auth import impersonated_credentials from google.auth import pluggable +from google.oauth2 import gdch_credentials from google.oauth2 import service_account import google.oauth2.credentials @@ -51,6 +52,8 @@ CLIENT_SECRETS_FILE = os.path.join(DATA_DIR, "client_secrets.json") +GDCH_SERVICE_ACCOUNT_FILE = os.path.join(DATA_DIR, "gdch_service_account.json") + with open(SERVICE_ACCOUNT_FILE) as fh: SERVICE_ACCOUNT_FILE_DATA = json.load(fh) @@ -645,6 +648,22 @@ def test__get_gcloud_sdk_credentials_no_project_id(load, unused_isfile, get_proj assert get_project_id.called +def test__get_gdch_service_account_credentials_no_format_version(): + with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: + _default._get_gdch_service_account_credentials({}) + assert excinfo.match( + "format_version is not provided or unsupported. Supported version is: v1" + ) + + +def test__get_gdch_service_account_credentials_invalid_format_version(): + with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: + _default._get_gdch_service_account_credentials({"format_version": "v2"}) + assert excinfo.match( + "format_version is not provided or unsupported. Supported version is: v1" + ) + + class _AppIdentityModule(object): """The interface of the App Idenity app engine module. See https://cloud.google.com/appengine/docs/standard/python/refdocs\ @@ -1150,6 +1169,31 @@ def test_default_impersonated_service_account_set_both_scopes_and_default_scopes assert credentials._target_scopes == scopes +@mock.patch( + "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True +) +@mock.patch("google.auth._default._apply_quota_project_id", autospec=True) +def test_default_gdch_service_account_credentials(apply_quota_project_id, get_adc_path): + get_adc_path.return_value = GDCH_SERVICE_ACCOUNT_FILE + + credentials, _ = _default.default(quota_project_id="project-foo") + + # make sure _apply_quota_project_id is not called since GDCH service account + # credential doesn't inheirt from CredentialsWithQuotaProject. + apply_quota_project_id.assert_not_called() + + assert isinstance(credentials, gdch_credentials.ServiceAccountCredentials) + assert credentials._k8s_ca_cert_path == "./k8s_ca_cert.pem" + assert credentials._k8s_cert_path == "./k8s_cert.pem" + assert credentials._k8s_key_path == "./k8s_key.pem" + assert ( + credentials._k8s_token_endpoint + == "https://k8s_endpoint/api/v1/namespaces/sa-token-test/serviceaccounts/sa-token-user/token" + ) + assert credentials._ais_ca_cert_path == "./ais_ca_cert.pem" + assert credentials._ais_token_endpoint == "https://ais_endpoint/sts/v1beta/token" + + @EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH def test_load_credentials_from_external_account_pluggable(get_project_id, tmpdir): config_file = tmpdir.join("config.json") From 1906d177b875c34ff41be5f10c9aff973401b487 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Tue, 10 May 2022 19:07:32 -0700 Subject: [PATCH 567/966] revert: pluggable auth support #995 (#1039) --- packages/google-auth/google/auth/_default.py | 11 +- packages/google-auth/google/auth/pluggable.py | 349 --------- packages/google-auth/tests/test__default.py | 20 - packages/google-auth/tests/test_pluggable.py | 692 ------------------ 4 files changed, 1 insertion(+), 1071 deletions(-) delete mode 100644 packages/google-auth/google/auth/pluggable.py delete mode 100644 packages/google-auth/tests/test_pluggable.py diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index 15799cea78c4..fd346b1021db 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -324,7 +324,7 @@ def _get_external_account_credentials( google.auth.exceptions.DefaultCredentialsError: if the info dictionary is in the wrong format or is missing required information. """ - # There are currently 3 types of external_account credentials. + # There are currently 2 types of external_account credentials. if info.get("subject_token_type") == _AWS_SUBJECT_TOKEN_TYPE: # Check if configuration corresponds to an AWS credentials. from google.auth import aws @@ -332,15 +332,6 @@ def _get_external_account_credentials( credentials = aws.Credentials.from_info( info, scopes=scopes, default_scopes=default_scopes ) - elif ( - info.get("credential_source") is not None - and info.get("credential_source").get("executable") is not None - ): - from google.auth import pluggable - - credentials = pluggable.Credentials.from_info( - info, scopes=scopes, default_scopes=default_scopes - ) else: try: # Check if configuration corresponds to an Identity Pool credentials. diff --git a/packages/google-auth/google/auth/pluggable.py b/packages/google-auth/google/auth/pluggable.py deleted file mode 100644 index ebfb2c585ff6..000000000000 --- a/packages/google-auth/google/auth/pluggable.py +++ /dev/null @@ -1,349 +0,0 @@ -# Copyright 2022 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Pluggable Credentials. -Pluggable Credentials are initialized using external_account arguments which -are typically loaded from third-party executables. Unlike other -credentials that can be initialized with a list of explicit arguments, secrets -or credentials, external account clients use the environment and hints/guidelines -provided by the external_account JSON file to retrieve credentials and exchange -them for Google access tokens. - -Example credential_source for pluggable credential: - - { - "executable": { - "command": "/path/to/get/credentials.sh --arg1=value1 --arg2=value2", - "timeout_millis": 5000, - "output_file": "/path/to/generated/cached/credentials" - } - } -""" - -try: - from collections.abc import Mapping -# Python 2.7 compatibility -except ImportError: # pragma: NO COVER - from collections import Mapping -import io -import json -import os -import subprocess -import time - -from google.auth import _helpers -from google.auth import exceptions -from google.auth import external_account - -# The max supported executable spec version. -EXECUTABLE_SUPPORTED_MAX_VERSION = 1 - - -class Credentials(external_account.Credentials): - """External account credentials sourced from executables.""" - - def __init__( - self, - audience, - subject_token_type, - token_url, - credential_source, - service_account_impersonation_url=None, - client_id=None, - client_secret=None, - quota_project_id=None, - scopes=None, - default_scopes=None, - workforce_pool_user_project=None, - ): - """Instantiates an external account credentials object from a executables. - - Args: - audience (str): The STS audience field. - subject_token_type (str): The subject token type. - token_url (str): The STS endpoint URL. - credential_source (Mapping): The credential source dictionary used to - provide instructions on how to retrieve external credential to be - exchanged for Google access tokens. - - Example credential_source for pluggable credential: - - { - "executable": { - "command": "/path/to/get/credentials.sh --arg1=value1 --arg2=value2", - "timeout_millis": 5000, - "output_file": "/path/to/generated/cached/credentials" - } - } - - service_account_impersonation_url (Optional[str]): The optional service account - impersonation getAccessToken URL. - client_id (Optional[str]): The optional client ID. - client_secret (Optional[str]): The optional client secret. - quota_project_id (Optional[str]): The optional quota project ID. - scopes (Optional[Sequence[str]]): Optional scopes to request during the - authorization grant. - default_scopes (Optional[Sequence[str]]): Default scopes passed by a - Google client library. Use 'scopes' for user-defined scopes. - workforce_pool_user_project (Optona[str]): The optional workforce pool user - project number when the credential corresponds to a workforce pool and not - a workload Pluggable. The underlying principal must still have - serviceusage.services.use IAM permission to use the project for - billing/quota. - - Raises: - google.auth.exceptions.RefreshError: If an error is encountered during - access token retrieval logic. - ValueError: For invalid parameters. - - .. note:: Typically one of the helper constructors - :meth:`from_file` or - :meth:`from_info` are used instead of calling the constructor directly. - """ - - super(Credentials, self).__init__( - audience=audience, - subject_token_type=subject_token_type, - token_url=token_url, - credential_source=credential_source, - service_account_impersonation_url=service_account_impersonation_url, - client_id=client_id, - client_secret=client_secret, - quota_project_id=quota_project_id, - scopes=scopes, - default_scopes=default_scopes, - workforce_pool_user_project=workforce_pool_user_project, - ) - if not isinstance(credential_source, Mapping): - self._credential_source_executable = None - raise ValueError( - "Missing credential_source. The credential_source is not a dict." - ) - self._credential_source_executable = credential_source.get("executable") - if not self._credential_source_executable: - raise ValueError( - "Missing credential_source. An 'executable' must be provided." - ) - self._credential_source_executable_command = self._credential_source_executable.get( - "command" - ) - self._credential_source_executable_timeout_millis = self._credential_source_executable.get( - "timeout_millis" - ) - self._credential_source_executable_output_file = self._credential_source_executable.get( - "output_file" - ) - - if not self._credential_source_executable_command: - raise ValueError( - "Missing command field. Executable command must be provided." - ) - if not self._credential_source_executable_timeout_millis: - self._credential_source_executable_timeout_millis = 30 * 1000 - elif ( - self._credential_source_executable_timeout_millis < 5 * 1000 - or self._credential_source_executable_timeout_millis > 120 * 1000 - ): - raise ValueError("Timeout must be between 5 and 120 seconds.") - - @_helpers.copy_docstring(external_account.Credentials) - def retrieve_subject_token(self, request): - env_allow_executables = os.environ.get( - "GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES" - ) - if env_allow_executables != "1": - raise ValueError( - "Executables need to be explicitly allowed (set GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES to '1') to run." - ) - - # Check output file. - if self._credential_source_executable_output_file is not None: - try: - with open( - self._credential_source_executable_output_file - ) as output_file: - response = json.load(output_file) - except Exception: - pass - else: - try: - # If the cached response is expired, _parse_subject_token will raise an error which will be ignored and we will call the executable again. - subject_token = self._parse_subject_token(response) - except ValueError: - raise - except exceptions.RefreshError: - pass - else: - return subject_token - - # Inject env vars. - original_audience = os.getenv("GOOGLE_EXTERNAL_ACCOUNT_AUDIENCE") - os.environ["GOOGLE_EXTERNAL_ACCOUNT_AUDIENCE"] = self._audience - original_subject_token_type = os.getenv("GOOGLE_EXTERNAL_ACCOUNT_TOKEN_TYPE") - os.environ["GOOGLE_EXTERNAL_ACCOUNT_TOKEN_TYPE"] = self._subject_token_type - original_interactive = os.getenv("GOOGLE_EXTERNAL_ACCOUNT_INTERACTIVE") - os.environ[ - "GOOGLE_EXTERNAL_ACCOUNT_INTERACTIVE" - ] = "0" # Always set to 0 until interactive mode is implemented. - original_service_account_impersonation_url = os.getenv( - "GOOGLE_EXTERNAL_ACCOUNT_IMPERSONATED_EMAIL" - ) - if self._service_account_impersonation_url is not None: - os.environ[ - "GOOGLE_EXTERNAL_ACCOUNT_IMPERSONATED_EMAIL" - ] = self.service_account_email - original_credential_source_executable_output_file = os.getenv( - "GOOGLE_EXTERNAL_ACCOUNT_OUTPUT_FILE" - ) - if self._credential_source_executable_output_file is not None: - os.environ[ - "GOOGLE_EXTERNAL_ACCOUNT_OUTPUT_FILE" - ] = self._credential_source_executable_output_file - - try: - result = subprocess.check_output( - self._credential_source_executable_command.split(), - stderr=subprocess.STDOUT, - ) - except subprocess.CalledProcessError as e: - raise exceptions.RefreshError( - "Executable exited with non-zero return code {}. Error: {}".format( - e.returncode, e.output - ) - ) - else: - try: - data = result.decode("utf-8") - response = json.loads(data) - subject_token = self._parse_subject_token(response) - except Exception: - raise - - # Reset env vars. - if original_audience is not None: - os.environ["GOOGLE_EXTERNAL_ACCOUNT_AUDIENCE"] = original_audience - else: - del os.environ["GOOGLE_EXTERNAL_ACCOUNT_AUDIENCE"] - if original_subject_token_type is not None: - os.environ[ - "GOOGLE_EXTERNAL_ACCOUNT_TOKEN_TYPE" - ] = original_subject_token_type - else: - del os.environ["GOOGLE_EXTERNAL_ACCOUNT_TOKEN_TYPE"] - if original_interactive is not None: - os.environ["GOOGLE_EXTERNAL_ACCOUNT_INTERACTIVE"] = original_interactive - else: - del os.environ["GOOGLE_EXTERNAL_ACCOUNT_INTERACTIVE"] - if original_service_account_impersonation_url is not None: - os.environ[ - "GOOGLE_EXTERNAL_ACCOUNT_IMPERSONATED_EMAIL" - ] = original_service_account_impersonation_url - elif os.getenv("GOOGLE_EXTERNAL_ACCOUNT_IMPERSONATED_EMAIL") is not None: - del os.environ["GOOGLE_EXTERNAL_ACCOUNT_IMPERSONATED_EMAIL"] - if original_credential_source_executable_output_file is not None: - os.environ[ - "GOOGLE_EXTERNAL_ACCOUNT_OUTPUT_FILE" - ] = original_credential_source_executable_output_file - elif os.getenv("GOOGLE_EXTERNAL_ACCOUNT_OUTPUT_FILE") is not None: - del os.environ["GOOGLE_EXTERNAL_ACCOUNT_OUTPUT_FILE"] - - return subject_token - - @classmethod - def from_info(cls, info, **kwargs): - """Creates a Pluggable Credentials instance from parsed external account info. - - Args: - info (Mapping[str, str]): The Pluggable external account info in Google - format. - kwargs: Additional arguments to pass to the constructor. - - Returns: - google.auth.pluggable.Credentials: The constructed - credentials. - - Raises: - ValueError: For invalid parameters. - """ - return cls( - audience=info.get("audience"), - subject_token_type=info.get("subject_token_type"), - token_url=info.get("token_url"), - service_account_impersonation_url=info.get( - "service_account_impersonation_url" - ), - client_id=info.get("client_id"), - client_secret=info.get("client_secret"), - credential_source=info.get("credential_source"), - quota_project_id=info.get("quota_project_id"), - workforce_pool_user_project=info.get("workforce_pool_user_project"), - **kwargs - ) - - @classmethod - def from_file(cls, filename, **kwargs): - """Creates an Pluggable Credentials instance from an external account json file. - - Args: - filename (str): The path to the Pluggable external account json file. - kwargs: Additional arguments to pass to the constructor. - - Returns: - google.auth.pluggable.Credentials: The constructed - credentials. - """ - with io.open(filename, "r", encoding="utf-8") as json_file: - data = json.load(json_file) - return cls.from_info(data, **kwargs) - - def _parse_subject_token(self, response): - if "version" not in response: - raise ValueError("The executable response is missing the version field.") - if response["version"] > EXECUTABLE_SUPPORTED_MAX_VERSION: - raise exceptions.RefreshError( - "Executable returned unsupported version {}.".format( - response["version"] - ) - ) - if "success" not in response: - raise ValueError("The executable response is missing the success field.") - if not response["success"]: - if "code" not in response or "message" not in response: - raise ValueError( - "Error code and message fields are required in the response." - ) - raise exceptions.RefreshError( - "Executable returned unsuccessful response: code: {}, message: {}.".format( - response["code"], response["message"] - ) - ) - if "expiration_time" not in response: - raise ValueError( - "The executable response is missing the expiration_time field." - ) - if response["expiration_time"] < time.time(): - raise exceptions.RefreshError( - "The token returned by the executable is expired." - ) - if "token_type" not in response: - raise ValueError("The executable response is missing the token_type field.") - if ( - response["token_type"] == "urn:ietf:params:oauth:token-type:jwt" - or response["token_type"] == "urn:ietf:params:oauth:token-type:id_token" - ): # OIDC - return response["id_token"] - elif response["token_type"] == "urn:ietf:params:oauth:token-type:saml2": # SAML - return response["saml_response"] - else: - raise exceptions.RefreshError("Executable returned unsupported token type.") diff --git a/packages/google-auth/tests/test__default.py b/packages/google-auth/tests/test__default.py index e92166811b61..ab8bad72edb0 100644 --- a/packages/google-auth/tests/test__default.py +++ b/packages/google-auth/tests/test__default.py @@ -28,7 +28,6 @@ from google.auth import external_account from google.auth import identity_pool from google.auth import impersonated_credentials -from google.auth import pluggable from google.oauth2 import gdch_credentials from google.oauth2 import service_account import google.oauth2.credentials @@ -76,13 +75,6 @@ "token_url": TOKEN_URL, "credential_source": {"file": SUBJECT_TOKEN_TEXT_FILE}, } -PLUGGABLE_DATA = { - "type": "external_account", - "audience": AUDIENCE, - "subject_token_type": "urn:ietf:params:oauth:token-type:jwt", - "token_url": TOKEN_URL, - "credential_source": {"executable": {"command": "command"}}, -} AWS_DATA = { "type": "external_account", "audience": AUDIENCE, @@ -1192,15 +1184,3 @@ def test_default_gdch_service_account_credentials(apply_quota_project_id, get_ad ) assert credentials._ais_ca_cert_path == "./ais_ca_cert.pem" assert credentials._ais_token_endpoint == "https://ais_endpoint/sts/v1beta/token" - - -@EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH -def test_load_credentials_from_external_account_pluggable(get_project_id, tmpdir): - config_file = tmpdir.join("config.json") - config_file.write(json.dumps(PLUGGABLE_DATA)) - credentials, project_id = _default.load_credentials_from_file(str(config_file)) - - assert isinstance(credentials, pluggable.Credentials) - # Since no scopes are specified, the project ID cannot be determined. - assert project_id is None - assert get_project_id.called diff --git a/packages/google-auth/tests/test_pluggable.py b/packages/google-auth/tests/test_pluggable.py deleted file mode 100644 index 2580fd1d435a..000000000000 --- a/packages/google-auth/tests/test_pluggable.py +++ /dev/null @@ -1,692 +0,0 @@ -# Copyright 2022 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# import datetime -import json -import os -import subprocess - -import mock -import pytest # type: ignore - -# from six.moves import http_client -# from six.moves import urllib - -# from google.auth import _helpers -from google.auth import exceptions -from google.auth import pluggable - -# from google.auth import transport - - -CLIENT_ID = "username" -CLIENT_SECRET = "password" -# Base64 encoding of "username:password". -BASIC_AUTH_ENCODING = "dXNlcm5hbWU6cGFzc3dvcmQ=" -SERVICE_ACCOUNT_EMAIL = "service-1234@service-name.iam.gserviceaccount.com" -SERVICE_ACCOUNT_IMPERSONATION_URL = ( - "https://us-east1-iamcredentials.googleapis.com/v1/projects/-" - + "/serviceAccounts/{}:generateAccessToken".format(SERVICE_ACCOUNT_EMAIL) -) -QUOTA_PROJECT_ID = "QUOTA_PROJECT_ID" -SCOPES = ["scope1", "scope2"] -SUBJECT_TOKEN_FIELD_NAME = "access_token" - -TOKEN_URL = "https://sts.googleapis.com/v1/token" -SERVICE_ACCOUNT_IMPERSONATION_URL = "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/byoid-test@cicpclientproj.iam.gserviceaccount.com:generateAccessToken" -SUBJECT_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:jwt" -AUDIENCE = "//iam.googleapis.com/projects/123456/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID" - - -class TestCredentials(object): - CREDENTIAL_SOURCE_EXECUTABLE_COMMAND = ( - "/fake/external/excutable --arg1=value1 --arg2=value2" - ) - CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE = "fake_output_file" - CREDENTIAL_SOURCE_EXECUTABLE = { - "command": CREDENTIAL_SOURCE_EXECUTABLE_COMMAND, - "timeout_millis": 30000, - "output_file": CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, - } - CREDENTIAL_SOURCE = {"executable": CREDENTIAL_SOURCE_EXECUTABLE} - EXECUTABLE_OIDC_TOKEN = "FAKE_ID_TOKEN" - EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_ID_TOKEN = { - "version": 1, - "success": True, - "token_type": "urn:ietf:params:oauth:token-type:id_token", - "id_token": EXECUTABLE_OIDC_TOKEN, - "expiration_time": 9999999999, - } - EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_JWT = { - "version": 1, - "success": True, - "token_type": "urn:ietf:params:oauth:token-type:jwt", - "id_token": EXECUTABLE_OIDC_TOKEN, - "expiration_time": 9999999999, - } - EXECUTABLE_SAML_TOKEN = "FAKE_SAML_RESPONSE" - EXECUTABLE_SUCCESSFUL_SAML_RESPONSE = { - "version": 1, - "success": True, - "token_type": "urn:ietf:params:oauth:token-type:saml2", - "saml_response": EXECUTABLE_SAML_TOKEN, - "expiration_time": 9999999999, - } - EXECUTABLE_FAILED_RESPONSE = { - "version": 1, - "success": False, - "code": "401", - "message": "Permission denied. Caller not authorized", - } - CREDENTIAL_URL = "http://fakeurl.com" - - @classmethod - def make_pluggable( - cls, - audience=AUDIENCE, - subject_token_type=SUBJECT_TOKEN_TYPE, - client_id=None, - client_secret=None, - quota_project_id=None, - scopes=None, - default_scopes=None, - service_account_impersonation_url=None, - credential_source=None, - workforce_pool_user_project=None, - ): - return pluggable.Credentials( - audience=audience, - subject_token_type=subject_token_type, - token_url=TOKEN_URL, - service_account_impersonation_url=service_account_impersonation_url, - credential_source=credential_source, - client_id=client_id, - client_secret=client_secret, - quota_project_id=quota_project_id, - scopes=scopes, - default_scopes=default_scopes, - workforce_pool_user_project=workforce_pool_user_project, - ) - - @mock.patch.object(pluggable.Credentials, "__init__", return_value=None) - def test_from_info_full_options(self, mock_init): - credentials = pluggable.Credentials.from_info( - { - "audience": AUDIENCE, - "subject_token_type": SUBJECT_TOKEN_TYPE, - "token_url": TOKEN_URL, - "service_account_impersonation_url": SERVICE_ACCOUNT_IMPERSONATION_URL, - "client_id": CLIENT_ID, - "client_secret": CLIENT_SECRET, - "quota_project_id": QUOTA_PROJECT_ID, - "credential_source": self.CREDENTIAL_SOURCE, - } - ) - - # Confirm pluggable.Credentials instantiated with expected attributes. - assert isinstance(credentials, pluggable.Credentials) - mock_init.assert_called_once_with( - audience=AUDIENCE, - subject_token_type=SUBJECT_TOKEN_TYPE, - token_url=TOKEN_URL, - service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, - client_id=CLIENT_ID, - client_secret=CLIENT_SECRET, - credential_source=self.CREDENTIAL_SOURCE, - quota_project_id=QUOTA_PROJECT_ID, - workforce_pool_user_project=None, - ) - - @mock.patch.object(pluggable.Credentials, "__init__", return_value=None) - def test_from_info_required_options_only(self, mock_init): - credentials = pluggable.Credentials.from_info( - { - "audience": AUDIENCE, - "subject_token_type": SUBJECT_TOKEN_TYPE, - "token_url": TOKEN_URL, - "credential_source": self.CREDENTIAL_SOURCE, - } - ) - - # Confirm pluggable.Credentials instantiated with expected attributes. - assert isinstance(credentials, pluggable.Credentials) - mock_init.assert_called_once_with( - audience=AUDIENCE, - subject_token_type=SUBJECT_TOKEN_TYPE, - token_url=TOKEN_URL, - service_account_impersonation_url=None, - client_id=None, - client_secret=None, - credential_source=self.CREDENTIAL_SOURCE, - quota_project_id=None, - workforce_pool_user_project=None, - ) - - @mock.patch.object(pluggable.Credentials, "__init__", return_value=None) - def test_from_file_full_options(self, mock_init, tmpdir): - info = { - "audience": AUDIENCE, - "subject_token_type": SUBJECT_TOKEN_TYPE, - "token_url": TOKEN_URL, - "service_account_impersonation_url": SERVICE_ACCOUNT_IMPERSONATION_URL, - "client_id": CLIENT_ID, - "client_secret": CLIENT_SECRET, - "quota_project_id": QUOTA_PROJECT_ID, - "credential_source": self.CREDENTIAL_SOURCE, - } - config_file = tmpdir.join("config.json") - config_file.write(json.dumps(info)) - credentials = pluggable.Credentials.from_file(str(config_file)) - - # Confirm pluggable.Credentials instantiated with expected attributes. - assert isinstance(credentials, pluggable.Credentials) - mock_init.assert_called_once_with( - audience=AUDIENCE, - subject_token_type=SUBJECT_TOKEN_TYPE, - token_url=TOKEN_URL, - service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, - client_id=CLIENT_ID, - client_secret=CLIENT_SECRET, - credential_source=self.CREDENTIAL_SOURCE, - quota_project_id=QUOTA_PROJECT_ID, - workforce_pool_user_project=None, - ) - - @mock.patch.object(pluggable.Credentials, "__init__", return_value=None) - def test_from_file_required_options_only(self, mock_init, tmpdir): - info = { - "audience": AUDIENCE, - "subject_token_type": SUBJECT_TOKEN_TYPE, - "token_url": TOKEN_URL, - "credential_source": self.CREDENTIAL_SOURCE, - } - config_file = tmpdir.join("config.json") - config_file.write(json.dumps(info)) - credentials = pluggable.Credentials.from_file(str(config_file)) - - # Confirm pluggable.Credentials instantiated with expected attributes. - assert isinstance(credentials, pluggable.Credentials) - mock_init.assert_called_once_with( - audience=AUDIENCE, - subject_token_type=SUBJECT_TOKEN_TYPE, - token_url=TOKEN_URL, - service_account_impersonation_url=None, - client_id=None, - client_secret=None, - credential_source=self.CREDENTIAL_SOURCE, - quota_project_id=None, - workforce_pool_user_project=None, - ) - - def test_constructor_invalid_options(self): - credential_source = {"unsupported": "value"} - - with pytest.raises(ValueError) as excinfo: - self.make_pluggable(credential_source=credential_source) - - assert excinfo.match(r"Missing credential_source") - - def test_constructor_invalid_credential_source(self): - with pytest.raises(ValueError) as excinfo: - self.make_pluggable(credential_source="non-dict") - - assert excinfo.match(r"Missing credential_source") - - def test_info_with_credential_source(self): - credentials = self.make_pluggable( - credential_source=self.CREDENTIAL_SOURCE.copy() - ) - - assert credentials.info == { - "type": "external_account", - "audience": AUDIENCE, - "subject_token_type": SUBJECT_TOKEN_TYPE, - "token_url": TOKEN_URL, - "credential_source": self.CREDENTIAL_SOURCE, - } - - @mock.patch.dict( - os.environ, - { - "GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1", - "GOOGLE_EXTERNAL_ACCOUNT_AUDIENCE": "original_audience", - "GOOGLE_EXTERNAL_ACCOUNT_TOKEN_TYPE": "original_token_type", - "GOOGLE_EXTERNAL_ACCOUNT_INTERACTIVE": "0", - "GOOGLE_EXTERNAL_ACCOUNT_IMPERSONATED_EMAIL": "original_impersonated_email", - "GOOGLE_EXTERNAL_ACCOUNT_OUTPUT_FILE": "original_output_file", - }, - ) - def test_retrieve_subject_token_oidc_id_token(self): - with mock.patch( - "subprocess.check_output", - return_value=json.dumps( - self.EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_ID_TOKEN - ).encode("UTF-8"), - ): - credentials = self.make_pluggable( - audience=AUDIENCE, - service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, - credential_source=self.CREDENTIAL_SOURCE, - ) - - subject_token = credentials.retrieve_subject_token(None) - - assert subject_token == self.EXECUTABLE_OIDC_TOKEN - - @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) - def test_retrieve_subject_token_oidc_jwt(self): - with mock.patch( - "subprocess.check_output", - return_value=json.dumps( - self.EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_JWT - ).encode("UTF-8"), - ): - credentials = self.make_pluggable( - audience=AUDIENCE, - service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, - credential_source=self.CREDENTIAL_SOURCE, - ) - - subject_token = credentials.retrieve_subject_token(None) - - assert subject_token == self.EXECUTABLE_OIDC_TOKEN - - @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) - def test_retrieve_subject_token_saml(self): - with mock.patch( - "subprocess.check_output", - return_value=json.dumps(self.EXECUTABLE_SUCCESSFUL_SAML_RESPONSE).encode( - "UTF-8" - ), - ): - credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE) - - subject_token = credentials.retrieve_subject_token(None) - - assert subject_token == self.EXECUTABLE_SAML_TOKEN - - @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) - def test_retrieve_subject_token_failed(self): - with mock.patch( - "subprocess.check_output", - return_value=json.dumps(self.EXECUTABLE_FAILED_RESPONSE).encode("UTF-8"), - ): - credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE) - - with pytest.raises(exceptions.RefreshError) as excinfo: - _ = credentials.retrieve_subject_token(None) - - assert excinfo.match( - r"Executable returned unsuccessful response: code: 401, message: Permission denied. Caller not authorized." - ) - - @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "0"}) - def test_retrieve_subject_token_not_allowd(self): - with mock.patch( - "subprocess.check_output", - return_value=json.dumps( - self.EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_ID_TOKEN - ).encode("UTF-8"), - ): - credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE) - - with pytest.raises(ValueError) as excinfo: - _ = credentials.retrieve_subject_token(None) - - assert excinfo.match(r"Executables need to be explicitly allowed") - - @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) - def test_retrieve_subject_token_invalid_version(self): - EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_VERSION_2 = { - "version": 2, - "success": True, - "token_type": "urn:ietf:params:oauth:token-type:id_token", - "id_token": self.EXECUTABLE_OIDC_TOKEN, - "expiration_time": 9999999999, - } - - with mock.patch( - "subprocess.check_output", - return_value=json.dumps( - EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_VERSION_2 - ).encode("UTF-8"), - ): - credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE) - - with pytest.raises(exceptions.RefreshError) as excinfo: - _ = credentials.retrieve_subject_token(None) - - assert excinfo.match(r"Executable returned unsupported version.") - - @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) - def test_retrieve_subject_token_expired_token(self): - EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_EXPIRED = { - "version": 1, - "success": True, - "token_type": "urn:ietf:params:oauth:token-type:id_token", - "id_token": self.EXECUTABLE_OIDC_TOKEN, - "expiration_time": 0, - } - - with mock.patch( - "subprocess.check_output", - return_value=json.dumps(EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_EXPIRED).encode( - "UTF-8" - ), - ): - credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE) - - with pytest.raises(exceptions.RefreshError) as excinfo: - _ = credentials.retrieve_subject_token(None) - - assert excinfo.match(r"The token returned by the executable is expired.") - - @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) - def test_retrieve_subject_token_file_cache(self): - ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE = "actual_output_file" - ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE = { - "command": "command", - "timeout_millis": 30000, - "output_file": ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, - } - ACTUAL_CREDENTIAL_SOURCE = {"executable": ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE} - with open(ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, "w") as output_file: - json.dump(self.EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_ID_TOKEN, output_file) - - credentials = self.make_pluggable(credential_source=ACTUAL_CREDENTIAL_SOURCE) - - subject_token = credentials.retrieve_subject_token(None) - assert subject_token == self.EXECUTABLE_OIDC_TOKEN - - os.remove(ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE) - - @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) - def test_retrieve_subject_token_no_file_cache(self): - ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE = { - "command": "command", - "timeout_millis": 30000, - } - ACTUAL_CREDENTIAL_SOURCE = {"executable": ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE} - - with mock.patch( - "subprocess.check_output", - return_value=json.dumps( - self.EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_ID_TOKEN - ).encode("UTF-8"), - ): - credentials = self.make_pluggable( - credential_source=ACTUAL_CREDENTIAL_SOURCE - ) - - subject_token = credentials.retrieve_subject_token(None) - - assert subject_token == self.EXECUTABLE_OIDC_TOKEN - - @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) - def test_retrieve_subject_token_file_cache_value_error_report(self): - ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE = "actual_output_file" - ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE = { - "command": "command", - "timeout_millis": 30000, - "output_file": ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, - } - ACTUAL_CREDENTIAL_SOURCE = {"executable": ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE} - ACTUAL_EXECUTABLE_RESPONSE = { - "success": True, - "token_type": "urn:ietf:params:oauth:token-type:id_token", - "id_token": self.EXECUTABLE_OIDC_TOKEN, - "expiration_time": 9999999999, - } - with open(ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, "w") as output_file: - json.dump(ACTUAL_EXECUTABLE_RESPONSE, output_file) - - credentials = self.make_pluggable(credential_source=ACTUAL_CREDENTIAL_SOURCE) - - with pytest.raises(ValueError) as excinfo: - _ = credentials.retrieve_subject_token(None) - - assert excinfo.match(r"The executable response is missing the version field.") - - os.remove(ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE) - - @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) - def test_retrieve_subject_token_file_cache_refresh_error_retry(self): - ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE = "actual_output_file" - ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE = { - "command": "command", - "timeout_millis": 30000, - "output_file": ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, - } - ACTUAL_CREDENTIAL_SOURCE = {"executable": ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE} - ACTUAL_EXECUTABLE_RESPONSE = { - "version": 2, - "success": True, - "token_type": "urn:ietf:params:oauth:token-type:id_token", - "id_token": self.EXECUTABLE_OIDC_TOKEN, - "expiration_time": 9999999999, - } - with open(ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, "w") as output_file: - json.dump(ACTUAL_EXECUTABLE_RESPONSE, output_file) - - with mock.patch( - "subprocess.check_output", - return_value=json.dumps( - self.EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_ID_TOKEN - ).encode("UTF-8"), - ): - credentials = self.make_pluggable( - credential_source=ACTUAL_CREDENTIAL_SOURCE - ) - - subject_token = credentials.retrieve_subject_token(None) - - assert subject_token == self.EXECUTABLE_OIDC_TOKEN - - os.remove(ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE) - - @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) - def test_retrieve_subject_token_unsupported_token_type(self): - EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE = { - "version": 1, - "success": True, - "token_type": "unsupported_token_type", - "id_token": self.EXECUTABLE_OIDC_TOKEN, - "expiration_time": 9999999999, - } - - with mock.patch( - "subprocess.check_output", - return_value=json.dumps(EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE).encode( - "UTF-8" - ), - ): - credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE) - - with pytest.raises(exceptions.RefreshError) as excinfo: - _ = credentials.retrieve_subject_token(None) - - assert excinfo.match(r"Executable returned unsupported token type.") - - @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) - def test_retrieve_subject_token_missing_version(self): - EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE = { - "success": True, - "token_type": "urn:ietf:params:oauth:token-type:id_token", - "id_token": self.EXECUTABLE_OIDC_TOKEN, - "expiration_time": 9999999999, - } - - with mock.patch( - "subprocess.check_output", - return_value=json.dumps(EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE).encode( - "UTF-8" - ), - ): - credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE) - - with pytest.raises(ValueError) as excinfo: - _ = credentials.retrieve_subject_token(None) - - assert excinfo.match( - r"The executable response is missing the version field." - ) - - @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) - def test_retrieve_subject_token_missing_success(self): - EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE = { - "version": 1, - "token_type": "urn:ietf:params:oauth:token-type:id_token", - "id_token": self.EXECUTABLE_OIDC_TOKEN, - "expiration_time": 9999999999, - } - - with mock.patch( - "subprocess.check_output", - return_value=json.dumps(EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE).encode( - "UTF-8" - ), - ): - credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE) - - with pytest.raises(ValueError) as excinfo: - _ = credentials.retrieve_subject_token(None) - - assert excinfo.match( - r"The executable response is missing the success field." - ) - - @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) - def test_retrieve_subject_token_missing_error_code_message(self): - EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE = {"version": 1, "success": False} - - with mock.patch( - "subprocess.check_output", - return_value=json.dumps(EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE).encode( - "UTF-8" - ), - ): - credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE) - - with pytest.raises(ValueError) as excinfo: - _ = credentials.retrieve_subject_token(None) - - assert excinfo.match( - r"Error code and message fields are required in the response." - ) - - @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) - def test_retrieve_subject_token_missing_expiration_time(self): - EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE = { - "version": 1, - "success": True, - "token_type": "urn:ietf:params:oauth:token-type:id_token", - "id_token": self.EXECUTABLE_OIDC_TOKEN, - } - - with mock.patch( - "subprocess.check_output", - return_value=json.dumps(EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE).encode( - "UTF-8" - ), - ): - credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE) - - with pytest.raises(ValueError) as excinfo: - _ = credentials.retrieve_subject_token(None) - - assert excinfo.match( - r"The executable response is missing the expiration_time field." - ) - - @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) - def test_retrieve_subject_token_missing_token_type(self): - EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE = { - "version": 1, - "success": True, - "id_token": self.EXECUTABLE_OIDC_TOKEN, - "expiration_time": 9999999999, - } - - with mock.patch( - "subprocess.check_output", - return_value=json.dumps(EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE).encode( - "UTF-8" - ), - ): - credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE) - - with pytest.raises(ValueError) as excinfo: - _ = credentials.retrieve_subject_token(None) - - assert excinfo.match( - r"The executable response is missing the token_type field." - ) - - @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) - def test_credential_source_missing_command(self): - with pytest.raises(ValueError) as excinfo: - CREDENTIAL_SOURCE = { - "executable": { - "timeout_millis": 30000, - "output_file": self.CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, - } - } - _ = self.make_pluggable(credential_source=CREDENTIAL_SOURCE) - - assert excinfo.match( - r"Missing command field. Executable command must be provided." - ) - - @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) - def test_credential_source_timeout_small(self): - with pytest.raises(ValueError) as excinfo: - CREDENTIAL_SOURCE = { - "executable": { - "command": self.CREDENTIAL_SOURCE_EXECUTABLE_COMMAND, - "timeout_millis": 5000 - 1, - "output_file": self.CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, - } - } - _ = self.make_pluggable(credential_source=CREDENTIAL_SOURCE) - - assert excinfo.match(r"Timeout must be between 5 and 120 seconds.") - - @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) - def test_credential_source_timeout_large(self): - with pytest.raises(ValueError) as excinfo: - CREDENTIAL_SOURCE = { - "executable": { - "command": self.CREDENTIAL_SOURCE_EXECUTABLE_COMMAND, - "timeout_millis": 120000 + 1, - "output_file": self.CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, - } - } - _ = self.make_pluggable(credential_source=CREDENTIAL_SOURCE) - - assert excinfo.match(r"Timeout must be between 5 and 120 seconds.") - - @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) - def test_retrieve_subject_token_executable_fail(self): - with mock.patch("subprocess.check_output") as subprocess_mock: - subprocess_mock.side_effect = subprocess.CalledProcessError( - returncode=1, cmd="" - ) - credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE) - - with pytest.raises(exceptions.RefreshError) as excinfo: - _ = credentials.retrieve_subject_token(None) - - assert excinfo.match( - r"Executable exited with non-zero return code 1. Error: None" - ) From bc454858b08b4e86371a80840298822c3e69a685 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Fri, 13 May 2022 14:46:45 -0700 Subject: [PATCH 568/966] chore: update sys test creds (#1041) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10323 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 91f6f45702822fe53e24ec255c572ac6d3a7aba3..151fa1ba546e06f593561959ec5a80b113680d42 100644 GIT binary patch literal 10324 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 literal 10323 zcmV-ZD6H2CB>?tKRTF!}KY{c}^}eKmKeGLJnGlRA|0v4}KwoM8VTURU=n)dBPyjE^ zFCYj7AaKTTI9-ur>AP@BO_yJuFDw^9`OOyG{GmRGch9x$I9W98IQkDfBoePEWTsB=z*y=F>jjWD_Xzh_&tQ9D;92ru3Gxus5-~n|Gwi za+S#bg<%*NnKDLXkowcyyJJjYp!(S$Cke2bjE|o4i5prA`5*EzBh{1OJsjhC=}DR0 zM@*3jIk_yEjV{(UkHqRW9yL9cSUP~|>(%I?)wCnyHiLMyw+4=`hrP=iUWg`GNsmiJztPkdRFs`^Gl6$vW$hC{qo;=2iXO+HMY;@L!&#tM zL7~#i0E;EDnN^5|RQsSP63UncME*W8x(%al3g02e-*8oT0;JM&@1UH>@5{H+QNg#0 z*CCgncUX?;kB=vb{b?3Qx3A**!=Nbi=psZSu?UjASycS^{ALjMzR(qhkgNLBMN|hB zr?-W9qLON)a)WalstmW-q}P1@01JNju|rad33(_Ap@I!V%s@@;Ac|xD6nE%F|KtEa z7+<-wL2qJp4_{y)mvQMs1%+FPL(l%-go2C51>!bw5^RPi=g$P13b}OM+9h{C92ykX zx0hW40Ex8m@$@Nu!7GKmb9B&l(1gO8hpAVELspWa)G+KGlJ8MW5YVA8`v2JDe^IwI=*&)

)bIsLe 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 From 6d8b304eb79aab87a3b82ce00ef5aaaac4daf00b Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Fri, 13 May 2022 15:08:47 -0700 Subject: [PATCH 569/966] revert: revert experimental GDCH support (#1022) (#1042) This reverts commit 5367aac881fdba814f66e4d6d5f59fccecc12547. --- packages/google-auth/google/auth/_default.py | 46 +---- packages/google-auth/google/oauth2/_client.py | 84 ++------ .../google/oauth2/gdch_credentials.py | 194 ------------------ .../tests/data/gdch_service_account.json | 10 - .../google-auth/tests/oauth2/test__client.py | 40 +--- .../tests/oauth2/test_gdch_credentials.py | 180 ---------------- packages/google-auth/tests/test__default.py | 44 ---- 7 files changed, 20 insertions(+), 578 deletions(-) delete mode 100644 packages/google-auth/google/oauth2/gdch_credentials.py delete mode 100644 packages/google-auth/tests/data/gdch_service_account.json delete mode 100644 packages/google-auth/tests/oauth2/test_gdch_credentials.py diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index fd346b1021db..d038438d5be5 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -36,13 +36,11 @@ _SERVICE_ACCOUNT_TYPE = "service_account" _EXTERNAL_ACCOUNT_TYPE = "external_account" _IMPERSONATED_SERVICE_ACCOUNT_TYPE = "impersonated_service_account" -_GDCH_SERVICE_ACCOUNT_TYPE = "gdch_service_account" _VALID_TYPES = ( _AUTHORIZED_USER_TYPE, _SERVICE_ACCOUNT_TYPE, _EXTERNAL_ACCOUNT_TYPE, _IMPERSONATED_SERVICE_ACCOUNT_TYPE, - _GDCH_SERVICE_ACCOUNT_TYPE, ) # Help message when no credentials can be found. @@ -136,8 +134,6 @@ def load_credentials_from_file( def _load_credentials_from_info( filename, info, scopes, default_scopes, quota_project_id, request ): - from google.auth.credentials import CredentialsWithQuotaProject - credential_type = info.get("type") if credential_type == _AUTHORIZED_USER_TYPE: @@ -162,8 +158,6 @@ def _load_credentials_from_info( credentials, project_id = _get_impersonated_service_account_credentials( filename, info, scopes ) - elif credential_type == _GDCH_SERVICE_ACCOUNT_TYPE: - credentials, project_id = _get_gdch_service_account_credentials(info) else: raise exceptions.DefaultCredentialsError( "The file {file} does not have a valid type. " @@ -171,8 +165,7 @@ def _load_credentials_from_info( file=filename, type=credential_type, valid_types=_VALID_TYPES ) ) - if isinstance(credentials, CredentialsWithQuotaProject): - credentials = _apply_quota_project_id(credentials, quota_project_id) + credentials = _apply_quota_project_id(credentials, quota_project_id) return credentials, project_id @@ -428,36 +421,6 @@ def _get_impersonated_service_account_credentials(filename, info, scopes): return credentials, None -def _get_gdch_service_account_credentials(info): - from google.oauth2 import gdch_credentials - - k8s_ca_cert_path = info.get("k8s_ca_cert_path") - k8s_cert_path = info.get("k8s_cert_path") - k8s_key_path = info.get("k8s_key_path") - k8s_token_endpoint = info.get("k8s_token_endpoint") - ais_ca_cert_path = info.get("ais_ca_cert_path") - ais_token_endpoint = info.get("ais_token_endpoint") - - format_version = info.get("format_version") - if format_version != "v1": - raise exceptions.DefaultCredentialsError( - "format_version is not provided or unsupported. Supported version is: v1" - ) - - return ( - gdch_credentials.ServiceAccountCredentials( - k8s_ca_cert_path, - k8s_cert_path, - k8s_key_path, - k8s_token_endpoint, - ais_ca_cert_path, - ais_token_endpoint, - None, - ), - None, - ) - - def _apply_quota_project_id(credentials, quota_project_id): if quota_project_id: credentials = credentials.with_quota_project(quota_project_id) @@ -493,11 +456,6 @@ def default(scopes=None, request=None, quota_project_id=None, default_scopes=Non endpoint. The project ID returned in this case is the one corresponding to the underlying workload identity pool resource if determinable. - - If the environment variable is set to the path of a valid GDCH service - account JSON file (`Google Distributed Cloud Hosted`_), then a GDCH - credential will be returned. The project ID returned is None unless it - is set via `GOOGLE_CLOUD_PROJECT` environment variable. 2. If the `Google Cloud SDK`_ is installed and has application default credentials set they are loaded and returned. @@ -532,8 +490,6 @@ def default(scopes=None, request=None, quota_project_id=None, default_scopes=Non .. _Metadata Service: https://cloud.google.com/compute/docs\ /storing-retrieving-metadata .. _Cloud Run: https://cloud.google.com/run - .. _Google Distributed Cloud Hosted: https://cloud.google.com/blog/topics\ - /hybrid-cloud/announcing-google-distributed-cloud-edge-and-hosted Example:: diff --git a/packages/google-auth/google/oauth2/_client.py b/packages/google-auth/google/oauth2/_client.py index 8831baf27a47..2f4e8474b557 100644 --- a/packages/google-auth/google/oauth2/_client.py +++ b/packages/google-auth/google/oauth2/_client.py @@ -44,13 +44,11 @@ def _handle_error_response(response_data): """Translates an error response into an exception. Args: - response_data (Mapping | str): The decoded response data. + response_data (Mapping): The decoded response data. Raises: google.auth.exceptions.RefreshError: The errors contained in response_data. """ - if isinstance(response_data, six.string_types): - raise exceptions.RefreshError(response_data) try: error_details = "{}: {}".format( response_data["error"], response_data.get("error_description") @@ -81,13 +79,7 @@ def _parse_expiry(response_data): def _token_endpoint_request_no_throw( - request, - token_uri, - body, - access_token=None, - use_json=False, - expected_status_code=http_client.OK, - **kwargs + request, token_uri, body, access_token=None, use_json=False ): """Makes a request to the OAuth 2.0 authorization server's token endpoint. This function doesn't throw on response errors. @@ -101,16 +93,6 @@ def _token_endpoint_request_no_throw( access_token (Optional(str)): The access token needed to make the request. use_json (Optional(bool)): Use urlencoded format or json format for the content type. The default value is False. - expected_status_code (Optional(int)): The expected the status code of - the token response. The default value is 200. We may expect other - status code like 201 for GDCH credentials. - kwargs: Additional arguments passed on to the request method. The - kwargs will be passed to `requests.request` method, see: - https://docs.python-requests.org/en/latest/api/#requests.request. - For example, you can use `cert=("cert_pem_path", "key_pem_path")` - to set up client side SSL certificate, and use - `verify="ca_bundle_path"` to set up the CA certificates for sever - side SSL certificate verification. Returns: Tuple(bool, Mapping[str, str]): A boolean indicating if the request is @@ -130,46 +112,32 @@ def _token_endpoint_request_no_throw( # retry to fetch token for maximum of two times if any internal failure # occurs. while True: - response = request( - method="POST", url=token_uri, headers=headers, body=body, **kwargs - ) + response = request(method="POST", url=token_uri, headers=headers, body=body) response_body = ( response.data.decode("utf-8") if hasattr(response.data, "decode") else response.data ) + response_data = json.loads(response_body) - if response.status == expected_status_code: - # response_body should be a JSON - response_data = json.loads(response_body) + if response.status == http_client.OK: break else: - # For a failed response, response_body could be a string - try: - response_data = json.loads(response_body) - error_desc = response_data.get("error_description") or "" - error_code = response_data.get("error") or "" - if ( - any(e == "internal_failure" for e in (error_code, error_desc)) - and retry < 1 - ): - retry += 1 - continue - except ValueError: - response_data = response_body - return False, response_data - - return response.status == expected_status_code, response_data + error_desc = response_data.get("error_description") or "" + error_code = response_data.get("error") or "" + if ( + any(e == "internal_failure" for e in (error_code, error_desc)) + and retry < 1 + ): + retry += 1 + continue + return response.status == http_client.OK, response_data + + return response.status == http_client.OK, response_data def _token_endpoint_request( - request, - token_uri, - body, - access_token=None, - use_json=False, - expected_status_code=http_client.OK, - **kwargs + request, token_uri, body, access_token=None, use_json=False ): """Makes a request to the OAuth 2.0 authorization server's token endpoint. @@ -182,16 +150,6 @@ def _token_endpoint_request( access_token (Optional(str)): The access token needed to make the request. use_json (Optional(bool)): Use urlencoded format or json format for the content type. The default value is False. - expected_status_code (Optional(int)): The expected the status code of - the token response. The default value is 200. We may expect other - status code like 201 for GDCH credentials. - kwargs: Additional arguments passed on to the request method. The - kwargs will be passed to `requests.request` method, see: - https://docs.python-requests.org/en/latest/api/#requests.request. - For example, you can use `cert=("cert_pem_path", "key_pem_path")` - to set up client side SSL certificate, and use - `verify="ca_bundle_path"` to set up the CA certificates for sever - side SSL certificate verification. Returns: Mapping[str, str]: The JSON-decoded response data. @@ -201,13 +159,7 @@ def _token_endpoint_request( an error. """ response_status_ok, response_data = _token_endpoint_request_no_throw( - request, - token_uri, - body, - access_token=access_token, - use_json=use_json, - expected_status_code=expected_status_code, - **kwargs + request, token_uri, body, access_token=access_token, use_json=use_json ) if not response_status_ok: _handle_error_response(response_data) diff --git a/packages/google-auth/google/oauth2/gdch_credentials.py b/packages/google-auth/google/oauth2/gdch_credentials.py deleted file mode 100644 index e0edbf039856..000000000000 --- a/packages/google-auth/google/oauth2/gdch_credentials.py +++ /dev/null @@ -1,194 +0,0 @@ -# Copyright 2022 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Experimental GDCH credentials support. -""" - -import six -from six.moves import http_client - -from google.auth import _helpers -from google.auth import credentials -from google.auth import exceptions -from google.oauth2 import _client - - -TOKEN_EXCHANGE_TYPE = "urn:ietf:params:oauth:token-type:token-exchange" -ACCESS_TOKEN_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token" -JWT_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:jwt" -SERVICE_ACCOUNT_TOKEN_TYPE = "urn:k8s:params:oauth:token-type:serviceaccount" - - -class ServiceAccountCredentials(credentials.Credentials): - """Credentials for GDCH (`Google Distributed Cloud Hosted`_) for service - account users. - - .. _Google Distributed Cloud Hosted: - https://cloud.google.com/blog/topics/hybrid-cloud/\ - announcing-google-distributed-cloud-edge-and-hosted - - Besides the constructor, a GDCH credential can be created via application - default credentials. - - To do so, user first creates a JSON file of the - following format:: - - { - "type":"gdch_service_account", - "format_version":"v1", - "k8s_ca_cert_path":"", - "k8s_cert_path":"", - "k8s_key_path":"", - "k8s_token_endpoint":"", - "ais_ca_cert_path":"", - "ais_token_endpoint":"" - } - - Here "k8s_*" files are used to request a k8s token from k8s token endpoint - using mutual TLS connection. The k8s token is then sent to AIS token endpoint - to exchange for an AIS token. The AIS token will be used to talk to Google - API services. - - "k8s_ca_cert_path" field is not needed if the k8s server uses well known CA. - "ais_ca_cert_path" field is not needed if the AIS server uses well known CA. - These two fields can be used for testing environments. - - The "format_version" field stands for the format of the JSON file. For now - it is always "v1". - - After the JSON file is created, set `GOOGLE_APPLICATION_CREDENTIALS` environment - variable to the JSON file path, then use the following code to create the - credential:: - - import google.auth - - credential, _ = google.auth.default() - credential = credential.with_audience("") - - The audience denotes the scope the AIS token is requested, for example, it - could be either a k8s cluster or API service. - """ - - def __init__( - self, - k8s_ca_cert_path, - k8s_cert_path, - k8s_key_path, - k8s_token_endpoint, - ais_ca_cert_path, - ais_token_endpoint, - audience, - ): - """ - Args: - k8s_ca_cert_path (str): CA cert path for k8s calls. This field is - useful if the specific k8s server doesn't use well known CA, - for instance, a testing k8s server. If the CA is well known, - you can pass `None` for this parameter. - k8s_cert_path (str): Certificate path for k8s calls - k8s_key_path (str): Key path for k8s calls - k8s_token_endpoint (str): k8s token endpoint url - ais_ca_cert_path (str): CA cert path for AIS token endpoint calls. - This field is useful if the specific AIS token server doesn't - uses well known CA, for instance, a testing AIS server. If the - CA is well known, you can pass `None` for this parameter. - ais_token_endpoint (str): AIS token endpoint url - audience (str): The audience for the requested AIS token. For - example, it could be a k8s cluster or API service. - """ - super(ServiceAccountCredentials, self).__init__() - self._k8s_ca_cert_path = k8s_ca_cert_path - self._k8s_cert_path = k8s_cert_path - self._k8s_key_path = k8s_key_path - self._k8s_token_endpoint = k8s_token_endpoint - self._ais_ca_cert_path = ais_ca_cert_path - self._ais_token_endpoint = ais_token_endpoint - self._audience = audience - - def _make_k8s_token_request(self, request): - k8s_request_body = { - "kind": "TokenRequest", - "apiVersion": "authentication.k8s.io/v1", - "spec": {"audiences": [self._ais_token_endpoint]}, - } - # mTLS connection to k8s token endpoint to get a k8s token. - k8s_response_data = _client._token_endpoint_request( - request, - self._k8s_token_endpoint, - k8s_request_body, - access_token=None, - use_json=True, - expected_status_code=http_client.CREATED, - cert=(self._k8s_cert_path, self._k8s_key_path), - verify=self._k8s_ca_cert_path, - ) - - try: - k8s_token = k8s_response_data["status"]["token"] - return k8s_token - except KeyError as caught_exc: - new_exc = exceptions.RefreshError( - "No access token in k8s token response.", k8s_response_data - ) - six.raise_from(new_exc, caught_exc) - - def _make_ais_token_request(self, k8s_token, request): - # send a request to AIS token point with the k8s token - ais_request_body = { - "grant_type": TOKEN_EXCHANGE_TYPE, - "audience": self._audience, - "requested_token_type": ACCESS_TOKEN_TOKEN_TYPE, - "subject_token": k8s_token, - "subject_token_type": SERVICE_ACCOUNT_TOKEN_TYPE, - } - ais_response_data = _client._token_endpoint_request( - request, - self._ais_token_endpoint, - ais_request_body, - access_token=None, - use_json=True, - verify=self._ais_ca_cert_path, - ) - ais_token, _, ais_expiry, _ = _client._handle_refresh_grant_response( - ais_response_data, None - ) - return ais_token, ais_expiry - - @_helpers.copy_docstring(credentials.Credentials) - def refresh(self, request): - import google.auth.transport.requests - - if not isinstance(request, google.auth.transport.requests.Request): - raise exceptions.RefreshError( - "For GDCH service account credentials, request must be a google.auth.transport.requests.Request object" - ) - - k8s_token = self._make_k8s_token_request(request) - self.token, self.expiry = self._make_ais_token_request(k8s_token, request) - - def with_audience(self, audience): - """Create a copy of GDCH credentials with the specified audience. - - Args: - audience (str): The intended audience for GDCH credentials. - """ - return self.__class__( - self._k8s_ca_cert_path, - self._k8s_cert_path, - self._k8s_key_path, - self._k8s_token_endpoint, - self._ais_ca_cert_path, - self._ais_token_endpoint, - audience, - ) diff --git a/packages/google-auth/tests/data/gdch_service_account.json b/packages/google-auth/tests/data/gdch_service_account.json deleted file mode 100644 index c6c441bfdd35..000000000000 --- a/packages/google-auth/tests/data/gdch_service_account.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "type":"gdch_service_account", - "format_version": "v1", - "k8s_ca_cert_path":"./k8s_ca_cert.pem", - "k8s_cert_path":"./k8s_cert.pem", - "k8s_key_path":"./k8s_key.pem", - "k8s_token_endpoint":"https://k8s_endpoint/api/v1/namespaces/sa-token-test/serviceaccounts/sa-token-user/token", - "ais_ca_cert_path":"./ais_ca_cert.pem", - "ais_token_endpoint":"https://ais_endpoint/sts/v1beta/token" -} diff --git a/packages/google-auth/tests/oauth2/test__client.py b/packages/google-auth/tests/oauth2/test__client.py index 400582fc305f..5485bed84e56 100644 --- a/packages/google-auth/tests/oauth2/test__client.py +++ b/packages/google-auth/tests/oauth2/test__client.py @@ -56,7 +56,7 @@ def test__handle_error_response(): assert excinfo.match(r"help: I\'m alive") -def test__handle_error_response_no_error(): +def test__handle_error_response_non_json(): response_data = {"foo": "bar"} with pytest.raises(exceptions.RefreshError) as excinfo: @@ -65,15 +65,6 @@ def test__handle_error_response_no_error(): assert excinfo.match(r"{\"foo\": \"bar\"}") -def test__handle_error_response_not_json(): - response_data = "this is an error message" - - with pytest.raises(exceptions.RefreshError) as excinfo: - _client._handle_error_response(response_data) - - assert excinfo.match(response_data) - - @mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) def test__parse_expiry(unused_utcnow): result = _client._parse_expiry({"expires_in": 500}) @@ -154,8 +145,6 @@ def test__token_endpoint_request_internal_failure_error(): _client._token_endpoint_request( request, "http://example.com", {"error_description": "internal_failure"} ) - # request should be called twice due to the retry - assert request.call_count == 2 request = make_request( {"error": "internal_failure"}, status=http_client.BAD_REQUEST @@ -165,33 +154,6 @@ def test__token_endpoint_request_internal_failure_error(): _client._token_endpoint_request( request, "http://example.com", {"error": "internal_failure"} ) - # request should be called twice due to the retry - assert request.call_count == 2 - - -def test__token_endpoint_request_string_error(): - response = mock.create_autospec(transport.Response, instance=True) - response.status = http_client.BAD_REQUEST - response.data = "this is an error message" - request = mock.create_autospec(transport.Request) - request.return_value = response - - with pytest.raises(exceptions.RefreshError) as excinfo: - _client._token_endpoint_request(request, "http://example.com", {}) - assert excinfo.match("this is an error message") - - -def test__token_endpoint_request_expected_status_code(): - request = make_request({}, status=http_client.CREATED) - - # It doesn't throw if the response code is the expected one. - _client._token_endpoint_request( - request, "http://example.com", {}, expected_status_code=http_client.CREATED - ) - - # It throws since the default status code is 200 OK, but we are expecting 201 CREATED. - with pytest.raises(exceptions.RefreshError): - _client._token_endpoint_request(request, "http://example.com", {}) def verify_request_params(request, params): diff --git a/packages/google-auth/tests/oauth2/test_gdch_credentials.py b/packages/google-auth/tests/oauth2/test_gdch_credentials.py deleted file mode 100644 index 41aa399af9fe..000000000000 --- a/packages/google-auth/tests/oauth2/test_gdch_credentials.py +++ /dev/null @@ -1,180 +0,0 @@ -# Copyright 2022 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import datetime - -import mock -import pytest # type: ignore -from six.moves import http_client - -from google.auth import exceptions -from google.auth.transport import requests -from google.oauth2 import gdch_credentials - - -class TestCredentials(object): - K8S_CA_CERT_PATH = "./k8s_ca_cert.pem" - K8S_CERT_PATH = "./k8s_cert.pem" - K8S_KEY_PATH = "./k8s_key.pem" - K8S_TOKEN = "k8s_token" - K8S_TOKEN_ENDPOINT = "https://k8s_endpoint/v1/token" - AIS_CA_CERT_PATH = "./ais_ca_cert.pem" - AIS_TOKEN_ENDPOINT = "https://k8s_endpoint/v1/token" - AUDIENCE = "audience_foo" - - @classmethod - def make_credentials(cls): - return gdch_credentials.ServiceAccountCredentials( - cls.K8S_CA_CERT_PATH, - cls.K8S_CERT_PATH, - cls.K8S_KEY_PATH, - cls.K8S_TOKEN_ENDPOINT, - cls.AIS_CA_CERT_PATH, - cls.AIS_TOKEN_ENDPOINT, - cls.AUDIENCE, - ) - - def test_with_audience(self): - creds = self.make_credentials() - assert creds._audience == self.AUDIENCE - - new_creds = creds.with_audience("bar") - assert new_creds._audience == "bar" - - @mock.patch("google.oauth2._client._token_endpoint_request", autospec=True) - def test__make_k8s_token_request(self, token_endpoint_request): - creds = self.make_credentials() - req = requests.Request() - - token_endpoint_request.return_value = { - "status": { - "token": self.K8S_TOKEN, - "expirationTimestamp": "2022-02-22T06:51:46Z", - } - } - assert creds._make_k8s_token_request(req) == self.K8S_TOKEN - token_endpoint_request.assert_called_with( - req, - creds._k8s_token_endpoint, - { - "kind": "TokenRequest", - "apiVersion": "authentication.k8s.io/v1", - "spec": {"audiences": [creds._ais_token_endpoint]}, - }, - None, - True, - http_client.CREATED, - cert=(creds._k8s_cert_path, creds._k8s_key_path), - verify=creds._k8s_ca_cert_path, - ) - - @mock.patch("google.oauth2._client._token_endpoint_request", autospec=True) - def test__make_k8s_token_request_no_token(self, token_endpoint_request): - creds = self.make_credentials() - req = requests.Request() - - token_endpoint_request.return_value = { - "status": {"expirationTimestamp": "2022-02-22T06:51:46Z"} - } - - with pytest.raises(exceptions.RefreshError) as excinfo: - creds._make_k8s_token_request(req) - assert excinfo.match("No access token in k8s token response") - - @mock.patch("google.oauth2._client._token_endpoint_request", autospec=True) - @mock.patch("google.auth._helpers.utcnow", autospec=True) - def test__make_ais_token_request(self, utcnow, token_endpoint_request): - creds = self.make_credentials() - req = requests.Request() - - issue_time = datetime.datetime(2022, 1, 1, 0, 0, 0) - utcnow.return_value = issue_time - expires_in_seconds = 3599 - - token_endpoint_request.return_value = { - "access_token": "ais_token", - "expires_in": expires_in_seconds, - "issued_token_type": "urn:ietf:params:oauth:token-type:access_token", - "token_type": "Bearer", - } - - k8s_token = self.K8S_TOKEN - ais_token, ais_expiry = creds._make_ais_token_request(k8s_token, req) - assert ais_token == "ais_token" - assert ais_expiry == issue_time + datetime.timedelta(seconds=expires_in_seconds) - token_endpoint_request.assert_called_with( - req, - creds._ais_token_endpoint, - { - "grant_type": gdch_credentials.TOKEN_EXCHANGE_TYPE, - "audience": creds._audience, - "requested_token_type": gdch_credentials.ACCESS_TOKEN_TOKEN_TYPE, - "subject_token": k8s_token, - "subject_token_type": gdch_credentials.SERVICE_ACCOUNT_TOKEN_TYPE, - }, - None, - True, - verify=creds._ais_ca_cert_path, - ) - - @mock.patch( - "google.oauth2.gdch_credentials.ServiceAccountCredentials._make_k8s_token_request", - autospec=True, - ) - @mock.patch( - "google.oauth2.gdch_credentials.ServiceAccountCredentials._make_ais_token_request", - autospec=True, - ) - def test_refresh(self, ais_token_request, k8s_token_request): - k8s_token_request.return_value = self.K8S_TOKEN - mock_expiry = mock.Mock() - ais_token_request.return_value = ("ais_token", mock_expiry) - - creds = self.make_credentials() - req = requests.Request() - creds.refresh(req) - - k8s_token_request.assert_called_with(creds, req) - ais_token_request.assert_called_with(creds, self.K8S_TOKEN, req) - assert creds.token == "ais_token" - assert creds.expiry == mock_expiry - - def test_refresh_request_not_requests_type(self): - creds = self.make_credentials() - req = mock.Mock() - - with pytest.raises(exceptions.RefreshError) as excinfo: - creds.refresh(req) - assert excinfo.match( - "request must be a google.auth.transport.requests.Request object" - ) - - @mock.patch( - "google.oauth2.gdch_credentials.ServiceAccountCredentials._make_k8s_token_request", - autospec=True, - ) - @mock.patch( - "google.oauth2.gdch_credentials.ServiceAccountCredentials._make_ais_token_request", - autospec=True, - ) - def test_before_request(self, ais_token_request, k8s_token_request): - ais_token_request.return_value = ("ais_token", mock.Mock()) - - cred = self.make_credentials() - headers = {} - - cred.before_request(requests.Request(), "GET", "https://example.com", headers) - k8s_token_request.assert_called() - ais_token_request.assert_called() - assert headers["authorization"] == "Bearer ais_token" diff --git a/packages/google-auth/tests/test__default.py b/packages/google-auth/tests/test__default.py index ab8bad72edb0..ed64bc723552 100644 --- a/packages/google-auth/tests/test__default.py +++ b/packages/google-auth/tests/test__default.py @@ -28,7 +28,6 @@ from google.auth import external_account from google.auth import identity_pool from google.auth import impersonated_credentials -from google.oauth2 import gdch_credentials from google.oauth2 import service_account import google.oauth2.credentials @@ -51,8 +50,6 @@ CLIENT_SECRETS_FILE = os.path.join(DATA_DIR, "client_secrets.json") -GDCH_SERVICE_ACCOUNT_FILE = os.path.join(DATA_DIR, "gdch_service_account.json") - with open(SERVICE_ACCOUNT_FILE) as fh: SERVICE_ACCOUNT_FILE_DATA = json.load(fh) @@ -640,22 +637,6 @@ def test__get_gcloud_sdk_credentials_no_project_id(load, unused_isfile, get_proj assert get_project_id.called -def test__get_gdch_service_account_credentials_no_format_version(): - with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: - _default._get_gdch_service_account_credentials({}) - assert excinfo.match( - "format_version is not provided or unsupported. Supported version is: v1" - ) - - -def test__get_gdch_service_account_credentials_invalid_format_version(): - with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: - _default._get_gdch_service_account_credentials({"format_version": "v2"}) - assert excinfo.match( - "format_version is not provided or unsupported. Supported version is: v1" - ) - - class _AppIdentityModule(object): """The interface of the App Idenity app engine module. See https://cloud.google.com/appengine/docs/standard/python/refdocs\ @@ -1159,28 +1140,3 @@ def test_default_impersonated_service_account_set_both_scopes_and_default_scopes credentials, _ = _default.default(scopes=scopes, default_scopes=default_scopes) assert credentials._target_scopes == scopes - - -@mock.patch( - "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True -) -@mock.patch("google.auth._default._apply_quota_project_id", autospec=True) -def test_default_gdch_service_account_credentials(apply_quota_project_id, get_adc_path): - get_adc_path.return_value = GDCH_SERVICE_ACCOUNT_FILE - - credentials, _ = _default.default(quota_project_id="project-foo") - - # make sure _apply_quota_project_id is not called since GDCH service account - # credential doesn't inheirt from CredentialsWithQuotaProject. - apply_quota_project_id.assert_not_called() - - assert isinstance(credentials, gdch_credentials.ServiceAccountCredentials) - assert credentials._k8s_ca_cert_path == "./k8s_ca_cert.pem" - assert credentials._k8s_cert_path == "./k8s_cert.pem" - assert credentials._k8s_key_path == "./k8s_key.pem" - assert ( - credentials._k8s_token_endpoint - == "https://k8s_endpoint/api/v1/namespaces/sa-token-test/serviceaccounts/sa-token-user/token" - ) - assert credentials._ais_ca_cert_path == "./ais_ca_cert.pem" - assert credentials._ais_token_endpoint == "https://ais_endpoint/sts/v1beta/token" From 28ae398735255a398ece4a2ca41550f5a272e153 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Wed, 1 Jun 2022 13:39:52 -0700 Subject: [PATCH 570/966] chore: fix docs test and update sys test creds (#1047) --- packages/google-auth/docs/conf.py | 2 +- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/docs/conf.py b/packages/google-auth/docs/conf.py index 652d808bdc7c..b01c7b6cbd87 100644 --- a/packages/google-auth/docs/conf.py +++ b/packages/google-auth/docs/conf.py @@ -75,7 +75,7 @@ # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = "en" # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 151fa1ba546e06f593561959ec5a80b113680d42..15cef6f6b01162407262c8a13df3cc610171d8ee 100644 GIT binary patch literal 10324 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 literal 10324 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 From da087c1e8801d7171c35f7904e80ed58f9ae4fbd Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Mon, 6 Jun 2022 12:09:53 -0700 Subject: [PATCH 571/966] chore: update sys test creds (#1051) * chore: update sys test creds * chore: update sys test creds --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 15cef6f6b01162407262c8a13df3cc610171d8ee..2d894aa4793a8b5ef8fbecf30e67371d25e6f581 100644 GIT binary patch literal 10324 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 literal 10324 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
bs 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 From 8ac818a03902f318b45c3afd963d66a6e93d0f4f Mon Sep 17 00:00:00 2001 From: Dan Lee <71398022+dandhlee@users.noreply.github.com> Date: Mon, 6 Jun 2022 16:29:01 -0400 Subject: [PATCH 572/966] docs: fix changelog header to consistent size (#1046) Co-authored-by: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 86 +++++++++++++++---------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 712b84126708..bc5a2a33d417 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,28 +4,28 @@ [1]: https://pypi.org/project/google-auth/#history -### [2.6.6](https://github.com/googleapis/google-auth-library-python/compare/v2.6.5...v2.6.6) (2022-04-21) +## [2.6.6](https://github.com/googleapis/google-auth-library-python/compare/v2.6.5...v2.6.6) (2022-04-21) ### Bug Fixes * silence TypeError during tear down stage ([#1027](https://github.com/googleapis/google-auth-library-python/issues/1027)) ([952a6aa](https://github.com/googleapis/google-auth-library-python/commit/952a6aad888140c13815aada95f33792e414e061)) -### [2.6.5](https://github.com/googleapis/google-auth-library-python/compare/v2.6.4...v2.6.5) (2022-04-14) +## [2.6.5](https://github.com/googleapis/google-auth-library-python/compare/v2.6.4...v2.6.5) (2022-04-14) ### Bug Fixes * add additional missing import in _default.py ([#1018](https://github.com/googleapis/google-auth-library-python/issues/1018)) ([638331b](https://github.com/googleapis/google-auth-library-python/commit/638331b5b89c807b40c23c1e6333845d9b7e169a)) -### [2.6.4](https://github.com/googleapis/google-auth-library-python/compare/v2.6.3...v2.6.4) (2022-04-12) +## [2.6.4](https://github.com/googleapis/google-auth-library-python/compare/v2.6.3...v2.6.4) (2022-04-12) ### Bug Fixes * fix missing import in _default.py ([#1015](https://github.com/googleapis/google-auth-library-python/issues/1015)) ([63f4e38](https://github.com/googleapis/google-auth-library-python/commit/63f4e38153ded9fe9b51b83a1de74f3b71f73abc)) -### [2.6.3](https://github.com/googleapis/google-auth-library-python/compare/v2.6.2...v2.6.3) (2022-04-06) +## [2.6.3](https://github.com/googleapis/google-auth-library-python/compare/v2.6.2...v2.6.3) (2022-04-06) ### Bug Fixes @@ -34,7 +34,7 @@ * clean up HTTP session and pool during tear down phase ([#1007](https://github.com/googleapis/google-auth-library-python/issues/1007)) ([d057376](https://github.com/googleapis/google-auth-library-python/commit/d057376245283402fe0b772ca138091c05864e5d)) * pin click version and update sys test creds ([#1008](https://github.com/googleapis/google-auth-library-python/issues/1008)) ([ae2804b](https://github.com/googleapis/google-auth-library-python/commit/ae2804bf292b5c8e6f935d2d0751db8fbe95a7b3)) -### [2.6.2](https://github.com/googleapis/google-auth-library-python/compare/v2.6.1...v2.6.2) (2022-03-16) +## [2.6.2](https://github.com/googleapis/google-auth-library-python/compare/v2.6.1...v2.6.2) (2022-03-16) ### Bug Fixes @@ -46,7 +46,7 @@ * let release-please finish the release ([#991](https://github.com/googleapis/google-auth-library-python/issues/991)) ([d2bdc9a](https://github.com/googleapis/google-auth-library-python/commit/d2bdc9a8a23930a01e5b8445e869a135511977cf)) -### [2.6.1](https://github.com/googleapis/google-auth-library-python/compare/v2.6.0...v2.6.1) (2022-02-09) +## [2.6.1](https://github.com/googleapis/google-auth-library-python/compare/v2.6.0...v2.6.1) (2022-02-09) ### Bug Fixes @@ -72,7 +72,7 @@ * ADC can load an impersonated service account credentials. ([#956](https://github.com/googleapis/google-auth-library-python/issues/956)) ([a8eb4c8](https://github.com/googleapis/google-auth-library-python/commit/a8eb4c8693055a3420cfe9c3420aae2bc8cd465a)) -### [2.4.1](https://github.com/googleapis/google-auth-library-python/compare/v2.4.0...v2.4.1) (2022-01-21) +## [2.4.1](https://github.com/googleapis/google-auth-library-python/compare/v2.4.0...v2.4.1) (2022-01-21) ### Bug Fixes @@ -99,7 +99,7 @@ * fix intersphinx link for 'requests-oauthlib' ([#921](https://github.com/googleapis/google-auth-library-python/issues/921)) ([967be4f](https://github.com/googleapis/google-auth-library-python/commit/967be4f4e2a43ba7e240d7acb01b6b992d40e6ec)) * note ValueError in `verify_oauth2_token` ([#928](https://github.com/googleapis/google-auth-library-python/issues/928)) ([82bc5f0](https://github.com/googleapis/google-auth-library-python/commit/82bc5f08111de78a2b475b0310d3f35470680dbe)) -### [2.3.3](https://www.github.com/googleapis/google-auth-library-python/compare/v2.3.2...v2.3.3) (2021-11-01) +## [2.3.3](https://www.github.com/googleapis/google-auth-library-python/compare/v2.3.2...v2.3.3) (2021-11-01) ### Bug Fixes @@ -108,14 +108,14 @@ * fix error in sign_bytes ([#905](https://www.github.com/googleapis/google-auth-library-python/issues/905)) ([ef31284](https://www.github.com/googleapis/google-auth-library-python/commit/ef3128474431b07d1d519209ea61622bc245ce91)) * use 'int.to_bytes' and 'int.from_bytes' for py3 ([#904](https://www.github.com/googleapis/google-auth-library-python/issues/904)) ([bd0ccc5](https://www.github.com/googleapis/google-auth-library-python/commit/bd0ccc5fe77d55f7a19f5278d6b60587c393ee3c)) -### [2.3.2](https://www.github.com/googleapis/google-auth-library-python/compare/v2.3.1...v2.3.2) (2021-10-26) +## [2.3.2](https://www.github.com/googleapis/google-auth-library-python/compare/v2.3.1...v2.3.2) (2021-10-26) ### Bug Fixes * add clock_skew_in_seconds to verify_token functions ([#894](https://www.github.com/googleapis/google-auth-library-python/issues/894)) ([8e95c1e](https://www.github.com/googleapis/google-auth-library-python/commit/8e95c1e458793593972b6b05a355aaeaecd31670)) -### [2.3.1](https://www.github.com/googleapis/google-auth-library-python/compare/v2.3.0...v2.3.1) (2021-10-21) +## [2.3.1](https://www.github.com/googleapis/google-auth-library-python/compare/v2.3.0...v2.3.1) (2021-10-21) ### Bug Fixes @@ -139,7 +139,7 @@ * ADC with impersonated workforce pools ([#877](https://www.github.com/googleapis/google-auth-library-python/issues/877)) ([10bd9fb](https://www.github.com/googleapis/google-auth-library-python/commit/10bd9fbecd462435246afa46fd666a2836cd9e89)) -### [2.2.1](https://www.github.com/googleapis/google-auth-library-python/compare/v2.2.0...v2.2.1) (2021-09-28) +## [2.2.1](https://www.github.com/googleapis/google-auth-library-python/compare/v2.2.0...v2.2.1) (2021-09-28) ### Bug Fixes @@ -167,7 +167,7 @@ * disable warning if quota project id provided to auth.default() ([#856](https://www.github.com/googleapis/google-auth-library-python/issues/856)) ([11ebaeb](https://www.github.com/googleapis/google-auth-library-python/commit/11ebaeb9d7c0862916154cfb810238574507629a)) * rename CLOCK_SKEW and separate client/server user case ([#863](https://www.github.com/googleapis/google-auth-library-python/issues/863)) ([738611b](https://www.github.com/googleapis/google-auth-library-python/commit/738611bd2914f0fd5fa8b49b65f56ef321829c85)) -### [2.0.2](https://www.github.com/googleapis/google-auth-library-python/compare/v2.0.1...v2.0.2) (2021-08-25) +## [2.0.2](https://www.github.com/googleapis/google-auth-library-python/compare/v2.0.1...v2.0.2) (2021-08-25) ### Bug Fixes @@ -175,7 +175,7 @@ * use 'int.to_bytes' rather than deprecated crypto wrapper ([#848](https://www.github.com/googleapis/google-auth-library-python/issues/848)) ([b79b554](https://www.github.com/googleapis/google-auth-library-python/commit/b79b55407b31933c9a8fe6de01478fa00a33fa2b)) * use int.from_bytes ([#846](https://www.github.com/googleapis/google-auth-library-python/issues/846)) ([466aed9](https://www.github.com/googleapis/google-auth-library-python/commit/466aed99f5c2ba15d2036fa21cc83b3f0fc22639)) -### [2.0.1](https://www.github.com/googleapis/google-auth-library-python/compare/v2.0.0...v2.0.1) (2021-08-17) +## [2.0.1](https://www.github.com/googleapis/google-auth-library-python/compare/v2.0.0...v2.0.1) (2021-08-17) ### Bug Fixes @@ -223,7 +223,7 @@ * do not use the GAE APIs on gen2+ runtimes ([#807](https://www.github.com/googleapis/google-auth-library-python/issues/807)) ([7f7d92d](https://www.github.com/googleapis/google-auth-library-python/commit/7f7d92d63ffee91859fc819416af78cef3baf574)) -### [1.33.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.33.0...v1.33.1) (2021-07-20) +## [1.33.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.33.0...v1.33.1) (2021-07-20) ### Bug Fixes @@ -254,7 +254,7 @@ * fix code block formatting in 'user-guide.rst' ([#794](https://www.github.com/googleapis/google-auth-library-python/issues/794)) ([4fd84bd](https://www.github.com/googleapis/google-auth-library-python/commit/4fd84bdf43694af5107dc8c8b443c06ba2f61d2c)) -### [1.32.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.32.0...v1.32.1) (2021-06-30) +## [1.32.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.32.0...v1.32.1) (2021-06-30) ### Bug Fixes @@ -280,7 +280,7 @@ * avoid deleting items while iterating ([#772](https://www.github.com/googleapis/google-auth-library-python/issues/772)) ([a5e6b65](https://www.github.com/googleapis/google-auth-library-python/commit/a5e6b651aa8ad407ce087fe32f40b46925bae527)) -### [1.30.2](https://www.github.com/googleapis/google-auth-library-python/compare/v1.30.1...v1.30.2) (2021-06-03) +## [1.30.2](https://www.github.com/googleapis/google-auth-library-python/compare/v1.30.1...v1.30.2) (2021-06-03) ### Bug Fixes @@ -289,7 +289,7 @@ * enforce constraints during unit tests ([#760](https://www.github.com/googleapis/google-auth-library-python/issues/760)) ([1a6496a](https://www.github.com/googleapis/google-auth-library-python/commit/1a6496abfc17ab781bfa485dc74d0f7dbbe0c44b)), closes [#759](https://www.github.com/googleapis/google-auth-library-python/issues/759) * session object was never used in aiohttp request ([#700](https://www.github.com/googleapis/google-auth-library-python/issues/700)) ([#701](https://www.github.com/googleapis/google-auth-library-python/issues/701)) ([09e0389](https://www.github.com/googleapis/google-auth-library-python/commit/09e0389db72cc9d6c5dde34864cb54d717dc0b92)) -### [1.30.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.30.0...v1.30.1) (2021-05-20) +## [1.30.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.30.0...v1.30.1) (2021-05-20) ### Bug Fixes @@ -316,7 +316,7 @@ * Allow multiple audiences for id_token.verify_token ([#733](https://www.github.com/googleapis/google-auth-library-python/issues/733)) ([56c3946](https://www.github.com/googleapis/google-auth-library-python/commit/56c394680ac6dfc07c611a9eb1e030e32edd4fe1)) -### [1.28.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.28.0...v1.28.1) (2021-04-08) +## [1.28.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.28.0...v1.28.1) (2021-04-08) ### Bug Fixes @@ -336,7 +336,7 @@ * fix unit tests so they can work in g3 ([#714](https://www.github.com/googleapis/google-auth-library-python/issues/714)) ([d80c85f](https://www.github.com/googleapis/google-auth-library-python/commit/d80c85f285ae1a44ddc5a5d94a66e065a79f6d19)) -### [1.27.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.27.0...v1.27.1) (2021-02-26) +## [1.27.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.27.0...v1.27.1) (2021-02-26) ### Bug Fixes @@ -356,7 +356,7 @@ * add pyopenssl as extra dependency ([#697](https://www.github.com/googleapis/google-auth-library-python/issues/697)) ([aeab5d0](https://www.github.com/googleapis/google-auth-library-python/commit/aeab5d07c5538f3d8cce817df24199534572b97d)) -### [1.26.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.26.0...v1.26.1) (2021-02-11) +## [1.26.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.26.0...v1.26.1) (2021-02-11) ### Documentation @@ -413,7 +413,7 @@ * pin 'aoihttp < 3.7.0dev' ([#634](https://www.github.com/googleapis/google-auth-library-python/issues/634)) ([05f9524](https://www.github.com/googleapis/google-auth-library-python/commit/05f95246fab928fe2f445781117eeac8088497fb)) * remove checks for ancient versions of Cryptography ([#596](https://www.github.com/googleapis/google-auth-library-python/issues/596)) ([6407258](https://www.github.com/googleapis/google-auth-library-python/commit/6407258956ec42e3b722418cb7f366e5ae9272ec)), closes [/github.com/googleapis/google-auth-library-python/issues/595#issuecomment-683903062](https://www.github.com/googleapis//github.com/googleapis/google-auth-library-python/issues/595/issues/issuecomment-683903062) -### [1.22.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.22.0...v1.22.1) (2020-10-05) +## [1.22.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.22.0...v1.22.1) (2020-10-05) ### Bug Fixes @@ -427,21 +427,21 @@ * add asyncio based auth flow ([#612](https://www.github.com/googleapis/google-auth-library-python/issues/612)) ([7e15258](https://www.github.com/googleapis/google-auth-library-python/commit/7e1525822d51bd9ce7dffca42d71313e6e776fcd)), closes [#572](https://www.github.com/googleapis/google-auth-library-python/issues/572) -### [1.21.3](https://www.github.com/googleapis/google-auth-library-python/compare/v1.21.2...v1.21.3) (2020-09-22) +## [1.21.3](https://www.github.com/googleapis/google-auth-library-python/compare/v1.21.2...v1.21.3) (2020-09-22) ### Bug Fixes * fix expiry for `to_json()` ([#589](https://www.github.com/googleapis/google-auth-library-python/issues/589)) ([d0e0aba](https://www.github.com/googleapis/google-auth-library-python/commit/d0e0aba0a9f665268ffa1b22d44f4bd7e9b449d6)), closes [/github.com/googleapis/oauth2client/blob/master/oauth2client/client.py#L55](https://www.github.com/googleapis//github.com/googleapis/oauth2client/blob/master/oauth2client/client.py/issues/L55) -### [1.21.2](https://www.github.com/googleapis/google-auth-library-python/compare/v1.21.1...v1.21.2) (2020-09-08) +## [1.21.2](https://www.github.com/googleapis/google-auth-library-python/compare/v1.21.1...v1.21.2) (2020-09-08) ### Bug Fixes * migrate signBlob to iamcredentials.googleapis.com ([#600](https://www.github.com/googleapis/google-auth-library-python/issues/600)) ([694d83f](https://www.github.com/googleapis/google-auth-library-python/commit/694d83fd23c0e8c2fde27136d1b3f8f6db6338a6)) -### [1.21.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.21.0...v1.21.1) (2020-09-03) +## [1.21.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.21.0...v1.21.1) (2020-09-03) ### Bug Fixes @@ -455,7 +455,7 @@ * add GOOGLE_API_USE_CLIENT_CERTIFICATE support ([#592](https://www.github.com/googleapis/google-auth-library-python/issues/592)) ([c0c995f](https://www.github.com/googleapis/google-auth-library-python/commit/c0c995f3de237a2346b59797ee7c4d44ff2a197c)) -### [1.20.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.20.0...v1.20.1) (2020-08-06) +## [1.20.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.20.0...v1.20.1) (2020-08-06) ### Bug Fixes @@ -472,14 +472,14 @@ * Show the transport exception that happened for GCE Metadata ([#474](https://www.github.com/googleapis/google-auth-library-python/issues/474)) ([23919bb](https://www.github.com/googleapis/google-auth-library-python/commit/23919bb60e5f9d9b73644e9a2e127d4d1dd68e8c)) * **packaging:** add support for Python 3.8 ([#569](https://www.github.com/googleapis/google-auth-library-python/issues/569)) ([1aad54a](https://www.github.com/googleapis/google-auth-library-python/commit/1aad54af6b1d5da73d7471cdbfaf0d0b37c5fde6)), closes [#568](https://www.github.com/googleapis/google-auth-library-python/issues/568) -### [1.19.2](https://www.github.com/googleapis/google-auth-library-python/compare/v1.19.1...v1.19.2) (2020-07-17) +## [1.19.2](https://www.github.com/googleapis/google-auth-library-python/compare/v1.19.1...v1.19.2) (2020-07-17) ### Bug fixes * Revert "fix: migrate signBlob to iamcredentials.googleapis.com" ([#563](https://www.github.com/googleapis/google-auth-library-python/issues/563)) ([a48b5b](https://www.github.com/googleapis/google-auth-library-python/commit/a48b5b9135b30ff06f1fe18dd9dbe92ffcf3a272)) -### [1.19.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.19.0...v1.19.1) (2020-07-15) +## [1.19.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.19.0...v1.19.1) (2020-07-15) ### Bug Fixes @@ -516,14 +516,14 @@ * no warning if quota_project_id is given ([#537](https://www.github.com/googleapis/google-auth-library-python/issues/537)) ([f30b45a](https://www.github.com/googleapis/google-auth-library-python/commit/f30b45a9b2f824c494724548732c5ce838218c30)) -### [1.17.2](https://www.github.com/googleapis/google-auth-library-python/compare/v1.17.1...v1.17.2) (2020-06-12) +## [1.17.2](https://www.github.com/googleapis/google-auth-library-python/compare/v1.17.1...v1.17.2) (2020-06-12) ### Bug Fixes * **dependencies:** Further restrict RSA versions ([#532](https://www.github.com/googleapis/google-auth-library-python/issues/532)) ([46677a0](https://www.github.com/googleapis/google-auth-library-python/commit/46677a0cb3bde6622be10061bc61daaff7a0aaca)), closes [#528](https://www.github.com/googleapis/google-auth-library-python/issues/528) -### [1.17.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.17.0...v1.17.1) (2020-06-11) +## [1.17.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.17.0...v1.17.1) (2020-06-11) ### Bug Fixes @@ -537,7 +537,7 @@ * add quota_project_id to service accounts; add with_quota_project methods ([#519](https://www.github.com/googleapis/google-auth-library-python/issues/519)) ([b12488c](https://www.github.com/googleapis/google-auth-library-python/commit/b12488cf552888299425c8009ea075511627cf08)) -### [1.16.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.16.0...v1.16.1) (2020-06-04) +## [1.16.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.16.0...v1.16.1) (2020-06-04) ### Bug Fixes @@ -569,21 +569,21 @@ * signBytes for impersonated credentials ([#506](https://www.github.com/googleapis/google-auth-library-python/issues/506)) ([ca8d98a](https://www.github.com/googleapis/google-auth-library-python/commit/ca8d98ab2e5277e53ab8df78beb1e75cdf5321e3)), closes [#338](https://www.github.com/googleapis/google-auth-library-python/issues/338) -### [1.14.3](https://www.github.com/googleapis/google-auth-library-python/compare/v1.14.2...v1.14.3) (2020-05-11) +## [1.14.3](https://www.github.com/googleapis/google-auth-library-python/compare/v1.14.2...v1.14.3) (2020-05-11) ### Bug Fixes * catch exceptions.RefreshError ([#508](https://www.github.com/googleapis/google-auth-library-python/issues/508)) ([3d672e9](https://www.github.com/googleapis/google-auth-library-python/commit/3d672e9cddd9e8c4946290ab9f90ca9009b8be69)) -### [1.14.2](https://www.github.com/googleapis/google-auth-library-python/compare/v1.14.1...v1.14.2) (2020-05-07) +## [1.14.2](https://www.github.com/googleapis/google-auth-library-python/compare/v1.14.1...v1.14.2) (2020-05-07) ### Bug Fixes * support string type response.data ([#504](https://www.github.com/googleapis/google-auth-library-python/issues/504)) ([9b7228e](https://www.github.com/googleapis/google-auth-library-python/commit/9b7228ec849e311bcb4007ad3e23cf2f1e54a721)) -### [1.14.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.14.0...v1.14.1) (2020-04-21) +## [1.14.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.14.0...v1.14.1) (2020-04-21) ### Bug Fixes @@ -597,7 +597,7 @@ * add default client cert source util ([#486](https://www.github.com/googleapis/google-auth-library-python/issues/486)) ([ed41b49](https://www.github.com/googleapis/google-auth-library-python/commit/ed41b49e9d7ba7402b27107b7aa47eed06ac6c55)) -### [1.13.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.13.0...v1.13.1) (2020-04-01) +## [1.13.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.13.0...v1.13.1) (2020-04-01) ### Bug Fixes @@ -629,7 +629,7 @@ * don't use threads for gRPC AuthMetadataPlugin ([#467](https://www.github.com/googleapis/google-auth-library-python/issues/467)) ([ee373f8](https://www.github.com/googleapis/google-auth-library-python/commit/ee373f88b512a38e791a1c085452c6c6da501eb6)) * make ThreadPoolExecutor a class var ([#461](https://www.github.com/googleapis/google-auth-library-python/issues/461)) ([b526473](https://www.github.com/googleapis/google-auth-library-python/commit/b5264730603947295cc97ecff2f6aef84aa3d6e9)) -### [1.11.3](https://www.github.com/googleapis/google-auth-library-python/compare/v1.11.2...v1.11.3) (2020-03-13) +## [1.11.3](https://www.github.com/googleapis/google-auth-library-python/compare/v1.11.2...v1.11.3) (2020-03-13) ### Bug Fixes @@ -637,14 +637,14 @@ * fix the scopes so test can pass for a local run ([#450](https://www.github.com/googleapis/google-auth-library-python/issues/450)) ([b2dd77f](https://www.github.com/googleapis/google-auth-library-python/commit/b2dd77fe4a538e1d165fc9d859c9a299f6832cda)) * only add IAM scope to credentials that can change scopes ([#451](https://www.github.com/googleapis/google-auth-library-python/issues/451)) ([82e224b](https://www.github.com/googleapis/google-auth-library-python/commit/82e224b0854950a5607cd028edbcbcdc3e9e6505)) -### [1.11.2](https://www.github.com/googleapis/google-auth-library-python/compare/v1.11.1...v1.11.2) (2020-02-14) +## [1.11.2](https://www.github.com/googleapis/google-auth-library-python/compare/v1.11.1...v1.11.2) (2020-02-14) ### Reverts * Revert "fix: update `_GOOGLE_OAUTH2_CERTS_URL` (#365)" (#444) ([901c259](https://www.github.com/googleapis/google-auth-library-python/commit/901c259b1764f5a305a542cbae14d926ba7a57db)), closes [#365](https://www.github.com/googleapis/google-auth-library-python/issues/365) [#444](https://www.github.com/googleapis/google-auth-library-python/issues/444) -### [1.11.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.11.0...v1.11.1) (2020-02-13) +## [1.11.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.11.0...v1.11.1) (2020-02-13) ### Bug Fixes @@ -660,14 +660,14 @@ * add non-None default timeout to AuthorizedSession.request() ([#435](https://www.github.com/googleapis/google-auth-library-python/issues/435)) ([d274a3a](https://www.github.com/googleapis/google-auth-library-python/commit/d274a3a2b3f913bc2cab4ca51f9c7fdef94b8f31)), closes [#434](https://www.github.com/googleapis/google-auth-library-python/issues/434) [googleapis/google-cloud-python#10182](https://www.github.com/googleapis/google-cloud-python/issues/10182) * distinguish transport and execution time timeouts ([#424](https://www.github.com/googleapis/google-auth-library-python/issues/424)) ([52a733d](https://www.github.com/googleapis/google-auth-library-python/commit/52a733d604528fa86d05321bb74241a43aea4211)), closes [#423](https://github.com/googleapis/google-auth-library-python/issues/423) -### [1.10.2](https://www.github.com/googleapis/google-auth-library-python/compare/v1.10.1...v1.10.2) (2020-01-18) +## [1.10.2](https://www.github.com/googleapis/google-auth-library-python/compare/v1.10.1...v1.10.2) (2020-01-18) ### Bug Fixes * make collections import compatible across Python versions ([#419](https://www.github.com/googleapis/google-auth-library-python/issues/419)) ([c5a3395](https://www.github.com/googleapis/google-auth-library-python/commit/c5a3395b8781e14c4566cf0e476b234d6a1c1224)), closes [#418](https://www.github.com/googleapis/google-auth-library-python/issues/418) -### [1.10.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.10.0...v1.10.1) (2020-01-10) +## [1.10.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.10.0...v1.10.1) (2020-01-10) ### Bug Fixes @@ -689,14 +689,14 @@ * add timeout parameter to `AuthorizedSession.request()` ([#406](https://www.github.com/googleapis/google-auth-library-python/issues/406)) ([d86d7b8](https://www.github.com/googleapis/google-auth-library-python/commit/d86d7b8c43df152765c7fc59a54015361b46dcde)) -### [1.8.2](https://www.github.com/googleapis/google-auth-library-python/compare/v1.8.1...v1.8.2) (2019-12-11) +## [1.8.2](https://www.github.com/googleapis/google-auth-library-python/compare/v1.8.1...v1.8.2) (2019-12-11) ### Bug Fixes * revert "feat: send quota project id in x-goog-user-project header for OAuth2 credentials ([#400](https://www.github.com/googleapis/google-auth-library-python/issues/400))" ([#407](https://www.github.com/googleapis/google-auth-library-python/issues/407)) ([25ea942](https://www.github.com/googleapis/google-auth-library-python/commit/25ea942cef4378ff22adf235dd1baf1ca0d595f8)) -### [1.8.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.8.0...v1.8.1) (2019-12-09) +## [1.8.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.8.0...v1.8.1) (2019-12-09) ### Bug Fixes @@ -712,7 +712,7 @@ * add timeout to AuthorizedSession.request() ([#397](https://www.github.com/googleapis/google-auth-library-python/issues/397)) ([381dd40](https://www.github.com/googleapis/google-auth-library-python/commit/381dd400911d29926ffbf04e0f2ba53ef7bb997e)) * send quota project id in x-goog-user-project header for OAuth2 credentials ([#400](https://www.github.com/googleapis/google-auth-library-python/issues/400)) ([ab3dc1e](https://www.github.com/googleapis/google-auth-library-python/commit/ab3dc1e26f5240ea3456de364c7c5cb8f40f9583)) -### [1.7.2](https://www.github.com/googleapis/google-auth-library-python/compare/v1.7.1...v1.7.2) (2019-12-02) +## [1.7.2](https://www.github.com/googleapis/google-auth-library-python/compare/v1.7.1...v1.7.2) (2019-12-02) ### Bug Fixes @@ -720,7 +720,7 @@ * in token endpoint request, do not decode the response data if it is not encoded ([#393](https://www.github.com/googleapis/google-auth-library-python/issues/393)) ([3b5d3e2](https://www.github.com/googleapis/google-auth-library-python/commit/3b5d3e2192ce0cdc97854a1d70d5e382e454275c)) * make gRPC auth plugin non-blocking + add default timeout value for requests transport ([#390](https://www.github.com/googleapis/google-auth-library-python/issues/390)) ([0c33e9c](https://www.github.com/googleapis/google-auth-library-python/commit/0c33e9c0fe4f87fa46c8f1a5afe725a467ac5fcc)), closes [#351](https://www.github.com/googleapis/google-auth-library-python/issues/351) -### [1.7.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.7.0...v1.7.1) (2019-11-13) +## [1.7.1](https://www.github.com/googleapis/google-auth-library-python/compare/v1.7.0...v1.7.1) (2019-11-13) ### Bug Fixes From 62c90b8f5b816f360fb06403a4b87c4b375cbf2e Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Mon, 6 Jun 2022 16:49:17 -0400 Subject: [PATCH 573/966] chore: test minimum dependencies in python 3.7 (#1049) Co-authored-by: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> --- packages/google-auth/testing/constraints-3.7.txt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/google-auth/testing/constraints-3.7.txt b/packages/google-auth/testing/constraints-3.7.txt index e69de29bb2d1..6c4dd2e8cbc7 100644 --- a/packages/google-auth/testing/constraints-3.7.txt +++ b/packages/google-auth/testing/constraints-3.7.txt @@ -0,0 +1,13 @@ +# This constraints file is used to check that lower bounds +# are correct in setup.py +# List *all* library dependencies and extras in this file. +# Pin the version to the lower bound. +# +# e.g., if setup.py has "foo >= 1.14.0, < 2.0.0dev", +# Then this file should have foo==1.14.0 +cachetools==2.0.0 +pyasn1-modules==0.2.1 +setuptools==40.3.0 +rsa==3.1.4 +aiohttp==3.6.2 +requests==2.20.0 From 466f9f9473f182f3682d8f777b8846ff4cf455c9 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Tue, 7 Jun 2022 13:27:57 -0700 Subject: [PATCH 574/966] chore: update sys test creds (#1053) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 2d894aa4793a8b5ef8fbecf30e67371d25e6f581..86f8b6e50ba88e4a3f13682b46ac275da056750d 100644 GIT binary patch literal 10324 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 literal 10324 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 From bb9131ab7196b4ead336363ab9e7513d9acd44a5 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Tue, 7 Jun 2022 14:03:54 -0700 Subject: [PATCH 575/966] feat: add experimental enterprise cert support (#1052) * feat: add experimental enterprise cert support * fix test issue * resolve comments --- packages/google-auth/.gitignore | 4 +- .../auth/transport/_custom_tls_signer.py | 235 ++++++++++++++++++ .../google/auth/transport/requests.py | 59 +++++ packages/google-auth/noxfile.py | 1 + packages/google-auth/setup.py | 3 + .../tests/data/enterprise_cert_invalid.json | 3 + .../tests/data/enterprise_cert_valid.json | 6 + .../transport/test__custom_tls_signer.py | 234 +++++++++++++++++ .../tests/transport/test_requests.py | 38 +++ 9 files changed, 582 insertions(+), 1 deletion(-) create mode 100644 packages/google-auth/google/auth/transport/_custom_tls_signer.py create mode 100644 packages/google-auth/tests/data/enterprise_cert_invalid.json create mode 100644 packages/google-auth/tests/data/enterprise_cert_valid.json create mode 100644 packages/google-auth/tests/transport/test__custom_tls_signer.py diff --git a/packages/google-auth/.gitignore b/packages/google-auth/.gitignore index ca0c0742c42c..a5c5f84141e0 100644 --- a/packages/google-auth/.gitignore +++ b/packages/google-auth/.gitignore @@ -45,4 +45,6 @@ pytype_output/ .python-version .DS_Store cert_path -key_path \ No newline at end of file +key_path +env/ +.vscode/ \ No newline at end of file diff --git a/packages/google-auth/google/auth/transport/_custom_tls_signer.py b/packages/google-auth/google/auth/transport/_custom_tls_signer.py new file mode 100644 index 000000000000..22a510daadd3 --- /dev/null +++ b/packages/google-auth/google/auth/transport/_custom_tls_signer.py @@ -0,0 +1,235 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Code for configuring client side TLS to offload the signing operation to +signing libraries. +""" + +import ctypes +import json +import logging +import os +import sys + +import cffi # type: ignore +import six + +from google.auth import exceptions + +_LOGGER = logging.getLogger(__name__) + +# C++ offload lib requires google-auth lib to provide the following callback: +# using SignFunc = int (*)(unsigned char *sig, size_t *sig_len, +# const unsigned char *tbs, size_t tbs_len) +# The bytes to be signed and the length are provided via `tbs` and `tbs_len`, +# the callback computes the signature, and write the signature and its length +# into `sig` and `sig_len`. +# If the signing is successful, the callback returns 1, otherwise it returns 0. +SIGN_CALLBACK_CTYPE = ctypes.CFUNCTYPE( + ctypes.c_int, # return type + ctypes.POINTER(ctypes.c_ubyte), # sig + ctypes.POINTER(ctypes.c_size_t), # sig_len + ctypes.POINTER(ctypes.c_ubyte), # tbs + ctypes.c_size_t, # tbs_len +) + + +# Cast SSL_CTX* to void* +def _cast_ssl_ctx_to_void_p(ssl_ctx): + return ctypes.cast(int(cffi.FFI().cast("intptr_t", ssl_ctx)), ctypes.c_void_p) + + +# Load offload library and set up the function types. +def load_offload_lib(offload_lib_path): + _LOGGER.debug("loading offload library from %s", offload_lib_path) + + # winmode parameter is only available for python 3.8+. + lib = ( + ctypes.CDLL(offload_lib_path, winmode=0) + if sys.version_info >= (3, 8) and os.name == "nt" + else ctypes.CDLL(offload_lib_path) + ) + + # Set up types for: + # int ConfigureSslContext(SignFunc sign_func, const char *cert, SSL_CTX *ctx) + lib.ConfigureSslContext.argtypes = [ + SIGN_CALLBACK_CTYPE, + ctypes.c_char_p, + ctypes.c_void_p, + ] + lib.ConfigureSslContext.restype = ctypes.c_int + + return lib + + +# Load signer library and set up the function types. +# See: https://github.com/googleapis/enterprise-certificate-proxy/blob/main/cshared/main.go +def load_signer_lib(signer_lib_path): + _LOGGER.debug("loading signer library from %s", signer_lib_path) + + # winmode parameter is only available for python 3.8+. + lib = ( + ctypes.CDLL(signer_lib_path, winmode=0) + if sys.version_info >= (3, 8) and os.name == "nt" + else ctypes.CDLL(signer_lib_path) + ) + + # Set up types for: + # func GetCertPemForPython(configFilePath *C.char, certHolder *byte, certHolderLen int) + lib.GetCertPemForPython.argtypes = [ctypes.c_char_p, ctypes.c_char_p, ctypes.c_int] + # Returns: certLen + lib.GetCertPemForPython.restype = ctypes.c_int + + # Set up types for: + # func SignForPython(configFilePath *C.char, digest *byte, digestLen int, + # sigHolder *byte, sigHolderLen int) + lib.SignForPython.argtypes = [ + ctypes.c_char_p, + ctypes.c_char_p, + ctypes.c_int, + ctypes.c_char_p, + ctypes.c_int, + ] + # Returns: the signature length + lib.SignForPython.restype = ctypes.c_int + + return lib + + +# Computes SHA256 hash. +def _compute_sha256_digest(to_be_signed, to_be_signed_len): + from cryptography.hazmat.primitives import hashes + + data = ctypes.string_at(to_be_signed, to_be_signed_len) + hash = hashes.Hash(hashes.SHA256()) + hash.update(data) + return hash.finalize() + + +# Create the signing callback. The actual signing work is done by the +# `SignForPython` method from the signer lib. +def get_sign_callback(signer_lib, config_file_path): + def sign_callback(sig, sig_len, tbs, tbs_len): + _LOGGER.debug("calling sign callback...") + + digest = _compute_sha256_digest(tbs, tbs_len) + digestArray = ctypes.c_char * len(digest) + + # reserve 2000 bytes for the signature, shoud be more then enough. + # RSA signature is 256 bytes, EC signature is 70~72. + sig_holder_len = 2000 + sig_holder = ctypes.create_string_buffer(sig_holder_len) + + signature_len = signer_lib.SignForPython( + config_file_path.encode(), # configFilePath + digestArray.from_buffer(bytearray(digest)), # digest + len(digest), # digestLen + sig_holder, # sigHolder + sig_holder_len, # sigHolderLen + ) + + if signature_len == 0: + # signing failed, return 0 + return 0 + + sig_len[0] = signature_len + bs = bytearray(sig_holder) + for i in range(signature_len): + sig[i] = bs[i] + + return 1 + + return SIGN_CALLBACK_CTYPE(sign_callback) + + +# Obtain the certificate bytes by calling the `GetCertPemForPython` method from +# the signer lib. The method is called twice, the first time is to compute the +# cert length, then we create a buffer to hold the cert, and call it again to +# fill the buffer. +def get_cert(signer_lib, config_file_path): + # First call to calculate the cert length + cert_len = signer_lib.GetCertPemForPython( + config_file_path.encode(), # configFilePath + None, # certHolder + 0, # certHolderLen + ) + if cert_len == 0: + raise exceptions.MutualTLSChannelError("failed to get certificate") + + # Then we create an array to hold the cert, and call again to fill the cert + cert_holder = ctypes.create_string_buffer(cert_len) + signer_lib.GetCertPemForPython( + config_file_path.encode(), # configFilePath + cert_holder, # certHolder + cert_len, # certHolderLen + ) + return bytes(cert_holder) + + +class CustomTlsSigner(object): + def __init__(self, enterprise_cert_file_path): + """ + This class loads the offload and signer library, and calls APIs from + these libraries to obtain the cert and a signing callback, and attach + them to SSL context. The cert and the signing callback will be used + for client authentication in TLS handshake. + + Args: + enterprise_cert_file_path (str): the path to a enterprise cert JSON + file. The file should contain the following field: + + { + "libs": { + "signer_library": "...", + "offload_library": "..." + } + } + """ + self._enterprise_cert_file_path = enterprise_cert_file_path + self._cert = None + self._sign_callback = None + + def load_libraries(self): + try: + with open(self._enterprise_cert_file_path, "r") as f: + enterprise_cert_json = json.load(f) + libs = enterprise_cert_json["libs"] + signer_library = libs["signer_library"] + offload_library = libs["offload_library"] + except (KeyError, ValueError) as caught_exc: + new_exc = exceptions.MutualTLSChannelError( + "enterprise cert file is invalid", caught_exc + ) + six.raise_from(new_exc, caught_exc) + self._offload_lib = load_offload_lib(offload_library) + self._signer_lib = load_signer_lib(signer_library) + + def set_up_custom_key(self): + # We need to keep a reference of the cert and sign callback so it won't + # be garbage collected, otherwise it will crash when used by signer lib. + self._cert = get_cert(self._signer_lib, self._enterprise_cert_file_path) + self._sign_callback = get_sign_callback( + self._signer_lib, self._enterprise_cert_file_path + ) + + def attach_to_ssl_context(self, ctx): + # In the TLS handshake, the signing operation will be done by the + # sign_callback. + if not self._offload_lib.ConfigureSslContext( + self._sign_callback, + ctypes.c_char_p(self._cert), + _cast_ssl_ctx_to_void_p(ctx._ctx._context), + ): + raise exceptions.MutualTLSChannelError("failed to configure SSL context") diff --git a/packages/google-auth/google/auth/transport/requests.py b/packages/google-auth/google/auth/transport/requests.py index f4a7c65d7571..2c746f8d72f7 100644 --- a/packages/google-auth/google/auth/transport/requests.py +++ b/packages/google-auth/google/auth/transport/requests.py @@ -245,6 +245,65 @@ def proxy_manager_for(self, *args, **kwargs): return super(_MutualTlsAdapter, self).proxy_manager_for(*args, **kwargs) +class _MutualTlsOffloadAdapter(requests.adapters.HTTPAdapter): + """ + A TransportAdapter that enables mutual TLS and offloads the client side + signing operation to the signing library. + + Args: + enterprise_cert_file_path (str): the path to a enterprise cert JSON + file. The file should contain the following field: + + { + "libs": { + "signer_library": "...", + "offload_library": "..." + } + } + + Raises: + ImportError: if certifi or pyOpenSSL is not installed + google.auth.exceptions.MutualTLSChannelError: If mutual TLS channel + creation failed for any reason. + """ + + def __init__(self, enterprise_cert_file_path): + import certifi + import urllib3.contrib.pyopenssl + + from google.auth.transport import _custom_tls_signer + + # Call inject_into_urllib3 to activate certificate checking. See the + # following links for more info: + # (1) doc: https://github.com/urllib3/urllib3/blob/cb9ebf8aac5d75f64c8551820d760b72b619beff/src/urllib3/contrib/pyopenssl.py#L31-L32 + # (2) mTLS example: https://github.com/urllib3/urllib3/issues/474#issuecomment-253168415 + urllib3.contrib.pyopenssl.inject_into_urllib3() + + self.signer = _custom_tls_signer.CustomTlsSigner(enterprise_cert_file_path) + self.signer.load_libraries() + self.signer.set_up_custom_key() + + poolmanager = create_urllib3_context() + poolmanager.load_verify_locations(cafile=certifi.where()) + self.signer.attach_to_ssl_context(poolmanager) + self._ctx_poolmanager = poolmanager + + proxymanager = create_urllib3_context() + proxymanager.load_verify_locations(cafile=certifi.where()) + self.signer.attach_to_ssl_context(proxymanager) + self._ctx_proxymanager = proxymanager + + super(_MutualTlsOffloadAdapter, self).__init__() + + def init_poolmanager(self, *args, **kwargs): + kwargs["ssl_context"] = self._ctx_poolmanager + super(_MutualTlsOffloadAdapter, self).init_poolmanager(*args, **kwargs) + + def proxy_manager_for(self, *args, **kwargs): + kwargs["ssl_context"] = self._ctx_proxymanager + return super(_MutualTlsOffloadAdapter, self).proxy_manager_for(*args, **kwargs) + + class AuthorizedSession(requests.Session): """A Requests Session class with credentials. diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index 937d35d6982e..7221315b0145 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -118,6 +118,7 @@ def unit_prev_versions(session): "--cov=google.oauth2", "--cov=tests", "tests", + "--ignore=tests/transport/test__custom_tls_signer.py", # enterprise cert is for python 3.6+ ) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 22f627b99139..1fce9743e2c0 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -38,6 +38,9 @@ ], "pyopenssl": "pyopenssl>=20.0.0", "reauth": "pyu2f>=0.1.5", + # Enterprise cert only works for OpenSSL 1.1.1. Newer versions of these + # dependencies are built with OpenSSL 3.0 so we need to fix the version. + "enterprise_cert": ["cryptography==36.0.2", "pyopenssl==22.0.0"], } with io.open("README.rst", "r") as fh: diff --git a/packages/google-auth/tests/data/enterprise_cert_invalid.json b/packages/google-auth/tests/data/enterprise_cert_invalid.json new file mode 100644 index 000000000000..4715a590a1ee --- /dev/null +++ b/packages/google-auth/tests/data/enterprise_cert_invalid.json @@ -0,0 +1,3 @@ +{ + "libs": {} +} \ No newline at end of file diff --git a/packages/google-auth/tests/data/enterprise_cert_valid.json b/packages/google-auth/tests/data/enterprise_cert_valid.json new file mode 100644 index 000000000000..de4b2d009392 --- /dev/null +++ b/packages/google-auth/tests/data/enterprise_cert_valid.json @@ -0,0 +1,6 @@ +{ + "libs": { + "signer_library": "/path/to/signer/lib", + "offload_library": "/path/to/offload/lib" + } +} \ No newline at end of file diff --git a/packages/google-auth/tests/transport/test__custom_tls_signer.py b/packages/google-auth/tests/transport/test__custom_tls_signer.py new file mode 100644 index 000000000000..5836b325addf --- /dev/null +++ b/packages/google-auth/tests/transport/test__custom_tls_signer.py @@ -0,0 +1,234 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import base64 +import ctypes +import os + +import mock +import pytest # type: ignore +from requests.packages.urllib3.util.ssl_ import create_urllib3_context # type: ignore +import urllib3.contrib.pyopenssl # type: ignore + +from google.auth import exceptions +from google.auth.transport import _custom_tls_signer + +urllib3.contrib.pyopenssl.inject_into_urllib3() + +FAKE_ENTERPRISE_CERT_FILE_PATH = "/path/to/enterprise/cert/file" +ENTERPRISE_CERT_FILE = os.path.join( + os.path.dirname(__file__), "../data/enterprise_cert_valid.json" +) +INVALID_ENTERPRISE_CERT_FILE = os.path.join( + os.path.dirname(__file__), "../data/enterprise_cert_invalid.json" +) + + +def test_load_offload_lib(): + with mock.patch("ctypes.CDLL", return_value=mock.MagicMock()): + lib = _custom_tls_signer.load_offload_lib("/path/to/offload/lib") + + assert lib.ConfigureSslContext.argtypes == [ + _custom_tls_signer.SIGN_CALLBACK_CTYPE, + ctypes.c_char_p, + ctypes.c_void_p, + ] + assert lib.ConfigureSslContext.restype == ctypes.c_int + + +def test_load_signer_lib(): + with mock.patch("ctypes.CDLL", return_value=mock.MagicMock()): + lib = _custom_tls_signer.load_signer_lib("/path/to/signer/lib") + + assert lib.SignForPython.restype == ctypes.c_int + assert lib.SignForPython.argtypes == [ + ctypes.c_char_p, + ctypes.c_char_p, + ctypes.c_int, + ctypes.c_char_p, + ctypes.c_int, + ] + + assert lib.GetCertPemForPython.restype == ctypes.c_int + assert lib.GetCertPemForPython.argtypes == [ + ctypes.c_char_p, + ctypes.c_char_p, + ctypes.c_int, + ] + + +def test__compute_sha256_digest(): + to_be_signed = ctypes.create_string_buffer(b"foo") + sig = _custom_tls_signer._compute_sha256_digest(to_be_signed, 4) + + assert ( + base64.b64encode(sig).decode() == "RG5gyEH8CAAh3lxgbt2PLPAHPO8p6i9+cn5dqHfUUYM=" + ) + + +def test_get_sign_callback(): + # mock signer lib's SignForPython function + mock_sig_len = 10 + mock_signer_lib = mock.MagicMock() + mock_signer_lib.SignForPython.return_value = mock_sig_len + + # create a sign callback. The callback calls signer lib's SignForPython method + sign_callback = _custom_tls_signer.get_sign_callback( + mock_signer_lib, FAKE_ENTERPRISE_CERT_FILE_PATH + ) + + # mock the parameters used to call the sign callback + to_be_signed = ctypes.POINTER(ctypes.c_ubyte)() + to_be_signed_len = 4 + returned_sig_array = ctypes.c_ubyte() + mock_sig_array = ctypes.byref(returned_sig_array) + returned_sign_len = ctypes.c_ulong() + mock_sig_len_array = ctypes.byref(returned_sign_len) + + # call the callback, make sure the signature len is returned via mock_sig_len_array[0] + assert sign_callback( + mock_sig_array, mock_sig_len_array, to_be_signed, to_be_signed_len + ) + assert returned_sign_len.value == mock_sig_len + + +def test_get_sign_callback_failed_to_sign(): + # mock signer lib's SignForPython function. Set the sig len to be 0 to + # indicate the signing failed. + mock_sig_len = 0 + mock_signer_lib = mock.MagicMock() + mock_signer_lib.SignForPython.return_value = mock_sig_len + + # create a sign callback. The callback calls signer lib's SignForPython method + sign_callback = _custom_tls_signer.get_sign_callback( + mock_signer_lib, FAKE_ENTERPRISE_CERT_FILE_PATH + ) + + # mock the parameters used to call the sign callback + to_be_signed = ctypes.POINTER(ctypes.c_ubyte)() + to_be_signed_len = 4 + returned_sig_array = ctypes.c_ubyte() + mock_sig_array = ctypes.byref(returned_sig_array) + returned_sign_len = ctypes.c_ulong() + mock_sig_len_array = ctypes.byref(returned_sign_len) + sign_callback(mock_sig_array, mock_sig_len_array, to_be_signed, to_be_signed_len) + + # sign callback should return 0 + assert not sign_callback( + mock_sig_array, mock_sig_len_array, to_be_signed, to_be_signed_len + ) + + +def test_get_cert_no_cert(): + # mock signer lib's GetCertPemForPython function to return 0 to indicts + # the cert doesn't exit (cert len = 0) + mock_signer_lib = mock.MagicMock() + mock_signer_lib.GetCertPemForPython.return_value = 0 + + # call the get cert method + with pytest.raises(exceptions.MutualTLSChannelError) as excinfo: + _custom_tls_signer.get_cert(mock_signer_lib, FAKE_ENTERPRISE_CERT_FILE_PATH) + + assert excinfo.match("failed to get certificate") + + +def test_get_cert(): + # mock signer lib's GetCertPemForPython function + mock_cert_len = 10 + mock_signer_lib = mock.MagicMock() + mock_signer_lib.GetCertPemForPython.return_value = mock_cert_len + + # call the get cert method + mock_cert = _custom_tls_signer.get_cert( + mock_signer_lib, FAKE_ENTERPRISE_CERT_FILE_PATH + ) + + # make sure the signer lib's GetCertPemForPython is called twice, and the + # mock_cert has length mock_cert_len + assert mock_signer_lib.GetCertPemForPython.call_count == 2 + assert len(mock_cert) == mock_cert_len + + +def test_custom_tls_signer(): + offload_lib = mock.MagicMock() + signer_lib = mock.MagicMock() + + # Test load_libraries method + with mock.patch( + "google.auth.transport._custom_tls_signer.load_signer_lib" + ) as load_signer_lib: + with mock.patch( + "google.auth.transport._custom_tls_signer.load_offload_lib" + ) as load_offload_lib: + load_offload_lib.return_value = offload_lib + load_signer_lib.return_value = signer_lib + signer_object = _custom_tls_signer.CustomTlsSigner(ENTERPRISE_CERT_FILE) + signer_object.load_libraries() + assert signer_object._cert is None + assert signer_object._enterprise_cert_file_path == ENTERPRISE_CERT_FILE + assert signer_object._offload_lib == offload_lib + assert signer_object._signer_lib == signer_lib + load_signer_lib.assert_called_with("/path/to/signer/lib") + load_offload_lib.assert_called_with("/path/to/offload/lib") + + # Test set_up_custom_key and set_up_ssl_context methods + with mock.patch("google.auth.transport._custom_tls_signer.get_cert") as get_cert: + with mock.patch( + "google.auth.transport._custom_tls_signer.get_sign_callback" + ) as get_sign_callback: + get_cert.return_value = b"mock_cert" + signer_object.set_up_custom_key() + signer_object.attach_to_ssl_context(create_urllib3_context()) + get_cert.assert_called_once() + get_sign_callback.assert_called_once() + offload_lib.ConfigureSslContext.assert_called_once() + + +def test_custom_tls_signer_failed_to_load_libraries(): + # Test load_libraries method + with pytest.raises(exceptions.MutualTLSChannelError) as excinfo: + signer_object = _custom_tls_signer.CustomTlsSigner(INVALID_ENTERPRISE_CERT_FILE) + signer_object.load_libraries() + assert excinfo.match("enterprise cert file is invalid") + + +def test_custom_tls_signer_fail_to_offload(): + offload_lib = mock.MagicMock() + signer_lib = mock.MagicMock() + + with mock.patch( + "google.auth.transport._custom_tls_signer.load_signer_lib" + ) as load_signer_lib: + with mock.patch( + "google.auth.transport._custom_tls_signer.load_offload_lib" + ) as load_offload_lib: + load_offload_lib.return_value = offload_lib + load_signer_lib.return_value = signer_lib + signer_object = _custom_tls_signer.CustomTlsSigner(ENTERPRISE_CERT_FILE) + signer_object.load_libraries() + + # set the return value to be 0 which indicts offload fails + offload_lib.ConfigureSslContext.return_value = 0 + + with pytest.raises(exceptions.MutualTLSChannelError) as excinfo: + with mock.patch( + "google.auth.transport._custom_tls_signer.get_cert" + ) as get_cert: + with mock.patch( + "google.auth.transport._custom_tls_signer.get_sign_callback" + ): + get_cert.return_value = b"mock_cert" + signer_object.set_up_custom_key() + signer_object.attach_to_ssl_context(create_urllib3_context()) + assert excinfo.match("failed to configure SSL context") diff --git a/packages/google-auth/tests/transport/test_requests.py b/packages/google-auth/tests/transport/test_requests.py index 7899419bee19..9532117b5f6a 100644 --- a/packages/google-auth/tests/transport/test_requests.py +++ b/packages/google-auth/tests/transport/test_requests.py @@ -28,6 +28,7 @@ from google.auth import environment_vars from google.auth import exceptions import google.auth.credentials +import google.auth.transport._custom_tls_signer import google.auth.transport._mtls_helper import google.auth.transport.requests from google.oauth2 import service_account @@ -535,3 +536,40 @@ def test_close_w_passed_in_auth_request(self): ) authed_session.close() # no raise + + +class TestMutualTlsOffloadAdapter(object): + @mock.patch.object(requests.adapters.HTTPAdapter, "init_poolmanager") + @mock.patch.object(requests.adapters.HTTPAdapter, "proxy_manager_for") + @mock.patch.object( + google.auth.transport._custom_tls_signer.CustomTlsSigner, "load_libraries" + ) + @mock.patch.object( + google.auth.transport._custom_tls_signer.CustomTlsSigner, "set_up_custom_key" + ) + @mock.patch.object( + google.auth.transport._custom_tls_signer.CustomTlsSigner, + "attach_to_ssl_context", + ) + def test_success( + self, + mock_attach_to_ssl_context, + mock_set_up_custom_key, + mock_load_libraries, + mock_proxy_manager_for, + mock_init_poolmanager, + ): + enterprise_cert_file_path = "/path/to/enterprise/cert/json" + adapter = google.auth.transport.requests._MutualTlsOffloadAdapter( + enterprise_cert_file_path + ) + + mock_load_libraries.assert_called_once() + mock_set_up_custom_key.assert_called_once() + assert mock_attach_to_ssl_context.call_count == 2 + + adapter.init_poolmanager() + mock_init_poolmanager.assert_called_with(ssl_context=adapter._ctx_poolmanager) + + adapter.proxy_manager_for() + mock_proxy_manager_for.assert_called_with(ssl_context=adapter._ctx_proxymanager) From 9321953c574ca754de418e5f5893da66366e40de Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 7 Jun 2022 21:24:15 +0000 Subject: [PATCH 576/966] chore(main): release 2.7.0 (#1036) :robot: I have created a release *beep* *boop* --- ## [2.7.0](https://github.com/googleapis/google-auth-library-python/compare/v2.6.6...v2.7.0) (2022-06-07) ### Features * add experimental enterprise cert support ([#1052](https://github.com/googleapis/google-auth-library-python/issues/1052)) ([dda7dda](https://github.com/googleapis/google-auth-library-python/commit/dda7ddaf859f5f7a21af714ebb422dfde4da46c8)) * add experimental GDCH support ([#1022](https://github.com/googleapis/google-auth-library-python/issues/1022)) ([5367aac](https://github.com/googleapis/google-auth-library-python/commit/5367aac881fdba814f66e4d6d5f59fccecc12547)) * Pluggable auth support ([#995](https://github.com/googleapis/google-auth-library-python/issues/995)) ([62daa73](https://github.com/googleapis/google-auth-library-python/commit/62daa73168f47806905bfc52b8f059995a193b71)) ### Bug Fixes * validate urls for external accounts ([#1031](https://github.com/googleapis/google-auth-library-python/issues/1031)) ([61b1f15](https://github.com/googleapis/google-auth-library-python/commit/61b1f1561ad6c3ddf4540143171351e53ff50f99)) ### Reverts * pluggable auth support [#995](https://github.com/googleapis/google-auth-library-python/issues/995) ([#1039](https://github.com/googleapis/google-auth-library-python/issues/1039)) ([513d999](https://github.com/googleapis/google-auth-library-python/commit/513d999d1f3b8a69bff86a2b91a73b6bdf6f92d0)) * revert experimental GDCH support ([#1022](https://github.com/googleapis/google-auth-library-python/issues/1022)) ([#1042](https://github.com/googleapis/google-auth-library-python/issues/1042)) ([c720995](https://github.com/googleapis/google-auth-library-python/commit/c720995aa08f539fe884685d9d53e599ca707e45)) ### Documentation * fix changelog header to consistent size ([#1046](https://github.com/googleapis/google-auth-library-python/issues/1046)) ([e64d084](https://github.com/googleapis/google-auth-library-python/commit/e64d0847276d456a95b171f5b79207e94ab818f3)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --- packages/google-auth/CHANGELOG.md | 25 +++++++++++++++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index bc5a2a33d417..54488dce0f7e 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,31 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.7.0](https://github.com/googleapis/google-auth-library-python/compare/v2.6.6...v2.7.0) (2022-06-07) + + +### Features + +* add experimental enterprise cert support ([#1052](https://github.com/googleapis/google-auth-library-python/issues/1052)) ([dda7dda](https://github.com/googleapis/google-auth-library-python/commit/dda7ddaf859f5f7a21af714ebb422dfde4da46c8)) +* add experimental GDCH support ([#1022](https://github.com/googleapis/google-auth-library-python/issues/1022)) ([5367aac](https://github.com/googleapis/google-auth-library-python/commit/5367aac881fdba814f66e4d6d5f59fccecc12547)) +* Pluggable auth support ([#995](https://github.com/googleapis/google-auth-library-python/issues/995)) ([62daa73](https://github.com/googleapis/google-auth-library-python/commit/62daa73168f47806905bfc52b8f059995a193b71)) + + +### Bug Fixes + +* validate urls for external accounts ([#1031](https://github.com/googleapis/google-auth-library-python/issues/1031)) ([61b1f15](https://github.com/googleapis/google-auth-library-python/commit/61b1f1561ad6c3ddf4540143171351e53ff50f99)) + + +### Reverts + +* pluggable auth support [#995](https://github.com/googleapis/google-auth-library-python/issues/995) ([#1039](https://github.com/googleapis/google-auth-library-python/issues/1039)) ([513d999](https://github.com/googleapis/google-auth-library-python/commit/513d999d1f3b8a69bff86a2b91a73b6bdf6f92d0)) +* revert experimental GDCH support ([#1022](https://github.com/googleapis/google-auth-library-python/issues/1022)) ([#1042](https://github.com/googleapis/google-auth-library-python/issues/1042)) ([c720995](https://github.com/googleapis/google-auth-library-python/commit/c720995aa08f539fe884685d9d53e599ca707e45)) + + +### Documentation + +* fix changelog header to consistent size ([#1046](https://github.com/googleapis/google-auth-library-python/issues/1046)) ([e64d084](https://github.com/googleapis/google-auth-library-python/commit/e64d0847276d456a95b171f5b79207e94ab818f3)) + ## [2.6.6](https://github.com/googleapis/google-auth-library-python/compare/v2.6.5...v2.6.6) (2022-04-21) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 6091bbe19874..9bc109a95243 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.6.6" +__version__ = "2.7.0" From 0d95f875ae1ef05d521be5792c343225046c9100 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Mon, 13 Jun 2022 16:34:27 -0700 Subject: [PATCH 577/966] chore: update sys test creds (#1060) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 86f8b6e50ba88e4a3f13682b46ac275da056750d..bc3cb3da94cb5ddbb22aee684b73c359ca258ebc 100644 GIT binary patch literal 10324 zcmV-aD67{BB>?tKRTGa^h8f~YL%`p(H_9PYZYZnGPXXgOnb|5^Z;KpR@W&FWPyjE^ zFCcV%{jrydM^ST^n{WJr&h0stnPWWN0{VL34KQbT-@&7LEkPbKn_rdJn&8m2WpG@V z@;8_^OJ7Ub@Wf}eVAcmMiu|FHS--Nc=~rGcr%vo~y@Mzm-rr3Gsv1;2@ho?y%`?H~ z8?2Q0*iaBrrksR4wD?Co1+2v62atPp(#xue+yV*i;_;|a%VP$p5TFcz?HRSTajmWC z0GTJ~Eqg4z3L52S)Bu|>>(CC^IHDVpas~%ox1xi}-LU4nL&4(H!dGTxUfhJinbs6o zry9G7Xw%w@XbP9Yel`~LsA&j$kCosnJaGMq;Y_Dy=B!r!IhI6oxPhhpven@K^8ZV? z!mV@OdBw0Qq@exT1%RbdTb7w_CfMMhkROP8kT#qj!#%_vnPCQPWv3rdz4xgz-^2rC zXtXH&p~j0mLPFYyGbScIQ*I4HX-3 zmSbNno6PYNZSJOMCrKn$Pe~=B?!4pGu>o~@B~YjDCK6Ui$P>mxzj1c@F38Jp-z}d; zymA?@dFkhTepJZbnm*A6_F?Dyp3Qn2;>dn8noiZN4Kv~V%*U|hU{*e=^wW&4`M=Lk zx|C^myAqL%Vciz4(!` zS{Gwit)$7ui9oMse+F4hmKaH({OmzA!$^atKNA65heyC&ZqOIJOADT<=o34Vh1a0>}b>rf1aFBcgCH$TKCcd~C5Bx$UJGyftQg0JLO z=WH*T+uLeCNrRHM{u0%LcAM0wuRVmzDwG2h(SK;;IqxTQbtkw}k&O&so%lw9#*LS#;?@8V)&R1-o}|!Ki^hh9Tm*UT z-Jgbk#(I|SkyPV1{0i}+#8YKPTGvt}SgC_{w51!yZ^69~Qirdndz(4VxhI%V!Jwk@ zI99AWII<0z=k`DIyy{=NDQnmyBtagc)8|8AsFCyXK(X3|!}DZs=7gdF8-F*_6`&0? zcF7ZVzs4kx-14?54={hC1w-N3sBq(@<0>LZIXs;<|75agw~!vew~@PLxv#517|jV`%R;sSG)RQj?-gLCT%Xaxc{;`0 z>o!1)z-~#e6WW2(c`Z^~CY{}cbY}AAxe|h{Nk*SQn;eg6*~zxL-jLXHNc&iX4s2H# ze-?$(O<9=uB=@898*bAJ{XF`=VYiT1;mgk3Ml%%CSVtpLonq0u^H9 z;WMqmJ*;I)#=K9g)Ju*co>arvq_M(z=Pcd9w)3fh>Lk;u56gs~jeX0G6|gEFfa+x6 ztND-qkXEemwe3fsDqSI#bstXdwDXq8~kq_O*;cy7)S#1?TPWo+^!QuETTc%f~WnE~#5&6M>W}C?X zaj>JN<&oqO$qAnyJ)iQqN=0y*a;zL~L-k5EHug+zt?F}qXCR(QXcfK5aP+P&6P-@^x$ zNuL{%s;|$YSmVD)JF4=AL@_CeocQ&s=*tbL)=U)Ymt40g<%;+ZH5Mcf18OmC3PWS3 z3D-^vu`6=GBLbkTkG5TIi=ab5kei+)#*e3%EiS05zLQR=u%Egaxag(pWKxW}dMSRu z@w?fSxe$R>&n&Yuky`vQVPfGdm*=G?YDl7~c19hyES_;JxA&A9?EWJnEK@Xg#{CSm z_BuvZY;#R>%!KrhJ(G_w#v%x5L@dcnC2sb_55E)0yYQS?9rYnii}fRvtaMX-rPma5 z)p*n!rpdgj%N)xHaPBn+BVh3p_@ONo(=UejG|}*={+|^jcdiG$ze_DMf`;ZMGtq4p z{g8#L#_>T0(2)jlr3?dSM;!squ=3Ls4ea8cdut;rvgy5tPZxowy#R3n`go$Y&n96% zA=XGTH{Bn{dP+H>IzG=%g%IyZxXTuFBe@Xxq zP~?IronQag)(oG|&`eM+ROW2~d+#ZO0yrin=Uv>n@jCBZNpJ`@B*O%Ks@4z$Yj%JE@XM~naJ&)tyNHT_74oIts1(XYyo@3uD z`qF|ODB&KU%s`DdJX->63_Pq_P4DbcgT0b0`sivbvzOhu)87lgmZTqHY+NFvP zZkR9AgwZP+*r`85(x_S;f-tjBz=<~LO1P};^&bwVt`8ewT-DslU;$x472B9u6fBL!1JDj=tGX~m4e`^g*qp;?INa&^A7 zGjLxb7Mjv)A+K>=9LMx>i5$%Kip&#GowwJaEt_)g2Vktzq){QHWJ3BlWi#;owO@X` zx3D@t7^~x_y|9`mK2bd7j`tnEtWg2jE|1dK&nIupW0eScpY)(}KQyv$g8%%!f%CM0 z$TSGApozn8lMd1$p_;xqt5GElVtiCfnwkH;K@!u>+Z$&pvjlYZ-5LOI+!uT<+HG^P z0!HF}V|zj-;n`(OEq+h3pTj*0ll+?*}or$KN0 z*Td|^kT)q;9y@j!?gv3m;2uk|sr6W4IJ~WxuTW@y^q2{rau?MEl(erfrvyMF1>_)W zr%{Wob9ZkUqVDcoqv!j;k_R9s906ZfknI+16Oy28UY%m`AJBWS5c{63Qu#X z5NI)Ct$D)a8cdYn-%XbOh_&SQ1fD#Nvbp6dNk%%J_(iz6#TZr_$Hx-Ghmq-IO3xi+ zMhmFPD%?SdYSA2U=_@84rsS{KpUGxW1Smz&7Q2}t*xc@H8BR8Svd~z7Lsv=7{w)~0 zV6st>KhF?R;RPU51y2u`cxv_Xtzj#+M%Wb_WDWHG= zxfbYVLj<+i`)Bau;#)gLi;%VnEok-|TQ^MIf=SisgcOzS6h(Kk z^xh3Q?57Dc9VL|5*-{eBmbLWCF@)?FE&Zev?VBQCpAc0JiQDCI0-{&3$Kh+t<)7o4 zsA``2026s1Di8rM-M=kJ6x!~#i;pY4vnJIYI!N{p5eSkgUkZ_q#*D zU7XW)DI^@mtylDSlsA{< z;5QQ>w9n+Y{;Qplvj99qQZbX-5OkVWmG?DZq2}z+;&ege--!uf+8vdQs{j;HwudYZ zW0R@%oIU9F1qxAmWHweBvK3dIZ)T2Gd@^e&3jM1^XeP~QEp;R@V$v42lR!7(-_hQO zFN=`vn6L+ZxhwzLQB|9KxKOc;Or&a zx7O@`W0UTl`Qy$mae@v8c1jAen}2hZQAAT3^-g0%Ah+emoSQvQVz|M)*DtGZ`v-35 zO7qm#0{z0iY0A>c#};n^eCiibZFMjnGc{JCDZCk;?qWw!55=;$PwtC4vphML|(x8m52YsrZ#|n!AmUrXRA=LAC0XN?lt^+pkS{ zd3JB1jrljfp;uNq^p`9Kfk4xkLZkhL()>tl3<(cy^Ks=c^)KPeZkgPG5rCPX^es|n zlh!9mWrFf|LQrgmR;7E6^oc;*uH(d3qWoQ!v#f55m7;$P9aQo8>TV)yLUvzlf&8=6 z4ZsLMr5rP~zGA~=Z`eC}-rT#eI4OngXem*&)gw}jSui;7i#q&oXxkOG{?QTNrVhCO z1S(1J;NxponmQw`7jDqV`1Eft-S@x@_^jL( zhTi5w~Obmkn$HLedz^-q530*+i@3+{;GXxm&LY zWM{te?=1U4Oi37B(c#1^FD<>-z=O=gf4Wwj#hJlw_f#^azB3~c8mz?RJF@uC;h4=_ zeF+M2XMTaD&1~N4x7rshw7%qi;Q^5Y>duRQa88d43_61(ovb>a2PojkyAd;K(kx7u zf-4trR9ax0&xPv~CC&7BSTHkR54>zygA0Wv65unE^G-VOAsdTJ*(lT(r`?<#xN`Yt zg|ovIu{0w?ozvfOk~#uqYfNzkGR2LWkArSYpMTk?&8eQ4cV7GrFD1&*1+Vrx@=OCz zMTwn^)H=z#yYmPTZ*}{eeelHFtY)iw*vcjZ-T!mr1{PJJX89F5bAOYB(svd|Y{{)ivyLZR+hmijClY>4nZD&gbOW*#$fB1uLUXU|g^wXG(8+!)VStAIXO+Of$wRM@m48 zSA-m>X9`OQ7QJe$PYcqfrt{-BE>;Hv#8y9lm?2o|1zD8UV0R#nSzA@eBWrw-1eVbi<*pr-%v$LBnv;~iHZ}F)9>J_e!d)I|m zz=?cNgIQPR>=0dJz66CCBcp<=x_5X`G>hoHB!HWyL+Z$Gkyr}yt^g4koBccDXy>oy zjD&RCc#^R#vX5uM8Yw{4F7s`JH#>G0PJpXGryB50ixU_Kx3yKiyqe!JH&B1}B|SUd z>ngTlVoss9{g`(odqp(=3^d1cr+u*bu4hLYn0=?^uJz%{CQ`la5d%(lGmDQovuD21;CQ}E^J zd;q2RMQtk%GDQ}E*jbZj3tOxMPWA)IH--8S8$ZeZ;Bu$`170*0H;Re20fqa~f9>&^ zRd&q@IUb$+j?O~GQz04pY?LFE;OyBRK3-g|bAFFZ>2_PAg183I41+7`>H&$THDyU9 zefrMp)Ki#iU+$HyVQU!V)!=-mT4AWtmB8m5t?BvSdTW(lhEmfAX365vY&=FWCMiMG zee_j}z&={vKor6W9ShVdoUkV8{1GRP^x3+lEvRjQv|yjGFRcXGc4o45IAAjuw6DuY z&fF*}(CH)OE;m6vj(<)71nR%ep;?}veNz>^ovlAS+H|@>nL|$6EEtm)m|Jydq`(X^ zTH3$4&bDW>G$U?`zby7j?nK=s;fBihH~x)uA9-z4AG^D3(Qb5znRWT#KYp-1g(0Qg zLmTRZ68tVr25Vb~$ucL(emrX_rl(+HP`kNHHS`HaFn6?QbD4K`ACFEKF^JhUMO^rM zZnTAy&##}ODarYp&(5_ZF2!fh6@+fPN^yEc(*LXd9%Yf8*`Z{)Xx5kbw!kREx>ZM6 zUZ${;qf<=b!;nMg#!>$Vg#Q6z9V1@kw+o~@a0Z8?ev~ZdHo`MYRwT*K#dvV_SDh_s zSK4rsff=G-FRXECX)zZ&7HyBfV)caxIC~g@5jJh!sPFwoARv=jCCg@+8gZzQpFGc1 z$gLZXlO`PBXm&|COjTJhoJ8B^3mC_lwQ+bl3R_8ZPb<)-P?4hO{;ecUsYrqQy!Ku) zTN)&?FVSGYMfO6(zMI5RS93@&ct8V^h7|kL+7N0GOxA8i)4rZTm2uAY;rf4k75Rg%3kA9~HuIRT}LJ0&$mIA$#Mf&I%&| zJ{2k2)?qMW77+d?Ax$6vqDi7IMv-geyIc+|MHiUK?e9GQci=sCZ?8U3Do^)G^lxCx zoN--GtT3uq7Xvz9NuiQj8Y z(`5o`>OtJvb=Y*c^o((>sHJa5kGc{nA*`~&s(~Rv#tzA*0 zW3lMS+jfkhQ}-W~GDBsxVMEweg_FY-3sg*(1B%rR&?aJuXozn+);$|>N_H}6r}h&K zz3ED3!d5dYIwX<(FOrV7GUAaBWnX%)^FVQHz>sEvRLe~@UWKp*U55NQ`=McQ2A=1j z6Vn()m|P&Y&XCF?c{1f>|GGYdwYD62fs{o}wHysAMJl_G38U1by^U&a?YmcwF1(U$;0;|OKsZpD+{p9l50gzjRYR>hQSxqQ z%V9nbuk(833*#7GqNh-Du57;#Q543E3kf@%NWXp@YRxW=tRV^|m=-V8TSaa{ zlO7dJ9%Pfpblhnjt=+ZOq~xfPib1ubnO6>Q#sXtB$d|2y{@Uq^#|lfPPbJk{Pbh#5=zmv?ffw~$9K7VXCb+C=8< zqm`8+(W5@Y(64t87dU(QRFN+3iqN!`N>Yp%zo%MiQu7T2>_&=5+VMlEE^IMQXTk;%C`rn>^n&6sOlr_5X@;$3KZ{s{?>I< zxJ8p>lv+N!X7I@m6vlvU^(31%N)`ZQQ-Ks2sz%u7$J@J|qF6wqbpc)$H~*6M;4&H( zgNfHwu1kbM0y=b%1wB@JS8JkrBUgiXq#B^5-6EF4H=9aONd(+X!)i3Hb!x(O;)^e? z7x>Nvq4}hvpjt1623fLs8TwVX+%q@9%URDN|DX3&*YjS)z1JDWQBmiRUt9ttyYVyj z7NuX5o#={c537faN;dP^Wyxw~w-s&Z)_Epx2wz_j{tm`FrA53rjP$mG zu#GYSWj0mFe5B2{&27OoyoJ&G7Xfo@ucn6Z&CtuKhm~ zTk0S9h~&cR=;A+UIt)8pKkif8zJQSHUFg8#LqF`Q;k4sB1HhI_dfkE%^uv5FhVs$C z?#tT@#IV@XSMBp=&iYoDp7X?DEhS9Gj#wPY#@Fa@fvN#@u5cZ}DxWz6$?_st1_gndAupF}?d} zR#KH07@o8@6s!Rk_^T-yFNiSnWzj#^ZbKW&2UE=6o|Bre0BD|`ZL}2!^n>`7mzI+D-(}`uP-HG~C%D80b@?L{b@p5@Nh1%tuFbDH0VRnZ7R2@=+ z00&PiJ&LCPs2j~8 zq!Q(II}o{j28XN1U*A@AybC8AglVjf%Vlb-Nl|*tfL)r;>`$h2WR`*)xz;u@CMMcd zgv?X|IYEkU^QacUmV|3TVDKaqn z>r*=c;j0~4OKLp@J)^CJxsD6IffM`Y$J|B%G+0M3^3U1c?Wi@=Uh{56)O<2K-L<$|+^Movcd`NiEi->V z(N(Z+QZvm=?C>)z2;6hZMP1>QD`7JIX&~VpdL~{C;qv%F5?;^Z&Oxe)=L)M?Yeiji zGD9`V1RyEfu7A@`r{&3TK4Li6e5y9I<%*gmXH3lKz@9%YEX^MEe4EUa2=~PBU;$HP zn!FkU7$#Qs4w1XJQ`G8bC!yQ_mP*y*Pg4#_8Z9Icm^!Gr#yPb9e6BFR94Lc|EFal! zAm?2FFTG4??7#GV*0przV~WpI<`Wap?p-uzFm(CDkoRluos!58vjyR^o!PjhHNWZ< zaJ%?t$E@6k1T}I5{=F*EBj<~PiUY+dpK2-37|-C!E_B&ZxT7AV7#3!uHpt*iGJagERh&l*`yNw$O*;y!R14xuc#44c))2l9`TRyZ8htdIdD( zF3?R3BYE4m;*k=8__Ia721JDLc}2C-F`1a=7+sD2v4F-|(&lf%*|k8dF24F{8bkE| zM}}P7;u9FunQPK&s(ZT7;|-z4dVHyBq^&rmafPLtA}euq#=83iG`P$i*%McaKP~(1Jpe~1XnbxD05ljg4sct6O?`UNx?Cz7-feI8!0TQl z>iQ;;ff$Z7xBsd%TUDu`PP8O4bvgTjxX1`3f|im>a?!%>tYP=o(_HISsFy zGA1UEodW(M-srOuUd=7b^%GQj1(8g4@h-W&v8&=pJe7uItT!f4j1L2Kvmf6gNz&zj z{1`g&YNT;qVCZxFC-hR>_kan9y2wWwwYAIHYA0GAo@J?ftQ^&@aw<&Xn>Pq@luArkFfyx$hBih8zU5H?H~nS z!2{mP$fri0(GMbW;w)3Q#8FpB1cJ%YrMj2kH#ch*NfBFXV!4zTRG+g2rm=o)GhWAw1X4^?LsA@y zN-|8ADLlXruOcB_Z+|;~nC-X%qh2=wSB8l)?q(5Poj>3haF$3^h? z{jXvP@(xr&u1a4RkDWHlRX%aDH8gFprv7e5)wb-T?HA-5wNpak?SHb5{OC=ScwVp&eGXHjfAio)l-96$xmE0ISRXs)+vK&Lf}MW zE)AuPWH9@wM4vds>V?xujhdA1+lGh-j@+r9G@QCyS45Oq*OQtl1!zfb3lldn2&Xlc z_=?mg%J76r%#LsI+Nb%7v)z5SIyXgQX6|&k6EF}X*`syJz8g{poKH+tbUQj(a%D8o zR8n&((f;@Kqkn><%u7+F2mu#6KiYRi9>v+6XnUEI0H5ZX$pk7Ch5$5jWe^>qZ=ta!Y>RMIT(BDHWlE42k_A5O6&k8qupZ)mjX|Hu>RGDNam}ciBKdO z?j|af1y#Ln|9Es+mAVIU-Z zP%l1*{FBxcnpjHk)VBwx7$tdVyFCPzd^qUJ-(4b1qrbzBKK*sZ-cig)crk{9`@laI zs$Y>1h|OJub0}ux&#nnziZ)gPBH~KIBG^AjpCmtEU;J(x7#qcsC|#+K?GqU_`6hZX zDxmI06>Djy+^lx3I`vjDFJs)p>Ft4URo^CR5T+Q%8XxJ8ctJLtY4YSQ!aC?R5U;VR zG!*>1BU7}>2xqS|q$&2QNuFWD#uYZ$DudW5kxf=>ugK-(vD)lvYfgg6qx9Kkd*D*u z_J8Yd`h2`RN70}MiG)a*^rCAdU3rGd&-d5;(-kwbXOF2MRrsdiTn=mWp-x#(;8*Ud zQ?*(~C>_j)x(%jdo>~oN4nV+9vHw}c$gZA^Ur>=EyfOu^OYg@zDS|qj3n8RJZE-Q9 zsuG|*B{WTcaCIXP&e}NXob7h7C1_A~7bpDQ;n4fDk^3|LkV*}I4fs*;StpbN)oRV0 zd*~@O85y;&+#tnoBd_!NDhr}VQo*~G6^2KHCYkx<^onNISr64TxxHdRn&Znrg6p{N z3(n}XDQIvI`VVMg6=M7QiX0;2p1X>gz3ksThYiZp_8<&6`-gjk mP%RcN!6l4z@vX`CQI56*SbmB&mHkzB} literal 10324 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 From 15a0ac34eacc66e051dfce109adc4040a1c9527e Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Tue, 14 Jun 2022 13:50:16 -0700 Subject: [PATCH 578/966] feat: add experimental GDCH support (#1044) * feat: add experimental GDCH support * use ec key * update comment * Update google/oauth2/gdch_credentials.py * fix * add project, update payload --- packages/google-auth/google/auth/_default.py | 30 ++- .../google/auth/_service_account_info.py | 15 +- packages/google-auth/google/oauth2/_client.py | 58 ++-- .../google/oauth2/gdch_credentials.py | 251 ++++++++++++++++++ .../tests/data/gdch_service_account.json | 11 + .../google-auth/tests/oauth2/test__client.py | 27 +- .../tests/oauth2/test_gdch_credentials.py | 174 ++++++++++++ packages/google-auth/tests/test__default.py | 27 ++ .../tests/test__service_account_info.py | 21 ++ 9 files changed, 591 insertions(+), 23 deletions(-) create mode 100644 packages/google-auth/google/oauth2/gdch_credentials.py create mode 100644 packages/google-auth/tests/data/gdch_service_account.json create mode 100644 packages/google-auth/tests/oauth2/test_gdch_credentials.py diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index d038438d5be5..3a4190389881 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -36,11 +36,13 @@ _SERVICE_ACCOUNT_TYPE = "service_account" _EXTERNAL_ACCOUNT_TYPE = "external_account" _IMPERSONATED_SERVICE_ACCOUNT_TYPE = "impersonated_service_account" +_GDCH_SERVICE_ACCOUNT_TYPE = "gdch_service_account" _VALID_TYPES = ( _AUTHORIZED_USER_TYPE, _SERVICE_ACCOUNT_TYPE, _EXTERNAL_ACCOUNT_TYPE, _IMPERSONATED_SERVICE_ACCOUNT_TYPE, + _GDCH_SERVICE_ACCOUNT_TYPE, ) # Help message when no credentials can be found. @@ -134,6 +136,8 @@ def load_credentials_from_file( def _load_credentials_from_info( filename, info, scopes, default_scopes, quota_project_id, request ): + from google.auth.credentials import CredentialsWithQuotaProject + credential_type = info.get("type") if credential_type == _AUTHORIZED_USER_TYPE: @@ -158,6 +162,8 @@ def _load_credentials_from_info( credentials, project_id = _get_impersonated_service_account_credentials( filename, info, scopes ) + elif credential_type == _GDCH_SERVICE_ACCOUNT_TYPE: + credentials, project_id = _get_gdch_service_account_credentials(filename, info) else: raise exceptions.DefaultCredentialsError( "The file {file} does not have a valid type. " @@ -165,7 +171,8 @@ def _load_credentials_from_info( file=filename, type=credential_type, valid_types=_VALID_TYPES ) ) - credentials = _apply_quota_project_id(credentials, quota_project_id) + if isinstance(credentials, CredentialsWithQuotaProject): + credentials = _apply_quota_project_id(credentials, quota_project_id) return credentials, project_id @@ -421,6 +428,20 @@ def _get_impersonated_service_account_credentials(filename, info, scopes): return credentials, None +def _get_gdch_service_account_credentials(filename, info): + from google.oauth2 import gdch_credentials + + try: + credentials = gdch_credentials.ServiceAccountCredentials.from_service_account_info( + info + ) + except ValueError as caught_exc: + msg = "Failed to load GDCH service account credentials from {}".format(filename) + new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) + six.raise_from(new_exc, caught_exc) + return credentials, info.get("project") + + def _apply_quota_project_id(credentials, quota_project_id): if quota_project_id: credentials = credentials.with_quota_project(quota_project_id) @@ -456,6 +477,11 @@ def default(scopes=None, request=None, quota_project_id=None, default_scopes=Non endpoint. The project ID returned in this case is the one corresponding to the underlying workload identity pool resource if determinable. + + If the environment variable is set to the path of a valid GDCH service + account JSON file (`Google Distributed Cloud Hosted`_), then a GDCH + credential will be returned. The project ID returned is the project + specified in the JSON file. 2. If the `Google Cloud SDK`_ is installed and has application default credentials set they are loaded and returned. @@ -490,6 +516,8 @@ def default(scopes=None, request=None, quota_project_id=None, default_scopes=Non .. _Metadata Service: https://cloud.google.com/compute/docs\ /storing-retrieving-metadata .. _Cloud Run: https://cloud.google.com/run + .. _Google Distributed Cloud Hosted: https://cloud.google.com/blog/topics\ + /hybrid-cloud/announcing-google-distributed-cloud-edge-and-hosted Example:: diff --git a/packages/google-auth/google/auth/_service_account_info.py b/packages/google-auth/google/auth/_service_account_info.py index 3d340c78d40c..15709927385e 100644 --- a/packages/google-auth/google/auth/_service_account_info.py +++ b/packages/google-auth/google/auth/_service_account_info.py @@ -22,7 +22,7 @@ from google.auth import crypt -def from_dict(data, require=None): +def from_dict(data, require=None, use_rsa_signer=True): """Validates a dictionary containing Google service account data. Creates and returns a :class:`google.auth.crypt.Signer` instance from the @@ -32,6 +32,8 @@ def from_dict(data, require=None): data (Mapping[str, str]): The service account data require (Sequence[str]): List of keys required to be present in the info. + use_rsa_signer (Optional[bool]): Whether to use RSA signer or EC signer. + We use RSA signer by default. Returns: google.auth.crypt.Signer: A signer created from the private key in the @@ -52,18 +54,23 @@ def from_dict(data, require=None): ) # Create a signer. - signer = crypt.RSASigner.from_service_account_info(data) + if use_rsa_signer: + signer = crypt.RSASigner.from_service_account_info(data) + else: + signer = crypt.ES256Signer.from_service_account_info(data) return signer -def from_filename(filename, require=None): +def from_filename(filename, require=None, use_rsa_signer=True): """Reads a Google service account JSON file and returns its parsed info. Args: filename (str): The path to the service account .json file. require (Sequence[str]): List of keys required to be present in the info. + use_rsa_signer (Optional[bool]): Whether to use RSA signer or EC signer. + We use RSA signer by default. Returns: Tuple[ Mapping[str, str], google.auth.crypt.Signer ]: The verified @@ -71,4 +78,4 @@ def from_filename(filename, require=None): """ with io.open(filename, "r", encoding="utf-8") as json_file: data = json.load(json_file) - return data, from_dict(data, require=require) + return data, from_dict(data, require=require, use_rsa_signer=use_rsa_signer) diff --git a/packages/google-auth/google/oauth2/_client.py b/packages/google-auth/google/oauth2/_client.py index 2f4e8474b557..847c5db8a487 100644 --- a/packages/google-auth/google/oauth2/_client.py +++ b/packages/google-auth/google/oauth2/_client.py @@ -44,11 +44,13 @@ def _handle_error_response(response_data): """Translates an error response into an exception. Args: - response_data (Mapping): The decoded response data. + response_data (Mapping | str): The decoded response data. Raises: google.auth.exceptions.RefreshError: The errors contained in response_data. """ + if isinstance(response_data, six.string_types): + raise exceptions.RefreshError(response_data) try: error_details = "{}: {}".format( response_data["error"], response_data.get("error_description") @@ -79,7 +81,7 @@ def _parse_expiry(response_data): def _token_endpoint_request_no_throw( - request, token_uri, body, access_token=None, use_json=False + request, token_uri, body, access_token=None, use_json=False, **kwargs ): """Makes a request to the OAuth 2.0 authorization server's token endpoint. This function doesn't throw on response errors. @@ -93,6 +95,13 @@ def _token_endpoint_request_no_throw( access_token (Optional(str)): The access token needed to make the request. use_json (Optional(bool)): Use urlencoded format or json format for the content type. The default value is False. + kwargs: Additional arguments passed on to the request method. The + kwargs will be passed to `requests.request` method, see: + https://docs.python-requests.org/en/latest/api/#requests.request. + For example, you can use `cert=("cert_pem_path", "key_pem_path")` + to set up client side SSL certificate, and use + `verify="ca_bundle_path"` to set up the CA certificates for sever + side SSL certificate verification. Returns: Tuple(bool, Mapping[str, str]): A boolean indicating if the request is @@ -112,32 +121,40 @@ def _token_endpoint_request_no_throw( # retry to fetch token for maximum of two times if any internal failure # occurs. while True: - response = request(method="POST", url=token_uri, headers=headers, body=body) + response = request( + method="POST", url=token_uri, headers=headers, body=body, **kwargs + ) response_body = ( response.data.decode("utf-8") if hasattr(response.data, "decode") else response.data ) - response_data = json.loads(response_body) if response.status == http_client.OK: + # response_body should be a JSON + response_data = json.loads(response_body) break else: - error_desc = response_data.get("error_description") or "" - error_code = response_data.get("error") or "" - if ( - any(e == "internal_failure" for e in (error_code, error_desc)) - and retry < 1 - ): - retry += 1 - continue - return response.status == http_client.OK, response_data - - return response.status == http_client.OK, response_data + # For a failed response, response_body could be a string + try: + response_data = json.loads(response_body) + error_desc = response_data.get("error_description") or "" + error_code = response_data.get("error") or "" + if ( + any(e == "internal_failure" for e in (error_code, error_desc)) + and retry < 1 + ): + retry += 1 + continue + except ValueError: + response_data = response_body + return False, response_data + + return True, response_data def _token_endpoint_request( - request, token_uri, body, access_token=None, use_json=False + request, token_uri, body, access_token=None, use_json=False, **kwargs ): """Makes a request to the OAuth 2.0 authorization server's token endpoint. @@ -150,6 +167,13 @@ def _token_endpoint_request( access_token (Optional(str)): The access token needed to make the request. use_json (Optional(bool)): Use urlencoded format or json format for the content type. The default value is False. + kwargs: Additional arguments passed on to the request method. The + kwargs will be passed to `requests.request` method, see: + https://docs.python-requests.org/en/latest/api/#requests.request. + For example, you can use `cert=("cert_pem_path", "key_pem_path")` + to set up client side SSL certificate, and use + `verify="ca_bundle_path"` to set up the CA certificates for sever + side SSL certificate verification. Returns: Mapping[str, str]: The JSON-decoded response data. @@ -159,7 +183,7 @@ def _token_endpoint_request( an error. """ response_status_ok, response_data = _token_endpoint_request_no_throw( - request, token_uri, body, access_token=access_token, use_json=use_json + request, token_uri, body, access_token=access_token, use_json=use_json, **kwargs ) if not response_status_ok: _handle_error_response(response_data) diff --git a/packages/google-auth/google/oauth2/gdch_credentials.py b/packages/google-auth/google/oauth2/gdch_credentials.py new file mode 100644 index 000000000000..7410cfc2e05e --- /dev/null +++ b/packages/google-auth/google/oauth2/gdch_credentials.py @@ -0,0 +1,251 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Experimental GDCH credentials support. +""" + +import datetime + +from google.auth import _helpers +from google.auth import _service_account_info +from google.auth import credentials +from google.auth import exceptions +from google.auth import jwt +from google.oauth2 import _client + + +TOKEN_EXCHANGE_TYPE = "urn:ietf:params:oauth:token-type:token-exchange" +ACCESS_TOKEN_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token" +SERVICE_ACCOUNT_TOKEN_TYPE = "urn:k8s:params:oauth:token-type:serviceaccount" +JWT_LIFETIME = datetime.timedelta(seconds=3600) # 1 hour + + +class ServiceAccountCredentials(credentials.Credentials): + """Credentials for GDCH (`Google Distributed Cloud Hosted`_) for service + account users. + + .. _Google Distributed Cloud Hosted: + https://cloud.google.com/blog/topics/hybrid-cloud/\ + announcing-google-distributed-cloud-edge-and-hosted + + To create a GDCH service account credential, first create a JSON file of + the following format:: + + { + "type": "gdch_service_account", + "format_version": "1", + "project": "", + "private_key_id": "", + "private_key": "-----BEGIN EC PRIVATE KEY-----\n\n-----END EC PRIVATE KEY-----\n", + "name": "", + "ca_cert_path": "", + "token_uri": "https://service-identity./authenticate" + } + + The "format_version" field stands for the format of the JSON file. For now + it is always "1". The `private_key_id` and `private_key` is used for signing. + The `ca_cert_path` is used for token server TLS certificate verification. + + After the JSON file is created, set `GOOGLE_APPLICATION_CREDENTIALS` environment + variable to the JSON file path, then use the following code to create the + credential:: + + import google.auth + + credential, _ = google.auth.default() + credential = credential.with_gdch_audience("") + + We can also create the credential directly:: + + from google.oauth import gdch_credentials + + credential = gdch_credentials.ServiceAccountCredentials.from_service_account_file("") + credential = credential.with_gdch_audience("") + + The token is obtained in the following way. This class first creates a + self signed JWT. It uses the `name` value as the `iss` and `sub` claim, and + the `token_uri` as the `aud` claim, and signs the JWT with the `private_key`. + It then sends the JWT to the `token_uri` to exchange a final token for + `audience`. + """ + + def __init__( + self, signer, service_identity_name, project, audience, token_uri, ca_cert_path + ): + """ + Args: + signer (google.auth.crypt.Signer): The signer used to sign JWTs. + service_identity_name (str): The service identity name. It will be + used as the `iss` and `sub` claim in the self signed JWT. + project (str): The project. + audience (str): The audience for the final token. + token_uri (str): The token server uri. + ca_cert_path (str): The CA cert path for token server side TLS + certificate verification. If the token server uses well known + CA, then this parameter can be `None`. + """ + super(ServiceAccountCredentials, self).__init__() + self._signer = signer + self._service_identity_name = service_identity_name + self._project = project + self._audience = audience + self._token_uri = token_uri + self._ca_cert_path = ca_cert_path + + def _create_jwt(self): + now = _helpers.utcnow() + expiry = now + JWT_LIFETIME + iss_sub_value = "system:serviceaccount:{}:{}".format( + self._project, self._service_identity_name + ) + + payload = { + "iss": iss_sub_value, + "sub": iss_sub_value, + "aud": self._token_uri, + "iat": _helpers.datetime_to_secs(now), + "exp": _helpers.datetime_to_secs(expiry), + } + + return _helpers.from_bytes(jwt.encode(self._signer, payload)) + + @_helpers.copy_docstring(credentials.Credentials) + def refresh(self, request): + import google.auth.transport.requests + + if not isinstance(request, google.auth.transport.requests.Request): + raise exceptions.RefreshError( + "For GDCH service account credentials, request must be a google.auth.transport.requests.Request object" + ) + + # Create a self signed JWT, and do token exchange. + jwt_token = self._create_jwt() + request_body = { + "grant_type": TOKEN_EXCHANGE_TYPE, + "audience": self._audience, + "requested_token_type": ACCESS_TOKEN_TOKEN_TYPE, + "subject_token": jwt_token, + "subject_token_type": SERVICE_ACCOUNT_TOKEN_TYPE, + } + response_data = _client._token_endpoint_request( + request, + self._token_uri, + request_body, + access_token=None, + use_json=True, + verify=self._ca_cert_path, + ) + + self.token, _, self.expiry, _ = _client._handle_refresh_grant_response( + response_data, None + ) + + def with_gdch_audience(self, audience): + """Create a copy of GDCH credentials with the specified audience. + + Args: + audience (str): The intended audience for GDCH credentials. + """ + return self.__class__( + self._signer, + self._service_identity_name, + self._project, + audience, + self._token_uri, + self._ca_cert_path, + ) + + @classmethod + def _from_signer_and_info(cls, signer, info): + """Creates a Credentials instance from a signer and service account + info. + + Args: + signer (google.auth.crypt.Signer): The signer used to sign JWTs. + info (Mapping[str, str]): The service account info. + + Returns: + google.oauth2.gdch_credentials.ServiceAccountCredentials: The constructed + credentials. + + Raises: + ValueError: If the info is not in the expected format. + """ + if info["format_version"] != "1": + raise ValueError("Only format version 1 is supported") + + return cls( + signer, + info["name"], # service_identity_name + info["project"], + None, # audience + info["token_uri"], + info.get("ca_cert_path", None), + ) + + @classmethod + def from_service_account_info(cls, info): + """Creates a Credentials instance from parsed service account info. + + Args: + info (Mapping[str, str]): The service account info in Google + format. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.oauth2.gdch_credentials.ServiceAccountCredentials: The constructed + credentials. + + Raises: + ValueError: If the info is not in the expected format. + """ + signer = _service_account_info.from_dict( + info, + require=[ + "format_version", + "private_key_id", + "private_key", + "name", + "project", + "token_uri", + ], + use_rsa_signer=False, + ) + return cls._from_signer_and_info(signer, info) + + @classmethod + def from_service_account_file(cls, filename): + """Creates a Credentials instance from a service account json file. + + Args: + filename (str): The path to the service account json file. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.oauth2.gdch_credentials.ServiceAccountCredentials: The constructed + credentials. + """ + info, signer = _service_account_info.from_filename( + filename, + require=[ + "format_version", + "private_key_id", + "private_key", + "name", + "project", + "token_uri", + ], + use_rsa_signer=False, + ) + return cls._from_signer_and_info(signer, info) diff --git a/packages/google-auth/tests/data/gdch_service_account.json b/packages/google-auth/tests/data/gdch_service_account.json new file mode 100644 index 000000000000..172164e9fa17 --- /dev/null +++ b/packages/google-auth/tests/data/gdch_service_account.json @@ -0,0 +1,11 @@ +{ + "type": "gdch_service_account", + "format_version": "1", + "project": "project_foo", + "private_key_id": "key_foo", + "private_key": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIIGb2np7v54Hs6++NiLE7CQtQg7rzm4znstHvrOUlcMMoAoGCCqGSM49\nAwEHoUQDQgAECvv0VyZS9nYOa8tdwKCbkNxlWgrAZVClhJXqrvOZHlH4N3d8Rplk\n2DEJvzp04eMxlHw1jm6JCs3iJR6KAokG+w==\n-----END EC PRIVATE KEY-----\n", + "name": "service_identity_name", + "ca_cert_path": "/path/to/ca/cert", + "token_uri": "https://service-identity./authenticate" +} + diff --git a/packages/google-auth/tests/oauth2/test__client.py b/packages/google-auth/tests/oauth2/test__client.py index 5485bed84e56..bd4cc500137d 100644 --- a/packages/google-auth/tests/oauth2/test__client.py +++ b/packages/google-auth/tests/oauth2/test__client.py @@ -56,7 +56,7 @@ def test__handle_error_response(): assert excinfo.match(r"help: I\'m alive") -def test__handle_error_response_non_json(): +def test__handle_error_response_no_error(): response_data = {"foo": "bar"} with pytest.raises(exceptions.RefreshError) as excinfo: @@ -65,6 +65,15 @@ def test__handle_error_response_non_json(): assert excinfo.match(r"{\"foo\": \"bar\"}") +def test__handle_error_response_not_json(): + response_data = "this is an error message" + + with pytest.raises(exceptions.RefreshError) as excinfo: + _client._handle_error_response(response_data) + + assert excinfo.match(response_data) + + @mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) def test__parse_expiry(unused_utcnow): result = _client._parse_expiry({"expires_in": 500}) @@ -145,6 +154,8 @@ def test__token_endpoint_request_internal_failure_error(): _client._token_endpoint_request( request, "http://example.com", {"error_description": "internal_failure"} ) + # request should be called twice due to the retry + assert request.call_count == 2 request = make_request( {"error": "internal_failure"}, status=http_client.BAD_REQUEST @@ -154,6 +165,20 @@ def test__token_endpoint_request_internal_failure_error(): _client._token_endpoint_request( request, "http://example.com", {"error": "internal_failure"} ) + # request should be called twice due to the retry + assert request.call_count == 2 + + +def test__token_endpoint_request_string_error(): + response = mock.create_autospec(transport.Response, instance=True) + response.status = http_client.BAD_REQUEST + response.data = "this is an error message" + request = mock.create_autospec(transport.Request) + request.return_value = response + + with pytest.raises(exceptions.RefreshError) as excinfo: + _client._token_endpoint_request(request, "http://example.com", {}) + assert excinfo.match("this is an error message") def verify_request_params(request, params): diff --git a/packages/google-auth/tests/oauth2/test_gdch_credentials.py b/packages/google-auth/tests/oauth2/test_gdch_credentials.py new file mode 100644 index 000000000000..60944ed41109 --- /dev/null +++ b/packages/google-auth/tests/oauth2/test_gdch_credentials.py @@ -0,0 +1,174 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import copy +import datetime +import json +import os + +import mock +import pytest # type: ignore +import requests +import six + +from google.auth import exceptions +from google.auth import jwt +import google.auth.transport.requests +from google.oauth2 import gdch_credentials +from google.oauth2.gdch_credentials import ServiceAccountCredentials + + +class TestServiceAccountCredentials(object): + AUDIENCE = "https://service-identity./authenticate" + PROJECT = "project_foo" + PRIVATE_KEY_ID = "key_foo" + NAME = "service_identity_name" + CA_CERT_PATH = "/path/to/ca/cert" + TOKEN_URI = "https://service-identity./authenticate" + + JSON_PATH = os.path.join( + os.path.dirname(__file__), "..", "data", "gdch_service_account.json" + ) + with open(JSON_PATH, "rb") as fh: + INFO = json.load(fh) + + def test_with_gdch_audience(self): + mock_signer = mock.Mock() + creds = ServiceAccountCredentials._from_signer_and_info(mock_signer, self.INFO) + assert creds._signer == mock_signer + assert creds._service_identity_name == self.NAME + assert creds._audience is None + assert creds._token_uri == self.TOKEN_URI + assert creds._ca_cert_path == self.CA_CERT_PATH + + new_creds = creds.with_gdch_audience(self.AUDIENCE) + assert new_creds._signer == mock_signer + assert new_creds._service_identity_name == self.NAME + assert new_creds._audience == self.AUDIENCE + assert new_creds._token_uri == self.TOKEN_URI + assert new_creds._ca_cert_path == self.CA_CERT_PATH + + def test__create_jwt(self): + creds = ServiceAccountCredentials.from_service_account_file(self.JSON_PATH) + with mock.patch("google.auth._helpers.utcnow") as utcnow: + utcnow.return_value = datetime.datetime.now() + jwt_token = creds._create_jwt() + header, payload, _, _ = jwt._unverified_decode(jwt_token) + + expected_iss_sub_value = ( + "system:serviceaccount:project_foo:service_identity_name" + ) + assert isinstance(jwt_token, six.text_type) + assert header["alg"] == "ES256" + assert header["kid"] == self.PRIVATE_KEY_ID + assert payload["iss"] == expected_iss_sub_value + assert payload["sub"] == expected_iss_sub_value + assert payload["aud"] == self.AUDIENCE + assert payload["exp"] == (payload["iat"] + 3600) + + @mock.patch( + "google.oauth2.gdch_credentials.ServiceAccountCredentials._create_jwt", + autospec=True, + ) + @mock.patch("google.oauth2._client._token_endpoint_request", autospec=True) + def test_refresh(self, token_endpoint_request, create_jwt): + creds = ServiceAccountCredentials.from_service_account_info(self.INFO) + creds = creds.with_gdch_audience(self.AUDIENCE) + req = google.auth.transport.requests.Request() + + mock_jwt_token = "jwt token" + create_jwt.return_value = mock_jwt_token + sts_token = "STS token" + token_endpoint_request.return_value = { + "access_token": sts_token, + "issued_token_type": "urn:ietf:params:oauth:token-type:access_token", + "token_type": "Bearer", + "expires_in": 3600, + } + + creds.refresh(req) + + token_endpoint_request.assert_called_with( + req, + self.TOKEN_URI, + { + "grant_type": gdch_credentials.TOKEN_EXCHANGE_TYPE, + "audience": self.AUDIENCE, + "requested_token_type": gdch_credentials.ACCESS_TOKEN_TOKEN_TYPE, + "subject_token": mock_jwt_token, + "subject_token_type": gdch_credentials.SERVICE_ACCOUNT_TOKEN_TYPE, + }, + access_token=None, + use_json=True, + verify=self.CA_CERT_PATH, + ) + assert creds.token == sts_token + + def test_refresh_wrong_requests_object(self): + creds = ServiceAccountCredentials.from_service_account_info(self.INFO) + creds = creds.with_gdch_audience(self.AUDIENCE) + req = requests.Request() + + with pytest.raises(exceptions.RefreshError) as excinfo: + creds.refresh(req) + assert excinfo.match( + "request must be a google.auth.transport.requests.Request object" + ) + + def test__from_signer_and_info_wrong_format_version(self): + with pytest.raises(ValueError) as excinfo: + ServiceAccountCredentials._from_signer_and_info( + mock.Mock(), {"format_version": "2"} + ) + assert excinfo.match("Only format version 1 is supported") + + def test_from_service_account_info_miss_field(self): + for field in [ + "format_version", + "private_key_id", + "private_key", + "name", + "project", + "token_uri", + ]: + info_with_missing_field = copy.deepcopy(self.INFO) + del info_with_missing_field[field] + with pytest.raises(ValueError) as excinfo: + ServiceAccountCredentials.from_service_account_info( + info_with_missing_field + ) + assert excinfo.match("missing fields") + + @mock.patch("google.auth._service_account_info.from_filename") + def test_from_service_account_file(self, from_filename): + mock_signer = mock.Mock() + from_filename.return_value = (self.INFO, mock_signer) + creds = ServiceAccountCredentials.from_service_account_file(self.JSON_PATH) + from_filename.assert_called_with( + self.JSON_PATH, + require=[ + "format_version", + "private_key_id", + "private_key", + "name", + "project", + "token_uri", + ], + use_rsa_signer=False, + ) + assert creds._signer == mock_signer + assert creds._service_identity_name == self.NAME + assert creds._audience is None + assert creds._token_uri == self.TOKEN_URI + assert creds._ca_cert_path == self.CA_CERT_PATH diff --git a/packages/google-auth/tests/test__default.py b/packages/google-auth/tests/test__default.py index ed64bc723552..61772c2e33cb 100644 --- a/packages/google-auth/tests/test__default.py +++ b/packages/google-auth/tests/test__default.py @@ -28,6 +28,7 @@ from google.auth import external_account from google.auth import identity_pool from google.auth import impersonated_credentials +from google.oauth2 import gdch_credentials from google.oauth2 import service_account import google.oauth2.credentials @@ -50,6 +51,8 @@ CLIENT_SECRETS_FILE = os.path.join(DATA_DIR, "client_secrets.json") +GDCH_SERVICE_ACCOUNT_FILE = os.path.join(DATA_DIR, "gdch_service_account.json") + with open(SERVICE_ACCOUNT_FILE) as fh: SERVICE_ACCOUNT_FILE_DATA = json.load(fh) @@ -637,6 +640,14 @@ def test__get_gcloud_sdk_credentials_no_project_id(load, unused_isfile, get_proj assert get_project_id.called +def test__get_gdch_service_account_credentials_invalid_format_version(): + with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: + _default._get_gdch_service_account_credentials( + "file_name", {"format_version": "2"} + ) + assert excinfo.match("Failed to load GDCH service account credentials") + + class _AppIdentityModule(object): """The interface of the App Idenity app engine module. See https://cloud.google.com/appengine/docs/standard/python/refdocs\ @@ -1140,3 +1151,19 @@ def test_default_impersonated_service_account_set_both_scopes_and_default_scopes credentials, _ = _default.default(scopes=scopes, default_scopes=default_scopes) assert credentials._target_scopes == scopes + + +@mock.patch( + "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True +) +def test_default_gdch_service_account_credentials(get_adc_path): + get_adc_path.return_value = GDCH_SERVICE_ACCOUNT_FILE + + creds, project = _default.default(quota_project_id="project-foo") + + assert isinstance(creds, gdch_credentials.ServiceAccountCredentials) + assert creds._service_identity_name == "service_identity_name" + assert creds._audience is None + assert creds._token_uri == "https://service-identity./authenticate" + assert creds._ca_cert_path == "/path/to/ca/cert" + assert project == "project_foo" diff --git a/packages/google-auth/tests/test__service_account_info.py b/packages/google-auth/tests/test__service_account_info.py index d5529bcce87f..9ad9f0fc8b50 100644 --- a/packages/google-auth/tests/test__service_account_info.py +++ b/packages/google-auth/tests/test__service_account_info.py @@ -24,10 +24,14 @@ DATA_DIR = os.path.join(os.path.dirname(__file__), "data") SERVICE_ACCOUNT_JSON_FILE = os.path.join(DATA_DIR, "service_account.json") +GDCH_SERVICE_ACCOUNT_JSON_FILE = os.path.join(DATA_DIR, "gdch_service_account.json") with open(SERVICE_ACCOUNT_JSON_FILE, "r") as fh: SERVICE_ACCOUNT_INFO = json.load(fh) +with open(GDCH_SERVICE_ACCOUNT_JSON_FILE, "r") as fh: + GDCH_SERVICE_ACCOUNT_INFO = json.load(fh) + def test_from_dict(): signer = _service_account_info.from_dict(SERVICE_ACCOUNT_INFO) @@ -35,6 +39,14 @@ def test_from_dict(): assert signer.key_id == SERVICE_ACCOUNT_INFO["private_key_id"] +def test_from_dict_es256_signer(): + signer = _service_account_info.from_dict( + GDCH_SERVICE_ACCOUNT_INFO, use_rsa_signer=False + ) + assert isinstance(signer, crypt.ES256Signer) + assert signer.key_id == GDCH_SERVICE_ACCOUNT_INFO["private_key_id"] + + def test_from_dict_bad_private_key(): info = SERVICE_ACCOUNT_INFO.copy() info["private_key"] = "garbage" @@ -60,3 +72,12 @@ def test_from_filename(): assert isinstance(signer, crypt.RSASigner) assert signer.key_id == SERVICE_ACCOUNT_INFO["private_key_id"] + + +def test_from_filename_es256_signer(): + _, signer = _service_account_info.from_filename( + GDCH_SERVICE_ACCOUNT_JSON_FILE, use_rsa_signer=False + ) + + assert isinstance(signer, crypt.ES256Signer) + assert signer.key_id == GDCH_SERVICE_ACCOUNT_INFO["private_key_id"] From 3e7446372866e7828e9cd0e1cda639ff1f1a3207 Mon Sep 17 00:00:00 2001 From: Kaiser Pister Date: Tue, 14 Jun 2022 16:45:32 -0500 Subject: [PATCH 579/966] Update credentials.py (#976) Fix a minor typo in documentation. Co-authored-by: Anthonios Partheniou Co-authored-by: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> --- packages/google-auth/google/oauth2/credentials.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/google/oauth2/credentials.py b/packages/google-auth/google/oauth2/credentials.py index 9b59f8cf532f..4cc502fea49d 100644 --- a/packages/google-auth/google/oauth2/credentials.py +++ b/packages/google-auth/google/oauth2/credentials.py @@ -54,7 +54,7 @@ class Credentials(credentials.ReadOnlyScoped, credentials.CredentialsWithQuotaPr The credentials are considered immutable. If you want to modify the quota project, use :meth:`with_quota_project` or :: - credentials = credentials.with_quota_project('myproject-123) + credentials = credentials.with_quota_project('myproject-123') Reauth is disabled by default. To enable reauth, set the `enable_reauth_refresh` parameter to True in the constructor. Note that From 360298f2af9111d73cd20d45c6eadebdb15a5989 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 14 Jun 2022 15:06:22 -0700 Subject: [PATCH 580/966] chore(main): release 2.8.0 (#1063) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> Co-authored-by: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 54488dce0f7e..77d05fca544b 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.8.0](https://github.com/googleapis/google-auth-library-python/compare/v2.7.0...v2.8.0) (2022-06-14) + + +### Features + +* add experimental GDCH support ([#1044](https://github.com/googleapis/google-auth-library-python/issues/1044)) ([94fb5e2](https://github.com/googleapis/google-auth-library-python/commit/94fb5e27ef57b9ffb2fa58386bc0cb382ddafec2)) + ## [2.7.0](https://github.com/googleapis/google-auth-library-python/compare/v2.6.6...v2.7.0) (2022-06-07) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 9bc109a95243..b4f8545fc130 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.7.0" +__version__ = "2.8.0" From 83665c81dbc33413336939bab33da5dcf3da761b Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Wed, 15 Jun 2022 16:27:46 -0700 Subject: [PATCH 581/966] chore: update sys test creds (#1065) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index bc3cb3da94cb5ddbb22aee684b73c359ca258ebc..6f1142d3d7a6d69853909f57e5929faa5d3786f8 100644 GIT binary patch literal 10324 zcmV-aD67{BB>?tKRTFZyGkIRhxmpQ@&^O-<85-0v+j`O+0ry56KaeaO+UpXkPyjE^ zFCchrZBw$mAFL@zK8y7IcMg{_u8hQYtU0?t&@F>J(K21pRodRiYFtW2;m+_+zD!1p zqQ!96C|%cMqe?qaPij@tQuUZ|9P&aY0P7pLCWt#*6>gc35gA5kmdIVo)1ve^JppOb zUg5DGo9IDpLV%$|tqe-+9>g4VGYTGbozR#Ts2#qy3as;Kcb#*m`<7OP*k+UNfil78+(bEsl|_N>J0PtU4?7&Mio`9Ir0M8zgz?M-eC=HLt_{ z2cv8Qn7D`43@Hq+Z7D2EfPE$X&GBfvRl(M`1oJxgvUo|)7}5XoFige2730w6?7E_c z38O-<14cR+=RRgPQ#$-4SNoiHqgM@p;ToeO)H=-hIYl0E9VJx}=6o@``}YhoCzT*` z9AeDlzkH?5Fn4K`_>N7|V*8OsK5(9~pNbx6Ng6S&kU59dC$)J~Z`I@OH7VpNG-65( z$BvOO&D;n%C4-MzI|!c&P@z*&sM=d3GF@$Sewmnze{H%;bo=fy2wITx6H}S^#XA4M z%KsJop~2$H&Gv|%p%CN^qsw2hVU6VNq_2A-Yhnr7*17-(NpBd6N}R$svFhXmb6T%K z60ZEQ84V}m^Z~&jm$6W%aHW5aYR5fXk2(){X1vOF^&BDnt#Ws?)QOmr4sNa@wx7ha z;o{C@;j_te_MVhI+9N9&DTX$0_(MExQ8xn`=N{c(SC^;k_yj<8jPZDZQAZqd;S@+D z1$yy#1;c+FJ;F_mFaTM|gUnEzRlz^l^11FA7`WBHXBz{Z@`v;d<3V?;A)~%?&d(0e zJ!l)#4Bu(lVqF46&-(LR2eoCCF5zxudxU5YY^hw7qcfro(XO1Qv>c+pY_@rkiyrqR zqoskp=a$rC435Kt~D!@KX<=xY|A$2vjbNLolqg%o zS?B*}Jsv}TaovGj`DBQhK3K#PW5B{JYpo+%V%Glf#JNtJ!@^WR7i|xAL zvo$`@IRtDm0+C~ZPV<(w%nKcVSNUqGN^=- zxP2P}C28A4Daq-N7CGlEMZJ;u0E`ck)4=eo+MTA~Gz~}MFb0^st~a^#a}tukD#ay6 zqe_c=eLH-Q*%R{jDbv^?f=v-u`>@GtQNHeJZo5%X{a1KIrY%9xH1m&=WR=!G41(_M_| z9#*s)pOuDrQYPM61b5T&!4dSq6D8}SXDvP?$vIW&H$Y|E*5h3O=mRJ~aG=>x{)8PT z&9gpz8XWUqGYueH?suyBbI-YloloqK=uHgfp9HLVM)K~nNLEWE+zCSMt=_8!8Szy@ zwj5+(vmc#!hx&-jW-#2xZx+S7ISQ|TBWr+(&=aW-F^ zpY(~M^ZS4pIzw(EC~LcEleW&_TS=&xYkp|uQV#&~tH#IrCmI9GK$tAsDtmAs5GS#j zTX2hAEz7OGesh3g;xALH4T7pHmxrA|MY6(W9fyn0CU2+K-J$;)GFiFAMfpY6d`bZ9 zZRFsE4GW-Xf0u&g*0i5Lcmt<-mKCjSo5Y>hs?xk##8Lh`WI@|B?iXMsBB;DglW2x# zM4Lx9SGPWBB8NVakGLYqNo@l>;Q!dQr|R&f6RN+`OP-_%8P=fpjMk^L91Fa?h9b$K|^&a zyj?^s32j~YnOxXPh-oPPI8{o~ZlnM4DpBRdNhUy??(ffOYu1%sP8H8W`inRCGwz}fU+XGMcmTSZXgdgen1j<0^dQae_ zL_{{Q1^*7$qNlg8>rMAlPD^qBKs{t0h1kQPfrz&)=*~qYlGrKw3(Pftp6x>530v9i z<-88R$_9Lyb3;a}GhPQLh{xMUdKZMCqqE6V#?;WAvaN#+8G8ij1q|JUdr=<&% zCh~0Fu8<5?;F0eF6zx5N$~2|kbSHcBarl!?^kYsMD|)g`*l$tld;8^ z6SQ1oV?!}pSHkV+s|=OMG;9IvhL`1Oh<~h8w*G1{N7YF^jP=_1c!Y}_Kv0gROWhFm=&3^8b0=xUdqfDT@K?-j zU;PF4IjsW_k&J|w##2$ySBoQOeWzQ54hfNo!$BvTg2BB--G6YT`mTsyv$huJiDc-H z<_f>9d1E179c2LyPC>EfRQ;s~<7tiiTg@0oDW274+rgy4Q`EX0?h#13;U5y82oFny zqtzuQDtw|F5v1Vt)r@aV8iD`j5$;Rxh*vfZdw5cYi=z;GT1zc=JtMH-3BVV?dqcs0 z>LV^O3Im@8;;k=9X|OwVU|kvO3*L~M-RjpbR z{T4pPWz53l7ZZVn zT-J!GfgTS7=@G)2q><1!)O8}!4j_WA+Cd~g;9C!<5)oo0S>+QUpZf8X%RXc9k?baC zq&?lcV1mhDn7dW?sVmn5-95~j@>S&kFbi`(!eC)a9>k9e+WAhk0V;%h7Qc}_z+`Gj zQS7rxo~ieR0K3znnyjAYE4*cC>+6MW?Fnir^ybv_=X2c=f_g9kz>V z(d%I~)@fr0@CGe4x#RnIvWq*DC#HetmL#j&d_*~7#BB5pwGk+SkMm&9OWqjaKDnri z@n(@hFC4UV?UF`1^Qw9nkRp2A&xwB_yvK-Z&M1U<%b67Df(_ynYJi-=GoG81x2>D5 ziM-!d+WDS<$EeHFasizb+*-S&FTm=*W(|)igW1xAa^;VKMLcwj5)1iapIRI{-?jef zRA1SmU5pnO?|XHB^bX}Jf!_pa?2^YXhLHm}RUENU*>kc{pP6~F^&RIzlqpp}YlC<* zFBhcpbC6vZnRgy6h&%;W^{95aNxJCh)Pu(%hkXAB1#ha;2*|s$1Hy;%dks(j_SK=F zY30^`>=%wGd(T<0yODrM_o95tn)JbtdSIKqN44YJ@WlWZw8~5x&hu7}ugtpwutZH| z@)sMXE?OfzjI;a4@mHXsha zQQa1wrs@5Hd585ZU3Dgdw~F|$u2_`CVINU7nxT>ZN|6L_&903s3F=CoZp(jgl7*I` zW$+%F*X&6czLL$OH_;=3-TBlXbffWT3XIjs5AjLJpwn#agE|(LrPIrh=@`v0c8ltU zH12Y~&+LSwFdU>C>WHty)x!>E2(Y^e^7h^PhOo{L` z4IZlZ?t8`>4Ot@~CJ*2FlLh%hKx*2sSes*EL7C$VN1$C+QuLQxy?HOkIwT{}N9n<| zRdBZrR@q?JU#s4-*1&XhYYmxH3D8C>uj9Mn+>52$Po)jsZs4>~25w}=1l45=bQBGV!Q_+)Z|+ DkMi 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 literal 10324 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 mP%RcN!6l4z@vX`CQI56*SbmB&mHkzB} From 6d17f8166389dbc45fa0e2d0c49d98c0c551742d Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Wed, 15 Jun 2022 18:02:48 -0700 Subject: [PATCH 582/966] chore: add Timur to CODEOWNERS (#1064) --- packages/google-auth/.github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/.github/CODEOWNERS b/packages/google-auth/.github/CODEOWNERS index 4381bcf23253..06c1e10f1476 100644 --- a/packages/google-auth/.github/CODEOWNERS +++ b/packages/google-auth/.github/CODEOWNERS @@ -5,7 +5,7 @@ # https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners#codeowners-syntax # The @googleapis/yoshi-python is the default owner for changes in this repo -* @arithmetic1728 @sai-sunder-s @googleapis/yoshi-python +* @arithmetic1728 @sai-sunder-s @TimurSadykov @googleapis/yoshi-python # The python-samples-reviewers team is the default owner for samples changes /samples/ @googleapis/python-samples-owners From b06952df766e848f49d5c9dcfc44fe5eca302456 Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Tue, 28 Jun 2022 22:49:42 +0000 Subject: [PATCH 583/966] chore: update sys creds (#1070) * chore: update sys creds * update pubsub version * update pubsub to 1.7.2 --- packages/google-auth/system_tests/noxfile.py | 2 +- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/system_tests/noxfile.py b/packages/google-auth/system_tests/noxfile.py index 459b71c78f65..e056d4b2947a 100644 --- a/packages/google-auth/system_tests/noxfile.py +++ b/packages/google-auth/system_tests/noxfile.py @@ -334,7 +334,7 @@ def app_engine(session): @nox.session(python=PYTHON_VERSIONS_SYNC) def grpc(session): session.install(LIBRARY_DIR) - session.install(*TEST_DEPENDENCIES_SYNC, "google-cloud-pubsub==1.7.0") + session.install(*TEST_DEPENDENCIES_SYNC, "google-cloud-pubsub==1.7.2") session.env[EXPLICIT_CREDENTIALS_ENV] = SERVICE_ACCOUNT_FILE default( session, diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 6f1142d3d7a6d69853909f57e5929faa5d3786f8..76bc77ae31e71d31181fc25b7ebb6b8b0e2b0052 100644 GIT binary patch literal 10324 zcmV-aD67{BB>?tKRTFJ?Lv{>~=_ZW{z(v{IS4;iWNB24wK}(9$vr7}7 z4^92b()mDknuJU~%2{55dw{2j4xIFXyV>;6C4gGR;sU2^zKMZ)(4*%;1rs$eMMP+@ zcKS@8Fs<&wJXeWUWP(TAjt=Zb`iiJf^5qb~{|+@JIafWtV9TX7|9@$kCvN6*8K(?0 zQT8pjij);d#fG>S-~QlZ_h#pa8*l&* z1w$Un)+1^JC&Jw(MHm$@rz~-YR@l&1`OAO88PTDz$ltGB8ABH(hf| zB!>DZ2t2q7gH5)gqB8l}escKqK|NzoHn5}LUpTWtCQR_e?EGwiTw`92^gI{O?S?m7 z*1%{3^;9SQgJ5kpzrnzAddbA2{}L6;xL8DPNS0m8GTqqk=8RyEXK*#bzaE*e{mW7_ zN&R~^f$44)@Ex%~PLJfuRqb_gN>_&F(f^f=R?;ichNB`j1gO*9wGq7b*8LKf9k;ly5gI zzFl}i`kn6S<LkSgDRd$N}b-uDD;huIh}zXlDrCF;tlVEpZLj2?oSL0hNJlW?2i zHE)e+ty4J45MFf?5!I(+8qbjjE)Mf~wu#FPaF>fU0@Uteu1eQ^-Wb3$>KtSE%F=xt z+1cs~X{!qsXOfaMz(&LmGURj%sJ?Q&{@g3)5xT2y$A|_fbe5 z^6qaN%=-q5Ju5fg_*y6Xs$=p!)@ zBKG$lc<1e?7QD6}1}cQY!n8TW84Fl6!8AlT2zeH7GLl)<@&CiC7JT@#tt=?q80_J{ z0p=4?sJy~(*$71m4eu#rv3FPzs@RGjg#$S_&vgTZQXniPYLG~zQ(>Sm+E(&~e!7P{ zwikt&_Qri%Ofc@iN*8IYycg{$roc&v8teTKEDyOHAjTPOHka1;n zSv!R%rC(tFrconmlLHYcR^?Y>-5CJU7&a+Y0n}t25pZ@us19?{hQt{cv7oT|+uSQD91Yz06zqM-jMI~b!Xw6gmFTMLROc0F7 zsbexAKdrq9Ee+|vFSp`|Np=RwyM3&Dr4-0EOE5;D60v*LH2^3VvXxk*1IVEa=au@S z8t`(&?~cJ`vmsqo&DO1n{wog+CK0&B9u@xlOHI($wb$lA34QF*~f6D10y#&%Q0p9hmDksb2|d#5b?FH$35&&)@J*+0*Aq=Rp`$O4Y(Sg+#c zjTQK-O2am1Xx&&h{csA1=8MWo9lraS-`Xcgxt?F6S0E6Bg0E&K`LernZq6OZ(S>d0 z?9b0^aFwBXk`wzGPFmchLSee|$xY2&ENG-cBcl&1K6+y(6iz-(KHFX+grI{1h%}Tx zf-V=!pCAr1E+d5<`xyZZK>`5N)8O|)I>_IHZC=uuF1}e!ft+0gx!92bUIudf!-7_j zUbUjmA89|FwL^sDhndzdhtZ2DZB#<1(l=k}3VZd79H>44`o%s!Etr=Sh)Ri^FXl7l ziJ+HR!?L2d%Ax+dg47^7;*S||e7n@o$d5y?iLPEJLSm6O1FsSMNQi?o!87J4UL|OcjN<#dta#sBU1wMH+lp$>(0KL`lTFMvh`?B!G|NWPX zzuin^VXQ4*NBDyQ{0c)$Kb95cA+3G?mq4)(Gt$Gpj#PjrHcW_U?TjFVTitQ4`Q|azj>yCVVJ=90C8pJ zEzGr74GuM3H!&FJkR>efDOA)KQ4Kh)5; z@hS-F{2mPzKZW%BeEZ9{L}}wBCfknXa$BfH4WRLuW@qE?aL>3>8r8U51B~|mBD&rgBXk^yq#ZuM zjizNGNShf?ysDm5PQix?tc59qQg;%z0+jrD&RJ?*6^H=%O{tOZAnyMRsBy&oEw3ua zv#HC>Qdd5W0^99t*qj%Lwp-N8G(*oLQpoFvVoF2QdiN<+fP}&*c)v6M)e-{xdGzr* zScsCEv1Uz2D^%YX{naO4?|`Sxev#gP=I9D+ z!XK#I%jx%j%7v?$H12hAogqC6{mF2bPo8dAID@e{Ub`m+trzymTk>t|h1)dsbR3^` zVKR0Z-YKq5-hmmS!zQ<%-LGB5wyy_zn4L^n7y+(zR4xL*#pxhR&h< zvJWYdm^cW?<2?!P29q-%>sV2XQSUx0C!nX*(nWV}`#AAVVyBfc6vEyjU-)Y*h3&3j zgALz)c$9v0agTP)AP&coIu3b2ER*rfYPNrD9VCU4(d{XyWU6?}GgIaz=dc zFF(ntmafw|e3Rt?PeAzs)2t^Ld)Li08U2%J z+uAWh1?WxG`QN^cRq<11p|S@)kWMYRq(~QGzJ8`K#niK#bwvvbAr`5RV)molRMf2@ zy*!R)b7O8gQ<2wjg2rNfvfl%*Ug_aAjzz2xzSmvi80?4yMcM*R%bUV@6D=b#6Rdon zHC2fY!{nvS>;dUxpNwSiZny{ghv%mCM;ef4^-%XwWu*Uis+`nIz+Lr2( z!W7e1o?(d0hw>|?$N#f^Y7hfqpkpGd^>km%^ID!N>eKO1$o)@4!P1umtY4A#hKn^q zb!5kE+4X%&pUphy)={%wOGXLtWY1p#`*qm3aQ|8y_qcO)m%&xsslF4FOuOqarimmc z%C*S1grC6Ugg0n==njHht2*-F44Le*F5Xbv1-4VUmDs*-53^Psk9KpR#&8D!)VM^6 zzBx0%3^HbJ7)k61@8@$TOi5UisW+IBhkQCRmtVeNVXJ%c+6j&NMhdwI2-@96vRERV!n zb^(Y%gn6v{r3v#wgozxyf4+$#IBngLj^Wj+EQiaTF10{;IKFrD2k)VJC`U!UJtCOd zE0|!sPU{u+2LwoYNCrNLuS1W_kB(KJU@#N*|KTNtg z3q%BEou_!pVv=aOsfmu?yYE|jCn=HGgJ>pF3Cq6SV&wwkm*&%%owx4^l$) z+jk$sna9+80o@=ORXBgu#-Iq%;WKFl*YnZ6-WF~0ex_p4e#ZJU-H|oMi}g=XCGD@# zsG0jf;S)3NT&J~<^91X%Ok=wBQD2#?1n0u*WE z#2BaG02Ud>60PvA0=Y9Pg#g`c2uu~$Vyf~mv6UMzIm&&dy=80qn%la)+hQ@|qHNNj zarUE{R-2BVZJq~S=caKI zWYn}eHaCtH0PW|D9y^Y^XpW;%B~+A^|KFt2paGNaxh%P07$Sp@pB5ad2f?ajBu~i6 z9-LfCFPXkP5HgIxmAw+z!7syvKgkV{_3@llZDVp6@EbC)v$~juzRZHA`eAGR!DTL9 z_!qxUgGUGu`N>6_*qQ%Lz)irJ;Iwws2iO`=Yh>riUew)fO>j%7+b3+-Cu`AT&4lVn z`WrIZ8WjXC&Qw(vQ-6|lBzNqE^B1@az!lo5Ra9(o(Rn5!OSMvo1F!o)neDS#d|XEA zhMM__WL`U$w>~gTjs4crL!UY<+9K5T9)Ho%y8Vu=%bVjWZ|OmF6)Z zvf=y}!w6*ooZUv?7~R1igSfE3F*yL>JH_Me4g#e~VG1l8da@l-$NEETj@&g;LeJrA zW9d2YZh!5&7D58*z^o@Gh}`?vi2KE|BeXGbVsfVLqX&oP8v0jl#fH|Q37GbAlKK8{ zDx-drq6J1-K35KQFqw;@idbi5UY(lBVgJGW+?g zGJ6MIlqk~Q&gTR%JmDe!WgH3`Loa2*_k2_>Tks{)htMHmXFv1hsua|wInH9g*3XbK z`7*@LfIc%4ZUZ8_KKR|iZpXBMcY*olt9&_z^F9%)WFyh3>IJeQLZ*=If`T#5t?@KYCmizn*EDk#uwa*Vo zQ_fzw2QYxQWe2w`C|D3by)Map&SW;@aFwezpiY`##T8mGO-R@ii2#rZo7ouz@N48X zsn2lT?(R+qS@3+cQZCNxb zz>uS}W(6IcR5=VP{>zhq?E{9SfZH2@nAI ze;0}nJyR#rM;Mk}WuWP~<62OWFPz*6*k714Q+g)?HNGQj<&eM=o!Z4UEbyoYl#aA( zayxyDV(@Ll4!PSP*-4&6vWAz}T!T?ooZeyaAJOxrs@tpJ#N(My%D~Yewz`f+U&@M~ zdP{0AaUfaq*e28lpO(cf>^cM(Ni-7tF8hIzn7*Q`i6{*+9$u>tJ|d`NPkqRww#nj! zDIT&RbY`W>)cq|^|5o~Q%oGU`oi$eZgl_$4q{?_>m)C2@v>BH5INlRwBs4a6odRQ^ z?hu}tHmW5pSt2)q9;2RBWJ1oSygw}NYg{;X9$q2gJ~ve^w+V)dwG)E;wfeWVV%yY+ zOcety8pdjGb;*8tR(7#(vbKOD`n?;X(!+)=w?Wd%QimjiuW`-DyOHc%U$Vr5FHrFl5;vt06uzK1%PMeRksjj$q zX@5PkA_ik01Fh>AtTWQjw^41l3>lY8%^45!|F?0trY{S2X^+7W&@e=bwi0ngfOb=9 z$z0r(e^*T+6{0qqco`b(_XgB*7+ZuthyhK?1u&-{einDLbQV7U2qnrHZDK}n7~GIq zQxYn;IUO>6+WjA1NQ3^9fyH7%L*roT!8tDwXUW;H)^f)28{m(U zp!jtNJi=I2iU+Z>$>!P%N|VrbgDgo<*)hui`LO@_8L-JbNaB$<9jUtcOd~*flI~el zUG)HutWO#~KsR7THYdyl`4P4RXy{zKRM|Nn`5Ja=Oe9ZxR0Wimr(ZnmGyWJoJH7bK zqF_Hu)`QB~l``N6oqkQo>W17^&jZZ4U`aG5Rn;UDXxsHZvXWV+$nXm7l?P>C=bDdV z8>UP*hPI+(fazxCK{^&u@rX}c(Sr=bnkAua%LtD*fH$@RPU6}<-ves*ZD`1sx8M6G zM;1&_$-1=R+R0s_{@{_%2lH{eVYLs%5R;h?D|<49m>d06_GUR?4BWpc`B`VD_cIdFX<}ncd?a}LSG8hgp3OiRduWzvnqO?~ zup`)V%N{9~oRda^@E&P26x43cIC)Ff(Y<(ZqYHpt2aFq@ff2}@62`ax!QIuxijohu zR=cNF3*yML3y>lJWl|u>GXLKq5NY9-QJR7fL8*pMa34Ehnv5l<4MfY_kdM!HYOA#p zt}&sG?QMQz_sFUWiE`o{=)QM0U5-j4 z#Y#!`pzbqrT@#Y|qWK2itFS-ItpR25qPjyLj1>cS1P4P?ynM!FlNwF7z`5BbRadUy z*J@u1_FW@zazj>Ay#!TyDqsqr}ug7IaRh; zaq9(~#SUCaMB1&ku4MJ}tc%`^plIDMkOEPJaAN>)`~wyv3^azbpr<1zDjxhll$Ruo zykE%SYp1GkVFB|%kUF7qLGH6UCs??}iI~tuM)cT?{RK5X#bpYiv9TJ2a}pb8Bg1JQ zRJp~5FCIe<@&Mf~yeoWwm*N{fjw8QKmxUaD!(+zDVr#=FIJI#npR~|yUaP6{M5sB4 z%f2PzN07OTc6NIGFtoG~^%BLqOFibD#$Kbzp713&Y0K z{i!-2?9-Tyj?m^)Y?Ob-gXBV;bOd|jyblxNk$w4+C6|T4rEgksZ22l5Y>Ww474QYc z$39^JJUk}}v99PhTbO_EN4DJn89Ycuxw|*;>U9tqZYk3z+J1i``>_5O;(p9|P41(OX=&-= z4M_Fz4uwUD;XZfN=ta||1-w-md|E_*!{k<(@r$3uH>)%1=~QeFm3I&+rE#E^w6*BE z&oMLGuDi2ZpiBY(<_d@r=UCpJLiv-ooo9*nrP4RNuy7~cDDPSS954{i8FlqxB+W`I!|0Q)zIMnIvb~w98hO3QH!yOEB~+b0@XuVlp@IJ zr&?xO(t7*ZDeR4&nA!eTf&wyg`ggSCe~%snVA`>`a}yDe<`a^GmSWLpilUz-#4k;~ zvbv*lk<%>=dZW!RrH3&F3zU$>h{6>wEC9*Z1H#XG+vPoCpul!{;Kx#j5^aNOvC0PT!a1`^r_ zyZw1Q^Q=!qSZnEd0pJaGzJRGn|JdZ>F**avB%fls{(11#>0npn)aUyk*1^|Zn5{A;6X;Q-Rk&CMLr{*iHP_T? znKAC+B7}+WRLKg0zj>+!-l?OyzU5;wn!9?BO;a27P101~pOhJw_ z!(jIj^DON#8ieGU?LW02UX1ryWV(u*e9VD9Dx^Ez!tH~RBeJ6TG~uK}q4$!^E7{wxy}1xD6K&o;A7Ss$7@nRkax!jFcR>M(VIAnwU*_yAv8k5$O! zeeFmT(ZI_yzOP*+%o9+=B$`w~frSDO+Z z-0c+A#EbjocE%{URQ6eZ8vSQE7V$}zHq+~%+MFk?&seT8y zP>gV14AfPxhUPt;eI);)#}~xcWQ>3!3o97)vt}OwUo8A(sg>Mjo+Q*-4~mr?-ckoO z@Jt2XfeQkRC0}e#Ll1Z6ShW?@Bwo(-T6}Gr#KW*RG!1}D(+Ftn>gCzg6feHyW8@B% zB|R%jY1jBmn5qjxU}TB58?#B9^9+;JzH&Tho)+d(1t27^qk??O#T8=Zi}eH4>kntC zXs)T}H~;2g4{7tdd47#ze;1d$H%MD7R8n&P$PF3rTiEb{_#_>4?zM)xVPGuo{$CoO zCW)IG=gL7^LORDD0VE46TN>-p{d$I0m7se_;bPh%#+2A(3?_~SHm=e5er@97ZSFYL zaQe6jxbx2&3xyqNQIdC_hwAOR zGjuyn(^rc8Qut@=-yz__gWMESm{%GvSkVv8+NaiRk8eii(grLAB>uYgu=H|T(kRDa zSuowB^hi3~yU|aqn$BJT=XrOytdPzhhQadp<2>sOTW1v85|xz8fPPLcj{<^1u+|dg z$O901J*%~=+)4KVgSa-T%_HCcK}2s)A~? z?Wnwd?=o>tiR#|P=bUog(9g_hF+oXG%U(WuvbQQGdqJ_{Rc9tYb0TPGU%aRA49obst#uiXmaG?2`iH0mNj{%Iry^W z1qcg&&1(%RcNt|t3kGe>x+u3YR(^40Y6pc2Lyj}a;}n@~AwMPmPOyyUOt&2R&4ffh z7~Te3BHZLFM0;G@vqx$#U2PkKPOmMsv2i%qL5`guZ265084q`onZ&&gywPvVIQfn2k`f}|0vP1M^27a2J3^uSQ}TJnVa zt6JM1s|!W{w)$He$Jle3jTkq_JiAC?z}F$%0Yk9X?`Fumno8l=?JqR z-0;xBe08_*>PATjRqU#gJt8rDYB^#rJ=63Zo2qjD%cb?X(AT$;PwCd=Z1+`!ae_Fz zw+uYsZp?r)a=x#ZdH!y0dR!V9LLxiAbG%7@&yA|W~6JgLt*EiSG6ywHE<}}KalDU mRaQGrZTC;wolL~-Mw;HmM~Pg0NV24j8Aq%0laHCU2+OL_;1}Tl literal 10324 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 DkMi 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 From 4c8a5f962d0fc5c974f2c98d4059421647685f4a Mon Sep 17 00:00:00 2001 From: chuanr Date: Tue, 28 Jun 2022 23:51:13 +0000 Subject: [PATCH 584/966] feat: pluggable auth support (#1045) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Add Pluggable auth support See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md feat: Add Pluggable auth support (#988) * Port identity pool credentials * access_token retrieved * -> pluggable * Update pluggable.py * Create test_pluggable.py * Unit tests * Address pr issues feat: Add file caching (#990) * Add file cache * feat: add output file cache support 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md Update pluggable.py 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md Update pluggable.py Update setup.py 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md Update setup.py Update setup.py pytest_subprocess timeout Update pluggable.py env 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md Update _default.py 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md Update requirements.txt Update _default.py Update pluggable.py Update pluggable.py Update pluggable.py Update test_pluggable.py format validations Update _default.py Update requirements.txt 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md Revert "Update requirements.txt" This reverts commit 1c9b6db25c683663ed4b71ab0ab39946fce8f6eb. Revert "Update _default.py" This reverts commit ac6c36072084a440c234a9465b35462bd52378b3. Revert "Revert "Update _default.py"" This reverts commit 1c08483586007e4caf1a36f2c9cbf2a45d403ee0. Raise output format error but retry parsing token if `success` is 0 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md Update requirements.txt Delete test_pluggable.py Revert "Delete test_pluggable.py" This reverts commit 74beba9405564a5b764af8718c49e640d9b84c5f. Update pluggable.py Update pluggable.py pytest-subprocess 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md lint Update pluggable.py nox cover nox cover 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md lint Update test_pluggable.py Update test_pluggable.py * Disable Pluggable Auth for Python 2.* Update noxfile.py * Update pluggable.py * Update pluggable.py * Update pluggable.py * Update pluggable.py * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * Address PR issues * Update pluggable.py * Update pluggable.py * Update user-guide.rst * Update user-guide.rst * Update user-guide.rst * Update user-guide.rst * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md Co-authored-by: Owl Bot Co-authored-by: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Co-authored-by: Leo <39062083+lsirac@users.noreply.github.com> --- packages/google-auth/docs/user-guide.rst | 153 +++- packages/google-auth/google/auth/_default.py | 11 +- packages/google-auth/google/auth/pluggable.py | 322 ++++++++ packages/google-auth/noxfile.py | 1 + packages/google-auth/tests/test__default.py | 20 + packages/google-auth/tests/test_pluggable.py | 752 ++++++++++++++++++ 6 files changed, 1256 insertions(+), 3 deletions(-) create mode 100644 packages/google-auth/google/auth/pluggable.py create mode 100644 packages/google-auth/tests/test_pluggable.py diff --git a/packages/google-auth/docs/user-guide.rst b/packages/google-auth/docs/user-guide.rst index 239b5a6d7176..a0924789787a 100644 --- a/packages/google-auth/docs/user-guide.rst +++ b/packages/google-auth/docs/user-guide.rst @@ -329,6 +329,155 @@ Follow the detailed instructions on how to .. _Configure Workload Identity Federation from an OIDC identity provider: https://cloud.google.com/iam/docs/access-resources-oidc +Using Executable-sourced credentials with OIDC and SAML +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**Executable-sourced credentials** For executable-sourced credentials, a +local executable is used to retrieve the 3rd party token. The executable +must handle providing a valid, unexpired OIDC ID token or SAML assertion +in JSON format to stdout. + +To use executable-sourced credentials, the +``GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES`` environment variable must +be set to ``1``. + +To generate an executable-sourced workload identity configuration, run +the following command: + +.. code:: bash + + # Generate a configuration file for executable-sourced credentials. + gcloud iam workload-identity-pools create-cred-config \ + projects/$PROJECT_NUMBER/locations/global/workloadIdentityPools/$POOL_ID/providers/$PROVIDER_ID \ + --service-account=$SERVICE_ACCOUNT_EMAIL \ + --subject-token-type=$SUBJECT_TOKEN_TYPE \ + # The absolute path for the program, including arguments. + # e.g. --executable-command="/path/to/command --foo=bar" + --executable-command=$EXECUTABLE_COMMAND \ + # Optional argument for the executable timeout. Defaults to 30s. + # --executable-timeout-millis=$EXECUTABLE_TIMEOUT \ + # Optional argument for the absolute path to the executable output file. + # See below on how this argument impacts the library behaviour. + # --executable-output-file=$EXECUTABLE_OUTPUT_FILE \ + --output-file /path/to/generated/config.json + +Where the following variables need to be substituted: - +``$PROJECT_NUMBER``: The Google Cloud project number. - ``$POOL_ID``: +The workload identity pool ID. - ``$PROVIDER_ID``: The OIDC or SAML +provider ID. - ``$SERVICE_ACCOUNT_EMAIL``: The email of the service +account to impersonate. - ``$SUBJECT_TOKEN_TYPE``: The subject token +type. - ``$EXECUTABLE_COMMAND``: The full command to run, including +arguments. Must be an absolute path to the program. + +The ``--executable-timeout-millis`` flag is optional. This is the +duration for which the auth library will wait for the executable to +finish, in milliseconds. Defaults to 30 seconds when not provided. The +maximum allowed value is 2 minutes. The minimum is 5 seconds. + +The ``--executable-output-file`` flag is optional. If provided, the file +path must point to the 3PI credential response generated by the +executable. This is useful for caching the credentials. By specifying +this path, the Auth libraries will first check for its existence before +running the executable. By caching the executable JSON response to this +file, it improves performance as it avoids the need to run the +executable until the cached credentials in the output file are expired. +The executable must handle writing to this file - the auth libraries +will only attempt to read from this location. The format of contents in +the file should match the JSON format expected by the executable shown +below. + +To retrieve the 3rd party token, the library will call the executable +using the command specified. The executable’s output must adhere to the +response format specified below. It must output the response to stdout. + +A sample successful executable OIDC response: + +.. code:: json + + { + "version": 1, + "success": true, + "token_type": "urn:ietf:params:oauth:token-type:id_token", + "id_token": "HEADER.PAYLOAD.SIGNATURE", + "expiration_time": 1620499962 + } + +A sample successful executable SAML response: + +.. code:: json + + { + "version": 1, + "success": true, + "token_type": "urn:ietf:params:oauth:token-type:saml2", + "saml_response": "...", + "expiration_time": 1620499962 + } + +A sample executable error response: + +.. code:: json + + { + "version": 1, + "success": false, + "code": "401", + "message": "Caller not authorized." + } + +These are all required fields for an error response. The code and +message fields will be used by the library as part of the thrown +exception. + +Response format fields summary: ``version``: The version of the JSON +output. Currently only version 1 is supported. ``success``: The +status of the response. When true, the response must contain the 3rd +party token, token type, and expiration. The executable must also exit +with exit code 0. When false, the response must contain the error code +and message fields and exit with a non-zero value. ``token_type``: +The 3rd party subject token type. Must be +*urn:ietf:params:oauth:token-type:jwt*, +*urn:ietf:params:oauth:token-type:id_token*, or +*urn:ietf:params:oauth:token-type:saml2*. ``id_token``: The 3rd party +OIDC token. ``saml_response``: The 3rd party SAML response. +``expiration_time``: The 3rd party subject token expiration time in +seconds (unix epoch time). ``code``: The error code string. +``message``: The error message. + +All response types must include both the ``version`` and ``success`` +fields. Successful responses must include the ``token_type``, +``expiration_time``, and one of ``id_token`` or ``saml_response``. +Error responses must include both the ``code`` and ``message`` fields. + +The library will populate the following environment variables when the +executable is run: ``GOOGLE_EXTERNAL_ACCOUNT_AUDIENCE``: The audience +field from the credential configuration. Always present. +``GOOGLE_EXTERNAL_ACCOUNT_IMPERSONATED_EMAIL``: The service account +email. Only present when service account impersonation is used. +``GOOGLE_EXTERNAL_ACCOUNT_OUTPUT_FILE``: The output file location from +the credential configuration. Only present when specified in the +credential configuration. + +These environment variables can be used by the executable to avoid +hard-coding these values. + +Security considerations + + The following security practices are highly recommended: + Access to the script should be restricted as it will be displaying + credentials to stdout. This ensures that rogue processes do not gain + access to the script. The configuration file should not be + modifiable. Write access should be restricted to avoid processes + modifying the executable command portion. + +Given the complexity of using executable-sourced credentials, it is +recommended to use the existing supported mechanisms +(file-sourced/URL-sourced) for providing 3rd party credentials unless +they do not meet your specific requirements. + +You can now `use the Auth library <#using-external-identities>`__ to +call Google Cloud resources from an OIDC or SAML provider. + Using External Identities ~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -395,7 +544,7 @@ Impersonated credentials ++++++++++++++++++++++++ Impersonated Credentials allows one set of credentials issued to a user or service account -to impersonate another. The source credentials must be granted +to impersonate another. The source credentials must be granted the "Service Account Token Creator" IAM role. :: from google.auth import impersonated_credentials @@ -417,7 +566,7 @@ the "Service Account Token Creator" IAM role. :: In the example above `source_credentials` does not have direct access to list buckets -in the target project. Using `ImpersonatedCredentials` will allow the source_credentials +in the target project. Using `ImpersonatedCredentials` will allow the source_credentials to assume the identity of a target_principal that does have access. diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index 3a4190389881..a2a07800a953 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -324,7 +324,7 @@ def _get_external_account_credentials( google.auth.exceptions.DefaultCredentialsError: if the info dictionary is in the wrong format or is missing required information. """ - # There are currently 2 types of external_account credentials. + # There are currently 3 types of external_account credentials. if info.get("subject_token_type") == _AWS_SUBJECT_TOKEN_TYPE: # Check if configuration corresponds to an AWS credentials. from google.auth import aws @@ -332,6 +332,15 @@ def _get_external_account_credentials( credentials = aws.Credentials.from_info( info, scopes=scopes, default_scopes=default_scopes ) + elif ( + info.get("credential_source") is not None + and info.get("credential_source").get("executable") is not None + ): + from google.auth import pluggable + + credentials = pluggable.Credentials.from_info( + info, scopes=scopes, default_scopes=default_scopes + ) else: try: # Check if configuration corresponds to an Identity Pool credentials. diff --git a/packages/google-auth/google/auth/pluggable.py b/packages/google-auth/google/auth/pluggable.py new file mode 100644 index 000000000000..12cd6240e16f --- /dev/null +++ b/packages/google-auth/google/auth/pluggable.py @@ -0,0 +1,322 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Pluggable Credentials. +Pluggable Credentials are initialized using external_account arguments which +are typically loaded from third-party executables. Unlike other +credentials that can be initialized with a list of explicit arguments, secrets +or credentials, external account clients use the environment and hints/guidelines +provided by the external_account JSON file to retrieve credentials and exchange +them for Google access tokens. + +Example credential_source for pluggable credential: +{ + "executable": { + "command": "/path/to/get/credentials.sh --arg1=value1 --arg2=value2", + "timeout_millis": 5000, + "output_file": "/path/to/generated/cached/credentials" + } +} +""" + +try: + from collections.abc import Mapping +# Python 2.7 compatibility +except ImportError: # pragma: NO COVER + from collections import Mapping +import io +import json +import os +import subprocess +import time + +from google.auth import _helpers +from google.auth import exceptions +from google.auth import external_account + +# The max supported executable spec version. +EXECUTABLE_SUPPORTED_MAX_VERSION = 1 + + +class Credentials(external_account.Credentials): + """External account credentials sourced from executables.""" + + def __init__( + self, + audience, + subject_token_type, + token_url, + credential_source, + service_account_impersonation_url=None, + client_id=None, + client_secret=None, + quota_project_id=None, + scopes=None, + default_scopes=None, + workforce_pool_user_project=None, + ): + """Instantiates an external account credentials object from a executables. + + Args: + audience (str): The STS audience field. + subject_token_type (str): The subject token type. + token_url (str): The STS endpoint URL. + credential_source (Mapping): The credential source dictionary used to + provide instructions on how to retrieve external credential to be + exchanged for Google access tokens. + + Example credential_source for pluggable credential: + + { + "executable": { + "command": "/path/to/get/credentials.sh --arg1=value1 --arg2=value2", + "timeout_millis": 5000, + "output_file": "/path/to/generated/cached/credentials" + } + } + + service_account_impersonation_url (Optional[str]): The optional service account + impersonation getAccessToken URL. + client_id (Optional[str]): The optional client ID. + client_secret (Optional[str]): The optional client secret. + quota_project_id (Optional[str]): The optional quota project ID. + scopes (Optional[Sequence[str]]): Optional scopes to request during the + authorization grant. + default_scopes (Optional[Sequence[str]]): Default scopes passed by a + Google client library. Use 'scopes' for user-defined scopes. + workforce_pool_user_project (Optona[str]): The optional workforce pool user + project number when the credential corresponds to a workforce pool and not + a workload Pluggable. The underlying principal must still have + serviceusage.services.use IAM permission to use the project for + billing/quota. + + Raises: + google.auth.exceptions.RefreshError: If an error is encountered during + access token retrieval logic. + ValueError: For invalid parameters. + + .. note:: Typically one of the helper constructors + :meth:`from_file` or + :meth:`from_info` are used instead of calling the constructor directly. + """ + + super(Credentials, self).__init__( + audience=audience, + subject_token_type=subject_token_type, + token_url=token_url, + credential_source=credential_source, + service_account_impersonation_url=service_account_impersonation_url, + client_id=client_id, + client_secret=client_secret, + quota_project_id=quota_project_id, + scopes=scopes, + default_scopes=default_scopes, + workforce_pool_user_project=workforce_pool_user_project, + ) + if not isinstance(credential_source, Mapping): + self._credential_source_executable = None + raise ValueError( + "Missing credential_source. The credential_source is not a dict." + ) + self._credential_source_executable = credential_source.get("executable") + if not self._credential_source_executable: + raise ValueError( + "Missing credential_source. An 'executable' must be provided." + ) + self._credential_source_executable_command = self._credential_source_executable.get( + "command" + ) + self._credential_source_executable_timeout_millis = self._credential_source_executable.get( + "timeout_millis" + ) + self._credential_source_executable_output_file = self._credential_source_executable.get( + "output_file" + ) + + if not self._credential_source_executable_command: + raise ValueError( + "Missing command field. Executable command must be provided." + ) + if not self._credential_source_executable_timeout_millis: + self._credential_source_executable_timeout_millis = 30 * 1000 + elif ( + self._credential_source_executable_timeout_millis < 5 * 1000 + or self._credential_source_executable_timeout_millis > 120 * 1000 + ): + raise ValueError("Timeout must be between 5 and 120 seconds.") + + @_helpers.copy_docstring(external_account.Credentials) + def retrieve_subject_token(self, request): + env_allow_executables = os.environ.get( + "GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES" + ) + if env_allow_executables != "1": + raise ValueError( + "Executables need to be explicitly allowed (set GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES to '1') to run." + ) + + # Check output file. + if self._credential_source_executable_output_file is not None: + try: + with open( + self._credential_source_executable_output_file + ) as output_file: + response = json.load(output_file) + except Exception: + pass + else: + try: + # If the cached response is expired, _parse_subject_token will raise an error which will be ignored and we will call the executable again. + subject_token = self._parse_subject_token(response) + except ValueError: + raise + except exceptions.RefreshError: + pass + else: + return subject_token + + if not _helpers.is_python_3(): + raise exceptions.RefreshError( + "Pluggable auth is only supported for python 3.6+" + ) + + # Inject env vars. + env = os.environ.copy() + env["GOOGLE_EXTERNAL_ACCOUNT_AUDIENCE"] = self._audience + env["GOOGLE_EXTERNAL_ACCOUNT_TOKEN_TYPE"] = self._subject_token_type + env[ + "GOOGLE_EXTERNAL_ACCOUNT_INTERACTIVE" + ] = "0" # Always set to 0 until interactive mode is implemented. + if self._service_account_impersonation_url is not None: + env[ + "GOOGLE_EXTERNAL_ACCOUNT_IMPERSONATED_EMAIL" + ] = self.service_account_email + if self._credential_source_executable_output_file is not None: + env[ + "GOOGLE_EXTERNAL_ACCOUNT_OUTPUT_FILE" + ] = self._credential_source_executable_output_file + + try: + result = subprocess.run( + self._credential_source_executable_command.split(), + timeout=self._credential_source_executable_timeout_millis / 1000, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + env=env, + ) + if result.returncode != 0: + raise exceptions.RefreshError( + "Executable exited with non-zero return code {}. Error: {}".format( + result.returncode, result.stdout + ) + ) + except Exception: + raise + else: + try: + data = result.stdout.decode("utf-8") + response = json.loads(data) + subject_token = self._parse_subject_token(response) + except Exception: + raise + + return subject_token + + @classmethod + def from_info(cls, info, **kwargs): + """Creates a Pluggable Credentials instance from parsed external account info. + + Args: + info (Mapping[str, str]): The Pluggable external account info in Google + format. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.pluggable.Credentials: The constructed + credentials. + + Raises: + ValueError: For invalid parameters. + """ + return cls( + audience=info.get("audience"), + subject_token_type=info.get("subject_token_type"), + token_url=info.get("token_url"), + service_account_impersonation_url=info.get( + "service_account_impersonation_url" + ), + client_id=info.get("client_id"), + client_secret=info.get("client_secret"), + credential_source=info.get("credential_source"), + quota_project_id=info.get("quota_project_id"), + workforce_pool_user_project=info.get("workforce_pool_user_project"), + **kwargs + ) + + @classmethod + def from_file(cls, filename, **kwargs): + """Creates an Pluggable Credentials instance from an external account json file. + + Args: + filename (str): The path to the Pluggable external account json file. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.pluggable.Credentials: The constructed + credentials. + """ + with io.open(filename, "r", encoding="utf-8") as json_file: + data = json.load(json_file) + return cls.from_info(data, **kwargs) + + def _parse_subject_token(self, response): + if "version" not in response: + raise ValueError("The executable response is missing the version field.") + if response["version"] > EXECUTABLE_SUPPORTED_MAX_VERSION: + raise exceptions.RefreshError( + "Executable returned unsupported version {}.".format( + response["version"] + ) + ) + if "success" not in response: + raise ValueError("The executable response is missing the success field.") + if not response["success"]: + if "code" not in response or "message" not in response: + raise ValueError( + "Error code and message fields are required in the response." + ) + raise exceptions.RefreshError( + "Executable returned unsuccessful response: code: {}, message: {}.".format( + response["code"], response["message"] + ) + ) + if "expiration_time" not in response: + raise ValueError( + "The executable response is missing the expiration_time field." + ) + if response["expiration_time"] < time.time(): + raise exceptions.RefreshError( + "The token returned by the executable is expired." + ) + if "token_type" not in response: + raise ValueError("The executable response is missing the token_type field.") + if ( + response["token_type"] == "urn:ietf:params:oauth:token-type:jwt" + or response["token_type"] == "urn:ietf:params:oauth:token-type:id_token" + ): # OIDC + return response["id_token"] + elif response["token_type"] == "urn:ietf:params:oauth:token-type:saml2": # SAML + return response["saml_response"] + else: + raise exceptions.RefreshError("Executable returned unsupported token type.") diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index 7221315b0145..18a7232e43e5 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -117,6 +117,7 @@ def unit_prev_versions(session): "--cov=google.auth", "--cov=google.oauth2", "--cov=tests", + "--ignore=tests/test_pluggable.py", # Pluggable auth only support 3.6+ for now. "tests", "--ignore=tests/transport/test__custom_tls_signer.py", # enterprise cert is for python 3.6+ ) diff --git a/packages/google-auth/tests/test__default.py b/packages/google-auth/tests/test__default.py index 61772c2e33cb..5ea9c73c5ff7 100644 --- a/packages/google-auth/tests/test__default.py +++ b/packages/google-auth/tests/test__default.py @@ -28,6 +28,7 @@ from google.auth import external_account from google.auth import identity_pool from google.auth import impersonated_credentials +from google.auth import pluggable from google.oauth2 import gdch_credentials from google.oauth2 import service_account import google.oauth2.credentials @@ -75,6 +76,13 @@ "token_url": TOKEN_URL, "credential_source": {"file": SUBJECT_TOKEN_TEXT_FILE}, } +PLUGGABLE_DATA = { + "type": "external_account", + "audience": AUDIENCE, + "subject_token_type": "urn:ietf:params:oauth:token-type:jwt", + "token_url": TOKEN_URL, + "credential_source": {"executable": {"command": "command"}}, +} AWS_DATA = { "type": "external_account", "audience": AUDIENCE, @@ -1153,6 +1161,18 @@ def test_default_impersonated_service_account_set_both_scopes_and_default_scopes assert credentials._target_scopes == scopes +@EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH +def test_load_credentials_from_external_account_pluggable(get_project_id, tmpdir): + config_file = tmpdir.join("config.json") + config_file.write(json.dumps(PLUGGABLE_DATA)) + credentials, project_id = _default.load_credentials_from_file(str(config_file)) + + assert isinstance(credentials, pluggable.Credentials) + # Since no scopes are specified, the project ID cannot be determined. + assert project_id is None + assert get_project_id.called + + @mock.patch( "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True ) diff --git a/packages/google-auth/tests/test_pluggable.py b/packages/google-auth/tests/test_pluggable.py new file mode 100644 index 000000000000..61ddabd45717 --- /dev/null +++ b/packages/google-auth/tests/test_pluggable.py @@ -0,0 +1,752 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# import datetime +import json +import os +import subprocess + +import mock +import pytest # type: ignore + +# from six.moves import http_client +# from six.moves import urllib + +# from google.auth import _helpers +from google.auth import exceptions +from google.auth import pluggable + +# from google.auth import transport + + +CLIENT_ID = "username" +CLIENT_SECRET = "password" +# Base64 encoding of "username:password". +BASIC_AUTH_ENCODING = "dXNlcm5hbWU6cGFzc3dvcmQ=" +SERVICE_ACCOUNT_EMAIL = "service-1234@service-name.iam.gserviceaccount.com" +SERVICE_ACCOUNT_IMPERSONATION_URL = ( + "https://us-east1-iamcredentials.googleapis.com/v1/projects/-" + + "/serviceAccounts/{}:generateAccessToken".format(SERVICE_ACCOUNT_EMAIL) +) +QUOTA_PROJECT_ID = "QUOTA_PROJECT_ID" +SCOPES = ["scope1", "scope2"] +SUBJECT_TOKEN_FIELD_NAME = "access_token" + +TOKEN_URL = "https://sts.googleapis.com/v1/token" +SERVICE_ACCOUNT_IMPERSONATION_URL = "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/byoid-test@cicpclientproj.iam.gserviceaccount.com:generateAccessToken" +SUBJECT_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:jwt" +AUDIENCE = "//iam.googleapis.com/projects/123456/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID" + + +class TestCredentials(object): + CREDENTIAL_SOURCE_EXECUTABLE_COMMAND = ( + "/fake/external/excutable --arg1=value1 --arg2=value2" + ) + CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE = "fake_output_file" + CREDENTIAL_SOURCE_EXECUTABLE = { + "command": CREDENTIAL_SOURCE_EXECUTABLE_COMMAND, + "timeout_millis": 30000, + "output_file": CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, + } + CREDENTIAL_SOURCE = {"executable": CREDENTIAL_SOURCE_EXECUTABLE} + EXECUTABLE_OIDC_TOKEN = "FAKE_ID_TOKEN" + EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_ID_TOKEN = { + "version": 1, + "success": True, + "token_type": "urn:ietf:params:oauth:token-type:id_token", + "id_token": EXECUTABLE_OIDC_TOKEN, + "expiration_time": 9999999999, + } + EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_JWT = { + "version": 1, + "success": True, + "token_type": "urn:ietf:params:oauth:token-type:jwt", + "id_token": EXECUTABLE_OIDC_TOKEN, + "expiration_time": 9999999999, + } + EXECUTABLE_SAML_TOKEN = "FAKE_SAML_RESPONSE" + EXECUTABLE_SUCCESSFUL_SAML_RESPONSE = { + "version": 1, + "success": True, + "token_type": "urn:ietf:params:oauth:token-type:saml2", + "saml_response": EXECUTABLE_SAML_TOKEN, + "expiration_time": 9999999999, + } + EXECUTABLE_FAILED_RESPONSE = { + "version": 1, + "success": False, + "code": "401", + "message": "Permission denied. Caller not authorized", + } + CREDENTIAL_URL = "http://fakeurl.com" + + @classmethod + def make_pluggable( + cls, + audience=AUDIENCE, + subject_token_type=SUBJECT_TOKEN_TYPE, + client_id=None, + client_secret=None, + quota_project_id=None, + scopes=None, + default_scopes=None, + service_account_impersonation_url=None, + credential_source=None, + workforce_pool_user_project=None, + ): + return pluggable.Credentials( + audience=audience, + subject_token_type=subject_token_type, + token_url=TOKEN_URL, + service_account_impersonation_url=service_account_impersonation_url, + credential_source=credential_source, + client_id=client_id, + client_secret=client_secret, + quota_project_id=quota_project_id, + scopes=scopes, + default_scopes=default_scopes, + workforce_pool_user_project=workforce_pool_user_project, + ) + + @mock.patch.object(pluggable.Credentials, "__init__", return_value=None) + def test_from_info_full_options(self, mock_init): + credentials = pluggable.Credentials.from_info( + { + "audience": AUDIENCE, + "subject_token_type": SUBJECT_TOKEN_TYPE, + "token_url": TOKEN_URL, + "service_account_impersonation_url": SERVICE_ACCOUNT_IMPERSONATION_URL, + "client_id": CLIENT_ID, + "client_secret": CLIENT_SECRET, + "quota_project_id": QUOTA_PROJECT_ID, + "credential_source": self.CREDENTIAL_SOURCE, + } + ) + + # Confirm pluggable.Credentials instantiated with expected attributes. + assert isinstance(credentials, pluggable.Credentials) + mock_init.assert_called_once_with( + audience=AUDIENCE, + subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + client_id=CLIENT_ID, + client_secret=CLIENT_SECRET, + credential_source=self.CREDENTIAL_SOURCE, + quota_project_id=QUOTA_PROJECT_ID, + workforce_pool_user_project=None, + ) + + @mock.patch.object(pluggable.Credentials, "__init__", return_value=None) + def test_from_info_required_options_only(self, mock_init): + credentials = pluggable.Credentials.from_info( + { + "audience": AUDIENCE, + "subject_token_type": SUBJECT_TOKEN_TYPE, + "token_url": TOKEN_URL, + "credential_source": self.CREDENTIAL_SOURCE, + } + ) + + # Confirm pluggable.Credentials instantiated with expected attributes. + assert isinstance(credentials, pluggable.Credentials) + mock_init.assert_called_once_with( + audience=AUDIENCE, + subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + service_account_impersonation_url=None, + client_id=None, + client_secret=None, + credential_source=self.CREDENTIAL_SOURCE, + quota_project_id=None, + workforce_pool_user_project=None, + ) + + @mock.patch.object(pluggable.Credentials, "__init__", return_value=None) + def test_from_file_full_options(self, mock_init, tmpdir): + info = { + "audience": AUDIENCE, + "subject_token_type": SUBJECT_TOKEN_TYPE, + "token_url": TOKEN_URL, + "service_account_impersonation_url": SERVICE_ACCOUNT_IMPERSONATION_URL, + "client_id": CLIENT_ID, + "client_secret": CLIENT_SECRET, + "quota_project_id": QUOTA_PROJECT_ID, + "credential_source": self.CREDENTIAL_SOURCE, + } + config_file = tmpdir.join("config.json") + config_file.write(json.dumps(info)) + credentials = pluggable.Credentials.from_file(str(config_file)) + + # Confirm pluggable.Credentials instantiated with expected attributes. + assert isinstance(credentials, pluggable.Credentials) + mock_init.assert_called_once_with( + audience=AUDIENCE, + subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + client_id=CLIENT_ID, + client_secret=CLIENT_SECRET, + credential_source=self.CREDENTIAL_SOURCE, + quota_project_id=QUOTA_PROJECT_ID, + workforce_pool_user_project=None, + ) + + @mock.patch.object(pluggable.Credentials, "__init__", return_value=None) + def test_from_file_required_options_only(self, mock_init, tmpdir): + info = { + "audience": AUDIENCE, + "subject_token_type": SUBJECT_TOKEN_TYPE, + "token_url": TOKEN_URL, + "credential_source": self.CREDENTIAL_SOURCE, + } + config_file = tmpdir.join("config.json") + config_file.write(json.dumps(info)) + credentials = pluggable.Credentials.from_file(str(config_file)) + + # Confirm pluggable.Credentials instantiated with expected attributes. + assert isinstance(credentials, pluggable.Credentials) + mock_init.assert_called_once_with( + audience=AUDIENCE, + subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + service_account_impersonation_url=None, + client_id=None, + client_secret=None, + credential_source=self.CREDENTIAL_SOURCE, + quota_project_id=None, + workforce_pool_user_project=None, + ) + + def test_constructor_invalid_options(self): + credential_source = {"unsupported": "value"} + + with pytest.raises(ValueError) as excinfo: + self.make_pluggable(credential_source=credential_source) + + assert excinfo.match(r"Missing credential_source") + + def test_constructor_invalid_credential_source(self): + with pytest.raises(ValueError) as excinfo: + self.make_pluggable(credential_source="non-dict") + + assert excinfo.match(r"Missing credential_source") + + def test_info_with_credential_source(self): + credentials = self.make_pluggable( + credential_source=self.CREDENTIAL_SOURCE.copy() + ) + + assert credentials.info == { + "type": "external_account", + "audience": AUDIENCE, + "subject_token_type": SUBJECT_TOKEN_TYPE, + "token_url": TOKEN_URL, + "credential_source": self.CREDENTIAL_SOURCE, + } + + @mock.patch.dict( + os.environ, + { + "GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1", + "GOOGLE_EXTERNAL_ACCOUNT_AUDIENCE": "original_audience", + "GOOGLE_EXTERNAL_ACCOUNT_TOKEN_TYPE": "original_token_type", + "GOOGLE_EXTERNAL_ACCOUNT_INTERACTIVE": "0", + "GOOGLE_EXTERNAL_ACCOUNT_IMPERSONATED_EMAIL": "original_impersonated_email", + "GOOGLE_EXTERNAL_ACCOUNT_OUTPUT_FILE": "original_output_file", + }, + ) + def test_retrieve_subject_token_oidc_id_token(self): + with mock.patch( + "subprocess.run", + return_value=subprocess.CompletedProcess( + args=[], + stdout=json.dumps( + self.EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_ID_TOKEN + ).encode("UTF-8"), + returncode=0, + ), + ): + credentials = self.make_pluggable( + audience=AUDIENCE, + service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + credential_source=self.CREDENTIAL_SOURCE, + ) + + subject_token = credentials.retrieve_subject_token(None) + + assert subject_token == self.EXECUTABLE_OIDC_TOKEN + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_retrieve_subject_token_oidc_jwt(self): + with mock.patch( + "subprocess.run", + return_value=subprocess.CompletedProcess( + args=[], + stdout=json.dumps(self.EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_JWT).encode( + "UTF-8" + ), + returncode=0, + ), + ): + credentials = self.make_pluggable( + audience=AUDIENCE, + service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + credential_source=self.CREDENTIAL_SOURCE, + ) + + subject_token = credentials.retrieve_subject_token(None) + + assert subject_token == self.EXECUTABLE_OIDC_TOKEN + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_retrieve_subject_token_saml(self): + with mock.patch( + "subprocess.run", + return_value=subprocess.CompletedProcess( + args=[], + stdout=json.dumps(self.EXECUTABLE_SUCCESSFUL_SAML_RESPONSE).encode( + "UTF-8" + ), + returncode=0, + ), + ): + credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE) + + subject_token = credentials.retrieve_subject_token(None) + + assert subject_token == self.EXECUTABLE_SAML_TOKEN + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_retrieve_subject_token_failed(self): + with mock.patch( + "subprocess.run", + return_value=subprocess.CompletedProcess( + args=[], + stdout=json.dumps(self.EXECUTABLE_FAILED_RESPONSE).encode("UTF-8"), + returncode=0, + ), + ): + credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE) + + with pytest.raises(exceptions.RefreshError) as excinfo: + _ = credentials.retrieve_subject_token(None) + + assert excinfo.match( + r"Executable returned unsuccessful response: code: 401, message: Permission denied. Caller not authorized." + ) + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "0"}) + def test_retrieve_subject_token_not_allowd(self): + with mock.patch( + "subprocess.run", + return_value=subprocess.CompletedProcess( + args=[], + stdout=json.dumps( + self.EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_ID_TOKEN + ).encode("UTF-8"), + returncode=0, + ), + ): + credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE) + + with pytest.raises(ValueError) as excinfo: + _ = credentials.retrieve_subject_token(None) + + assert excinfo.match(r"Executables need to be explicitly allowed") + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_retrieve_subject_token_invalid_version(self): + EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_VERSION_2 = { + "version": 2, + "success": True, + "token_type": "urn:ietf:params:oauth:token-type:id_token", + "id_token": self.EXECUTABLE_OIDC_TOKEN, + "expiration_time": 9999999999, + } + + with mock.patch( + "subprocess.run", + return_value=subprocess.CompletedProcess( + args=[], + stdout=json.dumps(EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_VERSION_2).encode( + "UTF-8" + ), + returncode=0, + ), + ): + credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE) + + with pytest.raises(exceptions.RefreshError) as excinfo: + _ = credentials.retrieve_subject_token(None) + + assert excinfo.match(r"Executable returned unsupported version.") + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_retrieve_subject_token_expired_token(self): + EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_EXPIRED = { + "version": 1, + "success": True, + "token_type": "urn:ietf:params:oauth:token-type:id_token", + "id_token": self.EXECUTABLE_OIDC_TOKEN, + "expiration_time": 0, + } + + with mock.patch( + "subprocess.run", + return_value=subprocess.CompletedProcess( + args=[], + stdout=json.dumps(EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_EXPIRED).encode( + "UTF-8" + ), + returncode=0, + ), + ): + credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE) + + with pytest.raises(exceptions.RefreshError) as excinfo: + _ = credentials.retrieve_subject_token(None) + + assert excinfo.match(r"The token returned by the executable is expired.") + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_retrieve_subject_token_file_cache(self): + ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE = "actual_output_file" + ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE = { + "command": "command", + "timeout_millis": 30000, + "output_file": ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, + } + ACTUAL_CREDENTIAL_SOURCE = {"executable": ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE} + with open(ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, "w") as output_file: + json.dump(self.EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_ID_TOKEN, output_file) + + credentials = self.make_pluggable(credential_source=ACTUAL_CREDENTIAL_SOURCE) + + subject_token = credentials.retrieve_subject_token(None) + assert subject_token == self.EXECUTABLE_OIDC_TOKEN + + os.remove(ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE) + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_retrieve_subject_token_no_file_cache(self): + ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE = { + "command": "command", + "timeout_millis": 30000, + } + ACTUAL_CREDENTIAL_SOURCE = {"executable": ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE} + + with mock.patch( + "subprocess.run", + return_value=subprocess.CompletedProcess( + args=[], + stdout=json.dumps( + self.EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_ID_TOKEN + ).encode("UTF-8"), + returncode=0, + ), + ): + credentials = self.make_pluggable( + credential_source=ACTUAL_CREDENTIAL_SOURCE + ) + + subject_token = credentials.retrieve_subject_token(None) + + assert subject_token == self.EXECUTABLE_OIDC_TOKEN + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_retrieve_subject_token_file_cache_value_error_report(self): + ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE = "actual_output_file" + ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE = { + "command": "command", + "timeout_millis": 30000, + "output_file": ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, + } + ACTUAL_CREDENTIAL_SOURCE = {"executable": ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE} + ACTUAL_EXECUTABLE_RESPONSE = { + "success": True, + "token_type": "urn:ietf:params:oauth:token-type:id_token", + "id_token": self.EXECUTABLE_OIDC_TOKEN, + "expiration_time": 9999999999, + } + with open(ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, "w") as output_file: + json.dump(ACTUAL_EXECUTABLE_RESPONSE, output_file) + + credentials = self.make_pluggable(credential_source=ACTUAL_CREDENTIAL_SOURCE) + + with pytest.raises(ValueError) as excinfo: + _ = credentials.retrieve_subject_token(None) + + assert excinfo.match(r"The executable response is missing the version field.") + + os.remove(ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE) + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_retrieve_subject_token_file_cache_refresh_error_retry(self): + ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE = "actual_output_file" + ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE = { + "command": "command", + "timeout_millis": 30000, + "output_file": ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, + } + ACTUAL_CREDENTIAL_SOURCE = {"executable": ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE} + ACTUAL_EXECUTABLE_RESPONSE = { + "version": 2, + "success": True, + "token_type": "urn:ietf:params:oauth:token-type:id_token", + "id_token": self.EXECUTABLE_OIDC_TOKEN, + "expiration_time": 9999999999, + } + with open(ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, "w") as output_file: + json.dump(ACTUAL_EXECUTABLE_RESPONSE, output_file) + + with mock.patch( + "subprocess.run", + return_value=subprocess.CompletedProcess( + args=[], + stdout=json.dumps( + self.EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_ID_TOKEN + ).encode("UTF-8"), + returncode=0, + ), + ): + credentials = self.make_pluggable( + credential_source=ACTUAL_CREDENTIAL_SOURCE + ) + + subject_token = credentials.retrieve_subject_token(None) + + assert subject_token == self.EXECUTABLE_OIDC_TOKEN + + os.remove(ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE) + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_retrieve_subject_token_unsupported_token_type(self): + EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE = { + "version": 1, + "success": True, + "token_type": "unsupported_token_type", + "id_token": self.EXECUTABLE_OIDC_TOKEN, + "expiration_time": 9999999999, + } + + with mock.patch( + "subprocess.run", + return_value=subprocess.CompletedProcess( + args=[], + stdout=json.dumps(EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE).encode("UTF-8"), + returncode=0, + ), + ): + credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE) + + with pytest.raises(exceptions.RefreshError) as excinfo: + _ = credentials.retrieve_subject_token(None) + + assert excinfo.match(r"Executable returned unsupported token type.") + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_retrieve_subject_token_missing_version(self): + EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE = { + "success": True, + "token_type": "urn:ietf:params:oauth:token-type:id_token", + "id_token": self.EXECUTABLE_OIDC_TOKEN, + "expiration_time": 9999999999, + } + + with mock.patch( + "subprocess.run", + return_value=subprocess.CompletedProcess( + args=[], + stdout=json.dumps(EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE).encode("UTF-8"), + returncode=0, + ), + ): + credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE) + + with pytest.raises(ValueError) as excinfo: + _ = credentials.retrieve_subject_token(None) + + assert excinfo.match( + r"The executable response is missing the version field." + ) + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_retrieve_subject_token_missing_success(self): + EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE = { + "version": 1, + "token_type": "urn:ietf:params:oauth:token-type:id_token", + "id_token": self.EXECUTABLE_OIDC_TOKEN, + "expiration_time": 9999999999, + } + + with mock.patch( + "subprocess.run", + return_value=subprocess.CompletedProcess( + args=[], + stdout=json.dumps(EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE).encode("UTF-8"), + returncode=0, + ), + ): + credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE) + + with pytest.raises(ValueError) as excinfo: + _ = credentials.retrieve_subject_token(None) + + assert excinfo.match( + r"The executable response is missing the success field." + ) + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_retrieve_subject_token_missing_error_code_message(self): + EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE = {"version": 1, "success": False} + + with mock.patch( + "subprocess.run", + return_value=subprocess.CompletedProcess( + args=[], + stdout=json.dumps(EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE).encode("UTF-8"), + returncode=0, + ), + ): + credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE) + + with pytest.raises(ValueError) as excinfo: + _ = credentials.retrieve_subject_token(None) + + assert excinfo.match( + r"Error code and message fields are required in the response." + ) + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_retrieve_subject_token_missing_expiration_time(self): + EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE = { + "version": 1, + "success": True, + "token_type": "urn:ietf:params:oauth:token-type:id_token", + "id_token": self.EXECUTABLE_OIDC_TOKEN, + } + + with mock.patch( + "subprocess.run", + return_value=subprocess.CompletedProcess( + args=[], + stdout=json.dumps(EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE).encode("UTF-8"), + returncode=0, + ), + ): + credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE) + + with pytest.raises(ValueError) as excinfo: + _ = credentials.retrieve_subject_token(None) + + assert excinfo.match( + r"The executable response is missing the expiration_time field." + ) + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_retrieve_subject_token_missing_token_type(self): + EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE = { + "version": 1, + "success": True, + "id_token": self.EXECUTABLE_OIDC_TOKEN, + "expiration_time": 9999999999, + } + + with mock.patch( + "subprocess.run", + return_value=subprocess.CompletedProcess( + args=[], + stdout=json.dumps(EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE).encode("UTF-8"), + returncode=0, + ), + ): + credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE) + + with pytest.raises(ValueError) as excinfo: + _ = credentials.retrieve_subject_token(None) + + assert excinfo.match( + r"The executable response is missing the token_type field." + ) + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_credential_source_missing_command(self): + with pytest.raises(ValueError) as excinfo: + CREDENTIAL_SOURCE = { + "executable": { + "timeout_millis": 30000, + "output_file": self.CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, + } + } + _ = self.make_pluggable(credential_source=CREDENTIAL_SOURCE) + + assert excinfo.match( + r"Missing command field. Executable command must be provided." + ) + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_credential_source_timeout_small(self): + with pytest.raises(ValueError) as excinfo: + CREDENTIAL_SOURCE = { + "executable": { + "command": self.CREDENTIAL_SOURCE_EXECUTABLE_COMMAND, + "timeout_millis": 5000 - 1, + "output_file": self.CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, + } + } + _ = self.make_pluggable(credential_source=CREDENTIAL_SOURCE) + + assert excinfo.match(r"Timeout must be between 5 and 120 seconds.") + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_credential_source_timeout_large(self): + with pytest.raises(ValueError) as excinfo: + CREDENTIAL_SOURCE = { + "executable": { + "command": self.CREDENTIAL_SOURCE_EXECUTABLE_COMMAND, + "timeout_millis": 120000 + 1, + "output_file": self.CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, + } + } + _ = self.make_pluggable(credential_source=CREDENTIAL_SOURCE) + + assert excinfo.match(r"Timeout must be between 5 and 120 seconds.") + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_retrieve_subject_token_executable_fail(self): + with mock.patch( + "subprocess.run", + return_value=subprocess.CompletedProcess( + args=[], stdout=None, returncode=1 + ), + ): + credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE) + + with pytest.raises(exceptions.RefreshError) as excinfo: + _ = credentials.retrieve_subject_token(None) + + assert excinfo.match( + r"Executable exited with non-zero return code 1. Error: None" + ) + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_retrieve_subject_token_python_2(self): + with mock.patch("sys.version_info", (2, 7)): + credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE) + + with pytest.raises(exceptions.RefreshError) as excinfo: + _ = credentials.retrieve_subject_token(None) + + assert excinfo.match(r"Pluggable auth is only supported for python 3.6+") From 646f7d26daf5adaf03a4d72a236c6a060b079edb Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 28 Jun 2022 20:37:16 -0700 Subject: [PATCH 585/966] chore(main): release 2.9.0 (#1071) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 77d05fca544b..ad367ae481f4 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.9.0](https://github.com/googleapis/google-auth-library-python/compare/v2.8.0...v2.9.0) (2022-06-28) + + +### Features + +* pluggable auth support ([#1045](https://github.com/googleapis/google-auth-library-python/issues/1045)) ([de14f4e](https://github.com/googleapis/google-auth-library-python/commit/de14f4e855c081c08f14cb0211a06107e5314bf7)) + ## [2.8.0](https://github.com/googleapis/google-auth-library-python/compare/v2.7.0...v2.8.0) (2022-06-14) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index b4f8545fc130..22b60f947bcd 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.8.0" +__version__ = "2.9.0" From cbb62f9e6ba0e5e807f2c1c54905e78b1ad4ab4b Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Tue, 12 Jul 2022 00:48:15 +0000 Subject: [PATCH 586/966] fix: there was a raise missing for throwing exceptions (#1077) --- .../google/auth/impersonated_credentials.py | 2 +- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index 00cabac3a26d..72e61a1fe957 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -100,7 +100,7 @@ def _make_iam_token_request( ) if response.status != http_client.OK: - exceptions.RefreshError(_REFRESH_ERROR, response_body) + raise exceptions.RefreshError(_REFRESH_ERROR, response_body) try: token_response = json.loads(response_body) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 76bc77ae31e71d31181fc25b7ebb6b8b0e2b0052..9baa3acd2cf4cf3d0bf30350bb91a10db05c2376 100644 GIT binary patch literal 10324 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 From 41dff9817521cf49b2e0846eb8df366bca5dc918 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 12 Jul 2022 15:48:19 -0700 Subject: [PATCH 587/966] chore(main): release 2.9.1 (#1078) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index ad367ae481f4..685ef1cdcd16 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.9.1](https://github.com/googleapis/google-auth-library-python/compare/v2.9.0...v2.9.1) (2022-07-12) + + +### Bug Fixes + +* there was a raise missing for throwing exceptions ([#1077](https://github.com/googleapis/google-auth-library-python/issues/1077)) ([d1f17b0](https://github.com/googleapis/google-auth-library-python/commit/d1f17b0ba71d90bff2402fde38bdbe51bc6481f9)) + ## [2.9.0](https://github.com/googleapis/google-auth-library-python/compare/v2.8.0...v2.9.0) (2022-06-28) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 22b60f947bcd..a6759c20a9b3 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.9.0" +__version__ = "2.9.1" From fcec6012ebfa03332b20b2f2e0c5756dad8c7a69 Mon Sep 17 00:00:00 2001 From: clundin25 <108372512+clundin25@users.noreply.github.com> Date: Fri, 22 Jul 2022 10:38:46 -0700 Subject: [PATCH 588/966] fix: Async system tests were not unwrapping async_generators (#1086) Currently the system tests were failing with a message "TypeError: 'async_generator' object is not callable". This is because newer versions of pytest_asyncio uses "strict" mode by default which does not automatically convert fixtures to async fixtures in async test cases. This became an issue as of pytest_asyncio version 19. The solution is to either switch the mode to "auto" in the nox commandline or to mark the async fixtures as async fixtures. I've opted for the second to avoid making this hard to discover. --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../system_tests_async/conftest.py | 15 ++++++++------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 9baa3acd2cf4cf3d0bf30350bb91a10db05c2376..040759bada7b7c78228ffd32cf999251731d2dba 100644 GIT binary patch literal 10324 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 m5r;`IP636yrM(D1HIqE1*Pa6G&2M3__y- literal 10324 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 m4;->JcLrlr Date: Fri, 22 Jul 2022 11:56:19 -0700 Subject: [PATCH 589/966] fix: async certificate decoding (#1085) The async `_cert_fetch` implementation was not properly decoding certificates into a utf-8 string. This updates the code and tests to decode the certificates into strings. This resolves https://github.com/googleapis/google-auth-library-python/issues/1050. --- packages/google-auth/google/oauth2/_id_token_async.py | 2 +- packages/google-auth/tests_async/oauth2/test_id_token.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/google/oauth2/_id_token_async.py b/packages/google-auth/google/oauth2/_id_token_async.py index 20630e0d4a91..b90994cdcc7a 100644 --- a/packages/google-auth/google/oauth2/_id_token_async.py +++ b/packages/google-auth/google/oauth2/_id_token_async.py @@ -95,7 +95,7 @@ async def _fetch_certs(request, certs_url): data = await response.data.read() - return json.loads(json.dumps(data)) + return json.loads(data.decode("utf-8")) async def verify_token( diff --git a/packages/google-auth/tests_async/oauth2/test_id_token.py b/packages/google-auth/tests_async/oauth2/test_id_token.py index b84e74db2ee0..a52b8b4e00de 100644 --- a/packages/google-auth/tests_async/oauth2/test_id_token.py +++ b/packages/google-auth/tests_async/oauth2/test_id_token.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import json import os import mock @@ -32,7 +33,9 @@ def make_request(status, data=None): if data is not None: response.data = mock.AsyncMock(spec=["__call__", "read"]) - response.data.read = mock.AsyncMock(spec=["__call__"], return_value=data) + response.data.read = mock.AsyncMock( + spec=["__call__"], return_value=json.dumps(data).encode("utf-8") + ) request = mock.AsyncMock(spec=["transport.Request"]) request.return_value = response From 6d5609e0dc7ee1dc4b1c848f5c31b85cf6245001 Mon Sep 17 00:00:00 2001 From: clundin25 <108372512+clundin25@users.noreply.github.com> Date: Tue, 26 Jul 2022 09:19:35 -0700 Subject: [PATCH 590/966] chore: add auth team to codeowners file (#1088) --- packages/google-auth/.github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/.github/CODEOWNERS b/packages/google-auth/.github/CODEOWNERS index 06c1e10f1476..07b04904684d 100644 --- a/packages/google-auth/.github/CODEOWNERS +++ b/packages/google-auth/.github/CODEOWNERS @@ -5,7 +5,7 @@ # https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners#codeowners-syntax # The @googleapis/yoshi-python is the default owner for changes in this repo -* @arithmetic1728 @sai-sunder-s @TimurSadykov @googleapis/yoshi-python +* @arithmetic1728 @sai-sunder-s @googleapis/googleapis-auth @googleapis/yoshi-python # The python-samples-reviewers team is the default owner for samples changes /samples/ @googleapis/python-samples-owners From 6199ec2bd178bf27c8c2dbd8caa08aac7b00ad88 Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Thu, 28 Jul 2022 17:46:43 +0000 Subject: [PATCH 591/966] chore: update system creds (#1084) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 040759bada7b7c78228ffd32cf999251731d2dba..d36f6132ffbaca43aabf17b4d28b42d3f4d633ad 100644 GIT binary patch literal 10324 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 literal 10324 zcmV-aD67{BB>?tKRTILmRv-a3#MEdso8oMCXamCp{rvj(9f46(xBq7}EmEZ?vhmb{wce)Hza@_lv_`vn#YU%+Uv^)cgd*%p-vjwi z(FvlNpA_h+J9dp~`PIuYgjURSzoTjx^zDo(K=%m4hBTlr({Y6zX2D^L6d$RWeCMwK zC}J5p0`lxP5WJ3ERRl}`5d%aB zWO}}yz5LVI{&iW;Exfr%gwPHN3a$tta1K)ckjIhGIDPrX2G-Ol$9WTONX&vTW2b(I zM;h;V2`hzRAb`AntXy`6vM*}KevmDz^c;+$3hFF06Uk+W7ZmszG+$D)tRzBl#gwM_ zXX(B?rXr`bAmS>4241f9^!XXNg93fhW&3$=`I`})Qa>x}6P!b#J>|)U3n)1y zfbLtPE~BM+Z2$r?V&vRA^wL#lADhjH&Xg7fDd?BG0f2^`ZuE%V%RC|U#B^c=@(gCI zP#Ty^DlVG-zbC|x_{JSBxtw5noFj-3RS)6hbM*=Xxk5qmiI)a zL1ul!hGQIQUKD6>{ zzr{EAj-`G4cyU=bWEjS( z^Xai~Jd@H$$s;2DhsnC_?(Le{V)zGIZOkZFlD zT+E}>sI370P((Y-4H|>cQWm!Si`&X6&2qC@HM$IcM#(@IkW|1MgwKpG*Kku2dOrrl z*Q_~Wci&(VWjg|gm18xc1)q;BQCr`OAK9v}3N$YCZUIO7jb;}aXo`tkX*>2YTn#UE zvKIggSDvLn9?Hj*>O-h?8n9~R z>?-sCD-H)l@7h8cBO}~5MmUhqx%kl(h1G<9fa#qBY;ryU$J(>*w^tnYgS>E2YzRBW z(SqsphOL+OXEvuCinH45Pu%~QA>rt-0JvU)a=VsfR$>xXdR-FI?AI72YkPghMiwW1 zn(O0xMH?5TG~D%SFEzblGGQnEn0yU70n-K3WA59rnumw5HtEIbi^b~oB~u|*j6PpB ztlOm{@?qB|kRNICD470Gi1-)?O``+b-_zHs z7bS?`?zTdA(oU6=zc(^l5Ntvkv?Qff(a6cjqY0Ju3PPAPeDN08!$n07tVOu#{p<#MBkO5e+x=#AOUXfxT-b#58`$JUw1yZFX%r8>7l&Wp8ZR=BwNJZ1W4g(4!>G5M1Z}5{X7fH8NxqOidJ)sSgrS@MLH;w zdqLFF2BUA|baS^&`7hZ_cIw-TQ#dI1?|#K#k@KFxF)H%~PKl@m`zrZ)`C0MgAM%@) z?(l;hg{o$>W7@IKM0skKMc7$tO#t;;a>#l?b4GYwhBm2~vT~pxQNl|kQE?=lYwk$? zH&z3|m)J;x!;p0{^b_SRrhqw0JUqyQLY#j=ie2n8$NhcPs3r(5x$GteSy3rugJ7U) zG0N_6>0kb%%l3vIJWY-jQ-LzMW^5&wa*gm#*~*2wYKUR$FdLUn8@(^8o{npG(JMv_4W+o-8dTCY-Q~oEcR4 z>^8Q(hy8@2P42JgWn-$Zx^L;PSiFUhVuY(f_eW=3`ry{{y0jVI?aaz+R9@;6V*utU zA2*erq=-m*CVuQ=j4C)=+5W^}3zcdh%XB3KD}kIhyrsJVI3ayXsrKo< zf6~8Z5p&!-bV9|o09!%7PdXZce#^;YT1|&uEc<5uuI=Lp))bBM`fMc~c)2wU7k_9N zkHa{c-^AJiuDfU>ekw8bjD=IK}&kL9aTxeX-jzttvs2{{S zyHAjEqZg5vet=xN{_18A+xK6sDjEeDN~Rc^gQDKo^iPkh)B*kgLVmAWE&(GAyzfFT@B_kv(|%*d{6hGo9MpU{1!4b0z*25 zND%A^xBbHjyda?OIQ(UzH2dQYgp0nsj<}Kh&fpV=RlW8P=~oD$ft$g1f_RONa{WLx zNzumX7Tas6%nRXG#ZftDt{{bA9&ORKX~KmjLkAP|i>G>bJzz&V2*o2yok?%alnRF$ zF{kP{9L1Q5(xD!h_j0MU=HaqA34j>f>FHsRSVd54?nnaETNwXgSdVAB1o-7SZSp2FHVeK2?p0bKrx?+Un2csIllRP9?n-T+^lc`O#)V=E<);Iu z!_ui(l&nQ-7w5-A>LOV54}Jk(*N3vKG*;hY8;S}ZvQaXm{|*%PgMqzP!gSmx!?3N_ zS~pVQ)=Sf-oFww(9NaC|R@4MC7SN-gmc{`0w_RPpob&rjJq11&W0T*}(&exXCkv=Q zbmR}aJE@AV9e|~*43b*H%2Ksp>zv{qNRr8;Rb6neX-Zr!T7OyD&@V+`*x8 z5h)oMld)tp$1n0tvJcB#JRN*K7`v29*z;|LBX(6PAt=|0SkJ`9Ofquh-59DCFKs`D zQM5zy3j+aZbX_DMV_l5ES)z^ujKU#^_Kpk7*WI?%d#ej_u6_>ET?~>)yavYIoQI$# zNz#djXt5Haj{>HXlT)^7vL73gq#yc2&=X=fzS>j|>uRuuyOA>sSLC$Z1)J6im~Ths zN%j!EI}V|2(&O*%5>swcvdg-6ZIUh2o=FTyA)h0J2t+}=c)VAP0Z=vUMy&>t6ygPg zjkQbgYD|8A?Melw9Z+fBz)-Mtew5HK5%24E$*)im5qwjUJp9lg>@C-TR6K?Dp*d`Z zFRv4(CM6CkCGf(5Xvn?pH7OyX_w&&2pP3uaZ#<&aD|V($DniWCn;Ar!K%dC?p%85W z)VjQMjOWPRzsgPJG@0JdG*$4q#AX$=U0(CyQcBFjAnX!YA#Ef(q362KaZh#rEKvea zsr_&`sM!8jRfbl9? zwMw4qn;qyhay^PqTm2_0@E%pD@2Sru$=}G-`TB|MgsmbAXU6T| zr^bjL8x9_BQ3ODo_EEPFkNuiz81cxe9tCBqR(|a)$%@y7QuX*`t1;a(Us0z@)@hCE zaT$&&a>eY}r#4lDtb(H!q^86lZH&QC@+j@0kQ<~aT*AUb@z`mPB%M#RA^(9R1&YZY zEfD6AM8&)azz#Rc(u1F+CNM9cB22ji{7c6bwSpYAf#o$S4_@oyFGoRtoQ@x??7nv@6I}l}7Frj+)jX`6;5w1XO zQOso_tS<#YwX$ZvZ+8WgiuF}Cp@28BvfX6=Bu%5rGP%$7r1bWLRom3docQ^fH~Kvw zaLRF^R_AqtBKHM1&uJk~_U-4lk@_074ZhF8v`?`SOofudbL%U-#?oP1_xunrlX37w z$o>(;-Z_msIWs}#L?jVYBx~d-exs`o!J)3N4ia=N7GL4*G6E2X;~n%{^1=d|?0xp_ zP3=4(E?F!VTKXPU++PvsSw_NJUahdv08=0_v6Y8HW^29x`*imI_#eCs6l2^IpD4jO z1Bxp0Ws>{CqfYY~i)EGEx}Rk|LIWLWm+o*yU$!3dx}CU~^CLzzVL$}tzV#6GnPM6E zX+Z|_u0;%f$;oS9%JhR*Xqa@gl39fV!jhHsBbemsM9G?^vzj{4>`z4;^Y|cP;=aLg z@ch2Z&N!j7JGGo!*{55t<^kx8^`S8t9NM`3;b`v}^Ias{(PN2>vd@~|@S?9h*E6S+ z^Xk4idSC6(ZgZ9rRkQ+X6tu(o2u>1e_KufKagl$VRoZ=^B*1g(`8_Yfdgw;y>`dEw zS;r8+lQJIlDIna zD&%zJVuSd#3h=74NX^(lSL4Vp@7zZP=)%L+IsM7(WcUcR>3cSwVM4AvrhYKH454$l9npc%~)zN+l#y~&Q z(7Pa-@Gl}Xr*c|>h9fxQm*37;LILO#X@QF*Z&IQ5NGeuAhAFzd@o8h5#S2xA|E8*2 z*(C>;q`l#jHxYp$Ng1XasR4JFJVJX*?2^zWFKEJ$MX&hZFL5(8R@2809q$_?)NMg4 zO{Od~%68vXDr}rz=yGRPei2 zUVX6CQ(|{$16fZa-P0HE0OakblzT<_;1(Jz5A2{&Ip`g2eBM3Gsv-y{96 zk-^zL?z>o7c;xwOYOm0wNm_&i>p%)x6FF@iN@`iO;fX?iiT~-2^!G`WV4|B#JKP{S za!G8Ys_)2wGVZo|je}svS7Tpg71y`MD0aZ@BI5}=7F5umdE%nF-`-xm0UT%J&@J7s znP;Z*xzc$ahST6QT~Bqco<;SE6LMK<-wN5eeK1rgUoCerKbe-=5zL$0R@z@WYIROD z!PS@Ma8J-9+4pC%^0gJ>c5C|~GIR%bfUE;P&TG^vr8#ywXFSULY*n+kFp6(=xkJf{ z$z}wrEVJ0wYx z7KHz8HSx}R^^Xvy06BcriAO?ENkg!DMD#nx2#2cDE%R@inP2!Dy16)+LITo=O2tqw z8&&iQzf>ANmlD+HmkP#Xl!9pJfUx{>X!GWpeCcr!U_8lAM}-obc`h(1V$V!P(qQE# zV#zHM!6`2hS+xuNgC@j^UwQ%E%hB*2_XMUBkJ=b?eAW@AuMgfAQOq@NJMoCDVog@d zv%kyBlU#Br41)y&8rK!Yb(FMgFQp@ixY|s^l;#WSP%4{GkV*fjaZo@efz+yBi51F$ zBOB_(&{#J8DX;Y-P{>DEV&{4oj<_@BN?%+n4fLGuDb@gvkbGO%$^Gg~HSOb$vhZa##2I-|l1P5;Cf=w)(!LXnt1PG@MPv<%0vS z0lqW$;~McLFTVjr^NR`{<|cQQ!rEqWm{;vCR6N&2w*+bFzou*ww%nAlthn)8xu#BzwtVwd>1^IGegZ=&Csy1R7bG!luS znCR<)l)p9icOza4!c@)w&C)(e8Di>Kk#@(}&>z_xqz6`8#gIJ!Ujy#8M!Qr|y z{N*q6wY@lHN#Rqmw0%J+J+<<~10p@}HGrPYWGHoF>IO|W2F`O+1&U~~guC*W{J%6YavWMK9u!lhhR>JYrdh#Aa3xOYYs@4* z1tGMY9CG2Pk#q?KP?fTnI5_LE`8Qi-Qs58cr+?A^rimf-^m4x=1Gr}O@}( z-DqR8c2*f1ZEhdr=*-40AmEpLS6QRdjn$@6MhEWglU?uahLsJOjiXq=J^q!u7&w>f zfSaFH)8eYWKac3=S)E*6;NDhqdsna@D$^D<-8dK^!8*`;O(=0# zy8c~Fa~aJz+v$QRSjflf)UVU?D`ERT$)njPdG>08GZ9`(0HFz_L^R4zw91(2FQc5h zrYKG|C3i{de#N$()N5f7S3On zKlo=R!F<>4WOyvHjZ$`gm@D6YhROl8ke8c-dW1bAfcXGQL;&={b2=h7fm8H5a{L0M z0iE@{1?TIrC0AL^OGfgKBPN6t4~$WRWwyYwuU3cP&5jZ8oeASl*>;{;P-_@u8{dYw zLV8vUt3~hUiv>Hu=@Nh72=?i05I<#KX!MqZ%dMMMf&-1>H6-++Kt_=hH%o4+8||pA53kZcQr7llHea0L&r|}nY`IPOjhf# zO4eW_yRNgsVZiPV8SEOII##!*WzH^dgrd3SBCqWl zy3!>{+{PRj;?wN)W!981;I{oZBG)VH{o)=ql*pKvT#C)rF()%}D9k@Kv)FNsvNi@#}o_#8OEY|fCpo3|{e8LD>NM2<1ls(0#7*D0oEuKqxGRNb|rw=_X< zh~3(L@q#V}jYCe(vH)1gprw9%DT@-t zuGRvFzt$8IGQL{;6k`+5z8wZ|GdaAIAYYi5cr@HG9*E6KZL2b9$lGz z3dN%m+X;y5wj%&VESC3!IdD)4t?k$oy0SWW_7@-%Vf71^ygQCIiGQ{%^pYgx8fp_Y z)XH;f>1wa4PkN`w=Ow;%^zA*jYhRt)o2S|T2O&z(2mKWXzTzkE1Q2b{s+PFvz07lW zgY9pKhrss7uaBlehiIcyQ8(teXaL28O=4y%AYxoDTK3Qr{>wUj8KjC`TJU^n?B`zJ z0y|12nFp5bN6)a0#J)*&;$WD2DWPl1bs$yKgs|ePFi5nh;Mw{QVdT^D(dY7?F zEOtgH-N+w^nkPY+KszTMwMhD^K6wO4!27+U{+}*=L)Ul?riPd<&#r)%1gBG>oCAr< z&c(XlwwnOw2~;7;y^DP)03XmAuCqQ;`T3clHwmj>#LF2LbPwKN7iDZ-8xEBL3AGDp z{o6ahJ%0{Jrwi9MGlC;cvpv}<3xX*z1v@v8WcfHSD)-w30CPU^f3MXV0s#ngKb!r@ z;XIHg2>l&H5t;i6HZZMrjoXsCe8mSF_riU7v_IbjAu*hqJHn_52*+2CcYku_v;ixs z4?oy)8Zi%`o3lzIEn;Kt(&B@3xt^K6@d%g=IyUr2JaWs)!G0?^zUs|%-PvBNZ}Ch%VK+Dk=j z|2^6sn+V|?4Ekigy{To-cM)&4J@%l>*}$8?kz*HO4d8Gx!U!yMQhn_R7N*;%OGjCG z7nP`3V$A;f{*w6c2!)#pyXyy!2U%LcRL3))RI2toQ$=y3 z^w?-J!_hInozYx^Wpr~#gcE-ViA!~~iiC+|N33!nKP6%Wd>B-TXQgri409wIMR%dj zR=06p3d4$6c^E|4D=Y<7gI;`Zy-Z z?8WShTlqb1L^*DfSG&13X}U)(%5HWDlw&~ivR4Mq3zSF*^Nd^7HZ0cfpM{%LkB{=HU{y z8yTdZ&oGUG`&j?I=*=Kq(AX^bT58yh0u4;8|HJ_d~|c6 zj&=wYQ-G>9krTWxPyF)q#S7q0Pi5PRs`l~Zo$a_X3AcS6wyziYOq|?vSr0F?cGkM>596#HT zT>PuT<{X!f4uYCzd?31b2)2-H!OucF4ixx2pUvo4y$#6V6dv!i5vyW+7UB zMHTuoLkadxUA0B6NQJy6-^fFbXK?|VlGsOGf(gR@ zHhMxb852y)rFh-SlhNU}EHXx6v-`15K6+EX9tq?}I@^Mkg{WsC=1z!uLOo_#d+!EY zvpmCvEhTOP=5G$MK(HzXO8!3G(u+@D*5yaa{m3Lfr9u969D;nZ6;AUlC;o2!t;g2r zIf-t+G!InBim-RU^xwd@%U=ckt!oQmJ#vlC6Jqou6f-n>Z(*!Hx_s`$PgO+XaRmQW zi;<`x;;cE~AFHa;>?p0oO&&OL|3VK?b3aamd1Y)Lr2s07Gi=FHJMvr6jz4SyxS@8? z{9OibtM|RQy3FGIAaB5jWT1s*#8ou1E^zxforVt57>-;Qvn*e5ZS{<*G?c^D>g>gU z!fe5o5mBxt47&-ams_@i-$>kn{AC^0XISb_FjA=soI;-jA^yMkne#tM(LHukRm~>L zQ%nDTN<(Yyd?b@2)iFTRR+i6q>&0jldM1P}Qb6S_Xp8x)_H`AGP<{Xo{eoNIgwPZB zLk-wls*JBNHlED=t!~GM`#~=ft708(>?Kr3>f2+`sV_dlL|7lAv+IB<)v{^9U%{TP zHXz1D8PoGB_%QjnH;W|d^VNw^c$9|$@DgCZ$d|AWOWk1ltwI};qDWn9xXN!om)`h| z*paMu#x6aX;>&gOEV+_NrBd!ndy`iTyUBq=8 zJ&sAhaN;I^J zaoaWingm=X`pD!;d7kq6=Cst%>AEt7J&>Z)30^ZClgwZsC(6d}RDY>Fh$_g==${RO zrMh;^@{7jL66nAq@CLPIwYDUHxFH#D-A`!%&*b0?Tr|;u0Xpq>;kn_gPLrH+C+ASc z3uwABYV->Y&N6MDYoekO85gV7x76`6fPa+)bL+GnulY~900|Jd&`0ybjyZn|VW>-Hy41VQBtR+ReJ)x(w={^jeH$4L%`i(qJI1a!fUtb?zD< m5r;`IP636yrM(D1HIqE1*Pa6G&2M3__y- From 4fa57a3cf11942b74222bd96e4313bdc67f3a722 Mon Sep 17 00:00:00 2001 From: jangjunha Date: Fri, 29 Jul 2022 03:38:22 +0900 Subject: [PATCH 592/966] doc: fix return type of get_service_account_token (#1083) Co-authored-by: clundin25 <108372512+clundin25@users.noreply.github.com> --- packages/google-auth/google/auth/compute_engine/_metadata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/compute_engine/_metadata.py b/packages/google-auth/google/auth/compute_engine/_metadata.py index d57c22a15de0..16c5e2138ffc 100644 --- a/packages/google-auth/google/auth/compute_engine/_metadata.py +++ b/packages/google-auth/google/auth/compute_engine/_metadata.py @@ -246,7 +246,7 @@ def get_service_account_token(request, service_account="default", scopes=None): scopes (Optional[Union[str, List[str]]]): Optional string or list of strings with auth scopes. Returns: - Union[str, datetime]: The access token and its expiration. + Tuple[str, datetime]: The access token and its expiration. Raises: google.auth.exceptions.TransportError: if an error occurred while From 58c5e213ca706c38240aced38ed219799fda66f1 Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Fri, 29 Jul 2022 20:29:15 +0000 Subject: [PATCH 593/966] chore: update system creds (#1093) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index d36f6132ffbaca43aabf17b4d28b42d3f4d633ad..8eea017fa54d54ab1e3c9568bdcdda9f6b5fa36d 100644 GIT binary patch literal 10324 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 mdd|6Ona~Ed- literal 10324 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 From ce14a0acac468b31544647f32d7f38549db2a23f Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Mon, 1 Aug 2022 16:56:34 +0000 Subject: [PATCH 594/966] chore: update system creds (#1094) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 8eea017fa54d54ab1e3c9568bdcdda9f6b5fa36d..97fcbb0dead259761d8dfa34c8031713dae74b0d 100644 GIT binary patch literal 10324 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 mdd|6Ona~Ed- From c7af67efec801e462a7d819d93282f51ad5970a2 Mon Sep 17 00:00:00 2001 From: Ryan Kohler Date: Mon, 1 Aug 2022 17:25:53 -0400 Subject: [PATCH 595/966] feat: support for configurable token lifetime (#1079) feat: support for configurable token lifetime --- packages/google-auth/google/auth/aws.py | 6 ++ .../google/auth/external_account.py | 14 ++++ .../google-auth/google/auth/identity_pool.py | 6 ++ .../google/auth/impersonated_credentials.py | 2 +- packages/google-auth/google/auth/pluggable.py | 5 ++ .../test_external_accounts.py | 28 +++++++ packages/google-auth/tests/test_aws.py | 6 ++ .../tests/test_external_account.py | 75 +++++++++++++++++++ .../google-auth/tests/test_identity_pool.py | 8 ++ packages/google-auth/tests/test_pluggable.py | 6 ++ 10 files changed, 155 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/aws.py b/packages/google-auth/google/auth/aws.py index 9df2d35e3723..873beef59b78 100644 --- a/packages/google-auth/google/auth/aws.py +++ b/packages/google-auth/google/auth/aws.py @@ -353,6 +353,7 @@ def __init__( token_url, credential_source=None, service_account_impersonation_url=None, + service_account_impersonation_options={}, client_id=None, client_secret=None, quota_project_id=None, @@ -393,6 +394,7 @@ def __init__( token_url=token_url, credential_source=credential_source, service_account_impersonation_url=service_account_impersonation_url, + service_account_impersonation_options=service_account_impersonation_options, client_id=client_id, client_secret=client_secret, quota_project_id=quota_project_id, @@ -755,6 +757,10 @@ def from_info(cls, info, **kwargs): service_account_impersonation_url=info.get( "service_account_impersonation_url" ), + service_account_impersonation_options=info.get( + "service_account_impersonation" + ) + or {}, client_id=info.get("client_id"), client_secret=info.get("client_secret"), credential_source=info.get("credential_source"), diff --git a/packages/google-auth/google/auth/external_account.py b/packages/google-auth/google/auth/external_account.py index 97aca108988c..5c6ce2a40c15 100644 --- a/packages/google-auth/google/auth/external_account.py +++ b/packages/google-auth/google/auth/external_account.py @@ -70,6 +70,7 @@ def __init__( token_url, credential_source, service_account_impersonation_url=None, + service_account_impersonation_options={}, client_id=None, client_secret=None, quota_project_id=None, @@ -108,6 +109,9 @@ def __init__( self._token_url = token_url self._credential_source = credential_source self._service_account_impersonation_url = service_account_impersonation_url + self._service_account_impersonation_options = ( + service_account_impersonation_options or {} + ) self._client_id = client_id self._client_secret = client_secret self._quota_project_id = quota_project_id @@ -158,6 +162,10 @@ def info(self): "subject_token_type": self._subject_token_type, "token_url": self._token_url, "service_account_impersonation_url": self._service_account_impersonation_url, + "service_account_impersonation": copy.deepcopy( + self._service_account_impersonation_options + ) + or None, "credential_source": copy.deepcopy(self._credential_source), "quota_project_id": self._quota_project_id, "client_id": self._client_id, @@ -250,6 +258,7 @@ def with_scopes(self, scopes, default_scopes=None): token_url=self._token_url, credential_source=self._credential_source, service_account_impersonation_url=self._service_account_impersonation_url, + service_account_impersonation_options=self._service_account_impersonation_options, client_id=self._client_id, client_secret=self._client_secret, quota_project_id=self._quota_project_id, @@ -360,6 +369,7 @@ def with_quota_project(self, quota_project_id): token_url=self._token_url, credential_source=self._credential_source, service_account_impersonation_url=self._service_account_impersonation_url, + service_account_impersonation_options=self._service_account_impersonation_options, client_id=self._client_id, client_secret=self._client_secret, quota_project_id=quota_project_id, @@ -393,6 +403,7 @@ def _initialize_impersonated_credentials(self): token_url=self._token_url, credential_source=self._credential_source, service_account_impersonation_url=None, + service_account_impersonation_options={}, client_id=self._client_id, client_secret=self._client_secret, quota_project_id=self._quota_project_id, @@ -419,6 +430,9 @@ def _initialize_impersonated_credentials(self): target_scopes=scopes, quota_project_id=self._quota_project_id, iam_endpoint_override=self._service_account_impersonation_url, + lifetime=self._service_account_impersonation_options.get( + "token_lifetime_seconds" + ), ) @staticmethod diff --git a/packages/google-auth/google/auth/identity_pool.py b/packages/google-auth/google/auth/identity_pool.py index fb33d7726f54..a086d283e39b 100644 --- a/packages/google-auth/google/auth/identity_pool.py +++ b/packages/google-auth/google/auth/identity_pool.py @@ -57,6 +57,7 @@ def __init__( token_url, credential_source, service_account_impersonation_url=None, + service_account_impersonation_options={}, client_id=None, client_secret=None, quota_project_id=None, @@ -122,6 +123,7 @@ def __init__( token_url=token_url, credential_source=credential_source, service_account_impersonation_url=service_account_impersonation_url, + service_account_impersonation_options=service_account_impersonation_options, client_id=client_id, client_secret=client_secret, quota_project_id=quota_project_id, @@ -262,6 +264,10 @@ def from_info(cls, info, **kwargs): service_account_impersonation_url=info.get( "service_account_impersonation_url" ), + service_account_impersonation_options=info.get( + "service_account_impersonation" + ) + or {}, client_id=info.get("client_id"), client_secret=info.get("client_secret"), credential_source=info.get("credential_source"), diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index 72e61a1fe957..29eab1684fda 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -232,7 +232,7 @@ def __init__( self._target_principal = target_principal self._target_scopes = target_scopes self._delegates = delegates - self._lifetime = lifetime + self._lifetime = lifetime or _DEFAULT_TOKEN_LIFETIME_SECS self.token = None self.expiry = _helpers.utcnow() self._quota_project_id = quota_project_id diff --git a/packages/google-auth/google/auth/pluggable.py b/packages/google-auth/google/auth/pluggable.py index 12cd6240e16f..96ccd9ca8714 100644 --- a/packages/google-auth/google/auth/pluggable.py +++ b/packages/google-auth/google/auth/pluggable.py @@ -59,6 +59,7 @@ def __init__( token_url, credential_source, service_account_impersonation_url=None, + service_account_impersonation_options={}, client_id=None, client_secret=None, quota_project_id=None, @@ -256,6 +257,10 @@ def from_info(cls, info, **kwargs): service_account_impersonation_url=info.get( "service_account_impersonation_url" ), + service_account_impersonation_options=info.get( + "service_account_impersonation" + ) + or {}, client_id=info.get("client_id"), client_secret=info.get("client_secret"), credential_source=info.get("credential_source"), diff --git a/packages/google-auth/system_tests/system_tests_sync/test_external_accounts.py b/packages/google-auth/system_tests/system_tests_sync/test_external_accounts.py index e24c7b40a54a..32fd62f7b001 100644 --- a/packages/google-auth/system_tests/system_tests_sync/test_external_accounts.py +++ b/packages/google-auth/system_tests/system_tests_sync/test_external_accounts.py @@ -171,6 +171,34 @@ def test_file_based_external_account( }, ) +# This test makes sure that setting a token lifetime works +# for service account impersonation. +def test_file_based_external_account_with_configure_token_lifetime( + oidc_credentials, service_account_info, dns_access +): + with NamedTemporaryFile() as tmpfile: + tmpfile.write(oidc_credentials.token.encode("utf-8")) + tmpfile.flush() + + assert get_project_dns( + dns_access, + { + "type": "external_account", + "audience": _AUDIENCE_OIDC, + "subject_token_type": "urn:ietf:params:oauth:token-type:jwt", + "token_url": "https://sts.googleapis.com/v1/token", + "service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/{}:generateAccessToken".format( + oidc_credentials.service_account_email + ), + "service_account_impersonation": { + "token_lifetime_seconds": 2800, + }, + "credential_source": { + "file": tmpfile.name, + }, + }, + ) + # This test makes sure that setting up an http server to provide credentials # works to allow access to Google resources. diff --git a/packages/google-auth/tests/test_aws.py b/packages/google-auth/tests/test_aws.py index d55afa6a8450..26c49e1971be 100644 --- a/packages/google-auth/tests/test_aws.py +++ b/packages/google-auth/tests/test_aws.py @@ -797,6 +797,7 @@ def test_from_info_full_options(self, mock_init): "subject_token_type": SUBJECT_TOKEN_TYPE, "token_url": TOKEN_URL, "service_account_impersonation_url": SERVICE_ACCOUNT_IMPERSONATION_URL, + "service_account_impersonation": {"token_lifetime_seconds": 2800}, "client_id": CLIENT_ID, "client_secret": CLIENT_SECRET, "quota_project_id": QUOTA_PROJECT_ID, @@ -811,6 +812,7 @@ def test_from_info_full_options(self, mock_init): subject_token_type=SUBJECT_TOKEN_TYPE, token_url=TOKEN_URL, service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + service_account_impersonation_options={"token_lifetime_seconds": 2800}, client_id=CLIENT_ID, client_secret=CLIENT_SECRET, credential_source=self.CREDENTIAL_SOURCE, @@ -835,6 +837,7 @@ def test_from_info_required_options_only(self, mock_init): subject_token_type=SUBJECT_TOKEN_TYPE, token_url=TOKEN_URL, service_account_impersonation_url=None, + service_account_impersonation_options={}, client_id=None, client_secret=None, credential_source=self.CREDENTIAL_SOURCE, @@ -848,6 +851,7 @@ def test_from_file_full_options(self, mock_init, tmpdir): "subject_token_type": SUBJECT_TOKEN_TYPE, "token_url": TOKEN_URL, "service_account_impersonation_url": SERVICE_ACCOUNT_IMPERSONATION_URL, + "service_account_impersonation": {"token_lifetime_seconds": 2800}, "client_id": CLIENT_ID, "client_secret": CLIENT_SECRET, "quota_project_id": QUOTA_PROJECT_ID, @@ -864,6 +868,7 @@ def test_from_file_full_options(self, mock_init, tmpdir): subject_token_type=SUBJECT_TOKEN_TYPE, token_url=TOKEN_URL, service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + service_account_impersonation_options={"token_lifetime_seconds": 2800}, client_id=CLIENT_ID, client_secret=CLIENT_SECRET, credential_source=self.CREDENTIAL_SOURCE, @@ -889,6 +894,7 @@ def test_from_file_required_options_only(self, mock_init, tmpdir): subject_token_type=SUBJECT_TOKEN_TYPE, token_url=TOKEN_URL, service_account_impersonation_url=None, + service_account_impersonation_options={}, client_id=None, client_secret=None, credential_source=self.CREDENTIAL_SOURCE, diff --git a/packages/google-auth/tests/test_external_account.py b/packages/google-auth/tests/test_external_account.py index 067fb59b6656..a289b5df918e 100644 --- a/packages/google-auth/tests/test_external_account.py +++ b/packages/google-auth/tests/test_external_account.py @@ -74,6 +74,7 @@ def __init__( token_url, credential_source, service_account_impersonation_url=None, + service_account_impersonation_options={}, client_id=None, client_secret=None, quota_project_id=None, @@ -87,6 +88,7 @@ def __init__( token_url=token_url, credential_source=credential_source, service_account_impersonation_url=service_account_impersonation_url, + service_account_impersonation_options=service_account_impersonation_options, client_id=client_id, client_secret=client_secret, quota_project_id=quota_project_id, @@ -166,12 +168,14 @@ def make_credentials( scopes=None, default_scopes=None, service_account_impersonation_url=None, + service_account_impersonation_options={}, ): return CredentialsImpl( audience=cls.AUDIENCE, subject_token_type=cls.SUBJECT_TOKEN_TYPE, token_url=cls.TOKEN_URL, service_account_impersonation_url=service_account_impersonation_url, + service_account_impersonation_options=service_account_impersonation_options, credential_source=cls.CREDENTIAL_SOURCE, client_id=client_id, client_secret=client_secret, @@ -493,6 +497,7 @@ def test_with_scopes_full_options_propagated(self): scopes=self.SCOPES, default_scopes=["default1"], service_account_impersonation_url=self.SERVICE_ACCOUNT_IMPERSONATION_URL, + service_account_impersonation_options={"token_lifetime_seconds": 2800}, ) with mock.patch.object( @@ -508,6 +513,7 @@ def test_with_scopes_full_options_propagated(self): token_url=self.TOKEN_URL, credential_source=self.CREDENTIAL_SOURCE, service_account_impersonation_url=self.SERVICE_ACCOUNT_IMPERSONATION_URL, + service_account_impersonation_options={"token_lifetime_seconds": 2800}, client_id=CLIENT_ID, client_secret=CLIENT_SECRET, quota_project_id=self.QUOTA_PROJECT_ID, @@ -550,6 +556,7 @@ def test_with_quota_project_full_options_propagated(self): scopes=self.SCOPES, default_scopes=["default1"], service_account_impersonation_url=self.SERVICE_ACCOUNT_IMPERSONATION_URL, + service_account_impersonation_options={"token_lifetime_seconds": 2800}, ) with mock.patch.object( @@ -565,6 +572,7 @@ def test_with_quota_project_full_options_propagated(self): token_url=self.TOKEN_URL, credential_source=self.CREDENTIAL_SOURCE, service_account_impersonation_url=self.SERVICE_ACCOUNT_IMPERSONATION_URL, + service_account_impersonation_options={"token_lifetime_seconds": 2800}, client_id=CLIENT_ID, client_secret=CLIENT_SECRET, quota_project_id="project-foo", @@ -614,6 +622,7 @@ def test_info_with_full_options(self): client_secret=CLIENT_SECRET, quota_project_id=self.QUOTA_PROJECT_ID, service_account_impersonation_url=self.SERVICE_ACCOUNT_IMPERSONATION_URL, + service_account_impersonation_options={"token_lifetime_seconds": 2800}, ) assert credentials.info == { @@ -622,6 +631,7 @@ def test_info_with_full_options(self): "subject_token_type": self.SUBJECT_TOKEN_TYPE, "token_url": self.TOKEN_URL, "service_account_impersonation_url": self.SERVICE_ACCOUNT_IMPERSONATION_URL, + "service_account_impersonation": {"token_lifetime_seconds": 2800}, "credential_source": self.CREDENTIAL_SOURCE.copy(), "quota_project_id": self.QUOTA_PROJECT_ID, "client_id": CLIENT_ID, @@ -1733,6 +1743,71 @@ def test_workforce_pool_get_project_id_cloud_resource_manager_success(self): # No additional requests. assert len(request.call_args_list) == 2 + def test_refresh_impersonation_with_lifetime(self): + # Simulate service account access token expires in 2800 seconds. + expire_time = ( + _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=2800) + ).isoformat("T") + "Z" + expected_expiry = datetime.datetime.strptime(expire_time, "%Y-%m-%dT%H:%M:%SZ") + # STS token exchange request/response. + token_response = self.SUCCESS_RESPONSE.copy() + token_headers = {"Content-Type": "application/x-www-form-urlencoded"} + token_request_data = { + "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange", + "audience": self.AUDIENCE, + "requested_token_type": "urn:ietf:params:oauth:token-type:access_token", + "subject_token": "subject_token_0", + "subject_token_type": self.SUBJECT_TOKEN_TYPE, + "scope": "https://www.googleapis.com/auth/iam", + } + # Service account impersonation request/response. + impersonation_response = { + "accessToken": "SA_ACCESS_TOKEN", + "expireTime": expire_time, + } + impersonation_headers = { + "Content-Type": "application/json", + "authorization": "Bearer {}".format(token_response["access_token"]), + } + impersonation_request_data = { + "delegates": None, + "scope": self.SCOPES, + "lifetime": "2800s", + } + # Initialize mock request to handle token exchange and service account + # impersonation request. + request = self.make_mock_request( + status=http_client.OK, + data=token_response, + impersonation_status=http_client.OK, + impersonation_data=impersonation_response, + ) + # Initialize credentials with service account impersonation. + credentials = self.make_credentials( + service_account_impersonation_url=self.SERVICE_ACCOUNT_IMPERSONATION_URL, + service_account_impersonation_options={"token_lifetime_seconds": 2800}, + scopes=self.SCOPES, + ) + + credentials.refresh(request) + + # Only 2 requests should be processed. + assert len(request.call_args_list) == 2 + # Verify token exchange request parameters. + self.assert_token_request_kwargs( + request.call_args_list[0][1], token_headers, token_request_data + ) + # Verify service account impersonation request parameters. + self.assert_impersonation_request_kwargs( + request.call_args_list[1][1], + impersonation_headers, + impersonation_request_data, + ) + assert credentials.valid + assert credentials.expiry == expected_expiry + assert not credentials.expired + assert credentials.token == impersonation_response["accessToken"] + def test_get_project_id_cloud_resource_manager_error(self): # Simulate resource doesn't have sufficient permissions to access # cloud resource manager. diff --git a/packages/google-auth/tests/test_identity_pool.py b/packages/google-auth/tests/test_identity_pool.py index 664c317d05b6..3f48675e288a 100644 --- a/packages/google-auth/tests/test_identity_pool.py +++ b/packages/google-auth/tests/test_identity_pool.py @@ -293,6 +293,7 @@ def test_from_info_full_options(self, mock_init): "subject_token_type": SUBJECT_TOKEN_TYPE, "token_url": TOKEN_URL, "service_account_impersonation_url": SERVICE_ACCOUNT_IMPERSONATION_URL, + "service_account_impersonation": {"token_lifetime_seconds": 2800}, "client_id": CLIENT_ID, "client_secret": CLIENT_SECRET, "quota_project_id": QUOTA_PROJECT_ID, @@ -307,6 +308,7 @@ def test_from_info_full_options(self, mock_init): subject_token_type=SUBJECT_TOKEN_TYPE, token_url=TOKEN_URL, service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + service_account_impersonation_options={"token_lifetime_seconds": 2800}, client_id=CLIENT_ID, client_secret=CLIENT_SECRET, credential_source=self.CREDENTIAL_SOURCE_TEXT, @@ -332,6 +334,7 @@ def test_from_info_required_options_only(self, mock_init): subject_token_type=SUBJECT_TOKEN_TYPE, token_url=TOKEN_URL, service_account_impersonation_url=None, + service_account_impersonation_options={}, client_id=None, client_secret=None, credential_source=self.CREDENTIAL_SOURCE_TEXT, @@ -358,6 +361,7 @@ def test_from_info_workforce_pool(self, mock_init): subject_token_type=WORKFORCE_SUBJECT_TOKEN_TYPE, token_url=TOKEN_URL, service_account_impersonation_url=None, + service_account_impersonation_options={}, client_id=None, client_secret=None, credential_source=self.CREDENTIAL_SOURCE_TEXT, @@ -372,6 +376,7 @@ def test_from_file_full_options(self, mock_init, tmpdir): "subject_token_type": SUBJECT_TOKEN_TYPE, "token_url": TOKEN_URL, "service_account_impersonation_url": SERVICE_ACCOUNT_IMPERSONATION_URL, + "service_account_impersonation": {"token_lifetime_seconds": 2800}, "client_id": CLIENT_ID, "client_secret": CLIENT_SECRET, "quota_project_id": QUOTA_PROJECT_ID, @@ -388,6 +393,7 @@ def test_from_file_full_options(self, mock_init, tmpdir): subject_token_type=SUBJECT_TOKEN_TYPE, token_url=TOKEN_URL, service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + service_account_impersonation_options={"token_lifetime_seconds": 2800}, client_id=CLIENT_ID, client_secret=CLIENT_SECRET, credential_source=self.CREDENTIAL_SOURCE_TEXT, @@ -414,6 +420,7 @@ def test_from_file_required_options_only(self, mock_init, tmpdir): subject_token_type=SUBJECT_TOKEN_TYPE, token_url=TOKEN_URL, service_account_impersonation_url=None, + service_account_impersonation_options={}, client_id=None, client_secret=None, credential_source=self.CREDENTIAL_SOURCE_TEXT, @@ -441,6 +448,7 @@ def test_from_file_workforce_pool(self, mock_init, tmpdir): subject_token_type=WORKFORCE_SUBJECT_TOKEN_TYPE, token_url=TOKEN_URL, service_account_impersonation_url=None, + service_account_impersonation_options={}, client_id=None, client_secret=None, credential_source=self.CREDENTIAL_SOURCE_TEXT, diff --git a/packages/google-auth/tests/test_pluggable.py b/packages/google-auth/tests/test_pluggable.py index 61ddabd45717..383b7a866976 100644 --- a/packages/google-auth/tests/test_pluggable.py +++ b/packages/google-auth/tests/test_pluggable.py @@ -127,6 +127,7 @@ def test_from_info_full_options(self, mock_init): "subject_token_type": SUBJECT_TOKEN_TYPE, "token_url": TOKEN_URL, "service_account_impersonation_url": SERVICE_ACCOUNT_IMPERSONATION_URL, + "service_account_impersonation": {"token_lifetime_seconds": 2800}, "client_id": CLIENT_ID, "client_secret": CLIENT_SECRET, "quota_project_id": QUOTA_PROJECT_ID, @@ -141,6 +142,7 @@ def test_from_info_full_options(self, mock_init): subject_token_type=SUBJECT_TOKEN_TYPE, token_url=TOKEN_URL, service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + service_account_impersonation_options={"token_lifetime_seconds": 2800}, client_id=CLIENT_ID, client_secret=CLIENT_SECRET, credential_source=self.CREDENTIAL_SOURCE, @@ -166,6 +168,7 @@ def test_from_info_required_options_only(self, mock_init): subject_token_type=SUBJECT_TOKEN_TYPE, token_url=TOKEN_URL, service_account_impersonation_url=None, + service_account_impersonation_options={}, client_id=None, client_secret=None, credential_source=self.CREDENTIAL_SOURCE, @@ -180,6 +183,7 @@ def test_from_file_full_options(self, mock_init, tmpdir): "subject_token_type": SUBJECT_TOKEN_TYPE, "token_url": TOKEN_URL, "service_account_impersonation_url": SERVICE_ACCOUNT_IMPERSONATION_URL, + "service_account_impersonation": {"token_lifetime_seconds": 2800}, "client_id": CLIENT_ID, "client_secret": CLIENT_SECRET, "quota_project_id": QUOTA_PROJECT_ID, @@ -196,6 +200,7 @@ def test_from_file_full_options(self, mock_init, tmpdir): subject_token_type=SUBJECT_TOKEN_TYPE, token_url=TOKEN_URL, service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + service_account_impersonation_options={"token_lifetime_seconds": 2800}, client_id=CLIENT_ID, client_secret=CLIENT_SECRET, credential_source=self.CREDENTIAL_SOURCE, @@ -222,6 +227,7 @@ def test_from_file_required_options_only(self, mock_init, tmpdir): subject_token_type=SUBJECT_TOKEN_TYPE, token_url=TOKEN_URL, service_account_impersonation_url=None, + service_account_impersonation_options={}, client_id=None, client_secret=None, credential_source=self.CREDENTIAL_SOURCE, From 0ee2883b76d7abf9a1e7e5972c07aae3cc795040 Mon Sep 17 00:00:00 2001 From: Ryan Kohler Date: Mon, 1 Aug 2022 19:32:19 -0400 Subject: [PATCH 596/966] feat: add integration tests for pluggable auth (#1073) * feat: add integration tests for pluggable auth --- packages/google-auth/system_tests/noxfile.py | 8 ++-- .../test_external_accounts.py | 44 +++++++++++++++++++ packages/google-auth/testing/requirements.txt | 2 +- 3 files changed, 50 insertions(+), 4 deletions(-) diff --git a/packages/google-auth/system_tests/noxfile.py b/packages/google-auth/system_tests/noxfile.py index e056d4b2947a..88f2e040ebbd 100644 --- a/packages/google-auth/system_tests/noxfile.py +++ b/packages/google-auth/system_tests/noxfile.py @@ -37,6 +37,7 @@ EXPLICIT_CREDENTIALS_ENV = "GOOGLE_APPLICATION_CREDENTIALS" EXPLICIT_PROJECT_ENV = "GOOGLE_CLOUD_PROJECT" EXPECT_PROJECT_ENV = "EXPECT_PROJECT_ID" +ALLOW_PLUGGABLE_ENV = "GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES" SKIP_GAE_TEST_ENV = "SKIP_APP_ENGINE_SYSTEM_TEST" GAE_APP_URL_TMPL = "https://{}-dot-{}.appspot.com" @@ -168,7 +169,7 @@ def configure_cloud_sdk(session, application_default_credentials, project=False) # Test sesssions -TEST_DEPENDENCIES_ASYNC = ["aiohttp", "pytest-asyncio", "nest-asyncio"] +TEST_DEPENDENCIES_ASYNC = ["aiohttp", "pytest-asyncio", "nest-asyncio", "mock"] TEST_DEPENDENCIES_SYNC = ["pytest", "requests", "mock"] PYTHON_VERSIONS_ASYNC = ["3.7"] PYTHON_VERSIONS_SYNC = ["2.7", "3.7"] @@ -379,10 +380,11 @@ def mtls_http(session): ) -@nox.session(python=PYTHON_VERSIONS_SYNC) +@nox.session(python=PYTHON_VERSIONS_ASYNC) def external_accounts(session): + session.env[ALLOW_PLUGGABLE_ENV] = "1" session.install( - *TEST_DEPENDENCIES_SYNC, + *TEST_DEPENDENCIES_ASYNC, LIBRARY_DIR, "google-api-python-client", ) diff --git a/packages/google-auth/system_tests/system_tests_sync/test_external_accounts.py b/packages/google-auth/system_tests/system_tests_sync/test_external_accounts.py index 32fd62f7b001..59fbd4bef061 100644 --- a/packages/google-auth/system_tests/system_tests_sync/test_external_accounts.py +++ b/packages/google-auth/system_tests/system_tests_sync/test_external_accounts.py @@ -32,11 +32,13 @@ # original service account key. +import datetime import json import os import socket from tempfile import NamedTemporaryFile import threading +import time import sys import google.auth @@ -331,3 +333,45 @@ def test_aws_based_external_account( }, }, ) + + +# This test makes sure that setting up an executable to provide credentials +# works to allow access to Google resources. +def test_pluggable_external_account( + oidc_credentials, service_account_info, dns_access +): + now = datetime.datetime.now() + unix_seconds = time.mktime(now.timetuple()) + expiration_time = (unix_seconds + 1 * 60 * 60) * 1000 + credential = { + "success": True, + "version": 1, + "expiration_time": expiration_time, + "token_type": "urn:ietf:params:oauth:token-type:jwt", + "id_token": oidc_credentials.token, + } + + tmpfile = NamedTemporaryFile(delete=True) + with open(tmpfile.name, "w") as f: + f.write("#!/bin/bash\n") + f.write("echo \"{}\"\n".format(json.dumps(credential).replace('"', '\\"'))) + tmpfile.file.close() + + os.chmod(tmpfile.name, 0o777) + assert get_project_dns( + dns_access, + { + "type": "external_account", + "audience": _AUDIENCE_OIDC, + "subject_token_type": "urn:ietf:params:oauth:token-type:jwt", + "token_url": "https://sts.googleapis.com/v1/token", + "service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/{}:generateAccessToken".format( + oidc_credentials.service_account_email + ), + "credential_source": { + "executable": { + "command": tmpfile.name, + } + }, + }, + ) diff --git a/packages/google-auth/testing/requirements.txt b/packages/google-auth/testing/requirements.txt index df20f96d65fe..299c8f2ef534 100644 --- a/packages/google-auth/testing/requirements.txt +++ b/packages/google-auth/testing/requirements.txt @@ -17,4 +17,4 @@ grpcio pytest-asyncio; python_version > '3.0' aioresponses; python_version > '3.0' asynctest; python_version > '3.0' -aiohttp; python_version > '3.0' \ No newline at end of file +aiohttp; python_version > '3.0' From 3c64591303b9f73890474e69726a597db14b3126 Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Tue, 2 Aug 2022 09:45:59 -0400 Subject: [PATCH 597/966] chore(python): fix prerelease session [autoapprove] (#1090) Source-Link: https://github.com/googleapis/synthtool/commit/1b9ad7694e44ddb4d9844df55ff7af77b51a4435 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:9db98b055a7f8bd82351238ccaacfd3cda58cdf73012ab58b8da146368330021 Co-authored-by: Owl Bot Co-authored-by: Anthonios Partheniou --- packages/google-auth/.github/.OwlBot.lock.yaml | 4 ++-- .../google-auth/.kokoro/continuous/prerelease-deps.cfg | 7 +++++++ packages/google-auth/.kokoro/presubmit/prerelease-deps.cfg | 7 +++++++ packages/google-auth/.kokoro/test-samples-impl.sh | 4 ++-- 4 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 packages/google-auth/.kokoro/continuous/prerelease-deps.cfg create mode 100644 packages/google-auth/.kokoro/presubmit/prerelease-deps.cfg diff --git a/packages/google-auth/.github/.OwlBot.lock.yaml b/packages/google-auth/.github/.OwlBot.lock.yaml index 64f82d6bf4bc..0eb02fda4c09 100644 --- a/packages/google-auth/.github/.OwlBot.lock.yaml +++ b/packages/google-auth/.github/.OwlBot.lock.yaml @@ -13,5 +13,5 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:bc5eed3804aec2f05fad42aacf973821d9500c174015341f721a984a0825b6fd -# created: 2022-04-21T15:43:16.246106921Z + digest: sha256:9db98b055a7f8bd82351238ccaacfd3cda58cdf73012ab58b8da146368330021 +# created: 2022-07-25T16:02:49.174178716Z diff --git a/packages/google-auth/.kokoro/continuous/prerelease-deps.cfg b/packages/google-auth/.kokoro/continuous/prerelease-deps.cfg new file mode 100644 index 000000000000..3595fb43f5c0 --- /dev/null +++ b/packages/google-auth/.kokoro/continuous/prerelease-deps.cfg @@ -0,0 +1,7 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Only run this nox session. +env_vars: { + key: "NOX_SESSION" + value: "prerelease_deps" +} diff --git a/packages/google-auth/.kokoro/presubmit/prerelease-deps.cfg b/packages/google-auth/.kokoro/presubmit/prerelease-deps.cfg new file mode 100644 index 000000000000..3595fb43f5c0 --- /dev/null +++ b/packages/google-auth/.kokoro/presubmit/prerelease-deps.cfg @@ -0,0 +1,7 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Only run this nox session. +env_vars: { + key: "NOX_SESSION" + value: "prerelease_deps" +} diff --git a/packages/google-auth/.kokoro/test-samples-impl.sh b/packages/google-auth/.kokoro/test-samples-impl.sh index 8a324c9c7bc6..2c6500cae0b9 100755 --- a/packages/google-auth/.kokoro/test-samples-impl.sh +++ b/packages/google-auth/.kokoro/test-samples-impl.sh @@ -33,7 +33,7 @@ export PYTHONUNBUFFERED=1 env | grep KOKORO # Install nox -python3.6 -m pip install --upgrade --quiet nox +python3.9 -m pip install --upgrade --quiet nox # Use secrets acessor service account to get secrets if [[ -f "${KOKORO_GFILE_DIR}/secrets_viewer_service_account.json" ]]; then @@ -76,7 +76,7 @@ for file in samples/**/requirements.txt; do echo "------------------------------------------------------------" # Use nox to execute the tests for the project. - python3.6 -m nox -s "$RUN_TESTS_SESSION" + python3.9 -m nox -s "$RUN_TESTS_SESSION" EXIT=$? # If this is a periodic build, send the test log to the FlakyBot. From b400f7ec1efd26f5d3553a6d3ce25ef888fdb22a Mon Sep 17 00:00:00 2001 From: Ryan Kohler Date: Wed, 3 Aug 2022 19:40:37 -0400 Subject: [PATCH 598/966] fix: refactor credential subclass parameters (#1095) * fix: refactor credential subclass parameters * Changes requested by @clundin25 * hotlinking __init__ method * punctuation * getting unit tests to pass * fixing comments * Refresh system test token. Co-authored-by: Leo <39062083+lsirac@users.noreply.github.com> Co-authored-by: Carl Lundin --- packages/google-auth/google/auth/aws.py | 52 +++------------ .../google/auth/external_account.py | 54 +++++++++++++++- .../google-auth/google/auth/identity_pool.py | 60 +++--------------- packages/google-auth/google/auth/pluggable.py | 60 +++--------------- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes packages/google-auth/tests/test_aws.py | 4 ++ 6 files changed, 81 insertions(+), 149 deletions(-) diff --git a/packages/google-auth/google/auth/aws.py b/packages/google-auth/google/auth/aws.py index 873beef59b78..08c94427ef42 100644 --- a/packages/google-auth/google/auth/aws.py +++ b/packages/google-auth/google/auth/aws.py @@ -39,7 +39,6 @@ import hashlib import hmac -import io import json import os import posixpath @@ -352,13 +351,8 @@ def __init__( subject_token_type, token_url, credential_source=None, - service_account_impersonation_url=None, - service_account_impersonation_options={}, - client_id=None, - client_secret=None, - quota_project_id=None, - scopes=None, - default_scopes=None, + *args, + **kwargs ): """Instantiates an AWS workload external account credentials object. @@ -369,15 +363,8 @@ def __init__( credential_source (Mapping): The credential source dictionary used to provide instructions on how to retrieve external credential to be exchanged for Google access tokens. - service_account_impersonation_url (Optional[str]): The optional - service account impersonation getAccessToken URL. - client_id (Optional[str]): The optional client ID. - client_secret (Optional[str]): The optional client secret. - quota_project_id (Optional[str]): The optional quota project ID. - scopes (Optional[Sequence[str]]): Optional scopes to request during - the authorization grant. - default_scopes (Optional[Sequence[str]]): Default scopes passed by a - Google client library. Use 'scopes' for user-defined scopes. + args (List): Optional positional arguments passed into the underlying :meth:`~external_account.Credentials.__init__` method. + kwargs (Mapping): Optional keyword arguments passed into the underlying :meth:`~external_account.Credentials.__init__` method. Raises: google.auth.exceptions.RefreshError: If an error is encountered during @@ -393,13 +380,8 @@ def __init__( subject_token_type=subject_token_type, token_url=token_url, credential_source=credential_source, - service_account_impersonation_url=service_account_impersonation_url, - service_account_impersonation_options=service_account_impersonation_options, - client_id=client_id, - client_secret=client_secret, - quota_project_id=quota_project_id, - scopes=scopes, - default_scopes=default_scopes, + *args, + **kwargs ) credential_source = credential_source or {} self._environment_id = credential_source.get("environment_id") or "" @@ -750,23 +732,7 @@ def from_info(cls, info, **kwargs): Raises: ValueError: For invalid parameters. """ - return cls( - audience=info.get("audience"), - subject_token_type=info.get("subject_token_type"), - token_url=info.get("token_url"), - service_account_impersonation_url=info.get( - "service_account_impersonation_url" - ), - service_account_impersonation_options=info.get( - "service_account_impersonation" - ) - or {}, - client_id=info.get("client_id"), - client_secret=info.get("client_secret"), - credential_source=info.get("credential_source"), - quota_project_id=info.get("quota_project_id"), - **kwargs - ) + return super(Credentials, cls).from_info(info, **kwargs) @classmethod def from_file(cls, filename, **kwargs): @@ -779,6 +745,4 @@ def from_file(cls, filename, **kwargs): Returns: google.auth.aws.Credentials: The constructed credentials. """ - with io.open(filename, "r", encoding="utf-8") as json_file: - data = json.load(json_file) - return cls.from_info(data, **kwargs) + return super(Credentials, cls).from_file(filename, **kwargs) diff --git a/packages/google-auth/google/auth/external_account.py b/packages/google-auth/google/auth/external_account.py index 5c6ce2a40c15..a87f92ea4d36 100644 --- a/packages/google-auth/google/auth/external_account.py +++ b/packages/google-auth/google/auth/external_account.py @@ -30,6 +30,7 @@ import abc import copy import datetime +import io import json import re @@ -70,7 +71,7 @@ def __init__( token_url, credential_source, service_account_impersonation_url=None, - service_account_impersonation_options={}, + service_account_impersonation_options=None, client_id=None, client_secret=None, quota_project_id=None, @@ -482,3 +483,54 @@ def is_valid_url(patterns, url): return False return any(re.compile(p).match(uri.hostname.lower()) for p in patterns) + + @classmethod + def from_info(cls, info, **kwargs): + """Creates a Credentials instance from parsed external account info. + + Args: + info (Mapping[str, str]): The external account info in Google + format. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.identity_pool.Credentials: The constructed + credentials. + + Raises: + ValueError: For invalid parameters. + """ + return cls( + audience=info.get("audience"), + subject_token_type=info.get("subject_token_type"), + token_url=info.get("token_url"), + service_account_impersonation_url=info.get( + "service_account_impersonation_url" + ), + service_account_impersonation_options=info.get( + "service_account_impersonation" + ) + or {}, + client_id=info.get("client_id"), + client_secret=info.get("client_secret"), + credential_source=info.get("credential_source"), + quota_project_id=info.get("quota_project_id"), + workforce_pool_user_project=info.get("workforce_pool_user_project"), + **kwargs + ) + + @classmethod + def from_file(cls, filename, **kwargs): + """Creates a Credentials instance from an external account json file. + + Args: + filename (str): The path to the external account json file. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.identity_pool.Credentials: The constructed + credentials. + """ + with io.open(filename, "r", encoding="utf-8") as json_file: + data = json.load(json_file) + return cls.from_info(data, **kwargs) diff --git a/packages/google-auth/google/auth/identity_pool.py b/packages/google-auth/google/auth/identity_pool.py index a086d283e39b..5fa9faef99c0 100644 --- a/packages/google-auth/google/auth/identity_pool.py +++ b/packages/google-auth/google/auth/identity_pool.py @@ -56,14 +56,8 @@ def __init__( subject_token_type, token_url, credential_source, - service_account_impersonation_url=None, - service_account_impersonation_options={}, - client_id=None, - client_secret=None, - quota_project_id=None, - scopes=None, - default_scopes=None, - workforce_pool_user_project=None, + *args, + **kwargs ): """Instantiates an external account credentials object from a file/URL. @@ -91,21 +85,8 @@ def __init__( { "file": "/path/to/token/file.txt" } - - service_account_impersonation_url (Optional[str]): The optional service account - impersonation getAccessToken URL. - client_id (Optional[str]): The optional client ID. - client_secret (Optional[str]): The optional client secret. - quota_project_id (Optional[str]): The optional quota project ID. - scopes (Optional[Sequence[str]]): Optional scopes to request during the - authorization grant. - default_scopes (Optional[Sequence[str]]): Default scopes passed by a - Google client library. Use 'scopes' for user-defined scopes. - workforce_pool_user_project (Optona[str]): The optional workforce pool user - project number when the credential corresponds to a workforce pool and not - a workload identity pool. The underlying principal must still have - serviceusage.services.use IAM permission to use the project for - billing/quota. + args (List): Optional positional arguments passed into the underlying :meth:`~external_account.Credentials.__init__` method. + kwargs (Mapping): Optional keyword arguments passed into the underlying :meth:`~external_account.Credentials.__init__` method. Raises: google.auth.exceptions.RefreshError: If an error is encountered during @@ -122,14 +103,8 @@ def __init__( subject_token_type=subject_token_type, token_url=token_url, credential_source=credential_source, - service_account_impersonation_url=service_account_impersonation_url, - service_account_impersonation_options=service_account_impersonation_options, - client_id=client_id, - client_secret=client_secret, - quota_project_id=quota_project_id, - scopes=scopes, - default_scopes=default_scopes, - workforce_pool_user_project=workforce_pool_user_project, + *args, + **kwargs ) if not isinstance(credential_source, Mapping): self._credential_source_file = None @@ -257,24 +232,7 @@ def from_info(cls, info, **kwargs): Raises: ValueError: For invalid parameters. """ - return cls( - audience=info.get("audience"), - subject_token_type=info.get("subject_token_type"), - token_url=info.get("token_url"), - service_account_impersonation_url=info.get( - "service_account_impersonation_url" - ), - service_account_impersonation_options=info.get( - "service_account_impersonation" - ) - or {}, - client_id=info.get("client_id"), - client_secret=info.get("client_secret"), - credential_source=info.get("credential_source"), - quota_project_id=info.get("quota_project_id"), - workforce_pool_user_project=info.get("workforce_pool_user_project"), - **kwargs - ) + return super(Credentials, cls).from_info(info, **kwargs) @classmethod def from_file(cls, filename, **kwargs): @@ -288,6 +246,4 @@ def from_file(cls, filename, **kwargs): google.auth.identity_pool.Credentials: The constructed credentials. """ - with io.open(filename, "r", encoding="utf-8") as json_file: - data = json.load(json_file) - return cls.from_info(data, **kwargs) + return super(Credentials, cls).from_file(filename, **kwargs) diff --git a/packages/google-auth/google/auth/pluggable.py b/packages/google-auth/google/auth/pluggable.py index 96ccd9ca8714..ca583744a81b 100644 --- a/packages/google-auth/google/auth/pluggable.py +++ b/packages/google-auth/google/auth/pluggable.py @@ -35,7 +35,6 @@ # Python 2.7 compatibility except ImportError: # pragma: NO COVER from collections import Mapping -import io import json import os import subprocess @@ -58,14 +57,8 @@ def __init__( subject_token_type, token_url, credential_source, - service_account_impersonation_url=None, - service_account_impersonation_options={}, - client_id=None, - client_secret=None, - quota_project_id=None, - scopes=None, - default_scopes=None, - workforce_pool_user_project=None, + *args, + **kwargs ): """Instantiates an external account credentials object from a executables. @@ -86,21 +79,8 @@ def __init__( "output_file": "/path/to/generated/cached/credentials" } } - - service_account_impersonation_url (Optional[str]): The optional service account - impersonation getAccessToken URL. - client_id (Optional[str]): The optional client ID. - client_secret (Optional[str]): The optional client secret. - quota_project_id (Optional[str]): The optional quota project ID. - scopes (Optional[Sequence[str]]): Optional scopes to request during the - authorization grant. - default_scopes (Optional[Sequence[str]]): Default scopes passed by a - Google client library. Use 'scopes' for user-defined scopes. - workforce_pool_user_project (Optona[str]): The optional workforce pool user - project number when the credential corresponds to a workforce pool and not - a workload Pluggable. The underlying principal must still have - serviceusage.services.use IAM permission to use the project for - billing/quota. + args (List): Optional positional arguments passed into the underlying :meth:`~external_account.Credentials.__init__` method. + kwargs (Mapping): Optional keyword arguments passed into the underlying :meth:`~external_account.Credentials.__init__` method. Raises: google.auth.exceptions.RefreshError: If an error is encountered during @@ -117,13 +97,8 @@ def __init__( subject_token_type=subject_token_type, token_url=token_url, credential_source=credential_source, - service_account_impersonation_url=service_account_impersonation_url, - client_id=client_id, - client_secret=client_secret, - quota_project_id=quota_project_id, - scopes=scopes, - default_scopes=default_scopes, - workforce_pool_user_project=workforce_pool_user_project, + *args, + **kwargs ) if not isinstance(credential_source, Mapping): self._credential_source_executable = None @@ -250,24 +225,7 @@ def from_info(cls, info, **kwargs): Raises: ValueError: For invalid parameters. """ - return cls( - audience=info.get("audience"), - subject_token_type=info.get("subject_token_type"), - token_url=info.get("token_url"), - service_account_impersonation_url=info.get( - "service_account_impersonation_url" - ), - service_account_impersonation_options=info.get( - "service_account_impersonation" - ) - or {}, - client_id=info.get("client_id"), - client_secret=info.get("client_secret"), - credential_source=info.get("credential_source"), - quota_project_id=info.get("quota_project_id"), - workforce_pool_user_project=info.get("workforce_pool_user_project"), - **kwargs - ) + return super(Credentials, cls).from_info(info, **kwargs) @classmethod def from_file(cls, filename, **kwargs): @@ -281,9 +239,7 @@ def from_file(cls, filename, **kwargs): google.auth.pluggable.Credentials: The constructed credentials. """ - with io.open(filename, "r", encoding="utf-8") as json_file: - data = json.load(json_file) - return cls.from_info(data, **kwargs) + return super(Credentials, cls).from_file(filename, **kwargs) def _parse_subject_token(self, response): if "version" not in response: diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 97fcbb0dead259761d8dfa34c8031713dae74b0d..84604a64bb03894af031ae960d793489f24b5b7a 100644 GIT binary patch literal 10324 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 literal 10324 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 mX0s8}zG_aK^N7Np< Date: Thu, 4 Aug 2022 11:14:00 -0700 Subject: [PATCH 599/966] chore: update system creds (#1097) Co-authored-by: Jin Qin --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 84604a64bb03894af031ae960d793489f24b5b7a..acdba1adcb0fcc119992c4472761999e660e1d20 100644 GIT binary patch literal 10324 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 literal 10324 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 From 32b3f7bf1f05085510a0bb9ad267f9d4424a2a67 Mon Sep 17 00:00:00 2001 From: "Michael B. McCoy" Date: Thu, 4 Aug 2022 13:19:45 -0700 Subject: [PATCH 600/966] fix: Fix IDTokenCredentials update bug (#1072) * fix: IDTokenCredentials Constructor bug Co-authored-by: clundin25 <108372512+clundin25@users.noreply.github.com> --- .../google/auth/impersonated_credentials.py | 2 +- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../tests/test_impersonated_credentials.py | 5 ++++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index 29eab1684fda..4d0c4f0f162e 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -374,7 +374,7 @@ def __init__( def from_credentials(self, target_credentials, target_audience=None): return self.__class__( - target_credentials=self._target_credentials, + target_credentials=target_credentials, target_audience=target_audience, include_email=self._include_email, quota_project_id=self._quota_project_id, diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index acdba1adcb0fcc119992c4472761999e660e1d20..f86cedc83e95a5349696fde3f72287e3aad5120a 100644 GIT binary patch literal 10324 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 diff --git a/packages/google-auth/tests/test_impersonated_credentials.py b/packages/google-auth/tests/test_impersonated_credentials.py index f65fb754149e..0e4dc08d7cf3 100644 --- a/packages/google-auth/tests/test_impersonated_credentials.py +++ b/packages/google-auth/tests/test_impersonated_credentials.py @@ -465,14 +465,17 @@ def test_id_token_from_credential( assert credentials.valid assert not credentials.expired + new_credentials = self.make_credentials(lifetime=None) + id_creds = impersonated_credentials.IDTokenCredentials( credentials, target_audience=target_audience, include_email=True ) - id_creds = id_creds.from_credentials(target_credentials=credentials) + id_creds = id_creds.from_credentials(target_credentials=new_credentials) id_creds.refresh(request) assert id_creds.token == ID_TOKEN_DATA assert id_creds._include_email is True + assert id_creds._target_credentials is new_credentials def test_id_token_with_target_audience( self, mock_donor_credentials, mock_authorizedsession_idtoken From a63bd72eef93ba48ecde33c223d53d10c88f8ea5 Mon Sep 17 00:00:00 2001 From: Jin Date: Fri, 5 Aug 2022 10:12:27 -0700 Subject: [PATCH 601/966] fix: make expiration_time optional in response schema (#1091) * fix: allowing missing expire time in response * fix: adding sanity check for expiration time when using output file * update exception content * addressing comments * Update exception info * Update user doc * fix indents of rst * udpate user doc Co-authored-by: Jin Qin --- packages/google-auth/docs/user-guide.rst | 40 ++++++------ packages/google-auth/google/auth/pluggable.py | 9 ++- packages/google-auth/tests/test_pluggable.py | 61 ++++++++++++++++++- 3 files changed, 87 insertions(+), 23 deletions(-) diff --git a/packages/google-auth/docs/user-guide.rst b/packages/google-auth/docs/user-guide.rst index a0924789787a..16de58d9d90b 100644 --- a/packages/google-auth/docs/user-guide.rst +++ b/packages/google-auth/docs/user-guide.rst @@ -429,24 +429,28 @@ These are all required fields for an error response. The code and message fields will be used by the library as part of the thrown exception. -Response format fields summary: ``version``: The version of the JSON -output. Currently only version 1 is supported. ``success``: The -status of the response. When true, the response must contain the 3rd -party token, token type, and expiration. The executable must also exit -with exit code 0. When false, the response must contain the error code -and message fields and exit with a non-zero value. ``token_type``: -The 3rd party subject token type. Must be -*urn:ietf:params:oauth:token-type:jwt*, -*urn:ietf:params:oauth:token-type:id_token*, or -*urn:ietf:params:oauth:token-type:saml2*. ``id_token``: The 3rd party -OIDC token. ``saml_response``: The 3rd party SAML response. -``expiration_time``: The 3rd party subject token expiration time in -seconds (unix epoch time). ``code``: The error code string. -``message``: The error message. - -All response types must include both the ``version`` and ``success`` -fields. Successful responses must include the ``token_type``, -``expiration_time``, and one of ``id_token`` or ``saml_response``. +Response format fields summary: + +- ``version``: The version of the JSON output. Currently only version 1 is + supported. +- ``success``: The status of the response. + - When true, the response must contain the 3rd party token, token type, and expiration. The executable must also exit with exit code 0. + - When false, the response must contain the error code and message fields and exit with a non-zero value. +- ``token_type``: The 3rd party subject token type. Must be + - *urn:ietf:params:oauth:token-type:jwt* + - *urn:ietf:params:oauth:token-type:id_token* + - *urn:ietf:params:oauth:token-type:saml2* +- ``id_token``: The 3rd party OIDC token. +- ``saml_response``: The 3rd party SAML response. +- ``expiration_time``: The 3rd party subject token expiration time in seconds + (unix epoch time). +- ``code``: The error code string. +- ``message``: The error message. + +All response types must include both the ``version`` and ``success`` fields. +Successful responses must include the ``token_type``, and one of ``id_token`` +or ``saml_response``. +If output file is specified, ``expiration_time`` is mandatory. Error responses must include both the ``code`` and ``message`` fields. The library will populate the following environment variables when the diff --git a/packages/google-auth/google/auth/pluggable.py b/packages/google-auth/google/auth/pluggable.py index ca583744a81b..42f6bcd81466 100644 --- a/packages/google-auth/google/auth/pluggable.py +++ b/packages/google-auth/google/auth/pluggable.py @@ -262,11 +262,14 @@ def _parse_subject_token(self, response): response["code"], response["message"] ) ) - if "expiration_time" not in response: + if ( + "expiration_time" not in response + and self._credential_source_executable_output_file + ): raise ValueError( - "The executable response is missing the expiration_time field." + "The executable response must contain an expiration_time for successful responses when an output_file has been specified in the configuration." ) - if response["expiration_time"] < time.time(): + if "expiration_time" in response and response["expiration_time"] < time.time(): raise exceptions.RefreshError( "The token returned by the executable is expired." ) diff --git a/packages/google-auth/tests/test_pluggable.py b/packages/google-auth/tests/test_pluggable.py index 383b7a866976..b90c86c3afd3 100644 --- a/packages/google-auth/tests/test_pluggable.py +++ b/packages/google-auth/tests/test_pluggable.py @@ -636,7 +636,9 @@ def test_retrieve_subject_token_missing_error_code_message(self): ) @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) - def test_retrieve_subject_token_missing_expiration_time(self): + def test_retrieve_subject_token_without_expiration_time_should_fail_when_output_file_specified( + self + ): EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE = { "version": 1, "success": True, @@ -658,9 +660,64 @@ def test_retrieve_subject_token_missing_expiration_time(self): _ = credentials.retrieve_subject_token(None) assert excinfo.match( - r"The executable response is missing the expiration_time field." + r"The executable response must contain an expiration_time for successful responses when an output_file has been specified in the configuration." ) + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_retrieve_subject_token_without_expiration_time_should_fail_when_retrieving_from_output_file( + self + ): + ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE = "actual_output_file" + ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE = { + "command": "command", + "timeout_millis": 30000, + "output_file": ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, + } + ACTUAL_CREDENTIAL_SOURCE = {"executable": ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE} + data = self.EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_ID_TOKEN.copy() + data.pop("expiration_time") + + with open(ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, "w") as output_file: + json.dump(data, output_file) + + credentials = self.make_pluggable(credential_source=ACTUAL_CREDENTIAL_SOURCE) + + with pytest.raises(ValueError) as excinfo: + _ = credentials.retrieve_subject_token(None) + + assert excinfo.match( + r"The executable response must contain an expiration_time for successful responses when an output_file has been specified in the configuration." + ) + os.remove(ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE) + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_retrieve_subject_token_without_expiration_time_should_pass_when_output_file_not_specified( + self + ): + EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE = { + "version": 1, + "success": True, + "token_type": "urn:ietf:params:oauth:token-type:id_token", + "id_token": self.EXECUTABLE_OIDC_TOKEN, + } + + CREDENTIAL_SOURCE = { + "executable": {"command": "command", "timeout_millis": 30000} + } + + with mock.patch( + "subprocess.run", + return_value=subprocess.CompletedProcess( + args=[], + stdout=json.dumps(EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE).encode("UTF-8"), + returncode=0, + ), + ): + credentials = self.make_pluggable(credential_source=CREDENTIAL_SOURCE) + subject_token = credentials.retrieve_subject_token(None) + + assert subject_token == self.EXECUTABLE_OIDC_TOKEN + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) def test_retrieve_subject_token_missing_token_type(self): EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE = { From cde72e9ea12044c4ed655ec64f12919ef5809299 Mon Sep 17 00:00:00 2001 From: clundin25 <108372512+clundin25@users.noreply.github.com> Date: Fri, 5 Aug 2022 13:12:14 -0700 Subject: [PATCH 602/966] chore: Refresh system test creds (#1098) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10323 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index f86cedc83e95a5349696fde3f72287e3aad5120a..f5652a5a43307c097d5572a2a6fe506ef322c1c8 100644 GIT binary patch literal 10323 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 Date: Fri, 5 Aug 2022 14:10:42 -0700 Subject: [PATCH 603/966] chore(main): release 2.10.0 (#1087) --- packages/google-auth/CHANGELOG.md | 18 ++++++++++++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 685ef1cdcd16..e08322a6a685 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,24 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.10.0](https://github.com/googleapis/google-auth-library-python/compare/v2.9.1...v2.10.0) (2022-08-05) + + +### Features + +* add integration tests for pluggable auth ([#1073](https://github.com/googleapis/google-auth-library-python/issues/1073)) ([f8d776a](https://github.com/googleapis/google-auth-library-python/commit/f8d776a290270da8c43b0f5ba8e8a1fabfcf4dd3)) +* support for configurable token lifetime ([0dc6a9a](https://github.com/googleapis/google-auth-library-python/commit/0dc6a9a30b994f20ad027bfc3715792aa97bd8af)) +* support for configurable token lifetime ([#1079](https://github.com/googleapis/google-auth-library-python/issues/1079)) ([0dc6a9a](https://github.com/googleapis/google-auth-library-python/commit/0dc6a9a30b994f20ad027bfc3715792aa97bd8af)) + + +### Bug Fixes + +* async certificate decoding ([#1085](https://github.com/googleapis/google-auth-library-python/issues/1085)) ([741c6c6](https://github.com/googleapis/google-auth-library-python/commit/741c6c6f5e2d4e98cbae1e6c7a9bc128c6a97bae)) +* Async system tests were not unwrapping async_generators ([#1086](https://github.com/googleapis/google-auth-library-python/issues/1086)) ([29d248a](https://github.com/googleapis/google-auth-library-python/commit/29d248acaf554c2bdba81c96999371c9e610c6b6)) +* Fix IDTokenCredentials update bug ([#1072](https://github.com/googleapis/google-auth-library-python/issues/1072)) ([b62c25c](https://github.com/googleapis/google-auth-library-python/commit/b62c25ca408f72d86fda35b611edb3d2c6eb4f85)) +* make expiration_time optional in response schema ([#1091](https://github.com/googleapis/google-auth-library-python/issues/1091)) ([032fb8d](https://github.com/googleapis/google-auth-library-python/commit/032fb8d1685a50081974ba85e6ead946f30a1ea8)) +* refactor credential subclass parameters ([#1095](https://github.com/googleapis/google-auth-library-python/issues/1095)) ([8d15f69](https://github.com/googleapis/google-auth-library-python/commit/8d15f69711f38196934eabff5f05be26b3afcbf6)) + ## [2.9.1](https://github.com/googleapis/google-auth-library-python/compare/v2.9.0...v2.9.1) (2022-07-12) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index a6759c20a9b3..bcf2a36f4cf0 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.9.1" +__version__ = "2.10.0" From ddad81ef621af1d8d46fd423f581e2478f8e11de Mon Sep 17 00:00:00 2001 From: Jin Date: Mon, 15 Aug 2022 13:32:32 -0700 Subject: [PATCH 604/966] doc: workforce identity federation for pluggable auth (#1104) * docs: workforce identity federation for pluggable auth * fix indent * chore: Refresh system test creds * fix the subtitles not outlined --- packages/google-auth/docs/user-guide.rst | 245 ++++++++++++++++++ .../google-auth/system_tests/secrets.tar.enc | Bin 10323 -> 10324 bytes 2 files changed, 245 insertions(+) diff --git a/packages/google-auth/docs/user-guide.rst b/packages/google-auth/docs/user-guide.rst index 16de58d9d90b..e689b11c6b21 100644 --- a/packages/google-auth/docs/user-guide.rst +++ b/packages/google-auth/docs/user-guide.rst @@ -544,6 +544,251 @@ For AWS providers, use :meth:`aws.Credentials.from_info ['https://www.googleapis.com/auth/cloud-platform']) +External credentials (Workforce identity federation) +++++++++++++++++++++++++++++++++++++++++++++++++++++ + +`Workforce identity federation`_ lets you use an external identify provider +(IdP) to authenticate and authorize a workforce—a group of users, such as +employees, partners, and contractors—using IAM, so that the users can access +Google Cloud services. Workforce identity federation extends Google Cloud's +identity capabilities to support syncless, attribute-based single sign on. + +With workforce identity federation, your workforce can access Google Cloud +resources using an external identity provider (IdP) that supports OpenID +Connect (OIDC) or SAML 2.0 such as Azure Active Directory (Azure AD), Active +Directory Federation Services (AD FS), Okta, and others. + + +Accessing resources using an OIDC or SAML 2.0 identity provider +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In order to access Google Cloud resources from an identity provider that +supports `OpenID Connect (OIDC)`_, the following requirements are needed: + +- A workforce identity pool needs to be created. +- An OIDC or SAML 2.0 identity provider needs to be added in the workforce pool. + +Follow the detailed `instructions`_ on how to configure workforce identity +federation. + +After configuring an OIDC or SAML 2.0 provider, a credential configuration file +needs to be generated. The generated credential configuration file contains +non-sensitive metadata to instruct the library on how to retrieve external +subject tokens and exchange them for GCP access tokens. The configuration file +can be generated by using the `gcloud CLI`_. + +The Auth library can retrieve external subject tokens from a local file +location (file-sourced credentials), from a local server (URL-sourced +credentials) or by calling an executable (executable-sourced credentials). + +File-sourced credentials +++++++++++++++++++++++++ + +For file-sourced credentials, a background process needs to be continuously +refreshing the file location with a new subject token prior to expiration. For +tokens with one hour lifetimes, the token needs to be updated in the file every +hour. The token can be stored directly as plain text or in JSON format. + +To generate a file-sourced OIDC configuration, run the following command: + +.. code-block:: bash + + # Generate an OIDC configuration file for file-sourced credentials. + gcloud iam workforce-pools create-cred-config \ + locations/global/workforcePools/$WORKFORCE_POOL_ID/providers/$PROVIDER_ID \ + --subject-token-type=urn:ietf:params:oauth:token-type:id_token \ + --credential-source-file=$PATH_TO_OIDC_ID_TOKEN \ + --workforce-pool-user-project=$WORKFORCE_POOL_USER_PROJECT \ + # Optional arguments for file types. Default is "text": + # --credential-source-type "json" \ + # Optional argument for the field that contains the OIDC credential. + # This is required for json. + # --credential-source-field-name "id_token" \ + --output-file=/path/to/generated/config.json + +Where the following variables need to be substituted: + +* ``$WORKFORCE_POOL_ID``: The workforce pool ID. +* ``$PROVIDER_ID``: The provider ID. +* ``$PATH_TO_OIDC_ID_TOKEN``: The file path used to retrieve the OIDC token. +* ``$WORKFORCE_POOL_USER_PROJECT``: The project number associated with the + `workforce pools user project`_. + +To generate a file-sourced SAML configuration, run the following command: + +.. code-block:: bash + + # Generate a SAML configuration file for file-sourced credentials. + gcloud iam workforce-pools create-cred-config \ + locations/global/workforcePools/$WORKFORCE_POOL_ID/providers/$PROVIDER_ID \ + --credential-source-file=$PATH_TO_SAML_ASSERTION \ + --subject-token-type=urn:ietf:params:oauth:token-type:saml2 \ + --workforce-pool-user-project=$WORKFORCE_POOL_USER_PROJECT \ + --output-file=/path/to/generated/config.json + +Where the following variables need to be substituted: + +* ``$WORKFORCE_POOL_ID``: The workforce pool ID. +* ``$PROVIDER_ID``: The provider ID. +* ``$PATH_TO_SAML_ASSERTION``: The file path used to retrieve the + base64-encoded SAML assertion. +* ``$WORKFORCE_POOL_USER_PROJECT``: The project number associated with the + `workforce pools user project`_. + +These commands generate the configuration file in the specified output file. + +URL-sourced credentials ++++++++++++++++++++++++ + +For URL-sourced credentials, a local server needs to host a GET endpoint to +return the OIDC token. The response can be in plain text or JSON. Additional +required request headers can also be specified. + +To generate a URL-sourced OIDC workforce identity configuration, run the +following command: + +.. code-block:: bash + + # Generate an OIDC configuration file for URL-sourced credentials. + gcloud iam workforce-pools create-cred-config \ + locations/global/workforcePools/$WORKFORCE_POOL_ID/providers/$PROVIDER_ID \ + --subject-token-type=urn:ietf:params:oauth:token-type:id_token \ + --credential-source-url=$URL_TO_RETURN_OIDC_ID_TOKEN \ + --credential-source-headers $HEADER_KEY=$HEADER_VALUE \ + --workforce-pool-user-project=$WORKFORCE_POOL_USER_PROJECT \ + --output-file=/path/to/generated/config.json + +Where the following variables need to be substituted: + +* ``$WORKFORCE_POOL_ID``: The workforce pool ID. +* ``$PROVIDER_ID``: The provider ID. +* ``$URL_TO_RETURN_OIDC_ID_TOKEN``: The URL of the local server endpoint. +* ``$HEADER_KEY`` and ``$HEADER_VALUE``: The additional header key/value + pairs to pass along the GET request to ``$URL_TO_GET_OIDC_TOKEN``, e.g. + ``Metadata-Flavor=Google``. +* ``$WORKFORCE_POOL_USER_PROJECT``: The project number associated with the + `workforce pools user project`_. + +To generate a URL-sourced SAML configuration, run the following command: + +.. code-block:: bash + + # Generate a SAML configuration file for file-sourced credentials. + gcloud iam workforce-pools create-cred-config \ + locations/global/workforcePools/$WORKFORCE_POOL_ID/providers/$PROVIDER_ID \ + --subject-token-type=urn:ietf:params:oauth:token-type:saml2 \ + --credential-source-url=$URL_TO_GET_SAML_ASSERTION \ + --credential-source-headers $HEADER_KEY=$HEADER_VALUE \ + --workforce-pool-user-project=$WORKFORCE_POOL_USER_PROJECT \ + --output-file=/path/to/generated/config.json + +These commands generate the configuration file in the specified output file. + +Where the following variables need to be substituted: + +* ``$WORKFORCE_POOL_ID``: The workforce pool ID. +* ``$PROVIDER_ID``: The provider ID. +* ``$URL_TO_GET_SAML_ASSERTION``: The URL of the local server endpoint. +* ``$HEADER_KEY`` and ``$HEADER_VALUE``: The additional header key/value + pairs to pass along the GET request to ``$URL_TO_GET_SAML_ASSERTION``, e.g. + ``Metadata-Flavor=Google``. +* ``$WORKFORCE_POOL_USER_PROJECT``: The project number associated with the + `workforce pools user project`_. + +Using Executable-sourced workforce credentials with OIDC and SAML +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**Executable-sourced credentials** For executable-sourced credentials, a local +executable is used to retrieve the 3rd party token. The executable must handle +providing a valid, unexpired OIDC ID token or SAML assertion in JSON format to +stdout. + +To use executable-sourced credentials, the +``GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES`` environment variable must be set +to ``1``. + +To generate an executable-sourced workforce identity configuration, run the +following command: + +.. code-block:: bash + + # Generate a configuration file for executable-sourced credentials. + gcloud iam workforce-pools create-cred-config \ + locations/global/workforcePools/$WORKFORCE_POOL_ID/providers/$PROVIDER_ID \ + --subject-token-type=$SUBJECT_TOKEN_TYPE \ + # The absolute path for the program, including arguments. + # e.g. --executable-command="/path/to/command --foo=bar" + --executable-command=$EXECUTABLE_COMMAND \ + # Optional argument for the executable timeout. Defaults to 30s. + # --executable-timeout-millis=$EXECUTABLE_TIMEOUT \ + # Optional argument for the absolute path to the executable output file. + # See below on how this argument impacts the library behaviour. + # --executable-output-file=$EXECUTABLE_OUTPUT_FILE \ + --workforce-pool-user-project=$WORKFORCE_POOL_USER_PROJECT \ + --output-file /path/to/generated/config.json + +Where the following variables need to be substituted: + +* ``$WORKFORCE_POOL_ID``: The workforce pool ID. +* ``$PROVIDER_ID``: The provider ID. +* ``$SUBJECT_TOKEN_TYPE``: The subject token type. +* ``$EXECUTABLE_COMMAND``: The full command to run, including arguments. Must be + an absolute path to the program. +* ``$WORKFORCE_POOL_USER_PROJECT``: The project number associated with the + workforce pools user project. + +The ``--executable-timeout-millis`` flag is optional. This is the duration for +which the auth library will wait for the executable to finish, in milliseconds. +Defaults to 30 seconds when not provided. The maximum allowed value is 2 +minutes. The minimum is 5 seconds. + +The ``--executable-output-file`` flag is optional. If provided, the file path +must point to the 3rd party credential response generated by the executable. +This is useful for caching the credentials. By specifying this path, the Auth +libraries will first check for its existence before running the executable. By +caching the executable JSON response to this file, it improves performance as it +avoids the need to run the executable until the cached credentials in the output +file are expired. The executable must handle writing to this file - the auth +libraries will only attempt to read from this location. The format of contents +in the file should match the JSON format expected by the executable shown below. + +To retrieve the 3rd party token, the library will call the executable using the +command specified. The executable's output must adhere to the response format +specified below. It must output the response to stdout. + +Refer to the `using executable-sourced credentials with Workload Identity +Federation `__ above +for the executable response specification. + +Security considerations +~~~~~~~~~~~~~~~~~~~~~~~ + +The following security practices are highly recommended: + +* Access to the script should be restricted as it will be displaying credentials + to stdout. This ensures that rogue processes do not gain access to the script. +* The configuration file should not be modifiable. Write access should be + restricted to avoid processes modifying the executable command portion. + +Given the complexity of using executable-sourced credentials, it is recommended +to use the existing supported mechanisms (file-sourced/URL-sourced) for +providing 3rd party credentials unless they do not meet your specific +requirements. + +You can now `use the Auth library <#using-external-identities>`__ to call Google +Cloud resources from an OIDC or SAML provider. + + +.. _Workforce Identity Federation: + https://cloud.google.com/iam/docs/workforce-identity-federation +.. _OpenID Connect (OIDC): https://openid.net/connect/ +.. _instructions: + https://cloud.google.com/iam/docs/configuring-workforce-identity-federation +.. _gcloud CLI: https://cloud.google.com/sdk/ +.. _workforce pools user project: + https://cloud.google.com/iam/docs/workforce-identity-federation#workforce-pools-user-project + + Impersonated credentials ++++++++++++++++++++++++ diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index f5652a5a43307c097d5572a2a6fe506ef322c1c8..77f7e7a2db30dce540e9457b8fe26019d13c781c 100644 GIT binary patch literal 10324 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 lJh>B>L8vNy;PZbrz=mwr#8V Date: Thu, 18 Aug 2022 23:02:14 +0300 Subject: [PATCH 605/966] Fix: Async certificate retrieving (#1101) * fix: Handle certificate retrieving in a more proper way --- .../google/oauth2/_id_token_async.py | 4 ++-- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../tests_async/oauth2/test_id_token.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/google-auth/google/oauth2/_id_token_async.py b/packages/google-auth/google/oauth2/_id_token_async.py index b90994cdcc7a..c32dfa47d1bc 100644 --- a/packages/google-auth/google/oauth2/_id_token_async.py +++ b/packages/google-auth/google/oauth2/_id_token_async.py @@ -93,9 +93,9 @@ async def _fetch_certs(request, certs_url): "Could not fetch certificates at {}".format(certs_url) ) - data = await response.data.read() + data = await response.content() - return json.loads(data.decode("utf-8")) + return json.loads(data) async def verify_token( diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 77f7e7a2db30dce540e9457b8fe26019d13c781c..ec376950bd458b8105dc47c111bcd1adcb90b406 100644 GIT binary patch literal 10324 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 literal 10324 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 Date: Thu, 18 Aug 2022 18:00:47 -0400 Subject: [PATCH 606/966] feat: add integration tests for configurable token lifespan (#1103) * feat: add integration tests for configurable token lifespan * Reducing buffer to 5 seconds --- .../test_external_accounts.py | 56 ++++++++++++++++--- 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/packages/google-auth/system_tests/system_tests_sync/test_external_accounts.py b/packages/google-auth/system_tests/system_tests_sync/test_external_accounts.py index 59fbd4bef061..29355f479512 100644 --- a/packages/google-auth/system_tests/system_tests_sync/test_external_accounts.py +++ b/packages/google-auth/system_tests/system_tests_sync/test_external_accounts.py @@ -42,6 +42,7 @@ import sys import google.auth +from google.auth import _helpers from googleapiclient import discovery from six.moves import BaseHTTPServer from google.oauth2 import service_account @@ -132,7 +133,6 @@ def get_project_dns(dns_access, credential_data): with NamedTemporaryFile() as credfile: credfile.write(json.dumps(credential_data).encode("utf-8")) credfile.flush() - old_credentials = os.environ.get("GOOGLE_APPLICATION_CREDENTIALS") with patch.dict(os.environ, {"GOOGLE_APPLICATION_CREDENTIALS": credfile.name}): # If our setup and credential file are correct, @@ -150,9 +150,7 @@ def get_xml_value_by_tagname(data, tagname): # This test makes sure that setting an accesible credential file # works to allow access to Google resources. -def test_file_based_external_account( - oidc_credentials, service_account_info, dns_access -): +def test_file_based_external_account(oidc_credentials, dns_access): with NamedTemporaryFile() as tmpfile: tmpfile.write(oidc_credentials.token.encode("utf-8")) tmpfile.flush() @@ -173,10 +171,11 @@ def test_file_based_external_account( }, ) + # This test makes sure that setting a token lifetime works # for service account impersonation. def test_file_based_external_account_with_configure_token_lifetime( - oidc_credentials, service_account_info, dns_access + oidc_credentials, dns_access ): with NamedTemporaryFile() as tmpfile: tmpfile.write(oidc_credentials.token.encode("utf-8")) @@ -202,6 +201,47 @@ def test_file_based_external_account_with_configure_token_lifetime( ) +def test_configurable_token_lifespan(oidc_credentials, http_request): + TOKEN_LIFETIME_SECONDS = 2800 + BUFFER_SECONDS = 5 + + def check_impersonation_expiration(): + # First, get the default credentials. + credentials, _ = google.auth.default( + scopes=["https://www.googleapis.com/auth/cloud-platform.read-only"], + request=http_request, + ) + + utcmax = _helpers.utcnow() + datetime.timedelta(seconds=TOKEN_LIFETIME_SECONDS) + utcmin = utcmax - datetime.timedelta(seconds=BUFFER_SECONDS) + assert utcmin < credentials._impersonated_credentials.expiry <= utcmax + + return True + + with NamedTemporaryFile() as tmpfile: + tmpfile.write(oidc_credentials.token.encode("utf-8")) + tmpfile.flush() + + assert get_project_dns( + check_impersonation_expiration, + { + "type": "external_account", + "audience": _AUDIENCE_OIDC, + "subject_token_type": "urn:ietf:params:oauth:token-type:jwt", + "token_url": "https://sts.googleapis.com/v1/token", + "service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/{}:generateAccessToken".format( + oidc_credentials.service_account_email + ), + "service_account_impersonation": { + "token_lifetime_seconds": TOKEN_LIFETIME_SECONDS, + }, + "credential_source": { + "file": tmpfile.name, + }, + }, + ) + + # This test makes sure that setting up an http server to provide credentials # works to allow access to Google resources. def test_url_based_external_account(dns_access, oidc_credentials, service_account_info): @@ -337,9 +377,7 @@ def test_aws_based_external_account( # This test makes sure that setting up an executable to provide credentials # works to allow access to Google resources. -def test_pluggable_external_account( - oidc_credentials, service_account_info, dns_access -): +def test_pluggable_external_account(oidc_credentials, service_account_info, dns_access): now = datetime.datetime.now() unix_seconds = time.mktime(now.timetuple()) expiration_time = (unix_seconds + 1 * 60 * 60) * 1000 @@ -354,7 +392,7 @@ def test_pluggable_external_account( tmpfile = NamedTemporaryFile(delete=True) with open(tmpfile.name, "w") as f: f.write("#!/bin/bash\n") - f.write("echo \"{}\"\n".format(json.dumps(credential).replace('"', '\\"'))) + f.write('echo "{}"\n'.format(json.dumps(credential).replace('"', '\\"'))) tmpfile.file.close() os.chmod(tmpfile.name, 0o777) From 01f3f875c946f1d2859cc297dd2b0b6841745c64 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Fri, 19 Aug 2022 16:25:38 -0700 Subject: [PATCH 607/966] chore(main): release 2.11.0 (#1106) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 12 ++++++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index e08322a6a685..7b1d296cc0e5 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,18 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.11.0](https://github.com/googleapis/google-auth-library-python/compare/v2.10.0...v2.11.0) (2022-08-18) + + +### Features + +* add integration tests for configurable token lifespan ([#1103](https://github.com/googleapis/google-auth-library-python/issues/1103)) ([124bae6](https://github.com/googleapis/google-auth-library-python/commit/124bae60771a8984674a1d7eeab3ec22b2fa0033)) + + +### Bug Fixes + +* Async certificate retrieving ([#1101](https://github.com/googleapis/google-auth-library-python/issues/1101)) ([05f125d](https://github.com/googleapis/google-auth-library-python/commit/05f125def1205a14db52c870f2bfcef47f047206)) + ## [2.10.0](https://github.com/googleapis/google-auth-library-python/compare/v2.9.1...v2.10.0) (2022-08-05) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index bcf2a36f4cf0..10a1c7fd97e2 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.10.0" +__version__ = "2.11.0" From e932701c09833c71d02c9caba3fc47c39d8e2876 Mon Sep 17 00:00:00 2001 From: Sita Lakshmi Sangameswaran Date: Tue, 23 Aug 2022 11:21:24 +0530 Subject: [PATCH 608/966] docs(samples): add auth samples and tests (#1102) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs(samples): add auth samples and tests * refactored verifying google token and lint fixed test file * Modified comment acc to review * renamed method acc to review comment * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * added comment acc to review * add samples tests as required checks * use GOOGLE_CLOUD_PROJECT * test new config 1 * adding refresh token for sys test * updating all py verion configs * update 3 * update 4 * update 5 - trimming nox * update 6 - fixing requirements.txt * update 7 - fixing pytest flags * update 8 - fixing sa test cred * update 9- reading sa path from env * update 10- testing explicit * update 11 - fix multi reference * update 12 - remove project id from client params * update 13 - use projectid from default * update 14 - remove project param * update 15- fix assert * update 16 - updating other py versions * update 17: try replacing compute with storage * update 18: fix assert and pass project * update 19: fixing comments * update 20: remove unused Co-authored-by: Owl Bot Co-authored-by: Anthonios Partheniou Co-authored-by: Sai Sunder Srinivasan --- .../.github/sync-repo-settings.yaml | 4 + .../google-auth/.kokoro/samples-test-setup.sh | 48 +++++++++++ .../.kokoro/samples/lint/common.cfg | 35 ++++---- .../.kokoro/samples/python3.10/common.cfg | 39 ++++----- .../.kokoro/samples/python3.7/common.cfg | 39 ++++----- .../.kokoro/samples/python3.8/common.cfg | 39 ++++----- .../.kokoro/samples/python3.9/common.cfg | 39 ++++----- .../authenticate_explicit_with_adc.py | 55 +++++++++++++ .../authenticate_implicit_with_adc.py | 46 +++++++++++ .../idtoken_from_impersonated_credentials.py | 75 +++++++++++++++++ .../snippets/idtoken_from_metadata_server.py | 50 ++++++++++++ .../snippets/idtoken_from_service_account.py | 50 ++++++++++++ .../samples/cloud-client/snippets/noxfile.py | 48 +++++++++++ .../cloud-client/snippets/noxfile_config.py | 38 +++++++++ .../cloud-client/snippets/requirements.txt | 4 + .../cloud-client/snippets/snippets_test.py | 77 ++++++++++++++++++ .../snippets/verify_google_idtoken.py | 62 ++++++++++++++ .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 18 files changed, 633 insertions(+), 115 deletions(-) create mode 100755 packages/google-auth/.kokoro/samples-test-setup.sh create mode 100644 packages/google-auth/samples/cloud-client/snippets/authenticate_explicit_with_adc.py create mode 100644 packages/google-auth/samples/cloud-client/snippets/authenticate_implicit_with_adc.py create mode 100644 packages/google-auth/samples/cloud-client/snippets/idtoken_from_impersonated_credentials.py create mode 100644 packages/google-auth/samples/cloud-client/snippets/idtoken_from_metadata_server.py create mode 100644 packages/google-auth/samples/cloud-client/snippets/idtoken_from_service_account.py create mode 100644 packages/google-auth/samples/cloud-client/snippets/noxfile.py create mode 100644 packages/google-auth/samples/cloud-client/snippets/noxfile_config.py create mode 100644 packages/google-auth/samples/cloud-client/snippets/requirements.txt create mode 100644 packages/google-auth/samples/cloud-client/snippets/snippets_test.py create mode 100644 packages/google-auth/samples/cloud-client/snippets/verify_google_idtoken.py diff --git a/packages/google-auth/.github/sync-repo-settings.yaml b/packages/google-auth/.github/sync-repo-settings.yaml index 675cd7c93097..ab8bb03fa44f 100644 --- a/packages/google-auth/.github/sync-repo-settings.yaml +++ b/packages/google-auth/.github/sync-repo-settings.yaml @@ -11,6 +11,10 @@ branchProtectionRules: - 'OwlBot Post Processor' - 'Kokoro system-3.7' - 'Kokoro' + - 'Samples - Python 3.7' + - 'Samples - Python 3.8' + - 'Samples - Python 3.9' + - 'Samples - Python 3.10' permissionRules: - team: actools-python permission: admin diff --git a/packages/google-auth/.kokoro/samples-test-setup.sh b/packages/google-auth/.kokoro/samples-test-setup.sh new file mode 100755 index 000000000000..938402a92202 --- /dev/null +++ b/packages/google-auth/.kokoro/samples-test-setup.sh @@ -0,0 +1,48 @@ +#!/bin/bash +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -eo pipefail + +if [[ -z "${PROJECT_ROOT:-}" ]]; then + PROJECT_ROOT="github/google-auth-library-python" +fi + +cd "${PROJECT_ROOT}" + +# Disable buffering, so that the logs stream through. +export PYTHONUNBUFFERED=1 + +# Remove old nox +python3 -m pip uninstall --yes --quiet nox-automation + +# Install nox +python3 -m pip install --upgrade --quiet nox +python3 -m nox --version + +# Setup service account credentials. +export GOOGLE_APPLICATION_CREDENTIALS=${KOKORO_GFILE_DIR}/service-account.json + +# Setup project id. +export PROJECT_ID=$(cat "${KOKORO_GFILE_DIR}/project-id.txt") + +# Activate gcloud with service account credentials +gcloud auth activate-service-account --key-file=$GOOGLE_APPLICATION_CREDENTIALS +gcloud config set project ${PROJECT_ID} + +# Decrypt system test secrets +./scripts/decrypt-secrets.sh + +# Run system tests which use a different noxfile +python3 -m nox -f samples/cloud-client/snippets/noxfile.py diff --git a/packages/google-auth/.kokoro/samples/lint/common.cfg b/packages/google-auth/.kokoro/samples/lint/common.cfg index f6b0c07c6533..da47830eb659 100644 --- a/packages/google-auth/.kokoro/samples/lint/common.cfg +++ b/packages/google-auth/.kokoro/samples/lint/common.cfg @@ -7,28 +7,25 @@ action { } } -# Specify which tests to run -env_vars: { - key: "RUN_TESTS_SESSION" - value: "lint" -} +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/google-auth-library-python/.kokoro/test-samples.sh" -} +# Download resources for system tests (service account key, etc.) +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-auth-library-python" + +# Use the trampoline script to run in docker. +build_file: "google-auth-library-python/.kokoro/trampoline.sh" # Configure the docker image for kokoro-trampoline. env_vars: { key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-kokoro-resources/python-samples-testing-docker" + value: "gcr.io/cloud-devrel-kokoro-resources/python-multi" } - -# Download secrets for samples -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/python-docs-samples" - -# Download trampoline resources. -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" - -# Use the trampoline script to run in docker. -build_file: "google-auth-library-python/.kokoro/trampoline_v2.sh" \ No newline at end of file +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/google-auth-library-python/.kokoro/build.sh" +} +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/google-auth-library-python/.kokoro/samples-test-setup.sh" +} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.10/common.cfg b/packages/google-auth/.kokoro/samples/python3.10/common.cfg index de052d35fc5e..da47830eb659 100644 --- a/packages/google-auth/.kokoro/samples/python3.10/common.cfg +++ b/packages/google-auth/.kokoro/samples/python3.10/common.cfg @@ -7,34 +7,25 @@ action { } } -# Specify which tests to run -env_vars: { - key: "RUN_TESTS_SESSION" - value: "py-3.10" -} +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" -# Declare build specific Cloud project. -env_vars: { - key: "BUILD_SPECIFIC_GCLOUD_PROJECT" - value: "python-docs-samples-tests-310" -} +# Download resources for system tests (service account key, etc.) +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-auth-library-python" -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/google-auth-library-python/.kokoro/test-samples.sh" -} +# Use the trampoline script to run in docker. +build_file: "google-auth-library-python/.kokoro/trampoline.sh" # Configure the docker image for kokoro-trampoline. env_vars: { key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-kokoro-resources/python-samples-testing-docker" + value: "gcr.io/cloud-devrel-kokoro-resources/python-multi" } - -# Download secrets for samples -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/python-docs-samples" - -# Download trampoline resources. -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" - -# Use the trampoline script to run in docker. -build_file: "google-auth-library-python/.kokoro/trampoline_v2.sh" \ No newline at end of file +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/google-auth-library-python/.kokoro/build.sh" +} +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/google-auth-library-python/.kokoro/samples-test-setup.sh" +} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.7/common.cfg b/packages/google-auth/.kokoro/samples/python3.7/common.cfg index 7ca2eb0eb6bd..da47830eb659 100644 --- a/packages/google-auth/.kokoro/samples/python3.7/common.cfg +++ b/packages/google-auth/.kokoro/samples/python3.7/common.cfg @@ -7,34 +7,25 @@ action { } } -# Specify which tests to run -env_vars: { - key: "RUN_TESTS_SESSION" - value: "py-3.7" -} +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" -# Declare build specific Cloud project. -env_vars: { - key: "BUILD_SPECIFIC_GCLOUD_PROJECT" - value: "python-docs-samples-tests-py37" -} +# Download resources for system tests (service account key, etc.) +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-auth-library-python" -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/google-auth-library-python/.kokoro/test-samples.sh" -} +# Use the trampoline script to run in docker. +build_file: "google-auth-library-python/.kokoro/trampoline.sh" # Configure the docker image for kokoro-trampoline. env_vars: { key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-kokoro-resources/python-samples-testing-docker" + value: "gcr.io/cloud-devrel-kokoro-resources/python-multi" } - -# Download secrets for samples -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/python-docs-samples" - -# Download trampoline resources. -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" - -# Use the trampoline script to run in docker. -build_file: "google-auth-library-python/.kokoro/trampoline_v2.sh" \ No newline at end of file +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/google-auth-library-python/.kokoro/build.sh" +} +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/google-auth-library-python/.kokoro/samples-test-setup.sh" +} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.8/common.cfg b/packages/google-auth/.kokoro/samples/python3.8/common.cfg index fbd029e68fe1..da47830eb659 100644 --- a/packages/google-auth/.kokoro/samples/python3.8/common.cfg +++ b/packages/google-auth/.kokoro/samples/python3.8/common.cfg @@ -7,34 +7,25 @@ action { } } -# Specify which tests to run -env_vars: { - key: "RUN_TESTS_SESSION" - value: "py-3.8" -} +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" -# Declare build specific Cloud project. -env_vars: { - key: "BUILD_SPECIFIC_GCLOUD_PROJECT" - value: "python-docs-samples-tests-py38" -} +# Download resources for system tests (service account key, etc.) +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-auth-library-python" -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/google-auth-library-python/.kokoro/test-samples.sh" -} +# Use the trampoline script to run in docker. +build_file: "google-auth-library-python/.kokoro/trampoline.sh" # Configure the docker image for kokoro-trampoline. env_vars: { key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-kokoro-resources/python-samples-testing-docker" + value: "gcr.io/cloud-devrel-kokoro-resources/python-multi" } - -# Download secrets for samples -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/python-docs-samples" - -# Download trampoline resources. -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" - -# Use the trampoline script to run in docker. -build_file: "google-auth-library-python/.kokoro/trampoline_v2.sh" \ No newline at end of file +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/google-auth-library-python/.kokoro/build.sh" +} +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/google-auth-library-python/.kokoro/samples-test-setup.sh" +} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.9/common.cfg b/packages/google-auth/.kokoro/samples/python3.9/common.cfg index 07cda0ab20e5..da47830eb659 100644 --- a/packages/google-auth/.kokoro/samples/python3.9/common.cfg +++ b/packages/google-auth/.kokoro/samples/python3.9/common.cfg @@ -7,34 +7,25 @@ action { } } -# Specify which tests to run -env_vars: { - key: "RUN_TESTS_SESSION" - value: "py-3.9" -} +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" -# Declare build specific Cloud project. -env_vars: { - key: "BUILD_SPECIFIC_GCLOUD_PROJECT" - value: "python-docs-samples-tests-py39" -} +# Download resources for system tests (service account key, etc.) +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-auth-library-python" -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/google-auth-library-python/.kokoro/test-samples.sh" -} +# Use the trampoline script to run in docker. +build_file: "google-auth-library-python/.kokoro/trampoline.sh" # Configure the docker image for kokoro-trampoline. env_vars: { key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-kokoro-resources/python-samples-testing-docker" + value: "gcr.io/cloud-devrel-kokoro-resources/python-multi" } - -# Download secrets for samples -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/python-docs-samples" - -# Download trampoline resources. -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" - -# Use the trampoline script to run in docker. -build_file: "google-auth-library-python/.kokoro/trampoline_v2.sh" \ No newline at end of file +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/google-auth-library-python/.kokoro/build.sh" +} +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/google-auth-library-python/.kokoro/samples-test-setup.sh" +} \ No newline at end of file diff --git a/packages/google-auth/samples/cloud-client/snippets/authenticate_explicit_with_adc.py b/packages/google-auth/samples/cloud-client/snippets/authenticate_explicit_with_adc.py new file mode 100644 index 000000000000..8483bd7ea3be --- /dev/null +++ b/packages/google-auth/samples/cloud-client/snippets/authenticate_explicit_with_adc.py @@ -0,0 +1,55 @@ +# Copyright 2022 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START auth_cloud_explicit_adc] + +from google.cloud import storage + +import google.oauth2.credentials +import google.auth + + +def authenticate_explicit_with_adc(): + """ + List storage buckets by authenticating with ADC. + + // TODO(Developer): + // 1. Before running this sample, + // set up ADC as described in https://cloud.google.com/docs/authentication/external/set-up-adc + // 2. Replace the project variable. + // 3. Make sure you have the necessary permission to list storage buckets: "storage.buckets.list" + """ + + # Construct the Google credentials object which obtains the default configuration from your + # working environment. + # google.auth.default() will give you ComputeEngineCredentials + # if you are on a GCE (or other metadata server supported environments). + credentials, project_id = google.auth.default() + # If you are authenticating to a Cloud API, you can let the library include the default scope, + # https://www.googleapis.com/auth/cloud-platform, because IAM is used to provide fine-grained + # permissions for Cloud. + # If you need to provide a scope, specify it as follows: + # credentials = google.auth.default(scopes=scope) + # For more information on scopes to use, + # see: https://developers.google.com/identity/protocols/oauth2/scopes + + # Construct the Storage client. + storage_client = storage.Client(credentials=credentials, project=project_id) + buckets = storage_client.list_buckets() + print("Buckets:") + for bucket in buckets: + print(bucket.name) + print("Listed all storage buckets.") + +# [END auth_cloud_explicit_adc] diff --git a/packages/google-auth/samples/cloud-client/snippets/authenticate_implicit_with_adc.py b/packages/google-auth/samples/cloud-client/snippets/authenticate_implicit_with_adc.py new file mode 100644 index 000000000000..006e75bf69ec --- /dev/null +++ b/packages/google-auth/samples/cloud-client/snippets/authenticate_implicit_with_adc.py @@ -0,0 +1,46 @@ +# Copyright 2022 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START auth_cloud_implicit_adc] + +from google.cloud import storage + + +def authenticate_implicit_with_adc(project_id="your-google-cloud-project-id"): + """ + When interacting with Google Cloud Client libraries, the library can auto-detect the + credentials to use. + + // TODO(Developer): + // 1. Before running this sample, + // set up ADC as described in https://cloud.google.com/docs/authentication/external/set-up-adc + // 2. Replace the project variable. + // 3. Make sure that the user account or service account that you are using + // has the required permissions. For this sample, you must have "storage.buckets.list". + Args: + project_id: The project id of your Google Cloud project. + """ + + # This snippet demonstrates how to list buckets. + # *NOTE*: Replace the client created below with the client required for your application. + # Note that the credentials are not specified when constructing the client. + # Hence, the client library will look for credentials using ADC. + storage_client = storage.Client(project=project_id) + buckets = storage_client.list_buckets() + print("Buckets:") + for bucket in buckets: + print(bucket.name) + print("Listed all storage buckets.") + +# [END auth_cloud_implicit_adc] \ No newline at end of file diff --git a/packages/google-auth/samples/cloud-client/snippets/idtoken_from_impersonated_credentials.py b/packages/google-auth/samples/cloud-client/snippets/idtoken_from_impersonated_credentials.py new file mode 100644 index 000000000000..a27e6cffd797 --- /dev/null +++ b/packages/google-auth/samples/cloud-client/snippets/idtoken_from_impersonated_credentials.py @@ -0,0 +1,75 @@ +# Copyright 2022 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [auth_cloud_idtoken_impersonated_credentials] + +import google +from google.auth import impersonated_credentials +import google.auth.transport.requests + + +def idtoken_from_impersonated_credentials( + impersonated_service_account: str, scope: str, target_audience: str): + """ + Use a service account (SA1) to impersonate as another service account (SA2) and obtain id token + for the impersonated account. + To obtain token for SA2, SA1 should have the "roles/iam.serviceAccountTokenCreator" permission + on SA2. + + Args: + impersonated_service_account: The name of the privilege-bearing service account for whom the credential is created. + Examples: name@project.service.gserviceaccount.com + + scope: Provide the scopes that you might need to request to access Google APIs, + depending on the level of access you need. + For this example, we use the cloud-wide scope and use IAM to narrow the permissions. + https://cloud.google.com/docs/authentication#authorization_for_services + For more information, see: https://developers.google.com/identity/protocols/oauth2/scopes + + target_audience: The service name for which the id token is requested. Service name refers to the + logical identifier of an API service, such as "iap.googleapis.com". + Examples: iap.googleapis.com + """ + + # Construct the GoogleCredentials object which obtains the default configuration from your + # working environment. + credentials, project_id = google.auth.default() + + # Create the impersonated credential. + target_credentials = impersonated_credentials.Credentials( + source_credentials=credentials, + target_principal=impersonated_service_account, + # delegates: The chained list of delegates required to grant the final accessToken. + # For more information, see: + # https://cloud.google.com/iam/docs/create-short-lived-credentials-direct#sa-credentials-permissions + # Delegate is NOT USED here. + delegates=[], + target_scopes=[scope], + lifetime=300) + + # Set the impersonated credential, target audience and token options. + id_creds = impersonated_credentials.IDTokenCredentials( + target_credentials, + target_audience=target_audience, + include_email=True) + + # Get the ID token. + # Once you've obtained the ID token, use it to make an authenticated call + # to the target audience. + request = google.auth.transport.requests.Request() + id_creds.refresh(request) + # token = id_creds.token + print("Generated ID token.") + +# [auth_cloud_idtoken_impersonated_credentials] diff --git a/packages/google-auth/samples/cloud-client/snippets/idtoken_from_metadata_server.py b/packages/google-auth/samples/cloud-client/snippets/idtoken_from_metadata_server.py new file mode 100644 index 000000000000..00b6545cf50e --- /dev/null +++ b/packages/google-auth/samples/cloud-client/snippets/idtoken_from_metadata_server.py @@ -0,0 +1,50 @@ +# Copyright 2022 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START auth_cloud_idtoken_metadata_server] + +import google +import google.oauth2.credentials +from google.auth import compute_engine +import google.auth.transport.requests + + +def idtoken_from_metadata_server(url: str): + """ + Use the Google Cloud metadata server in the Cloud Run (or AppEngine or Kubernetes etc.,) + environment to create an identity token and add it to the HTTP request as part of an + Authorization header. + + Args: + url: The url or target audience to obtain the ID token for. + Examples: http://www.abc.com + """ + + request = google.auth.transport.requests.Request() + # Set the target audience. + # Setting "use_metadata_identity_endpoint" to "True" will make the request use the default application + # credentials. Optionally, you can also specify a specific service account to use by mentioning + # the service_account_email. + credentials = compute_engine.IDTokenCredentials( + request=request, target_audience=url, use_metadata_identity_endpoint=True + ) + + # Get the ID token. + # Once you've obtained the ID token, use it to make an authenticated call + # to the target audience. + credentials.refresh(request) + # print(credentials.token) + print("Generated ID token.") + +# [END auth_cloud_idtoken_metadata_server] diff --git a/packages/google-auth/samples/cloud-client/snippets/idtoken_from_service_account.py b/packages/google-auth/samples/cloud-client/snippets/idtoken_from_service_account.py new file mode 100644 index 000000000000..912035b0bf9d --- /dev/null +++ b/packages/google-auth/samples/cloud-client/snippets/idtoken_from_service_account.py @@ -0,0 +1,50 @@ +# Copyright 2022 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START auth_cloud_idtoken_service_account] + +import google.auth +import google.auth.transport.requests + +from google.oauth2 import service_account + + +def get_idToken_from_serviceaccount(json_credential_path: str, target_audience: str): + """ + TODO(Developer): Replace the below variables before running the code. + + *NOTE*: + Using service account keys introduces risk; they are long-lived, and can be used by anyone + that obtains the key. Proper rotation and storage reduce this risk but do not eliminate it. + For these reasons, you should consider an alternative approach that + does not use a service account key. Several alternatives to service account keys + are described here: + https://cloud.google.com/docs/authentication/external/set-up-adc + + Args: + json_credential_path: Path to the service account json credential file. + target_audience: The url or target audience to obtain the ID token for. + Examples: http://www.abc.com + """ + + # Obtain the id token by providing the json file path and target audience. + credentials = service_account.IDTokenCredentials.from_service_account_file( + filename=json_credential_path, + target_audience=target_audience) + + request = google.auth.transport.requests.Request() + credentials.refresh(request) + print("Generated ID token.") + +# [END auth_cloud_idtoken_service_account] diff --git a/packages/google-auth/samples/cloud-client/snippets/noxfile.py b/packages/google-auth/samples/cloud-client/snippets/noxfile.py new file mode 100644 index 000000000000..e129dca59254 --- /dev/null +++ b/packages/google-auth/samples/cloud-client/snippets/noxfile.py @@ -0,0 +1,48 @@ +# Copyright 2019 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import pathlib +import shutil + +import nox + +CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() + +# https://github.com/psf/black/issues/2964, pin click version to 8.0.4 to +# avoid incompatiblity with black. +CLICK_VERSION = "click==8.0.4" +BLACK_VERSION = "black==19.3b0" +BLACK_PATHS = [ + "google", + "tests", + "tests_async", + "noxfile.py", + "setup.py", + "docs/conf.py", +] + +@nox.session(python=["3.7", "3.8", "3.9", "3.10"]) +def unit(session): + # constraints_path = str( + # CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt" + # ) + session.install("-r", "requirements.txt") + # session.install("-e", ".") + session.run( + "pytest", + f"--junitxml=unit_{session.python}_sponge_log.xml", + "snippets_test.py", + # "tests_async", + ) \ No newline at end of file diff --git a/packages/google-auth/samples/cloud-client/snippets/noxfile_config.py b/packages/google-auth/samples/cloud-client/snippets/noxfile_config.py new file mode 100644 index 000000000000..e892b338fcea --- /dev/null +++ b/packages/google-auth/samples/cloud-client/snippets/noxfile_config.py @@ -0,0 +1,38 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Default TEST_CONFIG_OVERRIDE for python repos. + +# You can copy this file into your directory, then it will be inported from +# the noxfile.py. + +# The source of truth: +# https://github.com/GoogleCloudPlatform/python-docs-samples/blob/master/noxfile_config.py + +TEST_CONFIG_OVERRIDE = { + # You can opt out from the test for specific Python versions. + "ignored_versions": ["2.7"], + # Old samples are opted out of enforcing Python type hints + # All new samples should feature them + "enforce_type_hints": True, + # An envvar key for determining the project id to use. Change it + # to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a + # build specific Cloud project. You can also use your own string + # to use your own Cloud project. + # "gcloud_project_env": "GOOGLE_CLOUD_PROJECT", + "gcloud_project_env": "GOOGLE_CLOUD_PROJECT", + # A dictionary you want to inject into your test. Don't put any + # secrets here. These values will override predefined values. + "envs": {}, +} diff --git a/packages/google-auth/samples/cloud-client/snippets/requirements.txt b/packages/google-auth/samples/cloud-client/snippets/requirements.txt new file mode 100644 index 000000000000..dbd3f00c44fc --- /dev/null +++ b/packages/google-auth/samples/cloud-client/snippets/requirements.txt @@ -0,0 +1,4 @@ +google-cloud-compute==1.3.2 +google-cloud-storage==2.3.0 +google-auth==2.10.0 +pytest==7.1.2 \ No newline at end of file diff --git a/packages/google-auth/samples/cloud-client/snippets/snippets_test.py b/packages/google-auth/samples/cloud-client/snippets/snippets_test.py new file mode 100644 index 000000000000..18088284392e --- /dev/null +++ b/packages/google-auth/samples/cloud-client/snippets/snippets_test.py @@ -0,0 +1,77 @@ +# Copyright 2022 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import re + +from _pytest.capture import CaptureFixture + +import authenticate_explicit_with_adc +import authenticate_implicit_with_adc +import idtoken_from_metadata_server +import idtoken_from_service_account +# from system_tests.noxfile import SERVICE_ACCOUNT_FILE +import verify_google_idtoken + +import google +from google.oauth2 import service_account +import google.auth.transport.requests +import os + +CREDENTIALS, PROJECT = google.auth.default() +SERVICE_ACCOUNT_FILE = os.getenv("GOOGLE_APPLICATION_CREDENTIALS") + + +def test_authenticate_explicit_with_adc(capsys: CaptureFixture): + authenticate_explicit_with_adc.authenticate_explicit_with_adc() + out, err = capsys.readouterr() + assert re.search("Listed all storage buckets.", out) + + +def test_authenticate_implicit_with_adc(capsys: CaptureFixture): + authenticate_implicit_with_adc.authenticate_implicit_with_adc(PROJECT) + out, err = capsys.readouterr() + assert re.search("Listed all storage buckets.", out) + + +def test_idtoken_from_metadata_server(capsys: CaptureFixture): + idtoken_from_metadata_server.idtoken_from_metadata_server("https://www.google.com") + out, err = capsys.readouterr() + assert re.search("Generated ID token.", out) + + +def test_idtoken_from_service_account(capsys: CaptureFixture): + idtoken_from_service_account.get_idToken_from_serviceaccount( + SERVICE_ACCOUNT_FILE, + "iap.googleapis.com") + out, err = capsys.readouterr() + assert re.search("Generated ID token.", out) + + +def test_verify_google_idtoken(): + idtoken = get_idtoken_from_service_account(SERVICE_ACCOUNT_FILE, "iap.googleapis.com") + + verify_google_idtoken.verify_google_idtoken( + idtoken, + "iap.googleapis.com", + "https://www.googleapis.com/oauth2/v3/certs" + ) + + +def get_idtoken_from_service_account(json_credential_path: str, target_audience: str): + credentials = service_account.IDTokenCredentials.from_service_account_file( + filename=json_credential_path, + target_audience=target_audience) + + request = google.auth.transport.requests.Request() + credentials.refresh(request) + return credentials.token diff --git a/packages/google-auth/samples/cloud-client/snippets/verify_google_idtoken.py b/packages/google-auth/samples/cloud-client/snippets/verify_google_idtoken.py new file mode 100644 index 000000000000..4d2216efdc1d --- /dev/null +++ b/packages/google-auth/samples/cloud-client/snippets/verify_google_idtoken.py @@ -0,0 +1,62 @@ +# Copyright 2022 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START auth_cloud_verify_google_idtoken] + +import google +import google.auth.transport.requests +from google.oauth2 import id_token + + +def verify_google_idtoken(idtoken: str, audience="iap.googleapis.com", + jwk_url="https://www.googleapis.com/oauth2/v3/certs"): + """ + Verifies the obtained Google id token. This is done at the receiving end of the OIDC endpoint. + The most common use case for verifying the ID token is when you are protecting + your own APIs with IAP. Google services already verify credentials as a platform, + so verifying ID tokens before making Google API calls is usually unnecessary. + + Args: + idtoken: The Google ID token to verify. + + audience: The service name for which the id token is requested. Service name refers to the + logical identifier of an API service, such as "iap.googleapis.com". + + jwk_url: To verify id tokens, get the Json Web Key endpoint (jwk). + OpenID Connect allows the use of a "Discovery document," a JSON document found at a + well-known location containing key-value pairs which provide details about the + OpenID Connect provider's configuration. + For more information on validating the jwt, see: + https://developers.google.com/identity/protocols/oauth2/openid-connect#validatinganidtoken + + Here, we validate Google's token using Google's OpenID Connect service (jwkUrl). + For more information on jwk,see: + https://auth0.com/docs/secure/tokens/json-web-tokens/json-web-key-sets + """ + + request = google.auth.transport.requests.Request() + # Set the parameters and verify the token. + # Setting "certs_url" is optional. When verifying a Google ID token, this is set by default. + result = id_token.verify_token(idtoken, request, audience) + + # Verify that the token contains subject and email claims. + # Get the User id. + if not result["sub"] is None: + print(f"User id: {result['sub']}") + # Optionally, if "INCLUDE_EMAIL" was set in the token options, check if the + # email was verified. + if result['email_verified'] == "True": + print(f"Email verified {result['email']}") + +# [END auth_cloud_verify_google_idtoken] diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index ec376950bd458b8105dc47c111bcd1adcb90b406..e8785c796c86ffe2b670c0133f576834ab2ec311 100644 GIT binary patch literal 10324 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

DX#d9^0S?RvIQ3F-dRCLe|~ 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 From 09e29f9a93ad201174f094038737e16455ba7022 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Tue, 23 Aug 2022 17:41:20 +0200 Subject: [PATCH 609/966] chore(deps): update dependency google-cloud-compute to v1.5.1 (#1110) * chore(deps): update dependency google-cloud-compute to v1.5.1 * exclude templated .kokoro/samples files Co-authored-by: Anthonios Partheniou --- packages/google-auth/owlbot.py | 1 + .../google-auth/samples/cloud-client/snippets/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/owlbot.py b/packages/google-auth/owlbot.py index 611ce920c527..cfa33fecd2f7 100644 --- a/packages/google-auth/owlbot.py +++ b/packages/google-auth/owlbot.py @@ -15,6 +15,7 @@ "continuous/common.cfg", "presubmit/common.cfg", "build.sh", + "samples/*", ], ) # just move kokoro configs s.move( diff --git a/packages/google-auth/samples/cloud-client/snippets/requirements.txt b/packages/google-auth/samples/cloud-client/snippets/requirements.txt index dbd3f00c44fc..51aba312adca 100644 --- a/packages/google-auth/samples/cloud-client/snippets/requirements.txt +++ b/packages/google-auth/samples/cloud-client/snippets/requirements.txt @@ -1,4 +1,4 @@ -google-cloud-compute==1.3.2 +google-cloud-compute==1.5.1 google-cloud-storage==2.3.0 google-auth==2.10.0 pytest==7.1.2 \ No newline at end of file From f58c78d06e2e9e89657e8c0ad417fb294c9a2b0b Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Tue, 23 Aug 2022 19:11:05 +0200 Subject: [PATCH 610/966] chore(deps): update dependency google-auth to v2.11.0 (#1109) --- .../google-auth/samples/cloud-client/snippets/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/samples/cloud-client/snippets/requirements.txt b/packages/google-auth/samples/cloud-client/snippets/requirements.txt index 51aba312adca..d021179749ae 100644 --- a/packages/google-auth/samples/cloud-client/snippets/requirements.txt +++ b/packages/google-auth/samples/cloud-client/snippets/requirements.txt @@ -1,4 +1,4 @@ google-cloud-compute==1.5.1 google-cloud-storage==2.3.0 -google-auth==2.10.0 +google-auth==2.11.0 pytest==7.1.2 \ No newline at end of file From cf1a9abce955d1d715fffaae1bebaab5a9ff904d Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Tue, 23 Aug 2022 19:38:36 +0200 Subject: [PATCH 611/966] chore(deps): update dependency google-cloud-storage to v2.5.0 (#1111) Co-authored-by: Anthonios Partheniou --- .../google-auth/samples/cloud-client/snippets/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/samples/cloud-client/snippets/requirements.txt b/packages/google-auth/samples/cloud-client/snippets/requirements.txt index d021179749ae..d661b7fc9823 100644 --- a/packages/google-auth/samples/cloud-client/snippets/requirements.txt +++ b/packages/google-auth/samples/cloud-client/snippets/requirements.txt @@ -1,4 +1,4 @@ google-cloud-compute==1.5.1 -google-cloud-storage==2.3.0 +google-cloud-storage==2.5.0 google-auth==2.11.0 pytest==7.1.2 \ No newline at end of file From fde25bcbb4d12c1aa83b884e14f37d0ebc0fc53e Mon Sep 17 00:00:00 2001 From: Ryan Kohler Date: Tue, 23 Aug 2022 19:41:34 -0400 Subject: [PATCH 612/966] fix: make pluggable auth tests work in all environments (#1114) --- packages/google-auth/tests/test_pluggable.py | 28 +++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/packages/google-auth/tests/test_pluggable.py b/packages/google-auth/tests/test_pluggable.py index b90c86c3afd3..80cde7972887 100644 --- a/packages/google-auth/tests/test_pluggable.py +++ b/packages/google-auth/tests/test_pluggable.py @@ -427,8 +427,10 @@ def test_retrieve_subject_token_expired_token(self): assert excinfo.match(r"The token returned by the executable is expired.") @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) - def test_retrieve_subject_token_file_cache(self): - ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE = "actual_output_file" + def test_retrieve_subject_token_file_cache(self, tmpdir): + ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE = tmpdir.join( + "actual_output_file" + ) ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE = { "command": "command", "timeout_millis": 30000, @@ -472,8 +474,10 @@ def test_retrieve_subject_token_no_file_cache(self): assert subject_token == self.EXECUTABLE_OIDC_TOKEN @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) - def test_retrieve_subject_token_file_cache_value_error_report(self): - ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE = "actual_output_file" + def test_retrieve_subject_token_file_cache_value_error_report(self, tmpdir): + ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE = tmpdir.join( + "actual_output_file" + ) ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE = { "command": "command", "timeout_millis": 30000, @@ -499,8 +503,10 @@ def test_retrieve_subject_token_file_cache_value_error_report(self): os.remove(ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE) @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) - def test_retrieve_subject_token_file_cache_refresh_error_retry(self): - ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE = "actual_output_file" + def test_retrieve_subject_token_file_cache_refresh_error_retry(self, tmpdir): + ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE = tmpdir.join( + "actual_output_file" + ) ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE = { "command": "command", "timeout_millis": 30000, @@ -637,7 +643,7 @@ def test_retrieve_subject_token_missing_error_code_message(self): @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) def test_retrieve_subject_token_without_expiration_time_should_fail_when_output_file_specified( - self + self, ): EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE = { "version": 1, @@ -665,9 +671,11 @@ def test_retrieve_subject_token_without_expiration_time_should_fail_when_output_ @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) def test_retrieve_subject_token_without_expiration_time_should_fail_when_retrieving_from_output_file( - self + self, tmpdir ): - ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE = "actual_output_file" + ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE = tmpdir.join( + "actual_output_file" + ) ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE = { "command": "command", "timeout_millis": 30000, @@ -692,7 +700,7 @@ def test_retrieve_subject_token_without_expiration_time_should_fail_when_retriev @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) def test_retrieve_subject_token_without_expiration_time_should_pass_when_output_file_not_specified( - self + self, ): EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE = { "version": 1, From 5ad86d4dc185de8f3399e221c678e52f32558716 Mon Sep 17 00:00:00 2001 From: Stewart Miles Date: Mon, 29 Aug 2022 12:48:32 -0700 Subject: [PATCH 613/966] fix: fix socket leak in impersonated_credentials (#1123) * fix: Fix socket leak in impersonated_credentials impersonated_credentials.Credentials.sign_bytes() created a session that wasn't closed leaking a socket. This fixes the issue by always closing the requests session after a signing request is complete. Fixes #1122 --- .../google/auth/impersonated_credentials.py | 9 ++++++--- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index 4d0c4f0f162e..f978b64ef4ca 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -288,9 +288,12 @@ def sign_bytes(self, message): authed_session = AuthorizedSession(self._source_credentials) - response = authed_session.post( - url=iam_sign_endpoint, headers=headers, json=body - ) + try: + response = authed_session.post( + url=iam_sign_endpoint, headers=headers, json=body + ) + finally: + authed_session.close() if response.status_code != http_client.OK: raise exceptions.TransportError( diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index e8785c796c86ffe2b670c0133f576834ab2ec311..37a9d6a1e49e126c56acf6d3ed739449a7d54b54 100644 GIT binary patch literal 10324 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

DX#d9^0S?RvIQ3F-dRCLe|~ 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 mY7U<*amU5 Date: Tue, 30 Aug 2022 12:26:04 -0400 Subject: [PATCH 614/966] chore: remove 'pip install' statements from python_library templates (#1124) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(python): exclude `grpcio==1.49.0rc1` in tests Source-Link: https://github.com/googleapis/synthtool/commit/c4dd5953003d13b239f872d329c3146586bb417e Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:ce3c1686bc81145c81dd269bd12c4025c6b275b22d14641358827334fddb1d72 * use templated renovate.json * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md Co-authored-by: Owl Bot Co-authored-by: Anthonios Partheniou --- .../google-auth/.github/.OwlBot.lock.yaml | 4 +- packages/google-auth/.kokoro/publish-docs.sh | 4 +- packages/google-auth/.kokoro/release.sh | 5 +- packages/google-auth/.kokoro/requirements.in | 8 + packages/google-auth/.kokoro/requirements.txt | 464 ++++++++++++++++++ packages/google-auth/owlbot.py | 3 + packages/google-auth/renovate.json | 11 +- 7 files changed, 488 insertions(+), 11 deletions(-) create mode 100644 packages/google-auth/.kokoro/requirements.in create mode 100644 packages/google-auth/.kokoro/requirements.txt diff --git a/packages/google-auth/.github/.OwlBot.lock.yaml b/packages/google-auth/.github/.OwlBot.lock.yaml index 0eb02fda4c09..23e106b65770 100644 --- a/packages/google-auth/.github/.OwlBot.lock.yaml +++ b/packages/google-auth/.github/.OwlBot.lock.yaml @@ -13,5 +13,5 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:9db98b055a7f8bd82351238ccaacfd3cda58cdf73012ab58b8da146368330021 -# created: 2022-07-25T16:02:49.174178716Z + digest: sha256:ce3c1686bc81145c81dd269bd12c4025c6b275b22d14641358827334fddb1d72 +# created: 2022-08-29T17:28:30.441852797Z diff --git a/packages/google-auth/.kokoro/publish-docs.sh b/packages/google-auth/.kokoro/publish-docs.sh index 8acb14e802b0..1c4d62370042 100755 --- a/packages/google-auth/.kokoro/publish-docs.sh +++ b/packages/google-auth/.kokoro/publish-docs.sh @@ -21,14 +21,12 @@ export PYTHONUNBUFFERED=1 export PATH="${HOME}/.local/bin:${PATH}" # Install nox -python3 -m pip install --user --upgrade --quiet nox +python3 -m pip install --require-hashes -r .kokoro/requirements.txt python3 -m nox --version # build docs nox -s docs -python3 -m pip install --user gcp-docuploader - # create metadata python3 -m docuploader create-metadata \ --name=$(jq --raw-output '.name // empty' .repo-metadata.json) \ diff --git a/packages/google-auth/.kokoro/release.sh b/packages/google-auth/.kokoro/release.sh index 638176950271..666e4317bb1b 100755 --- a/packages/google-auth/.kokoro/release.sh +++ b/packages/google-auth/.kokoro/release.sh @@ -16,12 +16,9 @@ set -eo pipefail # Start the releasetool reporter -python3 -m pip install gcp-releasetool +python3 -m pip install --require-hashes -r .kokoro/requirements.txt python3 -m releasetool publish-reporter-script > /tmp/publisher-script; source /tmp/publisher-script -# Ensure that we have the latest versions of Twine, Wheel, and Setuptools. -python3 -m pip install --upgrade twine wheel setuptools - # Disable buffering, so that the logs stream through. export PYTHONUNBUFFERED=1 diff --git a/packages/google-auth/.kokoro/requirements.in b/packages/google-auth/.kokoro/requirements.in new file mode 100644 index 000000000000..7718391a34d7 --- /dev/null +++ b/packages/google-auth/.kokoro/requirements.in @@ -0,0 +1,8 @@ +gcp-docuploader +gcp-releasetool +importlib-metadata +typing-extensions +twine +wheel +setuptools +nox \ No newline at end of file diff --git a/packages/google-auth/.kokoro/requirements.txt b/packages/google-auth/.kokoro/requirements.txt new file mode 100644 index 000000000000..4b29ef247bed --- /dev/null +++ b/packages/google-auth/.kokoro/requirements.txt @@ -0,0 +1,464 @@ +# +# This file is autogenerated by pip-compile with python 3.10 +# To update, run: +# +# pip-compile --allow-unsafe --generate-hashes requirements.in +# +argcomplete==2.0.0 \ + --hash=sha256:6372ad78c89d662035101418ae253668445b391755cfe94ea52f1b9d22425b20 \ + --hash=sha256:cffa11ea77999bb0dd27bb25ff6dc142a6796142f68d45b1a26b11f58724561e + # via nox +attrs==22.1.0 \ + --hash=sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6 \ + --hash=sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c + # via gcp-releasetool +bleach==5.0.1 \ + --hash=sha256:085f7f33c15bd408dd9b17a4ad77c577db66d76203e5984b1bd59baeee948b2a \ + --hash=sha256:0d03255c47eb9bd2f26aa9bb7f2107732e7e8fe195ca2f64709fcf3b0a4a085c + # via readme-renderer +cachetools==5.2.0 \ + --hash=sha256:6a94c6402995a99c3970cc7e4884bb60b4a8639938157eeed436098bf9831757 \ + --hash=sha256:f9f17d2aec496a9aa6b76f53e3b614c965223c061982d434d160f930c698a9db + # via google-auth +certifi==2022.6.15 \ + --hash=sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d \ + --hash=sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412 + # via requests +cffi==1.15.1 \ + --hash=sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5 \ + --hash=sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef \ + --hash=sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104 \ + --hash=sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426 \ + --hash=sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405 \ + --hash=sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375 \ + --hash=sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a \ + --hash=sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e \ + --hash=sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc \ + --hash=sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf \ + --hash=sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185 \ + --hash=sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497 \ + --hash=sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3 \ + --hash=sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35 \ + --hash=sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c \ + --hash=sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83 \ + --hash=sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21 \ + --hash=sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca \ + --hash=sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984 \ + --hash=sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac \ + --hash=sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd \ + --hash=sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee \ + --hash=sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a \ + --hash=sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2 \ + --hash=sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192 \ + --hash=sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7 \ + --hash=sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585 \ + --hash=sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f \ + --hash=sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e \ + --hash=sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27 \ + --hash=sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b \ + --hash=sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e \ + --hash=sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e \ + --hash=sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d \ + --hash=sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c \ + --hash=sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415 \ + --hash=sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82 \ + --hash=sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02 \ + --hash=sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314 \ + --hash=sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325 \ + --hash=sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c \ + --hash=sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3 \ + --hash=sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914 \ + --hash=sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045 \ + --hash=sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d \ + --hash=sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9 \ + --hash=sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5 \ + --hash=sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2 \ + --hash=sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c \ + --hash=sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3 \ + --hash=sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2 \ + --hash=sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8 \ + --hash=sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d \ + --hash=sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d \ + --hash=sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9 \ + --hash=sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162 \ + --hash=sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76 \ + --hash=sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4 \ + --hash=sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e \ + --hash=sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9 \ + --hash=sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6 \ + --hash=sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b \ + --hash=sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01 \ + --hash=sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0 + # via cryptography +charset-normalizer==2.1.1 \ + --hash=sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845 \ + --hash=sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f + # via requests +click==8.0.4 \ + --hash=sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1 \ + --hash=sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb + # via + # gcp-docuploader + # gcp-releasetool +colorlog==6.6.0 \ + --hash=sha256:344f73204009e4c83c5b6beb00b3c45dc70fcdae3c80db919e0a4171d006fde8 \ + --hash=sha256:351c51e866c86c3217f08e4b067a7974a678be78f07f85fc2d55b8babde6d94e + # via + # gcp-docuploader + # nox +commonmark==0.9.1 \ + --hash=sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60 \ + --hash=sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9 + # via rich +cryptography==37.0.4 \ + --hash=sha256:190f82f3e87033821828f60787cfa42bff98404483577b591429ed99bed39d59 \ + --hash=sha256:2be53f9f5505673eeda5f2736bea736c40f051a739bfae2f92d18aed1eb54596 \ + --hash=sha256:30788e070800fec9bbcf9faa71ea6d8068f5136f60029759fd8c3efec3c9dcb3 \ + --hash=sha256:3d41b965b3380f10e4611dbae366f6dc3cefc7c9ac4e8842a806b9672ae9add5 \ + --hash=sha256:4c590ec31550a724ef893c50f9a97a0c14e9c851c85621c5650d699a7b88f7ab \ + --hash=sha256:549153378611c0cca1042f20fd9c5030d37a72f634c9326e225c9f666d472884 \ + --hash=sha256:63f9c17c0e2474ccbebc9302ce2f07b55b3b3fcb211ded18a42d5764f5c10a82 \ + --hash=sha256:6bc95ed67b6741b2607298f9ea4932ff157e570ef456ef7ff0ef4884a134cc4b \ + --hash=sha256:7099a8d55cd49b737ffc99c17de504f2257e3787e02abe6d1a6d136574873441 \ + --hash=sha256:75976c217f10d48a8b5a8de3d70c454c249e4b91851f6838a4e48b8f41eb71aa \ + --hash=sha256:7bc997818309f56c0038a33b8da5c0bfbb3f1f067f315f9abd6fc07ad359398d \ + --hash=sha256:80f49023dd13ba35f7c34072fa17f604d2f19bf0989f292cedf7ab5770b87a0b \ + --hash=sha256:91ce48d35f4e3d3f1d83e29ef4a9267246e6a3be51864a5b7d2247d5086fa99a \ + --hash=sha256:a958c52505c8adf0d3822703078580d2c0456dd1d27fabfb6f76fe63d2971cd6 \ + --hash=sha256:b62439d7cd1222f3da897e9a9fe53bbf5c104fff4d60893ad1355d4c14a24157 \ + --hash=sha256:b7f8dd0d4c1f21759695c05a5ec8536c12f31611541f8904083f3dc582604280 \ + --hash=sha256:d204833f3c8a33bbe11eda63a54b1aad7aa7456ed769a982f21ec599ba5fa282 \ + --hash=sha256:e007f052ed10cc316df59bc90fbb7ff7950d7e2919c9757fd42a2b8ecf8a5f67 \ + --hash=sha256:f2dcb0b3b63afb6df7fd94ec6fbddac81b5492513f7b0436210d390c14d46ee8 \ + --hash=sha256:f721d1885ecae9078c3f6bbe8a88bc0786b6e749bf32ccec1ef2b18929a05046 \ + --hash=sha256:f7a6de3e98771e183645181b3627e2563dcde3ce94a9e42a3f427d2255190327 \ + --hash=sha256:f8c0a6e9e1dd3eb0414ba320f85da6b0dcbd543126e30fcc546e7372a7fbf3b9 + # via + # gcp-releasetool + # secretstorage +distlib==0.3.6 \ + --hash=sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46 \ + --hash=sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e + # via virtualenv +docutils==0.19 \ + --hash=sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6 \ + --hash=sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc + # via readme-renderer +filelock==3.8.0 \ + --hash=sha256:55447caa666f2198c5b6b13a26d2084d26fa5b115c00d065664b2124680c4edc \ + --hash=sha256:617eb4e5eedc82fc5f47b6d61e4d11cb837c56cb4544e39081099fa17ad109d4 + # via virtualenv +gcp-docuploader==0.6.3 \ + --hash=sha256:ba8c9d76b3bbac54b0311c503a373b00edc2dc02d6d54ea9507045adb8e870f7 \ + --hash=sha256:c0f5aaa82ce1854a386197e4e359b120ad6d4e57ae2c812fce42219a3288026b + # via -r requirements.in +gcp-releasetool==1.8.6 \ + --hash=sha256:42e51ab8e2e789bc8e22a03c09352962cd3452951c801a2230d564816630304a \ + --hash=sha256:a3518b79d1b243c494eac392a01c7fd65187fd6d52602dcab9b529bc934d4da1 + # via -r requirements.in +google-api-core==2.8.2 \ + --hash=sha256:06f7244c640322b508b125903bb5701bebabce8832f85aba9335ec00b3d02edc \ + --hash=sha256:93c6a91ccac79079ac6bbf8b74ee75db970cc899278b97d53bc012f35908cf50 + # via + # google-cloud-core + # google-cloud-storage +google-auth==2.11.0 \ + --hash=sha256:be62acaae38d0049c21ca90f27a23847245c9f161ff54ede13af2cb6afecbac9 \ + --hash=sha256:ed65ecf9f681832298e29328e1ef0a3676e3732b2e56f41532d45f70a22de0fb + # via + # gcp-releasetool + # google-api-core + # google-cloud-core + # google-cloud-storage +google-cloud-core==2.3.2 \ + --hash=sha256:8417acf6466be2fa85123441696c4badda48db314c607cf1e5d543fa8bdc22fe \ + --hash=sha256:b9529ee7047fd8d4bf4a2182de619154240df17fbe60ead399078c1ae152af9a + # via google-cloud-storage +google-cloud-storage==2.5.0 \ + --hash=sha256:19a26c66c317ce542cea0830b7e787e8dac2588b6bfa4d3fd3b871ba16305ab0 \ + --hash=sha256:382f34b91de2212e3c2e7b40ec079d27ee2e3dbbae99b75b1bcd8c63063ce235 + # via gcp-docuploader +google-crc32c==1.3.0 \ + --hash=sha256:04e7c220798a72fd0f08242bc8d7a05986b2a08a0573396187fd32c1dcdd58b3 \ + --hash=sha256:05340b60bf05b574159e9bd940152a47d38af3fb43803ffe71f11d704b7696a6 \ + --hash=sha256:12674a4c3b56b706153a358eaa1018c4137a5a04635b92b4652440d3d7386206 \ + --hash=sha256:127f9cc3ac41b6a859bd9dc4321097b1a4f6aa7fdf71b4f9227b9e3ebffb4422 \ + --hash=sha256:13af315c3a0eec8bb8b8d80b8b128cb3fcd17d7e4edafc39647846345a3f003a \ + --hash=sha256:1926fd8de0acb9d15ee757175ce7242e235482a783cd4ec711cc999fc103c24e \ + --hash=sha256:226f2f9b8e128a6ca6a9af9b9e8384f7b53a801907425c9a292553a3a7218ce0 \ + --hash=sha256:276de6273eb074a35bc598f8efbc00c7869c5cf2e29c90748fccc8c898c244df \ + --hash=sha256:318f73f5484b5671f0c7f5f63741ab020a599504ed81d209b5c7129ee4667407 \ + --hash=sha256:3bbce1be3687bbfebe29abdb7631b83e6b25da3f4e1856a1611eb21854b689ea \ + --hash=sha256:42ae4781333e331a1743445931b08ebdad73e188fd554259e772556fc4937c48 \ + --hash=sha256:58be56ae0529c664cc04a9c76e68bb92b091e0194d6e3c50bea7e0f266f73713 \ + --hash=sha256:5da2c81575cc3ccf05d9830f9e8d3c70954819ca9a63828210498c0774fda1a3 \ + --hash=sha256:6311853aa2bba4064d0c28ca54e7b50c4d48e3de04f6770f6c60ebda1e975267 \ + --hash=sha256:650e2917660e696041ab3dcd7abac160b4121cd9a484c08406f24c5964099829 \ + --hash=sha256:6a4db36f9721fdf391646685ecffa404eb986cbe007a3289499020daf72e88a2 \ + --hash=sha256:779cbf1ce375b96111db98fca913c1f5ec11b1d870e529b1dc7354b2681a8c3a \ + --hash=sha256:7f6fe42536d9dcd3e2ffb9d3053f5d05221ae3bbcefbe472bdf2c71c793e3183 \ + --hash=sha256:891f712ce54e0d631370e1f4997b3f182f3368179198efc30d477c75d1f44942 \ + --hash=sha256:95c68a4b9b7828ba0428f8f7e3109c5d476ca44996ed9a5f8aac6269296e2d59 \ + --hash=sha256:96a8918a78d5d64e07c8ea4ed2bc44354e3f93f46a4866a40e8db934e4c0d74b \ + --hash=sha256:9c3cf890c3c0ecfe1510a452a165431b5831e24160c5fcf2071f0f85ca5a47cd \ + --hash=sha256:9f58099ad7affc0754ae42e6d87443299f15d739b0ce03c76f515153a5cda06c \ + --hash=sha256:a0b9e622c3b2b8d0ce32f77eba617ab0d6768b82836391e4f8f9e2074582bf02 \ + --hash=sha256:a7f9cbea4245ee36190f85fe1814e2d7b1e5f2186381b082f5d59f99b7f11328 \ + --hash=sha256:bab4aebd525218bab4ee615786c4581952eadc16b1ff031813a2fd51f0cc7b08 \ + --hash=sha256:c124b8c8779bf2d35d9b721e52d4adb41c9bfbde45e6a3f25f0820caa9aba73f \ + --hash=sha256:c9da0a39b53d2fab3e5467329ed50e951eb91386e9d0d5b12daf593973c3b168 \ + --hash=sha256:ca60076c388728d3b6ac3846842474f4250c91efbfe5afa872d3ffd69dd4b318 \ + --hash=sha256:cb6994fff247987c66a8a4e550ef374671c2b82e3c0d2115e689d21e511a652d \ + --hash=sha256:d1c1d6236feab51200272d79b3d3e0f12cf2cbb12b208c835b175a21efdb0a73 \ + --hash=sha256:dd7760a88a8d3d705ff562aa93f8445ead54f58fd482e4f9e2bafb7e177375d4 \ + --hash=sha256:dda4d8a3bb0b50f540f6ff4b6033f3a74e8bf0bd5320b70fab2c03e512a62812 \ + --hash=sha256:e0f1ff55dde0ebcfbef027edc21f71c205845585fffe30d4ec4979416613e9b3 \ + --hash=sha256:e7a539b9be7b9c00f11ef16b55486141bc2cdb0c54762f84e3c6fc091917436d \ + --hash=sha256:eb0b14523758e37802f27b7f8cd973f5f3d33be7613952c0df904b68c4842f0e \ + --hash=sha256:ed447680ff21c14aaceb6a9f99a5f639f583ccfe4ce1a5e1d48eb41c3d6b3217 \ + --hash=sha256:f52a4ad2568314ee713715b1e2d79ab55fab11e8b304fd1462ff5cccf4264b3e \ + --hash=sha256:fbd60c6aaa07c31d7754edbc2334aef50601b7f1ada67a96eb1eb57c7c72378f \ + --hash=sha256:fc28e0db232c62ca0c3600884933178f0825c99be4474cdd645e378a10588125 \ + --hash=sha256:fe31de3002e7b08eb20823b3735b97c86c5926dd0581c7710a680b418a8709d4 \ + --hash=sha256:fec221a051150eeddfdfcff162e6db92c65ecf46cb0f7bb1bf812a1520ec026b \ + --hash=sha256:ff71073ebf0e42258a42a0b34f2c09ec384977e7f6808999102eedd5b49920e3 + # via google-resumable-media +google-resumable-media==2.3.3 \ + --hash=sha256:27c52620bd364d1c8116eaac4ea2afcbfb81ae9139fb3199652fcac1724bfb6c \ + --hash=sha256:5b52774ea7a829a8cdaa8bd2d4c3d4bc660c91b30857ab2668d0eb830f4ea8c5 + # via google-cloud-storage +googleapis-common-protos==1.56.4 \ + --hash=sha256:8eb2cbc91b69feaf23e32452a7ae60e791e09967d81d4fcc7fc388182d1bd394 \ + --hash=sha256:c25873c47279387cfdcbdafa36149887901d36202cb645a0e4f29686bf6e4417 + # via google-api-core +idna==3.3 \ + --hash=sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff \ + --hash=sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d + # via requests +importlib-metadata==4.12.0 \ + --hash=sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670 \ + --hash=sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23 + # via + # -r requirements.in + # twine +jeepney==0.8.0 \ + --hash=sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806 \ + --hash=sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755 + # via + # keyring + # secretstorage +jinja2==3.1.2 \ + --hash=sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852 \ + --hash=sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61 + # via gcp-releasetool +keyring==23.8.2 \ + --hash=sha256:0d9973f8891850f1ade5f26aafd06bb16865fbbae3fc56b0defb6a14a2624003 \ + --hash=sha256:10d2a8639663fe2090705a00b8c47c687cacdf97598ea9c11456679fa974473a + # via + # gcp-releasetool + # twine +markupsafe==2.1.1 \ + --hash=sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003 \ + --hash=sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88 \ + --hash=sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5 \ + --hash=sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7 \ + --hash=sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a \ + --hash=sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603 \ + --hash=sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1 \ + --hash=sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135 \ + --hash=sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247 \ + --hash=sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6 \ + --hash=sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601 \ + --hash=sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77 \ + --hash=sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02 \ + --hash=sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e \ + --hash=sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63 \ + --hash=sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f \ + --hash=sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980 \ + --hash=sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b \ + --hash=sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812 \ + --hash=sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff \ + --hash=sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96 \ + --hash=sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1 \ + --hash=sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925 \ + --hash=sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a \ + --hash=sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6 \ + --hash=sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e \ + --hash=sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f \ + --hash=sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4 \ + --hash=sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f \ + --hash=sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3 \ + --hash=sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c \ + --hash=sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a \ + --hash=sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417 \ + --hash=sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a \ + --hash=sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a \ + --hash=sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37 \ + --hash=sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452 \ + --hash=sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933 \ + --hash=sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a \ + --hash=sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7 + # via jinja2 +nox==2022.8.7 \ + --hash=sha256:1b894940551dc5c389f9271d197ca5d655d40bdc6ccf93ed6880e4042760a34b \ + --hash=sha256:96cca88779e08282a699d672258ec01eb7c792d35bbbf538c723172bce23212c + # via -r requirements.in +packaging==21.3 \ + --hash=sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb \ + --hash=sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522 + # via + # gcp-releasetool + # nox +pkginfo==1.8.3 \ + --hash=sha256:848865108ec99d4901b2f7e84058b6e7660aae8ae10164e015a6dcf5b242a594 \ + --hash=sha256:a84da4318dd86f870a9447a8c98340aa06216bfc6f2b7bdc4b8766984ae1867c + # via twine +platformdirs==2.5.2 \ + --hash=sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788 \ + --hash=sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19 + # via virtualenv +protobuf==3.20.1 \ + --hash=sha256:06059eb6953ff01e56a25cd02cca1a9649a75a7e65397b5b9b4e929ed71d10cf \ + --hash=sha256:097c5d8a9808302fb0da7e20edf0b8d4703274d140fd25c5edabddcde43e081f \ + --hash=sha256:284f86a6207c897542d7e956eb243a36bb8f9564c1742b253462386e96c6b78f \ + --hash=sha256:32ca378605b41fd180dfe4e14d3226386d8d1b002ab31c969c366549e66a2bb7 \ + --hash=sha256:3cc797c9d15d7689ed507b165cd05913acb992d78b379f6014e013f9ecb20996 \ + --hash=sha256:62f1b5c4cd6c5402b4e2d63804ba49a327e0c386c99b1675c8a0fefda23b2067 \ + --hash=sha256:69ccfdf3657ba59569c64295b7d51325f91af586f8d5793b734260dfe2e94e2c \ + --hash=sha256:6f50601512a3d23625d8a85b1638d914a0970f17920ff39cec63aaef80a93fb7 \ + --hash=sha256:7403941f6d0992d40161aa8bb23e12575637008a5a02283a930addc0508982f9 \ + --hash=sha256:755f3aee41354ae395e104d62119cb223339a8f3276a0cd009ffabfcdd46bb0c \ + --hash=sha256:77053d28427a29987ca9caf7b72ccafee011257561259faba8dd308fda9a8739 \ + --hash=sha256:7e371f10abe57cee5021797126c93479f59fccc9693dafd6bd5633ab67808a91 \ + --hash=sha256:9016d01c91e8e625141d24ec1b20fed584703e527d28512aa8c8707f105a683c \ + --hash=sha256:9be73ad47579abc26c12024239d3540e6b765182a91dbc88e23658ab71767153 \ + --hash=sha256:adc31566d027f45efe3f44eeb5b1f329da43891634d61c75a5944e9be6dd42c9 \ + --hash=sha256:adfc6cf69c7f8c50fd24c793964eef18f0ac321315439d94945820612849c388 \ + --hash=sha256:af0ebadc74e281a517141daad9d0f2c5d93ab78e9d455113719a45a49da9db4e \ + --hash=sha256:cb29edb9eab15742d791e1025dd7b6a8f6fcb53802ad2f6e3adcb102051063ab \ + --hash=sha256:cd68be2559e2a3b84f517fb029ee611546f7812b1fdd0aa2ecc9bc6ec0e4fdde \ + --hash=sha256:cdee09140e1cd184ba9324ec1df410e7147242b94b5f8b0c64fc89e38a8ba531 \ + --hash=sha256:db977c4ca738dd9ce508557d4fce0f5aebd105e158c725beec86feb1f6bc20d8 \ + --hash=sha256:dd5789b2948ca702c17027c84c2accb552fc30f4622a98ab5c51fcfe8c50d3e7 \ + --hash=sha256:e250a42f15bf9d5b09fe1b293bdba2801cd520a9f5ea2d7fb7536d4441811d20 \ + --hash=sha256:ff8d8fa42675249bb456f5db06c00de6c2f4c27a065955917b28c4f15978b9c3 + # via + # gcp-docuploader + # gcp-releasetool + # google-api-core +py==1.11.0 \ + --hash=sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719 \ + --hash=sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378 + # via nox +pyasn1==0.4.8 \ + --hash=sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d \ + --hash=sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba + # via + # pyasn1-modules + # rsa +pyasn1-modules==0.2.8 \ + --hash=sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e \ + --hash=sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74 + # via google-auth +pycparser==2.21 \ + --hash=sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 \ + --hash=sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206 + # via cffi +pygments==2.13.0 \ + --hash=sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1 \ + --hash=sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42 + # via + # readme-renderer + # rich +pyjwt==2.4.0 \ + --hash=sha256:72d1d253f32dbd4f5c88eaf1fdc62f3a19f676ccbadb9dbc5d07e951b2b26daf \ + --hash=sha256:d42908208c699b3b973cbeb01a969ba6a96c821eefb1c5bfe4c390c01d67abba + # via gcp-releasetool +pyparsing==3.0.9 \ + --hash=sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb \ + --hash=sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc + # via packaging +pyperclip==1.8.2 \ + --hash=sha256:105254a8b04934f0bc84e9c24eb360a591aaf6535c9def5f29d92af107a9bf57 + # via gcp-releasetool +python-dateutil==2.8.2 \ + --hash=sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 \ + --hash=sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9 + # via gcp-releasetool +readme-renderer==37.0 \ + --hash=sha256:07b7ea234e03e58f77cc222e206e6abb8f4c0435becce5104794ee591f9301c5 \ + --hash=sha256:9fa416704703e509eeb900696751c908ddeb2011319d93700d8f18baff887a69 + # via twine +requests==2.28.1 \ + --hash=sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983 \ + --hash=sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349 + # via + # gcp-releasetool + # google-api-core + # google-cloud-storage + # requests-toolbelt + # twine +requests-toolbelt==0.9.1 \ + --hash=sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f \ + --hash=sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0 + # via twine +rfc3986==2.0.0 \ + --hash=sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd \ + --hash=sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c + # via twine +rich==12.5.1 \ + --hash=sha256:2eb4e6894cde1e017976d2975ac210ef515d7548bc595ba20e195fb9628acdeb \ + --hash=sha256:63a5c5ce3673d3d5fbbf23cd87e11ab84b6b451436f1b7f19ec54b6bc36ed7ca + # via twine +rsa==4.9 \ + --hash=sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7 \ + --hash=sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21 + # via google-auth +secretstorage==3.3.3 \ + --hash=sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77 \ + --hash=sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99 + # via keyring +six==1.16.0 \ + --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \ + --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 + # via + # bleach + # gcp-docuploader + # google-auth + # python-dateutil +twine==4.0.1 \ + --hash=sha256:42026c18e394eac3e06693ee52010baa5313e4811d5a11050e7d48436cf41b9e \ + --hash=sha256:96b1cf12f7ae611a4a40b6ae8e9570215daff0611828f5fe1f37a16255ab24a0 + # via -r requirements.in +typing-extensions==4.3.0 \ + --hash=sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02 \ + --hash=sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6 + # via -r requirements.in +urllib3==1.26.12 \ + --hash=sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e \ + --hash=sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997 + # via + # requests + # twine +virtualenv==20.16.3 \ + --hash=sha256:4193b7bc8a6cd23e4eb251ac64f29b4398ab2c233531e66e40b19a6b7b0d30c1 \ + --hash=sha256:d86ea0bb50e06252d79e6c241507cb904fcd66090c3271381372d6221a3970f9 + # via nox +webencodings==0.5.1 \ + --hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \ + --hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923 + # via bleach +wheel==0.37.1 \ + --hash=sha256:4bdcd7d840138086126cd09254dc6195fb4fc6f01c050a1d7236f2630db1d22a \ + --hash=sha256:e9a504e793efbca1b8e0e9cb979a249cf4a0a7b5b8c9e8b65a5e39d49529c1c4 + # via -r requirements.in +zipp==3.8.1 \ + --hash=sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2 \ + --hash=sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009 + # via importlib-metadata + +# The following packages are considered to be unsafe in a requirements file: +setuptools==65.2.0 \ + --hash=sha256:7f4bc85450898a09f76ebf28b72fa25bc7111f6c7d665d514a60bba9c75ef2a9 \ + --hash=sha256:a3ca5857c89f82f5c9410e8508cb32f4872a3bafd4aa7ae122a24ca33bccc750 + # via -r requirements.in diff --git a/packages/google-auth/owlbot.py b/packages/google-auth/owlbot.py index cfa33fecd2f7..99fcc3898967 100644 --- a/packages/google-auth/owlbot.py +++ b/packages/google-auth/owlbot.py @@ -22,6 +22,9 @@ # needed by samples kokoro jobs templated_files / ".trampolinerc" ) +s.move( + templated_files / "renovate.json", +) assert 1 == s.replace( diff --git a/packages/google-auth/renovate.json b/packages/google-auth/renovate.json index 4fa949311b20..566a70f3cc3c 100644 --- a/packages/google-auth/renovate.json +++ b/packages/google-auth/renovate.json @@ -1,5 +1,12 @@ { "extends": [ - "config:base", ":preserveSemverRanges" - ] + "config:base", + "group:all", + ":preserveSemverRanges", + ":disableDependencyDashboard" + ], + "ignorePaths": [".pre-commit-config.yaml", ".kokoro/requirements.txt"], + "pip_requirements": { + "fileMatch": ["requirements-test.txt", "samples/[\\S/]*constraints.txt", "samples/[\\S/]*constraints-test.txt"] + } } From 1c54d49fab9585b3291f422c3ee735a0eceff61f Mon Sep 17 00:00:00 2001 From: clundin25 <108372512+clundin25@users.noreply.github.com> Date: Tue, 30 Aug 2022 22:37:17 +0000 Subject: [PATCH 615/966] refactor: Add **kwargs to parent constructor for Async API (#1127) * refactor: Add **kwargs to parent constructor for Async API This resolves https://github.com/googleapis/google-auth-library-python/issues/1061. By passing in **kwargs to the parent constructor, `aiohhttp.ClientSession` parameters can be configured by users. --- .../auth/transport/_aiohttp_requests.py | 5 ++++- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/transport/_aiohttp_requests.py b/packages/google-auth/google/auth/transport/_aiohttp_requests.py index 8a20e7abe1cc..179cadbdfb1e 100644 --- a/packages/google-auth/google/auth/transport/_aiohttp_requests.py +++ b/packages/google-auth/google/auth/transport/_aiohttp_requests.py @@ -233,6 +233,8 @@ class AuthorizedSession(aiohttp.ClientSession): refreshing credentials. If not passed, an instance of :class:`~google.auth.transport.aiohttp_requests.Request` is created. + kwargs: Additional arguments passed through to the underlying + ClientSession :meth:`aiohttp.ClientSession` object. """ def __init__( @@ -243,8 +245,9 @@ def __init__( refresh_timeout=None, auth_request=None, auto_decompress=False, + **kwargs, ): - super(AuthorizedSession, self).__init__() + super(AuthorizedSession, self).__init__(**kwargs) self.credentials = credentials self._refresh_status_codes = refresh_status_codes self._max_refresh_attempts = max_refresh_attempts diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 37a9d6a1e49e126c56acf6d3ed739449a7d54b54..e363c4f57b6fd7edb001b6a8fa30c08e4704423b 100644 GIT binary patch literal 10324 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 Date: Thu, 1 Sep 2022 16:26:26 +0000 Subject: [PATCH 616/966] chore: Remove code owner notification spam by filtering out system_test creds (#1133) * chore: Remove code owner notification spam by filtering out system_test creds. --- packages/google-auth/.github/CODEOWNERS | 2 ++ .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 2 files changed, 2 insertions(+) diff --git a/packages/google-auth/.github/CODEOWNERS b/packages/google-auth/.github/CODEOWNERS index 07b04904684d..b76778cd16a8 100644 --- a/packages/google-auth/.github/CODEOWNERS +++ b/packages/google-auth/.github/CODEOWNERS @@ -6,6 +6,8 @@ # The @googleapis/yoshi-python is the default owner for changes in this repo * @arithmetic1728 @sai-sunder-s @googleapis/googleapis-auth @googleapis/yoshi-python +system_tests/secrets.tar.enc # Remove noise from test creds. + # The python-samples-reviewers team is the default owner for samples changes /samples/ @googleapis/python-samples-owners diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index e363c4f57b6fd7edb001b6a8fa30c08e4704423b..f457463585da82f46f58530e4e17e9ba825b7129 100644 GIT binary patch literal 10324 zcmV-aD67{BB>?tKRTFbYox*(c8B+V@vQL3ucp#b?$nC1_0@OtCYa)7z(%cfNPyjnQ zDV&rnUDkR9BhB|4H;6zKcIF>XePT_TlfeZNe)=4B8gKTMTaDIwV^Wj?iwI95>Dz4q zFz)_G!W4M9!e=OPVb_Vbei5?TTaupAOktWiniQqHMW=u6wS9nz3*U5%soH>4veb@m z&9XDrq%a7vGR^|Zs{ZPWNEpi(f=c zV| ztf9%vMX`&Z-ERd2z1%zKYf_B5G0(CBx7|PC)E?3#{Gi#ljago%n&NEUL_#H@31<1) zU95uGze=fbd(@X_UxTO zjsVL2m9w;&L=Neg(+g8^6IwcBFogup=}b2odt>i@G zOhi<-K|Bj99}w+ap=9&(N3wda|DWPK>=kn6^e}NpZn&(m!u&>~$&WBF@`G54ex#>1 z*EcF+y;7;D{{ssI*N{G}v;e{#E8z9wwrrWOb{7-DJ2VQeTM_1OX`yM|YN6Wx$NwBp z=Jnkc+yyKDVD`OTpCj>(f4OM@lTIk4IjK%nK1bjz8P(lzf>&r!iZFTg6l562k52 z(64FHemFbj?yfkpMX^JN{MbOuY=-PS`$9*>)nuXO3LrSvyy6P0@#&ePjCEl!gMR#2 zXUdRSS=ywZ`T3A#le>O7=AuOTuGNnsJ(n-P97+oY(}JJnc~m44felo!)aRPsIOg9; zW#SAnqfNSn2ch`}{NJ?7C;~0B^L7W@It$z*EsN9GOai%o z*oK2Zt!+*`Kr5Ya4Mu`dps&7BxA4@rhhi#si40q^Hz#*b*oETUdUL5#c}akwml#+K zL^}(;8B>bRh!2uDSq@e$6P1!hh!#9rsqG%UTMvyz=IlG03TKikWsQwlcHpxW=BZhe ziOgC!RVem9Nw#;Z*%nIguv4u^%b1&!ATQ|MoQP|Y0zh>2Wf`rr-?8%CF`pw&Wn0N} zUpfO4kve?vD_m0*X>ECC<(E zuH86km7dPzqziP{c4mpP7`pW4QS2tTf;Rc@B7aA;MsoJ{9nj_kHHJ9 zbz`-@gPpVk?Rjii;J9Z>3xNrV=4p>cy;Ac^r# zxJwMRtiJK|2#4YCWo?hFB|oBOKRJp}JA8Eq3(waG9dvX#ID;wKS``k-(dF7J?)xDv_}I8N z1NQj}EsJ@(Im9ZClsss$sBf}9#&BgH{{6a3usm)&pmdo_1O*gYDiYmp`nRuIfZ5-; zJtiGduJoVCi@tUJ9*^D{qGSH-BU82tdk7|bNYk43_H3xIE6O<^eKI;(W`WcR&Dgnu z5Wk}(%VYg14;eH}hm60|Hf+3WiU8Q8np2BU^@r}eZ~%*|n{UmQrb6(1cEShdFkqL? z3L{H3ZH%k+GT_y()IHv~ZZ02QzGg?alIgdJ+pJe-a@(Bukk3g3Ly;-;`3Tp(BgiGa zegg91=58ViXsShO4VdC$#N8^^;VV3)@jyKeXd=)}TU5g7$YiH(Udl`($NOb7t2)CJ z*|E>#<2f+QamI~i^W^x*^~Y(+qb*Y5sq`eI9=bq)@$RvNB}imT?_Oy(K$I7rHBZx; zjq{wu3GQe!Xo#KX^47=ThVkoQ)m%_ga_ijM?j?&6Urcx}B-^Ygr)Hro_&Wu9uZ%sa zj`doVKZX`=iK7AnqJORl`1~MoSoF=8+0awBE}?X$)?Zl_G=&MzqA?7rUR7C_ttD5% zKYi-XVr-gF)fNzU z8dNzDuk(FGM}s>p8?#a*d3GY70AS@P-Xo!AA?Yf3_uX%C3em!Cza;yv`_81*dA;NG zQn2a3PfT{2Jz)MPL3ng=5jgYLJmO1X!kG*>4wh9Jen=hmx@n)3bff_?)iXD+cGlLV z`0;tx!rZaI4LIcpoqm<4NGSun?gTBMu}|0d@f;%Ia?+^L9?Wtr#NB>gDRbA_;X`p_ zSQ1?1f5o?P!9zDbd-&{0}jVVAn{d%8VQ7 z_Sm#3>OLey!Ks>*lK$V689W+WeJI!}a$$$FwZUSPOF(4>*sI5iY-kVgB|Y=%cwAV8 zZ+2mo9D6kN^BJcK{C{R)+|cEaPPiVv2AX9s(d-)KVTZNM!*j>>o6;MY9GW-UFj?q* z-MtRWDq2=2i2>`4mvUwX9a}J3lx^*)1!CqijO)Hdg1dO}R(UjnPtva}Yc3%>q6t1D z2z!@f+|&q|U;cHg?p_re4M>V_pV-CaUjO;fg3W8sdf1I}JWvn_AP1X1Fj)cf4IKo{ zpkYSrJhCHo`-4+G0{wB8&1FoNwsA4DARh-Yh$zT;a~O+QEA5AL@Hh;a z-HsZ_Tt&6|M5nXD(X}u^jFBm%$|Um3K8{x9KXf@8`N+@yE!OM=94fMs(Fd?m{omAE zc+Pmlab;U)7U4)5T{|JbJwbU~(&-fH%D+)85YC2k*ES!RDSm||Mz zOcc=K!XRTASA+6D$;1C%%`VP4!w>?gyW^m3h7Jykx#KDGg$`AY;B5;--$+6x9zUUb zq0-Zvy?El4bv^X8qY~;ENhklxCig(BVBM5)(Fp<7r(qC{vNUL@A~R>`X+rSw@B|~X zt-lGNsDWiYt*fLmv$VUQVE7wz5k!4w&2G6tZG;pB4=KX$#?SDvY{&BSItW1Hv38xT zF5JRF3KUC;(E%L>>qi@8OQyDkxF^SSMe@Sc?&Iw{fSnUV0PYSXwq?*R@DVwbHvMl= z7^}ZavY4$}fWMkoaz1nD4wJI+vFi<95Ca_2!FP~#ii9o=jhml&q7k%#p6Q>n*LHl$ zl$H$kQ;trSlV5M+Dv)V)1y8%aXw%l3d{b(Arr;nz|8}_Pkdp}yQ#wKDWXiG4i1nuK zm9M~R=UsWQ++iNM)K4v7`jgkQ3xokvYEM-i7GDQ z&D~g$HD`{Yyk1+(N z95g``Zl?P)lKfF$O8rsMXJrlbiEJC<#$}^M#*94X8rrM)@P!SPzpORV(PvxSmD_`9 z&twok;j=l|&{;L*R%VW-`vl`5pT;tGme-lcG9{l`9gET4U8u%}DWw!?K50iU@Ov^4 z^pKuJdFlT=je?j7KOCzsMKUkQHdGBnYf1!alGAXA)EFueV_PNt5Oql}9TuSNdSW^D zcs`)`vj{0}8YDK60~)Y?Z_HDl3gSSnw|cMZN5s{V#Zxn{!6v9~)#((~6zDwzE$`a_7pfHhdz1T`kW}Y!=!pAeo04#A(6%=j2n%~!b*nBvpD~yMNH&%CA~pb7 z5zRU7Zw=Qqm4>lrz(1y0g?`BA^Hlg z@mqYQ?d)k_In%mG&NkcQcUk*@8^cGT?TWP>QU!O3*S#cEJnUVVQf7z_F4%WjPWYdk z8f|k=-V+g6Mlq4{kYBDCtHu9A3Aj~gHP5YHKn=r$QWMS{rG|}Lgo}<)AKS2zna(mp zo;t0R+#m9b9IQ{ZJl6kRFZx1^7ajhTMCMG)sbq0qWS|e70>KQg9RKkRWnyC^AI0g; z{x(pRZg+s9Gt@PrsyWdtD3M4g@^3OnQ8t5o9Mmc$fz)9ex+Yix-MMKFc+?@|k-Bjs zIXiL5@FtDXqni8u5N+AWrBY!d+Y}G79}3l-SqMPngb$?KM`!SaY31rXkdJ6DvyAj!o-E6cvF`vuzqXl?Gc&Th@Yey1%T7_ujQ! zf!H7f>}=!uSRD&Smk&dO&h!;N5gs4l6v_OrfMh!lGNqfiO>H4vIGqt;YPMha>roMRQaSa zp3I(rs$U>lV2NBWS^krZMdqjU_R$~IRB^TWdo4EEnuQ8H*Q@A~>Uj(JGf3i~)6~fo zBNmT{ZRi-X*Mt%^!CFQpVfzJ`Hi?j=;w~bx5@itdz|cNx9N|^EXDKCGZ3!}jL~;r@ zl}We&l0alCw{fAj3?>=%Dikl=+9ookmoJk3J+EIMw-vC`!5g(Yg+twI?-4~93APwn zlI<7P-QyN4p=EBTA1YB(sYKVp&!XHpHP<AgiWsAw-(J2fwrPP7%ERHP^peJWZOb1Lc2?@6ddl=;tJ2nxJv zWVoa+0Xl)@2Rc%e7!%Ve%NgyX9kK(~fT)Y4sb%Rg61K_Y1Z%W&Eq#z}s4_xHZtyx9 z#>iymON(uF&pD-k6TQoz$JI$&J4yNBK@jlG)AH)=V-HJ917^gjG(-}l>S4k|&f3g6 z;`chDB08hiD`71?@CZc zJ}=^9KC_ZGQ6r4%7YhmEZ14`FuC^(HNeDCZ>y$NjCEd@Aa#02&m-&Hf^o-GE<)Ywt z{ps6RYjLkM)F5``3BOscyvI{C%ihKu+p^ZHWSGTLpbK>5t0wtww`}@CNzXtO{NLhU z(5^u$Girnw?CHn0F8e8l|1(F;PvVmCz}W*cDK}$R+OwMBaC+jgJ<5vln~4e;8ATOg zNqPc&@$AdeuYVfXfScJ2{axcD1p-g&uon9kT;dal98=I3eJ#XK*K?Jvz8SW1%34{L zVhGSgNepMXFQEb_r!N&|Ln}h9I|ZLW5Z6Fba;HlhV)~V4PjRmPAHw>a66QmodZ)8~ zOVL77Ay+gdu8-P{nTpaoP|c#R$>aUCZxw|tgQ~SY#k9U3RWC&SXtVc1sce)_6z)YT zTTnHTr%1&ek*Ahe3VX_4dHTlZ)Hks5YjzuN769uEUU)*znhf7nidE;!1@xCgTBQYsLGNY9W7U6!My6?A{#1oGf>vO+UL1}=I!?w z03~%F*wn_}b+&DLT{Y*A+044Verk9fj#}AgbO_ucF~5}2*=(l87+Et0i_Jk9&Ccwc zYSy_N`uYOymXho=8=l_O7CXE8v@uCV$aJvgwhsf!AHg_gdiY$fAcYet?yQajfI4HR zBhHQ%!F)oJlG1?%Ldn6Sj2-8SIBS zdJ%xlqtC(wmx>`=w!m(+(6J~rP0hIO!t4e-4V`#EbPwZXX$T><^+f##pAq)1^pCq9 zF98q|L*zd)p=+A2xFm6t@&QC@iaE_k1Wmc* zDlQ?h@l-{aEEf|*prj&4AS&JvN=+*afNbQ2A@F@Vc)9~Vw&HMp2;Y`o72s2?s7IM? zi`+H=iBJm$dnyu{=(3)0GtS_3Vl)HY7W3Qp*^d$m*f>|f*=^XXn{Xv-Z5ET)iP+`t z3E%s7Q&upT^sd-rV9=ewNrXLiz)XeICvn! z`Yl>*g;s*;^>P|BBQf|afd>%j-OI!wl%o`4-V!DfyfcYOrdO|~M8x{JjhjcT7-s#x zIqse9dbw42J;@qUBg96JNuLXW!tb&ktdriQ99&aQV?%x zC%Bw5NQGSCT@VZGZsFyn03W!V)`7D$A7=V8G7O4grVGwbhqWz)MODZhKYL>sb&065 zdEY4Cn}LBCbLMFN>H(7I6!GLZR}M6^7DKkXjvSFL8L-PQP@xP#rpwUXO3L!18FJu+x`)m(&Wz)KbODc*2c|~LV8`= z%R+W}TOCfcYIsQVagme8Mm>aPIxEQqj!oWfw$!3w z8!W&vYgE*$&hZkGyL60%8Ex36(2lKnZAx4m`(u6abDE1k3HGt0oi8Q?C+aG}GYpGB zxc1sDMU=>R`}?kx(5O3$wbV4-a4=5LhRhwkutdpv&UPlc6A&+jp*Pq3VC z0Yyo^d>2aOCl};4soeJD^p7%=73g$^0(WHTmLJgAeOyT&(Q!0;*#ryU&z{NFFO17KLNTk2w5kerI zPo)(B8$5cH=36^OeP54Bw?-`JOACl>P;kLv|BE^tFD$J{>%T=CIZ+sFPz9Xger7&U z!e-PLZ1|QwNryV>2<7*P4?MO;!>JK6jyCw5xwyH8P^=^XM^Vn5a#hWsg(4o6Jb(89 zU%fJ-99qjYw`VG2Uoi|WCkO#o z!PFs!&~vP7y=Al@pVIlz`km_&%aNhjO4-I)cyMkn6Wf z`P3>oX{Eni)1JUtjnj-%wMa0mu+)cA67yl3ccCZGFE0N%?xMul-q}NDcOL!IzF9}V z;lspR?D)6cdsW2a%{D%8+S1eemJ&E_q85Hzdn5A!MaSnnw6f- zvqh%_oWK+*?vo$qc&xf_cc7m|fjsg;+KvCs?+)z1Tcp>d!Wm!9vkX@amEU5sU11>5G#mCDt=knCGES>rU=^#!AodjZR#4)CTI z_*l6z+D4?jV}E!~atDO^65zdW87>JG!4|~fv&_t5uACIjfa}! zdFJknOQ+DVByqou++se$To;usXZ^Pp8FW8A*9Itk#?h5qIj54e2!)Da&-nHdIhp(c zsh)lG&<->JFdJ;@m_Cm{y12vYH1@dbO%Dz)p16KXj-5+RECLhE9-<5vghn9_?YJYq#B|nn$gvokb2mIl;FP2>?jiVy-6o;UQ2bjbnH^ z<}APZ4!$u!auiMB2!34g!{P+=#M^k0tRpgO%#hajV}tPwGgu*})`drkNkpMmLqK8d z^gyWNBK~uXxe>#CQRh&(SaR)ytZy9If| zDxlHyb}UxCgw`tEI3;FuFYn-9xa>^DO89B(!lz@oDAuj(tv3_py*huvOx*04Db(Nl zz19?dGf1A6ATIXq+iy zqf(${ATvN^{B&!B2~ZR^;mqo9VtILP)U4lvvkeCdEGAes<94cseC#G$r&ku3F7%%X zbxK}XZ8KJEqL)Rg(|+J3+_|ijaViDn*tB>7bN6)HM(j}EFZ(6G=|={X zhbQ~B{85$hY1cl!D*}1tA&1$;;r&S&zni}I%%-BVLAnj%#JwY%jdV)rh~t$pR3F$L zz|PGt&2G?Q^gPT(do@pi57hdL>BxKgefv#T;z8lThNWiNLkU>WQ#~aFnhmnN;eEpb zOxixmECQ=%)~(fAU(__0JPZQw8AFn|3IiE?5ipH<4+(0Zndj_12(gLyl#UInl2_Wc zPNr$~c6fEjhEetw`}yuYyLE8)Kf;C(j#K)c-AbwopS2 z9QCQeHQFE;x9LM4P+iQ=sDmH6&VIF?p&hjalE;aYOyOTIVBP|2(oh^~^N`BExk0Q8AQWpk3efTC<(1&IX4a zhGl_<_lB};0ddQjQQd!mX7vIaClGo95bU_prw%bhq&bEi9ap@Lmi;WF=lI=4sg%Qt z=MQn#(Wei)mlA4AwSN-}Rw|dNR?&ChOqlxj7l0eTC)#hp+rE-Z<>x;O@V-_HNR^$* z33Wyko7&4|P^Hw}Q8a)>XLfbrHk3-k^gKJoV$jqUK(8f8CEOa;IwK_DSQvif$)T|V zaEn~(icA5ui$eyJ)P&*RPrAI$j2{ZMG|S9zoe18`F1Lm8@Q9(bf~t>hfC4y#&;&+) zcKwrU2tn-hkc;0`!FSB&G51m__~qGCYN-T;rI;;UY0QB9&xb zQXD^BHs%pV*ibT3@V~r-mPfmfKyy#8R-I||K$v0&s$G-;8x!;NA?+bLFmoBhV_=IV za4zPG`a$TQ?hjT7t^-)6djq4=%ZbeZc96}otEZ#QOF$%Px;d{RTagiZcGq({`uW$85o?0-FhrAZeab%Dv2Lg%?>LX z0G%&U{juV?*ST^f+69I2so7Q%h8B`Ikrb*=4S6Nxv2 z1BLpCDzueNZ%uFy^*SU&B|%-$E%eiTM((w^@E-6y8r4k&_=BnvCcL|WiDZ!cQTvvD z`hvYSVp!!?#zXTcOaYIr)zJ?!#9LHnn7OXt*k+VF_rAr#fLXCi(0cRqhslIeAf7mp zglOC{k6w$ASP11m^`%$~mS+x5fz^rW#;C0h`8k3QD)7QOXG3Z$Nmsi!@-whT2jcHT z31zBXUebPJHQXlXqJC6GR2EP!GQe?{d{jv%p*12xjr))U4zC%SPblZBom78b6eC)& z+&gZMQgy6vtmshx65H9DpWavQxL#Mnyfo3p;}UmATkfI{%n3xzsluh8J`-jG8R^Ld z4*n<9!=$)57&-8HzYLNw1aD)ZFirurIyW%TIGN^#%x4rY2P#O5^>Ac@>6~LNYUd{q zREq}MyK#Rm{U@TXI_!M6M6p+OB>8cBdY({KEfZo6aEdvi;^Vyr*6ExD;ku&v_a@mQ z{x1#Izkk8E!?0M*{KoYVcBbbh{ZCG}ToMu_Fe(wnn`}yr=AB!V(ho}Nj|L{O*$vB? zp``~D;H%n20lQ%u82N1;0J(H0ats83zcL9&Q0gwGZ_YxZwSpUAX*3ta(BI&S8=x%l z@$j@~=8z02Jo0Trhp;kExH;fwE4$;h?1I-Lj4i8PO&EtVchPCZ>zXi z?9Qw87?_GWJ(oCkiTg*5r&$tobAwETr`1JXC9(Y-RsLqw9W_WbUw>dn9rGD*b6sRTFw1 z<5`=ws!)OV)b{!B%hr7rFOWuR#=)A`r%+;#WATvRnKaTlZ2$CCwu{M#i~{S~g)|dN zQ|c(p3063|N7kGt>aJc!s_7QBQf#3c2A0YctzXx>5kNh5aS@VwiPa$0Qc69SJ!fAA zWO(<`mJOSX#h>2X;2gv`xWk-VbU-prnrax%^b!O4lzMeUp^WYs0}EuwyfNVtuf4vD zE+?=?_o5oY*|&L+I8sEU=3obl|5X+rwkVlwStvjUbhTAwX=seckd{@4#!7?OUcugP z)(0w||4DO^=Uq4$_}G_pemtRPQ$~+(_B7#ZuhsD_vWY%o1h`*ipx;q(ITnA@NGH;N z+_P&Y{$z08GujmA)TrYvoJTd7-VsBK>pr7pAo@)^KNCwTD4%aWMV((6b9bExp~Q;H zdT7atix8`_KGLL(623&C`1Qo>-!5ZrR5hU59uc)CR_LEXHv}iB73x7Dm@!LP(T&;% z_?Rz(fat?;wvI`7`FB2J|FBnnB^l%g5zNptq9q;>mFBx+saVl?Z^GGgpww(h;9>cP zo|1$!EGp)yPO9(F1g m82D#VB+aagPO#eh*90aVd1IG=Uzg8m^4|OOSUZ(MSyJu!d>PIF literal 10324 zcmV-aD67{BB>?tKRTIz`#b|-}Z_DTC-Z~V(s5n{Cn&Y zY}9Z;+g(Jl)Td^(y)+hdd;XLU@_;>YisDEKU7iiWZCI90xZ z5fHRmUZ5tUWx(D4eRLt{B;afyNI-cI(JxjR-+7LS8lJpRriDinR)ieA}Ib1NKMWY`(yiI zvc@HRBX4U!{27L+=Z;SalHv-HucgtJL){szm2nZb+P$QeV)(pHudCbvZoXUv;t8#Y zst+`Yr1?r?;gpJFC_jwf-B8IlF*j1nvSArwxLK*h$FhB*3!caEDBxir`f>(boZe72 zJU1NHueN**8<##3P0tn2zQp7x%AyK$S)VgAj5NHPmEwsIYfH@87lJYo_#>+LV{8Ru zX>j0WB8EAA>3V(z5PHxhL|%sHb#u-gNW*1G7Px6Q>q|o?o_r;lX<6M;rnY@!%Hy09 zfoa%B6g8D@uzA-2vxh`5(1(^msBel!3mX#qs!>Ix@y{pX?4RRSv6;t)dK$KqiggST zXILTO0u~kA1afWun3P_3F&v-z|LsWTRI2uo)9OBM**V}~Iye_YK(}q>E77B>i#Qek z6ix8lu0+`pZp9_c;*o>bb9QPb#O5^?i=eN?=KFu}&aA8N?I{QLBB#stj@X#=&J5q1 zKX~bI@l00|UZIxGjy?G|h$hP#e{5`h6p_lt)rxJPSWko-A28V-jtak+*yx2}|9R@{Zdbi2 zg`1r-9%>JZ3qG+MWdEAvZa%BKI@${F`6hUme|0bzDQX}W@ZjG_m&L*Y@0iDG8-`+_N``b(L^qeV(T(*n6b&s z$qujK8&}ck>Zy>*=;CV74|j`i{`+C?v~9nuajc!>LR2I2qw~fS}??>A4^4N<{FU z`YO;>7X#Ej+Jo{Imm{QZ4_y=mJB?Wn^R9J68p+V8+=sQ6mjM+_fk{X3$=s1JiDbu} zcL>ST2yfAACZ7xcs7U{J;MRSRuqM@`7Ri{_G;ttR5Hb_Y7X>a5bR9=j$?ttK;;Y&B zCkEvXi?br^Gb(xEn3a+V19p8ldvkW}d~EBS@!rO%&Pcmqk~+3%7(|eVzdL!v8T@>T zA6z{Fmkyiu7xn|X45i=Nv0`+0x87yCert}_Lr4= z=Z$wPcWZv~x>=1v!&-nDLRHOLIUT;NG*v3UU4#NJn2XO`jp7h4?*6 z6Z@@SqK&&M=?`8$0%>4;`Uo>wFh^-Al^SukKR9bYtlhXymB z^X%Y07gn>GxG1Rn02k>;_L?^ARE-N-Ip@;0U* zzOJ0%B4@8LXpvR$p)Arq2NJM}7Yk~(`1?4T2p0+A0{U>7_+}l7IGDvPB`>!{PS1g??9AXSi*|YHL-o!*kKC({q@m6Z~u_=V|fUmjZXp zKX}s}^(RW-`M(t$DqwWU3*u*psmOq^YA{Zx^fZm`h zD++3s-|fEYBrwY^w$QX)-`C^R6-5rGpCLVd)eH@nT-Umu^25M&m+CKMJzSMQ7Ew2X zcHZY+rmEwFGM(=gT>M<}yc63*JYA>kcjw@sJUYZU69Uf;e=_=4S6A@RoD*$u4_6{I zPx7=+0{OKI(V3}ti6Uz#PThTJ@3q{QBsnME(8JrpkeRwA4?-C&IY{s(yK85C41B%V zq{sA}j~1uoaIZ)z6&>P|!Tn5~_VhIiuo%9KT9fvw`vcVM)5$GFw406*b)ntNN)D+G z+%;0vB!P3ndRX5;C*L9O9-=ODdY&f56f~ZXn$Ma@b6?j_SduT%r*D<4tJbM@pNiPI z=rzVUwGd(DrbOEK`02W~^48qMF$bR}-yO~hd+mizGoz?o*yv%r#(C}zM%j}(@$iyr z@A_;CWs=>S?d!$;bityi(y43FB#U~(0a(@z<&Dla&muNP4*DslHmBHZhn=VznGI(D z^E3}m^(WrIyiR6t((%e|T$)?sz=BTX0WAKL4!}Du4pdgbRRRu$m4IM{LL)I~xQeWW znuWI`L$&iU?aRd_-O{oPKy?Db;!)-#uS~x*0!@qiqM$x5e1@uK6Jwl|Ez}LS( zaLsR>_I=+zgCZ_qYnExsre-hB+`Rr9c$xLILZ-jNEsABk>Ve)t{!R5Oy$Mg5Im6Q5 z?{sI5PT-xBl3@_C4$YLs#WC)+)HG8rK|n)_M_kQTZ4>lAzh=+3%coBDF6vq7cJ6$f zvkcaPucnLJ{Jfu#(#h~{Zw&WO;SB<3ALtdzIT9&o#YKH?GkmEY>k6*VkMyiO3%!#k z!Zjz2G%X+v*8lYVX`y+_pdxsBV8 zXh1NRjO7hy%X_N>FG*{+j=w>^AQ2H7}16^uriV;v!{gSF2eSkBh zIn%;>&CVOJ^>obe`?~T*pzspBdrVUI*i`aIdk;LVl{?&fX{F;iEwBaV(}@4(e%La! zdkhW+I>uTQgf}XZUN*W}Ay-;mtVofs#rn#xb=qo-I24|qLL%auGzilFB9a2N1_m~V zM_+LxVVGS>1LYV8=P=kuv|L2^F~Jzi$46N9sN%Wb0beTS)|_&_oga2t?Js1TmHaCk z%|A5!_``)S4;M`fs#vqdhQ-TKVtRVoscbqW9{$SY1}n-R<*@2PzEAFQcn-h92|&b{^7-Lq!IXAdO4Eg;!p&Yhm@CBOjdp!|Q=cDqNMu6B$WO z4@ib)R1M6jl1g8wz-k(Gpo9S2r-bP7d$6g8-_Ro<)_921=1}8zEA;YF5a*4^Th(R6 zEFj*h2eu2Gr0hB+GQdUet$nmqCJW;{yZ5Q$cA$nXhOpZAfP7`SdMqXzfL+*9Rp!Tg zwouAnYb&pa!tZA98HLnxv!FS#-sd|4lp;Kc`!8^D*G5gQp23G~&WoG( zD)80Fm569G2ajocICiQaeWUzpRjd_CoM!5z|XEN@nq$b!@0E4dfQH z?(_(g*~}Z@a!=kQ3!m^<2m;v>=h?4hk-B&glrgeufsGs}7D~r@9Wx)j4Y4&8#6*w4 z3F~Y$0NV3m;|C1rilEgcGV$vXB6e3e$vg>fn!J*)N?_-A(9zUn+HfWpf6TSYduHFj zL@m+Dn}q~q6kmFskY{DsBd^f?vafo1)&~BgyoF4G#{KPF*Z~36$n1s918*c6q zaLwnV7D!IwQvYEgMbCq)8E2v`#s_r9b^5f39>qHO!=iIB+a)RmAD}ZSkS`7~=t0_7 zjnnZuATiyC(Ns)xwOpFneqpB+V#~NKAfr}-$3+A=&@B@oWrgjt2(^G(d1~BWmU?dVnp}H+GK5b4~#kHSwdPkXhjZWBC z)puw*X!dCqAa~3;W}4Tz|8aWeKY$kOBXqBMBRQ|Sen)@RASBXUM{Y8@0J6SKDH(2D zBajj0v{#Ud9^OFxx2`e&L-8Js#MwX!^-*TR|C}7*9j+#i+(a?Q&!HQXuU7+2 zXBB#e}4=tC|OT~+o zZarBK0b8{Jbo0hihh>_SFG@Cqb?xkv2R?}?SKk@>S<3VDua)+b370jG4pxcx2LNW5 z>4$Kp8dp1I`v_yAWc9~bP9mL-4mJ^y(D;8DcgCTPbA=tAvY4wvHs^6 z1@RThA~m^kaXavHXGE!NbnZDKTF}`00iyq@N1;ZpdAv7UP(mW0D2ZwBL$(s^#{vJG zd=XXi42Z9MlsVM`TYCV^Q>swsnC zG~sS9chuBdi5Em7-JKMdR?^2(EL97!@IJK z5yPz!AYgcqZl{n9%(pBFiXIM}@;Wp?x6TY-MnoPJmK~*kJI(SO%;8SvE7re5;x4tH z5op)>l|IY%o-_7VYknWjh^D~_bA#vMk_mAF@)bS z?gZ)~j9QtplAw!}DYT|ZKU2?&wJe=}FJ%d01D~g zDqUmfkfrbw{Kh)WXe$<(0mwJiNmTefK!g@ft&yg6$ug}`FYE&yJEh1}q6_;I8wgQ^ zowg*z8bzcleqiI7D})a>G;D~J#8RxKKrr1r?4OZYbg>=NQrAH8A#(<)?CUC`Wdb#Q)@O>R>(Gs>d-{GTFq-lp;E(BBcTCRh$be`JWmr^mCNO=dQN@urc zB~MP?CH4Jqp^H+;`s$^1jlT;zony5s=`^P&RD5Y9(^u`N9U~loLW&6s@%5p5-j#ql zTR0i?-+4^VPVt4!O_|h~--Vr{=%rNF>cSsgk&!^c*Q_)Q6kXz>IF4VA3HLE?{;n}5 zU+?b{0f`yMaifIOoHTIabXJnKtEz~{t%MEX^<$2hYt*OAqr^$_9{M4%d=rf?-I#Q= zL-V78X3qmt%!r6cl|#Lk%C4u-#6be=t1Z_J=PUR2O%HvHZPGexo&6hrBh(buK=|}8 zc#DuhEEdePIa_fAfd^E5NF7wpRp5!cTOouFZ>8SEc97f<0Lj5JW+;pbUbsV7@n>!f za)`l3)zq&gU*(KIbDB3cGhV44AoneQxj$-xN@5j#F+XgUB8d~E5MGc6uHed!hs|!& zI^Q>F(!k+2Ft&kD@lx}-QY%1I;m7nOR=JVmnAuw+&k<|mE8QVHpnqS^00;19L;&D! zH*MNEU6$|um}v0Q5oZwtMGLvI;DooVfw)EX$dRbi`vpFHkII;GuX7%hm3Hv#G}Jy$ zm)GM13VeE;z{LIaphUxPDx?LgMB9Hb-eFrE$u-It$IXXS_3x`X)y|e1 z(2tXY!rdvDgjUPjoR3Y=tp#a%a}nIL-Lc~|H3ceg)k_5kyJ-^oNN=LNpMwtsU1nWv z={k{MYHD?|TRX{S@Aa@_n>VaMivejVcpL@hiwKCP`^P}=Y%0zC4It|&w&IOt*j>eb zzN>@gbkU#On6w3U(QJKF_Fc}_fsg9iY)jsdeVrXfV$hKi~O9Jgo2SEkST zsXkn!OW7cwS|6(Nr4!cm%8FG1ne=PgE=@GcCghoI*175}9kwFapwQTfX zD~vtl1f*W*MD|;K0i7aqEze_4*9A|r5o7Y8rMP zM)Hib4^T%Qf#z*rE^Er@K+Kq$*rdEWi(N>sM6ij8z81d5G266&vCF}%?_Xr(+zSyQ zI$ajoG>VQ!T8L>L^uBM3glJ`w8{&N@m_Em?AU0aZ+H-%*ek(R zQ5%tucyK!#_XX(f_1n(D%N#cWT@B(~CR?NLIjblg_(`}oo392nGV@-wE-%t}wht;l zIm9!e!_t?yz8kL+B|SL6;`KR4rj4IVfSGzJeZWH_t{U_;u(o!b9ASk*>{B#~9C(UN z3>LMhu=I7ZB{bZMRHj*)DeKvn#pgD5tb^VncwRON3>z6l$odXqIafB{WX#f_Rl3GS z(ws#B&XWQ=SHU}R-?7Vd>RetF^?15KO5+~tNi?lPvAigbPE+RXdwAV10J@l-cOi%@ zMKTG;z(hYZS|`~-zC6y;aqj?~hqXN9(O{l4NfsF!v^0% zOBkff;=m3^v!`guORbf728o!{zgCkVrE79J=rf6-BgN{}@cF2}k2GG9?kKR^v zna+jq+I=(uhL%}G^ueT^WF#6&O`Go=m|g5-$}pX>4p55uNtcrD#Flunot>J8s8 zX%5m=Wx=SpgC8eDl?h$hdZyZiJ&2%VJc?JCtE1O88PZ-vkvD&nP3*KvVWP1Rd{E?| ztV2N_-geP@9B4FoYF>!+6YtQ}jD(ly)3|1_DY{WV%vyA{xKy5koM&(tiffc<^4hN(xx|1r`j}xF5DM5b#+u{8T&faqTkFul%Xg~qnIkxP=dAT| zXEfQ2-E9sUA$r|rCqvsB3plsKIxudq^1+JsFonBoZ17UWU)I-eOmJodWG7-}UeA== zzRu`-7$4Lo5!6&UV8quo1HEQ7z!v+#(^fn7!ps%c!nUg}b{V($1Nm-=PfgOpvbk20 zI0-_hC>uMey|Q?Mt~lX_ssUba;URj{8?*LsH^Z!QRs6;b$(iRP`Q!^aYX{UCn5zA5 zVbCYc0B#|%U$zvsY%%hMF%a~<0RLmUCCD76lzV2jO=v$dRh=v+3g?jwsITQQ&I-}V zRnni4g+@DS?EDbgnZ|J@Oy>~dmLw_S5L0SXQ9`Ff8~viSuy!l&|FEkblZM0l?X#Z- z4EXGB;1T8ojW7t)ez>_*GsNajm$M1`ogA*pnmV$^sZ1|*P>y`3w*wJ5KAXds7iXt+ z(50RI`&sls{qqu!pBcjZH%;qX&H)>|vF)1qZsV?(=Q+QyN!e8@i`PGhf!e~eaC>9_ zxMRu(3&S_C;Bp6P#oz;{O2mTmr3a3LH1uJ-$yc zqE4z4uWyf->x`#B=s&?5xF4Y)Cm~lzwgDKN%dxm*Lp=om0(dv}2(Lf?E*n3;o^5_W zgOpIm6zM{~{V|{Z41eZ5*B*7A zL#YQCDuZTj^wKGO+RcDLCP2PP@zr>+_3qa&=i&HcDQ0#?1c)$ zfzfKVtZIq)PW5+`>lkfx0(Cy6Uf`5w0r*c+NIhs)gag1Rm}dZv?w3E!QkDGn#pa=7 z@wXfZugEhouNx0Kq(ilg^{aJTYktz?kTrR^1wh_@3<44_!P~~$IbZ8D{YsfxvmR;3 zjE*rqF~?k2DqLUy7Clbm5D{oi^tNTw_Rn{39=?;aDX6WmPm+^#DoLw!ZjNLW~xFj$`)^NiiJHI}}r)1U#7W4d|-Dzze2| z4r{m^2l*!sBTS@=9R&vpy78kL`YMr{{VsKEJzUAv$lyeRzFdq@8P`SXWb1Iba7$nP zGoStGj$2~p!yvRuT28?HS@>d@VDo6u;cu{7pe-ruG&)B_!m_+cH`pUJ)QX=(eHjsS zq}7U6A}}fUP3FW;3MrCLn;hYhqvS%}G)F^n?=c;S!U9L}}FL+&4= zdn2J5kCCwG$qYEdUcc2AhmeTV5o0pi^9lW1fqc;N3+8LFrJkm|E8zSV7hVC-o&My1IQ}tViivU{zUWMip-{Yqqgiw5m0IMd<}f6>}ND788-*RBZITv=%lGqmNm&Z_|r{k*Z=J|iS(QlSjiAA8Bnizz7>YN7Q?uz@RXh(Rpv?iId``a zmxusVT@kj^-^pd!y?zQ%QniZQQ_}K;Vm4IXLJAY?IA8Wh*}f_Ey)5A;4)D{Uge*j3 zbR#To(@K4|@dVS-8ffYtb%&E1qEr&ImjYF^Kz!j?_&Qul!EIkqRb&}%u!`Fih#>Jt zZX!tXCDR)pGn&RzK6V#eOa5ZxZRlC_Tg7KP$vO{7_`}ms%=#F|10KmEz~B>_yz3Svu5sae%X8;y?~|*e zKTc%&d*@UI_|H*R6)KcpBpOu~fLY`+&mw#W3WY@W2%sHr$>E`mB=Qi(YH0FWB?GMP zf-HAR>-DiH#&E`l4KrF8H$Y!`zjV4~cQ1sswvF=Iasb(!(|ECQ&X{Ua{10(AN6frC zvns%jz7PmG6HFLbKRny`8$}Ji#oDN@VZ=$mX)Vym4I7c?)H#IW;x!+zaqSS8kCY|? zxIfK1W-t$G^y(;wr8{WvFRi&If_%{{c5G)5*WO*Hui$k3+-R(DAk_m}n+ZFDMY*#j z&_Z9X%v5f=s8-=`Gb^&>d;Di{Xk^BCV;q`1T{x2yjD(wA^aP|PrNt-^B26nD7`=gZ z^-|{4&V0h^KXt@Xm?4j2O^4NDsy|=tHwkcW>wnm$N3a%9I37oOz$_{)M1o8+plYqR zPCrk5hhwdzPbS+R%hE}>{s11Kc+OLu2fZ~?!b8$`r+!SZA_zH`6FGb}{-$v2+)A@d z<2bk5gftY2M@lx+DNRDl114>`(r-MkGKz^tNyYhM+Hi~=StkCE9MWb|bhSUA3p){}W+HlaYsHfYs>NH~L#jvSE@@2`53e=A&L7G7l_V{jhwmvUsla~8#1JMGI zyyZ6vBo9tcuHDW)`Ezl2hdP-BYV}7qEv%sUn)2S}=5350VT15a&0>*_jC1W>=F$N# z3?f~qU>d+k%%ls|Lry!mwwmatV<2ts3TpJw5N_abK~P)R%bG$EA%0B&OO#gX0_x<# z#$nUisAjpAedc_RO8lZMUXR#vZJNSW%?7>oy!yP?Aw|_NfS(0eOY*U$^AoT@)HELx z2gLJyrs11Ri(e@*l-^VQ|2-!BW^n(GS~wFV%V@&EN-Fn3S2@VG>tf@#4!3D=nGcjY z!|z)F-GLxIbk=?`*-l&w*EJN{GLv!RvCBW}V}W%`-fYETSi>T7b!+9oz`Q!W zi61>kUnFRa;0<2?7C(irno8S%3wB(|Iv#%m`If8!lwWIQ<3Pym|;4Pah8pL}}4j9Hg z{I^J>P1}XMXRka!A}`B&CkX(VtdB@-F{)9eL*evpv|81)Hw!pZ;c2!pnlFG znI^1o#9Yq!99=e}QxR>eN2JP~siTHIbBA)@w}Pa1HQVON98V9xV*GP?0NQ?x3}p8( zWI4wlnzvw1a0TRfYv5rj9`6?w?ikDDksz+r(x7BPzu zDKy{xE%>3{vf-s?JseL&oaa;a{wI=M_*WxDlu;gpYf(I{o~ca4DO2A;MP*@Cw%O z1p%!O`ggetPQ%&=nFnk{D9}?)(f}To30?%uwj^=oDu#%3d~O^r@u(z=EHfT1&~N0;nLKw-{PaapV|A2`p$9F$@V&XMfsxh zT1Xvj8|6X| Date: Thu, 1 Sep 2022 18:34:44 +0000 Subject: [PATCH 617/966] fix: Skip oauth2client adapter tests if oauth2client is not installed (#1132) * fix: Skip oauth2client adapter tests if oauth2client is not installed. This resolves https://github.com/googleapis/google-auth-library-python/issues/1118. --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../google-auth/tests/test__oauth2client.py | 13 ++++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index f457463585da82f46f58530e4e17e9ba825b7129..54b52bfd435a81dde179ce61f6d1f61545ece00d 100644 GIT binary patch literal 10324 zcmV-aD67{BB>?tKRTH4Q;LZMC5T6=k^t*2)sync1`idz)F%Y(pw?|uuPRA0ePyjnQ zDV%xB?}CQP6O2tR{yHf9WMQ3HzUW+^kAavLEE!S1Q#5>f4H3^vn?`;~mUz1x8c+`N zf>{?%CUpX>nikXrjo|uOiSJx6>%KY248(O#dZ>C<ob+C21N=;&g>_3Px%VYCeq$nhBTgRd7z}-iIy}1P;RK9`5D2l`C1z|1 zhzqCc1MqQ8vc2{3mxyvh$M_}{mK0vxm^dB)Ep3dkf zEJ0oIG8#IS@e0Y3&;)z6Er}{T)~%!85vwJ)S=#!Un>37@k~u+1{P9>DKANI-2tT5f zag*J{`hy#)m^&7Tv zv}0u^oQY+`=w%4fiUU)ELk=C(9}Wag+pTFP%YO#Yp$dsKrEq7gd3VS=Wl1bE7gMWXCiWT<7QT=_L;-}0^6|FB2j08y^qxw#ww!H-P( z3vDVlk=pdl#8tQ=NL(L;=(gt=&!;E9E+MKonuo=21U(wHtc7B2xSb6*gS#YpIuOnR zSZsXR2arq;Cm|2okWWTm?S&#^AO;MX!+v#Z9JC9&=0Sj%Lfj~K_+bV64QPDRDbdTX z+HV#?@{%88R%$oH2^Jg1-5(V-ydJlOJoVEluVbE5k@SZafK?NRBNHr?{4ES=ZV%1y zuF6b(zb8O!`7tve^=_@j&LVNgS%!-x+-3h*=|Dzc(G9)-7h5erE4_hpWXXO+H`3a& z?^0ZHV9+BS!a1r@o-H<&b^Zjnc=%Gl`)0i$UgJR~w?U z+aNbKxmbRH9LPz2puAHF{UmWOOo^$WOoVP^E!uP(rjF+daclo)r{ za+jhL#ohcjs`ZffP`v)drdej`0=CvA_rIWri&x~NJS!HbXLmYD6(BLgrr>p=aQ`0Q z%Y!Z#Nt*qLQ9cKta1nJ;EmIR-^N34plz{Q#jxdo9F^OnfN>q3ZL@k%22_w$us4cUKG5JdlNxnL zKH%~ufrhV6LL*MiZY}SE<*D$JcRBZN)^pL=(>Iq;omGQHH2yJ}>Lc%Qc?#C@)i1yJ zqY4uk?gM}(q$V0xQ0H;%Tx`>5^ro5B2c4cfw_DvtDT7P*_}I$09)R+e`+i=ZjE68d zZ?l<&QV3G!@YIepY;l~hiY_m2;&a@g;3}eG+_Y$tNQ;O@%%y>rqxPZFjQdWJ>a{=DJ(CY&_suXtdN#MK_D& z_1qXawu755Fd4=NN$Q2nBA9s=hKoN~{`ej_I{7v<0|>~6ln?GGZ8pvIbxcdW)piv( ztaP~5{4mhQ4-V>a9F0ro1W70&&5zMqQFSlcbs-sxqJX%W<-!Bwv|N7Ujk;q(VKC-k z4sr2)pD;dLgstA@6bWFNra&4Tc1w)WQ54ccc?pkEC7b4tLL&(JB2!Uql_7m|@wVE1 zR!JpNL={aq|HjR@A~{?Ftl5VVa3?V%(#Z#HD9 zQ)4gI*DKf{f&KysyvXG@JTyTCW2eX$b*D!|-8~3HG^+ZR#h)iYf$z&ClR1tm@9imX zIanI+o^9b)^E?d>N|-{(GqtPv;m(>KSseXfxEI6AR>OK#1^T(wk&+&GC;m7B37tf zFXm0hEPv_iMWOg1=aQrVlmN%V?&m$}b#!!E6}2kKHMdw z)?hl>jc2I(W7QmvlR*o>p*&z6F*2|lqo}`_%}5ktnbTga{^3W#hzhc)Qy*ft^&qwj z_%p%O3Da$m;yMH;D{9n;uYLI1${~hgA_@&ela(-Z8t~5MHdSj`!Hvq`zcQb5S;Ghu z;yP%DFSZNoQXOqYwjw6EQKeMOrZwTlMx}bty*W*P+kXZ^M{{at2D!cy0|U_wcEib? z6i;Z{Cs3jzx@wf8P*G&c6N!)bGd1S!XacHd063Xbxt^z9^PeezPEo;}pKm(F_h$dn z9g}W(yl**=V3xn}L|`NbBO+UfX0ZwdAv66c7aSLl4!0Q2GhM zw7=n)P42)_hWQUpW~XR71ktxZMZTINRiM{kcL@|f(LOn#XcPe#lQ(xosHpL#1Oumv zHrwvxeWGoHT7o$~g2wH?{@An>?g~%A3VR9s$5FnmK%bJGnT3cf!;7Getc3BYvaj6Z zv|JnPc8e*AHIENYkv#*08RO9z!B&HL&gwr zki>lEXe=*yoYiJ1??>gBPAMZqJ0$R7?~Upk_SHNW7`X)vj`2)_Px)Sh=tAeo z)`L_~u_Fi5o^tnIE6`XpEqrID?oTa~;zp%UFcRRX3VPI>C1?!|<~HZbeGn|E2qG(J zf<{#twcjU=MeK4=C#gEE@q3==$xBhf9aqUU(6$EFNsVB(8x)SJ1iQJY)}n4U7;YZwi2G z#)Q*n+vFQzz9g?F=Hjr123%7zyskOtJACf+Cvvmna1U$4 zii0qzUa2MNZ_?2tuhj;)dSW9I>G>T2p8hx}gzFM^!8NEgU~cau8rs~ujuz}1b`!~` zf(uzF=yU7}o=U$iiX?c_q1!eyv!d`Dq*jnObjhVKj7-EA>v# zq+fLPhwr|s%Cvha=8Zo2d0N#GL#6Mt`pNSCkkxv54cUUH-b)(4JfdwXPWhxnOEn0J z$^R??xV!KjT&kw)yB#0OP&Z^JL_iKv&Z}n{{*c#AxZ{)8$|b?bj3`oB;DE=0E1DB% zjfZ!fw;8mh{JcJm`*hMi=WAk^1F-P(=ZNXTv%xBc zP%#h3Ar>RDK3@!wO=jv^55BAe0bI zM9X+GM?!VlvB}2~&hq0M8uM1lFPm8yzJ4oV{hlIvnK5AKoyR@YntjJ2N51Eo8WwZl zxR;Mvs`T;k!^5ow6VlxDyfdr5<59BNYyQ&Rm=$3wJD&xf)tsC@o2=GGNG(U(hA1Y} zPLVUgX`CT@uN3z%M2X^Uk(F;UuZ5~~Vj1#%#Cz>W?T|Iwjc$M=^lDJ{XXGcW448or z;G`OEQ(Gf-4Ra58zkvdKU%<(*cNySL$A6zf=B}W;Wf4LWIGu1~R18s4==CaHu*&TPHI2UozCAgza1*4vU>qgf@;y5%K`}r>xXQ% zUHeLQQPfK!#+#Z9$akaKN~Lwd#9XTxF@$=X=7gjC0%M0>#?lTaB7VCykcJqjL@_#J zfDY+{M)5<7Sc}&!Cx6V!VI}jYsIMsfTuv&S>Z;j8m;j>R#D!^NNV3mNs}9mzqIw%`-`UN2Q6W2P8WHem55qK%w6$%x~5z!HwG7p2rh z${N5|tr}Vg30oF!3W$O=vlxG`vkf`qfEGbS(d_UVv%EKF3d|Jg^{#?A{{wIFvj{nq zSmMnQp-6{f@Ezg<$8%>KDK@mdQI&m56C8i^Y5#3FLI~!DCOcrM6!6evHsG$5zXV>| zWfDshGmbDn#mM|Bw%J!$2;k50PE`weV5+atO^U;yy^!f@M1GpoC4tC% zngRSUkJh@k!Tp6WqHvt<$7cb7=i_|LN`r^fyxtNN9pZ&4C+h)2AkRkk>9J|`e<(*3 zBBZn)UcY0cS}pCyUOrDrP{9U$#z)-F)wzik-8_*Dgtt-$qi&TBp;A9CYR)H#xey*7 zMZXw$OM}t8mTk|?njPB7W)xth=(Qd;Flp@^C@yB+MZh*XOx|MzoP%Q{Tkq zZ^=6?1TwVbdAWNrK92W{lL=$NgDuY0Y%Yl+5&g%GG(l&Q_67e=rR#zi16C}%jadRE zv}}pOf1M#Q1kij~RY>3K!o|N$*t~A`fW(>_^`P?-4)fNlo!fDRrdl+v8-`@s;Yr$6 zUD2>vd-PK0Dra#2vxQmnDY}hci|eVII+{SJ(?n;o%>cGlXGA|+BhsQ{#r%;)Neunz z6w4EQ&&kGRF1!34Dz~PQgX;RV7BaO%+^N>h)D~Jd_V@po_#zVfo5tdqu69{fM#{0# z=?qbxP$#9~L^F6}f@+K3Q7_l|wXY7@7q*Lm0J+Yb7&9JiVso^e8fe^67FF*eFpz2i zAD6Bokz7>f(6sb*wm3>>1&hyQXYbZGGgzxD<8!uom%eTs;wF;ky!EtI$%Zr8ZTW!? zUa$!a%d(yLz~*1t1#d@pT9y3hSj^L(3`t*nWSAvukB-ZYJarkxf|tY>uvhSfTs$w{ z#kGM0(X@;PR32krpIs9)>(_B)g{nbtfNJuUgDOeN>gi>jnz==#b6u^`W4!DjvOG6s zO08=I^TYSMdY|Cth2EuINpPgL4i4*A8Dj;BAHFd}1##QH3L8!qH4>qm>72}*lndPX zs%deXUqXr;UA~}gEap=jNy3HxwB~zcqm$q}--n&x3`9xE^h94`> z7h&Y*s#-j-v0TB)83^{M*$V?@W;MZ%zEZG2<+@U?1PGmOtVa`4yJo4DJzMqtIO=>b z2Z^i$St1j5dYseMmz_f8kXrPDGN{Q=0?W8NS2b~Kq^p`&vyEXndwpJyed5A9(~e~*-s%RUBx6j)yy z;C9UE!RdHX;qh=F;NsNV`a_&B!)M;+#@e;T#Fc(c!O$kR5n)vP4&Mh_A|UnWH1H9e z7b$Uocw)gQjO`uSGL;bqXd6ZYRAto}^%Osy=SWW;TnX-VYYT*(mO z3 ziv&c8#}y^U@@GFb04iWsqC!~;Us%&*3Pf}88OGI3@L(iiI|FXG-!OL9+(Lngz)JdB z1&Ge>-HN7Ij&J<+Ps-#viCpb87S(ZFim)2Kj@jnFO=QCgtw(vnF;$)hRICRvQ4rIwXe#o+ai)fq`M${nWZOpfAuXZHy7>rpH}|1a zL?%8)ee(01y0B_S@s=Q_tWZl3EcS`t#0$@-6rj%^4>EHspXpOr+@32YBKvv{G+ZL5 z1NLCOjAt;;bT=(r7D*3U$z)fF$wlGAOBXU*aBz-g*aFBRLcE9M-lmY-B1H5XF!g0> zr7QGW8!DOIjP};PWbT9ID5FR#U*&(un~Q>7|CgKHIsfU@I#2UR>~GsYJsf5ITKXV$ z*I#0JR_6al5Qji)$CX<^4;?JH()Tj6-H=MUXL*t|ScU{hYaJ2!Y`HU9?yGo5`Ld$( zk*m~l*fbRvoRH07?YQU?LcF#!uAfEpvR~0ZwlKbWY?ADOucM1~$#N>Lmyc5z578{P zkdB6EDd%a=3gibg3YMJidX_7=0ZFs0PoeCtd;nG7YM-KID0DW(6Y9h>Xk{OhXg4(0 zY5-dlA{tjDeZU!>Bk46g9&l;3$;X4gr7J)rW=?x%%%A}ZZWD!lFe{qJNR%xx!mJOS zLm#jo!^w8mynQ5TT=i~=d%l-uOfhVjW`>l}x9bkKq%ojMnYWGji5wt76vl7oVwk1- z3;w$I9K7Us!Iod@c8|4zeZ|1c%kq+!)$sM1eGkB_fg6?5t`XJPdResSQ6%aNfLPia zc`q2SYzH4ewi({(VwpfGhzFBPjp=-!hQV24DEJxg>2h(;X$ONM2ox^eOZXNkah5UQ z03QO((VvRzmZ>xX;L5?adhIo289^=gl@1ZEU(CJSw7NkS+ckANap~!*oB$5{!Kcs!hOp&#LNk4w6Dr1j3cEp@GbLU~28{~SX zq=j9a@aZCzf0r~Ve~I=18#aaov3@XIC=y0UdehWef~I9&Lt0I+ZY)Htxj7WnKfBJy zYVkYbsa#@^(T>|5-4-VJv#_+N zhFcQOm&glMZW*FUP>+UH&JuDw+zHha`I&*H|7y<{I_}$5eNL0Sq;qfq@4LlQQRYmi zvysZ_*KU#gj$UpjcH2n2G3O){!I46mPfV};vNttOY8g@ZWOLWOJle3uF)y=?yDw-g zSe{H_D?M4*P+$8Vc6)$zLz3sG{s6Ba!+x|9u4BwCBNd{3kRkb)xSzMV_i)x5P(>&{ zaT!`g2#pR5#3WXt+b51sAjc*YGrCIP3m77>H!~X9xHas}xg@J<#dOZyQ!8gYwb6y1 zPKr>nNpmbpatMU^iq<_<)~!}15iJyPF!3*E#0@-;AXAzn_>#p6Yceqsq3|B@jYF!5 zrP6pTX)8)q>DD7*mY9LzVh;$UF0tK@z)RT{eYISo<$aNzGRKc3N0Bz_cC!sW+Aex{jszU%R2Is|DcgF6-2+rWj?;4W z6Y;PhULm{w?!UKH57+yYRY+!NCl zL438olA;k`rP@Mk7L2~8z#PUg^!q%&(s>k{l06nD-v6NNwO08ar{uoGfj@pBnF+^> zS#WJNhpAWs8yuy(lN?XOMAWu^Lw&SargU z7b@ty(qvGsH3shGWBWTM2V-TWKWLr(M`eqhGTTAQVDgNgs<#d!x?0rfgPCvKXQweG8+&_``f+ zdTO)MsiF{*0OSKJ{)BYR)k<4z7byUWO0+h?*RxN*1U=&I1aA#)ZUSAEc{$Er+u7g2 z>z_%KX20b|5|Y6x@66xLF)wSha28AA%X(x0<+mU&`8&7-{KY}~6Wcp)R1Jz09lZMh zCyf@h)J}65Bu7=}$=WD2GsSE103oy}LYU5HuFm1!DqN6%#?tCA z(&=}i)m)23Pe3qikc?8f5>HDtW8j2nH8z-c$#VQMyE8QVF=t+@>{#lVJdWB*;((`b z{W$SoNPOVIVXkq3fUSY2%lANQ9qem4A<>A0ZI68SNlhTea8y2|JKPj2wMyR%W&;)P zGt(s(Q5&lYY!q&JgL;!v2dAr(g{vILdNJmw9!cazz3^p7Ft=)|URbk_Y|B~$`85A` zt+r6OU781;)wix$eb(hOXeZguOGWPds?^~`t>{gqitS&V12}po8r8GFULGFevVPPP zuV%eJEHN_HPkhwo#41y(Asc}$JMV9|tj!s0)Ebq6DHOZZufPn6#H_WvZJDBj9ihwe zpPK)|Pc9}+d@~nz@yhYR=q>i3EA$n&jhx9&oACv8Y&0ak$B3qm>> zD`nK5n8rh{sTr!9x)W^E%D1wHCD~K=)?FxYCSh|!ky~!0Q7CZEKiJRP>_DG#p?_s) z4poqk{%Zk}bX%dQLyXF1%RksjezNBkz#9#5GxEU$MT@s^5L9cyANc%c7?Rb}mzJL{ zsk~WMD>IcNRhTp~)=ru<&qk1fHHsqky7FZ%i$q7&M5~@duIH%?p&WLY)zpJo*6|n? z>*vNf$eNQq=YM_Xup1mLwDiszC%w`iDHMH+1xTtKUFgm7&Yv*j{PQ*EXf7<=D4G!B$&+qf8`^4zZ>r)589`HuPuPa+F zz+%k{S@NH#2fcIglO}ABp}>|#PP`<3(GU%=AWaptj$cSU)xvOApD@F^5roKXuGK;t z)S~w}bb+xn03NjIw(M^wgbm-Av==QlhgrA`F9K8lQwcat(yi;u3-(-JV~1NRP-tF4T3SfqV>uNELK>F#EAp!VFv0tK?Rb zP}P$}HScF9HA*5~B z;-0#bhbMTd!E}idTqk2iA6U+*J?XSJB9zRNg z3jo#5)t&oWg%)nQJPtHt!s&}o(th!OLFjhxi4U%Nq2_P}$?|OsHZ?2Nj@(*aA# zSC4iYO176^vSa|t!7Oqj8?h4d-E>0e5d-&q4nah?mTkWvYy3Cd^)0--BOCpMgHXa7 z+p5q1Fc^^y?sR$P}F2mSX<9A z$vEhI+j3_{*$^+vyiu!{HErd+fh%ptW*F zc-awaWbm=4h0#ypa7soBp>~gym2Ado*@RNZrk|2VBbhlLZ?<8=gM`3`V3VO9D@o%P zs}d2=NYX8c*K7Na%SuyM?!6+3;6XCX=cpVEg(p&;(Z?r0Sdmg#)uGC5z7RlE#To5! z`n*TRN7AiACL^+yM+YK3FrIG-GhPDJx@A3s98G1vGx zU$s||TIAiIoDj}xwcg6-nsdK=s9GXV+(O^(hjRHlQFg6>D3T<} z=%Ze=G{E1)xOvV3nmRaGB`Zzdd$6BN|3G`4emqeAmiH`zo&p)+z>-az0`S#CMb9RD zpLsn#8P+!73)FeE&JEnQ9ms4IHQWYHR%eHaqC>tVQf5%)v6ejE)uy(te8Z4L^(}zL z2eU5hdTLZ=w*cl~UjS@d+5x9t)8g3VM}1SbI}W77xZ@L1rX-FcbbNlqXSh3YgrZ;RhRA-1QLmV|yd;LjCFe^IW zWbv&48Uzp^GMMEm<0$L@_I$m+3s1W7+C&MayE{C~_QessbO^Bp-)&EfZ9$6pZ)9|& za2GG%WMuxsx&)@RLk@lqeTatob6!I^h`Q4N4}pEgCH^1`&a2flH!>^P)<(5MQn3Dn{}+(%96zm}Ni4~O;=_G?cEX&<& zM^y!3~y2-sZjfXPAVC&FH}YYvh^Tt#a*cK3@e)?j?UVoJEY}hqhOk3Wa858#`GANQ0EE6 zB8U9L5Gp8#?ll4IOcS%pY`e9wJ(bgMDMC7tLoX(B(DYa~raJsTY3p6JEMqXd5Ufg) zuk}5B6zKN^Xfvdx_`EchI6a8dBi?{`!uh|oCk(dw9v81v9DvSE zp4PGkj&oJwg2Df+M4zc1!jrQCv~2EI4rtsP61Br#3X|Q`M`5)Uyeb`X=c_KOB{Y<6 zs0&Vi8@;`j1h!qKB*B3M3@246?h;a@+$y=wy8XN@$RLwz-!ApR@p_fYs1Ka)wh}p| zUCPdsbMuV$1VsM{;qEVcC`kqjeI;$zS6=wK({O;ph%)1NA1e5%M_n$KxiaovrWwQg mi1UEy1c{1kFBTaN{8=b6GMZEQS$_~p+N6#`Id+y;X+;m@?EQEE literal 10324 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 diff --git a/packages/google-auth/tests/test__oauth2client.py b/packages/google-auth/tests/test__oauth2client.py index f1fac511b410..8802ba17f7cc 100644 --- a/packages/google-auth/tests/test__oauth2client.py +++ b/packages/google-auth/tests/test__oauth2client.py @@ -17,12 +17,19 @@ import sys import mock -import oauth2client.client # type: ignore -import oauth2client.contrib.gce # type: ignore -import oauth2client.service_account # type: ignore import pytest # type: ignore from six.moves import reload_module +try: + import oauth2client.client # type: ignore + import oauth2client.contrib.gce # type: ignore + import oauth2client.service_account # type: ignore +except ImportError: # pragma: NO COVER + pytest.skip( + "Skipping oauth2client tests since oauth2client is not installed.", + allow_module_level=True, + ) + from google.auth import _oauth2client From 104b5c96c36e953aea852b17b6054101a3fcc1d7 Mon Sep 17 00:00:00 2001 From: clundin25 <108372512+clundin25@users.noreply.github.com> Date: Fri, 2 Sep 2022 11:04:15 +0000 Subject: [PATCH 618/966] chore: [AUTO-GENERATED] Refresh system test creds. (#1139) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 54b52bfd435a81dde179ce61f6d1f61545ece00d..30a208ef5e1e6a09e1110541a05998d1db2559d6 100644 GIT binary patch literal 10324 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 literal 10324 zcmV-aD67{BB>?tKRTH4Q;LZMC5T6=k^t*2)sync1`idz)F%Y(pw?|uuPRA0ePyjnQ zDV%xB?}CQP6O2tR{yHf9WMQ3HzUW+^kAavLEE!S1Q#5>f4H3^vn?`;~mUz1x8c+`N zf>{?%CUpX>nikXrjo|uOiSJx6>%KY248(O#dZ>C<ob+C21N=;&g>_3Px%VYCeq$nhBTgRd7z}-iIy}1P;RK9`5D2l`C1z|1 zhzqCc1MqQ8vc2{3mxyvh$M_}{mK0vxm^dB)Ep3dkf zEJ0oIG8#IS@e0Y3&;)z6Er}{T)~%!85vwJ)S=#!Un>37@k~u+1{P9>DKANI-2tT5f zag*J{`hy#)m^&7Tv zv}0u^oQY+`=w%4fiUU)ELk=C(9}Wag+pTFP%YO#Yp$dsKrEq7gd3VS=Wl1bE7gMWXCiWT<7QT=_L;-}0^6|FB2j08y^qxw#ww!H-P( z3vDVlk=pdl#8tQ=NL(L;=(gt=&!;E9E+MKonuo=21U(wHtc7B2xSb6*gS#YpIuOnR zSZsXR2arq;Cm|2okWWTm?S&#^AO;MX!+v#Z9JC9&=0Sj%Lfj~K_+bV64QPDRDbdTX z+HV#?@{%88R%$oH2^Jg1-5(V-ydJlOJoVEluVbE5k@SZafK?NRBNHr?{4ES=ZV%1y zuF6b(zb8O!`7tve^=_@j&LVNgS%!-x+-3h*=|Dzc(G9)-7h5erE4_hpWXXO+H`3a& z?^0ZHV9+BS!a1r@o-H<&b^Zjnc=%Gl`)0i$UgJR~w?U z+aNbKxmbRH9LPz2puAHF{UmWOOo^$WOoVP^E!uP(rjF+daclo)r{ za+jhL#ohcjs`ZffP`v)drdej`0=CvA_rIWri&x~NJS!HbXLmYD6(BLgrr>p=aQ`0Q z%Y!Z#Nt*qLQ9cKta1nJ;EmIR-^N34plz{Q#jxdo9F^OnfN>q3ZL@k%22_w$us4cUKG5JdlNxnL zKH%~ufrhV6LL*MiZY}SE<*D$JcRBZN)^pL=(>Iq;omGQHH2yJ}>Lc%Qc?#C@)i1yJ zqY4uk?gM}(q$V0xQ0H;%Tx`>5^ro5B2c4cfw_DvtDT7P*_}I$09)R+e`+i=ZjE68d zZ?l<&QV3G!@YIepY;l~hiY_m2;&a@g;3}eG+_Y$tNQ;O@%%y>rqxPZFjQdWJ>a{=DJ(CY&_suXtdN#MK_D& z_1qXawu755Fd4=NN$Q2nBA9s=hKoN~{`ej_I{7v<0|>~6ln?GGZ8pvIbxcdW)piv( ztaP~5{4mhQ4-V>a9F0ro1W70&&5zMqQFSlcbs-sxqJX%W<-!Bwv|N7Ujk;q(VKC-k z4sr2)pD;dLgstA@6bWFNra&4Tc1w)WQ54ccc?pkEC7b4tLL&(JB2!Uql_7m|@wVE1 zR!JpNL={aq|HjR@A~{?Ftl5VVa3?V%(#Z#HD9 zQ)4gI*DKf{f&KysyvXG@JTyTCW2eX$b*D!|-8~3HG^+ZR#h)iYf$z&ClR1tm@9imX zIanI+o^9b)^E?d>N|-{(GqtPv;m(>KSseXfxEI6AR>OK#1^T(wk&+&GC;m7B37tf zFXm0hEPv_iMWOg1=aQrVlmN%V?&m$}b#!!E6}2kKHMdw z)?hl>jc2I(W7QmvlR*o>p*&z6F*2|lqo}`_%}5ktnbTga{^3W#hzhc)Qy*ft^&qwj z_%p%O3Da$m;yMH;D{9n;uYLI1${~hgA_@&ela(-Z8t~5MHdSj`!Hvq`zcQb5S;Ghu z;yP%DFSZNoQXOqYwjw6EQKeMOrZwTlMx}bty*W*P+kXZ^M{{at2D!cy0|U_wcEib? z6i;Z{Cs3jzx@wf8P*G&c6N!)bGd1S!XacHd063Xbxt^z9^PeezPEo;}pKm(F_h$dn z9g}W(yl**=V3xn}L|`NbBO+UfX0ZwdAv66c7aSLl4!0Q2GhM zw7=n)P42)_hWQUpW~XR71ktxZMZTINRiM{kcL@|f(LOn#XcPe#lQ(xosHpL#1Oumv zHrwvxeWGoHT7o$~g2wH?{@An>?g~%A3VR9s$5FnmK%bJGnT3cf!;7Getc3BYvaj6Z zv|JnPc8e*AHIENYkv#*08RO9z!B&HL&gwr zki>lEXe=*yoYiJ1??>gBPAMZqJ0$R7?~Upk_SHNW7`X)vj`2)_Px)Sh=tAeo z)`L_~u_Fi5o^tnIE6`XpEqrID?oTa~;zp%UFcRRX3VPI>C1?!|<~HZbeGn|E2qG(J zf<{#twcjU=MeK4=C#gEE@q3==$xBhf9aqUU(6$EFNsVB(8x)SJ1iQJY)}n4U7;YZwi2G z#)Q*n+vFQzz9g?F=Hjr123%7zyskOtJACf+Cvvmna1U$4 zii0qzUa2MNZ_?2tuhj;)dSW9I>G>T2p8hx}gzFM^!8NEgU~cau8rs~ujuz}1b`!~` zf(uzF=yU7}o=U$iiX?c_q1!eyv!d`Dq*jnObjhVKj7-EA>v# zq+fLPhwr|s%Cvha=8Zo2d0N#GL#6Mt`pNSCkkxv54cUUH-b)(4JfdwXPWhxnOEn0J z$^R??xV!KjT&kw)yB#0OP&Z^JL_iKv&Z}n{{*c#AxZ{)8$|b?bj3`oB;DE=0E1DB% zjfZ!fw;8mh{JcJm`*hMi=WAk^1F-P(=ZNXTv%xBc zP%#h3Ar>RDK3@!wO=jv^55BAe0bI zM9X+GM?!VlvB}2~&hq0M8uM1lFPm8yzJ4oV{hlIvnK5AKoyR@YntjJ2N51Eo8WwZl zxR;Mvs`T;k!^5ow6VlxDyfdr5<59BNYyQ&Rm=$3wJD&xf)tsC@o2=GGNG(U(hA1Y} zPLVUgX`CT@uN3z%M2X^Uk(F;UuZ5~~Vj1#%#Cz>W?T|Iwjc$M=^lDJ{XXGcW448or z;G`OEQ(Gf-4Ra58zkvdKU%<(*cNySL$A6zf=B}W;Wf4LWIGu1~R18s4==CaHu*&TPHI2UozCAgza1*4vU>qgf@;y5%K`}r>xXQ% zUHeLQQPfK!#+#Z9$akaKN~Lwd#9XTxF@$=X=7gjC0%M0>#?lTaB7VCykcJqjL@_#J zfDY+{M)5<7Sc}&!Cx6V!VI}jYsIMsfTuv&S>Z;j8m;j>R#D!^NNV3mNs}9mzqIw%`-`UN2Q6W2P8WHem55qK%w6$%x~5z!HwG7p2rh z${N5|tr}Vg30oF!3W$O=vlxG`vkf`qfEGbS(d_UVv%EKF3d|Jg^{#?A{{wIFvj{nq zSmMnQp-6{f@Ezg<$8%>KDK@mdQI&m56C8i^Y5#3FLI~!DCOcrM6!6evHsG$5zXV>| zWfDshGmbDn#mM|Bw%J!$2;k50PE`weV5+atO^U;yy^!f@M1GpoC4tC% zngRSUkJh@k!Tp6WqHvt<$7cb7=i_|LN`r^fyxtNN9pZ&4C+h)2AkRkk>9J|`e<(*3 zBBZn)UcY0cS}pCyUOrDrP{9U$#z)-F)wzik-8_*Dgtt-$qi&TBp;A9CYR)H#xey*7 zMZXw$OM}t8mTk|?njPB7W)xth=(Qd;Flp@^C@yB+MZh*XOx|MzoP%Q{Tkq zZ^=6?1TwVbdAWNrK92W{lL=$NgDuY0Y%Yl+5&g%GG(l&Q_67e=rR#zi16C}%jadRE zv}}pOf1M#Q1kij~RY>3K!o|N$*t~A`fW(>_^`P?-4)fNlo!fDRrdl+v8-`@s;Yr$6 zUD2>vd-PK0Dra#2vxQmnDY}hci|eVII+{SJ(?n;o%>cGlXGA|+BhsQ{#r%;)Neunz z6w4EQ&&kGRF1!34Dz~PQgX;RV7BaO%+^N>h)D~Jd_V@po_#zVfo5tdqu69{fM#{0# z=?qbxP$#9~L^F6}f@+K3Q7_l|wXY7@7q*Lm0J+Yb7&9JiVso^e8fe^67FF*eFpz2i zAD6Bokz7>f(6sb*wm3>>1&hyQXYbZGGgzxD<8!uom%eTs;wF;ky!EtI$%Zr8ZTW!? zUa$!a%d(yLz~*1t1#d@pT9y3hSj^L(3`t*nWSAvukB-ZYJarkxf|tY>uvhSfTs$w{ z#kGM0(X@;PR32krpIs9)>(_B)g{nbtfNJuUgDOeN>gi>jnz==#b6u^`W4!DjvOG6s zO08=I^TYSMdY|Cth2EuINpPgL4i4*A8Dj;BAHFd}1##QH3L8!qH4>qm>72}*lndPX zs%deXUqXr;UA~}gEap=jNy3HxwB~zcqm$q}--n&x3`9xE^h94`> z7h&Y*s#-j-v0TB)83^{M*$V?@W;MZ%zEZG2<+@U?1PGmOtVa`4yJo4DJzMqtIO=>b z2Z^i$St1j5dYseMmz_f8kXrPDGN{Q=0?W8NS2b~Kq^p`&vyEXndwpJyed5A9(~e~*-s%RUBx6j)yy z;C9UE!RdHX;qh=F;NsNV`a_&B!)M;+#@e;T#Fc(c!O$kR5n)vP4&Mh_A|UnWH1H9e z7b$Uocw)gQjO`uSGL;bqXd6ZYRAto}^%Osy=SWW;TnX-VYYT*(mO z3 ziv&c8#}y^U@@GFb04iWsqC!~;Us%&*3Pf}88OGI3@L(iiI|FXG-!OL9+(Lngz)JdB z1&Ge>-HN7Ij&J<+Ps-#viCpb87S(ZFim)2Kj@jnFO=QCgtw(vnF;$)hRICRvQ4rIwXe#o+ai)fq`M${nWZOpfAuXZHy7>rpH}|1a zL?%8)ee(01y0B_S@s=Q_tWZl3EcS`t#0$@-6rj%^4>EHspXpOr+@32YBKvv{G+ZL5 z1NLCOjAt;;bT=(r7D*3U$z)fF$wlGAOBXU*aBz-g*aFBRLcE9M-lmY-B1H5XF!g0> zr7QGW8!DOIjP};PWbT9ID5FR#U*&(un~Q>7|CgKHIsfU@I#2UR>~GsYJsf5ITKXV$ z*I#0JR_6al5Qji)$CX<^4;?JH()Tj6-H=MUXL*t|ScU{hYaJ2!Y`HU9?yGo5`Ld$( zk*m~l*fbRvoRH07?YQU?LcF#!uAfEpvR~0ZwlKbWY?ADOucM1~$#N>Lmyc5z578{P zkdB6EDd%a=3gibg3YMJidX_7=0ZFs0PoeCtd;nG7YM-KID0DW(6Y9h>Xk{OhXg4(0 zY5-dlA{tjDeZU!>Bk46g9&l;3$;X4gr7J)rW=?x%%%A}ZZWD!lFe{qJNR%xx!mJOS zLm#jo!^w8mynQ5TT=i~=d%l-uOfhVjW`>l}x9bkKq%ojMnYWGji5wt76vl7oVwk1- z3;w$I9K7Us!Iod@c8|4zeZ|1c%kq+!)$sM1eGkB_fg6?5t`XJPdResSQ6%aNfLPia zc`q2SYzH4ewi({(VwpfGhzFBPjp=-!hQV24DEJxg>2h(;X$ONM2ox^eOZXNkah5UQ z03QO((VvRzmZ>xX;L5?adhIo289^=gl@1ZEU(CJSw7NkS+ckANap~!*oB$5{!Kcs!hOp&#LNk4w6Dr1j3cEp@GbLU~28{~SX zq=j9a@aZCzf0r~Ve~I=18#aaov3@XIC=y0UdehWef~I9&Lt0I+ZY)Htxj7WnKfBJy zYVkYbsa#@^(T>|5-4-VJv#_+N zhFcQOm&glMZW*FUP>+UH&JuDw+zHha`I&*H|7y<{I_}$5eNL0Sq;qfq@4LlQQRYmi zvysZ_*KU#gj$UpjcH2n2G3O){!I46mPfV};vNttOY8g@ZWOLWOJle3uF)y=?yDw-g zSe{H_D?M4*P+$8Vc6)$zLz3sG{s6Ba!+x|9u4BwCBNd{3kRkb)xSzMV_i)x5P(>&{ zaT!`g2#pR5#3WXt+b51sAjc*YGrCIP3m77>H!~X9xHas}xg@J<#dOZyQ!8gYwb6y1 zPKr>nNpmbpatMU^iq<_<)~!}15iJyPF!3*E#0@-;AXAzn_>#p6Yceqsq3|B@jYF!5 zrP6pTX)8)q>DD7*mY9LzVh;$UF0tK@z)RT{eYISo<$aNzGRKc3N0Bz_cC!sW+Aex{jszU%R2Is|DcgF6-2+rWj?;4W z6Y;PhULm{w?!UKH57+yYRY+!NCl zL438olA;k`rP@Mk7L2~8z#PUg^!q%&(s>k{l06nD-v6NNwO08ar{uoGfj@pBnF+^> zS#WJNhpAWs8yuy(lN?XOMAWu^Lw&SargU z7b@ty(qvGsH3shGWBWTM2V-TWKWLr(M`eqhGTTAQVDgNgs<#d!x?0rfgPCvKXQweG8+&_``f+ zdTO)MsiF{*0OSKJ{)BYR)k<4z7byUWO0+h?*RxN*1U=&I1aA#)ZUSAEc{$Er+u7g2 z>z_%KX20b|5|Y6x@66xLF)wSha28AA%X(x0<+mU&`8&7-{KY}~6Wcp)R1Jz09lZMh zCyf@h)J}65Bu7=}$=WD2GsSE103oy}LYU5HuFm1!DqN6%#?tCA z(&=}i)m)23Pe3qikc?8f5>HDtW8j2nH8z-c$#VQMyE8QVF=t+@>{#lVJdWB*;((`b z{W$SoNPOVIVXkq3fUSY2%lANQ9qem4A<>A0ZI68SNlhTea8y2|JKPj2wMyR%W&;)P zGt(s(Q5&lYY!q&JgL;!v2dAr(g{vILdNJmw9!cazz3^p7Ft=)|URbk_Y|B~$`85A` zt+r6OU781;)wix$eb(hOXeZguOGWPds?^~`t>{gqitS&V12}po8r8GFULGFevVPPP zuV%eJEHN_HPkhwo#41y(Asc}$JMV9|tj!s0)Ebq6DHOZZufPn6#H_WvZJDBj9ihwe zpPK)|Pc9}+d@~nz@yhYR=q>i3EA$n&jhx9&oACv8Y&0ak$B3qm>> zD`nK5n8rh{sTr!9x)W^E%D1wHCD~K=)?FxYCSh|!ky~!0Q7CZEKiJRP>_DG#p?_s) z4poqk{%Zk}bX%dQLyXF1%RksjezNBkz#9#5GxEU$MT@s^5L9cyANc%c7?Rb}mzJL{ zsk~WMD>IcNRhTp~)=ru<&qk1fHHsqky7FZ%i$q7&M5~@duIH%?p&WLY)zpJo*6|n? z>*vNf$eNQq=YM_Xup1mLwDiszC%w`iDHMH+1xTtKUFgm7&Yv*j{PQ*EXf7<=D4G!B$&+qf8`^4zZ>r)589`HuPuPa+F zz+%k{S@NH#2fcIglO}ABp}>|#PP`<3(GU%=AWaptj$cSU)xvOApD@F^5roKXuGK;t z)S~w}bb+xn03NjIw(M^wgbm-Av==QlhgrA`F9K8lQwcat(yi;u3-(-JV~1NRP-tF4T3SfqV>uNELK>F#EAp!VFv0tK?Rb zP}P$}HScF9HA*5~B z;-0#bhbMTd!E}idTqk2iA6U+*J?XSJB9zRNg z3jo#5)t&oWg%)nQJPtHt!s&}o(th!OLFjhxi4U%Nq2_P}$?|OsHZ?2Nj@(*aA# zSC4iYO176^vSa|t!7Oqj8?h4d-E>0e5d-&q4nah?mTkWvYy3Cd^)0--BOCpMgHXa7 z+p5q1Fc^^y?sR$P}F2mSX<9A z$vEhI+j3_{*$^+vyiu!{HErd+fh%ptW*F zc-awaWbm=4h0#ypa7soBp>~gym2Ado*@RNZrk|2VBbhlLZ?<8=gM`3`V3VO9D@o%P zs}d2=NYX8c*K7Na%SuyM?!6+3;6XCX=cpVEg(p&;(Z?r0Sdmg#)uGC5z7RlE#To5! z`n*TRN7AiACL^+yM+YK3FrIG-GhPDJx@A3s98G1vGx zU$s||TIAiIoDj}xwcg6-nsdK=s9GXV+(O^(hjRHlQFg6>D3T<} z=%Ze=G{E1)xOvV3nmRaGB`Zzdd$6BN|3G`4emqeAmiH`zo&p)+z>-az0`S#CMb9RD zpLsn#8P+!73)FeE&JEnQ9ms4IHQWYHR%eHaqC>tVQf5%)v6ejE)uy(te8Z4L^(}zL z2eU5hdTLZ=w*cl~UjS@d+5x9t)8g3VM}1SbI}W77xZ@L1rX-FcbbNlqXSh3YgrZ;RhRA-1QLmV|yd;LjCFe^IW zWbv&48Uzp^GMMEm<0$L@_I$m+3s1W7+C&MayE{C~_QessbO^Bp-)&EfZ9$6pZ)9|& za2GG%WMuxsx&)@RLk@lqeTatob6!I^h`Q4N4}pEgCH^1`&a2flH!>^P)<(5MQn3Dn{}+(%96zm}Ni4~O;=_G?cEX&<& zM^y!3~y2-sZjfXPAVC&FH}YYvh^Tt#a*cK3@e)?j?UVoJEY}hqhOk3Wa858#`GANQ0EE6 zB8U9L5Gp8#?ll4IOcS%pY`e9wJ(bgMDMC7tLoX(B(DYa~raJsTY3p6JEMqXd5Ufg) zuk}5B6zKN^Xfvdx_`EchI6a8dBi?{`!uh|oCk(dw9v81v9DvSE zp4PGkj&oJwg2Df+M4zc1!jrQCv~2EI4rtsP61Br#3X|Q`M`5)Uyeb`X=c_KOB{Y<6 zs0&Vi8@;`j1h!qKB*B3M3@246?h;a@+$y=wy8XN@$RLwz-!ApR@p_fYs1Ka)wh}p| zUCPdsbMuV$1VsM{;qEVcC`kqjeI;$zS6=wK({O;ph%)1NA1e5%M_n$KxiaovrWwQg mi1UEy1c{1kFBTaN{8=b6GMZEQS$_~p+N6#`Id+y;X+;m@?EQEE From a6c7594520f48d94cabc0177c1d39352bc4800f0 Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Fri, 2 Sep 2022 11:36:41 +0000 Subject: [PATCH 619/966] chore: fix path to requirements.txt in release script (#1138) Source-Link: https://github.com/googleapis/synthtool/commit/703554a14c7479542335b62fa69279f93a9e38ec Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:94961fdc5c9ca6d13530a6a414a49d2f607203168215d074cdb0a1df9ec31c0b --- .../google-auth/.github/.OwlBot.lock.yaml | 3 +- packages/google-auth/.kokoro/release.sh | 2 +- packages/google-auth/.kokoro/requirements.txt | 32 ++++++++++++------- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/packages/google-auth/.github/.OwlBot.lock.yaml b/packages/google-auth/.github/.OwlBot.lock.yaml index 23e106b65770..2fa0f7c4fe15 100644 --- a/packages/google-auth/.github/.OwlBot.lock.yaml +++ b/packages/google-auth/.github/.OwlBot.lock.yaml @@ -13,5 +13,4 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:ce3c1686bc81145c81dd269bd12c4025c6b275b22d14641358827334fddb1d72 -# created: 2022-08-29T17:28:30.441852797Z + digest: sha256:94961fdc5c9ca6d13530a6a414a49d2f607203168215d074cdb0a1df9ec31c0b diff --git a/packages/google-auth/.kokoro/release.sh b/packages/google-auth/.kokoro/release.sh index 666e4317bb1b..ce4182c3444b 100755 --- a/packages/google-auth/.kokoro/release.sh +++ b/packages/google-auth/.kokoro/release.sh @@ -16,7 +16,7 @@ set -eo pipefail # Start the releasetool reporter -python3 -m pip install --require-hashes -r .kokoro/requirements.txt +python3 -m pip install --require-hashes -r github/google-auth-library-python/.kokoro/requirements.txt python3 -m releasetool publish-reporter-script > /tmp/publisher-script; source /tmp/publisher-script # Disable buffering, so that the logs stream through. diff --git a/packages/google-auth/.kokoro/requirements.txt b/packages/google-auth/.kokoro/requirements.txt index 4b29ef247bed..385f2d4d6106 100644 --- a/packages/google-auth/.kokoro/requirements.txt +++ b/packages/google-auth/.kokoro/requirements.txt @@ -100,9 +100,9 @@ click==8.0.4 \ # via # gcp-docuploader # gcp-releasetool -colorlog==6.6.0 \ - --hash=sha256:344f73204009e4c83c5b6beb00b3c45dc70fcdae3c80db919e0a4171d006fde8 \ - --hash=sha256:351c51e866c86c3217f08e4b067a7974a678be78f07f85fc2d55b8babde6d94e +colorlog==6.7.0 \ + --hash=sha256:0d33ca236784a1ba3ff9c532d4964126d8a2c44f1f0cb1d2b0728196f512f662 \ + --hash=sha256:bd94bd21c1e13fac7bd3153f4bc3a7dc0eb0974b8bc2fdf1a989e474f6e582e5 # via # gcp-docuploader # nox @@ -152,9 +152,9 @@ gcp-docuploader==0.6.3 \ --hash=sha256:ba8c9d76b3bbac54b0311c503a373b00edc2dc02d6d54ea9507045adb8e870f7 \ --hash=sha256:c0f5aaa82ce1854a386197e4e359b120ad6d4e57ae2c812fce42219a3288026b # via -r requirements.in -gcp-releasetool==1.8.6 \ - --hash=sha256:42e51ab8e2e789bc8e22a03c09352962cd3452951c801a2230d564816630304a \ - --hash=sha256:a3518b79d1b243c494eac392a01c7fd65187fd6d52602dcab9b529bc934d4da1 +gcp-releasetool==1.8.7 \ + --hash=sha256:3d2a67c9db39322194afb3b427e9cb0476ce8f2a04033695f0aeb63979fc2b37 \ + --hash=sha256:5e4d28f66e90780d77f3ecf1e9155852b0c3b13cbccb08ab07e66b2357c8da8d # via -r requirements.in google-api-core==2.8.2 \ --hash=sha256:06f7244c640322b508b125903bb5701bebabce8832f85aba9335ec00b3d02edc \ @@ -241,6 +241,10 @@ importlib-metadata==4.12.0 \ # via # -r requirements.in # twine +jaraco-classes==3.2.2 \ + --hash=sha256:6745f113b0b588239ceb49532aa09c3ebb947433ce311ef2f8e3ad64ebb74594 \ + --hash=sha256:e6ef6fd3fcf4579a7a019d87d1e56a883f4e4c35cfe925f86731abc58804e647 + # via keyring jeepney==0.8.0 \ --hash=sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806 \ --hash=sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755 @@ -251,9 +255,9 @@ jinja2==3.1.2 \ --hash=sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852 \ --hash=sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61 # via gcp-releasetool -keyring==23.8.2 \ - --hash=sha256:0d9973f8891850f1ade5f26aafd06bb16865fbbae3fc56b0defb6a14a2624003 \ - --hash=sha256:10d2a8639663fe2090705a00b8c47c687cacdf97598ea9c11456679fa974473a +keyring==23.9.0 \ + --hash=sha256:4c32a31174faaee48f43a7e2c7e9c3216ec5e95acf22a2bebfb4a1d05056ee44 \ + --hash=sha256:98f060ec95ada2ab910c195a2d4317be6ef87936a766b239c46aa3c7aac4f0db # via # gcp-releasetool # twine @@ -299,6 +303,10 @@ markupsafe==2.1.1 \ --hash=sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a \ --hash=sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7 # via jinja2 +more-itertools==8.14.0 \ + --hash=sha256:1bc4f91ee5b1b31ac7ceacc17c09befe6a40a503907baf9c839c229b5095cfd2 \ + --hash=sha256:c09443cd3d5438b8dafccd867a6bc1cb0894389e90cb53d227456b0b0bccb750 + # via jaraco-classes nox==2022.8.7 \ --hash=sha256:1b894940551dc5c389f9271d197ca5d655d40bdc6ccf93ed6880e4042760a34b \ --hash=sha256:96cca88779e08282a699d672258ec01eb7c792d35bbbf538c723172bce23212c @@ -440,9 +448,9 @@ urllib3==1.26.12 \ # via # requests # twine -virtualenv==20.16.3 \ - --hash=sha256:4193b7bc8a6cd23e4eb251ac64f29b4398ab2c233531e66e40b19a6b7b0d30c1 \ - --hash=sha256:d86ea0bb50e06252d79e6c241507cb904fcd66090c3271381372d6221a3970f9 +virtualenv==20.16.4 \ + --hash=sha256:014f766e4134d0008dcaa1f95bafa0fb0f575795d07cae50b1bee514185d6782 \ + --hash=sha256:035ed57acce4ac35c82c9d8802202b0e71adac011a511ff650cbcf9635006a22 # via nox webencodings==0.5.1 \ --hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \ From 1b3ce97df7d5d1dbc83b29176488299f6e7fad32 Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Fri, 2 Sep 2022 19:00:27 +0000 Subject: [PATCH 620/966] chore(python): exclude setup.py in renovate config (#1140) Source-Link: https://github.com/googleapis/synthtool/commit/56da63e80c384a871356d1ea6640802017f213b4 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:993a058718e84a82fda04c3177e58f0a43281a996c7c395e0a56ccc4d6d210d7 --- packages/google-auth/.github/.OwlBot.lock.yaml | 2 +- packages/google-auth/renovate.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/.github/.OwlBot.lock.yaml b/packages/google-auth/.github/.OwlBot.lock.yaml index 2fa0f7c4fe15..b8dcb4a4af99 100644 --- a/packages/google-auth/.github/.OwlBot.lock.yaml +++ b/packages/google-auth/.github/.OwlBot.lock.yaml @@ -13,4 +13,4 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:94961fdc5c9ca6d13530a6a414a49d2f607203168215d074cdb0a1df9ec31c0b + digest: sha256:993a058718e84a82fda04c3177e58f0a43281a996c7c395e0a56ccc4d6d210d7 diff --git a/packages/google-auth/renovate.json b/packages/google-auth/renovate.json index 566a70f3cc3c..39b2a0ec9296 100644 --- a/packages/google-auth/renovate.json +++ b/packages/google-auth/renovate.json @@ -5,7 +5,7 @@ ":preserveSemverRanges", ":disableDependencyDashboard" ], - "ignorePaths": [".pre-commit-config.yaml", ".kokoro/requirements.txt"], + "ignorePaths": [".pre-commit-config.yaml", ".kokoro/requirements.txt", "setup.py"], "pip_requirements": { "fileMatch": ["requirements-test.txt", "samples/[\\S/]*constraints.txt", "samples/[\\S/]*constraints-test.txt"] } From bf1b5aed29b4ddfc8594df0369a8b2ffe4aa8c5c Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Tue, 20 Sep 2022 01:50:14 +0000 Subject: [PATCH 621/966] fix: Update token refresh threshold from 20 seconds to 5 minutes (#1146) * fix: Update token refresh threshold from 20 seconds to 5 minutes * update rt --- packages/google-auth/google/auth/_helpers.py | 6 +----- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/google-auth/google/auth/_helpers.py b/packages/google-auth/google/auth/_helpers.py index 1b08ab87f83c..c70fd6a6bbdf 100644 --- a/packages/google-auth/google/auth/_helpers.py +++ b/packages/google-auth/google/auth/_helpers.py @@ -23,11 +23,7 @@ from six.moves import urllib -# Token server doesn't provide a new a token when doing refresh unless the -# token is expiring within 30 seconds, so refresh threshold should not be -# more than 30 seconds. Otherwise auth lib will send tons of refresh requests -# until 30 seconds before the expiration, and cause a spike of CPU usage. -REFRESH_THRESHOLD = datetime.timedelta(seconds=20) +REFRESH_THRESHOLD = datetime.timedelta(seconds=300) def copy_docstring(source_class): diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 30a208ef5e1e6a09e1110541a05998d1db2559d6..9d3f4bcfdeb625d4d3acbba9d2eefe93d1c2e031 100644 GIT binary patch literal 10324 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 literal 10324 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 From 8a82895f7ec235ad8960c38b3e8cbc49a461a188 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 19 Sep 2022 19:10:41 -0700 Subject: [PATCH 622/966] chore(main): release 2.11.1 (#1112) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 15 +++++++++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 7b1d296cc0e5..d8d469e8053a 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,21 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.11.1](https://github.com/googleapis/google-auth-library-python/compare/v2.11.0...v2.11.1) (2022-09-20) + + +### Bug Fixes + +* Fix socket leak in impersonated_credentials ([#1123](https://github.com/googleapis/google-auth-library-python/issues/1123)) ([b1eb467](https://github.com/googleapis/google-auth-library-python/commit/b1eb467f50f0c080e89a122426061b28f0be0567)), closes [#1122](https://github.com/googleapis/google-auth-library-python/issues/1122) +* Make pluggable auth tests work in all environments ([#1114](https://github.com/googleapis/google-auth-library-python/issues/1114)) ([bb5c979](https://github.com/googleapis/google-auth-library-python/commit/bb5c9791c64e2472a90ba7191f79f4c5fedb2538)) +* Skip oauth2client adapter tests if oauth2client is not installed ([#1132](https://github.com/googleapis/google-auth-library-python/issues/1132)) ([d15092f](https://github.com/googleapis/google-auth-library-python/commit/d15092ff8b66b3039641d482a0debafde4ba0077)) +* Update token refresh threshold from 20 seconds to 5 minutes ([#1146](https://github.com/googleapis/google-auth-library-python/issues/1146)) ([261a561](https://github.com/googleapis/google-auth-library-python/commit/261a56138fba33ff7d898ab5907a6098125fefef)) + + +### Documentation + +* **samples:** Add auth samples and tests ([#1102](https://github.com/googleapis/google-auth-library-python/issues/1102)) ([ac87520](https://github.com/googleapis/google-auth-library-python/commit/ac875201bc8ba5d638a9eafcd3ccfdeb73a2f0ec)) + ## [2.11.0](https://github.com/googleapis/google-auth-library-python/compare/v2.10.0...v2.11.0) (2022-08-18) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 10a1c7fd97e2..83a562d6e37f 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.11.0" +__version__ = "2.11.1" From 56743b1bc6a9959bf4748fa6c723e0624bb1b0ad Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Tue, 20 Sep 2022 20:05:20 +0000 Subject: [PATCH 623/966] feat: Introduce environment variable to set refresh threshold (#1147) --- packages/google-auth/google/auth/_helpers.py | 21 ++++++++++++++++++- .../google-auth/google/auth/credentials.py | 2 +- .../google/auth/environment_vars.py | 4 ++++ .../google-auth/google/oauth2/credentials.py | 4 ++-- .../tests/compute_engine/test_credentials.py | 4 ++-- .../tests/oauth2/test_credentials.py | 14 ++++++------- packages/google-auth/tests/test__helpers.py | 11 ++++++++++ .../google-auth/tests/test_credentials.py | 2 +- packages/google-auth/tests/test_downscoped.py | 2 +- .../tests/test_external_account.py | 4 ++-- packages/google-auth/tests/test_iam.py | 2 +- .../tests/test_impersonated_credentials.py | 6 +++--- .../google-auth/tests/transport/test_grpc.py | 2 +- .../oauth2/test_credentials_async.py | 8 +++---- .../tests_async/test_credentials_async.py | 2 +- 15 files changed, 61 insertions(+), 27 deletions(-) diff --git a/packages/google-auth/google/auth/_helpers.py b/packages/google-auth/google/auth/_helpers.py index c70fd6a6bbdf..d8db099cdeca 100644 --- a/packages/google-auth/google/auth/_helpers.py +++ b/packages/google-auth/google/auth/_helpers.py @@ -17,13 +17,32 @@ import base64 import calendar import datetime +import os import sys import six from six.moves import urllib +from google.auth import environment_vars -REFRESH_THRESHOLD = datetime.timedelta(seconds=300) + +# Token server doesn't provide a new a token when doing refresh unless the +# token is expiring within 30 seconds, so refresh threshold should not be +# more than 30 seconds. Otherwise auth lib will send tons of refresh requests +# until 30 seconds before the expiration, and cause a spike of CPU usage. +REFRESH_THRESHOLD = datetime.timedelta(seconds=20) + + +def get_refresh_threshold(): + env_threshold = os.environ.get( + environment_vars.GOOGLE_CLOUD_TOKEN_REFRESH_THRESHOLD + ) + + if env_threshold is not None: + # If the value is not an int, we can throw exception and let it bubble up + return datetime.timedelta(seconds=int(env_threshold)) + + return REFRESH_THRESHOLD def copy_docstring(source_class): diff --git a/packages/google-auth/google/auth/credentials.py b/packages/google-auth/google/auth/credentials.py index 004fde9c2f37..a633cd35f063 100644 --- a/packages/google-auth/google/auth/credentials.py +++ b/packages/google-auth/google/auth/credentials.py @@ -65,7 +65,7 @@ def expired(self): # Remove some threshold from expiry to err on the side of reporting # expiration early so that we avoid the 401-refresh-retry loop. - skewed_expiry = self.expiry - _helpers.REFRESH_THRESHOLD + skewed_expiry = self.expiry - _helpers.get_refresh_threshold() return _helpers.utcnow() >= skewed_expiry @property diff --git a/packages/google-auth/google/auth/environment_vars.py b/packages/google-auth/google/auth/environment_vars.py index c076dc59da1c..6fd14ef40fb0 100644 --- a/packages/google-auth/google/auth/environment_vars.py +++ b/packages/google-auth/google/auth/environment_vars.py @@ -29,6 +29,10 @@ situations (such as Google App Engine). """ +GOOGLE_CLOUD_TOKEN_REFRESH_THRESHOLD = "GOOGLE_CLOUD_TOKEN_REFRESH_THRESHOLD" +"""Environment variable defines a time in seconds. If a token is going to +expire within this time, then it will be refreshed""" + CREDENTIALS = "GOOGLE_APPLICATION_CREDENTIALS" """Environment variable defining the location of Google application default credentials.""" diff --git a/packages/google-auth/google/oauth2/credentials.py b/packages/google-auth/google/oauth2/credentials.py index 4cc502fea49d..ee6556919439 100644 --- a/packages/google-auth/google/oauth2/credentials.py +++ b/packages/google-auth/google/oauth2/credentials.py @@ -272,7 +272,7 @@ def refresh(self, request): raise exceptions.RefreshError( "The refresh_handler returned expiry is not a datetime object." ) - if _helpers.utcnow() >= expiry - _helpers.REFRESH_THRESHOLD: + if _helpers.utcnow() >= expiry - _helpers.get_refresh_threshold(): raise exceptions.RefreshError( "The credentials returned by the refresh_handler are " "already expired." @@ -361,7 +361,7 @@ def from_authorized_user_info(cls, info, scopes=None): expiry.rstrip("Z").split(".")[0], "%Y-%m-%dT%H:%M:%S" ) else: - expiry = _helpers.utcnow() - _helpers.REFRESH_THRESHOLD + expiry = _helpers.utcnow() - _helpers.get_refresh_threshold() # process scopes, which needs to be a seq if scopes is None and "scopes" in info: diff --git a/packages/google-auth/tests/compute_engine/test_credentials.py b/packages/google-auth/tests/compute_engine/test_credentials.py index ff01720c460d..a5d829c7d737 100644 --- a/packages/google-auth/tests/compute_engine/test_credentials.py +++ b/packages/google-auth/tests/compute_engine/test_credentials.py @@ -64,7 +64,7 @@ def test_default_state(self): @mock.patch( "google.auth._helpers.utcnow", - return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD, + return_value=datetime.datetime.min + _helpers.get_refresh_threshold(), ) @mock.patch("google.auth.compute_engine._metadata.get", autospec=True) def test_refresh_success(self, get, utcnow): @@ -98,7 +98,7 @@ def test_refresh_success(self, get, utcnow): @mock.patch( "google.auth._helpers.utcnow", - return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD, + return_value=datetime.datetime.min + _helpers.get_refresh_threshold(), ) @mock.patch("google.auth.compute_engine._metadata.get", autospec=True) def test_refresh_success_with_scopes(self, get, utcnow): diff --git a/packages/google-auth/tests/oauth2/test_credentials.py b/packages/google-auth/tests/oauth2/test_credentials.py index e5f71def04cb..a2a641927a4a 100644 --- a/packages/google-auth/tests/oauth2/test_credentials.py +++ b/packages/google-auth/tests/oauth2/test_credentials.py @@ -115,7 +115,7 @@ def test_invalid_refresh_handler(self): @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", - return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD, + return_value=datetime.datetime.min + _helpers.get_refresh_threshold(), ) def test_refresh_success(self, unused_utcnow, refresh_grant): token = "token" @@ -175,7 +175,7 @@ def test_refresh_no_refresh_token(self): @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", - return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD, + return_value=datetime.datetime.min + _helpers.get_refresh_threshold(), ) def test_refresh_with_refresh_token_and_refresh_handler( self, unused_utcnow, refresh_grant @@ -361,7 +361,7 @@ def test_refresh_with_refresh_handler_invalid_expiry(self): @mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) def test_refresh_with_refresh_handler_expired_token(self, unused_utcnow): - expected_expiry = datetime.datetime.min + _helpers.REFRESH_THRESHOLD + expected_expiry = datetime.datetime.min + _helpers.get_refresh_threshold() # Simulate refresh handler returns an expired token. refresh_handler = mock.Mock(return_value=("TOKEN", expected_expiry)) scopes = ["email", "profile"] @@ -391,7 +391,7 @@ def test_refresh_with_refresh_handler_expired_token(self, unused_utcnow): @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", - return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD, + return_value=datetime.datetime.min + _helpers.get_refresh_threshold(), ) def test_credentials_with_scopes_requested_refresh_success( self, unused_utcnow, refresh_grant @@ -457,7 +457,7 @@ def test_credentials_with_scopes_requested_refresh_success( @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", - return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD, + return_value=datetime.datetime.min + _helpers.get_refresh_threshold(), ) def test_credentials_with_only_default_scopes_requested( self, unused_utcnow, refresh_grant @@ -521,7 +521,7 @@ def test_credentials_with_only_default_scopes_requested( @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", - return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD, + return_value=datetime.datetime.min + _helpers.get_refresh_threshold(), ) def test_credentials_with_scopes_returned_refresh_success( self, unused_utcnow, refresh_grant @@ -588,7 +588,7 @@ def test_credentials_with_scopes_returned_refresh_success( @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", - return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD, + return_value=datetime.datetime.min + _helpers.get_refresh_threshold(), ) def test_credentials_with_scopes_refresh_failure_raises_refresh_error( self, unused_utcnow, refresh_grant diff --git a/packages/google-auth/tests/test__helpers.py b/packages/google-auth/tests/test__helpers.py index 8c71f3e51f4b..a14030292f6a 100644 --- a/packages/google-auth/tests/test__helpers.py +++ b/packages/google-auth/tests/test__helpers.py @@ -13,11 +13,14 @@ # limitations under the License. import datetime +import os +import mock import pytest # type: ignore from six.moves import urllib from google.auth import _helpers +from google.auth import environment_vars class SourceClass(object): @@ -25,6 +28,14 @@ def func(self): # pragma: NO COVER """example docstring""" +@mock.patch.dict(os.environ) +def test_get_refresh_threshold(): + assert _helpers.get_refresh_threshold() == _helpers.REFRESH_THRESHOLD + + os.environ[environment_vars.GOOGLE_CLOUD_TOKEN_REFRESH_THRESHOLD] = "300" + assert _helpers.get_refresh_threshold() == datetime.timedelta(seconds=300) + + def test_copy_docstring_success(): def func(): # pragma: NO COVER pass diff --git a/packages/google-auth/tests/test_credentials.py b/packages/google-auth/tests/test_credentials.py index da074143a00d..5a92b819e276 100644 --- a/packages/google-auth/tests/test_credentials.py +++ b/packages/google-auth/tests/test_credentials.py @@ -47,7 +47,7 @@ def test_expired_and_valid(): # accomodation. These credentials should be valid. credentials.expiry = ( datetime.datetime.utcnow() - + _helpers.REFRESH_THRESHOLD + + _helpers.get_refresh_threshold() + datetime.timedelta(seconds=1) ) diff --git a/packages/google-auth/tests/test_downscoped.py b/packages/google-auth/tests/test_downscoped.py index 7d0768a18086..82fd80b97a3e 100644 --- a/packages/google-auth/tests/test_downscoped.py +++ b/packages/google-auth/tests/test_downscoped.py @@ -670,7 +670,7 @@ def test_before_request_expired(self, utcnow): # accommodation. These credentials should be valid. credentials.expiry = ( datetime.datetime.min - + _helpers.REFRESH_THRESHOLD + + _helpers.get_refresh_threshold() + datetime.timedelta(seconds=1) ) diff --git a/packages/google-auth/tests/test_external_account.py b/packages/google-auth/tests/test_external_account.py index a289b5df918e..b8912cb72479 100644 --- a/packages/google-auth/tests/test_external_account.py +++ b/packages/google-auth/tests/test_external_account.py @@ -1457,7 +1457,7 @@ def test_before_request_expired(self, utcnow): # accomodation. These credentials should be valid. credentials.expiry = ( datetime.datetime.min - + _helpers.REFRESH_THRESHOLD + + _helpers.get_refresh_threshold() + datetime.timedelta(seconds=1) ) @@ -1510,7 +1510,7 @@ def test_before_request_impersonation_expired(self, utcnow): # accomodation. These credentials should be valid. credentials.expiry = ( datetime.datetime.min - + _helpers.REFRESH_THRESHOLD + + _helpers.get_refresh_threshold() + datetime.timedelta(seconds=1) ) diff --git a/packages/google-auth/tests/test_iam.py b/packages/google-auth/tests/test_iam.py index ae482765b475..ce499fe71847 100644 --- a/packages/google-auth/tests/test_iam.py +++ b/packages/google-auth/tests/test_iam.py @@ -45,7 +45,7 @@ def __init__(self): super(CredentialsImpl, self).__init__() self.token = "token" # Force refresh - self.expiry = datetime.datetime.min + _helpers.REFRESH_THRESHOLD + self.expiry = datetime.datetime.min + _helpers.get_refresh_threshold() def refresh(self, request): pass diff --git a/packages/google-auth/tests/test_impersonated_credentials.py b/packages/google-auth/tests/test_impersonated_credentials.py index 0e4dc08d7cf3..167da0afe314 100644 --- a/packages/google-auth/tests/test_impersonated_credentials.py +++ b/packages/google-auth/tests/test_impersonated_credentials.py @@ -216,11 +216,11 @@ def test_refresh_source_credentials(self, time_skew): credentials = self.make_credentials(lifetime=None) # Source credentials is refreshed only if it is expired within - # _helpers.REFRESH_THRESHOLD from now. We add a time_skew to the expiry, so + # _helpers.get_refresh_threshold() from now. We add a time_skew to the expiry, so # source credentials is refreshed only if time_skew <= 0. credentials._source_credentials.expiry = ( _helpers.utcnow() - + _helpers.REFRESH_THRESHOLD + + _helpers.get_refresh_threshold() + datetime.timedelta(seconds=time_skew) ) credentials._source_credentials.token = "Token" @@ -243,7 +243,7 @@ def test_refresh_source_credentials(self, time_skew): assert not credentials.expired # Source credentials is refreshed only if it is expired within - # _helpers.REFRESH_THRESHOLD + # _helpers.get_refresh_threshold() if time_skew > 0: source_cred_refresh.assert_not_called() else: diff --git a/packages/google-auth/tests/transport/test_grpc.py b/packages/google-auth/tests/transport/test_grpc.py index f62ab0eae760..45bb9b91792b 100644 --- a/packages/google-auth/tests/transport/test_grpc.py +++ b/packages/google-auth/tests/transport/test_grpc.py @@ -80,7 +80,7 @@ def test_call_no_refresh(self): def test_call_refresh(self): credentials = CredentialsStub() - credentials.expiry = datetime.datetime.min + _helpers.REFRESH_THRESHOLD + credentials.expiry = datetime.datetime.min + _helpers.get_refresh_threshold() request = mock.create_autospec(transport.Request) plugin = google.auth.transport.grpc.AuthMetadataPlugin(credentials, request) diff --git a/packages/google-auth/tests_async/oauth2/test_credentials_async.py b/packages/google-auth/tests_async/oauth2/test_credentials_async.py index a328cc3cba6d..d56d58df6007 100644 --- a/packages/google-auth/tests_async/oauth2/test_credentials_async.py +++ b/packages/google-auth/tests_async/oauth2/test_credentials_async.py @@ -62,7 +62,7 @@ def test_default_state(self): @mock.patch("google.oauth2._reauth_async.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", - return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD, + return_value=datetime.datetime.min + _helpers.get_refresh_threshold(), ) @pytest.mark.asyncio async def test_refresh_success(self, unused_utcnow, refresh_grant): @@ -124,7 +124,7 @@ async def test_refresh_no_refresh_token(self): @mock.patch("google.oauth2._reauth_async.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", - return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD, + return_value=datetime.datetime.min + _helpers.get_refresh_threshold(), ) @pytest.mark.asyncio async def test_credentials_with_scopes_requested_refresh_success( @@ -188,7 +188,7 @@ async def test_credentials_with_scopes_requested_refresh_success( @mock.patch("google.oauth2._reauth_async.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", - return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD, + return_value=datetime.datetime.min + _helpers.get_refresh_threshold(), ) @pytest.mark.asyncio async def test_credentials_with_scopes_returned_refresh_success( @@ -251,7 +251,7 @@ async def test_credentials_with_scopes_returned_refresh_success( @mock.patch("google.oauth2._reauth_async.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", - return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD, + return_value=datetime.datetime.min + _helpers.get_refresh_threshold(), ) @pytest.mark.asyncio async def test_credentials_with_scopes_refresh_failure_raises_refresh_error( diff --git a/packages/google-auth/tests_async/test_credentials_async.py b/packages/google-auth/tests_async/test_credentials_async.py index 9db5fc9ae754..71c70a52df97 100644 --- a/packages/google-auth/tests_async/test_credentials_async.py +++ b/packages/google-auth/tests_async/test_credentials_async.py @@ -47,7 +47,7 @@ def test_expired_and_valid(): # accomodation. These credentials should be valid. credentials.expiry = ( datetime.datetime.utcnow() - + _helpers.REFRESH_THRESHOLD + + _helpers.get_refresh_threshold() + datetime.timedelta(seconds=1) ) From 7a7c2f35d34c1dc55f28ddd30aa4dfa26438673d Mon Sep 17 00:00:00 2001 From: Carl Lundin <108372512+clundin25@users.noreply.github.com> Date: Tue, 20 Sep 2022 23:01:47 +0000 Subject: [PATCH 624/966] fix: Modify RefreshError exception to use gcloud ADC command. (#1149) --- packages/google-auth/google/oauth2/_reauth_async.py | 2 +- packages/google-auth/google/oauth2/reauth.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/google/oauth2/_reauth_async.py b/packages/google-auth/google/oauth2/_reauth_async.py index 0276ddd0b217..30b0b0b1ec00 100644 --- a/packages/google-auth/google/oauth2/_reauth_async.py +++ b/packages/google-auth/google/oauth2/_reauth_async.py @@ -307,7 +307,7 @@ async def refresh_grant( ): if not enable_reauth_refresh: raise exceptions.RefreshError( - "Reauthentication is needed. Please run `gcloud auth login --update-adc` to reauthenticate." + "Reauthentication is needed. Please run `gcloud auth application-default login` to reauthenticate." ) rapt_token = await get_rapt_token( diff --git a/packages/google-auth/google/oauth2/reauth.py b/packages/google-auth/google/oauth2/reauth.py index cbf1d7f09394..2c32bda2ad80 100644 --- a/packages/google-auth/google/oauth2/reauth.py +++ b/packages/google-auth/google/oauth2/reauth.py @@ -332,7 +332,7 @@ def refresh_grant( ): if not enable_reauth_refresh: raise exceptions.RefreshError( - "Reauthentication is needed. Please run `gcloud auth login --update-adc` to reauthenticate." + "Reauthentication is needed. Please run `gcloud auth application-default login` to reauthenticate." ) rapt_token = get_rapt_token( From 2776f69be27ed8444d1a96956b0b90a87f89bd7b Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Thu, 22 Sep 2022 04:41:55 +0000 Subject: [PATCH 625/966] Revert "feat: Introduce environment variable to set refresh threshold" (#1150) * Revert "feat: Introduce environment variable to set refresh threshold (#1147)" This reverts commit 055f15544a3b049a20875f16c8771af068f2f0e1. * refresh token --- packages/google-auth/google/auth/_helpers.py | 21 +----------------- .../google-auth/google/auth/credentials.py | 2 +- .../google/auth/environment_vars.py | 4 ---- .../google-auth/google/oauth2/credentials.py | 4 ++-- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../tests/compute_engine/test_credentials.py | 4 ++-- .../tests/oauth2/test_credentials.py | 14 ++++++------ packages/google-auth/tests/test__helpers.py | 11 --------- .../google-auth/tests/test_credentials.py | 2 +- packages/google-auth/tests/test_downscoped.py | 2 +- .../tests/test_external_account.py | 4 ++-- packages/google-auth/tests/test_iam.py | 2 +- .../tests/test_impersonated_credentials.py | 6 ++--- .../google-auth/tests/transport/test_grpc.py | 2 +- .../oauth2/test_credentials_async.py | 8 +++---- .../tests_async/test_credentials_async.py | 2 +- 16 files changed, 27 insertions(+), 61 deletions(-) diff --git a/packages/google-auth/google/auth/_helpers.py b/packages/google-auth/google/auth/_helpers.py index d8db099cdeca..c70fd6a6bbdf 100644 --- a/packages/google-auth/google/auth/_helpers.py +++ b/packages/google-auth/google/auth/_helpers.py @@ -17,32 +17,13 @@ import base64 import calendar import datetime -import os import sys import six from six.moves import urllib -from google.auth import environment_vars - -# Token server doesn't provide a new a token when doing refresh unless the -# token is expiring within 30 seconds, so refresh threshold should not be -# more than 30 seconds. Otherwise auth lib will send tons of refresh requests -# until 30 seconds before the expiration, and cause a spike of CPU usage. -REFRESH_THRESHOLD = datetime.timedelta(seconds=20) - - -def get_refresh_threshold(): - env_threshold = os.environ.get( - environment_vars.GOOGLE_CLOUD_TOKEN_REFRESH_THRESHOLD - ) - - if env_threshold is not None: - # If the value is not an int, we can throw exception and let it bubble up - return datetime.timedelta(seconds=int(env_threshold)) - - return REFRESH_THRESHOLD +REFRESH_THRESHOLD = datetime.timedelta(seconds=300) def copy_docstring(source_class): diff --git a/packages/google-auth/google/auth/credentials.py b/packages/google-auth/google/auth/credentials.py index a633cd35f063..004fde9c2f37 100644 --- a/packages/google-auth/google/auth/credentials.py +++ b/packages/google-auth/google/auth/credentials.py @@ -65,7 +65,7 @@ def expired(self): # Remove some threshold from expiry to err on the side of reporting # expiration early so that we avoid the 401-refresh-retry loop. - skewed_expiry = self.expiry - _helpers.get_refresh_threshold() + skewed_expiry = self.expiry - _helpers.REFRESH_THRESHOLD return _helpers.utcnow() >= skewed_expiry @property diff --git a/packages/google-auth/google/auth/environment_vars.py b/packages/google-auth/google/auth/environment_vars.py index 6fd14ef40fb0..c076dc59da1c 100644 --- a/packages/google-auth/google/auth/environment_vars.py +++ b/packages/google-auth/google/auth/environment_vars.py @@ -29,10 +29,6 @@ situations (such as Google App Engine). """ -GOOGLE_CLOUD_TOKEN_REFRESH_THRESHOLD = "GOOGLE_CLOUD_TOKEN_REFRESH_THRESHOLD" -"""Environment variable defines a time in seconds. If a token is going to -expire within this time, then it will be refreshed""" - CREDENTIALS = "GOOGLE_APPLICATION_CREDENTIALS" """Environment variable defining the location of Google application default credentials.""" diff --git a/packages/google-auth/google/oauth2/credentials.py b/packages/google-auth/google/oauth2/credentials.py index ee6556919439..4cc502fea49d 100644 --- a/packages/google-auth/google/oauth2/credentials.py +++ b/packages/google-auth/google/oauth2/credentials.py @@ -272,7 +272,7 @@ def refresh(self, request): raise exceptions.RefreshError( "The refresh_handler returned expiry is not a datetime object." ) - if _helpers.utcnow() >= expiry - _helpers.get_refresh_threshold(): + if _helpers.utcnow() >= expiry - _helpers.REFRESH_THRESHOLD: raise exceptions.RefreshError( "The credentials returned by the refresh_handler are " "already expired." @@ -361,7 +361,7 @@ def from_authorized_user_info(cls, info, scopes=None): expiry.rstrip("Z").split(".")[0], "%Y-%m-%dT%H:%M:%S" ) else: - expiry = _helpers.utcnow() - _helpers.get_refresh_threshold() + expiry = _helpers.utcnow() - _helpers.REFRESH_THRESHOLD # process scopes, which needs to be a seq if scopes is None and "scopes" in info: diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 9d3f4bcfdeb625d4d3acbba9d2eefe93d1c2e031..f32ace9608743fbfa7e1782e6af1b68a45676dfc 100644 GIT binary patch literal 10324 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 diff --git a/packages/google-auth/tests/compute_engine/test_credentials.py b/packages/google-auth/tests/compute_engine/test_credentials.py index a5d829c7d737..ff01720c460d 100644 --- a/packages/google-auth/tests/compute_engine/test_credentials.py +++ b/packages/google-auth/tests/compute_engine/test_credentials.py @@ -64,7 +64,7 @@ def test_default_state(self): @mock.patch( "google.auth._helpers.utcnow", - return_value=datetime.datetime.min + _helpers.get_refresh_threshold(), + return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD, ) @mock.patch("google.auth.compute_engine._metadata.get", autospec=True) def test_refresh_success(self, get, utcnow): @@ -98,7 +98,7 @@ def test_refresh_success(self, get, utcnow): @mock.patch( "google.auth._helpers.utcnow", - return_value=datetime.datetime.min + _helpers.get_refresh_threshold(), + return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD, ) @mock.patch("google.auth.compute_engine._metadata.get", autospec=True) def test_refresh_success_with_scopes(self, get, utcnow): diff --git a/packages/google-auth/tests/oauth2/test_credentials.py b/packages/google-auth/tests/oauth2/test_credentials.py index a2a641927a4a..e5f71def04cb 100644 --- a/packages/google-auth/tests/oauth2/test_credentials.py +++ b/packages/google-auth/tests/oauth2/test_credentials.py @@ -115,7 +115,7 @@ def test_invalid_refresh_handler(self): @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", - return_value=datetime.datetime.min + _helpers.get_refresh_threshold(), + return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD, ) def test_refresh_success(self, unused_utcnow, refresh_grant): token = "token" @@ -175,7 +175,7 @@ def test_refresh_no_refresh_token(self): @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", - return_value=datetime.datetime.min + _helpers.get_refresh_threshold(), + return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD, ) def test_refresh_with_refresh_token_and_refresh_handler( self, unused_utcnow, refresh_grant @@ -361,7 +361,7 @@ def test_refresh_with_refresh_handler_invalid_expiry(self): @mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) def test_refresh_with_refresh_handler_expired_token(self, unused_utcnow): - expected_expiry = datetime.datetime.min + _helpers.get_refresh_threshold() + expected_expiry = datetime.datetime.min + _helpers.REFRESH_THRESHOLD # Simulate refresh handler returns an expired token. refresh_handler = mock.Mock(return_value=("TOKEN", expected_expiry)) scopes = ["email", "profile"] @@ -391,7 +391,7 @@ def test_refresh_with_refresh_handler_expired_token(self, unused_utcnow): @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", - return_value=datetime.datetime.min + _helpers.get_refresh_threshold(), + return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD, ) def test_credentials_with_scopes_requested_refresh_success( self, unused_utcnow, refresh_grant @@ -457,7 +457,7 @@ def test_credentials_with_scopes_requested_refresh_success( @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", - return_value=datetime.datetime.min + _helpers.get_refresh_threshold(), + return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD, ) def test_credentials_with_only_default_scopes_requested( self, unused_utcnow, refresh_grant @@ -521,7 +521,7 @@ def test_credentials_with_only_default_scopes_requested( @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", - return_value=datetime.datetime.min + _helpers.get_refresh_threshold(), + return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD, ) def test_credentials_with_scopes_returned_refresh_success( self, unused_utcnow, refresh_grant @@ -588,7 +588,7 @@ def test_credentials_with_scopes_returned_refresh_success( @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", - return_value=datetime.datetime.min + _helpers.get_refresh_threshold(), + return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD, ) def test_credentials_with_scopes_refresh_failure_raises_refresh_error( self, unused_utcnow, refresh_grant diff --git a/packages/google-auth/tests/test__helpers.py b/packages/google-auth/tests/test__helpers.py index a14030292f6a..8c71f3e51f4b 100644 --- a/packages/google-auth/tests/test__helpers.py +++ b/packages/google-auth/tests/test__helpers.py @@ -13,14 +13,11 @@ # limitations under the License. import datetime -import os -import mock import pytest # type: ignore from six.moves import urllib from google.auth import _helpers -from google.auth import environment_vars class SourceClass(object): @@ -28,14 +25,6 @@ def func(self): # pragma: NO COVER """example docstring""" -@mock.patch.dict(os.environ) -def test_get_refresh_threshold(): - assert _helpers.get_refresh_threshold() == _helpers.REFRESH_THRESHOLD - - os.environ[environment_vars.GOOGLE_CLOUD_TOKEN_REFRESH_THRESHOLD] = "300" - assert _helpers.get_refresh_threshold() == datetime.timedelta(seconds=300) - - def test_copy_docstring_success(): def func(): # pragma: NO COVER pass diff --git a/packages/google-auth/tests/test_credentials.py b/packages/google-auth/tests/test_credentials.py index 5a92b819e276..da074143a00d 100644 --- a/packages/google-auth/tests/test_credentials.py +++ b/packages/google-auth/tests/test_credentials.py @@ -47,7 +47,7 @@ def test_expired_and_valid(): # accomodation. These credentials should be valid. credentials.expiry = ( datetime.datetime.utcnow() - + _helpers.get_refresh_threshold() + + _helpers.REFRESH_THRESHOLD + datetime.timedelta(seconds=1) ) diff --git a/packages/google-auth/tests/test_downscoped.py b/packages/google-auth/tests/test_downscoped.py index 82fd80b97a3e..7d0768a18086 100644 --- a/packages/google-auth/tests/test_downscoped.py +++ b/packages/google-auth/tests/test_downscoped.py @@ -670,7 +670,7 @@ def test_before_request_expired(self, utcnow): # accommodation. These credentials should be valid. credentials.expiry = ( datetime.datetime.min - + _helpers.get_refresh_threshold() + + _helpers.REFRESH_THRESHOLD + datetime.timedelta(seconds=1) ) diff --git a/packages/google-auth/tests/test_external_account.py b/packages/google-auth/tests/test_external_account.py index b8912cb72479..a289b5df918e 100644 --- a/packages/google-auth/tests/test_external_account.py +++ b/packages/google-auth/tests/test_external_account.py @@ -1457,7 +1457,7 @@ def test_before_request_expired(self, utcnow): # accomodation. These credentials should be valid. credentials.expiry = ( datetime.datetime.min - + _helpers.get_refresh_threshold() + + _helpers.REFRESH_THRESHOLD + datetime.timedelta(seconds=1) ) @@ -1510,7 +1510,7 @@ def test_before_request_impersonation_expired(self, utcnow): # accomodation. These credentials should be valid. credentials.expiry = ( datetime.datetime.min - + _helpers.get_refresh_threshold() + + _helpers.REFRESH_THRESHOLD + datetime.timedelta(seconds=1) ) diff --git a/packages/google-auth/tests/test_iam.py b/packages/google-auth/tests/test_iam.py index ce499fe71847..ae482765b475 100644 --- a/packages/google-auth/tests/test_iam.py +++ b/packages/google-auth/tests/test_iam.py @@ -45,7 +45,7 @@ def __init__(self): super(CredentialsImpl, self).__init__() self.token = "token" # Force refresh - self.expiry = datetime.datetime.min + _helpers.get_refresh_threshold() + self.expiry = datetime.datetime.min + _helpers.REFRESH_THRESHOLD def refresh(self, request): pass diff --git a/packages/google-auth/tests/test_impersonated_credentials.py b/packages/google-auth/tests/test_impersonated_credentials.py index 167da0afe314..0e4dc08d7cf3 100644 --- a/packages/google-auth/tests/test_impersonated_credentials.py +++ b/packages/google-auth/tests/test_impersonated_credentials.py @@ -216,11 +216,11 @@ def test_refresh_source_credentials(self, time_skew): credentials = self.make_credentials(lifetime=None) # Source credentials is refreshed only if it is expired within - # _helpers.get_refresh_threshold() from now. We add a time_skew to the expiry, so + # _helpers.REFRESH_THRESHOLD from now. We add a time_skew to the expiry, so # source credentials is refreshed only if time_skew <= 0. credentials._source_credentials.expiry = ( _helpers.utcnow() - + _helpers.get_refresh_threshold() + + _helpers.REFRESH_THRESHOLD + datetime.timedelta(seconds=time_skew) ) credentials._source_credentials.token = "Token" @@ -243,7 +243,7 @@ def test_refresh_source_credentials(self, time_skew): assert not credentials.expired # Source credentials is refreshed only if it is expired within - # _helpers.get_refresh_threshold() + # _helpers.REFRESH_THRESHOLD if time_skew > 0: source_cred_refresh.assert_not_called() else: diff --git a/packages/google-auth/tests/transport/test_grpc.py b/packages/google-auth/tests/transport/test_grpc.py index 45bb9b91792b..f62ab0eae760 100644 --- a/packages/google-auth/tests/transport/test_grpc.py +++ b/packages/google-auth/tests/transport/test_grpc.py @@ -80,7 +80,7 @@ def test_call_no_refresh(self): def test_call_refresh(self): credentials = CredentialsStub() - credentials.expiry = datetime.datetime.min + _helpers.get_refresh_threshold() + credentials.expiry = datetime.datetime.min + _helpers.REFRESH_THRESHOLD request = mock.create_autospec(transport.Request) plugin = google.auth.transport.grpc.AuthMetadataPlugin(credentials, request) diff --git a/packages/google-auth/tests_async/oauth2/test_credentials_async.py b/packages/google-auth/tests_async/oauth2/test_credentials_async.py index d56d58df6007..a328cc3cba6d 100644 --- a/packages/google-auth/tests_async/oauth2/test_credentials_async.py +++ b/packages/google-auth/tests_async/oauth2/test_credentials_async.py @@ -62,7 +62,7 @@ def test_default_state(self): @mock.patch("google.oauth2._reauth_async.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", - return_value=datetime.datetime.min + _helpers.get_refresh_threshold(), + return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD, ) @pytest.mark.asyncio async def test_refresh_success(self, unused_utcnow, refresh_grant): @@ -124,7 +124,7 @@ async def test_refresh_no_refresh_token(self): @mock.patch("google.oauth2._reauth_async.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", - return_value=datetime.datetime.min + _helpers.get_refresh_threshold(), + return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD, ) @pytest.mark.asyncio async def test_credentials_with_scopes_requested_refresh_success( @@ -188,7 +188,7 @@ async def test_credentials_with_scopes_requested_refresh_success( @mock.patch("google.oauth2._reauth_async.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", - return_value=datetime.datetime.min + _helpers.get_refresh_threshold(), + return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD, ) @pytest.mark.asyncio async def test_credentials_with_scopes_returned_refresh_success( @@ -251,7 +251,7 @@ async def test_credentials_with_scopes_returned_refresh_success( @mock.patch("google.oauth2._reauth_async.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", - return_value=datetime.datetime.min + _helpers.get_refresh_threshold(), + return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD, ) @pytest.mark.asyncio async def test_credentials_with_scopes_refresh_failure_raises_refresh_error( diff --git a/packages/google-auth/tests_async/test_credentials_async.py b/packages/google-auth/tests_async/test_credentials_async.py index 71c70a52df97..9db5fc9ae754 100644 --- a/packages/google-auth/tests_async/test_credentials_async.py +++ b/packages/google-auth/tests_async/test_credentials_async.py @@ -47,7 +47,7 @@ def test_expired_and_valid(): # accomodation. These credentials should be valid. credentials.expiry = ( datetime.datetime.utcnow() - + _helpers.get_refresh_threshold() + + _helpers.REFRESH_THRESHOLD + datetime.timedelta(seconds=1) ) From 42f9bd9fb1e13032f81c1a0f1a2969fcf6cb1440 Mon Sep 17 00:00:00 2001 From: Carl Lundin <108372512+clundin25@users.noreply.github.com> Date: Thu, 22 Sep 2022 21:52:10 +0000 Subject: [PATCH 626/966] feat: Retry behavior (#1113) * feat: Retry behavior * Introduce `retryable` property to auth library exceptions. This can be used to determine if an exception should be retried. * Introduce `should_retry` parameter to token endpoints. If set to `False` the auth library will not retry failed requests. If set to `True` the auth library will retry failed requests. The default value is `True` to maintain existing behavior. * Expanded list of HTTP Status codes that will be retried. * Modified retry behavior to use exponential backoff. * Increased default retry attempts from 2 to 3. --- .../google/auth/_exponential_backoff.py | 111 +++++++++ .../google-auth/google/auth/exceptions.py | 17 +- .../google/auth/transport/__init__.py | 14 +- packages/google-auth/google/oauth2/_client.py | 171 ++++++++++---- .../google/oauth2/_client_async.py | 95 +++++--- .../google/oauth2/_reauth_async.py | 5 +- packages/google-auth/google/oauth2/reauth.py | 12 +- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../tests/compute_engine/test_credentials.py | 2 +- .../google-auth/tests/oauth2/test__client.py | 192 +++++++++++++++- .../google-auth/tests/oauth2/test_reauth.py | 11 +- .../tests/test__exponential_backoff.py | 41 ++++ packages/google-auth/tests/test_exceptions.py | 55 +++++ .../tests_async/oauth2/test__client_async.py | 214 +++++++++++++++++- .../tests_async/oauth2/test_reauth_async.py | 16 +- 15 files changed, 841 insertions(+), 115 deletions(-) create mode 100644 packages/google-auth/google/auth/_exponential_backoff.py create mode 100644 packages/google-auth/tests/test__exponential_backoff.py create mode 100644 packages/google-auth/tests/test_exceptions.py diff --git a/packages/google-auth/google/auth/_exponential_backoff.py b/packages/google-auth/google/auth/_exponential_backoff.py new file mode 100644 index 000000000000..b5801bec9d40 --- /dev/null +++ b/packages/google-auth/google/auth/_exponential_backoff.py @@ -0,0 +1,111 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import random +import time + +import six + +# The default amount of retry attempts +_DEFAULT_RETRY_TOTAL_ATTEMPTS = 3 + +# The default initial backoff period (1.0 second). +_DEFAULT_INITIAL_INTERVAL_SECONDS = 1.0 + +# The default randomization factor (0.1 which results in a random period ranging +# between 10% below and 10% above the retry interval). +_DEFAULT_RANDOMIZATION_FACTOR = 0.1 + +# The default multiplier value (2 which is 100% increase per back off). +_DEFAULT_MULTIPLIER = 2.0 + +"""Exponential Backoff Utility + +This is a private module that implements the exponential back off algorithm. +It can be used as a utility for code that needs to retry on failure, for example +an HTTP request. +""" + + +class ExponentialBackoff(six.Iterator): + """An exponential backoff iterator. This can be used in a for loop to + perform requests with exponential backoff. + + Args: + total_attempts Optional[int]: + The maximum amount of retries that should happen. + The default value is 3 attempts. + initial_wait_seconds Optional[int]: + The amount of time to sleep in the first backoff. This parameter + should be in seconds. + The default value is 1 second. + randomization_factor Optional[float]: + The amount of jitter that should be in each backoff. For example, + a value of 0.1 will introduce a jitter range of 10% to the + current backoff period. + The default value is 0.1. + multiplier Optional[float]: + The backoff multipler. This adjusts how much each backoff will + increase. For example a value of 2.0 leads to a 200% backoff + on each attempt. If the initial_wait is 1.0 it would look like + this sequence [1.0, 2.0, 4.0, 8.0]. + The default value is 2.0. + """ + + def __init__( + self, + total_attempts=_DEFAULT_RETRY_TOTAL_ATTEMPTS, + initial_wait_seconds=_DEFAULT_INITIAL_INTERVAL_SECONDS, + randomization_factor=_DEFAULT_RANDOMIZATION_FACTOR, + multiplier=_DEFAULT_MULTIPLIER, + ): + self._total_attempts = total_attempts + self._initial_wait_seconds = initial_wait_seconds + + self._current_wait_in_seconds = self._initial_wait_seconds + + self._randomization_factor = randomization_factor + self._multiplier = multiplier + self._backoff_count = 0 + + def __iter__(self): + self._backoff_count = 0 + self._current_wait_in_seconds = self._initial_wait_seconds + return self + + def __next__(self): + if self._backoff_count >= self._total_attempts: + raise StopIteration + self._backoff_count += 1 + + jitter_variance = self._current_wait_in_seconds * self._randomization_factor + jitter = random.uniform( + self._current_wait_in_seconds - jitter_variance, + self._current_wait_in_seconds + jitter_variance, + ) + + time.sleep(jitter) + + self._current_wait_in_seconds *= self._multiplier + return self._backoff_count + + @property + def total_attempts(self): + """The total amount of backoff attempts that will be made.""" + return self._total_attempts + + @property + def backoff_count(self): + """The current amount of backoff attempts that have been made.""" + return self._backoff_count diff --git a/packages/google-auth/google/auth/exceptions.py b/packages/google-auth/google/auth/exceptions.py index e9e737780a99..7760c87b897b 100644 --- a/packages/google-auth/google/auth/exceptions.py +++ b/packages/google-auth/google/auth/exceptions.py @@ -18,6 +18,15 @@ class GoogleAuthError(Exception): """Base class for all google.auth errors.""" + def __init__(self, *args, **kwargs): + super(GoogleAuthError, self).__init__(*args) + retryable = kwargs.get("retryable", False) + self._retryable = retryable + + @property + def retryable(self): + return self._retryable + class TransportError(GoogleAuthError): """Used to indicate an error occurred during an HTTP request.""" @@ -44,6 +53,10 @@ class MutualTLSChannelError(GoogleAuthError): class ClientCertError(GoogleAuthError): """Used to indicate that client certificate is missing or invalid.""" + @property + def retryable(self): + return False + class OAuthError(GoogleAuthError): """Used to indicate an error occurred during an OAuth related HTTP @@ -53,9 +66,9 @@ class OAuthError(GoogleAuthError): class ReauthFailError(RefreshError): """An exception for when reauth failed.""" - def __init__(self, message=None): + def __init__(self, message=None, **kwargs): super(ReauthFailError, self).__init__( - "Reauthentication failed. {0}".format(message) + "Reauthentication failed. {0}".format(message), **kwargs ) diff --git a/packages/google-auth/google/auth/transport/__init__.py b/packages/google-auth/google/auth/transport/__init__.py index 374e7b4d7228..8334145a1a3a 100644 --- a/packages/google-auth/google/auth/transport/__init__.py +++ b/packages/google-auth/google/auth/transport/__init__.py @@ -29,9 +29,21 @@ import six from six.moves import http_client +TOO_MANY_REQUESTS = 429 # Python 2.7 six is missing this status code. + +DEFAULT_RETRYABLE_STATUS_CODES = ( + http_client.INTERNAL_SERVER_ERROR, + http_client.SERVICE_UNAVAILABLE, + http_client.REQUEST_TIMEOUT, + TOO_MANY_REQUESTS, +) +"""Sequence[int]: HTTP status codes indicating a request can be retried. +""" + + DEFAULT_REFRESH_STATUS_CODES = (http_client.UNAUTHORIZED,) """Sequence[int]: Which HTTP status code indicate that credentials should be -refreshed and a request should be retried. +refreshed. """ DEFAULT_MAX_REFRESH_ATTEMPTS = 2 diff --git a/packages/google-auth/google/oauth2/_client.py b/packages/google-auth/google/oauth2/_client.py index 847c5db8a487..7f866d446a31 100644 --- a/packages/google-auth/google/oauth2/_client.py +++ b/packages/google-auth/google/oauth2/_client.py @@ -30,9 +30,11 @@ from six.moves import http_client from six.moves import urllib +from google.auth import _exponential_backoff from google.auth import _helpers from google.auth import exceptions from google.auth import jwt +from google.auth import transport _URLENCODED_CONTENT_TYPE = "application/x-www-form-urlencoded" _JSON_CONTENT_TYPE = "application/json" @@ -40,17 +42,22 @@ _REFRESH_GRANT_TYPE = "refresh_token" -def _handle_error_response(response_data): +def _handle_error_response(response_data, retryable_error): """Translates an error response into an exception. Args: response_data (Mapping | str): The decoded response data. + retryable_error Optional[bool]: A boolean indicating if an error is retryable. + Defaults to False. Raises: google.auth.exceptions.RefreshError: The errors contained in response_data. """ + + retryable_error = retryable_error if retryable_error else False + if isinstance(response_data, six.string_types): - raise exceptions.RefreshError(response_data) + raise exceptions.RefreshError(response_data, retryable=retryable_error) try: error_details = "{}: {}".format( response_data["error"], response_data.get("error_description") @@ -59,7 +66,45 @@ def _handle_error_response(response_data): except (KeyError, ValueError): error_details = json.dumps(response_data) - raise exceptions.RefreshError(error_details, response_data) + raise exceptions.RefreshError( + error_details, response_data, retryable=retryable_error + ) + + +def _can_retry(status_code, response_data): + """Checks if a request can be retried by inspecting the status code + and response body of the request. + + Args: + status_code (int): The response status code. + response_data (Mapping | str): The decoded response data. + + Returns: + bool: True if the response is retryable. False otherwise. + """ + if status_code in transport.DEFAULT_RETRYABLE_STATUS_CODES: + return True + + try: + # For a failed response, response_body could be a string + error_desc = response_data.get("error_description") or "" + error_code = response_data.get("error") or "" + + # Per Oauth 2.0 RFC https://www.rfc-editor.org/rfc/rfc6749.html#section-4.1.2.1 + # This is needed because a redirect will not return a 500 status code. + retryable_error_descriptions = { + "internal_failure", + "server_error", + "temporarily_unavailable", + } + + if any(e in retryable_error_descriptions for e in (error_code, error_desc)): + return True + + except AttributeError: + pass + + return False def _parse_expiry(response_data): @@ -81,7 +126,13 @@ def _parse_expiry(response_data): def _token_endpoint_request_no_throw( - request, token_uri, body, access_token=None, use_json=False, **kwargs + request, + token_uri, + body, + access_token=None, + use_json=False, + can_retry=True, + **kwargs ): """Makes a request to the OAuth 2.0 authorization server's token endpoint. This function doesn't throw on response errors. @@ -95,6 +146,7 @@ def _token_endpoint_request_no_throw( access_token (Optional(str)): The access token needed to make the request. use_json (Optional(bool)): Use urlencoded format or json format for the content type. The default value is False. + can_retry (bool): Enable or disable request retry behavior. kwargs: Additional arguments passed on to the request method. The kwargs will be passed to `requests.request` method, see: https://docs.python-requests.org/en/latest/api/#requests.request. @@ -104,8 +156,10 @@ def _token_endpoint_request_no_throw( side SSL certificate verification. Returns: - Tuple(bool, Mapping[str, str]): A boolean indicating if the request is - successful, and a mapping for the JSON-decoded response data. + Tuple(bool, Mapping[str, str], Optional[bool]): A boolean indicating + if the request is successful, a mapping for the JSON-decoded response + data and in the case of an error a boolean indicating if the error + is retryable. """ if use_json: headers = {"Content-Type": _JSON_CONTENT_TYPE} @@ -117,10 +171,7 @@ def _token_endpoint_request_no_throw( if access_token: headers["Authorization"] = "Bearer {}".format(access_token) - retry = 0 - # retry to fetch token for maximum of two times if any internal failure - # occurs. - while True: + def _perform_request(): response = request( method="POST", url=token_uri, headers=headers, body=body, **kwargs ) @@ -129,32 +180,44 @@ def _token_endpoint_request_no_throw( if hasattr(response.data, "decode") else response.data ) - - if response.status == http_client.OK: + response_data = "" + try: # response_body should be a JSON response_data = json.loads(response_body) - break - else: - # For a failed response, response_body could be a string - try: - response_data = json.loads(response_body) - error_desc = response_data.get("error_description") or "" - error_code = response_data.get("error") or "" - if ( - any(e == "internal_failure" for e in (error_code, error_desc)) - and retry < 1 - ): - retry += 1 - continue - except ValueError: - response_data = response_body - return False, response_data - - return True, response_data + except ValueError: + response_data = response_body + + if response.status == http_client.OK: + return True, response_data, None + + retryable_error = _can_retry( + status_code=response.status, response_data=response_data + ) + + return False, response_data, retryable_error + + request_succeeded, response_data, retryable_error = _perform_request() + + if request_succeeded or not retryable_error or not can_retry: + return request_succeeded, response_data, retryable_error + + retries = _exponential_backoff.ExponentialBackoff() + for _ in retries: + request_succeeded, response_data, retryable_error = _perform_request() + if request_succeeded or not retryable_error: + return request_succeeded, response_data, retryable_error + + return False, response_data, retryable_error def _token_endpoint_request( - request, token_uri, body, access_token=None, use_json=False, **kwargs + request, + token_uri, + body, + access_token=None, + use_json=False, + can_retry=True, + **kwargs ): """Makes a request to the OAuth 2.0 authorization server's token endpoint. @@ -167,6 +230,7 @@ def _token_endpoint_request( access_token (Optional(str)): The access token needed to make the request. use_json (Optional(bool)): Use urlencoded format or json format for the content type. The default value is False. + can_retry (bool): Enable or disable request retry behavior. kwargs: Additional arguments passed on to the request method. The kwargs will be passed to `requests.request` method, see: https://docs.python-requests.org/en/latest/api/#requests.request. @@ -182,15 +246,22 @@ def _token_endpoint_request( google.auth.exceptions.RefreshError: If the token endpoint returned an error. """ - response_status_ok, response_data = _token_endpoint_request_no_throw( - request, token_uri, body, access_token=access_token, use_json=use_json, **kwargs + + response_status_ok, response_data, retryable_error = _token_endpoint_request_no_throw( + request, + token_uri, + body, + access_token=access_token, + use_json=use_json, + can_retry=can_retry, + **kwargs ) if not response_status_ok: - _handle_error_response(response_data) + _handle_error_response(response_data, retryable_error) return response_data -def jwt_grant(request, token_uri, assertion): +def jwt_grant(request, token_uri, assertion, can_retry=True): """Implements the JWT Profile for OAuth 2.0 Authorization Grants. For more details, see `rfc7523 section 4`_. @@ -201,6 +272,7 @@ def jwt_grant(request, token_uri, assertion): token_uri (str): The OAuth 2.0 authorizations server's token endpoint URI. assertion (str): The OAuth 2.0 assertion. + can_retry (bool): Enable or disable request retry behavior. Returns: Tuple[str, Optional[datetime], Mapping[str, str]]: The access token, @@ -214,12 +286,16 @@ def jwt_grant(request, token_uri, assertion): """ body = {"assertion": assertion, "grant_type": _JWT_GRANT_TYPE} - response_data = _token_endpoint_request(request, token_uri, body) + response_data = _token_endpoint_request( + request, token_uri, body, can_retry=can_retry + ) try: access_token = response_data["access_token"] except KeyError as caught_exc: - new_exc = exceptions.RefreshError("No access token in response.", response_data) + new_exc = exceptions.RefreshError( + "No access token in response.", response_data, retryable=False + ) six.raise_from(new_exc, caught_exc) expiry = _parse_expiry(response_data) @@ -227,7 +303,7 @@ def jwt_grant(request, token_uri, assertion): return access_token, expiry, response_data -def id_token_jwt_grant(request, token_uri, assertion): +def id_token_jwt_grant(request, token_uri, assertion, can_retry=True): """Implements the JWT Profile for OAuth 2.0 Authorization Grants, but requests an OpenID Connect ID Token instead of an access token. @@ -242,6 +318,7 @@ def id_token_jwt_grant(request, token_uri, assertion): URI. assertion (str): JWT token signed by a service account. The token's payload must include a ``target_audience`` claim. + can_retry (bool): Enable or disable request retry behavior. Returns: Tuple[str, Optional[datetime], Mapping[str, str]]: @@ -254,12 +331,16 @@ def id_token_jwt_grant(request, token_uri, assertion): """ body = {"assertion": assertion, "grant_type": _JWT_GRANT_TYPE} - response_data = _token_endpoint_request(request, token_uri, body) + response_data = _token_endpoint_request( + request, token_uri, body, can_retry=can_retry + ) try: id_token = response_data["id_token"] except KeyError as caught_exc: - new_exc = exceptions.RefreshError("No ID token in response.", response_data) + new_exc = exceptions.RefreshError( + "No ID token in response.", response_data, retryable=False + ) six.raise_from(new_exc, caught_exc) payload = jwt.decode(id_token, verify=False) @@ -288,7 +369,9 @@ def _handle_refresh_grant_response(response_data, refresh_token): try: access_token = response_data["access_token"] except KeyError as caught_exc: - new_exc = exceptions.RefreshError("No access token in response.", response_data) + new_exc = exceptions.RefreshError( + "No access token in response.", response_data, retryable=False + ) six.raise_from(new_exc, caught_exc) refresh_token = response_data.get("refresh_token", refresh_token) @@ -305,6 +388,7 @@ def refresh_grant( client_secret, scopes=None, rapt_token=None, + can_retry=True, ): """Implements the OAuth 2.0 refresh token grant. @@ -324,6 +408,7 @@ def refresh_grant( token has a wild card scope (e.g. 'https://www.googleapis.com/auth/any-api'). rapt_token (Optional(str)): The reauth Proof Token. + can_retry (bool): Enable or disable request retry behavior. Returns: Tuple[str, str, Optional[datetime], Mapping[str, str]]: The access @@ -347,5 +432,7 @@ def refresh_grant( if rapt_token: body["rapt"] = rapt_token - response_data = _token_endpoint_request(request, token_uri, body) + response_data = _token_endpoint_request( + request, token_uri, body, can_retry=can_retry + ) return _handle_refresh_grant_response(response_data, refresh_token) diff --git a/packages/google-auth/google/oauth2/_client_async.py b/packages/google-auth/google/oauth2/_client_async.py index cf51211379ee..428084a70a8b 100644 --- a/packages/google-auth/google/oauth2/_client_async.py +++ b/packages/google-auth/google/oauth2/_client_async.py @@ -30,13 +30,14 @@ from six.moves import http_client from six.moves import urllib +from google.auth import _exponential_backoff from google.auth import exceptions from google.auth import jwt from google.oauth2 import _client as client async def _token_endpoint_request_no_throw( - request, token_uri, body, access_token=None, use_json=False + request, token_uri, body, access_token=None, use_json=False, can_retry=True ): """Makes a request to the OAuth 2.0 authorization server's token endpoint. This function doesn't throw on response errors. @@ -50,10 +51,13 @@ async def _token_endpoint_request_no_throw( access_token (Optional(str)): The access token needed to make the request. use_json (Optional(bool)): Use urlencoded format or json format for the content type. The default value is False. + can_retry (bool): Enable or disable request retry behavior. Returns: - Tuple(bool, Mapping[str, str]): A boolean indicating if the request is - successful, and a mapping for the JSON-decoded response data. + Tuple(bool, Mapping[str, str], Optional[bool]): A boolean indicating + if the request is successful, a mapping for the JSON-decoded response + data and in the case of an error a boolean indicating if the error + is retryable. """ if use_json: headers = {"Content-Type": client._JSON_CONTENT_TYPE} @@ -65,11 +69,7 @@ async def _token_endpoint_request_no_throw( if access_token: headers["Authorization"] = "Bearer {}".format(access_token) - retry = 0 - # retry to fetch token for maximum of two times if any internal failure - # occurs. - while True: - + async def _perform_request(): response = await request( method="POST", url=token_uri, headers=headers, body=body ) @@ -83,26 +83,36 @@ async def _token_endpoint_request_no_throw( else response_body1 ) - response_data = json.loads(response_body) + try: + response_data = json.loads(response_body) + except ValueError: + response_data = response_body if response.status == http_client.OK: - break - else: - error_desc = response_data.get("error_description") or "" - error_code = response_data.get("error") or "" - if ( - any(e == "internal_failure" for e in (error_code, error_desc)) - and retry < 1 - ): - retry += 1 - continue - return response.status == http_client.OK, response_data + return True, response_data, None + + retryable_error = client._can_retry( + status_code=response.status, response_data=response_data + ) + + return False, response_data, retryable_error + + request_succeeded, response_data, retryable_error = await _perform_request() + + if request_succeeded or not retryable_error or not can_retry: + return request_succeeded, response_data, retryable_error + + retries = _exponential_backoff.ExponentialBackoff() + for _ in retries: + request_succeeded, response_data, retryable_error = await _perform_request() + if request_succeeded or not retryable_error: + return request_succeeded, response_data, retryable_error - return response.status == http_client.OK, response_data + return False, response_data, retryable_error async def _token_endpoint_request( - request, token_uri, body, access_token=None, use_json=False + request, token_uri, body, access_token=None, use_json=False, can_retry=True ): """Makes a request to the OAuth 2.0 authorization server's token endpoint. @@ -115,6 +125,7 @@ async def _token_endpoint_request( access_token (Optional(str)): The access token needed to make the request. use_json (Optional(bool)): Use urlencoded format or json format for the content type. The default value is False. + can_retry (bool): Enable or disable request retry behavior. Returns: Mapping[str, str]: The JSON-decoded response data. @@ -123,15 +134,21 @@ async def _token_endpoint_request( google.auth.exceptions.RefreshError: If the token endpoint returned an error. """ - response_status_ok, response_data = await _token_endpoint_request_no_throw( - request, token_uri, body, access_token=access_token, use_json=use_json + + response_status_ok, response_data, retryable_error = await _token_endpoint_request_no_throw( + request, + token_uri, + body, + access_token=access_token, + use_json=use_json, + can_retry=can_retry, ) if not response_status_ok: - client._handle_error_response(response_data) + client._handle_error_response(response_data, retryable_error) return response_data -async def jwt_grant(request, token_uri, assertion): +async def jwt_grant(request, token_uri, assertion, can_retry=True): """Implements the JWT Profile for OAuth 2.0 Authorization Grants. For more details, see `rfc7523 section 4`_. @@ -142,6 +159,7 @@ async def jwt_grant(request, token_uri, assertion): token_uri (str): The OAuth 2.0 authorizations server's token endpoint URI. assertion (str): The OAuth 2.0 assertion. + can_retry (bool): Enable or disable request retry behavior. Returns: Tuple[str, Optional[datetime], Mapping[str, str]]: The access token, @@ -155,12 +173,16 @@ async def jwt_grant(request, token_uri, assertion): """ body = {"assertion": assertion, "grant_type": client._JWT_GRANT_TYPE} - response_data = await _token_endpoint_request(request, token_uri, body) + response_data = await _token_endpoint_request( + request, token_uri, body, can_retry=can_retry + ) try: access_token = response_data["access_token"] except KeyError as caught_exc: - new_exc = exceptions.RefreshError("No access token in response.", response_data) + new_exc = exceptions.RefreshError( + "No access token in response.", response_data, retryable=False + ) six.raise_from(new_exc, caught_exc) expiry = client._parse_expiry(response_data) @@ -168,7 +190,7 @@ async def jwt_grant(request, token_uri, assertion): return access_token, expiry, response_data -async def id_token_jwt_grant(request, token_uri, assertion): +async def id_token_jwt_grant(request, token_uri, assertion, can_retry=True): """Implements the JWT Profile for OAuth 2.0 Authorization Grants, but requests an OpenID Connect ID Token instead of an access token. @@ -183,6 +205,7 @@ async def id_token_jwt_grant(request, token_uri, assertion): URI. assertion (str): JWT token signed by a service account. The token's payload must include a ``target_audience`` claim. + can_retry (bool): Enable or disable request retry behavior. Returns: Tuple[str, Optional[datetime], Mapping[str, str]]: @@ -195,12 +218,16 @@ async def id_token_jwt_grant(request, token_uri, assertion): """ body = {"assertion": assertion, "grant_type": client._JWT_GRANT_TYPE} - response_data = await _token_endpoint_request(request, token_uri, body) + response_data = await _token_endpoint_request( + request, token_uri, body, can_retry=can_retry + ) try: id_token = response_data["id_token"] except KeyError as caught_exc: - new_exc = exceptions.RefreshError("No ID token in response.", response_data) + new_exc = exceptions.RefreshError( + "No ID token in response.", response_data, retryable=False + ) six.raise_from(new_exc, caught_exc) payload = jwt.decode(id_token, verify=False) @@ -217,6 +244,7 @@ async def refresh_grant( client_secret, scopes=None, rapt_token=None, + can_retry=True, ): """Implements the OAuth 2.0 refresh token grant. @@ -236,6 +264,7 @@ async def refresh_grant( token has a wild card scope (e.g. 'https://www.googleapis.com/auth/any-api'). rapt_token (Optional(str)): The reauth Proof Token. + can_retry (bool): Enable or disable request retry behavior. Returns: Tuple[str, Optional[str], Optional[datetime], Mapping[str, str]]: The @@ -259,5 +288,7 @@ async def refresh_grant( if rapt_token: body["rapt"] = rapt_token - response_data = await _token_endpoint_request(request, token_uri, body) + response_data = await _token_endpoint_request( + request, token_uri, body, can_retry=can_retry + ) return client._handle_refresh_grant_response(response_data, refresh_token) diff --git a/packages/google-auth/google/oauth2/_reauth_async.py b/packages/google-auth/google/oauth2/_reauth_async.py index 30b0b0b1ec00..6b69c6e67c68 100644 --- a/packages/google-auth/google/oauth2/_reauth_async.py +++ b/packages/google-auth/google/oauth2/_reauth_async.py @@ -292,7 +292,7 @@ async def refresh_grant( if rapt_token: body["rapt"] = rapt_token - response_status_ok, response_data = await _client_async._token_endpoint_request_no_throw( + response_status_ok, response_data, retryable_error = await _client_async._token_endpoint_request_no_throw( request, token_uri, body ) if ( @@ -317,12 +317,13 @@ async def refresh_grant( ( response_status_ok, response_data, + retryable_error, ) = await _client_async._token_endpoint_request_no_throw( request, token_uri, body ) if not response_status_ok: - _client._handle_error_response(response_data) + _client._handle_error_response(response_data, retryable_error) refresh_response = _client._handle_refresh_grant_response( response_data, refresh_token ) diff --git a/packages/google-auth/google/oauth2/reauth.py b/packages/google-auth/google/oauth2/reauth.py index 2c32bda2ad80..ad2ad1b2ee25 100644 --- a/packages/google-auth/google/oauth2/reauth.py +++ b/packages/google-auth/google/oauth2/reauth.py @@ -319,7 +319,7 @@ def refresh_grant( if rapt_token: body["rapt"] = rapt_token - response_status_ok, response_data = _client._token_endpoint_request_no_throw( + response_status_ok, response_data, retryable_error = _client._token_endpoint_request_no_throw( request, token_uri, body ) if ( @@ -339,12 +339,14 @@ def refresh_grant( request, client_id, client_secret, refresh_token, token_uri, scopes=scopes ) body["rapt"] = rapt_token - (response_status_ok, response_data) = _client._token_endpoint_request_no_throw( - request, token_uri, body - ) + ( + response_status_ok, + response_data, + retryable_error, + ) = _client._token_endpoint_request_no_throw(request, token_uri, body) if not response_status_ok: - _client._handle_error_response(response_data) + _client._handle_error_response(response_data, retryable_error) return _client._handle_refresh_grant_response(response_data, refresh_token) + ( rapt_token, ) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index f32ace9608743fbfa7e1782e6af1b68a45676dfc..735379ef1d88cf0e42dea06bd5ff36e79d99c642 100644 GIT binary patch literal 10324 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 Date: Mon, 26 Sep 2022 19:24:49 +0000 Subject: [PATCH 627/966] refactor: ECP Config schema update. Googlers see go/enterpise-cert-config (#1152) * refactor: ECP Config schema update. Googlers see go/enterpise-cert-config --- .../auth/transport/_custom_tls_signer.py | 8 ++++---- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../tests/data/enterprise_cert_valid.json | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/google-auth/google/auth/transport/_custom_tls_signer.py b/packages/google-auth/google/auth/transport/_custom_tls_signer.py index 22a510daadd3..dfef6d00f1da 100644 --- a/packages/google-auth/google/auth/transport/_custom_tls_signer.py +++ b/packages/google-auth/google/auth/transport/_custom_tls_signer.py @@ -192,8 +192,8 @@ def __init__(self, enterprise_cert_file_path): { "libs": { - "signer_library": "...", - "offload_library": "..." + "ecp_client": "...", + "tls_offload": "..." } } """ @@ -206,8 +206,8 @@ def load_libraries(self): with open(self._enterprise_cert_file_path, "r") as f: enterprise_cert_json = json.load(f) libs = enterprise_cert_json["libs"] - signer_library = libs["signer_library"] - offload_library = libs["offload_library"] + signer_library = libs["ecp_client"] + offload_library = libs["tls_offload"] except (KeyError, ValueError) as caught_exc: new_exc = exceptions.MutualTLSChannelError( "enterprise cert file is invalid", caught_exc diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 735379ef1d88cf0e42dea06bd5ff36e79d99c642..df5017ab6f4dd70bdbeb7fec0decbf00edff7624 100644 GIT binary patch literal 10324 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 literal 10324 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 Date: Mon, 26 Sep 2022 23:07:38 +0000 Subject: [PATCH 628/966] Revert "fix: Update token refresh threshold from 20 seconds to 5 minutes (#1146)" (#1153) This reverts commit 261a56138fba33ff7d898ab5907a6098125fefef. --- packages/google-auth/google/auth/_helpers.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/_helpers.py b/packages/google-auth/google/auth/_helpers.py index c70fd6a6bbdf..1b08ab87f83c 100644 --- a/packages/google-auth/google/auth/_helpers.py +++ b/packages/google-auth/google/auth/_helpers.py @@ -23,7 +23,11 @@ from six.moves import urllib -REFRESH_THRESHOLD = datetime.timedelta(seconds=300) +# Token server doesn't provide a new a token when doing refresh unless the +# token is expiring within 30 seconds, so refresh threshold should not be +# more than 30 seconds. Otherwise auth lib will send tons of refresh requests +# until 30 seconds before the expiration, and cause a spike of CPU usage. +REFRESH_THRESHOLD = datetime.timedelta(seconds=20) def copy_docstring(source_class): From ce06c62c31674548f7235ceb9bbee4274e4f6661 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 27 Sep 2022 13:37:04 -0700 Subject: [PATCH 629/966] chore(main): release 2.12.0 (#1148) * chore(main): release 2.12.0 --- packages/google-auth/CHANGELOG.md | 13 +++++++++++++ packages/google-auth/google/auth/version.py | 2 +- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index d8d469e8053a..e1f87b4194fd 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,19 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.12.0](https://github.com/googleapis/google-auth-library-python/compare/v2.11.1...v2.12.0) (2022-09-26) + + +### Features + +* Retry behavior ([#1113](https://github.com/googleapis/google-auth-library-python/issues/1113)) ([78d3790](https://github.com/googleapis/google-auth-library-python/commit/78d37906f0811f9878834ac34d5b83e5cbd58800)) + + +### Bug Fixes + +* Modify RefreshError exception to use gcloud ADC command. ([#1149](https://github.com/googleapis/google-auth-library-python/issues/1149)) ([059fd35](https://github.com/googleapis/google-auth-library-python/commit/059fd353d5f2a8527de8bf1fe6dbd5e326c0e29a)) +* Revert "Update token refresh threshold from 20 seconds to 5 minutes". ([186464b](https://github.com/googleapis/google-auth-library-python/commit/186464bf5920fb3b76499ac542b0fb90023629de)) + ## [2.11.1](https://github.com/googleapis/google-auth-library-python/compare/v2.11.0...v2.11.1) (2022-09-20) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 83a562d6e37f..576f40705d30 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.11.1" +__version__ = "2.12.0" diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index df5017ab6f4dd70bdbeb7fec0decbf00edff7624..61917177f498741b2b4673c9d27c20e3a48ecb55 100644 GIT binary patch literal 10324 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 literal 10324 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 From f6772ceed56b09cbfd81a58da195e444e714166b Mon Sep 17 00:00:00 2001 From: Jin Date: Wed, 28 Sep 2022 10:29:19 -0700 Subject: [PATCH 630/966] feat: implement pluggable auth interactive mode (#1131) For interactive mode: 1. Always using output to read the result. 2. Make `expiration_time` optional for all mode. 3. Implement interactive mode run executable 4. Implement `revoke()` function. 5. Refactor tests Co-authored-by: Leo <39062083+lsirac@users.noreply.github.com> Co-authored-by: Carl Lundin <108372512+clundin25@users.noreply.github.com> --- packages/google-auth/docs/user-guide.rst | 10 +- packages/google-auth/google/auth/pluggable.py | 244 ++++++--- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes packages/google-auth/tests/test_pluggable.py | 468 ++++++++++++++---- 4 files changed, 554 insertions(+), 168 deletions(-) diff --git a/packages/google-auth/docs/user-guide.rst b/packages/google-auth/docs/user-guide.rst index e689b11c6b21..682b58a76725 100644 --- a/packages/google-auth/docs/user-guide.rst +++ b/packages/google-auth/docs/user-guide.rst @@ -434,8 +434,10 @@ Response format fields summary: - ``version``: The version of the JSON output. Currently only version 1 is supported. - ``success``: The status of the response. - - When true, the response must contain the 3rd party token, token type, and expiration. The executable must also exit with exit code 0. - - When false, the response must contain the error code and message fields and exit with a non-zero value. + - When true, the response must contain the 3rd party token, token type, and + expiration. The executable must also exit with exit code 0. + - When false, the response must contain the error code and message fields + and exit with a non-zero value. - ``token_type``: The 3rd party subject token type. Must be - *urn:ietf:params:oauth:token-type:jwt* - *urn:ietf:params:oauth:token-type:id_token* @@ -450,7 +452,9 @@ Response format fields summary: All response types must include both the ``version`` and ``success`` fields. Successful responses must include the ``token_type``, and one of ``id_token`` or ``saml_response``. -If output file is specified, ``expiration_time`` is mandatory. +``expiration_time`` is optional. If the output file does not contain the +``expiration_time`` field, the response will be considered expired and the +executable will be called. Error responses must include both the ``code`` and ``message`` fields. The library will populate the following environment variables when the diff --git a/packages/google-auth/google/auth/pluggable.py b/packages/google-auth/google/auth/pluggable.py index 42f6bcd81466..6be8222c1eb4 100644 --- a/packages/google-auth/google/auth/pluggable.py +++ b/packages/google-auth/google/auth/pluggable.py @@ -38,6 +38,7 @@ import json import os import subprocess +import sys import time from google.auth import _helpers @@ -47,6 +48,14 @@ # The max supported executable spec version. EXECUTABLE_SUPPORTED_MAX_VERSION = 1 +EXECUTABLE_TIMEOUT_MILLIS_DEFAULT = 30 * 1000 # 30 seconds +EXECUTABLE_TIMEOUT_MILLIS_LOWER_BOUND = 5 * 1000 # 5 seconds +EXECUTABLE_TIMEOUT_MILLIS_UPPER_BOUND = 120 * 1000 # 2 minutes + +EXECUTABLE_INTERACTIVE_TIMEOUT_MILLIS_DEFAULT = 5 * 60 * 1000 # 5 minutes +EXECUTABLE_INTERACTIVE_TIMEOUT_MILLIS_LOWER_BOUND = 5 * 60 * 1000 # 5 minutes +EXECUTABLE_INTERACTIVE_TIMEOUT_MILLIS_UPPER_BOUND = 30 * 60 * 1000 # 30 minutes + class Credentials(external_account.Credentials): """External account credentials sourced from executables.""" @@ -92,6 +101,7 @@ def __init__( :meth:`from_info` are used instead of calling the constructor directly. """ + self.interactive = kwargs.pop("interactive", False) super(Credentials, self).__init__( audience=audience, subject_token_type=subject_token_type, @@ -116,37 +126,51 @@ def __init__( self._credential_source_executable_timeout_millis = self._credential_source_executable.get( "timeout_millis" ) + self._credential_source_executable_interactive_timeout_millis = self._credential_source_executable.get( + "interactive_timeout_millis" + ) self._credential_source_executable_output_file = self._credential_source_executable.get( "output_file" ) + self._tokeninfo_username = kwargs.get("tokeninfo_username", "") # dummy value if not self._credential_source_executable_command: raise ValueError( "Missing command field. Executable command must be provided." ) if not self._credential_source_executable_timeout_millis: - self._credential_source_executable_timeout_millis = 30 * 1000 + self._credential_source_executable_timeout_millis = ( + EXECUTABLE_TIMEOUT_MILLIS_DEFAULT + ) elif ( - self._credential_source_executable_timeout_millis < 5 * 1000 - or self._credential_source_executable_timeout_millis > 120 * 1000 + self._credential_source_executable_timeout_millis + < EXECUTABLE_TIMEOUT_MILLIS_LOWER_BOUND + or self._credential_source_executable_timeout_millis + > EXECUTABLE_TIMEOUT_MILLIS_UPPER_BOUND ): raise ValueError("Timeout must be between 5 and 120 seconds.") + if not self._credential_source_executable_interactive_timeout_millis: + self._credential_source_executable_interactive_timeout_millis = ( + EXECUTABLE_INTERACTIVE_TIMEOUT_MILLIS_DEFAULT + ) + elif ( + self._credential_source_executable_interactive_timeout_millis + < EXECUTABLE_INTERACTIVE_TIMEOUT_MILLIS_LOWER_BOUND + or self._credential_source_executable_interactive_timeout_millis + > EXECUTABLE_INTERACTIVE_TIMEOUT_MILLIS_UPPER_BOUND + ): + raise ValueError("Interactive timeout must be between 5 and 30 minutes.") + @_helpers.copy_docstring(external_account.Credentials) def retrieve_subject_token(self, request): - env_allow_executables = os.environ.get( - "GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES" - ) - if env_allow_executables != "1": - raise ValueError( - "Executables need to be explicitly allowed (set GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES to '1') to run." - ) + self._validate_running_mode() # Check output file. if self._credential_source_executable_output_file is not None: try: with open( - self._credential_source_executable_output_file + self._credential_source_executable_output_file, encoding="utf-8" ) as output_file: response = json.load(output_file) except Exception: @@ -155,6 +179,10 @@ def retrieve_subject_token(self, request): try: # If the cached response is expired, _parse_subject_token will raise an error which will be ignored and we will call the executable again. subject_token = self._parse_subject_token(response) + if ( + "expiration_time" not in response + ): # Always treat missing expiration_time as expired and proceed to executable run. + raise exceptions.RefreshError except ValueError: raise except exceptions.RefreshError: @@ -169,46 +197,102 @@ def retrieve_subject_token(self, request): # Inject env vars. env = os.environ.copy() - env["GOOGLE_EXTERNAL_ACCOUNT_AUDIENCE"] = self._audience - env["GOOGLE_EXTERNAL_ACCOUNT_TOKEN_TYPE"] = self._subject_token_type - env[ - "GOOGLE_EXTERNAL_ACCOUNT_INTERACTIVE" - ] = "0" # Always set to 0 until interactive mode is implemented. - if self._service_account_impersonation_url is not None: - env[ - "GOOGLE_EXTERNAL_ACCOUNT_IMPERSONATED_EMAIL" - ] = self.service_account_email - if self._credential_source_executable_output_file is not None: - env[ - "GOOGLE_EXTERNAL_ACCOUNT_OUTPUT_FILE" - ] = self._credential_source_executable_output_file + self._inject_env_variables(env) + env["GOOGLE_EXTERNAL_ACCOUNT_REVOKE"] = "0" - try: - result = subprocess.run( - self._credential_source_executable_command.split(), - timeout=self._credential_source_executable_timeout_millis / 1000, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - env=env, - ) - if result.returncode != 0: - raise exceptions.RefreshError( - "Executable exited with non-zero return code {}. Error: {}".format( - result.returncode, result.stdout - ) + # Run executable. + exe_timeout = ( + self._credential_source_executable_interactive_timeout_millis / 1000 + if self.interactive + else self._credential_source_executable_timeout_millis / 1000 + ) + exe_stdin = sys.stdin if self.interactive else None + exe_stdout = sys.stdout if self.interactive else subprocess.PIPE + exe_stderr = sys.stdout if self.interactive else subprocess.STDOUT + + result = subprocess.run( + self._credential_source_executable_command.split(), + timeout=exe_timeout, + stdin=exe_stdin, + stdout=exe_stdout, + stderr=exe_stderr, + env=env, + ) + if result.returncode != 0: + raise exceptions.RefreshError( + "Executable exited with non-zero return code {}. Error: {}".format( + result.returncode, result.stdout ) - except Exception: - raise - else: - try: - data = result.stdout.decode("utf-8") - response = json.loads(data) - subject_token = self._parse_subject_token(response) - except Exception: - raise + ) + + # Handle executable output. + response = json.loads(result.stdout.decode("utf-8")) if result.stdout else None + if not response and self._credential_source_executable_output_file is not None: + response = json.load( + open(self._credential_source_executable_output_file, encoding="utf-8") + ) + subject_token = self._parse_subject_token(response) return subject_token + def revoke(self, request): + """Revokes the subject token using the credential_source object. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + Raises: + google.auth.exceptions.RefreshError: If the executable revocation + not properly executed. + + """ + if not self.interactive: + raise ValueError("Revoke is only enabled under interactive mode.") + self._validate_running_mode() + + if not _helpers.is_python_3(): + raise exceptions.RefreshError( + "Pluggable auth is only supported for python 3.6+" + ) + + # Inject variables + env = os.environ.copy() + self._inject_env_variables(env) + env["GOOGLE_EXTERNAL_ACCOUNT_REVOKE"] = "1" + + # Run executable + result = subprocess.run( + self._credential_source_executable_command.split(), + timeout=self._credential_source_executable_interactive_timeout_millis + / 1000, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + env=env, + ) + + if result.returncode != 0: + raise exceptions.RefreshError( + "Auth revoke failed on executable. Exit with non-zero return code {}. Error: {}".format( + result.returncode, result.stdout + ) + ) + + response = json.loads(result.stdout.decode("utf-8")) + self._validate_revoke_response(response) + + @property + def external_account_id(self): + """Returns the external account identifier. + + When service account impersonation is used the identifier is the service + account email. + + Without service account impersonation, this returns None, unless it is + being used by the Google Cloud CLI which populates this field. + """ + + return self.service_account_email or self._tokeninfo_username + @classmethod def from_info(cls, info, **kwargs): """Creates a Pluggable Credentials instance from parsed external account info. @@ -241,17 +325,23 @@ def from_file(cls, filename, **kwargs): """ return super(Credentials, cls).from_file(filename, **kwargs) + def _inject_env_variables(self, env): + env["GOOGLE_EXTERNAL_ACCOUNT_AUDIENCE"] = self._audience + env["GOOGLE_EXTERNAL_ACCOUNT_TOKEN_TYPE"] = self._subject_token_type + env["GOOGLE_EXTERNAL_ACCOUNT_ID"] = self.external_account_id + env["GOOGLE_EXTERNAL_ACCOUNT_INTERACTIVE"] = "1" if self.interactive else "0" + + if self._service_account_impersonation_url is not None: + env[ + "GOOGLE_EXTERNAL_ACCOUNT_IMPERSONATED_EMAIL" + ] = self.service_account_email + if self._credential_source_executable_output_file is not None: + env[ + "GOOGLE_EXTERNAL_ACCOUNT_OUTPUT_FILE" + ] = self._credential_source_executable_output_file + def _parse_subject_token(self, response): - if "version" not in response: - raise ValueError("The executable response is missing the version field.") - if response["version"] > EXECUTABLE_SUPPORTED_MAX_VERSION: - raise exceptions.RefreshError( - "Executable returned unsupported version {}.".format( - response["version"] - ) - ) - if "success" not in response: - raise ValueError("The executable response is missing the success field.") + self._validate_response_schema(response) if not response["success"]: if "code" not in response or "message" not in response: raise ValueError( @@ -262,13 +352,6 @@ def _parse_subject_token(self, response): response["code"], response["message"] ) ) - if ( - "expiration_time" not in response - and self._credential_source_executable_output_file - ): - raise ValueError( - "The executable response must contain an expiration_time for successful responses when an output_file has been specified in the configuration." - ) if "expiration_time" in response and response["expiration_time"] < time.time(): raise exceptions.RefreshError( "The token returned by the executable is expired." @@ -284,3 +367,38 @@ def _parse_subject_token(self, response): return response["saml_response"] else: raise exceptions.RefreshError("Executable returned unsupported token type.") + + def _validate_revoke_response(self, response): + self._validate_response_schema(response) + if not response["success"]: + raise exceptions.RefreshError("Revoke failed with unsuccessful response.") + + def _validate_response_schema(self, response): + if "version" not in response: + raise ValueError("The executable response is missing the version field.") + if response["version"] > EXECUTABLE_SUPPORTED_MAX_VERSION: + raise exceptions.RefreshError( + "Executable returned unsupported version {}.".format( + response["version"] + ) + ) + + if "success" not in response: + raise ValueError("The executable response is missing the success field.") + + def _validate_running_mode(self): + env_allow_executables = os.environ.get( + "GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES" + ) + if env_allow_executables != "1": + raise ValueError( + "Executables need to be explicitly allowed (set GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES to '1') to run." + ) + + if self.interactive and not self._credential_source_executable_output_file: + raise ValueError( + "An output_file must be specified in the credential configuration for interactive mode." + ) + + if self.interactive and not self.is_workforce_pool: + raise ValueError("Interactive mode is only enabled for workforce pool.") diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 61917177f498741b2b4673c9d27c20e3a48ecb55..2bef7d971ed407a5c67431d403624e65b2c28877 100644 GIT binary patch literal 10324 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 diff --git a/packages/google-auth/tests/test_pluggable.py b/packages/google-auth/tests/test_pluggable.py index 80cde7972887..293d5c6edb1b 100644 --- a/packages/google-auth/tests/test_pluggable.py +++ b/packages/google-auth/tests/test_pluggable.py @@ -26,6 +26,7 @@ # from google.auth import _helpers from google.auth import exceptions from google.auth import pluggable +from tests.test__default import WORKFORCE_AUDIENCE # from google.auth import transport @@ -44,7 +45,6 @@ SUBJECT_TOKEN_FIELD_NAME = "access_token" TOKEN_URL = "https://sts.googleapis.com/v1/token" -SERVICE_ACCOUNT_IMPERSONATION_URL = "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/byoid-test@cicpclientproj.iam.gserviceaccount.com:generateAccessToken" SUBJECT_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:jwt" AUDIENCE = "//iam.googleapis.com/projects/123456/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID" @@ -57,6 +57,7 @@ class TestCredentials(object): CREDENTIAL_SOURCE_EXECUTABLE = { "command": CREDENTIAL_SOURCE_EXECUTABLE_COMMAND, "timeout_millis": 30000, + "interactive_timeout_millis": 300000, "output_file": CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, } CREDENTIAL_SOURCE = {"executable": CREDENTIAL_SOURCE_EXECUTABLE} @@ -68,6 +69,12 @@ class TestCredentials(object): "id_token": EXECUTABLE_OIDC_TOKEN, "expiration_time": 9999999999, } + EXECUTABLE_SUCCESSFUL_OIDC_NO_EXPIRATION_TIME_RESPONSE_ID_TOKEN = { + "version": 1, + "success": True, + "token_type": "urn:ietf:params:oauth:token-type:id_token", + "id_token": EXECUTABLE_OIDC_TOKEN, + } EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_JWT = { "version": 1, "success": True, @@ -75,6 +82,12 @@ class TestCredentials(object): "id_token": EXECUTABLE_OIDC_TOKEN, "expiration_time": 9999999999, } + EXECUTABLE_SUCCESSFUL_OIDC_NO_EXPIRATION_TIME_RESPONSE_JWT = { + "version": 1, + "success": True, + "token_type": "urn:ietf:params:oauth:token-type:jwt", + "id_token": EXECUTABLE_OIDC_TOKEN, + } EXECUTABLE_SAML_TOKEN = "FAKE_SAML_RESPONSE" EXECUTABLE_SUCCESSFUL_SAML_RESPONSE = { "version": 1, @@ -83,6 +96,12 @@ class TestCredentials(object): "saml_response": EXECUTABLE_SAML_TOKEN, "expiration_time": 9999999999, } + EXECUTABLE_SUCCESSFUL_SAML_NO_EXPIRATION_TIME_RESPONSE = { + "version": 1, + "success": True, + "token_type": "urn:ietf:params:oauth:token-type:saml2", + "saml_response": EXECUTABLE_SAML_TOKEN, + } EXECUTABLE_FAILED_RESPONSE = { "version": 1, "success": False, @@ -104,6 +123,7 @@ def make_pluggable( service_account_impersonation_url=None, credential_source=None, workforce_pool_user_project=None, + interactive=None, ): return pluggable.Credentials( audience=audience, @@ -117,6 +137,7 @@ def make_pluggable( scopes=scopes, default_scopes=default_scopes, workforce_pool_user_project=workforce_pool_user_project, + interactive=interactive, ) @mock.patch.object(pluggable.Credentials, "__init__", return_value=None) @@ -262,77 +283,134 @@ def test_info_with_credential_source(self): "credential_source": self.CREDENTIAL_SOURCE, } - @mock.patch.dict( - os.environ, - { - "GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1", - "GOOGLE_EXTERNAL_ACCOUNT_AUDIENCE": "original_audience", - "GOOGLE_EXTERNAL_ACCOUNT_TOKEN_TYPE": "original_token_type", - "GOOGLE_EXTERNAL_ACCOUNT_INTERACTIVE": "0", - "GOOGLE_EXTERNAL_ACCOUNT_IMPERSONATED_EMAIL": "original_impersonated_email", - "GOOGLE_EXTERNAL_ACCOUNT_OUTPUT_FILE": "original_output_file", - }, - ) - def test_retrieve_subject_token_oidc_id_token(self): - with mock.patch( - "subprocess.run", - return_value=subprocess.CompletedProcess( - args=[], - stdout=json.dumps( + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_retrieve_subject_token_successfully(self, tmpdir): + ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE = tmpdir.join( + "actual_output_file" + ) + ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE = { + "command": "command", + "interactive_timeout_millis": 300000, + "output_file": ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, + } + ACTUAL_CREDENTIAL_SOURCE = {"executable": ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE} + + testData = { + "subject_token_oidc_id_token": { + "stdout": json.dumps( self.EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_ID_TOKEN ).encode("UTF-8"), - returncode=0, - ), - ): - credentials = self.make_pluggable( - audience=AUDIENCE, - service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, - credential_source=self.CREDENTIAL_SOURCE, - ) + "impersonation_url": SERVICE_ACCOUNT_IMPERSONATION_URL, + "file_content": self.EXECUTABLE_SUCCESSFUL_OIDC_NO_EXPIRATION_TIME_RESPONSE_ID_TOKEN, + "expect_token": self.EXECUTABLE_OIDC_TOKEN, + }, + "subject_token_oidc_id_token_interacitve_mode": { + "audience": WORKFORCE_AUDIENCE, + "file_content": self.EXECUTABLE_SUCCESSFUL_OIDC_NO_EXPIRATION_TIME_RESPONSE_ID_TOKEN, + "interactive": True, + "expect_token": self.EXECUTABLE_OIDC_TOKEN, + }, + "subject_token_oidc_jwt": { + "stdout": json.dumps( + self.EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_JWT + ).encode("UTF-8"), + "impersonation_url": SERVICE_ACCOUNT_IMPERSONATION_URL, + "file_content": self.EXECUTABLE_SUCCESSFUL_OIDC_NO_EXPIRATION_TIME_RESPONSE_JWT, + "expect_token": self.EXECUTABLE_OIDC_TOKEN, + }, + "subject_token_oidc_jwt_interactive_mode": { + "audience": WORKFORCE_AUDIENCE, + "file_content": self.EXECUTABLE_SUCCESSFUL_OIDC_NO_EXPIRATION_TIME_RESPONSE_JWT, + "interactive": True, + "expect_token": self.EXECUTABLE_OIDC_TOKEN, + }, + "subject_token_saml": { + "stdout": json.dumps(self.EXECUTABLE_SUCCESSFUL_SAML_RESPONSE).encode( + "UTF-8" + ), + "impersonation_url": SERVICE_ACCOUNT_IMPERSONATION_URL, + "file_content": self.EXECUTABLE_SUCCESSFUL_SAML_NO_EXPIRATION_TIME_RESPONSE, + "expect_token": self.EXECUTABLE_SAML_TOKEN, + }, + "subject_token_saml_interactive_mode": { + "audience": WORKFORCE_AUDIENCE, + "file_content": self.EXECUTABLE_SUCCESSFUL_SAML_NO_EXPIRATION_TIME_RESPONSE, + "interactive": True, + "expect_token": self.EXECUTABLE_SAML_TOKEN, + }, + } - subject_token = credentials.retrieve_subject_token(None) + for data in testData.values(): + with open( + ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, "w" + ) as output_file: + json.dump(data.get("file_content"), output_file) - assert subject_token == self.EXECUTABLE_OIDC_TOKEN + with mock.patch( + "subprocess.run", + return_value=subprocess.CompletedProcess( + args=[], stdout=data.get("stdout"), returncode=0 + ), + ): + credentials = self.make_pluggable( + audience=data.get("audience", AUDIENCE), + service_account_impersonation_url=data.get("impersonation_url"), + credential_source=ACTUAL_CREDENTIAL_SOURCE, + interactive=data.get("interactive", False), + ) + subject_token = credentials.retrieve_subject_token(None) + assert subject_token == data.get("expect_token") + os.remove(ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE) @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) - def test_retrieve_subject_token_oidc_jwt(self): + def test_retrieve_subject_token_saml(self): with mock.patch( "subprocess.run", return_value=subprocess.CompletedProcess( args=[], - stdout=json.dumps(self.EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_JWT).encode( + stdout=json.dumps(self.EXECUTABLE_SUCCESSFUL_SAML_RESPONSE).encode( "UTF-8" ), returncode=0, ), ): - credentials = self.make_pluggable( - audience=AUDIENCE, - service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, - credential_source=self.CREDENTIAL_SOURCE, - ) + credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE) subject_token = credentials.retrieve_subject_token(None) - assert subject_token == self.EXECUTABLE_OIDC_TOKEN + assert subject_token == self.EXECUTABLE_SAML_TOKEN @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) - def test_retrieve_subject_token_saml(self): + def test_retrieve_subject_token_saml_interactive_mode(self, tmpdir): + + ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE = tmpdir.join( + "actual_output_file" + ) + ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE = { + "command": "command", + "interactive_timeout_millis": 300000, + "output_file": ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, + } + ACTUAL_CREDENTIAL_SOURCE = {"executable": ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE} + with open(ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, "w") as output_file: + json.dump( + self.EXECUTABLE_SUCCESSFUL_SAML_NO_EXPIRATION_TIME_RESPONSE, output_file + ) + with mock.patch( "subprocess.run", - return_value=subprocess.CompletedProcess( - args=[], - stdout=json.dumps(self.EXECUTABLE_SUCCESSFUL_SAML_RESPONSE).encode( - "UTF-8" - ), - returncode=0, - ), + return_value=subprocess.CompletedProcess(args=[], returncode=0), ): - credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE) + credentials = self.make_pluggable( + audience=WORKFORCE_AUDIENCE, + credential_source=ACTUAL_CREDENTIAL_SOURCE, + interactive=True, + ) subject_token = credentials.retrieve_subject_token(None) assert subject_token == self.EXECUTABLE_SAML_TOKEN + os.remove(ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE) @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) def test_retrieve_subject_token_failed(self): @@ -353,6 +431,46 @@ def test_retrieve_subject_token_failed(self): r"Executable returned unsuccessful response: code: 401, message: Permission denied. Caller not authorized." ) + @mock.patch.dict( + os.environ, + { + "GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1", + "GOOGLE_EXTERNAL_ACCOUNT_INTERACTIVE": "1", + }, + ) + def test_retrieve_subject_token_failed_interactive_mode(self, tmpdir): + ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE = tmpdir.join( + "actual_output_file" + ) + ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE = { + "command": "command", + "interactive_timeout_millis": 300000, + "output_file": ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, + } + ACTUAL_CREDENTIAL_SOURCE = {"executable": ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE} + with open( + ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, "w", encoding="utf-8" + ) as output_file: + json.dump(self.EXECUTABLE_FAILED_RESPONSE, output_file) + + with mock.patch( + "subprocess.run", + return_value=subprocess.CompletedProcess(args=[], returncode=0), + ): + credentials = self.make_pluggable( + audience=WORKFORCE_AUDIENCE, + credential_source=ACTUAL_CREDENTIAL_SOURCE, + interactive=True, + ) + + with pytest.raises(exceptions.RefreshError) as excinfo: + _ = credentials.retrieve_subject_token(None) + + assert excinfo.match( + r"Executable returned unsuccessful response: code: 401, message: Permission denied. Caller not authorized." + ) + os.remove(ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE) + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "0"}) def test_retrieve_subject_token_not_allowd(self): with mock.patch( @@ -641,63 +759,6 @@ def test_retrieve_subject_token_missing_error_code_message(self): r"Error code and message fields are required in the response." ) - @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) - def test_retrieve_subject_token_without_expiration_time_should_fail_when_output_file_specified( - self, - ): - EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE = { - "version": 1, - "success": True, - "token_type": "urn:ietf:params:oauth:token-type:id_token", - "id_token": self.EXECUTABLE_OIDC_TOKEN, - } - - with mock.patch( - "subprocess.run", - return_value=subprocess.CompletedProcess( - args=[], - stdout=json.dumps(EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE).encode("UTF-8"), - returncode=0, - ), - ): - credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE) - - with pytest.raises(ValueError) as excinfo: - _ = credentials.retrieve_subject_token(None) - - assert excinfo.match( - r"The executable response must contain an expiration_time for successful responses when an output_file has been specified in the configuration." - ) - - @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) - def test_retrieve_subject_token_without_expiration_time_should_fail_when_retrieving_from_output_file( - self, tmpdir - ): - ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE = tmpdir.join( - "actual_output_file" - ) - ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE = { - "command": "command", - "timeout_millis": 30000, - "output_file": ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, - } - ACTUAL_CREDENTIAL_SOURCE = {"executable": ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE} - data = self.EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_ID_TOKEN.copy() - data.pop("expiration_time") - - with open(ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, "w") as output_file: - json.dump(data, output_file) - - credentials = self.make_pluggable(credential_source=ACTUAL_CREDENTIAL_SOURCE) - - with pytest.raises(ValueError) as excinfo: - _ = credentials.retrieve_subject_token(None) - - assert excinfo.match( - r"The executable response must contain an expiration_time for successful responses when an output_file has been specified in the configuration." - ) - os.remove(ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE) - @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) def test_retrieve_subject_token_without_expiration_time_should_pass_when_output_file_not_specified( self, @@ -767,6 +828,36 @@ def test_credential_source_missing_command(self): r"Missing command field. Executable command must be provided." ) + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_credential_source_missing_output_interactive_mode(self): + CREDENTIAL_SOURCE = { + "executable": {"command": self.CREDENTIAL_SOURCE_EXECUTABLE_COMMAND} + } + credentials = self.make_pluggable( + credential_source=CREDENTIAL_SOURCE, interactive=True + ) + with pytest.raises(ValueError) as excinfo: + _ = credentials.retrieve_subject_token(None) + + assert excinfo.match( + r"An output_file must be specified in the credential configuration for interactive mode." + ) + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_credential_source_timeout_missing_will_use_default_timeout_value(self): + CREDENTIAL_SOURCE = { + "executable": { + "command": self.CREDENTIAL_SOURCE_EXECUTABLE_COMMAND, + "output_file": self.CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, + } + } + credentials = self.make_pluggable(credential_source=CREDENTIAL_SOURCE) + + assert ( + credentials._credential_source_executable_timeout_millis + == pluggable.EXECUTABLE_TIMEOUT_MILLIS_DEFAULT + ) + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) def test_credential_source_timeout_small(self): with pytest.raises(ValueError) as excinfo: @@ -795,6 +886,51 @@ def test_credential_source_timeout_large(self): assert excinfo.match(r"Timeout must be between 5 and 120 seconds.") + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_credential_source_interactive_timeout_missing_will_use_default_interactive_timeout_value( + self + ): + CREDENTIAL_SOURCE = { + "executable": { + "command": self.CREDENTIAL_SOURCE_EXECUTABLE_COMMAND, + "output_file": self.CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, + } + } + credentials = self.make_pluggable(credential_source=CREDENTIAL_SOURCE) + + assert ( + credentials._credential_source_executable_interactive_timeout_millis + == pluggable.EXECUTABLE_INTERACTIVE_TIMEOUT_MILLIS_DEFAULT + ) + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_credential_source_interactive_timeout_small(self): + with pytest.raises(ValueError) as excinfo: + CREDENTIAL_SOURCE = { + "executable": { + "command": self.CREDENTIAL_SOURCE_EXECUTABLE_COMMAND, + "interactive_timeout_millis": 30000 - 1, + "output_file": self.CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, + } + } + _ = self.make_pluggable(credential_source=CREDENTIAL_SOURCE) + + assert excinfo.match(r"Interactive timeout must be between 5 and 30 minutes.") + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_credential_source_interactive_timeout_large(self): + with pytest.raises(ValueError) as excinfo: + CREDENTIAL_SOURCE = { + "executable": { + "command": self.CREDENTIAL_SOURCE_EXECUTABLE_COMMAND, + "interactive_timeout_millis": 1800000 + 1, + "output_file": self.CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, + } + } + _ = self.make_pluggable(credential_source=CREDENTIAL_SOURCE) + + assert excinfo.match(r"Interactive timeout must be between 5 and 30 minutes.") + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) def test_retrieve_subject_token_executable_fail(self): with mock.patch( @@ -812,6 +948,120 @@ def test_retrieve_subject_token_executable_fail(self): r"Executable exited with non-zero return code 1. Error: None" ) + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_retrieve_subject_token_non_workforce_fail_interactive_mode(self): + credentials = self.make_pluggable( + credential_source=self.CREDENTIAL_SOURCE, interactive=True + ) + with pytest.raises(ValueError) as excinfo: + _ = credentials.retrieve_subject_token(None) + + assert excinfo.match(r"Interactive mode is only enabled for workforce pool.") + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_retrieve_subject_token_executable_fail_interactive_mode(self): + with mock.patch( + "subprocess.run", + return_value=subprocess.CompletedProcess( + args=[], stdout=None, returncode=1 + ), + ): + credentials = self.make_pluggable( + audience=WORKFORCE_AUDIENCE, + credential_source=self.CREDENTIAL_SOURCE, + interactive=True, + ) + + with pytest.raises(exceptions.RefreshError) as excinfo: + _ = credentials.retrieve_subject_token(None) + + assert excinfo.match( + r"Executable exited with non-zero return code 1. Error: None" + ) + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "0"}) + def test_revoke_failed_executable_not_allowed(self): + credentials = self.make_pluggable( + credential_source=self.CREDENTIAL_SOURCE, interactive=True + ) + with pytest.raises(ValueError) as excinfo: + _ = credentials.revoke(None) + + assert excinfo.match(r"Executables need to be explicitly allowed") + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_revoke_failed(self): + testData = { + "non_interactive_mode": { + "interactive": False, + "expectErrType": ValueError, + "expectErrPattern": r"Revoke is only enabled under interactive mode.", + }, + "executable_failed": { + "returncode": 1, + "expectErrType": exceptions.RefreshError, + "expectErrPattern": r"Auth revoke failed on executable.", + }, + "response_validation_missing_version": { + "response": {}, + "expectErrType": ValueError, + "expectErrPattern": r"The executable response is missing the version field.", + }, + "response_validation_invalid_version": { + "response": {"version": 2}, + "expectErrType": exceptions.RefreshError, + "expectErrPattern": r"Executable returned unsupported version.", + }, + "response_validation_missing_success": { + "response": {"version": 1}, + "expectErrType": ValueError, + "expectErrPattern": r"The executable response is missing the success field.", + }, + "response_validation_failed_with_success_field_is_false": { + "response": {"version": 1, "success": False}, + "expectErrType": exceptions.RefreshError, + "expectErrPattern": r"Revoke failed with unsuccessful response.", + }, + } + for data in testData.values(): + with mock.patch( + "subprocess.run", + return_value=subprocess.CompletedProcess( + args=[], + stdout=json.dumps(data.get("response")).encode("UTF-8"), + returncode=data.get("returncode", 0), + ), + ): + credentials = self.make_pluggable( + audience=WORKFORCE_AUDIENCE, + service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + credential_source=self.CREDENTIAL_SOURCE, + interactive=data.get("interactive", True), + ) + + with pytest.raises(data.get("expectErrType")) as excinfo: + _ = credentials.revoke(None) + + assert excinfo.match(data.get("expectErrPattern")) + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_revoke_successfully(self): + ACTUAL_RESPONSE = {"version": 1, "success": True} + with mock.patch( + "subprocess.run", + return_value=subprocess.CompletedProcess( + args=[], + stdout=json.dumps(ACTUAL_RESPONSE).encode("utf-8"), + returncode=0, + ), + ): + credentials = self.make_pluggable( + audience=WORKFORCE_AUDIENCE, + credential_source=self.CREDENTIAL_SOURCE, + interactive=True, + ) + _ = credentials.revoke(None) + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) def test_retrieve_subject_token_python_2(self): with mock.patch("sys.version_info", (2, 7)): @@ -821,3 +1071,17 @@ def test_retrieve_subject_token_python_2(self): _ = credentials.retrieve_subject_token(None) assert excinfo.match(r"Pluggable auth is only supported for python 3.6+") + + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_revoke_subject_token_python_2(self): + with mock.patch("sys.version_info", (2, 7)): + credentials = self.make_pluggable( + audience=WORKFORCE_AUDIENCE, + credential_source=self.CREDENTIAL_SOURCE, + interactive=True, + ) + + with pytest.raises(exceptions.RefreshError) as excinfo: + _ = credentials.revoke(None) + + assert excinfo.match(r"Pluggable auth is only supported for python 3.6+") From 1a9c6f3edd9a9c45a196ef70879dce71f9d69382 Mon Sep 17 00:00:00 2001 From: Jin Date: Thu, 6 Oct 2022 09:52:56 -0700 Subject: [PATCH 631/966] fix: adding one more pattern to relax the regex check for sts and impersonation url endpoints (#1158) * fix: relax regex for sts and impersonation url with one more pattern * adding more testcases for invalid url * chore: update token --- .../google/auth/external_account.py | 2 ++ .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../tests/test_external_account.py | 20 ++++++++++++++++++ 3 files changed, 22 insertions(+) diff --git a/packages/google-auth/google/auth/external_account.py b/packages/google-auth/google/auth/external_account.py index a87f92ea4d36..eb216fb72cf2 100644 --- a/packages/google-auth/google/auth/external_account.py +++ b/packages/google-auth/google/auth/external_account.py @@ -443,6 +443,7 @@ def validate_token_url(token_url): "^sts\\.googleapis\\.com$", "^sts\\.[^\\.\\s\\/\\\\]+\\.googleapis\\.com$", "^[^\\.\\s\\/\\\\]+\\-sts\\.googleapis\\.com$", + "^sts\\-[^\\.\\s\\/\\\\]+\\.p\\.googleapis\\.com$", ] if not Credentials.is_valid_url(_TOKEN_URL_PATTERNS, token_url): @@ -455,6 +456,7 @@ def validate_service_account_impersonation_url(url): "^iamcredentials\\.googleapis\\.com$", "^iamcredentials\\.[^\\.\\s\\/\\\\]+\\.googleapis\\.com$", "^[^\\.\\s\\/\\\\]+\\-iamcredentials\\.googleapis\\.com$", + "^iamcredentials\\-[^\\.\\s\\/\\\\]+\\.p\\.googleapis\\.com$", ] if not Credentials.is_valid_url( diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 2bef7d971ed407a5c67431d403624e65b2c28877..78f7254cb92a2cbd1bdb0b492bb9168b9fe9ff68 100644 GIT binary patch literal 10324 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 Date: Tue, 11 Oct 2022 19:48:12 +0000 Subject: [PATCH 632/966] feat: Introduce the functionality to override token_uri in credentials (#1159) * feat: Introduce the functionality to override token_uri in credentials * update rt --- .../google/auth/compute_engine/credentials.py | 27 ++++++++++- .../google-auth/google/auth/credentials.py | 15 ++++++ .../google/auth/external_account.py | 26 ++++++++++- .../google-auth/google/oauth2/credentials.py | 17 +++++++ .../google/oauth2/service_account.py | 38 ++++++++++++++- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../tests/compute_engine/test_credentials.py | 44 ++++++++++++++++++ .../tests/oauth2/test_credentials.py | 12 +++++ .../tests/oauth2/test_service_account.py | 14 ++++++ .../tests/test_external_account.py | 27 +++++++++++ 10 files changed, 216 insertions(+), 4 deletions(-) diff --git a/packages/google-auth/google/auth/compute_engine/credentials.py b/packages/google-auth/google/auth/compute_engine/credentials.py index 59b48dae68c9..e97fabea94a2 100644 --- a/packages/google-auth/google/auth/compute_engine/credentials.py +++ b/packages/google-auth/google/auth/compute_engine/credentials.py @@ -154,7 +154,11 @@ def with_scopes(self, scopes, default_scopes=None): _DEFAULT_TOKEN_URI = "https://www.googleapis.com/oauth2/v4/token" -class IDTokenCredentials(credentials.CredentialsWithQuotaProject, credentials.Signing): +class IDTokenCredentials( + credentials.CredentialsWithQuotaProject, + credentials.Signing, + credentials.CredentialsWithTokenUri, +): """Open ID Connect ID Token-based service account credentials. These credentials relies on the default service account of a GCE instance. @@ -302,6 +306,27 @@ def with_quota_project(self, quota_project_id): quota_project_id=quota_project_id, ) + @_helpers.copy_docstring(credentials.CredentialsWithTokenUri) + def with_token_uri(self, token_uri): + + # since the signer is already instantiated, + # the request is not needed + if self._use_metadata_identity_endpoint: + raise ValueError( + "If use_metadata_identity_endpoint is set, token_uri" " must not be set" + ) + else: + return self.__class__( + None, + service_account_email=self._service_account_email, + token_uri=token_uri, + target_audience=self._target_audience, + additional_claims=self._additional_claims.copy(), + signer=self.signer, + use_metadata_identity_endpoint=False, + quota_project_id=self.quota_project_id, + ) + def _make_authorization_grant_assertion(self): """Create the OAuth 2.0 assertion. This assertion is used during the OAuth 2.0 grant to acquire an diff --git a/packages/google-auth/google/auth/credentials.py b/packages/google-auth/google/auth/credentials.py index 004fde9c2f37..2735892d4db2 100644 --- a/packages/google-auth/google/auth/credentials.py +++ b/packages/google-auth/google/auth/credentials.py @@ -150,6 +150,21 @@ def with_quota_project(self, quota_project_id): raise NotImplementedError("This credential does not support quota project.") +class CredentialsWithTokenUri(Credentials): + """Abstract base for credentials supporting ``with_token_uri`` factory""" + + def with_token_uri(self, token_uri): + """Returns a copy of these credentials with a modified token uri. + + Args: + token_uri (str): The uri to use for fetching/exchanging tokens + + Returns: + google.oauth2.credentials.Credentials: A new credentials instance. + """ + raise NotImplementedError("This credential does not use token uri.") + + class AnonymousCredentials(Credentials): """Credentials that do not provide any authentication information. diff --git a/packages/google-auth/google/auth/external_account.py b/packages/google-auth/google/auth/external_account.py index eb216fb72cf2..c1ba5efa0c40 100644 --- a/packages/google-auth/google/auth/external_account.py +++ b/packages/google-auth/google/auth/external_account.py @@ -55,7 +55,11 @@ @six.add_metaclass(abc.ABCMeta) -class Credentials(credentials.Scoped, credentials.CredentialsWithQuotaProject): +class Credentials( + credentials.Scoped, + credentials.CredentialsWithQuotaProject, + credentials.CredentialsWithTokenUri, +): """Base class for all external account credentials. This is used to instantiate Credentials for exchanging external account @@ -382,6 +386,26 @@ def with_quota_project(self, quota_project_id): d.pop("workforce_pool_user_project") return self.__class__(**d) + @_helpers.copy_docstring(credentials.CredentialsWithTokenUri) + def with_token_uri(self, token_uri): + d = dict( + audience=self._audience, + subject_token_type=self._subject_token_type, + token_url=token_uri, + credential_source=self._credential_source, + service_account_impersonation_url=self._service_account_impersonation_url, + service_account_impersonation_options=self._service_account_impersonation_options, + client_id=self._client_id, + client_secret=self._client_secret, + quota_project_id=self._quota_project_id, + scopes=self._scopes, + default_scopes=self._default_scopes, + workforce_pool_user_project=self._workforce_pool_user_project, + ) + if not self.is_workforce_pool: + d.pop("workforce_pool_user_project") + return self.__class__(**d) + def _initialize_impersonated_credentials(self): """Generates an impersonated credentials. diff --git a/packages/google-auth/google/oauth2/credentials.py b/packages/google-auth/google/oauth2/credentials.py index 4cc502fea49d..8f1c3dda470b 100644 --- a/packages/google-auth/google/oauth2/credentials.py +++ b/packages/google-auth/google/oauth2/credentials.py @@ -254,6 +254,23 @@ def with_quota_project(self, quota_project_id): enable_reauth_refresh=self._enable_reauth_refresh, ) + @_helpers.copy_docstring(credentials.CredentialsWithTokenUri) + def with_token_uri(self, token_uri): + + return self.__class__( + self.token, + refresh_token=self.refresh_token, + id_token=self.id_token, + token_uri=token_uri, + client_id=self.client_id, + client_secret=self.client_secret, + scopes=self.scopes, + default_scopes=self.default_scopes, + quota_project_id=self.quota_project_id, + rapt_token=self.rapt_token, + enable_reauth_refresh=self._enable_reauth_refresh, + ) + @_helpers.copy_docstring(credentials.Credentials) def refresh(self, request): scopes = self._scopes if self._scopes is not None else self._default_scopes diff --git a/packages/google-auth/google/oauth2/service_account.py b/packages/google-auth/google/oauth2/service_account.py index 5c4f340fa0a9..0989750db566 100644 --- a/packages/google-auth/google/oauth2/service_account.py +++ b/packages/google-auth/google/oauth2/service_account.py @@ -84,7 +84,10 @@ class Credentials( - credentials.Signing, credentials.Scoped, credentials.CredentialsWithQuotaProject + credentials.Signing, + credentials.Scoped, + credentials.CredentialsWithQuotaProject, + credentials.CredentialsWithTokenUri, ): """Service account credentials @@ -364,6 +367,22 @@ def with_quota_project(self, quota_project_id): always_use_jwt_access=self._always_use_jwt_access, ) + @_helpers.copy_docstring(credentials.CredentialsWithTokenUri) + def with_token_uri(self, token_uri): + + return self.__class__( + self._signer, + service_account_email=self._service_account_email, + default_scopes=self._default_scopes, + scopes=self._scopes, + token_uri=token_uri, + subject=self._subject, + project_id=self._project_id, + quota_project_id=self._quota_project_id, + additional_claims=self._additional_claims.copy(), + always_use_jwt_access=self._always_use_jwt_access, + ) + def _make_authorization_grant_assertion(self): """Create the OAuth 2.0 assertion. @@ -455,7 +474,11 @@ def signer_email(self): return self._service_account_email -class IDTokenCredentials(credentials.Signing, credentials.CredentialsWithQuotaProject): +class IDTokenCredentials( + credentials.Signing, + credentials.CredentialsWithQuotaProject, + credentials.CredentialsWithTokenUri, +): """Open ID Connect ID Token-based service account credentials. These credentials are largely similar to :class:`.Credentials`, but instead @@ -627,6 +650,17 @@ def with_quota_project(self, quota_project_id): quota_project_id=quota_project_id, ) + @_helpers.copy_docstring(credentials.CredentialsWithTokenUri) + def with_token_uri(self, token_uri): + return self.__class__( + self._signer, + service_account_email=self._service_account_email, + token_uri=token_uri, + target_audience=self._target_audience, + additional_claims=self._additional_claims.copy(), + quota_project_id=self._quota_project_id, + ) + def _make_authorization_grant_assertion(self): """Create the OAuth 2.0 assertion. diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 78f7254cb92a2cbd1bdb0b492bb9168b9fe9ff68..37bd829047df8f49c333ff8429aba105b6256177 100644 GIT binary patch literal 10324 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 m(FEmGkUwT^F)4XOoasezkp Date: Fri, 14 Oct 2022 01:28:39 +0000 Subject: [PATCH 633/966] chore: update refresh token (#1164) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 37bd829047df8f49c333ff8429aba105b6256177..69e4225b85adfdb5798cc583243f723faafe94ba 100644 GIT binary patch literal 10324 zcmV-aD67{BB>?tKRTDh(Jk3z)%*?O!_mAS=4x>GqyuyTA^O`Z*(m7J6IgS#lPyni{ zhrC(X$b3d$KfHI+fp-?Y^^Hki971bWlk_$vuz*X@?Tc^H=hnw60Mg=P=_)uG1hvVm zMMm}Boq#3;g4`o@MAj?uu0wv(`94BKtLmtX&)0LNQKzWs{*IHn)p{L2WJ8Ss0i?7` zQ8A{yj#}6e0g#dZJp0E(ze4D=W~}UmqP9~=(L^8HxZb{qTfqo?*fl&TtkarwcW7l#XLY%sM>1^aDRJsVTh{_K)@d zCy11hD+PDCb%z@bLsByT*7$niBwTbLp6)`UO`;5K1~#LdSSX1SCv3*2N_VBLjYX7_ z(5gu-mFh3WGGQH|eV@ZuBJGgiZpf9%{h`2L%l+>hux3;lGn_+&^nM*U4=eLhLP zi?`zbGThRjpZf8UttufMBX^wFbx=p=Jf+rT;e=q%CBU#VKRsaq3GAzc+X!fe!912f z*(Fr7o~J_!7>EV7_ryGSgomL?kbkr$IAQhmfF0BER^cJXsOv9{(vhewjMNjZ#a=1o z+qxvzew*58wR*bH^^n8IK4)5@H+BA%+C==4$?||fB>3c4;;|D*~=ZG@$ zwhpc<2Ip2Dy$mc$woa8~!qfhfD@mFzSHGmXOP|36B{09tiewp>rme?=WS-XFUHZAB zVoLhF_>1HSVlCm}9#epyMCndi0D;IQy7M3bFO*l17m6^l4VQv0idC5_T@@5|nI$P& zecHwa+h&zqWw;uLjY9)kTrW`fc0xguZKU^@gkYnM=+*z4x{F=T8aGT(>U? zVGha7EGI&_(X()(`G1nWav_Tq8DBP83i|uYwZsW|arXb@TsN#pno2~l7e!4o4`k%^f|GsD? zMs_QfPBL7Gouq_VW3AR`bg@GCFn|N}N*tYW8F`^gWxxqt8%#SGcSwpw=NOG`%?#Nd zc`Y*9wy69G{RfEY)87xe!4-KsMrSun<0ctmVRDLRlUA!%KX=PksiTul`S0H#upJhx zHrG-%!z2sU;qzJw=~f1xDe?XrZ%Qt?J18;<-xdw-@rXCJm7#Bieau; zum?|3TZa#FkTqKQ_J5pFw<&nPy#*i$dhnJF?d1_Jq{#GDT6C4fJA~q*g`G2TgmqP3 zcE)Zt5G|jXsI7lLc5d`e!%A}glj`EM1=dRs_b3W6(ScTJyFWKYDAlu2N+1#mApTyO0XEK{m3jK^?uxUJVD$pdyRtgVa7od+T6 zjEzB+32!c@|7j4^&ISI7ESWE)QGATYy;2s306g0*%wRHbVe%XUs*lT8C|VJ7x8X9Z zD>`AFK5yi=FD7L4;pNXYUUaC}*3z4~b0vqb=j+G3`aD=lL!M*8_aZ+Ok3znh@OAe# zf(w_IvB#U-XHLe2A^#RQntcds%l~}>J_9uib92Jd4m{RT9|;Vg9bB zYu02Rp1{wf=o75J0?pI^pLd~OMOtBgT?3h{PmMlC3Mc~{l0@<_AHuX0xO^I^q|PzF z@w|c-@;$)YJH1`Hz*AnHPb_)OJSz@=rPX&q>eTaU8|j0E3&w7$zh~>(3kVGrX+cqY zr~uel35cpniX6Jl9uO1vcexKr_l4{U84bOQR@i6A*f4c?%GKfVAAeTSOB8Ys*t&5< z;(~WiV(eLyKe zLr;L6V=$3M;SYiQZ*;8eg40Hj>CVH;PW)tK=FdVcYs`5Q;S(BQ3Uq~r*O;sa2p{Q> z2)k1v2(Ps>1)u!QUjGqVEF-;~{xP3w1bnAO5_TFYws%@1@(v3*{tJ!_O<5ms6u$s1 zBnPrJR;Y43ye5Q<1UJ5O5=)43F^Cq;Lj%RVtla`QuAYk-q;0*M^ z4(=x)pa()Qug|+>)nqtme?x)!6cbGu!W^*?O34g3Zi~(?614}FeLq=d9$dVChi*+4 z--+dM4w00Qgy{OskU_iPD38PmR9L*J{;iQFjT%JfG7((fZJr4!>?tgKHFwSg{F4-t za5m@u;O*E+=|fOG$mfEOj96`_Itv`szDyuCeug0V0e#-9hbC2;Yo%4}STAAa<-Dzat2j7+~(sF<{Jr$#Dbq9eB|9&*!;- z?{>^vZ3R+f&|m1>POF7o(tV>Sz?Y#b7cGH0o3A&-IDr->bFwJK-2Tr#YTc@UaHD&dn1wp3X1X&MRv3 z%EYQKmduI*RIcL3BuNuyUMKIS z-=3V)h=CRx39RSd>q)|(a8Lth;yCRr4Bt&Z%;auW81Y1N5fIMPTUv@wE+KQoXLlR1 z3IW@J?7rcX-0@Y&Fsyu|quCM>zOi{xTk<@IB}Wn(EBn^jWi_QJyxw{I24^RL;&qKh zbNk-wzAJ;<(TFG4)LUG6hVn*_b%+1>QKu0>BvbXTpFm~c-&gNdT zz{@?p8Xk1=rr&p=j3P=q)$efOWV9*jib{UzGQ5Wbmq#vBTC!o%5h!prqXn1}k`vS} z!KUCr_Vb5S6uxj0A=;-LN88KF)_+2W$8qmrn|7HEI(5Zqb%La)PLi@|i(Lgn5Q|lP-kZ?A8!EHSd z+wdw=7#}CyO;|<<;X_XgR5b5?&H(-v(&$mwJy$9#BUkTy(r6m!N-LNG`c%L0=4{kr zr#y+YAKh$cQx4c5sDQLeTFWYXw#O}xg7U?)W*At(EtSmx#PNK+sK_jP@^bKdm_WuC ztu()~GP24zTush(lYBd9R1)6Y0cLN@JBHt+frXTI+T*63d?`wr_kK?aG83_lR!>XLoO=Zc3?D*(LkEg0vxhSm6K@s%7}9-*$Y(T;`?YIWXeEirDD|aZzL%Yf#ml4I~@<( z3#eTwu=3^amG3T5%Q`SBp>dHQ%|hnOgQn4+XdJw~eglOnu|D<<-6?C73STmJzi}k8 zNUGdL?S3$=2cn`fgLZsNamKnRd!if%NTt$A;ILhew{9NverkX)TEccdD6HPzMuMhh zs%Lr=(b{Q+w+D8l5yj2pQge8!r-hJiBAHp8fMoD#zqbEyiw_E0BK^K={H8Ws^ zXF>e(cp~KdQA{GRuF(+gqYF~sbOWjZbeF#^#@#yhgmI^6&jDxGIDpU=yZJcWw38`( zNmw&PhJ9K`W2Mlc1MIMLEb$3ebE|`e%DCfL>5ed@7b|O}4;IsvW(qu45dw>Zlh~`1XaxRB|y^G8BnN& zi`a=e!4>LrbVhxC<~2^{#FyHnZ0fHq9~x|cpO@M*DZ6h<*ntgUI^+y<(;jDeWtk-Q z*I}o6Ui}MUfe)HzV{T!V5}t)UJJqI+BN=)ZW#?ljT|?G{W^--4bG`>X-c;@GuM5bT z>8UNbF8r}{)qWki--A*MIx@%`frff1wpXoTL@7N_q|tHs@`1Z&(d5&r>D=1N^gym< zn?p&exD93uFK$W3Hu{G=&QCt2{UIaA;;(m3`*;$A1H3a#Ttk+)3~e&q3+qk3Ym3$( zK}lT6F`~>nwm1d7uUQRV?nJ)(YXbd+8(?z~b{GQc%lpbG;nteBFYDBVG1n7+Xvh=& zMI5O4B!%1hcGG!6TcJ!lGqS}q=dD8eRLLHC>RywAB_Q1kxO)itpG{PnJV)@v;-c(> zJ$|%vrc`u>FEF2UY!0LV|6<7j}0)@HOJridDshmLF9JNyC{lZ_{`c;v&;qE?a z*m*dsJHZ@dac_;+CYrgD-+p*7RbxJl&i0Tc`5{m{n3KA(#HW?CtqsnZ*1>Eak2N+=DZD z&r|{u_2gkJy;r5vP$)k2?=B-4sXS|AN^fP}tw+(y*Lf6In@w!k>1vQ}o|B9D(%u}A zXS7>J5WH#Xgd?AVLxqC;4RL9bj|G(P- za~lGHWHjwWVgr{lLa`~H)32oAf4^47$#2vl{7~sB8iA~m5GtzX&O32DpdKTS)n4(jbiop;*t#Q#q(PnzN;juq!jaVcc5e>XK;(v9-Y^_~LH%2ZP0fij9rcAqM7bAYwh;2P_g`8Pja$}!ITqO%e9`JWgV|62 z1Lx-l#wwIl%$5st$!+sn;M!yR>)t81)JjgdF6}#vR^F0c49roTKyH$flqR;=s_PK3 zUvtZWX44tu);%8Z`jU772FUW5&{9n)&aV#V?`ZnIIvN*2{HP4&4SV3;hNCp`1rTHC zGUT<`n)`rYbb?L?`djPrxz-OX>r4@SgZs8;hfN|;t4-KH*8I?QtwU>s`IQsb2#M_# z0Rhw=63}QQA|NB30u3UnmKxBFdob_0g0MhA-(JbkQP=?uapG~Sq0}|P0S$R{Hil*S zF%G1i`j~3RGZW3Iq?FrL3gM8ubv}XN5SeN1s$2n?@+#HVN_WuHH}& zkY3Ur(M-;nLu+!WrK07*91ZJ z1~ldjIfD(DGSqQ8!oV)YY3ridjzXZL7S``F*r3qm@;#D(XGu^95lo#6o~wQ(q#jyB zQkCukp4}ag3>W?Ars6B^TCmc?QtqzFy!eAz7MU0uTVI^x{&O8=zx?zRbh}d-8|w~W z@V_49PJF7kHZa3g!!~Y``A@}+3H3F)taZlF|{n{J-caP73KFN}jQ%o5qJGeYooORP;B;pDiwx<41);H5P z#$57T6cZYulH=$lO!Ea1mSA&;qN%!=k&I~=bhdw(neeny5^9hq#7mEGS(gW(OW6f% z`{@&y=}6k4kDJR|B*V>oqzF9X*{lkUcYNM+QpV^|7d5sZE?49> z8qqPEo=X%?xGPQY%Tl)Md#-bfVP-A^zrI+r){BDnJFx6VEep0wRJpixE*g$PJ$Jf` z08x9vgE)z8Q<6qZ8rh?6)h!QwYRjBW_NdChkpDEUSlM*W20dsw7=0l(4Sd$nP+nP2 zQP1k#Zh_PAqV&SySGc;zvsWbNWUzFFrS&ybCbEB98X{Pw=L>TY#WvK7e?NW&ORQ5W zJH4WHHMnujTXC1au$gg*a~Lreq;=S}eE(&B=Jdo1*hC$u1z159^7N6vQPq=nMr{LU zd;Ztii&bjc)ca;Rqj5le9@PzxVqOH-ILM4(Pn$UWQj?+gv+J<|2^P?g_A(qPi8=$d zQ(TduJ9aTyuI)RLXAs122C)5KUWlfforJL0PCR5xA!yAhsRZE>Um2W@b1Qsi z^N6*oVc=O`1$ekHU_sk~+c{_)8rbfPZDbVC52{R7RCo)ua)P|^CV+1I#=#X+ZS-sJ zcF-z@cy{y(U#Vt#L!B!u4-|=70U(ZN8pM8JCr_zT$9T39k`9;<*c`R9`@Hdj_PnlI zC8u!hA8teTpzI8(bFOR)nwwp{4AKXWN3m+j$DQnJY#X6I)2M_xa5=V?Z$lV66UddX zw?o9pMZEZ2H>QTKSBa5d%`l4Otb!h#K0rq@u?7_}l}O1Z?gE&jFsKxxr4lq-871R) zt?mWN6Q^&sm#bI*R5hwKUaAfrTXi=9a$q=8o(dYC8atGa1?lDaOeBgpPdVxQaV|0` zvJdiw!-CT_7abwR2ue(i41`W1W2G>TAfM@5GMTjXA2ya&D_w}JZpE5Vzm2(CC@cJfVWjcz;>Y zQ+GoTM~qbwy1CQcT?S3gugimv$~DAdTNvvEm=LRERD+xY-5rg06}FJ6g)OvoM_5CY zgoxuBI10J0$TrNeWDH~bo{9YZV)XqryV*>tAywpKre~3FDTxx2F4pJ@cz#hT<=2;`7I6CNO2xpj=xe6F{ks%Y45#UxHqnu$Nn6CU+fSc zMp3Xro3!C2Lt0q51*omB-P5y2P|i97;q*01Rv@HE2E{)qp0oxXLJT(7VCJ zZ*AUJKL616Va{{s?d`vOUV`rca>v}EkJ{ctO1f#Aq1zH$&y0_+e%5&_sgm4Z?#(Pb zD(4HAPB;3z+u35(@R+DHZw%K4J$Ga-E3@nbrY9?gR!Yun6MK^HirpV6ZiC1&lN!3_ z`dZGeO_@syncYpVROrzv%yH=TkD}-|II`qKW=6} zCzqmiEy%MWMsuUH%RvNO1{*>pJ-_2|oY%rw#ZMa_1>!&q%g+nkJyBHQ?zK#s7VvcF z7rUZ4)6?^xL;SOAc*Z4l51gvPn{84`s^UbW~9I zco%>>U+XQxCTs#x+8!rFFaUjVp)@tmiTVviScdn+?k`SdR64IcVtXc9qeq}{n*91n z(&1;hN;&%?cZ{>~!JKj&BkFk;Wfma4R_bm!WE%xVvV8%}?5R-XLF&uLKHf!4_{l@% zrb@^}*jHYkGZ$oWaBILI36G#G*lecEk)N-A`3?c|bzlmQCOs_|cf*d;n{dWT&ph2@ zk9-p4nhxJWbh34lhaL$21Z#M22&mq{$RJS1H1Ov~#LlIl2Fac6BA5W~&7l5|sbp3G z*=exAA8fm`#lDE;A!56EI22|P|H6hiHcN6L!O~DO-`a8P@e zyB~kj8<$Iszh<=zd6)4wyIq>chE#6!AA3t7_WR;T!CRfSu|z>+ztP_+?b{Ff`;gJu zFr*+&`}xW?-i%I0ZB?~GKGqTBwmgH;m~Fj|rXraE-rly)zJgi?vaJK^Tby03o+v+y zkbq~w&AS0i4D+(gDvn;g=MON#lkXkLpb(@Yw&=ONskef}*bS<^x%NnPFnf$CX@N0B zErWES{PL>fu?fL7r`FUp>$vX3jSxh%wa<!@gn7SM@QMt5H+zs26Gv=lTe zrjS|FVH^nCR^R9;sM_s&%Yx0pP?@31hdurj>ONZLkb_9n_GSZw3W#v3Z`S}a?3mbi1<*Fvv6&zQEvocbBE!EvYnBAd;HKP|r;Ff;K} zA0J^i5_qbDaI53Fr=jcg{?r;8^%o9%ulUC&w2<8O1+jPq1S4i_Lux&5L^heSxR*S@ z9mqcwuUO;YsqOdgI0|8C*N4*gFvyY!5Ub`xN^VuoQPnk-QWf2sb-_F16Db4O`FT~r zQ?|X-c$n(1)=g?>Ra7f_(->W+!JVh_8hR@k~tX*AX z?bnvDL+UXvjUXrBKId}p<7K?_N-Djk|7-AUU}wn?BL&9rCtY5!szSCW##P+AeUZCk z5eEVBPgK~*c(Ux+Mf9uRY}YsFQ1K3Ji$bJ_HL8ci9}z;hME|~sjHm);*1!;|d5)N| zE{v*%drarGcYLZz_QbYiMh{oo*X@beS6yppt+;9A_aMa759dhcki< z$Oj_%u3%Uq9rZs?+X}RACdnyU4*CIczQOEqi#sYHU$0`kVU&p4xhpHR0L;nKCaEP5nWk^ z#)fMH3)&Zj`gvDvtUJQ0C{M+C^gpk)e;ja*hJ7#@W;jkdc^}Mc4T_HRc>mez zR1F(=byr&v48Mw=pRbhy*vI#}#ixud_Xc_dsQ}!1h@jx|fy4Z&Hq*q(41TN-9YsvJ z%ZxKXb?d3R4ucBtu$%%_9j0r3Co$aRk{;0ij{~;!2AHWH?-`ASjMnF`{#oK4UtEh8 zGKxp6jWu}7d;6{XovNqaJONB5QG6j6D}bX7$+M@1&kNSR>8i|E;lw~kyDb!G<*{WJ z%of=67J?+|54^yr$uoUBh;&&L%S@;2o@P-{+q#c&y_CBIv)tFmQOKVbpNh(kD6?lt z840o;9=#;XKd^@Z{jML1#@PwLCh0B4)`ge19A4$r52zlqHC zCZ>pt($%}uogp0ATQ@D{N==e*%|G|pd+}(mbYF>ih((Y&%|@9CU5?p!8?4om3{LnV zasM@ykD?wXw1APu@Shy$;PAawV7sU`rQH#%1fiMn(n_R)L!o27>zX1W)T%Fe)lfvs zT~i`7s?>|5s*yssY)ku=TX-XWo?VaF?@hg``LOR~Du0qM_&6JLA zWDaGPyfv6Dfxh7I;}iGZwPS2_8+(Pal!#;LO#KYgKLiPNF>Yl|YHM#V9W0R(lmVldOb3r}{e_kVlA&`f@yuEwUw_GwQggC(z;T1#y zZ%DurN_FM02?&wnp%E|%jWFmVf=%E2)PF(zl5d((U{ zm=6WPm6kw*t$4KGKAY1yus~t`p$~RNlhsF%4#et+^Y3;$U6&J5IV{qG0+LnHeRNs~ zgi_IvGgUog840bH?NriFtM@;??PKg8whqde<_QVwKYnaA@qXh$FgqJSRG@>muzY#e zAAH>Yt-&XcQ5e@K6D|WB=vB4aTsP<7HJLsout=O<@#uP`DmWo3fe2N-evslJNY*~%x4+bsWZ=+* zza1O1-~huoKE@|mY^8aKS|Y6_v}QfTV+@onH`&NPkF6oPxOATk-OCxhGu! literal 10324 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 Date: Fri, 14 Oct 2022 00:52:27 -0400 Subject: [PATCH 634/966] feat: adds new external account authorized user credentials (#1160) --- packages/google-auth/google/auth/_default.py | 25 + .../auth/external_account_authorized_user.py | 290 +++++++++++ packages/google-auth/google/oauth2/sts.py | 76 ++- .../external_account_authorized_user.json | 9 + packages/google-auth/tests/oauth2/test_sts.py | 85 ++++ packages/google-auth/tests/test__default.py | 27 + .../test_external_account_authorized_user.py | 463 ++++++++++++++++++ 7 files changed, 948 insertions(+), 27 deletions(-) create mode 100644 packages/google-auth/google/auth/external_account_authorized_user.py create mode 100644 packages/google-auth/tests/data/external_account_authorized_user.json create mode 100644 packages/google-auth/tests/test_external_account_authorized_user.py diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index a2a07800a953..bef09659bfc7 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -35,12 +35,14 @@ _AUTHORIZED_USER_TYPE = "authorized_user" _SERVICE_ACCOUNT_TYPE = "service_account" _EXTERNAL_ACCOUNT_TYPE = "external_account" +_EXTERNAL_ACCOUNT_AUTHORIZED_USER_TYPE = "external_account_authorized_user" _IMPERSONATED_SERVICE_ACCOUNT_TYPE = "impersonated_service_account" _GDCH_SERVICE_ACCOUNT_TYPE = "gdch_service_account" _VALID_TYPES = ( _AUTHORIZED_USER_TYPE, _SERVICE_ACCOUNT_TYPE, _EXTERNAL_ACCOUNT_TYPE, + _EXTERNAL_ACCOUNT_AUTHORIZED_USER_TYPE, _IMPERSONATED_SERVICE_ACCOUNT_TYPE, _GDCH_SERVICE_ACCOUNT_TYPE, ) @@ -158,6 +160,12 @@ def _load_credentials_from_info( default_scopes=default_scopes, request=request, ) + + elif credential_type == _EXTERNAL_ACCOUNT_AUTHORIZED_USER_TYPE: + credentials, project_id = _get_external_account_authorized_user_credentials( + filename, info, request + ) + elif credential_type == _IMPERSONATED_SERVICE_ACCOUNT_TYPE: credentials, project_id = _get_impersonated_service_account_credentials( filename, info, scopes @@ -363,6 +371,23 @@ def _get_external_account_credentials( return credentials, credentials.get_project_id(request=request) +def _get_external_account_authorized_user_credentials( + filename, info, scopes=None, default_scopes=None, request=None +): + try: + from google.auth import external_account_authorized_user + + credentials = external_account_authorized_user.Credentials.from_info(info) + except ValueError: + raise exceptions.DefaultCredentialsError( + "Failed to load external account authorized user credentials from {}".format( + filename + ) + ) + + return credentials, None + + def _get_authorized_user_credentials(filename, info, scopes=None): from google.oauth2 import credentials diff --git a/packages/google-auth/google/auth/external_account_authorized_user.py b/packages/google-auth/google/auth/external_account_authorized_user.py new file mode 100644 index 000000000000..c0ffc49f31b9 --- /dev/null +++ b/packages/google-auth/google/auth/external_account_authorized_user.py @@ -0,0 +1,290 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""External Account Authorized User Credentials. +This module provides credentials based on OAuth 2.0 access and refresh tokens. +These credentials usually access resources on behalf of a user (resource +owner). + +Specifically, these are sourced using external identities via Workforce Identity Federation. + +Obtaining the initial access and refresh token can be done through the Google Cloud CLI. + +Example credential: +{ + "type": "external_account_authorized_user", + "audience": "//iam.googleapis.com/locations/global/workforcePools/$WORKFORCE_POOL_ID/providers/$PROVIDER_ID", + "refresh_token": "refreshToken", + "token_url": "https://sts.googleapis.com/v1/oauth/token", + "token_info_url": "https://sts.googleapis.com/v1/instrospect", + "client_id": "clientId", + "client_secret": "clientSecret" +} +""" + +import datetime +import io +import json + +from google.auth import _helpers +from google.auth import credentials +from google.auth import exceptions +from google.oauth2 import sts +from google.oauth2 import utils + +_EXTERNAL_ACCOUNT_AUTHORIZED_USER_JSON_TYPE = "external_account_authorized_user" + + +class Credentials( + credentials.CredentialsWithQuotaProject, + credentials.ReadOnlyScoped, + credentials.CredentialsWithTokenUri, +): + """Credentials for External Account Authorized Users. + + This is used to instantiate Credentials for exchanging refresh tokens from + authorized users for Google access token and authorizing requests to Google + APIs. + + The credentials are considered immutable. If you want to modify the + quota project, use `with_quota_project` and if you want to modify the token + uri, use `with_token_uri`. + """ + + def __init__( + self, + token=None, + expiry=None, + refresh_token=None, + audience=None, + client_id=None, + client_secret=None, + token_url=None, + token_info_url=None, + revoke_url=None, + quota_project_id=None, + ): + """Instantiates a external account authorized user credentials object. + + Args: + token (str): The OAuth 2.0 access token. Can be None if refresh information + is provided. + expiry (datetime.datetime): The optional expiration datetime of the OAuth 2.0 access + token. + refresh_token (str): The optional OAuth 2.0 refresh token. If specified, + credentials can be refreshed. + audience (str): The optional STS audience which contains the resource name for the workforce + pool and the provider identifier in that pool. + client_id (str): The OAuth 2.0 client ID. Must be specified for refresh, can be left as + None if the token can not be refreshed. + client_secret (str): The OAuth 2.0 client secret. Must be specified for refresh, can be + left as None if the token can not be refreshed. + token_url (str): The optional STS token exchange endpoint. Must be specified fro refresh, + can be leftas None if the token can not be refreshed. + token_info_url (str): The optional STS endpoint URL for token introspection. + revoke_url (str): The optional STS endpoint URL for revoking tokens. + quota_project_id (str): The optional project ID used for quota and billing. + This project may be different from the project used to + create the credentials. + + Returns: + google.auth.external_account_authorized_user.Credentials: The + constructed credentials. + """ + if not any((refresh_token, token)): + raise ValueError("Either `refresh_token` or `token` should be set.") + + super(Credentials, self).__init__() + + self.token = token + self.expiry = expiry + self._audience = audience + self._refresh_token = refresh_token + self._token_url = token_url + self._token_info_url = token_info_url + self._client_id = client_id + self._client_secret = client_secret + self._revoke_url = revoke_url + self._quota_project_id = quota_project_id + + self._client_auth = None + if self._client_id: + self._client_auth = utils.ClientAuthentication( + utils.ClientAuthType.basic, self._client_id, self._client_secret + ) + self._sts_client = sts.Client(self._token_url, self._client_auth) + + @property + def info(self): + """Generates the serializable dictionary representation of the current + credentials. + + Returns: + Mapping: The dictionary representation of the credentials. This is the + reverse of the "from_info" method defined in this class. It is + useful for serializing the current credentials so it can deserialized + later. + """ + config_info = self.constructor_args() + config_info.update(type=_EXTERNAL_ACCOUNT_AUTHORIZED_USER_JSON_TYPE) + if config_info["expiry"]: + config_info["expiry"] = config_info["expiry"].isoformat() + "Z" + + return {key: value for key, value in config_info.items() if value is not None} + + def constructor_args(self): + return { + "audience": self._audience, + "refresh_token": self._refresh_token, + "token_url": self._token_url, + "token_info_url": self._token_info_url, + "client_id": self._client_id, + "client_secret": self._client_secret, + "token": self.token, + "expiry": self.expiry, + "revoke_url": self._revoke_url, + "quota_project_id": self._quota_project_id, + } + + @property + def requires_scopes(self): + """ False: OAuth 2.0 credentials have their scopes set when + the initial token is requested and can not be changed.""" + return False + + @property + def is_user(self): + """ True: This credential always represents a user.""" + return True + + def get_project_id(self): + """Retrieves the project ID corresponding to the workload identity or workforce pool. + For workforce pool credentials, it returns the project ID corresponding to + the workforce_pool_user_project. + + When not determinable, None is returned. + """ + + return None + + def to_json(self, strip=None): + """Utility function that creates a JSON representation of this + credential. + Args: + strip (Sequence[str]): Optional list of members to exclude from the + generated JSON. + Returns: + str: A JSON representation of this instance. When converted into + a dictionary, it can be passed to from_info() + to create a new instance. + """ + strip = strip if strip else [] + return json.dumps({k: v for (k, v) in self.info.items() if k not in strip}) + + def refresh(self, request): + """Refreshes the access token. + + Args: + request (google.auth.transport.Request): The object used to make + HTTP requests. + + Raises: + google.auth.exceptions.RefreshError: If the credentials could + not be refreshed. + """ + if not all( + (self._refresh_token, self._token_url, self._client_id, self._client_secret) + ): + raise exceptions.RefreshError( + "The credentials do not contain the necessary fields need to " + "refresh the access token. You must specify refresh_token, " + "token_url, client_id, and client_secret." + ) + + now = _helpers.utcnow() + response_data = self._make_sts_request(request) + + self.token = response_data.get("access_token") + + lifetime = datetime.timedelta(seconds=response_data.get("expires_in")) + self.expiry = now + lifetime + + if "refresh_token" in response_data: + self._refresh_token = response_data["refresh_token"] + + def _make_sts_request(self, request): + return self._sts_client.refresh_token(request, self._refresh_token) + + @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) + def with_quota_project(self, quota_project_id): + kwargs = self.constructor_args() + kwargs.update(quota_project_id=quota_project_id) + return self.__class__(**kwargs) + + @_helpers.copy_docstring(credentials.CredentialsWithTokenUri) + def with_token_uri(self, token_uri): + kwargs = self.constructor_args() + kwargs.update(token_url=token_uri) + return self.__class__(**kwargs) + + @classmethod + def from_info(cls, info, **kwargs): + """Creates a Credentials instance from parsed external account info. + + Args: + info (Mapping[str, str]): The external account info in Google + format. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.external_account_authorized_user.Credentials: The + constructed credentials. + + Raises: + ValueError: For invalid parameters. + """ + expiry = info.get("expiry") + if expiry: + expiry = datetime.datetime.strptime( + expiry.rstrip("Z").split(".")[0], "%Y-%m-%dT%H:%M:%S" + ) + return cls( + audience=info.get("audience"), + refresh_token=info.get("refresh_token"), + token_url=info.get("token_url"), + token_info_url=info.get("token_info_url"), + client_id=info.get("client_id"), + client_secret=info.get("client_secret"), + token=info.get("token"), + expiry=expiry, + revoke_url=info.get("revoke_url"), + quota_project_id=info.get("quota_project_id"), + **kwargs + ) + + @classmethod + def from_file(cls, filename, **kwargs): + """Creates a Credentials instance from an external account json file. + + Args: + filename (str): The path to the external account json file. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.external_account_authorized_user.Credentials: The + constructed credentials. + """ + with io.open(filename, "r", encoding="utf-8") as json_file: + data = json.load(json_file) + return cls.from_info(data, **kwargs) diff --git a/packages/google-auth/google/oauth2/sts.py b/packages/google-auth/google/oauth2/sts.py index ae3c0146b114..5cf06d4d4586 100644 --- a/packages/google-auth/google/oauth2/sts.py +++ b/packages/google-auth/google/oauth2/sts.py @@ -58,6 +58,41 @@ def __init__(self, token_exchange_endpoint, client_authentication=None): super(Client, self).__init__(client_authentication) self._token_exchange_endpoint = token_exchange_endpoint + def _make_request(self, request, headers, request_body): + # Initialize request headers. + request_headers = _URLENCODED_HEADERS.copy() + + # Inject additional headers. + if headers: + for k, v in dict(headers).items(): + request_headers[k] = v + + # Apply OAuth client authentication. + self.apply_client_authentication_options(request_headers, request_body) + + # Execute request. + response = request( + url=self._token_exchange_endpoint, + method="POST", + headers=request_headers, + body=urllib.parse.urlencode(request_body).encode("utf-8"), + ) + + response_body = ( + response.data.decode("utf-8") + if hasattr(response.data, "decode") + else response.data + ) + + # If non-200 response received, translate to OAuthError exception. + if response.status != http_client.OK: + utils.handle_error_response(response_body) + + response_data = json.loads(response_body) + + # Return successful response. + return response_data + def exchange_token( self, request, @@ -102,12 +137,6 @@ def exchange_token( google.auth.exceptions.OAuthError: If the token endpoint returned an error. """ - # Initialize request headers. - headers = _URLENCODED_HEADERS.copy() - # Inject additional headers. - if additional_headers: - for k, v in dict(additional_headers).items(): - headers[k] = v # Initialize request body. request_body = { "grant_type": grant_type, @@ -128,28 +157,21 @@ def exchange_token( for k, v in dict(request_body).items(): if v is None or v == "": del request_body[k] - # Apply OAuth client authentication. - self.apply_client_authentication_options(headers, request_body) - # Execute request. - response = request( - url=self._token_exchange_endpoint, - method="POST", - headers=headers, - body=urllib.parse.urlencode(request_body).encode("utf-8"), - ) + return self._make_request(request, additional_headers, request_body) - response_body = ( - response.data.decode("utf-8") - if hasattr(response.data, "decode") - else response.data - ) + def refresh_token(self, request, refresh_token): + """Exchanges a refresh token for an access token based on the + RFC6749 spec. - # If non-200 response received, translate to OAuthError exception. - if response.status != http_client.OK: - utils.handle_error_response(response_body) - - response_data = json.loads(response_body) + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + subject_token (str): The OAuth 2.0 refresh token. + """ - # Return successful response. - return response_data + return self._make_request( + request, + None, + {"grant_type": "refresh_token", "refresh_token": refresh_token}, + ) diff --git a/packages/google-auth/tests/data/external_account_authorized_user.json b/packages/google-auth/tests/data/external_account_authorized_user.json new file mode 100644 index 000000000000..e0bd20c8fd5c --- /dev/null +++ b/packages/google-auth/tests/data/external_account_authorized_user.json @@ -0,0 +1,9 @@ +{ + "type": "external_account_authorized_user", + "audience": "//iam.googleapis.com/locations/global/workforcePools/$WORKFORCE_POOL_ID/providers/$PROVIDER_ID", + "refresh_token": "refreshToken", + "token_url": "https://sts.googleapis.com/v1/oauth/token", + "token_info_url": "https://sts.googleapis.com/v1/instrospect", + "client_id": "clientId", + "client_secret": "clientSecret" +} diff --git a/packages/google-auth/tests/oauth2/test_sts.py b/packages/google-auth/tests/oauth2/test_sts.py index f61a1d33853a..a543d42a8db6 100644 --- a/packages/google-auth/tests/oauth2/test_sts.py +++ b/packages/google-auth/tests/oauth2/test_sts.py @@ -50,6 +50,11 @@ class TestStsClient(object): "expires_in": 3600, "scope": "scope1 scope2", } + SUCCESS_RESPONSE_WITH_REFRESH = { + "access_token": "abc", + "refresh_token": "xyz", + "expires_in": 3600, + } ERROR_RESPONSE = { "error": "invalid_request", "error_description": "Invalid subject token", @@ -393,3 +398,83 @@ def test_exchange_token_non200_with_reqbody_auth(self): assert excinfo.match( r"Error code invalid_request: Invalid subject token - https://tools.ietf.org/html/rfc6749" ) + + def test_refresh_token_success(self): + """Test refresh token with successful response.""" + client = self.make_client(self.CLIENT_AUTH_BASIC) + request = self.make_mock_request( + status=http_client.OK, data=self.SUCCESS_RESPONSE + ) + + response = client.refresh_token(request, "refreshtoken") + + headers = { + "Authorization": "Basic dXNlcm5hbWU6cGFzc3dvcmQ=", + "Content-Type": "application/x-www-form-urlencoded", + } + request_data = {"grant_type": "refresh_token", "refresh_token": "refreshtoken"} + self.assert_request_kwargs(request.call_args[1], headers, request_data) + assert response == self.SUCCESS_RESPONSE + + def test_refresh_token_success_with_refresh(self): + """Test refresh token with successful response.""" + client = self.make_client(self.CLIENT_AUTH_BASIC) + request = self.make_mock_request( + status=http_client.OK, data=self.SUCCESS_RESPONSE_WITH_REFRESH + ) + + response = client.refresh_token(request, "refreshtoken") + + headers = { + "Authorization": "Basic dXNlcm5hbWU6cGFzc3dvcmQ=", + "Content-Type": "application/x-www-form-urlencoded", + } + request_data = {"grant_type": "refresh_token", "refresh_token": "refreshtoken"} + self.assert_request_kwargs(request.call_args[1], headers, request_data) + assert response == self.SUCCESS_RESPONSE_WITH_REFRESH + + def test_refresh_token_failure(self): + """Test refresh token with failure response.""" + client = self.make_client(self.CLIENT_AUTH_BASIC) + request = self.make_mock_request( + status=http_client.BAD_REQUEST, data=self.ERROR_RESPONSE + ) + + with pytest.raises(exceptions.OAuthError) as excinfo: + client.refresh_token(request, "refreshtoken") + + assert excinfo.match( + r"Error code invalid_request: Invalid subject token - https://tools.ietf.org/html/rfc6749" + ) + + def test__make_request_success(self): + """Test base method with successful response.""" + client = self.make_client(self.CLIENT_AUTH_BASIC) + request = self.make_mock_request( + status=http_client.OK, data=self.SUCCESS_RESPONSE + ) + + response = client._make_request(request, {"a": "b"}, {"c": "d"}) + + headers = { + "Authorization": "Basic dXNlcm5hbWU6cGFzc3dvcmQ=", + "Content-Type": "application/x-www-form-urlencoded", + "a": "b", + } + request_data = {"c": "d"} + self.assert_request_kwargs(request.call_args[1], headers, request_data) + assert response == self.SUCCESS_RESPONSE + + def test_make_request_failure(self): + """Test refresh token with failure response.""" + client = self.make_client(self.CLIENT_AUTH_BASIC) + request = self.make_mock_request( + status=http_client.BAD_REQUEST, data=self.ERROR_RESPONSE + ) + + with pytest.raises(exceptions.OAuthError) as excinfo: + client._make_request(request, {"a": "b"}, {"c": "d"}) + + assert excinfo.match( + r"Error code invalid_request: Invalid subject token - https://tools.ietf.org/html/rfc6749" + ) diff --git a/packages/google-auth/tests/test__default.py b/packages/google-auth/tests/test__default.py index 5ea9c73c5ff7..11d87f4cb357 100644 --- a/packages/google-auth/tests/test__default.py +++ b/packages/google-auth/tests/test__default.py @@ -26,6 +26,7 @@ from google.auth import environment_vars from google.auth import exceptions from google.auth import external_account +from google.auth import external_account_authorized_user from google.auth import identity_pool from google.auth import impersonated_credentials from google.auth import pluggable @@ -151,6 +152,9 @@ DATA_DIR, "impersonated_service_account_service_account_source.json" ) +EXTERNAL_ACCOUNT_AUTHORIZED_USER_FILE = os.path.join( + DATA_DIR, "external_account_authorized_user.json" +) MOCK_CREDENTIALS = mock.Mock(spec=credentials.CredentialsWithQuotaProject) MOCK_CREDENTIALS.with_quota_project.return_value = MOCK_CREDENTIALS @@ -540,6 +544,29 @@ def test__get_explicit_environ_credentials_no_env(): assert _default._get_explicit_environ_credentials() == (None, None) +def test_load_credentials_from_file_external_account_authorized_user(): + credentials, project_id = _default.load_credentials_from_file( + EXTERNAL_ACCOUNT_AUTHORIZED_USER_FILE, request=mock.sentinel.request + ) + + assert isinstance(credentials, external_account_authorized_user.Credentials) + assert project_id is None + + +def test_load_credentials_from_file_external_account_authorized_user_bad_format(tmpdir): + filename = tmpdir.join("external_account_authorized_user_bad.json") + filename.write(json.dumps({"type": "external_account_authorized_user"})) + + with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: + _default.load_credentials_from_file(str(filename)) + + assert excinfo.match( + "Failed to load external account authorized user credentials from {}".format( + str(filename) + ) + ) + + @pytest.mark.parametrize("quota_project_id", [None, "project-foo"]) @LOAD_FILE_PATCH def test__get_explicit_environ_credentials(load, quota_project_id, monkeypatch): diff --git a/packages/google-auth/tests/test_external_account_authorized_user.py b/packages/google-auth/tests/test_external_account_authorized_user.py new file mode 100644 index 000000000000..49c34a9a4019 --- /dev/null +++ b/packages/google-auth/tests/test_external_account_authorized_user.py @@ -0,0 +1,463 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import datetime +import json + +import mock +import pytest # type: ignore +from six.moves import http_client + +from google.auth import exceptions +from google.auth import external_account_authorized_user +from google.auth import transport + +TOKEN_URL = "https://sts.googleapis.com/v1/token" +TOKEN_INFO_URL = "https://sts.googleapis.com/v1/introspect" +REVOKE_URL = "https://sts.googleapis.com/v1/revoke" +PROJECT_NUMBER = "123456" +QUOTA_PROJECT_ID = "654321" +POOL_ID = "POOL_ID" +PROVIDER_ID = "PROVIDER_ID" +AUDIENCE = ( + "//iam.googleapis.com/projects/{}" + "/locations/global/workloadIdentityPools/{}" + "/providers/{}" +).format(PROJECT_NUMBER, POOL_ID, PROVIDER_ID) +REFRESH_TOKEN = "REFRESH_TOKEN" +NEW_REFRESH_TOKEN = "NEW_REFRESH_TOKEN" +ACCESS_TOKEN = "ACCESS_TOKEN" +CLIENT_ID = "username" +CLIENT_SECRET = "password" +# Base64 encoding of "username:password". +BASIC_AUTH_ENCODING = "dXNlcm5hbWU6cGFzc3dvcmQ=" + + +class TestCredentials(object): + @classmethod + def make_credentials( + cls, + audience=AUDIENCE, + refresh_token=REFRESH_TOKEN, + token_url=TOKEN_URL, + token_info_url=TOKEN_INFO_URL, + client_id=CLIENT_ID, + client_secret=CLIENT_SECRET, + **kwargs + ): + return external_account_authorized_user.Credentials( + audience=audience, + refresh_token=refresh_token, + token_url=token_url, + token_info_url=token_info_url, + client_id=client_id, + client_secret=client_secret, + **kwargs + ) + + @classmethod + def make_mock_request(cls, status=http_client.OK, data=None): + # STS token exchange request. + token_response = mock.create_autospec(transport.Response, instance=True) + token_response.status = status + token_response.data = json.dumps(data).encode("utf-8") + responses = [token_response] + + request = mock.create_autospec(transport.Request) + request.side_effect = responses + + return request + + def test_default_state(self): + creds = self.make_credentials() + + assert not creds.expiry + assert not creds.expired + assert not creds.token + assert not creds.valid + assert not creds.requires_scopes + assert creds.is_user + + def test_basic_create(self): + creds = external_account_authorized_user.Credentials( + token=ACCESS_TOKEN, expiry=datetime.datetime.max + ) + + assert creds.expiry == datetime.datetime.max + assert not creds.expired + assert creds.token == ACCESS_TOKEN + assert creds.valid + assert not creds.requires_scopes + assert creds.is_user + + def test_stunted_create(self): + with pytest.raises(ValueError) as excinfo: + self.make_credentials(token=None, refresh_token=None) + + assert excinfo.match(r"Either `refresh_token` or `token` should be set") + + @mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) + def test_refresh_auth_success(self, utcnow): + request = self.make_mock_request( + status=http_client.OK, + data={"access_token": ACCESS_TOKEN, "expires_in": 3600}, + ) + creds = self.make_credentials() + + creds.refresh(request) + + assert creds.expiry == utcnow() + datetime.timedelta(seconds=3600) + assert not creds.expired + assert creds.token == ACCESS_TOKEN + assert creds.valid + assert not creds.requires_scopes + assert creds.is_user + assert creds._refresh_token == REFRESH_TOKEN + + request.assert_called_once_with( + url=TOKEN_URL, + method="POST", + headers={ + "Content-Type": "application/x-www-form-urlencoded", + "Authorization": "Basic " + BASIC_AUTH_ENCODING, + }, + body=("grant_type=refresh_token&refresh_token=" + REFRESH_TOKEN).encode( + "UTF-8" + ), + ) + + @mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) + def test_refresh_auth_success_new_refresh_token(self, utcnow): + request = self.make_mock_request( + status=http_client.OK, + data={ + "access_token": ACCESS_TOKEN, + "expires_in": 3600, + "refresh_token": NEW_REFRESH_TOKEN, + }, + ) + creds = self.make_credentials() + + creds.refresh(request) + + assert creds.expiry == utcnow() + datetime.timedelta(seconds=3600) + assert not creds.expired + assert creds.token == ACCESS_TOKEN + assert creds.valid + assert not creds.requires_scopes + assert creds.is_user + assert creds._refresh_token == NEW_REFRESH_TOKEN + + request.assert_called_once_with( + url=TOKEN_URL, + method="POST", + headers={ + "Content-Type": "application/x-www-form-urlencoded", + "Authorization": "Basic " + BASIC_AUTH_ENCODING, + }, + body=("grant_type=refresh_token&refresh_token=" + REFRESH_TOKEN).encode( + "UTF-8" + ), + ) + + def test_refresh_auth_failure(self): + request = self.make_mock_request( + status=http_client.BAD_REQUEST, + data={ + "error": "invalid_request", + "error_description": "Invalid subject token", + "error_uri": "https://tools.ietf.org/html/rfc6749", + }, + ) + creds = self.make_credentials() + + with pytest.raises(exceptions.OAuthError) as excinfo: + creds.refresh(request) + + assert excinfo.match( + r"Error code invalid_request: Invalid subject token - https://tools.ietf.org/html/rfc6749" + ) + + assert not creds.expiry + assert not creds.expired + assert not creds.token + assert not creds.valid + assert not creds.requires_scopes + assert creds.is_user + + request.assert_called_once_with( + url=TOKEN_URL, + method="POST", + headers={ + "Content-Type": "application/x-www-form-urlencoded", + "Authorization": "Basic " + BASIC_AUTH_ENCODING, + }, + body=("grant_type=refresh_token&refresh_token=" + REFRESH_TOKEN).encode( + "UTF-8" + ), + ) + + def test_refresh_without_refresh_token(self): + request = self.make_mock_request() + creds = self.make_credentials(refresh_token=None, token=ACCESS_TOKEN) + + with pytest.raises(exceptions.RefreshError) as excinfo: + creds.refresh(request) + + assert excinfo.match( + r"The credentials do not contain the necessary fields need to refresh the access token. You must specify refresh_token, token_url, client_id, and client_secret." + ) + + assert not creds.expiry + assert not creds.expired + assert not creds.requires_scopes + assert creds.is_user + + request.assert_not_called() + + def test_refresh_without_token_url(self): + request = self.make_mock_request() + creds = self.make_credentials(token_url=None) + + with pytest.raises(exceptions.RefreshError) as excinfo: + creds.refresh(request) + + assert excinfo.match( + r"The credentials do not contain the necessary fields need to refresh the access token. You must specify refresh_token, token_url, client_id, and client_secret." + ) + + assert not creds.expiry + assert not creds.expired + assert not creds.token + assert not creds.valid + assert not creds.requires_scopes + assert creds.is_user + + request.assert_not_called() + + def test_refresh_without_client_id(self): + request = self.make_mock_request() + creds = self.make_credentials(client_id=None) + + with pytest.raises(exceptions.RefreshError) as excinfo: + creds.refresh(request) + + assert excinfo.match( + r"The credentials do not contain the necessary fields need to refresh the access token. You must specify refresh_token, token_url, client_id, and client_secret." + ) + + assert not creds.expiry + assert not creds.expired + assert not creds.token + assert not creds.valid + assert not creds.requires_scopes + assert creds.is_user + + request.assert_not_called() + + def test_refresh_without_client_secret(self): + request = self.make_mock_request() + creds = self.make_credentials(client_secret=None) + + with pytest.raises(exceptions.RefreshError) as excinfo: + creds.refresh(request) + + assert excinfo.match( + r"The credentials do not contain the necessary fields need to refresh the access token. You must specify refresh_token, token_url, client_id, and client_secret." + ) + + assert not creds.expiry + assert not creds.expired + assert not creds.token + assert not creds.valid + assert not creds.requires_scopes + assert creds.is_user + + request.assert_not_called() + + def test_info(self): + creds = self.make_credentials() + info = creds.info + + assert info["audience"] == AUDIENCE + assert info["refresh_token"] == REFRESH_TOKEN + assert info["token_url"] == TOKEN_URL + assert info["token_info_url"] == TOKEN_INFO_URL + assert info["client_id"] == CLIENT_ID + assert info["client_secret"] == CLIENT_SECRET + assert "token" not in info + assert "expiry" not in info + assert "revoke_url" not in info + assert "quota_project_id" not in info + + def test_info_full(self): + creds = self.make_credentials( + token=ACCESS_TOKEN, + expiry=datetime.datetime.min, + revoke_url=REVOKE_URL, + quota_project_id=QUOTA_PROJECT_ID, + ) + info = creds.info + + assert info["audience"] == AUDIENCE + assert info["refresh_token"] == REFRESH_TOKEN + assert info["token_url"] == TOKEN_URL + assert info["token_info_url"] == TOKEN_INFO_URL + assert info["client_id"] == CLIENT_ID + assert info["client_secret"] == CLIENT_SECRET + assert info["token"] == ACCESS_TOKEN + assert info["expiry"] == datetime.datetime.min.isoformat() + "Z" + assert info["revoke_url"] == REVOKE_URL + assert info["quota_project_id"] == QUOTA_PROJECT_ID + + def test_to_json(self): + creds = self.make_credentials() + json_info = creds.to_json() + info = json.loads(json_info) + + assert info["audience"] == AUDIENCE + assert info["refresh_token"] == REFRESH_TOKEN + assert info["token_url"] == TOKEN_URL + assert info["token_info_url"] == TOKEN_INFO_URL + assert info["client_id"] == CLIENT_ID + assert info["client_secret"] == CLIENT_SECRET + assert "token" not in info + assert "expiry" not in info + assert "revoke_url" not in info + assert "quota_project_id" not in info + + def test_to_json_full(self): + creds = self.make_credentials( + token=ACCESS_TOKEN, + expiry=datetime.datetime.min, + revoke_url=REVOKE_URL, + quota_project_id=QUOTA_PROJECT_ID, + ) + json_info = creds.to_json() + info = json.loads(json_info) + + assert info["audience"] == AUDIENCE + assert info["refresh_token"] == REFRESH_TOKEN + assert info["token_url"] == TOKEN_URL + assert info["token_info_url"] == TOKEN_INFO_URL + assert info["client_id"] == CLIENT_ID + assert info["client_secret"] == CLIENT_SECRET + assert info["token"] == ACCESS_TOKEN + assert info["expiry"] == datetime.datetime.min.isoformat() + "Z" + assert info["revoke_url"] == REVOKE_URL + assert info["quota_project_id"] == QUOTA_PROJECT_ID + + def test_to_json_full_with_strip(self): + creds = self.make_credentials( + token=ACCESS_TOKEN, + expiry=datetime.datetime.min, + revoke_url=REVOKE_URL, + quota_project_id=QUOTA_PROJECT_ID, + ) + json_info = creds.to_json(strip=["token", "expiry"]) + info = json.loads(json_info) + + assert info["audience"] == AUDIENCE + assert info["refresh_token"] == REFRESH_TOKEN + assert info["token_url"] == TOKEN_URL + assert info["token_info_url"] == TOKEN_INFO_URL + assert info["client_id"] == CLIENT_ID + assert info["client_secret"] == CLIENT_SECRET + assert "token" not in info + assert "expiry" not in info + assert info["revoke_url"] == REVOKE_URL + assert info["quota_project_id"] == QUOTA_PROJECT_ID + + def test_get_project_id(self): + creds = self.make_credentials() + assert creds.get_project_id() is None + + def test_with_quota_project(self): + creds = self.make_credentials( + token=ACCESS_TOKEN, + expiry=datetime.datetime.min, + revoke_url=REVOKE_URL, + quota_project_id=QUOTA_PROJECT_ID, + ) + new_creds = creds.with_quota_project(QUOTA_PROJECT_ID) + assert new_creds._audience == creds._audience + assert new_creds._refresh_token == creds._refresh_token + assert new_creds._token_url == creds._token_url + assert new_creds._token_info_url == creds._token_info_url + assert new_creds._client_id == creds._client_id + assert new_creds._client_secret == creds._client_secret + assert new_creds.token == creds.token + assert new_creds.expiry == creds.expiry + assert new_creds._revoke_url == creds._revoke_url + assert new_creds._quota_project_id == QUOTA_PROJECT_ID + + def test_with_token_uri(self): + creds = self.make_credentials( + token=ACCESS_TOKEN, + expiry=datetime.datetime.min, + revoke_url=REVOKE_URL, + quota_project_id=QUOTA_PROJECT_ID, + ) + new_creds = creds.with_token_uri("https://google.com") + assert new_creds._audience == creds._audience + assert new_creds._refresh_token == creds._refresh_token + assert new_creds._token_url == "https://google.com" + assert new_creds._token_info_url == creds._token_info_url + assert new_creds._client_id == creds._client_id + assert new_creds._client_secret == creds._client_secret + assert new_creds.token == creds.token + assert new_creds.expiry == creds.expiry + assert new_creds._revoke_url == creds._revoke_url + assert new_creds._quota_project_id == creds._quota_project_id + + def test_from_file_required_options_only(self, tmpdir): + from_creds = self.make_credentials() + config_file = tmpdir.join("config.json") + config_file.write(from_creds.to_json()) + creds = external_account_authorized_user.Credentials.from_file(str(config_file)) + + assert isinstance(creds, external_account_authorized_user.Credentials) + assert creds._audience == AUDIENCE + assert creds._refresh_token == REFRESH_TOKEN + assert creds._token_url == TOKEN_URL + assert creds._token_info_url == TOKEN_INFO_URL + assert creds._client_id == CLIENT_ID + assert creds._client_secret == CLIENT_SECRET + assert creds.token is None + assert creds.expiry is None + assert creds._revoke_url is None + assert creds._quota_project_id is None + + def test_from_file_full_options(self, tmpdir): + from_creds = self.make_credentials( + token=ACCESS_TOKEN, + expiry=datetime.datetime.min, + revoke_url=REVOKE_URL, + quota_project_id=QUOTA_PROJECT_ID, + ) + config_file = tmpdir.join("config.json") + config_file.write(from_creds.to_json()) + creds = external_account_authorized_user.Credentials.from_file(str(config_file)) + + assert isinstance(creds, external_account_authorized_user.Credentials) + assert creds._audience == AUDIENCE + assert creds._refresh_token == REFRESH_TOKEN + assert creds._token_url == TOKEN_URL + assert creds._token_info_url == TOKEN_INFO_URL + assert creds._client_id == CLIENT_ID + assert creds._client_secret == CLIENT_SECRET + assert creds.token == ACCESS_TOKEN + assert creds.expiry == datetime.datetime.min + assert creds._revoke_url == REVOKE_URL + assert creds._quota_project_id == QUOTA_PROJECT_ID From 1bc65b2a3de7c0dc903473a8d9eee68097b01c1c Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 17 Oct 2022 08:06:20 -0700 Subject: [PATCH 635/966] chore(main): release 2.13.0 (#1156) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 14 ++++++++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index e1f87b4194fd..2122c34d802f 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,20 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.13.0](https://github.com/googleapis/google-auth-library-python/compare/v2.12.0...v2.13.0) (2022-10-14) + + +### Features + +* Adds new external account authorized user credentials ([#1160](https://github.com/googleapis/google-auth-library-python/issues/1160)) ([523f811](https://github.com/googleapis/google-auth-library-python/commit/523f8117a72548d91f1bb169a3c91b095477ce3b)) +* Implement pluggable auth interactive mode ([#1131](https://github.com/googleapis/google-auth-library-python/issues/1131)) ([44a189f](https://github.com/googleapis/google-auth-library-python/commit/44a189fc6185bf33e9d5609cf8d57a846cd98aaf)) +* Introduce the functionality to override token_uri in credentials ([#1159](https://github.com/googleapis/google-auth-library-python/issues/1159)) ([73bc7e9](https://github.com/googleapis/google-auth-library-python/commit/73bc7e9e0e72b6c5057a13cdb4ac996b754ddb58)) + + +### Bug Fixes + +* Adding one more pattern to relax the regex check for sts and impersonation url endpoints ([#1158](https://github.com/googleapis/google-auth-library-python/issues/1158)) ([75326e3](https://github.com/googleapis/google-auth-library-python/commit/75326e397c619a2b58963d3fd9fc1a1a5eda13a0)) + ## [2.12.0](https://github.com/googleapis/google-auth-library-python/compare/v2.11.1...v2.12.0) (2022-09-26) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 576f40705d30..b5e62bac3ccd 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.12.0" +__version__ = "2.13.0" From 25b573b67c0f0ea70593e5133d44daaf99164199 Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Thu, 27 Oct 2022 19:37:13 +0000 Subject: [PATCH 636/966] chore: update system test refresh token (#1170) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10323 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 69e4225b85adfdb5798cc583243f723faafe94ba..b74b69024e63b5c420a9c3a9fb5b38160e82c6ab 100644 GIT binary patch literal 10323 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 literal 10324 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 From 22913c7b4189df5ccaa9953b2bd3fa9eeee857a5 Mon Sep 17 00:00:00 2001 From: Ryan Kohler Date: Thu, 27 Oct 2022 16:39:02 -0700 Subject: [PATCH 637/966] feat: add token_info_url to external account credentials (#1168) * feat: add token_info_url to external account credentials Also some minor refactoring * chore: Refresh system test creds. * Changes requested by @clundin * Changes requested by Leo * validating token info url * comment * chore: Refresh system test creds. * tests * secrets * tests * lint * 2.7 Co-authored-by: Carl Lundin --- .../google/auth/external_account.py | 117 ++++------ packages/google-auth/tests/test_aws.py | 184 ++++++++++++++- .../tests/test_external_account.py | 213 ++++++++---------- .../google-auth/tests/test_identity_pool.py | 190 +++++++++++++++- packages/google-auth/tests/test_pluggable.py | 184 ++++++++++++++- 5 files changed, 695 insertions(+), 193 deletions(-) diff --git a/packages/google-auth/google/auth/external_account.py b/packages/google-auth/google/auth/external_account.py index c1ba5efa0c40..7edb55f63b6d 100644 --- a/packages/google-auth/google/auth/external_account.py +++ b/packages/google-auth/google/auth/external_account.py @@ -78,6 +78,7 @@ def __init__( service_account_impersonation_options=None, client_id=None, client_secret=None, + token_info_url=None, quota_project_id=None, scopes=None, default_scopes=None, @@ -94,6 +95,7 @@ def __init__( impersonation generateAccessToken URL. client_id (Optional[str]): The optional client ID. client_secret (Optional[str]): The optional client secret. + token_info_url (str): The optional STS endpoint URL for token introspection. quota_project_id (Optional[str]): The optional quota project ID. scopes (Optional[Sequence[str]]): Optional scopes to request during the authorization grant. @@ -112,6 +114,7 @@ def __init__( self._audience = audience self._subject_token_type = subject_token_type self._token_url = token_url + self._token_info_url = token_info_url self._credential_source = credential_source self._service_account_impersonation_url = service_account_impersonation_url self._service_account_impersonation_options = ( @@ -125,6 +128,8 @@ def __init__( self._workforce_pool_user_project = workforce_pool_user_project Credentials.validate_token_url(token_url) + if token_info_url: + Credentials.validate_token_url(token_info_url, url_type="token info") if service_account_impersonation_url: Credentials.validate_service_account_impersonation_url( service_account_impersonation_url @@ -161,13 +166,25 @@ def info(self): useful for serializing the current credentials so it can deserialized later. """ - config_info = { - "type": _EXTERNAL_ACCOUNT_JSON_TYPE, + config_info = self._constructor_args() + config_info.update( + type=_EXTERNAL_ACCOUNT_JSON_TYPE, + service_account_impersonation=config_info.pop( + "service_account_impersonation_options", None + ), + ) + config_info.pop("scopes", None) + config_info.pop("default_scopes", None) + return {key: value for key, value in config_info.items() if value is not None} + + def _constructor_args(self): + args = { "audience": self._audience, "subject_token_type": self._subject_token_type, "token_url": self._token_url, + "token_info_url": self._token_info_url, "service_account_impersonation_url": self._service_account_impersonation_url, - "service_account_impersonation": copy.deepcopy( + "service_account_impersonation_options": copy.deepcopy( self._service_account_impersonation_options ) or None, @@ -176,8 +193,12 @@ def info(self): "client_id": self._client_id, "client_secret": self._client_secret, "workforce_pool_user_project": self._workforce_pool_user_project, + "scopes": self._scopes, + "default_scopes": self._default_scopes, } - return {key: value for key, value in config_info.items() if value is not None} + if not self.is_workforce_pool: + args.pop("workforce_pool_user_project") + return args @property def service_account_email(self): @@ -255,25 +276,17 @@ def project_number(self): except ValueError: return None + @property + def token_info_url(self): + """Optional[str]: The STS token introspection endpoint.""" + + return self._token_info_url + @_helpers.copy_docstring(credentials.Scoped) def with_scopes(self, scopes, default_scopes=None): - d = dict( - audience=self._audience, - subject_token_type=self._subject_token_type, - token_url=self._token_url, - credential_source=self._credential_source, - service_account_impersonation_url=self._service_account_impersonation_url, - service_account_impersonation_options=self._service_account_impersonation_options, - client_id=self._client_id, - client_secret=self._client_secret, - quota_project_id=self._quota_project_id, - scopes=scopes, - default_scopes=default_scopes, - workforce_pool_user_project=self._workforce_pool_user_project, - ) - if not self.is_workforce_pool: - d.pop("workforce_pool_user_project") - return self.__class__(**d) + kwargs = self._constructor_args() + kwargs.update(scopes=scopes, default_scopes=default_scopes) + return self.__class__(**kwargs) @abc.abstractmethod def retrieve_subject_token(self, request): @@ -368,43 +381,15 @@ def refresh(self, request): @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) def with_quota_project(self, quota_project_id): # Return copy of instance with the provided quota project ID. - d = dict( - audience=self._audience, - subject_token_type=self._subject_token_type, - token_url=self._token_url, - credential_source=self._credential_source, - service_account_impersonation_url=self._service_account_impersonation_url, - service_account_impersonation_options=self._service_account_impersonation_options, - client_id=self._client_id, - client_secret=self._client_secret, - quota_project_id=quota_project_id, - scopes=self._scopes, - default_scopes=self._default_scopes, - workforce_pool_user_project=self._workforce_pool_user_project, - ) - if not self.is_workforce_pool: - d.pop("workforce_pool_user_project") - return self.__class__(**d) + kwargs = self._constructor_args() + kwargs.update(quota_project_id=quota_project_id) + return self.__class__(**kwargs) @_helpers.copy_docstring(credentials.CredentialsWithTokenUri) def with_token_uri(self, token_uri): - d = dict( - audience=self._audience, - subject_token_type=self._subject_token_type, - token_url=token_uri, - credential_source=self._credential_source, - service_account_impersonation_url=self._service_account_impersonation_url, - service_account_impersonation_options=self._service_account_impersonation_options, - client_id=self._client_id, - client_secret=self._client_secret, - quota_project_id=self._quota_project_id, - scopes=self._scopes, - default_scopes=self._default_scopes, - workforce_pool_user_project=self._workforce_pool_user_project, - ) - if not self.is_workforce_pool: - d.pop("workforce_pool_user_project") - return self.__class__(**d) + kwargs = self._constructor_args() + kwargs.update(token_url=token_uri) + return self.__class__(**kwargs) def _initialize_impersonated_credentials(self): """Generates an impersonated credentials. @@ -422,23 +407,12 @@ def _initialize_impersonated_credentials(self): endpoint returned an error. """ # Return copy of instance with no service account impersonation. - d = dict( - audience=self._audience, - subject_token_type=self._subject_token_type, - token_url=self._token_url, - credential_source=self._credential_source, + kwargs = self._constructor_args() + kwargs.update( service_account_impersonation_url=None, service_account_impersonation_options={}, - client_id=self._client_id, - client_secret=self._client_secret, - quota_project_id=self._quota_project_id, - scopes=self._scopes, - default_scopes=self._default_scopes, - workforce_pool_user_project=self._workforce_pool_user_project, ) - if not self.is_workforce_pool: - d.pop("workforce_pool_user_project") - source_credentials = self.__class__(**d) + source_credentials = self.__class__(**kwargs) # Determine target_principal. target_principal = self.service_account_email @@ -461,7 +435,7 @@ def _initialize_impersonated_credentials(self): ) @staticmethod - def validate_token_url(token_url): + def validate_token_url(token_url, url_type="token"): _TOKEN_URL_PATTERNS = [ "^[^\\.\\s\\/\\\\]+\\.sts\\.googleapis\\.com$", "^sts\\.googleapis\\.com$", @@ -471,7 +445,7 @@ def validate_token_url(token_url): ] if not Credentials.is_valid_url(_TOKEN_URL_PATTERNS, token_url): - raise ValueError("The provided token URL is invalid.") + raise ValueError("The provided {} URL is invalid.".format(url_type)) @staticmethod def validate_service_account_impersonation_url(url): @@ -530,6 +504,7 @@ def from_info(cls, info, **kwargs): audience=info.get("audience"), subject_token_type=info.get("subject_token_type"), token_url=info.get("token_url"), + token_info_url=info.get("token_info_url"), service_account_impersonation_url=info.get( "service_account_impersonation_url" ), diff --git a/packages/google-auth/tests/test_aws.py b/packages/google-auth/tests/test_aws.py index 0a451f3ebdf9..85f5e8dd4385 100644 --- a/packages/google-auth/tests/test_aws.py +++ b/packages/google-auth/tests/test_aws.py @@ -32,13 +32,19 @@ # Base64 encoding of "username:password". BASIC_AUTH_ENCODING = "dXNlcm5hbWU6cGFzc3dvcmQ=" SERVICE_ACCOUNT_EMAIL = "service-1234@service-name.iam.gserviceaccount.com" +SERVICE_ACCOUNT_IMPERSONATION_URL_BASE = ( + "https://us-east1-iamcredentials.googleapis.com" +) +SERVICE_ACCOUNT_IMPERSONATION_URL_ROUTE = "/v1/projects/-/serviceAccounts/{}:generateAccessToken".format( + SERVICE_ACCOUNT_EMAIL +) SERVICE_ACCOUNT_IMPERSONATION_URL = ( - "https://us-east1-iamcredentials.googleapis.com/v1/projects/-" - + "/serviceAccounts/{}:generateAccessToken".format(SERVICE_ACCOUNT_EMAIL) + SERVICE_ACCOUNT_IMPERSONATION_URL_BASE + SERVICE_ACCOUNT_IMPERSONATION_URL_ROUTE ) QUOTA_PROJECT_ID = "QUOTA_PROJECT_ID" SCOPES = ["scope1", "scope2"] TOKEN_URL = "https://sts.googleapis.com/v1/token" +TOKEN_INFO_URL = "https://sts.googleapis.com/v1/introspect" SUBJECT_TOKEN_TYPE = "urn:ietf:params:aws:token-type:aws4_request" AUDIENCE = "//iam.googleapis.com/projects/123456/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID" REGION_URL = "http://169.254.169.254/latest/meta-data/placement/availability-zone" @@ -56,6 +62,89 @@ REQUEST_PARAMS = '{"KeySchema":[{"KeyType":"HASH","AttributeName":"Id"}],"TableName":"TestTable","AttributeDefinitions":[{"AttributeName":"Id","AttributeType":"S"}],"ProvisionedThroughput":{"WriteCapacityUnits":5,"ReadCapacityUnits":5}}' # Each tuple contains the following entries: # region, time, credentials, original_request, signed_request + +VALID_TOKEN_URLS = [ + "https://sts.googleapis.com", + "https://us-east-1.sts.googleapis.com", + "https://US-EAST-1.sts.googleapis.com", + "https://sts.us-east-1.googleapis.com", + "https://sts.US-WEST-1.googleapis.com", + "https://us-east-1-sts.googleapis.com", + "https://US-WEST-1-sts.googleapis.com", + "https://us-west-1-sts.googleapis.com/path?query", + "https://sts-us-east-1.p.googleapis.com", +] +INVALID_TOKEN_URLS = [ + "https://iamcredentials.googleapis.com", + "sts.googleapis.com", + "https://", + "http://sts.googleapis.com", + "https://st.s.googleapis.com", + "https://us-eas\t-1.sts.googleapis.com", + "https:/us-east-1.sts.googleapis.com", + "https://US-WE/ST-1-sts.googleapis.com", + "https://sts-us-east-1.googleapis.com", + "https://sts-US-WEST-1.googleapis.com", + "testhttps://us-east-1.sts.googleapis.com", + "https://us-east-1.sts.googleapis.comevil.com", + "https://us-east-1.us-east-1.sts.googleapis.com", + "https://us-ea.s.t.sts.googleapis.com", + "https://sts.googleapis.comevil.com", + "hhttps://us-east-1.sts.googleapis.com", + "https://us- -1.sts.googleapis.com", + "https://-sts.googleapis.com", + "https://us-east-1.sts.googleapis.com.evil.com", + "https://sts.pgoogleapis.com", + "https://p.googleapis.com", + "https://sts.p.com", + "http://sts.p.googleapis.com", + "https://xyz-sts.p.googleapis.com", + "https://sts-xyz.123.p.googleapis.com", + "https://sts-xyz.p1.googleapis.com", + "https://sts-xyz.p.foo.com", + "https://sts-xyz.p.foo.googleapis.com", +] +VALID_SERVICE_ACCOUNT_IMPERSONATION_URLS = [ + "https://iamcredentials.googleapis.com", + "https://us-east-1.iamcredentials.googleapis.com", + "https://US-EAST-1.iamcredentials.googleapis.com", + "https://iamcredentials.us-east-1.googleapis.com", + "https://iamcredentials.US-WEST-1.googleapis.com", + "https://us-east-1-iamcredentials.googleapis.com", + "https://US-WEST-1-iamcredentials.googleapis.com", + "https://us-west-1-iamcredentials.googleapis.com/path?query", + "https://iamcredentials-us-east-1.p.googleapis.com", +] +INVALID_SERVICE_ACCOUNT_IMPERSONATION_URLS = [ + "https://sts.googleapis.com", + "iamcredentials.googleapis.com", + "https://", + "http://iamcredentials.googleapis.com", + "https://iamcre.dentials.googleapis.com", + "https://us-eas\t-1.iamcredentials.googleapis.com", + "https:/us-east-1.iamcredentials.googleapis.com", + "https://US-WE/ST-1-iamcredentials.googleapis.com", + "https://iamcredentials-us-east-1.googleapis.com", + "https://iamcredentials-US-WEST-1.googleapis.com", + "testhttps://us-east-1.iamcredentials.googleapis.com", + "https://us-east-1.iamcredentials.googleapis.comevil.com", + "https://us-east-1.us-east-1.iamcredentials.googleapis.com", + "https://us-ea.s.t.iamcredentials.googleapis.com", + "https://iamcredentials.googleapis.comevil.com", + "hhttps://us-east-1.iamcredentials.googleapis.com", + "https://us- -1.iamcredentials.googleapis.com", + "https://-iamcredentials.googleapis.com", + "https://us-east-1.iamcredentials.googleapis.com.evil.com", + "https://iamcredentials.pgoogleapis.com", + "https://p.googleapis.com", + "https://iamcredentials.p.com", + "http://iamcredentials.p.googleapis.com", + "https://xyz-iamcredentials.p.googleapis.com", + "https://iamcredentials-xyz.123.p.googleapis.com", + "https://iamcredentials-xyz.p1.googleapis.com", + "https://iamcredentials-xyz.p.foo.com", + "https://iamcredentials-xyz.p.foo.googleapis.com", +] TEST_FIXTURES = [ # GET request (AWS botocore tests). # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/get-vanilla.req @@ -727,6 +816,8 @@ def make_mock_request( def make_credentials( cls, credential_source, + token_url=TOKEN_URL, + token_info_url=TOKEN_INFO_URL, client_id=None, client_secret=None, quota_project_id=None, @@ -737,7 +828,8 @@ def make_credentials( return aws.Credentials( audience=AUDIENCE, subject_token_type=SUBJECT_TOKEN_TYPE, - token_url=TOKEN_URL, + token_url=token_url, + token_info_url=token_info_url, service_account_impersonation_url=service_account_impersonation_url, credential_source=credential_source, client_id=client_id, @@ -796,6 +888,7 @@ def test_from_info_full_options(self, mock_init): "audience": AUDIENCE, "subject_token_type": SUBJECT_TOKEN_TYPE, "token_url": TOKEN_URL, + "token_info_url": TOKEN_INFO_URL, "service_account_impersonation_url": SERVICE_ACCOUNT_IMPERSONATION_URL, "service_account_impersonation": {"token_lifetime_seconds": 2800}, "client_id": CLIENT_ID, @@ -811,6 +904,7 @@ def test_from_info_full_options(self, mock_init): audience=AUDIENCE, subject_token_type=SUBJECT_TOKEN_TYPE, token_url=TOKEN_URL, + token_info_url=TOKEN_INFO_URL, service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, service_account_impersonation_options={"token_lifetime_seconds": 2800}, client_id=CLIENT_ID, @@ -837,6 +931,7 @@ def test_from_info_required_options_only(self, mock_init): audience=AUDIENCE, subject_token_type=SUBJECT_TOKEN_TYPE, token_url=TOKEN_URL, + token_info_url=None, service_account_impersonation_url=None, service_account_impersonation_options={}, client_id=None, @@ -852,6 +947,7 @@ def test_from_file_full_options(self, mock_init, tmpdir): "audience": AUDIENCE, "subject_token_type": SUBJECT_TOKEN_TYPE, "token_url": TOKEN_URL, + "token_info_url": TOKEN_INFO_URL, "service_account_impersonation_url": SERVICE_ACCOUNT_IMPERSONATION_URL, "service_account_impersonation": {"token_lifetime_seconds": 2800}, "client_id": CLIENT_ID, @@ -869,6 +965,7 @@ def test_from_file_full_options(self, mock_init, tmpdir): audience=AUDIENCE, subject_token_type=SUBJECT_TOKEN_TYPE, token_url=TOKEN_URL, + token_info_url=TOKEN_INFO_URL, service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, service_account_impersonation_options={"token_lifetime_seconds": 2800}, client_id=CLIENT_ID, @@ -896,6 +993,7 @@ def test_from_file_required_options_only(self, mock_init, tmpdir): audience=AUDIENCE, subject_token_type=SUBJECT_TOKEN_TYPE, token_url=TOKEN_URL, + token_info_url=None, service_account_impersonation_url=None, service_account_impersonation_options={}, client_id=None, @@ -954,9 +1052,89 @@ def test_info(self): "audience": AUDIENCE, "subject_token_type": SUBJECT_TOKEN_TYPE, "token_url": TOKEN_URL, + "token_info_url": TOKEN_INFO_URL, "credential_source": self.CREDENTIAL_SOURCE, } + def test_token_info_url(self): + credentials = self.make_credentials( + credential_source=self.CREDENTIAL_SOURCE.copy() + ) + + assert credentials.token_info_url == TOKEN_INFO_URL + + def test_token_info_url_custom(self): + for url in VALID_TOKEN_URLS: + credentials = self.make_credentials( + credential_source=self.CREDENTIAL_SOURCE.copy(), + token_info_url=(url + "/introspect"), + ) + + assert credentials.token_info_url == (url + "/introspect") + + def test_token_info_url_bad(self): + for url in INVALID_TOKEN_URLS: + with pytest.raises(ValueError) as excinfo: + self.make_credentials( + credential_source=self.CREDENTIAL_SOURCE.copy(), + token_info_url=(url + "/introspect"), + ) + + assert excinfo.match(r"The provided token info URL is invalid\.") + + def test_token_info_url_negative(self): + credentials = self.make_credentials( + credential_source=self.CREDENTIAL_SOURCE.copy(), token_info_url=None + ) + + assert not credentials.token_info_url + + def test_token_url_custom(self): + for url in VALID_TOKEN_URLS: + credentials = self.make_credentials( + credential_source=self.CREDENTIAL_SOURCE.copy(), + token_url=(url + "/token"), + ) + + assert credentials._token_url == (url + "/token") + + def test_token_url_bad(self): + for url in INVALID_TOKEN_URLS: + with pytest.raises(ValueError) as excinfo: + self.make_credentials( + credential_source=self.CREDENTIAL_SOURCE.copy(), + token_url=(url + "/token"), + ) + + assert excinfo.match(r"The provided token URL is invalid\.") + + def test_service_account_impersonation_url_custom(self): + for url in VALID_SERVICE_ACCOUNT_IMPERSONATION_URLS: + credentials = self.make_credentials( + credential_source=self.CREDENTIAL_SOURCE.copy(), + service_account_impersonation_url=( + url + SERVICE_ACCOUNT_IMPERSONATION_URL_ROUTE + ), + ) + + assert credentials._service_account_impersonation_url == ( + url + SERVICE_ACCOUNT_IMPERSONATION_URL_ROUTE + ) + + def test_service_account_impersonation_url_bad(self): + for url in INVALID_SERVICE_ACCOUNT_IMPERSONATION_URLS: + with pytest.raises(ValueError) as excinfo: + self.make_credentials( + credential_source=self.CREDENTIAL_SOURCE.copy(), + service_account_impersonation_url=( + url + SERVICE_ACCOUNT_IMPERSONATION_URL_ROUTE + ), + ) + + assert excinfo.match( + r"The provided service account impersonation URL is invalid\." + ) + def test_retrieve_subject_token_missing_region_url(self): # When AWS_REGION envvar is not available, region_url is required for # determining the current AWS region. diff --git a/packages/google-auth/tests/test_external_account.py b/packages/google-auth/tests/test_external_account.py index 468152e05bcc..18ac75511e36 100644 --- a/packages/google-auth/tests/test_external_account.py +++ b/packages/google-auth/tests/test_external_account.py @@ -65,37 +65,93 @@ "//iam.googleapis.com/locations//workforcePool/pool-id/providers/provider-id", ] +VALID_TOKEN_URLS = [ + "https://sts.googleapis.com", + "https://us-east-1.sts.googleapis.com", + "https://US-EAST-1.sts.googleapis.com", + "https://sts.us-east-1.googleapis.com", + "https://sts.US-WEST-1.googleapis.com", + "https://us-east-1-sts.googleapis.com", + "https://US-WEST-1-sts.googleapis.com", + "https://us-west-1-sts.googleapis.com/path?query", + "https://sts-us-east-1.p.googleapis.com", +] +INVALID_TOKEN_URLS = [ + "https://iamcredentials.googleapis.com", + "sts.googleapis.com", + "https://", + "http://sts.googleapis.com", + "https://st.s.googleapis.com", + "https://us-eas\t-1.sts.googleapis.com", + "https:/us-east-1.sts.googleapis.com", + "https://US-WE/ST-1-sts.googleapis.com", + "https://sts-us-east-1.googleapis.com", + "https://sts-US-WEST-1.googleapis.com", + "testhttps://us-east-1.sts.googleapis.com", + "https://us-east-1.sts.googleapis.comevil.com", + "https://us-east-1.us-east-1.sts.googleapis.com", + "https://us-ea.s.t.sts.googleapis.com", + "https://sts.googleapis.comevil.com", + "hhttps://us-east-1.sts.googleapis.com", + "https://us- -1.sts.googleapis.com", + "https://-sts.googleapis.com", + "https://us-east-1.sts.googleapis.com.evil.com", + "https://sts.pgoogleapis.com", + "https://p.googleapis.com", + "https://sts.p.com", + "http://sts.p.googleapis.com", + "https://xyz-sts.p.googleapis.com", + "https://sts-xyz.123.p.googleapis.com", + "https://sts-xyz.p1.googleapis.com", + "https://sts-xyz.p.foo.com", + "https://sts-xyz.p.foo.googleapis.com", +] +VALID_SERVICE_ACCOUNT_IMPERSONATION_URLS = [ + "https://iamcredentials.googleapis.com", + "https://us-east-1.iamcredentials.googleapis.com", + "https://US-EAST-1.iamcredentials.googleapis.com", + "https://iamcredentials.us-east-1.googleapis.com", + "https://iamcredentials.US-WEST-1.googleapis.com", + "https://us-east-1-iamcredentials.googleapis.com", + "https://US-WEST-1-iamcredentials.googleapis.com", + "https://us-west-1-iamcredentials.googleapis.com/path?query", + "https://iamcredentials-us-east-1.p.googleapis.com", +] +INVALID_SERVICE_ACCOUNT_IMPERSONATION_URLS = [ + "https://sts.googleapis.com", + "iamcredentials.googleapis.com", + "https://", + "http://iamcredentials.googleapis.com", + "https://iamcre.dentials.googleapis.com", + "https://us-eas\t-1.iamcredentials.googleapis.com", + "https:/us-east-1.iamcredentials.googleapis.com", + "https://US-WE/ST-1-iamcredentials.googleapis.com", + "https://iamcredentials-us-east-1.googleapis.com", + "https://iamcredentials-US-WEST-1.googleapis.com", + "testhttps://us-east-1.iamcredentials.googleapis.com", + "https://us-east-1.iamcredentials.googleapis.comevil.com", + "https://us-east-1.us-east-1.iamcredentials.googleapis.com", + "https://us-ea.s.t.iamcredentials.googleapis.com", + "https://iamcredentials.googleapis.comevil.com", + "hhttps://us-east-1.iamcredentials.googleapis.com", + "https://us- -1.iamcredentials.googleapis.com", + "https://-iamcredentials.googleapis.com", + "https://us-east-1.iamcredentials.googleapis.com.evil.com", + "https://iamcredentials.pgoogleapis.com", + "https://p.googleapis.com", + "https://iamcredentials.p.com", + "http://iamcredentials.p.googleapis.com", + "https://xyz-iamcredentials.p.googleapis.com", + "https://iamcredentials-xyz.123.p.googleapis.com", + "https://iamcredentials-xyz.p1.googleapis.com", + "https://iamcredentials-xyz.p.foo.com", + "https://iamcredentials-xyz.p.foo.googleapis.com", +] + class CredentialsImpl(external_account.Credentials): - def __init__( - self, - audience, - subject_token_type, - token_url, - credential_source, - service_account_impersonation_url=None, - service_account_impersonation_options={}, - client_id=None, - client_secret=None, - quota_project_id=None, - scopes=None, - default_scopes=None, - workforce_pool_user_project=None, - ): - super(CredentialsImpl, self).__init__( - audience=audience, - subject_token_type=subject_token_type, - token_url=token_url, - credential_source=credential_source, - service_account_impersonation_url=service_account_impersonation_url, - service_account_impersonation_options=service_account_impersonation_options, - client_id=client_id, - client_secret=client_secret, - quota_project_id=quota_project_id, - scopes=scopes, - default_scopes=default_scopes, - workforce_pool_user_project=workforce_pool_user_project, - ) + def __init__(self, **kwargs): + super(CredentialsImpl, self).__init__(**kwargs) self._counter = 0 def retrieve_subject_token(self, request): @@ -106,6 +162,7 @@ def retrieve_subject_token(self, request): class TestCredentials(object): TOKEN_URL = "https://sts.googleapis.com/v1/token" + TOKEN_INFO_URL = "https://sts.googleapis.com/v1/introspect" PROJECT_NUMBER = "123456" POOL_ID = "POOL_ID" PROVIDER_ID = "PROVIDER_ID" @@ -165,6 +222,7 @@ def make_credentials( client_id=None, client_secret=None, quota_project_id=None, + token_info_url=None, scopes=None, default_scopes=None, service_account_impersonation_url=None, @@ -174,6 +232,7 @@ def make_credentials( audience=cls.AUDIENCE, subject_token_type=cls.SUBJECT_TOKEN_TYPE, token_url=cls.TOKEN_URL, + token_info_url=token_info_url, service_account_impersonation_url=service_account_impersonation_url, service_account_impersonation_options=service_account_impersonation_options, credential_source=cls.CREDENTIAL_SOURCE, @@ -280,53 +339,14 @@ def assert_resource_manager_request_kwargs( assert "body" not in request_kwargs def test_valid_token_url_shall_pass_validation(self): - valid_urls = [ - "https://sts.googleapis.com", - "https://us-east-1.sts.googleapis.com", - "https://US-EAST-1.sts.googleapis.com", - "https://sts.us-east-1.googleapis.com", - "https://sts.US-WEST-1.googleapis.com", - "https://us-east-1-sts.googleapis.com", - "https://US-WEST-1-sts.googleapis.com", - "https://us-west-1-sts.googleapis.com/path?query", - "https://sts-us-east-1.p.googleapis.com", - ] + valid_urls = VALID_TOKEN_URLS for url in valid_urls: # A valid url shouldn't throw exception and a None value should be returned external_account.Credentials.validate_token_url(url) def test_invalid_token_url_shall_throw_exceptions(self): - invalid_urls = [ - "https://iamcredentials.googleapis.com", - "sts.googleapis.com", - "https://", - "http://sts.googleapis.com", - "https://st.s.googleapis.com", - "https://us-eas\t-1.sts.googleapis.com", - "https:/us-east-1.sts.googleapis.com", - "https://US-WE/ST-1-sts.googleapis.com", - "https://sts-us-east-1.googleapis.com", - "https://sts-US-WEST-1.googleapis.com", - "testhttps://us-east-1.sts.googleapis.com", - "https://us-east-1.sts.googleapis.comevil.com", - "https://us-east-1.us-east-1.sts.googleapis.com", - "https://us-ea.s.t.sts.googleapis.com", - "https://sts.googleapis.comevil.com", - "hhttps://us-east-1.sts.googleapis.com", - "https://us- -1.sts.googleapis.com", - "https://-sts.googleapis.com", - "https://us-east-1.sts.googleapis.com.evil.com", - "https://sts.pgoogleapis.com", - "https://p.googleapis.com", - "https://sts.p.com", - "http://sts.p.googleapis.com", - "https://xyz-sts.p.googleapis.com", - "https://sts-xyz.123.p.googleapis.com", - "https://sts-xyz.p1.googleapis.com", - "https://sts-xyz.p.foo.com", - "https://sts-xyz.p.foo.googleapis.com", - ] + invalid_urls = INVALID_TOKEN_URLS for url in invalid_urls: # An invalid url should throw a ValueError exception @@ -336,53 +356,14 @@ def test_invalid_token_url_shall_throw_exceptions(self): assert excinfo.match("The provided token URL is invalid.") def test_valid_service_account_impersonation_url_shall_pass_validation(self): - valid_urls = [ - "https://iamcredentials.googleapis.com", - "https://us-east-1.iamcredentials.googleapis.com", - "https://US-EAST-1.iamcredentials.googleapis.com", - "https://iamcredentials.us-east-1.googleapis.com", - "https://iamcredentials.US-WEST-1.googleapis.com", - "https://us-east-1-iamcredentials.googleapis.com", - "https://US-WEST-1-iamcredentials.googleapis.com", - "https://us-west-1-iamcredentials.googleapis.com/path?query", - "https://iamcredentials-us-east-1.p.googleapis.com", - ] + valid_urls = VALID_SERVICE_ACCOUNT_IMPERSONATION_URLS for url in valid_urls: # A valid url shouldn't throw exception and a None value should be returned external_account.Credentials.validate_service_account_impersonation_url(url) def test_invalid_service_account_impersonate_url_shall_throw_exceptions(self): - invalid_urls = [ - "https://sts.googleapis.com", - "iamcredentials.googleapis.com", - "https://", - "http://iamcredentials.googleapis.com", - "https://iamcre.dentials.googleapis.com", - "https://us-eas\t-1.iamcredentials.googleapis.com", - "https:/us-east-1.iamcredentials.googleapis.com", - "https://US-WE/ST-1-iamcredentials.googleapis.com", - "https://iamcredentials-us-east-1.googleapis.com", - "https://iamcredentials-US-WEST-1.googleapis.com", - "testhttps://us-east-1.iamcredentials.googleapis.com", - "https://us-east-1.iamcredentials.googleapis.comevil.com", - "https://us-east-1.us-east-1.iamcredentials.googleapis.com", - "https://us-ea.s.t.iamcredentials.googleapis.com", - "https://iamcredentials.googleapis.comevil.com", - "hhttps://us-east-1.iamcredentials.googleapis.com", - "https://us- -1.iamcredentials.googleapis.com", - "https://-iamcredentials.googleapis.com", - "https://us-east-1.iamcredentials.googleapis.com.evil.com", - "https://iamcredentials.pgoogleapis.com", - "https://p.googleapis.com", - "https://iamcredentials.p.com", - "http://iamcredentials.p.googleapis.com", - "https://xyz-iamcredentials.p.googleapis.com", - "https://iamcredentials-xyz.123.p.googleapis.com", - "https://iamcredentials-xyz.p1.googleapis.com", - "https://iamcredentials-xyz.p.foo.com", - "https://iamcredentials-xyz.p.foo.googleapis.com", - ] + invalid_urls = INVALID_SERVICE_ACCOUNT_IMPERSONATION_URLS for url in invalid_urls: # An invalid url should throw a ValueError exception @@ -413,6 +394,8 @@ def test_default_state(self): assert not credentials.scopes assert credentials.requires_scopes assert not credentials.quota_project_id + # Token info url not set yet + assert not credentials.token_info_url def test_invalid_token_url(self): with pytest.raises(ValueError) as excinfo: @@ -515,6 +498,7 @@ def test_with_scopes_full_options_propagated(self): client_secret=CLIENT_SECRET, quota_project_id=self.QUOTA_PROJECT_ID, scopes=self.SCOPES, + token_info_url=self.TOKEN_INFO_URL, default_scopes=["default1"], service_account_impersonation_url=self.SERVICE_ACCOUNT_IMPERSONATION_URL, service_account_impersonation_options={"token_lifetime_seconds": 2800}, @@ -531,6 +515,7 @@ def test_with_scopes_full_options_propagated(self): audience=self.AUDIENCE, subject_token_type=self.SUBJECT_TOKEN_TYPE, token_url=self.TOKEN_URL, + token_info_url=self.TOKEN_INFO_URL, credential_source=self.CREDENTIAL_SOURCE, service_account_impersonation_url=self.SERVICE_ACCOUNT_IMPERSONATION_URL, service_account_impersonation_options={"token_lifetime_seconds": 2800}, @@ -539,7 +524,6 @@ def test_with_scopes_full_options_propagated(self): quota_project_id=self.QUOTA_PROJECT_ID, scopes=["email"], default_scopes=["default2"], - workforce_pool_user_project=None, ) def test_with_token_uri(self): @@ -599,6 +583,7 @@ def test_with_quota_project_full_options_propagated(self): credentials = self.make_credentials( client_id=CLIENT_ID, client_secret=CLIENT_SECRET, + token_info_url=self.TOKEN_INFO_URL, quota_project_id=self.QUOTA_PROJECT_ID, scopes=self.SCOPES, default_scopes=["default1"], @@ -617,6 +602,7 @@ def test_with_quota_project_full_options_propagated(self): audience=self.AUDIENCE, subject_token_type=self.SUBJECT_TOKEN_TYPE, token_url=self.TOKEN_URL, + token_info_url=self.TOKEN_INFO_URL, credential_source=self.CREDENTIAL_SOURCE, service_account_impersonation_url=self.SERVICE_ACCOUNT_IMPERSONATION_URL, service_account_impersonation_options={"token_lifetime_seconds": 2800}, @@ -625,7 +611,6 @@ def test_with_quota_project_full_options_propagated(self): quota_project_id="project-foo", scopes=self.SCOPES, default_scopes=["default1"], - workforce_pool_user_project=None, ) def test_with_invalid_impersonation_target_principal(self): @@ -668,6 +653,7 @@ def test_info_with_full_options(self): client_id=CLIENT_ID, client_secret=CLIENT_SECRET, quota_project_id=self.QUOTA_PROJECT_ID, + token_info_url=self.TOKEN_INFO_URL, service_account_impersonation_url=self.SERVICE_ACCOUNT_IMPERSONATION_URL, service_account_impersonation_options={"token_lifetime_seconds": 2800}, ) @@ -677,6 +663,7 @@ def test_info_with_full_options(self): "audience": self.AUDIENCE, "subject_token_type": self.SUBJECT_TOKEN_TYPE, "token_url": self.TOKEN_URL, + "token_info_url": self.TOKEN_INFO_URL, "service_account_impersonation_url": self.SERVICE_ACCOUNT_IMPERSONATION_URL, "service_account_impersonation": {"token_lifetime_seconds": 2800}, "credential_source": self.CREDENTIAL_SOURCE.copy(), diff --git a/packages/google-auth/tests/test_identity_pool.py b/packages/google-auth/tests/test_identity_pool.py index 3f48675e288a..0b0156eb0a1d 100644 --- a/packages/google-auth/tests/test_identity_pool.py +++ b/packages/google-auth/tests/test_identity_pool.py @@ -32,10 +32,16 @@ # Base64 encoding of "username:password". BASIC_AUTH_ENCODING = "dXNlcm5hbWU6cGFzc3dvcmQ=" SERVICE_ACCOUNT_EMAIL = "service-1234@service-name.iam.gserviceaccount.com" +SERVICE_ACCOUNT_IMPERSONATION_URL_BASE = ( + "https://us-east1-iamcredentials.googleapis.com" +) +SERVICE_ACCOUNT_IMPERSONATION_URL_ROUTE = "/v1/projects/-/serviceAccounts/{}:generateAccessToken".format( + SERVICE_ACCOUNT_EMAIL +) SERVICE_ACCOUNT_IMPERSONATION_URL = ( - "https://us-east1-iamcredentials.googleapis.com/v1/projects/-" - + "/serviceAccounts/{}:generateAccessToken".format(SERVICE_ACCOUNT_EMAIL) + SERVICE_ACCOUNT_IMPERSONATION_URL_BASE + SERVICE_ACCOUNT_IMPERSONATION_URL_ROUTE ) + QUOTA_PROJECT_ID = "QUOTA_PROJECT_ID" SCOPES = ["scope1", "scope2"] DATA_DIR = os.path.join(os.path.dirname(__file__), "data") @@ -51,6 +57,7 @@ JSON_FILE_SUBJECT_TOKEN = JSON_FILE_CONTENT.get(SUBJECT_TOKEN_FIELD_NAME) TOKEN_URL = "https://sts.googleapis.com/v1/token" +TOKEN_INFO_URL = "https://sts.googleapis.com/v1/introspect" SUBJECT_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:jwt" AUDIENCE = "//iam.googleapis.com/projects/123456/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID" WORKFORCE_AUDIENCE = ( @@ -60,6 +67,90 @@ WORKFORCE_POOL_USER_PROJECT = "WORKFORCE_POOL_USER_PROJECT_NUMBER" +VALID_TOKEN_URLS = [ + "https://sts.googleapis.com", + "https://us-east-1.sts.googleapis.com", + "https://US-EAST-1.sts.googleapis.com", + "https://sts.us-east-1.googleapis.com", + "https://sts.US-WEST-1.googleapis.com", + "https://us-east-1-sts.googleapis.com", + "https://US-WEST-1-sts.googleapis.com", + "https://us-west-1-sts.googleapis.com/path?query", + "https://sts-us-east-1.p.googleapis.com", +] +INVALID_TOKEN_URLS = [ + "https://iamcredentials.googleapis.com", + "sts.googleapis.com", + "https://", + "http://sts.googleapis.com", + "https://st.s.googleapis.com", + "https://us-eas\t-1.sts.googleapis.com", + "https:/us-east-1.sts.googleapis.com", + "https://US-WE/ST-1-sts.googleapis.com", + "https://sts-us-east-1.googleapis.com", + "https://sts-US-WEST-1.googleapis.com", + "testhttps://us-east-1.sts.googleapis.com", + "https://us-east-1.sts.googleapis.comevil.com", + "https://us-east-1.us-east-1.sts.googleapis.com", + "https://us-ea.s.t.sts.googleapis.com", + "https://sts.googleapis.comevil.com", + "hhttps://us-east-1.sts.googleapis.com", + "https://us- -1.sts.googleapis.com", + "https://-sts.googleapis.com", + "https://us-east-1.sts.googleapis.com.evil.com", + "https://sts.pgoogleapis.com", + "https://p.googleapis.com", + "https://sts.p.com", + "http://sts.p.googleapis.com", + "https://xyz-sts.p.googleapis.com", + "https://sts-xyz.123.p.googleapis.com", + "https://sts-xyz.p1.googleapis.com", + "https://sts-xyz.p.foo.com", + "https://sts-xyz.p.foo.googleapis.com", +] +VALID_SERVICE_ACCOUNT_IMPERSONATION_URLS = [ + "https://iamcredentials.googleapis.com", + "https://us-east-1.iamcredentials.googleapis.com", + "https://US-EAST-1.iamcredentials.googleapis.com", + "https://iamcredentials.us-east-1.googleapis.com", + "https://iamcredentials.US-WEST-1.googleapis.com", + "https://us-east-1-iamcredentials.googleapis.com", + "https://US-WEST-1-iamcredentials.googleapis.com", + "https://us-west-1-iamcredentials.googleapis.com/path?query", + "https://iamcredentials-us-east-1.p.googleapis.com", +] +INVALID_SERVICE_ACCOUNT_IMPERSONATION_URLS = [ + "https://sts.googleapis.com", + "iamcredentials.googleapis.com", + "https://", + "http://iamcredentials.googleapis.com", + "https://iamcre.dentials.googleapis.com", + "https://us-eas\t-1.iamcredentials.googleapis.com", + "https:/us-east-1.iamcredentials.googleapis.com", + "https://US-WE/ST-1-iamcredentials.googleapis.com", + "https://iamcredentials-us-east-1.googleapis.com", + "https://iamcredentials-US-WEST-1.googleapis.com", + "testhttps://us-east-1.iamcredentials.googleapis.com", + "https://us-east-1.iamcredentials.googleapis.comevil.com", + "https://us-east-1.us-east-1.iamcredentials.googleapis.com", + "https://us-ea.s.t.iamcredentials.googleapis.com", + "https://iamcredentials.googleapis.comevil.com", + "hhttps://us-east-1.iamcredentials.googleapis.com", + "https://us- -1.iamcredentials.googleapis.com", + "https://-iamcredentials.googleapis.com", + "https://us-east-1.iamcredentials.googleapis.com.evil.com", + "https://iamcredentials.pgoogleapis.com", + "https://p.googleapis.com", + "https://iamcredentials.p.com", + "http://iamcredentials.p.googleapis.com", + "https://xyz-iamcredentials.p.googleapis.com", + "https://iamcredentials-xyz.123.p.googleapis.com", + "https://iamcredentials-xyz.p1.googleapis.com", + "https://iamcredentials-xyz.p.foo.com", + "https://iamcredentials-xyz.p.foo.googleapis.com", +] + + class TestCredentials(object): CREDENTIAL_SOURCE_TEXT = {"file": SUBJECT_TOKEN_TEXT_FILE} CREDENTIAL_SOURCE_JSON = { @@ -262,6 +353,8 @@ def make_credentials( cls, audience=AUDIENCE, subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + token_info_url=TOKEN_INFO_URL, client_id=None, client_secret=None, quota_project_id=None, @@ -274,7 +367,8 @@ def make_credentials( return identity_pool.Credentials( audience=audience, subject_token_type=subject_token_type, - token_url=TOKEN_URL, + token_url=token_url, + token_info_url=token_info_url, service_account_impersonation_url=service_account_impersonation_url, credential_source=credential_source, client_id=client_id, @@ -292,6 +386,7 @@ def test_from_info_full_options(self, mock_init): "audience": AUDIENCE, "subject_token_type": SUBJECT_TOKEN_TYPE, "token_url": TOKEN_URL, + "token_info_url": TOKEN_INFO_URL, "service_account_impersonation_url": SERVICE_ACCOUNT_IMPERSONATION_URL, "service_account_impersonation": {"token_lifetime_seconds": 2800}, "client_id": CLIENT_ID, @@ -307,6 +402,7 @@ def test_from_info_full_options(self, mock_init): audience=AUDIENCE, subject_token_type=SUBJECT_TOKEN_TYPE, token_url=TOKEN_URL, + token_info_url=TOKEN_INFO_URL, service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, service_account_impersonation_options={"token_lifetime_seconds": 2800}, client_id=CLIENT_ID, @@ -333,6 +429,7 @@ def test_from_info_required_options_only(self, mock_init): audience=AUDIENCE, subject_token_type=SUBJECT_TOKEN_TYPE, token_url=TOKEN_URL, + token_info_url=None, service_account_impersonation_url=None, service_account_impersonation_options={}, client_id=None, @@ -360,6 +457,7 @@ def test_from_info_workforce_pool(self, mock_init): audience=WORKFORCE_AUDIENCE, subject_token_type=WORKFORCE_SUBJECT_TOKEN_TYPE, token_url=TOKEN_URL, + token_info_url=None, service_account_impersonation_url=None, service_account_impersonation_options={}, client_id=None, @@ -375,6 +473,7 @@ def test_from_file_full_options(self, mock_init, tmpdir): "audience": AUDIENCE, "subject_token_type": SUBJECT_TOKEN_TYPE, "token_url": TOKEN_URL, + "token_info_url": TOKEN_INFO_URL, "service_account_impersonation_url": SERVICE_ACCOUNT_IMPERSONATION_URL, "service_account_impersonation": {"token_lifetime_seconds": 2800}, "client_id": CLIENT_ID, @@ -392,6 +491,7 @@ def test_from_file_full_options(self, mock_init, tmpdir): audience=AUDIENCE, subject_token_type=SUBJECT_TOKEN_TYPE, token_url=TOKEN_URL, + token_info_url=TOKEN_INFO_URL, service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, service_account_impersonation_options={"token_lifetime_seconds": 2800}, client_id=CLIENT_ID, @@ -419,6 +519,7 @@ def test_from_file_required_options_only(self, mock_init, tmpdir): audience=AUDIENCE, subject_token_type=SUBJECT_TOKEN_TYPE, token_url=TOKEN_URL, + token_info_url=None, service_account_impersonation_url=None, service_account_impersonation_options={}, client_id=None, @@ -447,6 +548,7 @@ def test_from_file_workforce_pool(self, mock_init, tmpdir): audience=WORKFORCE_AUDIENCE, subject_token_type=WORKFORCE_SUBJECT_TOKEN_TYPE, token_url=TOKEN_URL, + token_info_url=None, service_account_impersonation_url=None, service_account_impersonation_options={}, client_id=None, @@ -534,6 +636,7 @@ def test_info_with_workforce_pool_user_project(self): "audience": WORKFORCE_AUDIENCE, "subject_token_type": WORKFORCE_SUBJECT_TOKEN_TYPE, "token_url": TOKEN_URL, + "token_info_url": TOKEN_INFO_URL, "credential_source": self.CREDENTIAL_SOURCE_TEXT_URL, "workforce_pool_user_project": WORKFORCE_POOL_USER_PROJECT, } @@ -548,6 +651,7 @@ def test_info_with_file_credential_source(self): "audience": AUDIENCE, "subject_token_type": SUBJECT_TOKEN_TYPE, "token_url": TOKEN_URL, + "token_info_url": TOKEN_INFO_URL, "credential_source": self.CREDENTIAL_SOURCE_TEXT_URL, } @@ -561,6 +665,7 @@ def test_info_with_url_credential_source(self): "audience": AUDIENCE, "subject_token_type": SUBJECT_TOKEN_TYPE, "token_url": TOKEN_URL, + "token_info_url": TOKEN_INFO_URL, "credential_source": self.CREDENTIAL_SOURCE_JSON_URL, } @@ -638,6 +743,85 @@ def test_retrieve_subject_token_file_not_found(self): assert excinfo.match(r"File './not_found.txt' was not found") + def test_token_info_url(self): + credentials = self.make_credentials( + credential_source=self.CREDENTIAL_SOURCE_JSON + ) + + assert credentials.token_info_url == TOKEN_INFO_URL + + def test_token_info_url_custom(self): + for url in VALID_TOKEN_URLS: + credentials = self.make_credentials( + credential_source=self.CREDENTIAL_SOURCE_JSON.copy(), + token_info_url=(url + "/introspect"), + ) + + assert credentials.token_info_url == url + "/introspect" + + def test_token_info_url_bad(self): + for url in INVALID_TOKEN_URLS: + with pytest.raises(ValueError) as excinfo: + self.make_credentials( + credential_source=self.CREDENTIAL_SOURCE_JSON.copy(), + token_info_url=(url + "/introspect"), + ) + + assert excinfo.match(r"The provided token info URL is invalid.") + + def test_token_info_url_negative(self): + credentials = self.make_credentials( + credential_source=self.CREDENTIAL_SOURCE_JSON.copy(), token_info_url=None + ) + + assert not credentials.token_info_url + + def test_token_url_custom(self): + for url in VALID_TOKEN_URLS: + credentials = self.make_credentials( + credential_source=self.CREDENTIAL_SOURCE_JSON.copy(), + token_url=(url + "/token"), + ) + + assert credentials._token_url == (url + "/token") + + def test_token_url_bad(self): + for url in INVALID_TOKEN_URLS: + with pytest.raises(ValueError) as excinfo: + self.make_credentials( + credential_source=self.CREDENTIAL_SOURCE_JSON.copy(), + token_url=(url + "/token"), + ) + + assert excinfo.match(r"The provided token URL is invalid\.") + + def test_service_account_impersonation_url_custom(self): + for url in VALID_SERVICE_ACCOUNT_IMPERSONATION_URLS: + credentials = self.make_credentials( + credential_source=self.CREDENTIAL_SOURCE_JSON.copy(), + service_account_impersonation_url=( + url + SERVICE_ACCOUNT_IMPERSONATION_URL_ROUTE + ), + ) + + assert credentials._service_account_impersonation_url == ( + url + SERVICE_ACCOUNT_IMPERSONATION_URL_ROUTE + ) + + def test_service_account_impersonation_url_bad(self): + for url in INVALID_SERVICE_ACCOUNT_IMPERSONATION_URLS: + with pytest.raises(ValueError) as excinfo: + self.make_credentials( + credential_source=self.CREDENTIAL_SOURCE_JSON.copy(), + service_account_impersonation_url=( + url + SERVICE_ACCOUNT_IMPERSONATION_URL_ROUTE + ), + ) + + assert excinfo.match( + r"The provided service account impersonation URL is invalid\." + ) + def test_refresh_text_file_success_without_impersonation_ignore_default_scopes( self, ): diff --git a/packages/google-auth/tests/test_pluggable.py b/packages/google-auth/tests/test_pluggable.py index 293d5c6edb1b..0c0ebeb061bd 100644 --- a/packages/google-auth/tests/test_pluggable.py +++ b/packages/google-auth/tests/test_pluggable.py @@ -36,18 +36,107 @@ # Base64 encoding of "username:password". BASIC_AUTH_ENCODING = "dXNlcm5hbWU6cGFzc3dvcmQ=" SERVICE_ACCOUNT_EMAIL = "service-1234@service-name.iam.gserviceaccount.com" +SERVICE_ACCOUNT_IMPERSONATION_URL_BASE = ( + "https://us-east1-iamcredentials.googleapis.com" +) +SERVICE_ACCOUNT_IMPERSONATION_URL_ROUTE = "/v1/projects/-/serviceAccounts/{}:generateAccessToken".format( + SERVICE_ACCOUNT_EMAIL +) SERVICE_ACCOUNT_IMPERSONATION_URL = ( - "https://us-east1-iamcredentials.googleapis.com/v1/projects/-" - + "/serviceAccounts/{}:generateAccessToken".format(SERVICE_ACCOUNT_EMAIL) + SERVICE_ACCOUNT_IMPERSONATION_URL_BASE + SERVICE_ACCOUNT_IMPERSONATION_URL_ROUTE ) QUOTA_PROJECT_ID = "QUOTA_PROJECT_ID" SCOPES = ["scope1", "scope2"] SUBJECT_TOKEN_FIELD_NAME = "access_token" TOKEN_URL = "https://sts.googleapis.com/v1/token" +TOKEN_INFO_URL = "https://sts.googleapis.com/v1/introspect" SUBJECT_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:jwt" AUDIENCE = "//iam.googleapis.com/projects/123456/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID" +VALID_TOKEN_URLS = [ + "https://sts.googleapis.com", + "https://us-east-1.sts.googleapis.com", + "https://US-EAST-1.sts.googleapis.com", + "https://sts.us-east-1.googleapis.com", + "https://sts.US-WEST-1.googleapis.com", + "https://us-east-1-sts.googleapis.com", + "https://US-WEST-1-sts.googleapis.com", + "https://us-west-1-sts.googleapis.com/path?query", + "https://sts-us-east-1.p.googleapis.com", +] +INVALID_TOKEN_URLS = [ + "https://iamcredentials.googleapis.com", + "sts.googleapis.com", + "https://", + "http://sts.googleapis.com", + "https://st.s.googleapis.com", + "https://us-eas\t-1.sts.googleapis.com", + "https:/us-east-1.sts.googleapis.com", + "https://US-WE/ST-1-sts.googleapis.com", + "https://sts-us-east-1.googleapis.com", + "https://sts-US-WEST-1.googleapis.com", + "testhttps://us-east-1.sts.googleapis.com", + "https://us-east-1.sts.googleapis.comevil.com", + "https://us-east-1.us-east-1.sts.googleapis.com", + "https://us-ea.s.t.sts.googleapis.com", + "https://sts.googleapis.comevil.com", + "hhttps://us-east-1.sts.googleapis.com", + "https://us- -1.sts.googleapis.com", + "https://-sts.googleapis.com", + "https://us-east-1.sts.googleapis.com.evil.com", + "https://sts.pgoogleapis.com", + "https://p.googleapis.com", + "https://sts.p.com", + "http://sts.p.googleapis.com", + "https://xyz-sts.p.googleapis.com", + "https://sts-xyz.123.p.googleapis.com", + "https://sts-xyz.p1.googleapis.com", + "https://sts-xyz.p.foo.com", + "https://sts-xyz.p.foo.googleapis.com", +] +VALID_SERVICE_ACCOUNT_IMPERSONATION_URLS = [ + "https://iamcredentials.googleapis.com", + "https://us-east-1.iamcredentials.googleapis.com", + "https://US-EAST-1.iamcredentials.googleapis.com", + "https://iamcredentials.us-east-1.googleapis.com", + "https://iamcredentials.US-WEST-1.googleapis.com", + "https://us-east-1-iamcredentials.googleapis.com", + "https://US-WEST-1-iamcredentials.googleapis.com", + "https://us-west-1-iamcredentials.googleapis.com/path?query", + "https://iamcredentials-us-east-1.p.googleapis.com", +] +INVALID_SERVICE_ACCOUNT_IMPERSONATION_URLS = [ + "https://sts.googleapis.com", + "iamcredentials.googleapis.com", + "https://", + "http://iamcredentials.googleapis.com", + "https://iamcre.dentials.googleapis.com", + "https://us-eas\t-1.iamcredentials.googleapis.com", + "https:/us-east-1.iamcredentials.googleapis.com", + "https://US-WE/ST-1-iamcredentials.googleapis.com", + "https://iamcredentials-us-east-1.googleapis.com", + "https://iamcredentials-US-WEST-1.googleapis.com", + "testhttps://us-east-1.iamcredentials.googleapis.com", + "https://us-east-1.iamcredentials.googleapis.comevil.com", + "https://us-east-1.us-east-1.iamcredentials.googleapis.com", + "https://us-ea.s.t.iamcredentials.googleapis.com", + "https://iamcredentials.googleapis.comevil.com", + "hhttps://us-east-1.iamcredentials.googleapis.com", + "https://us- -1.iamcredentials.googleapis.com", + "https://-iamcredentials.googleapis.com", + "https://us-east-1.iamcredentials.googleapis.com.evil.com", + "https://iamcredentials.pgoogleapis.com", + "https://p.googleapis.com", + "https://iamcredentials.p.com", + "http://iamcredentials.p.googleapis.com", + "https://xyz-iamcredentials.p.googleapis.com", + "https://iamcredentials-xyz.123.p.googleapis.com", + "https://iamcredentials-xyz.p1.googleapis.com", + "https://iamcredentials-xyz.p.foo.com", + "https://iamcredentials-xyz.p.foo.googleapis.com", +] + class TestCredentials(object): CREDENTIAL_SOURCE_EXECUTABLE_COMMAND = ( @@ -115,6 +204,8 @@ def make_pluggable( cls, audience=AUDIENCE, subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + token_info_url=TOKEN_INFO_URL, client_id=None, client_secret=None, quota_project_id=None, @@ -128,7 +219,8 @@ def make_pluggable( return pluggable.Credentials( audience=audience, subject_token_type=subject_token_type, - token_url=TOKEN_URL, + token_url=token_url, + token_info_url=token_info_url, service_account_impersonation_url=service_account_impersonation_url, credential_source=credential_source, client_id=client_id, @@ -147,6 +239,7 @@ def test_from_info_full_options(self, mock_init): "audience": AUDIENCE, "subject_token_type": SUBJECT_TOKEN_TYPE, "token_url": TOKEN_URL, + "token_info_url": TOKEN_INFO_URL, "service_account_impersonation_url": SERVICE_ACCOUNT_IMPERSONATION_URL, "service_account_impersonation": {"token_lifetime_seconds": 2800}, "client_id": CLIENT_ID, @@ -162,6 +255,7 @@ def test_from_info_full_options(self, mock_init): audience=AUDIENCE, subject_token_type=SUBJECT_TOKEN_TYPE, token_url=TOKEN_URL, + token_info_url=TOKEN_INFO_URL, service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, service_account_impersonation_options={"token_lifetime_seconds": 2800}, client_id=CLIENT_ID, @@ -188,6 +282,7 @@ def test_from_info_required_options_only(self, mock_init): audience=AUDIENCE, subject_token_type=SUBJECT_TOKEN_TYPE, token_url=TOKEN_URL, + token_info_url=None, service_account_impersonation_url=None, service_account_impersonation_options={}, client_id=None, @@ -203,6 +298,7 @@ def test_from_file_full_options(self, mock_init, tmpdir): "audience": AUDIENCE, "subject_token_type": SUBJECT_TOKEN_TYPE, "token_url": TOKEN_URL, + "token_info_url": TOKEN_INFO_URL, "service_account_impersonation_url": SERVICE_ACCOUNT_IMPERSONATION_URL, "service_account_impersonation": {"token_lifetime_seconds": 2800}, "client_id": CLIENT_ID, @@ -220,6 +316,7 @@ def test_from_file_full_options(self, mock_init, tmpdir): audience=AUDIENCE, subject_token_type=SUBJECT_TOKEN_TYPE, token_url=TOKEN_URL, + token_info_url=TOKEN_INFO_URL, service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, service_account_impersonation_options={"token_lifetime_seconds": 2800}, client_id=CLIENT_ID, @@ -247,6 +344,7 @@ def test_from_file_required_options_only(self, mock_init, tmpdir): audience=AUDIENCE, subject_token_type=SUBJECT_TOKEN_TYPE, token_url=TOKEN_URL, + token_info_url=None, service_account_impersonation_url=None, service_account_impersonation_options={}, client_id=None, @@ -280,9 +378,89 @@ def test_info_with_credential_source(self): "audience": AUDIENCE, "subject_token_type": SUBJECT_TOKEN_TYPE, "token_url": TOKEN_URL, + "token_info_url": TOKEN_INFO_URL, "credential_source": self.CREDENTIAL_SOURCE, } + def test_token_info_url(self): + credentials = self.make_pluggable( + credential_source=self.CREDENTIAL_SOURCE.copy() + ) + + assert credentials.token_info_url == TOKEN_INFO_URL + + def test_token_info_url_custom(self): + for url in VALID_TOKEN_URLS: + credentials = self.make_pluggable( + credential_source=self.CREDENTIAL_SOURCE.copy(), + token_info_url=(url + "/introspect"), + ) + + assert credentials.token_info_url == url + "/introspect" + + def test_token_info_url_bad(self): + for url in INVALID_TOKEN_URLS: + with pytest.raises(ValueError) as excinfo: + self.make_pluggable( + credential_source=self.CREDENTIAL_SOURCE.copy(), + token_info_url=(url + "/introspect"), + ) + + assert excinfo.match(r"The provided token info URL is invalid.") + + def test_token_info_url_negative(self): + credentials = self.make_pluggable( + credential_source=self.CREDENTIAL_SOURCE.copy(), token_info_url=None + ) + + assert not credentials.token_info_url + + def test_token_url_custom(self): + for url in VALID_TOKEN_URLS: + credentials = self.make_pluggable( + credential_source=self.CREDENTIAL_SOURCE.copy(), + token_url=(url + "/token"), + ) + + assert credentials._token_url == (url + "/token") + + def test_token_url_bad(self): + for url in INVALID_TOKEN_URLS: + with pytest.raises(ValueError) as excinfo: + self.make_pluggable( + credential_source=self.CREDENTIAL_SOURCE.copy(), + token_url=(url + "/token"), + ) + + assert excinfo.match(r"The provided token URL is invalid\.") + + def test_service_account_impersonation_url_custom(self): + for url in VALID_SERVICE_ACCOUNT_IMPERSONATION_URLS: + credentials = self.make_pluggable( + credential_source=self.CREDENTIAL_SOURCE.copy(), + service_account_impersonation_url=( + url + SERVICE_ACCOUNT_IMPERSONATION_URL_ROUTE + ), + ) + + assert credentials._service_account_impersonation_url == ( + url + SERVICE_ACCOUNT_IMPERSONATION_URL_ROUTE + ) + + def test_service_account_impersonation_url_bad(self): + for url in INVALID_SERVICE_ACCOUNT_IMPERSONATION_URLS: + with pytest.raises(ValueError) as excinfo: + self.make_pluggable( + credential_source=self.CREDENTIAL_SOURCE.copy(), + service_account_impersonation_url=( + url + SERVICE_ACCOUNT_IMPERSONATION_URL_ROUTE + ), + ) + + assert excinfo.match( + r"The provided service account impersonation URL is invalid\." + ) + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) def test_retrieve_subject_token_successfully(self, tmpdir): ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE = tmpdir.join( From a61d6e0f8e831be51b579ace2ee17e2b64396616 Mon Sep 17 00:00:00 2001 From: Jin Date: Fri, 28 Oct 2022 21:24:42 -0700 Subject: [PATCH 638/966] chore: update token (#1172) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10323 -> 10323 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index b74b69024e63b5c420a9c3a9fb5b38160e82c6ab..79b062223349fdf241980b73500b05710d3d387b 100644 GIT binary patch literal 10323 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 literal 10323 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 From 80f7bc4484e3e559820b35783144c62b46cff4db Mon Sep 17 00:00:00 2001 From: Ryan Kohler Date: Fri, 28 Oct 2022 23:02:20 -0700 Subject: [PATCH 639/966] fix: adding more properties to external_account_authorized_user (#1169) * fix: adding more properties to external_account_authorized_user * Adding token_info_url property * Changes requested by Leo * Changes requested by Leo * Changes requested by Leo and Timur * Empty-Commit Co-authored-by: Leo <39062083+lsirac@users.noreply.github.com> --- .../auth/external_account_authorized_user.py | 69 +++++++++-- .../test_external_account_authorized_user.py | 116 ++++++++++++------ 2 files changed, 142 insertions(+), 43 deletions(-) diff --git a/packages/google-auth/google/auth/external_account_authorized_user.py b/packages/google-auth/google/auth/external_account_authorized_user.py index c0ffc49f31b9..51e7f2058fff 100644 --- a/packages/google-auth/google/auth/external_account_authorized_user.py +++ b/packages/google-auth/google/auth/external_account_authorized_user.py @@ -73,6 +73,7 @@ def __init__( token_url=None, token_info_url=None, revoke_url=None, + scopes=None, quota_project_id=None, ): """Instantiates a external account authorized user credentials object. @@ -90,8 +91,8 @@ def __init__( None if the token can not be refreshed. client_secret (str): The OAuth 2.0 client secret. Must be specified for refresh, can be left as None if the token can not be refreshed. - token_url (str): The optional STS token exchange endpoint. Must be specified fro refresh, - can be leftas None if the token can not be refreshed. + token_url (str): The optional STS token exchange endpoint for refresh. Must be specified for + refresh, can be left as None if the token can not be refreshed. token_info_url (str): The optional STS endpoint URL for token introspection. revoke_url (str): The optional STS endpoint URL for revoking tokens. quota_project_id (str): The optional project ID used for quota and billing. @@ -102,9 +103,6 @@ def __init__( google.auth.external_account_authorized_user.Credentials: The constructed credentials. """ - if not any((refresh_token, token)): - raise ValueError("Either `refresh_token` or `token` should be set.") - super(Credentials, self).__init__() self.token = token @@ -117,6 +115,14 @@ def __init__( self._client_secret = client_secret self._revoke_url = revoke_url self._quota_project_id = quota_project_id + self._scopes = scopes + + if not self.valid and not self.can_refresh: + raise ValueError( + "Token should be created with fields to make it valid (`token` and " + "`expiry`), or fields to allow it to refresh (`refresh_token`, " + "`token_url`, `client_id`, `client_secret`)." + ) self._client_auth = None if self._client_id: @@ -154,20 +160,68 @@ def constructor_args(self): "token": self.token, "expiry": self.expiry, "revoke_url": self._revoke_url, + "scopes": self._scopes, "quota_project_id": self._quota_project_id, } + @property + def scopes(self): + """Optional[str]: The OAuth 2.0 permission scopes.""" + return self._scopes + @property def requires_scopes(self): """ False: OAuth 2.0 credentials have their scopes set when the initial token is requested and can not be changed.""" return False + @property + def client_id(self): + """Optional[str]: The OAuth 2.0 client ID.""" + return self._client_id + + @property + def client_secret(self): + """Optional[str]: The OAuth 2.0 client secret.""" + return self._client_secret + + @property + def audience(self): + """Optional[str]: The STS audience which contains the resource name for the + workforce pool and the provider identifier in that pool.""" + return self._audience + + @property + def refresh_token(self): + """Optional[str]: The OAuth 2.0 refresh token.""" + return self._refresh_token + + @property + def token_url(self): + """Optional[str]: The STS token exchange endpoint for refresh.""" + return self._token_url + + @property + def token_info_url(self): + """Optional[str]: The STS endpoint for token info.""" + return self._token_info_url + + @property + def revoke_url(self): + """Optional[str]: The STS endpoint for token revocation.""" + return self._revoke_url + @property def is_user(self): """ True: This credential always represents a user.""" return True + @property + def can_refresh(self): + return all( + (self._refresh_token, self._token_url, self._client_id, self._client_secret) + ) + def get_project_id(self): """Retrieves the project ID corresponding to the workload identity or workforce pool. For workforce pool credentials, it returns the project ID corresponding to @@ -203,9 +257,7 @@ def refresh(self, request): google.auth.exceptions.RefreshError: If the credentials could not be refreshed. """ - if not all( - (self._refresh_token, self._token_url, self._client_id, self._client_secret) - ): + if not self.can_refresh: raise exceptions.RefreshError( "The credentials do not contain the necessary fields need to " "refresh the access token. You must specify refresh_token, " @@ -270,6 +322,7 @@ def from_info(cls, info, **kwargs): expiry=expiry, revoke_url=info.get("revoke_url"), quota_project_id=info.get("quota_project_id"), + scopes=info.get("scopes"), **kwargs ) diff --git a/packages/google-auth/tests/test_external_account_authorized_user.py b/packages/google-auth/tests/test_external_account_authorized_user.py index 49c34a9a4019..c97d087b3b40 100644 --- a/packages/google-auth/tests/test_external_account_authorized_user.py +++ b/packages/google-auth/tests/test_external_account_authorized_user.py @@ -42,6 +42,8 @@ CLIENT_SECRET = "password" # Base64 encoding of "username:password". BASIC_AUTH_ENCODING = "dXNlcm5hbWU6cGFzc3dvcmQ=" +SCOPES = ["email", "profile"] +NOW = datetime.datetime(1990, 8, 27, 6, 54, 30) class TestCredentials(object): @@ -87,11 +89,22 @@ def test_default_state(self): assert not creds.token assert not creds.valid assert not creds.requires_scopes + assert not creds.scopes + assert not creds.revoke_url + assert creds.token_info_url + assert creds.client_id + assert creds.client_secret assert creds.is_user + assert creds.refresh_token == REFRESH_TOKEN + assert creds.audience == AUDIENCE + assert creds.token_url == TOKEN_URL def test_basic_create(self): creds = external_account_authorized_user.Credentials( - token=ACCESS_TOKEN, expiry=datetime.datetime.max + token=ACCESS_TOKEN, + expiry=datetime.datetime.max, + scopes=SCOPES, + revoke_url=REVOKE_URL, ) assert creds.expiry == datetime.datetime.max @@ -99,15 +112,51 @@ def test_basic_create(self): assert creds.token == ACCESS_TOKEN assert creds.valid assert not creds.requires_scopes + assert creds.scopes == SCOPES assert creds.is_user + assert creds.revoke_url == REVOKE_URL - def test_stunted_create(self): + def test_stunted_create_no_refresh_token(self): with pytest.raises(ValueError) as excinfo: self.make_credentials(token=None, refresh_token=None) - assert excinfo.match(r"Either `refresh_token` or `token` should be set") + assert excinfo.match( + r"Token should be created with fields to make it valid \(`token` and " + r"`expiry`\), or fields to allow it to refresh \(`refresh_token`, " + r"`token_url`, `client_id`, `client_secret`\)\." + ) + + def test_stunted_create_no_token_url(self): + with pytest.raises(ValueError) as excinfo: + self.make_credentials(token=None, token_url=None) + + assert excinfo.match( + r"Token should be created with fields to make it valid \(`token` and " + r"`expiry`\), or fields to allow it to refresh \(`refresh_token`, " + r"`token_url`, `client_id`, `client_secret`\)\." + ) + + def test_stunted_create_no_client_id(self): + with pytest.raises(ValueError) as excinfo: + self.make_credentials(token=None, client_id=None) + + assert excinfo.match( + r"Token should be created with fields to make it valid \(`token` and " + r"`expiry`\), or fields to allow it to refresh \(`refresh_token`, " + r"`token_url`, `client_id`, `client_secret`\)\." + ) - @mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) + def test_stunted_create_no_client_secret(self): + with pytest.raises(ValueError) as excinfo: + self.make_credentials(token=None, client_secret=None) + + assert excinfo.match( + r"Token should be created with fields to make it valid \(`token` and " + r"`expiry`\), or fields to allow it to refresh \(`refresh_token`, " + r"`token_url`, `client_id`, `client_secret`\)\." + ) + + @mock.patch("google.auth._helpers.utcnow", return_value=NOW) def test_refresh_auth_success(self, utcnow): request = self.make_mock_request( status=http_client.OK, @@ -137,7 +186,7 @@ def test_refresh_auth_success(self, utcnow): ), ) - @mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) + @mock.patch("google.auth._helpers.utcnow", return_value=NOW) def test_refresh_auth_success_new_refresh_token(self, utcnow): request = self.make_mock_request( status=http_client.OK, @@ -228,7 +277,7 @@ def test_refresh_without_refresh_token(self): def test_refresh_without_token_url(self): request = self.make_mock_request() - creds = self.make_credentials(token_url=None) + creds = self.make_credentials(token_url=None, token=ACCESS_TOKEN) with pytest.raises(exceptions.RefreshError) as excinfo: creds.refresh(request) @@ -239,8 +288,6 @@ def test_refresh_without_token_url(self): assert not creds.expiry assert not creds.expired - assert not creds.token - assert not creds.valid assert not creds.requires_scopes assert creds.is_user @@ -248,7 +295,7 @@ def test_refresh_without_token_url(self): def test_refresh_without_client_id(self): request = self.make_mock_request() - creds = self.make_credentials(client_id=None) + creds = self.make_credentials(client_id=None, token=ACCESS_TOKEN) with pytest.raises(exceptions.RefreshError) as excinfo: creds.refresh(request) @@ -259,8 +306,6 @@ def test_refresh_without_client_id(self): assert not creds.expiry assert not creds.expired - assert not creds.token - assert not creds.valid assert not creds.requires_scopes assert creds.is_user @@ -268,7 +313,7 @@ def test_refresh_without_client_id(self): def test_refresh_without_client_secret(self): request = self.make_mock_request() - creds = self.make_credentials(client_secret=None) + creds = self.make_credentials(client_secret=None, token=ACCESS_TOKEN) with pytest.raises(exceptions.RefreshError) as excinfo: creds.refresh(request) @@ -279,8 +324,6 @@ def test_refresh_without_client_secret(self): assert not creds.expiry assert not creds.expired - assert not creds.token - assert not creds.valid assert not creds.requires_scopes assert creds.is_user @@ -304,7 +347,7 @@ def test_info(self): def test_info_full(self): creds = self.make_credentials( token=ACCESS_TOKEN, - expiry=datetime.datetime.min, + expiry=NOW, revoke_url=REVOKE_URL, quota_project_id=QUOTA_PROJECT_ID, ) @@ -317,7 +360,7 @@ def test_info_full(self): assert info["client_id"] == CLIENT_ID assert info["client_secret"] == CLIENT_SECRET assert info["token"] == ACCESS_TOKEN - assert info["expiry"] == datetime.datetime.min.isoformat() + "Z" + assert info["expiry"] == NOW.isoformat() + "Z" assert info["revoke_url"] == REVOKE_URL assert info["quota_project_id"] == QUOTA_PROJECT_ID @@ -340,7 +383,7 @@ def test_to_json(self): def test_to_json_full(self): creds = self.make_credentials( token=ACCESS_TOKEN, - expiry=datetime.datetime.min, + expiry=NOW, revoke_url=REVOKE_URL, quota_project_id=QUOTA_PROJECT_ID, ) @@ -354,14 +397,14 @@ def test_to_json_full(self): assert info["client_id"] == CLIENT_ID assert info["client_secret"] == CLIENT_SECRET assert info["token"] == ACCESS_TOKEN - assert info["expiry"] == datetime.datetime.min.isoformat() + "Z" + assert info["expiry"] == NOW.isoformat() + "Z" assert info["revoke_url"] == REVOKE_URL assert info["quota_project_id"] == QUOTA_PROJECT_ID def test_to_json_full_with_strip(self): creds = self.make_credentials( token=ACCESS_TOKEN, - expiry=datetime.datetime.min, + expiry=NOW, revoke_url=REVOKE_URL, quota_project_id=QUOTA_PROJECT_ID, ) @@ -386,7 +429,7 @@ def test_get_project_id(self): def test_with_quota_project(self): creds = self.make_credentials( token=ACCESS_TOKEN, - expiry=datetime.datetime.min, + expiry=NOW, revoke_url=REVOKE_URL, quota_project_id=QUOTA_PROJECT_ID, ) @@ -405,7 +448,7 @@ def test_with_quota_project(self): def test_with_token_uri(self): creds = self.make_credentials( token=ACCESS_TOKEN, - expiry=datetime.datetime.min, + expiry=NOW, revoke_url=REVOKE_URL, quota_project_id=QUOTA_PROJECT_ID, ) @@ -428,36 +471,39 @@ def test_from_file_required_options_only(self, tmpdir): creds = external_account_authorized_user.Credentials.from_file(str(config_file)) assert isinstance(creds, external_account_authorized_user.Credentials) - assert creds._audience == AUDIENCE - assert creds._refresh_token == REFRESH_TOKEN - assert creds._token_url == TOKEN_URL - assert creds._token_info_url == TOKEN_INFO_URL - assert creds._client_id == CLIENT_ID - assert creds._client_secret == CLIENT_SECRET + assert creds.audience == AUDIENCE + assert creds.refresh_token == REFRESH_TOKEN + assert creds.token_url == TOKEN_URL + assert creds.token_info_url == TOKEN_INFO_URL + assert creds.client_id == CLIENT_ID + assert creds.client_secret == CLIENT_SECRET assert creds.token is None assert creds.expiry is None + assert creds.scopes is None assert creds._revoke_url is None assert creds._quota_project_id is None def test_from_file_full_options(self, tmpdir): from_creds = self.make_credentials( token=ACCESS_TOKEN, - expiry=datetime.datetime.min, + expiry=NOW, revoke_url=REVOKE_URL, quota_project_id=QUOTA_PROJECT_ID, + scopes=SCOPES, ) config_file = tmpdir.join("config.json") config_file.write(from_creds.to_json()) creds = external_account_authorized_user.Credentials.from_file(str(config_file)) assert isinstance(creds, external_account_authorized_user.Credentials) - assert creds._audience == AUDIENCE - assert creds._refresh_token == REFRESH_TOKEN - assert creds._token_url == TOKEN_URL - assert creds._token_info_url == TOKEN_INFO_URL - assert creds._client_id == CLIENT_ID - assert creds._client_secret == CLIENT_SECRET + assert creds.audience == AUDIENCE + assert creds.refresh_token == REFRESH_TOKEN + assert creds.token_url == TOKEN_URL + assert creds.token_info_url == TOKEN_INFO_URL + assert creds.client_id == CLIENT_ID + assert creds.client_secret == CLIENT_SECRET assert creds.token == ACCESS_TOKEN - assert creds.expiry == datetime.datetime.min + assert creds.expiry == NOW + assert creds.scopes == SCOPES assert creds._revoke_url == REVOKE_URL assert creds._quota_project_id == QUOTA_PROJECT_ID From d478b4d39b2a7dfc55db9deb30f0539a17eeb12a Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Mon, 31 Oct 2022 20:52:24 +0000 Subject: [PATCH 640/966] feat: Read Quota Project from Environment Variable (#1163) * feat: Read Quota Project from Environment Variable * remove unused code * Update google/auth/credentials.py Co-authored-by: Carl Lundin <108372512+clundin25@users.noreply.github.com> * update rt Co-authored-by: Carl Lundin <108372512+clundin25@users.noreply.github.com> --- packages/google-auth/google/auth/_default.py | 2 ++ .../google-auth/google/auth/credentials.py | 9 +++++++- .../google/auth/environment_vars.py | 4 ++++ .../google-auth/system_tests/secrets.tar.enc | Bin 10323 -> 10324 bytes packages/google-auth/tests/test__default.py | 20 ++++++++++++++++++ 5 files changed, 34 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index bef09659bfc7..8fe168428147 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -479,6 +479,8 @@ def _get_gdch_service_account_credentials(filename, info): def _apply_quota_project_id(credentials, quota_project_id): if quota_project_id: credentials = credentials.with_quota_project(quota_project_id) + else: + credentials = credentials.with_quota_project_from_environment() from google.oauth2 import credentials as authorized_user_credentials diff --git a/packages/google-auth/google/auth/credentials.py b/packages/google-auth/google/auth/credentials.py index 2735892d4db2..ca1032a14579 100644 --- a/packages/google-auth/google/auth/credentials.py +++ b/packages/google-auth/google/auth/credentials.py @@ -16,10 +16,11 @@ """Interfaces for credentials.""" import abc +import os import six -from google.auth import _helpers +from google.auth import _helpers, environment_vars @six.add_metaclass(abc.ABCMeta) @@ -149,6 +150,12 @@ def with_quota_project(self, quota_project_id): """ raise NotImplementedError("This credential does not support quota project.") + def with_quota_project_from_environment(self): + quota_from_env = os.environ.get(environment_vars.GOOGLE_CLOUD_QUOTA_PROJECT) + if quota_from_env: + return self.with_quota_project(quota_from_env) + return self + class CredentialsWithTokenUri(Credentials): """Abstract base for credentials supporting ``with_token_uri`` factory""" diff --git a/packages/google-auth/google/auth/environment_vars.py b/packages/google-auth/google/auth/environment_vars.py index c076dc59da1c..81f31571eb80 100644 --- a/packages/google-auth/google/auth/environment_vars.py +++ b/packages/google-auth/google/auth/environment_vars.py @@ -29,6 +29,10 @@ situations (such as Google App Engine). """ +GOOGLE_CLOUD_QUOTA_PROJECT = "GOOGLE_CLOUD_QUOTA_PROJECT" +"""Environment variable defining the project to be used for +quota and billing.""" + CREDENTIALS = "GOOGLE_APPLICATION_CREDENTIALS" """Environment variable defining the location of Google application default credentials.""" diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 79b062223349fdf241980b73500b05710d3d387b..abfaddd92913cbd9392312fc240a82ee8fe99e47 100644 GIT binary patch literal 10324 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�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 literal 10323 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 diff --git a/packages/google-auth/tests/test__default.py b/packages/google-auth/tests/test__default.py index 11d87f4cb357..26b41b995a2f 100644 --- a/packages/google-auth/tests/test__default.py +++ b/packages/google-auth/tests/test__default.py @@ -1214,3 +1214,23 @@ def test_default_gdch_service_account_credentials(get_adc_path): assert creds._token_uri == "https://service-identity./authenticate" assert creds._ca_cert_path == "/path/to/ca/cert" assert project == "project_foo" + + +@mock.patch.dict(os.environ) +@mock.patch( + "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True +) +def test_quota_project_from_environment(get_adc_path): + get_adc_path.return_value = AUTHORIZED_USER_CLOUD_SDK_WITH_QUOTA_PROJECT_ID_FILE + + credentials, _ = _default.default(quota_project_id=None) + assert credentials.quota_project_id == "quota_project_id" + + quota_from_env = "quota_from_env" + os.environ[environment_vars.GOOGLE_CLOUD_QUOTA_PROJECT] = quota_from_env + credentials, _ = _default.default(quota_project_id=None) + assert credentials.quota_project_id == quota_from_env + + explicit_quota = "explicit_quota" + credentials, _ = _default.default(quota_project_id=explicit_quota) + assert credentials.quota_project_id == explicit_quota From 61e0a3ca91fd09a7e56ae86d1a9b243639cc0c9e Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 31 Oct 2022 14:48:32 -0700 Subject: [PATCH 641/966] chore(main): release 2.14.0 (#1171) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 13 +++++++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 2122c34d802f..a974e525610c 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,19 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.14.0](https://github.com/googleapis/google-auth-library-python/compare/v2.13.0...v2.14.0) (2022-10-31) + + +### Features + +* Add token_info_url to external account credentials ([#1168](https://github.com/googleapis/google-auth-library-python/issues/1168)) ([9adee75](https://github.com/googleapis/google-auth-library-python/commit/9adee75712202234aa0b124a9ca0424654022428)) +* Read Quota Project from Environment Variable ([#1163](https://github.com/googleapis/google-auth-library-python/issues/1163)) ([57b3e42](https://github.com/googleapis/google-auth-library-python/commit/57b3e424927a5d86fbab8b231109a5aae1233745)) + + +### Bug Fixes + +* Adding more properties to external_account_authorized_user ([#1169](https://github.com/googleapis/google-auth-library-python/issues/1169)) ([a12b96d](https://github.com/googleapis/google-auth-library-python/commit/a12b96dcfa7cb58d9171fd7f2a7ea8331a228419)) + ## [2.13.0](https://github.com/googleapis/google-auth-library-python/compare/v2.12.0...v2.13.0) (2022-10-14) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index b5e62bac3ccd..6f8afc72a472 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.13.0" +__version__ = "2.14.0" From 76a7f0cb4610e564df5168297c541d4a002091e6 Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Mon, 7 Nov 2022 17:31:40 +0000 Subject: [PATCH 642/966] fix: Update minimum required version of cryptography in pyopenssl extra (#1176) * fix: Update minimum required version of cryptography in pyopenssl extra * update rst * update rst * update rt --- packages/google-auth/README.rst | 11 +++++++++++ packages/google-auth/setup.py | 2 +- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/README.rst b/packages/google-auth/README.rst index 6e6716173896..1f6b9affb9e9 100644 --- a/packages/google-auth/README.rst +++ b/packages/google-auth/README.rst @@ -22,6 +22,17 @@ For more information on setting up your Python development environment, please r .. _`Python Development Environment Setup Guide`: https://cloud.google.com/python/setup +Extras +------ + +google-auth has few extras that you can install. For example:: + + $ pip install google-auth[pyopenssl] + +Note that the extras pyopenssl and enterprise_cert should not be used together because they use conflicting versions of `cryptography`_. + +.. _`cryptography`: https://cryptography.io/en/latest/ + Supported Python Versions ^^^^^^^^^^^^^^^^^^^^^^^^^ Python >= 3.6 diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 1fce9743e2c0..79d31b87c240 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -36,7 +36,7 @@ "aiohttp >= 3.6.2, < 4.0.0dev; python_version>='3.6'", "requests >= 2.20.0, < 3.0.0dev", ], - "pyopenssl": "pyopenssl>=20.0.0", + "pyopenssl": ["pyopenssl>=20.0.0", "cryptography>=38.0.3"], "reauth": "pyu2f>=0.1.5", # Enterprise cert only works for OpenSSL 1.1.1. Newer versions of these # dependencies are built with OpenSSL 3.0 so we need to fix the version. diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index abfaddd92913cbd9392312fc240a82ee8fe99e47..d428e414d2cd621ebc5a333bc3a33d272dd172ca 100644 GIT binary patch literal 10324 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 literal 10324 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�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 From a9f72f5dab5b324f75cd2fb03a4791305339c5a9 Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Mon, 7 Nov 2022 18:24:13 +0000 Subject: [PATCH 643/966] fix: apply quota project for compute cred in adc (#1177) * fix: apply quota project for compute cred in adc * update secret --- packages/google-auth/google/auth/_default.py | 9 ++++-- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes packages/google-auth/tests/test__default.py | 29 ++++++++++++++++++ 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index 8fe168428147..67a1c369d464 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -267,7 +267,7 @@ def _get_gae_credentials(): return None, None -def _get_gce_credentials(request=None): +def _get_gce_credentials(request=None, quota_project_id=None): """Gets credentials and project ID from the GCE Metadata Service.""" # Ping requires a transport, but we want application default credentials # to require no arguments. So, we'll use the _http_client transport which @@ -293,7 +293,10 @@ def _get_gce_credentials(request=None): except exceptions.TransportError: project_id = None - return compute_engine.Credentials(), project_id + cred = compute_engine.Credentials() + cred = _apply_quota_project_id(cred, quota_project_id) + + return cred, project_id else: _LOGGER.warning( "Authentication failed using Compute Engine authentication due to unavailable metadata server." @@ -603,7 +606,7 @@ def default(scopes=None, request=None, quota_project_id=None, default_scopes=Non lambda: _get_explicit_environ_credentials(quota_project_id=quota_project_id), lambda: _get_gcloud_sdk_credentials(quota_project_id=quota_project_id), _get_gae_credentials, - lambda: _get_gce_credentials(request), + lambda: _get_gce_credentials(request, quota_project_id=quota_project_id), ) for checker in checkers: diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index d428e414d2cd621ebc5a333bc3a33d272dd172ca..99e8d8b8280442550f1718fd0b57c77579ac386b 100644 GIT binary patch literal 10324 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 literal 10324 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 diff --git a/packages/google-auth/tests/test__default.py b/packages/google-auth/tests/test__default.py index 26b41b995a2f..7f19656b943b 100644 --- a/packages/google-auth/tests/test__default.py +++ b/packages/google-auth/tests/test__default.py @@ -1234,3 +1234,32 @@ def test_quota_project_from_environment(get_adc_path): explicit_quota = "explicit_quota" credentials, _ = _default.default(quota_project_id=explicit_quota) assert credentials.quota_project_id == explicit_quota + + +@mock.patch( + "google.auth.compute_engine._metadata.ping", return_value=True, autospec=True +) +@mock.patch( + "google.auth.compute_engine._metadata.get_project_id", + return_value="example-project", + autospec=True, +) +@mock.patch.dict(os.environ) +def test_quota_gce_credentials(unused_get, unused_ping): + # No quota + credentials, project_id = _default._get_gce_credentials() + assert project_id == "example-project" + assert credentials.quota_project_id is None + + # Quota from environment + quota_from_env = "quota_from_env" + os.environ[environment_vars.GOOGLE_CLOUD_QUOTA_PROJECT] = quota_from_env + credentials, project_id = _default._get_gce_credentials() + assert credentials.quota_project_id == quota_from_env + + # Explicit quota + explicit_quota = "explicit_quota" + credentials, project_id = _default._get_gce_credentials( + quota_project_id=explicit_quota + ) + assert credentials.quota_project_id == explicit_quota From 254c8cf5b68f3ab603a0660b2d8c034c2c1ffa5f Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Mon, 7 Nov 2022 19:59:15 +0000 Subject: [PATCH 644/966] fix: Validate url domain for aws metadata urls (#1174) * fix: Validate url domain for aws metadata urls * optimize tests --- packages/google-auth/google/auth/aws.py | 19 +++++ packages/google-auth/tests/test_aws.py | 103 ++++++++++++++++++++++++ 2 files changed, 122 insertions(+) diff --git a/packages/google-auth/google/auth/aws.py b/packages/google-auth/google/auth/aws.py index 08c94427ef42..04c5e7c5da3a 100644 --- a/packages/google-auth/google/auth/aws.py +++ b/packages/google-auth/google/auth/aws.py @@ -47,6 +47,7 @@ from six.moves import http_client from six.moves import urllib from six.moves.urllib.parse import urljoin +from six.moves.urllib.parse import urlparse from google.auth import _helpers from google.auth import environment_vars @@ -397,6 +398,8 @@ def __init__( self._request_signer = None self._target_resource = audience + self.validate_metadata_server_urls() + # Get the environment ID. Currently, only one version supported (v1). matches = re.match(r"^(aws)([\d]+)$", self._environment_id) if matches: @@ -413,6 +416,22 @@ def __init__( ) ) + def validate_metadata_server_urls(self): + self.validate_metadata_server_url_if_any(self._region_url, "region_url") + self.validate_metadata_server_url_if_any(self._security_credentials_url, "url") + self.validate_metadata_server_url_if_any( + self._imdsv2_session_token_url, "imdsv2_session_token_url" + ) + + @staticmethod + def validate_metadata_server_url_if_any(url_string, name_of_data): + if url_string: + url = urlparse(url_string) + if url.hostname != "169.254.169.254" and url.hostname != "fd00:ec2::254": + raise ValueError( + "Invalid hostname '{}' for '{}'".format(url.hostname, name_of_data) + ) + def retrieve_subject_token(self, request): """Retrieves the subject token using the credential_source object. The subject token is a serialized `AWS GetCallerIdentity signed request`_. diff --git a/packages/google-auth/tests/test_aws.py b/packages/google-auth/tests/test_aws.py index 85f5e8dd4385..d059487f4352 100644 --- a/packages/google-auth/tests/test_aws.py +++ b/packages/google-auth/tests/test_aws.py @@ -50,6 +50,11 @@ REGION_URL = "http://169.254.169.254/latest/meta-data/placement/availability-zone" IMDSV2_SESSION_TOKEN_URL = "http://169.254.169.254/latest/api/token" SECURITY_CREDS_URL = "http://169.254.169.254/latest/meta-data/iam/security-credentials" +REGION_URL_IPV6 = "http://[fd00:ec2::254]/latest/meta-data/placement/availability-zone" +IMDSV2_SESSION_TOKEN_URL_IPV6 = "http://[fd00:ec2::254]/latest/api/token" +SECURITY_CREDS_URL_IPV6 = ( + "http://[fd00:ec2::254]/latest/meta-data/iam/security-credentials" +) CRED_VERIFICATION_URL = ( "https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15" ) @@ -676,6 +681,13 @@ class TestCredentials(object): "url": SECURITY_CREDS_URL, "regional_cred_verification_url": CRED_VERIFICATION_URL, } + CREDENTIAL_SOURCE_IPV6 = { + "environment_id": "aws1", + "region_url": REGION_URL_IPV6, + "url": SECURITY_CREDS_URL_IPV6, + "regional_cred_verification_url": CRED_VERIFICATION_URL, + "imdsv2_session_token_url": IMDSV2_SESSION_TOKEN_URL_IPV6, + } SUCCESS_RESPONSE = { "access_token": "ACCESS_TOKEN", "issued_token_type": "urn:ietf:params:oauth:token-type:access_token", @@ -1311,6 +1323,97 @@ def test_retrieve_subject_token_success_temp_creds_no_environment_vars_idmsv2( }, ) + def test_validate_metadata_server_url_if_any(self): + aws.Credentials.validate_metadata_server_url_if_any( + "http://[fd00:ec2::254]/latest/meta-data/placement/availability-zone", "url" + ) + aws.Credentials.validate_metadata_server_url_if_any( + "http://169.254.169.254/latest/meta-data/placement/availability-zone", "url" + ) + + with pytest.raises(ValueError) as excinfo: + aws.Credentials.validate_metadata_server_url_if_any( + "http://fd00:ec2::254/latest/meta-data/placement/availability-zone", + "url", + ) + assert excinfo.match("Invalid hostname 'fd00' for 'url'") + + with pytest.raises(ValueError) as excinfo: + aws.Credentials.validate_metadata_server_url_if_any( + "http://abc.com/latest/meta-data/placement/availability-zone", "url" + ) + assert excinfo.match("Invalid hostname 'abc.com' for 'url'") + + def test_retrieve_subject_token_invalid_hosts(self): + keys = ["url", "region_url", "imdsv2_session_token_url"] + for key in keys: + credential_source = self.CREDENTIAL_SOURCE.copy() + credential_source[ + key + ] = "http://abc.com/latest/meta-data/iam/security-credentials" + + with pytest.raises(ValueError) as excinfo: + self.make_credentials(credential_source=credential_source) + assert excinfo.match("Invalid hostname 'abc.com' for '{}'".format(key)) + + @mock.patch("google.auth._helpers.utcnow") + def test_retrieve_subject_token_success_ipv6(self, utcnow): + utcnow.return_value = datetime.datetime.strptime( + self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ" + ) + request = self.make_mock_request( + region_status=http_client.OK, + region_name=self.AWS_REGION, + role_status=http_client.OK, + role_name=self.AWS_ROLE, + security_credentials_status=http_client.OK, + security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE, + imdsv2_session_token_status=http_client.OK, + imdsv2_session_token_data=self.AWS_IMDSV2_SESSION_TOKEN, + ) + credential_source_token_url = self.CREDENTIAL_SOURCE_IPV6.copy() + credentials = self.make_credentials( + credential_source=credential_source_token_url + ) + + subject_token = credentials.retrieve_subject_token(request) + + assert subject_token == self.make_serialized_aws_signed_request( + { + "access_key_id": ACCESS_KEY_ID, + "secret_access_key": SECRET_ACCESS_KEY, + "security_token": TOKEN, + } + ) + # Assert session token request. + self.assert_aws_metadata_request_kwargs( + request.call_args_list[0][1], + IMDSV2_SESSION_TOKEN_URL_IPV6, + {"X-aws-ec2-metadata-token-ttl-seconds": "300"}, + "PUT", + ) + # Assert region request. + self.assert_aws_metadata_request_kwargs( + request.call_args_list[1][1], + REGION_URL_IPV6, + {"X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN}, + ) + # Assert role request. + self.assert_aws_metadata_request_kwargs( + request.call_args_list[2][1], + SECURITY_CREDS_URL_IPV6, + {"X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN}, + ) + # Assert security credentials request. + self.assert_aws_metadata_request_kwargs( + request.call_args_list[3][1], + "{}/{}".format(SECURITY_CREDS_URL_IPV6, self.AWS_ROLE), + { + "Content-Type": "application/json", + "X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN, + }, + ) + @mock.patch("google.auth._helpers.utcnow") def test_retrieve_subject_token_session_error_idmsv2(self, utcnow): utcnow.return_value = datetime.datetime.strptime( From f0f67fa632986b1ec7d5177a2ea28957461c01e7 Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Mon, 7 Nov 2022 12:47:35 -0800 Subject: [PATCH 645/966] chore(python): update dependencies in .kokoro/requirements.txt (#1180) Source-Link: https://github.com/googleapis/synthtool/commit/e3a1277ac35fc88c09db1930533e24292b132ced Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:452901c74a22f9b9a3bd02bce780b8e8805c97270d424684bff809ce5be8c2a2 Co-authored-by: Owl Bot Co-authored-by: Anthonios Partheniou --- .../google-auth/.github/.OwlBot.lock.yaml | 2 +- packages/google-auth/.kokoro/requirements.txt | 326 ++++++++++-------- 2 files changed, 178 insertions(+), 150 deletions(-) diff --git a/packages/google-auth/.github/.OwlBot.lock.yaml b/packages/google-auth/.github/.OwlBot.lock.yaml index b8dcb4a4af99..12edee77695a 100644 --- a/packages/google-auth/.github/.OwlBot.lock.yaml +++ b/packages/google-auth/.github/.OwlBot.lock.yaml @@ -13,4 +13,4 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:993a058718e84a82fda04c3177e58f0a43281a996c7c395e0a56ccc4d6d210d7 + digest: sha256:452901c74a22f9b9a3bd02bce780b8e8805c97270d424684bff809ce5be8c2a2 diff --git a/packages/google-auth/.kokoro/requirements.txt b/packages/google-auth/.kokoro/requirements.txt index 385f2d4d6106..31425f164783 100644 --- a/packages/google-auth/.kokoro/requirements.txt +++ b/packages/google-auth/.kokoro/requirements.txt @@ -20,9 +20,9 @@ cachetools==5.2.0 \ --hash=sha256:6a94c6402995a99c3970cc7e4884bb60b4a8639938157eeed436098bf9831757 \ --hash=sha256:f9f17d2aec496a9aa6b76f53e3b614c965223c061982d434d160f930c698a9db # via google-auth -certifi==2022.6.15 \ - --hash=sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d \ - --hash=sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412 +certifi==2022.9.24 \ + --hash=sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14 \ + --hash=sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382 # via requests cffi==1.15.1 \ --hash=sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5 \ @@ -110,29 +110,33 @@ commonmark==0.9.1 \ --hash=sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60 \ --hash=sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9 # via rich -cryptography==37.0.4 \ - --hash=sha256:190f82f3e87033821828f60787cfa42bff98404483577b591429ed99bed39d59 \ - --hash=sha256:2be53f9f5505673eeda5f2736bea736c40f051a739bfae2f92d18aed1eb54596 \ - --hash=sha256:30788e070800fec9bbcf9faa71ea6d8068f5136f60029759fd8c3efec3c9dcb3 \ - --hash=sha256:3d41b965b3380f10e4611dbae366f6dc3cefc7c9ac4e8842a806b9672ae9add5 \ - --hash=sha256:4c590ec31550a724ef893c50f9a97a0c14e9c851c85621c5650d699a7b88f7ab \ - --hash=sha256:549153378611c0cca1042f20fd9c5030d37a72f634c9326e225c9f666d472884 \ - --hash=sha256:63f9c17c0e2474ccbebc9302ce2f07b55b3b3fcb211ded18a42d5764f5c10a82 \ - --hash=sha256:6bc95ed67b6741b2607298f9ea4932ff157e570ef456ef7ff0ef4884a134cc4b \ - --hash=sha256:7099a8d55cd49b737ffc99c17de504f2257e3787e02abe6d1a6d136574873441 \ - --hash=sha256:75976c217f10d48a8b5a8de3d70c454c249e4b91851f6838a4e48b8f41eb71aa \ - --hash=sha256:7bc997818309f56c0038a33b8da5c0bfbb3f1f067f315f9abd6fc07ad359398d \ - --hash=sha256:80f49023dd13ba35f7c34072fa17f604d2f19bf0989f292cedf7ab5770b87a0b \ - --hash=sha256:91ce48d35f4e3d3f1d83e29ef4a9267246e6a3be51864a5b7d2247d5086fa99a \ - --hash=sha256:a958c52505c8adf0d3822703078580d2c0456dd1d27fabfb6f76fe63d2971cd6 \ - --hash=sha256:b62439d7cd1222f3da897e9a9fe53bbf5c104fff4d60893ad1355d4c14a24157 \ - --hash=sha256:b7f8dd0d4c1f21759695c05a5ec8536c12f31611541f8904083f3dc582604280 \ - --hash=sha256:d204833f3c8a33bbe11eda63a54b1aad7aa7456ed769a982f21ec599ba5fa282 \ - --hash=sha256:e007f052ed10cc316df59bc90fbb7ff7950d7e2919c9757fd42a2b8ecf8a5f67 \ - --hash=sha256:f2dcb0b3b63afb6df7fd94ec6fbddac81b5492513f7b0436210d390c14d46ee8 \ - --hash=sha256:f721d1885ecae9078c3f6bbe8a88bc0786b6e749bf32ccec1ef2b18929a05046 \ - --hash=sha256:f7a6de3e98771e183645181b3627e2563dcde3ce94a9e42a3f427d2255190327 \ - --hash=sha256:f8c0a6e9e1dd3eb0414ba320f85da6b0dcbd543126e30fcc546e7372a7fbf3b9 +cryptography==38.0.3 \ + --hash=sha256:068147f32fa662c81aebab95c74679b401b12b57494872886eb5c1139250ec5d \ + --hash=sha256:06fc3cc7b6f6cca87bd56ec80a580c88f1da5306f505876a71c8cfa7050257dd \ + --hash=sha256:25c1d1f19729fb09d42e06b4bf9895212292cb27bb50229f5aa64d039ab29146 \ + --hash=sha256:402852a0aea73833d982cabb6d0c3bb582c15483d29fb7085ef2c42bfa7e38d7 \ + --hash=sha256:4e269dcd9b102c5a3d72be3c45d8ce20377b8076a43cbed6f660a1afe365e436 \ + --hash=sha256:5419a127426084933076132d317911e3c6eb77568a1ce23c3ac1e12d111e61e0 \ + --hash=sha256:554bec92ee7d1e9d10ded2f7e92a5d70c1f74ba9524947c0ba0c850c7b011828 \ + --hash=sha256:5e89468fbd2fcd733b5899333bc54d0d06c80e04cd23d8c6f3e0542358c6060b \ + --hash=sha256:65535bc550b70bd6271984d9863a37741352b4aad6fb1b3344a54e6950249b55 \ + --hash=sha256:6ab9516b85bebe7aa83f309bacc5f44a61eeb90d0b4ec125d2d003ce41932d36 \ + --hash=sha256:6addc3b6d593cd980989261dc1cce38263c76954d758c3c94de51f1e010c9a50 \ + --hash=sha256:728f2694fa743a996d7784a6194da430f197d5c58e2f4e278612b359f455e4a2 \ + --hash=sha256:785e4056b5a8b28f05a533fab69febf5004458e20dad7e2e13a3120d8ecec75a \ + --hash=sha256:78cf5eefac2b52c10398a42765bfa981ce2372cbc0457e6bf9658f41ec3c41d8 \ + --hash=sha256:7f836217000342d448e1c9a342e9163149e45d5b5eca76a30e84503a5a96cab0 \ + --hash=sha256:8d41a46251bf0634e21fac50ffd643216ccecfaf3701a063257fe0b2be1b6548 \ + --hash=sha256:984fe150f350a3c91e84de405fe49e688aa6092b3525f407a18b9646f6612320 \ + --hash=sha256:9b24bcff7853ed18a63cfb0c2b008936a9554af24af2fb146e16d8e1aed75748 \ + --hash=sha256:b1b35d9d3a65542ed2e9d90115dfd16bbc027b3f07ee3304fc83580f26e43249 \ + --hash=sha256:b1b52c9e5f8aa2b802d48bd693190341fae201ea51c7a167d69fc48b60e8a959 \ + --hash=sha256:bbf203f1a814007ce24bd4d51362991d5cb90ba0c177a9c08825f2cc304d871f \ + --hash=sha256:be243c7e2bfcf6cc4cb350c0d5cdf15ca6383bbcb2a8ef51d3c9411a9d4386f0 \ + --hash=sha256:bfbe6ee19615b07a98b1d2287d6a6073f734735b49ee45b11324d85efc4d5cbd \ + --hash=sha256:c46837ea467ed1efea562bbeb543994c2d1f6e800785bd5a2c98bc096f5cb220 \ + --hash=sha256:dfb4f4dd568de1b6af9f4cda334adf7d72cf5bc052516e1b2608b683375dd95c \ + --hash=sha256:ed7b00096790213e09eb11c97cc6e2b757f15f3d2f85833cd2d3ec3fe37c1722 # via # gcp-releasetool # secretstorage @@ -148,23 +152,23 @@ filelock==3.8.0 \ --hash=sha256:55447caa666f2198c5b6b13a26d2084d26fa5b115c00d065664b2124680c4edc \ --hash=sha256:617eb4e5eedc82fc5f47b6d61e4d11cb837c56cb4544e39081099fa17ad109d4 # via virtualenv -gcp-docuploader==0.6.3 \ - --hash=sha256:ba8c9d76b3bbac54b0311c503a373b00edc2dc02d6d54ea9507045adb8e870f7 \ - --hash=sha256:c0f5aaa82ce1854a386197e4e359b120ad6d4e57ae2c812fce42219a3288026b +gcp-docuploader==0.6.4 \ + --hash=sha256:01486419e24633af78fd0167db74a2763974765ee8078ca6eb6964d0ebd388af \ + --hash=sha256:70861190c123d907b3b067da896265ead2eeb9263969d6955c9e0bb091b5ccbf # via -r requirements.in -gcp-releasetool==1.8.7 \ - --hash=sha256:3d2a67c9db39322194afb3b427e9cb0476ce8f2a04033695f0aeb63979fc2b37 \ - --hash=sha256:5e4d28f66e90780d77f3ecf1e9155852b0c3b13cbccb08ab07e66b2357c8da8d +gcp-releasetool==1.9.1 \ + --hash=sha256:952f4055d5d986b070ae2a71c4410b250000f9cc5a1e26398fcd55a5bbc5a15f \ + --hash=sha256:d0d3c814a97c1a237517e837d8cfa668ced8df4b882452578ecef4a4e79c583b # via -r requirements.in -google-api-core==2.8.2 \ - --hash=sha256:06f7244c640322b508b125903bb5701bebabce8832f85aba9335ec00b3d02edc \ - --hash=sha256:93c6a91ccac79079ac6bbf8b74ee75db970cc899278b97d53bc012f35908cf50 +google-api-core==2.10.2 \ + --hash=sha256:10c06f7739fe57781f87523375e8e1a3a4674bf6392cd6131a3222182b971320 \ + --hash=sha256:34f24bd1d5f72a8c4519773d99ca6bf080a6c4e041b4e9f024fe230191dda62e # via # google-cloud-core # google-cloud-storage -google-auth==2.11.0 \ - --hash=sha256:be62acaae38d0049c21ca90f27a23847245c9f161ff54ede13af2cb6afecbac9 \ - --hash=sha256:ed65ecf9f681832298e29328e1ef0a3676e3732b2e56f41532d45f70a22de0fb +google-auth==2.14.0 \ + --hash=sha256:1ad5b0e6eba5f69645971abb3d2c197537d5914070a8c6d30299dfdb07c5c700 \ + --hash=sha256:cf24817855d874ede2efd071aa22125445f555de1685b739a9782fcf408c2a3d # via # gcp-releasetool # google-api-core @@ -178,72 +182,97 @@ google-cloud-storage==2.5.0 \ --hash=sha256:19a26c66c317ce542cea0830b7e787e8dac2588b6bfa4d3fd3b871ba16305ab0 \ --hash=sha256:382f34b91de2212e3c2e7b40ec079d27ee2e3dbbae99b75b1bcd8c63063ce235 # via gcp-docuploader -google-crc32c==1.3.0 \ - --hash=sha256:04e7c220798a72fd0f08242bc8d7a05986b2a08a0573396187fd32c1dcdd58b3 \ - --hash=sha256:05340b60bf05b574159e9bd940152a47d38af3fb43803ffe71f11d704b7696a6 \ - --hash=sha256:12674a4c3b56b706153a358eaa1018c4137a5a04635b92b4652440d3d7386206 \ - --hash=sha256:127f9cc3ac41b6a859bd9dc4321097b1a4f6aa7fdf71b4f9227b9e3ebffb4422 \ - --hash=sha256:13af315c3a0eec8bb8b8d80b8b128cb3fcd17d7e4edafc39647846345a3f003a \ - --hash=sha256:1926fd8de0acb9d15ee757175ce7242e235482a783cd4ec711cc999fc103c24e \ - --hash=sha256:226f2f9b8e128a6ca6a9af9b9e8384f7b53a801907425c9a292553a3a7218ce0 \ - --hash=sha256:276de6273eb074a35bc598f8efbc00c7869c5cf2e29c90748fccc8c898c244df \ - --hash=sha256:318f73f5484b5671f0c7f5f63741ab020a599504ed81d209b5c7129ee4667407 \ - --hash=sha256:3bbce1be3687bbfebe29abdb7631b83e6b25da3f4e1856a1611eb21854b689ea \ - --hash=sha256:42ae4781333e331a1743445931b08ebdad73e188fd554259e772556fc4937c48 \ - --hash=sha256:58be56ae0529c664cc04a9c76e68bb92b091e0194d6e3c50bea7e0f266f73713 \ - --hash=sha256:5da2c81575cc3ccf05d9830f9e8d3c70954819ca9a63828210498c0774fda1a3 \ - --hash=sha256:6311853aa2bba4064d0c28ca54e7b50c4d48e3de04f6770f6c60ebda1e975267 \ - --hash=sha256:650e2917660e696041ab3dcd7abac160b4121cd9a484c08406f24c5964099829 \ - --hash=sha256:6a4db36f9721fdf391646685ecffa404eb986cbe007a3289499020daf72e88a2 \ - --hash=sha256:779cbf1ce375b96111db98fca913c1f5ec11b1d870e529b1dc7354b2681a8c3a \ - --hash=sha256:7f6fe42536d9dcd3e2ffb9d3053f5d05221ae3bbcefbe472bdf2c71c793e3183 \ - --hash=sha256:891f712ce54e0d631370e1f4997b3f182f3368179198efc30d477c75d1f44942 \ - --hash=sha256:95c68a4b9b7828ba0428f8f7e3109c5d476ca44996ed9a5f8aac6269296e2d59 \ - --hash=sha256:96a8918a78d5d64e07c8ea4ed2bc44354e3f93f46a4866a40e8db934e4c0d74b \ - --hash=sha256:9c3cf890c3c0ecfe1510a452a165431b5831e24160c5fcf2071f0f85ca5a47cd \ - --hash=sha256:9f58099ad7affc0754ae42e6d87443299f15d739b0ce03c76f515153a5cda06c \ - --hash=sha256:a0b9e622c3b2b8d0ce32f77eba617ab0d6768b82836391e4f8f9e2074582bf02 \ - --hash=sha256:a7f9cbea4245ee36190f85fe1814e2d7b1e5f2186381b082f5d59f99b7f11328 \ - --hash=sha256:bab4aebd525218bab4ee615786c4581952eadc16b1ff031813a2fd51f0cc7b08 \ - --hash=sha256:c124b8c8779bf2d35d9b721e52d4adb41c9bfbde45e6a3f25f0820caa9aba73f \ - --hash=sha256:c9da0a39b53d2fab3e5467329ed50e951eb91386e9d0d5b12daf593973c3b168 \ - --hash=sha256:ca60076c388728d3b6ac3846842474f4250c91efbfe5afa872d3ffd69dd4b318 \ - --hash=sha256:cb6994fff247987c66a8a4e550ef374671c2b82e3c0d2115e689d21e511a652d \ - --hash=sha256:d1c1d6236feab51200272d79b3d3e0f12cf2cbb12b208c835b175a21efdb0a73 \ - --hash=sha256:dd7760a88a8d3d705ff562aa93f8445ead54f58fd482e4f9e2bafb7e177375d4 \ - --hash=sha256:dda4d8a3bb0b50f540f6ff4b6033f3a74e8bf0bd5320b70fab2c03e512a62812 \ - --hash=sha256:e0f1ff55dde0ebcfbef027edc21f71c205845585fffe30d4ec4979416613e9b3 \ - --hash=sha256:e7a539b9be7b9c00f11ef16b55486141bc2cdb0c54762f84e3c6fc091917436d \ - --hash=sha256:eb0b14523758e37802f27b7f8cd973f5f3d33be7613952c0df904b68c4842f0e \ - --hash=sha256:ed447680ff21c14aaceb6a9f99a5f639f583ccfe4ce1a5e1d48eb41c3d6b3217 \ - --hash=sha256:f52a4ad2568314ee713715b1e2d79ab55fab11e8b304fd1462ff5cccf4264b3e \ - --hash=sha256:fbd60c6aaa07c31d7754edbc2334aef50601b7f1ada67a96eb1eb57c7c72378f \ - --hash=sha256:fc28e0db232c62ca0c3600884933178f0825c99be4474cdd645e378a10588125 \ - --hash=sha256:fe31de3002e7b08eb20823b3735b97c86c5926dd0581c7710a680b418a8709d4 \ - --hash=sha256:fec221a051150eeddfdfcff162e6db92c65ecf46cb0f7bb1bf812a1520ec026b \ - --hash=sha256:ff71073ebf0e42258a42a0b34f2c09ec384977e7f6808999102eedd5b49920e3 +google-crc32c==1.5.0 \ + --hash=sha256:024894d9d3cfbc5943f8f230e23950cd4906b2fe004c72e29b209420a1e6b05a \ + --hash=sha256:02c65b9817512edc6a4ae7c7e987fea799d2e0ee40c53ec573a692bee24de876 \ + --hash=sha256:02ebb8bf46c13e36998aeaad1de9b48f4caf545e91d14041270d9dca767b780c \ + --hash=sha256:07eb3c611ce363c51a933bf6bd7f8e3878a51d124acfc89452a75120bc436289 \ + --hash=sha256:1034d91442ead5a95b5aaef90dbfaca8633b0247d1e41621d1e9f9db88c36298 \ + --hash=sha256:116a7c3c616dd14a3de8c64a965828b197e5f2d121fedd2f8c5585c547e87b02 \ + --hash=sha256:19e0a019d2c4dcc5e598cd4a4bc7b008546b0358bd322537c74ad47a5386884f \ + --hash=sha256:1c7abdac90433b09bad6c43a43af253e688c9cfc1c86d332aed13f9a7c7f65e2 \ + --hash=sha256:1e986b206dae4476f41bcec1faa057851f3889503a70e1bdb2378d406223994a \ + --hash=sha256:272d3892a1e1a2dbc39cc5cde96834c236d5327e2122d3aaa19f6614531bb6eb \ + --hash=sha256:278d2ed7c16cfc075c91378c4f47924c0625f5fc84b2d50d921b18b7975bd210 \ + --hash=sha256:2ad40e31093a4af319dadf503b2467ccdc8f67c72e4bcba97f8c10cb078207b5 \ + --hash=sha256:2e920d506ec85eb4ba50cd4228c2bec05642894d4c73c59b3a2fe20346bd00ee \ + --hash=sha256:3359fc442a743e870f4588fcf5dcbc1bf929df1fad8fb9905cd94e5edb02e84c \ + --hash=sha256:37933ec6e693e51a5b07505bd05de57eee12f3e8c32b07da7e73669398e6630a \ + --hash=sha256:398af5e3ba9cf768787eef45c803ff9614cc3e22a5b2f7d7ae116df8b11e3314 \ + --hash=sha256:3b747a674c20a67343cb61d43fdd9207ce5da6a99f629c6e2541aa0e89215bcd \ + --hash=sha256:461665ff58895f508e2866824a47bdee72497b091c730071f2b7575d5762ab65 \ + --hash=sha256:4c6fdd4fccbec90cc8a01fc00773fcd5fa28db683c116ee3cb35cd5da9ef6c37 \ + --hash=sha256:5829b792bf5822fd0a6f6eb34c5f81dd074f01d570ed7f36aa101d6fc7a0a6e4 \ + --hash=sha256:596d1f98fc70232fcb6590c439f43b350cb762fb5d61ce7b0e9db4539654cc13 \ + --hash=sha256:5ae44e10a8e3407dbe138984f21e536583f2bba1be9491239f942c2464ac0894 \ + --hash=sha256:635f5d4dd18758a1fbd1049a8e8d2fee4ffed124462d837d1a02a0e009c3ab31 \ + --hash=sha256:64e52e2b3970bd891309c113b54cf0e4384762c934d5ae56e283f9a0afcd953e \ + --hash=sha256:66741ef4ee08ea0b2cc3c86916ab66b6aef03768525627fd6a1b34968b4e3709 \ + --hash=sha256:67b741654b851abafb7bc625b6d1cdd520a379074e64b6a128e3b688c3c04740 \ + --hash=sha256:6ac08d24c1f16bd2bf5eca8eaf8304812f44af5cfe5062006ec676e7e1d50afc \ + --hash=sha256:6f998db4e71b645350b9ac28a2167e6632c239963ca9da411523bb439c5c514d \ + --hash=sha256:72218785ce41b9cfd2fc1d6a017dc1ff7acfc4c17d01053265c41a2c0cc39b8c \ + --hash=sha256:74dea7751d98034887dbd821b7aae3e1d36eda111d6ca36c206c44478035709c \ + --hash=sha256:759ce4851a4bb15ecabae28f4d2e18983c244eddd767f560165563bf9aefbc8d \ + --hash=sha256:77e2fd3057c9d78e225fa0a2160f96b64a824de17840351b26825b0848022906 \ + --hash=sha256:7c074fece789b5034b9b1404a1f8208fc2d4c6ce9decdd16e8220c5a793e6f61 \ + --hash=sha256:7c42c70cd1d362284289c6273adda4c6af8039a8ae12dc451dcd61cdabb8ab57 \ + --hash=sha256:7f57f14606cd1dd0f0de396e1e53824c371e9544a822648cd76c034d209b559c \ + --hash=sha256:83c681c526a3439b5cf94f7420471705bbf96262f49a6fe546a6db5f687a3d4a \ + --hash=sha256:8485b340a6a9e76c62a7dce3c98e5f102c9219f4cfbf896a00cf48caf078d438 \ + --hash=sha256:84e6e8cd997930fc66d5bb4fde61e2b62ba19d62b7abd7a69920406f9ecca946 \ + --hash=sha256:89284716bc6a5a415d4eaa11b1726d2d60a0cd12aadf5439828353662ede9dd7 \ + --hash=sha256:8b87e1a59c38f275c0e3676fc2ab6d59eccecfd460be267ac360cc31f7bcde96 \ + --hash=sha256:8f24ed114432de109aa9fd317278518a5af2d31ac2ea6b952b2f7782b43da091 \ + --hash=sha256:98cb4d057f285bd80d8778ebc4fde6b4d509ac3f331758fb1528b733215443ae \ + --hash=sha256:998679bf62b7fb599d2878aa3ed06b9ce688b8974893e7223c60db155f26bd8d \ + --hash=sha256:9ba053c5f50430a3fcfd36f75aff9caeba0440b2d076afdb79a318d6ca245f88 \ + --hash=sha256:9c99616c853bb585301df6de07ca2cadad344fd1ada6d62bb30aec05219c45d2 \ + --hash=sha256:a1fd716e7a01f8e717490fbe2e431d2905ab8aa598b9b12f8d10abebb36b04dd \ + --hash=sha256:a2355cba1f4ad8b6988a4ca3feed5bff33f6af2d7f134852cf279c2aebfde541 \ + --hash=sha256:b1f8133c9a275df5613a451e73f36c2aea4fe13c5c8997e22cf355ebd7bd0728 \ + --hash=sha256:b8667b48e7a7ef66afba2c81e1094ef526388d35b873966d8a9a447974ed9178 \ + --hash=sha256:ba1eb1843304b1e5537e1fca632fa894d6f6deca8d6389636ee5b4797affb968 \ + --hash=sha256:be82c3c8cfb15b30f36768797a640e800513793d6ae1724aaaafe5bf86f8f346 \ + --hash=sha256:c02ec1c5856179f171e032a31d6f8bf84e5a75c45c33b2e20a3de353b266ebd8 \ + --hash=sha256:c672d99a345849301784604bfeaeba4db0c7aae50b95be04dd651fd2a7310b93 \ + --hash=sha256:c6c777a480337ac14f38564ac88ae82d4cd238bf293f0a22295b66eb89ffced7 \ + --hash=sha256:cae0274952c079886567f3f4f685bcaf5708f0a23a5f5216fdab71f81a6c0273 \ + --hash=sha256:cd67cf24a553339d5062eff51013780a00d6f97a39ca062781d06b3a73b15462 \ + --hash=sha256:d3515f198eaa2f0ed49f8819d5732d70698c3fa37384146079b3799b97667a94 \ + --hash=sha256:d5280312b9af0976231f9e317c20e4a61cd2f9629b7bfea6a693d1878a264ebd \ + --hash=sha256:de06adc872bcd8c2a4e0dc51250e9e65ef2ca91be023b9d13ebd67c2ba552e1e \ + --hash=sha256:e1674e4307fa3024fc897ca774e9c7562c957af85df55efe2988ed9056dc4e57 \ + --hash=sha256:e2096eddb4e7c7bdae4bd69ad364e55e07b8316653234a56552d9c988bd2d61b \ + --hash=sha256:e560628513ed34759456a416bf86b54b2476c59144a9138165c9a1575801d0d9 \ + --hash=sha256:edfedb64740750e1a3b16152620220f51d58ff1b4abceb339ca92e934775c27a \ + --hash=sha256:f13cae8cc389a440def0c8c52057f37359014ccbc9dc1f0827936bcd367c6100 \ + --hash=sha256:f314013e7dcd5cf45ab1945d92e713eec788166262ae8deb2cfacd53def27325 \ + --hash=sha256:f583edb943cf2e09c60441b910d6a20b4d9d626c75a36c8fcac01a6c96c01183 \ + --hash=sha256:fd8536e902db7e365f49e7d9029283403974ccf29b13fc7028b97e2295b33556 \ + --hash=sha256:fe70e325aa68fa4b5edf7d1a4b6f691eb04bbccac0ace68e34820d283b5f80d4 # via google-resumable-media -google-resumable-media==2.3.3 \ - --hash=sha256:27c52620bd364d1c8116eaac4ea2afcbfb81ae9139fb3199652fcac1724bfb6c \ - --hash=sha256:5b52774ea7a829a8cdaa8bd2d4c3d4bc660c91b30857ab2668d0eb830f4ea8c5 +google-resumable-media==2.4.0 \ + --hash=sha256:2aa004c16d295c8f6c33b2b4788ba59d366677c0a25ae7382436cb30f776deaa \ + --hash=sha256:8d5518502f92b9ecc84ac46779bd4f09694ecb3ba38a3e7ca737a86d15cbca1f # via google-cloud-storage googleapis-common-protos==1.56.4 \ --hash=sha256:8eb2cbc91b69feaf23e32452a7ae60e791e09967d81d4fcc7fc388182d1bd394 \ --hash=sha256:c25873c47279387cfdcbdafa36149887901d36202cb645a0e4f29686bf6e4417 # via google-api-core -idna==3.3 \ - --hash=sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff \ - --hash=sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d +idna==3.4 \ + --hash=sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4 \ + --hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2 # via requests -importlib-metadata==4.12.0 \ - --hash=sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670 \ - --hash=sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23 +importlib-metadata==5.0.0 \ + --hash=sha256:da31db32b304314d044d3c12c79bd59e307889b287ad12ff387b3500835fc2ab \ + --hash=sha256:ddb0e35065e8938f867ed4928d0ae5bf2a53b7773871bfe6bcc7e4fcdc7dea43 # via # -r requirements.in # twine -jaraco-classes==3.2.2 \ - --hash=sha256:6745f113b0b588239ceb49532aa09c3ebb947433ce311ef2f8e3ad64ebb74594 \ - --hash=sha256:e6ef6fd3fcf4579a7a019d87d1e56a883f4e4c35cfe925f86731abc58804e647 +jaraco-classes==3.2.3 \ + --hash=sha256:2353de3288bc6b82120752201c6b1c1a14b058267fa424ed5ce5984e3b922158 \ + --hash=sha256:89559fa5c1d3c34eff6f631ad80bb21f378dbcbb35dd161fd2c6b93f5be2f98a # via keyring jeepney==0.8.0 \ --hash=sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806 \ @@ -255,9 +284,9 @@ jinja2==3.1.2 \ --hash=sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852 \ --hash=sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61 # via gcp-releasetool -keyring==23.9.0 \ - --hash=sha256:4c32a31174faaee48f43a7e2c7e9c3216ec5e95acf22a2bebfb4a1d05056ee44 \ - --hash=sha256:98f060ec95ada2ab910c195a2d4317be6ef87936a766b239c46aa3c7aac4f0db +keyring==23.9.3 \ + --hash=sha256:69732a15cb1433bdfbc3b980a8a36a04878a6cfd7cb99f497b573f31618001c0 \ + --hash=sha256:69b01dd83c42f590250fe7a1f503fc229b14de83857314b1933a3ddbf595c4a5 # via # gcp-releasetool # twine @@ -303,9 +332,9 @@ markupsafe==2.1.1 \ --hash=sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a \ --hash=sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7 # via jinja2 -more-itertools==8.14.0 \ - --hash=sha256:1bc4f91ee5b1b31ac7ceacc17c09befe6a40a503907baf9c839c229b5095cfd2 \ - --hash=sha256:c09443cd3d5438b8dafccd867a6bc1cb0894389e90cb53d227456b0b0bccb750 +more-itertools==9.0.0 \ + --hash=sha256:250e83d7e81d0c87ca6bd942e6aeab8cc9daa6096d12c5308f3f92fa5e5c1f41 \ + --hash=sha256:5a6257e40878ef0520b1803990e3e22303a41b5714006c32a3fd8304b26ea1ab # via jaraco-classes nox==2022.8.7 \ --hash=sha256:1b894940551dc5c389f9271d197ca5d655d40bdc6ccf93ed6880e4042760a34b \ @@ -325,35 +354,34 @@ platformdirs==2.5.2 \ --hash=sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788 \ --hash=sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19 # via virtualenv -protobuf==3.20.1 \ - --hash=sha256:06059eb6953ff01e56a25cd02cca1a9649a75a7e65397b5b9b4e929ed71d10cf \ - --hash=sha256:097c5d8a9808302fb0da7e20edf0b8d4703274d140fd25c5edabddcde43e081f \ - --hash=sha256:284f86a6207c897542d7e956eb243a36bb8f9564c1742b253462386e96c6b78f \ - --hash=sha256:32ca378605b41fd180dfe4e14d3226386d8d1b002ab31c969c366549e66a2bb7 \ - --hash=sha256:3cc797c9d15d7689ed507b165cd05913acb992d78b379f6014e013f9ecb20996 \ - --hash=sha256:62f1b5c4cd6c5402b4e2d63804ba49a327e0c386c99b1675c8a0fefda23b2067 \ - --hash=sha256:69ccfdf3657ba59569c64295b7d51325f91af586f8d5793b734260dfe2e94e2c \ - --hash=sha256:6f50601512a3d23625d8a85b1638d914a0970f17920ff39cec63aaef80a93fb7 \ - --hash=sha256:7403941f6d0992d40161aa8bb23e12575637008a5a02283a930addc0508982f9 \ - --hash=sha256:755f3aee41354ae395e104d62119cb223339a8f3276a0cd009ffabfcdd46bb0c \ - --hash=sha256:77053d28427a29987ca9caf7b72ccafee011257561259faba8dd308fda9a8739 \ - --hash=sha256:7e371f10abe57cee5021797126c93479f59fccc9693dafd6bd5633ab67808a91 \ - --hash=sha256:9016d01c91e8e625141d24ec1b20fed584703e527d28512aa8c8707f105a683c \ - --hash=sha256:9be73ad47579abc26c12024239d3540e6b765182a91dbc88e23658ab71767153 \ - --hash=sha256:adc31566d027f45efe3f44eeb5b1f329da43891634d61c75a5944e9be6dd42c9 \ - --hash=sha256:adfc6cf69c7f8c50fd24c793964eef18f0ac321315439d94945820612849c388 \ - --hash=sha256:af0ebadc74e281a517141daad9d0f2c5d93ab78e9d455113719a45a49da9db4e \ - --hash=sha256:cb29edb9eab15742d791e1025dd7b6a8f6fcb53802ad2f6e3adcb102051063ab \ - --hash=sha256:cd68be2559e2a3b84f517fb029ee611546f7812b1fdd0aa2ecc9bc6ec0e4fdde \ - --hash=sha256:cdee09140e1cd184ba9324ec1df410e7147242b94b5f8b0c64fc89e38a8ba531 \ - --hash=sha256:db977c4ca738dd9ce508557d4fce0f5aebd105e158c725beec86feb1f6bc20d8 \ - --hash=sha256:dd5789b2948ca702c17027c84c2accb552fc30f4622a98ab5c51fcfe8c50d3e7 \ - --hash=sha256:e250a42f15bf9d5b09fe1b293bdba2801cd520a9f5ea2d7fb7536d4441811d20 \ - --hash=sha256:ff8d8fa42675249bb456f5db06c00de6c2f4c27a065955917b28c4f15978b9c3 +protobuf==3.20.3 \ + --hash=sha256:03038ac1cfbc41aa21f6afcbcd357281d7521b4157926f30ebecc8d4ea59dcb7 \ + --hash=sha256:28545383d61f55b57cf4df63eebd9827754fd2dc25f80c5253f9184235db242c \ + --hash=sha256:2e3427429c9cffebf259491be0af70189607f365c2f41c7c3764af6f337105f2 \ + --hash=sha256:398a9e0c3eaceb34ec1aee71894ca3299605fa8e761544934378bbc6c97de23b \ + --hash=sha256:44246bab5dd4b7fbd3c0c80b6f16686808fab0e4aca819ade6e8d294a29c7050 \ + --hash=sha256:447d43819997825d4e71bf5769d869b968ce96848b6479397e29fc24c4a5dfe9 \ + --hash=sha256:67a3598f0a2dcbc58d02dd1928544e7d88f764b47d4a286202913f0b2801c2e7 \ + --hash=sha256:74480f79a023f90dc6e18febbf7b8bac7508420f2006fabd512013c0c238f454 \ + --hash=sha256:819559cafa1a373b7096a482b504ae8a857c89593cf3a25af743ac9ecbd23480 \ + --hash=sha256:899dc660cd599d7352d6f10d83c95df430a38b410c1b66b407a6b29265d66469 \ + --hash=sha256:8c0c984a1b8fef4086329ff8dd19ac77576b384079247c770f29cc8ce3afa06c \ + --hash=sha256:9aae4406ea63d825636cc11ffb34ad3379335803216ee3a856787bcf5ccc751e \ + --hash=sha256:a7ca6d488aa8ff7f329d4c545b2dbad8ac31464f1d8b1c87ad1346717731e4db \ + --hash=sha256:b6cc7ba72a8850621bfec987cb72623e703b7fe2b9127a161ce61e61558ad905 \ + --hash=sha256:bf01b5720be110540be4286e791db73f84a2b721072a3711efff6c324cdf074b \ + --hash=sha256:c02ce36ec760252242a33967d51c289fd0e1c0e6e5cc9397e2279177716add86 \ + --hash=sha256:d9e4432ff660d67d775c66ac42a67cf2453c27cb4d738fc22cb53b5d84c135d4 \ + --hash=sha256:daa564862dd0d39c00f8086f88700fdbe8bc717e993a21e90711acfed02f2402 \ + --hash=sha256:de78575669dddf6099a8a0f46a27e82a1783c557ccc38ee620ed8cc96d3be7d7 \ + --hash=sha256:e64857f395505ebf3d2569935506ae0dfc4a15cb80dc25261176c784662cdcc4 \ + --hash=sha256:f4bd856d702e5b0d96a00ec6b307b0f51c1982c2bf9c0052cf9019e9a544ba99 \ + --hash=sha256:f4c42102bc82a51108e449cbb32b19b180022941c727bac0cfd50170341f16ee # via # gcp-docuploader # gcp-releasetool # google-api-core + # googleapis-common-protos py==1.11.0 \ --hash=sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719 \ --hash=sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378 @@ -378,9 +406,9 @@ pygments==2.13.0 \ # via # readme-renderer # rich -pyjwt==2.4.0 \ - --hash=sha256:72d1d253f32dbd4f5c88eaf1fdc62f3a19f676ccbadb9dbc5d07e951b2b26daf \ - --hash=sha256:d42908208c699b3b973cbeb01a969ba6a96c821eefb1c5bfe4c390c01d67abba +pyjwt==2.6.0 \ + --hash=sha256:69285c7e31fc44f68a1feb309e948e0df53259d579295e6cfe2b1792329f05fd \ + --hash=sha256:d83c3d892a77bbb74d3e1a2cfa90afaadb60945205d1095d9221f04466f64c14 # via gcp-releasetool pyparsing==3.0.9 \ --hash=sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb \ @@ -393,9 +421,9 @@ python-dateutil==2.8.2 \ --hash=sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 \ --hash=sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9 # via gcp-releasetool -readme-renderer==37.0 \ - --hash=sha256:07b7ea234e03e58f77cc222e206e6abb8f4c0435becce5104794ee591f9301c5 \ - --hash=sha256:9fa416704703e509eeb900696751c908ddeb2011319d93700d8f18baff887a69 +readme-renderer==37.3 \ + --hash=sha256:cd653186dfc73055656f090f227f5cb22a046d7f71a841dfa305f55c9a513273 \ + --hash=sha256:f67a16caedfa71eef48a31b39708637a6f4664c4394801a7b0d6432d13907343 # via twine requests==2.28.1 \ --hash=sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983 \ @@ -406,17 +434,17 @@ requests==2.28.1 \ # google-cloud-storage # requests-toolbelt # twine -requests-toolbelt==0.9.1 \ - --hash=sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f \ - --hash=sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0 +requests-toolbelt==0.10.1 \ + --hash=sha256:18565aa58116d9951ac39baa288d3adb5b3ff975c4f25eee78555d89e8f247f7 \ + --hash=sha256:62e09f7ff5ccbda92772a29f394a49c3ad6cb181d568b1337626b2abb628a63d # via twine rfc3986==2.0.0 \ --hash=sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd \ --hash=sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c # via twine -rich==12.5.1 \ - --hash=sha256:2eb4e6894cde1e017976d2975ac210ef515d7548bc595ba20e195fb9628acdeb \ - --hash=sha256:63a5c5ce3673d3d5fbbf23cd87e11ab84b6b451436f1b7f19ec54b6bc36ed7ca +rich==12.6.0 \ + --hash=sha256:a4eb26484f2c82589bd9a17c73d32a010b1e29d89f1604cd9bf3a2097b81bb5e \ + --hash=sha256:ba3a3775974105c221d31141f2c116f4fd65c5ceb0698657a11e9f295ec93fd0 # via twine rsa==4.9 \ --hash=sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7 \ @@ -438,9 +466,9 @@ twine==4.0.1 \ --hash=sha256:42026c18e394eac3e06693ee52010baa5313e4811d5a11050e7d48436cf41b9e \ --hash=sha256:96b1cf12f7ae611a4a40b6ae8e9570215daff0611828f5fe1f37a16255ab24a0 # via -r requirements.in -typing-extensions==4.3.0 \ - --hash=sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02 \ - --hash=sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6 +typing-extensions==4.4.0 \ + --hash=sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa \ + --hash=sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e # via -r requirements.in urllib3==1.26.12 \ --hash=sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e \ @@ -448,9 +476,9 @@ urllib3==1.26.12 \ # via # requests # twine -virtualenv==20.16.4 \ - --hash=sha256:014f766e4134d0008dcaa1f95bafa0fb0f575795d07cae50b1bee514185d6782 \ - --hash=sha256:035ed57acce4ac35c82c9d8802202b0e71adac011a511ff650cbcf9635006a22 +virtualenv==20.16.6 \ + --hash=sha256:186ca84254abcbde98180fd17092f9628c5fe742273c02724972a1d8a2035108 \ + --hash=sha256:530b850b523c6449406dfba859d6345e48ef19b8439606c5d74d7d3c9e14d76e # via nox webencodings==0.5.1 \ --hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \ @@ -460,13 +488,13 @@ wheel==0.37.1 \ --hash=sha256:4bdcd7d840138086126cd09254dc6195fb4fc6f01c050a1d7236f2630db1d22a \ --hash=sha256:e9a504e793efbca1b8e0e9cb979a249cf4a0a7b5b8c9e8b65a5e39d49529c1c4 # via -r requirements.in -zipp==3.8.1 \ - --hash=sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2 \ - --hash=sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009 +zipp==3.10.0 \ + --hash=sha256:4fcb6f278987a6605757302a6e40e896257570d11c51628968ccb2a47e80c6c1 \ + --hash=sha256:7a7262fd930bd3e36c50b9a64897aec3fafff3dfdeec9623ae22b40e93f99bb8 # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: -setuptools==65.2.0 \ - --hash=sha256:7f4bc85450898a09f76ebf28b72fa25bc7111f6c7d665d514a60bba9c75ef2a9 \ - --hash=sha256:a3ca5857c89f82f5c9410e8508cb32f4872a3bafd4aa7ae122a24ca33bccc750 +setuptools==65.5.0 \ + --hash=sha256:512e5536220e38146176efb833d4a62aa726b7bbff82cfbc8ba9eaa3996e0b17 \ + --hash=sha256:f62ea9da9ed6289bfe868cd6845968a2c854d1427f8548d52cae02a42b4f0356 # via -r requirements.in From e3dff47cd6fdeae6a76975bded7b4af9be1baf86 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 7 Nov 2022 15:40:32 -0800 Subject: [PATCH 646/966] chore(main): release 2.14.1 (#1179) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> Co-authored-by: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 9 +++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index a974e525610c..f65921b5df6b 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,15 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.14.1](https://github.com/googleapis/google-auth-library-python/compare/v2.14.0...v2.14.1) (2022-11-07) + + +### Bug Fixes + +* Apply quota project for compute cred in adc ([#1177](https://github.com/googleapis/google-auth-library-python/issues/1177)) ([b9aa92a](https://github.com/googleapis/google-auth-library-python/commit/b9aa92a1f1640f9c8bc4beb7b13051a01cb263a4)) +* Update minimum required version of cryptography in pyopenssl extra ([#1176](https://github.com/googleapis/google-auth-library-python/issues/1176)) ([e9e76d1](https://github.com/googleapis/google-auth-library-python/commit/e9e76d1ee4e4b39edcd6821ced7a5bc3ed60eba0)) +* Validate url domain for aws metadata urls ([#1174](https://github.com/googleapis/google-auth-library-python/issues/1174)) ([f9d7d77](https://github.com/googleapis/google-auth-library-python/commit/f9d7d77739db86870c9b87b003e8ce0c93d53e53)) + ## [2.14.0](https://github.com/googleapis/google-auth-library-python/compare/v2.13.0...v2.14.0) (2022-10-31) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 6f8afc72a472..5a2a1ee060f6 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.14.0" +__version__ = "2.14.1" From c63652cf7edce34a6e29151fb18b890cbd1087e2 Mon Sep 17 00:00:00 2001 From: Jin Date: Thu, 10 Nov 2022 15:38:20 -0800 Subject: [PATCH 647/966] =?UTF-8?q?fix:=20updated=20the=20lower=20bound=20?= =?UTF-8?q?of=20interactive=20timeout=20and=20fix=20the=20kwarg=E2=80=A6?= =?UTF-8?q?=20(#1182)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: udpated the lower bound of interactive timeout and fix the kwargs invalid syntax * update token * update token * prohibit the access from constructor and only allow the injection and test * fix lint * adding interactive timeout validation test * update token --- packages/google-auth/google/auth/pluggable.py | 36 ++++++----- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes packages/google-auth/tests/test_pluggable.py | 59 ++++++++++++------ 3 files changed, 62 insertions(+), 33 deletions(-) diff --git a/packages/google-auth/google/auth/pluggable.py b/packages/google-auth/google/auth/pluggable.py index 6be8222c1eb4..b4fa448b87f0 100644 --- a/packages/google-auth/google/auth/pluggable.py +++ b/packages/google-auth/google/auth/pluggable.py @@ -52,8 +52,7 @@ EXECUTABLE_TIMEOUT_MILLIS_LOWER_BOUND = 5 * 1000 # 5 seconds EXECUTABLE_TIMEOUT_MILLIS_UPPER_BOUND = 120 * 1000 # 2 minutes -EXECUTABLE_INTERACTIVE_TIMEOUT_MILLIS_DEFAULT = 5 * 60 * 1000 # 5 minutes -EXECUTABLE_INTERACTIVE_TIMEOUT_MILLIS_LOWER_BOUND = 5 * 60 * 1000 # 5 minutes +EXECUTABLE_INTERACTIVE_TIMEOUT_MILLIS_LOWER_BOUND = 30 * 1000 # 30 seconds EXECUTABLE_INTERACTIVE_TIMEOUT_MILLIS_UPPER_BOUND = 30 * 60 * 1000 # 30 minutes @@ -132,7 +131,9 @@ def __init__( self._credential_source_executable_output_file = self._credential_source_executable.get( "output_file" ) - self._tokeninfo_username = kwargs.get("tokeninfo_username", "") # dummy value + + # Dummy value. This variable is only used via injection, not exposed to ctor + self._tokeninfo_username = "" if not self._credential_source_executable_command: raise ValueError( @@ -150,17 +151,16 @@ def __init__( ): raise ValueError("Timeout must be between 5 and 120 seconds.") - if not self._credential_source_executable_interactive_timeout_millis: - self._credential_source_executable_interactive_timeout_millis = ( - EXECUTABLE_INTERACTIVE_TIMEOUT_MILLIS_DEFAULT - ) - elif ( - self._credential_source_executable_interactive_timeout_millis - < EXECUTABLE_INTERACTIVE_TIMEOUT_MILLIS_LOWER_BOUND - or self._credential_source_executable_interactive_timeout_millis - > EXECUTABLE_INTERACTIVE_TIMEOUT_MILLIS_UPPER_BOUND - ): - raise ValueError("Interactive timeout must be between 5 and 30 minutes.") + if self._credential_source_executable_interactive_timeout_millis: + if ( + self._credential_source_executable_interactive_timeout_millis + < EXECUTABLE_INTERACTIVE_TIMEOUT_MILLIS_LOWER_BOUND + or self._credential_source_executable_interactive_timeout_millis + > EXECUTABLE_INTERACTIVE_TIMEOUT_MILLIS_UPPER_BOUND + ): + raise ValueError( + "Interactive timeout must be between 30 seconds and 30 minutes." + ) @_helpers.copy_docstring(external_account.Credentials) def retrieve_subject_token(self, request): @@ -400,5 +400,13 @@ def _validate_running_mode(self): "An output_file must be specified in the credential configuration for interactive mode." ) + if ( + self.interactive + and not self._credential_source_executable_interactive_timeout_millis + ): + raise ValueError( + "Interactive mode cannot run without an interactive timeout." + ) + if self.interactive and not self.is_workforce_pool: raise ValueError("Interactive mode is only enabled for workforce pool.") diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 99e8d8b8280442550f1718fd0b57c77579ac386b..93947b26481b915183644c6704f2abb62927ffb7 100644 GIT binary patch literal 10324 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 literal 10324 zcmV-aD67{BB>?tKRTCO0rj^?VDAkuUCm*}IVD$wgxagi9<|WSx-uGH0HTDv!Pyni{ zhrCo*jtT~M4@;+k8jXi#I=}&)GoHoP_kzB^7^SfvjvLI3Wh5NthWw`Iwyshv;8aSx zg5I`I_{}^@rc4njr_r!;hM=bAOddeo{e83TwE5Q(-#aqf{jP)}nUZEXT-E}Zl0he}yvRreS%=cR0MGtfRnjTFU4|5KK8J z?vXNq3`YE;hOzQydwP1I+rX%Ow@tO^hz`{y1niNxsis4_iTAps2BeJ<57;*3wVZI^ zqDm{qJ&zH3LN?77FPDIY5Ty-$JBG@E)+x7dOgw&Mg7soD$uuwUJXDWXV82kVl$7jl zoo3|TpNl2Mj=^(g6JP41RpHmbeLajd4S*Po*6!18${|0H2*Xs!8u-yR3CM+PHyleB zH=W-8QiNOFEr)tBdMC`X*+x}wUU~_?{-_3~`50$+q!T#QiuHj#W+Ih)XCmtp&Ho#w zr#KS%hz8h-52aS)0{el7d#-$=`{Ib@_uXDAOV4yW5LiT}`rS72v$GB<6mA=9auSzu z;Wz(PxVdZ!`~~pKPV6{q{o;VEi?;{I)t=BT7geST>CQsgdzM>Uo)m3aLn8 zTWDkNcth{!-5%0dB8l`c3PiAg57(;G=r;>)h*$OjnT%*U*6DxlN_;JX*oA3T1L*OLj~F=>2mVDy#>qS z7^fI~{Jqxsu5j2S3Wre>Z@|;TAz-I0SgUKOnkpet)l8vXa~i1MprmHT6;rNXuMRC< zV>)L6iBR(;3jmtU+h}o(SUauPFmu~{D5$xQTG!Y_AaT4IR& zy$2{!yN0lr^|R$L$cjWk`2b0Wv(KngYAL*$7Ua9lRp#LO4(Kto5C$7x-sBHK82$r; z8F8Dl#VxecaTOQ&HCSL>-5&)vDAsP_r2?b5R^Rg5DC@-Au5R=#n^~PN9kpz_`_|2ANd`GrJ@U7tZ z?>WInBjWup^73N7L*?^wSv-LmHhqcVN zZ;5c@3#&QDR85q*&F0C_(^v;@c2MP35$?-Ido2g&R=jrl1aN3x zfQd_xm%=;`khh*XKnfcLU)GEOG9C3w88UrgCzS4m5+3ss#F2GbY(nvOyTboOu4mtY zaC2kBo=qG9zvrE9QR{*mI4otQq$x9%SZvbG%}GAhL7H}k| z<9h$)c_O3l@xDBbn-l$}=6JkiPrB|nO!rv-rK*AE>*K%+^IZ;0^{JB{a-&)ykq$r% z7hg$;YDWIF-!T|tB3awin;x7YNFmxSY5EK#MWJBn(~y~_PYoySV;+km zr)(JKgReo6dAPokeNpqR3#=X>V?EyWZKE)K3x)4F$HJCJj`ZOnW{b~xYbQQkKjeN4 z)w&olt=#Q~KX>AxC)go(UEBA{M1QQ^iJ$?=wisot9X6Cu?@uOf)Elb>`nZN;plcFx zcH1v?57}KxxAtiW-dZL+w{9i*NkGN2ns<5C7}_+c!iyJ)=YD3;2Yy#Gt2MZ~_K*u% zfIO1!jnP9ll@JdF!vYGgPk;a@S#;QhC|v--d@&l%!0RAt)Sje-A5TUlF{L zYC6ZYPEzyN0@mkz{Sfz4o+tW{?m(zYelt1fW3wN-;6MBY4#KNZ`UHA*SLrjlcNx{0 z2W)YZ)>QJ^J^&=0&_n$Z?Yi2dy2G93iZ$0kl^kK%({-T$7mK^@)U%EKY{Pox_riDa z4Un6sj}Kj9Q(kr9p(YNeN3!vf?#gEYMUn*kM(If8m=Em=|J8VjW!KL9psO(#$&o2- znIdmveaYkLkHN!c_taQeZJi4$h|{L5zNl6S08uY63IqG8PvWzO_Q8~*+7hr)mIR{6 zAl_DnPgFJqT#J-Pv4~yj%u_Q}`|qCvnSSeak9OU0&-jLf=ljdoJ3`2#bf!%b$brb^ z7_iv_=(0|UZ~)({Y?qB0-q?gqzsaZ{OjT1m4w&pPIJR(za@|OmzQCO-NpA|q`;0c} zW=6BSqi}Vxv%XiSdNNzG$@v|fL=lPL$XQ8_49Q;I!8?hYjJ~?|aP9iVz-Sb#O%T#j z0>14E_rNFYj4Lg;^g!l0@&MZw17C6@SnG084Z2B6L-z zOASRSt>$gb%RsrB(?aL8si>do+3^HU(IW1?PlrrgPh@aXMb^jnbBydoh-J6+|45Ol zJ=Z!3eT3PK4{Mx)UXXN`NLX-QXfaRcsY4vgt|uxZuL6>FP_MKHlQGi)Mjff-zZq8~ zRi|F-lkXkU0Nqc#&5aOfv*#aUDb#8k_c)Bvr7h)&>0s&LOa8+t9sVAb z&4qoZN(<=du^0DKepdJNh0L9vIeN^BQcU4F3Z)w8v-XgT8ik^SM={VlwgQDQCr=Vv za)k?Covc+Jefgg?tnzGypzC3;V3$`i4Cg<6%Xe_1PhdJl zcTMyY*+J_x_LlWqhhR0$eu&Mf3`edfpYrgeJWUDJ4S_^1p1^8V*}6GaG7I@e9!ZLw=$rEEE`htTq6l5#eV$+SQ&Q@wa=~Fz`ZZtfG1a2n*kXu3Tr$mN0xI)vub>y za-Qfv7ikPz$160CR`u)%N0*r;m|`>v62AnEF9#`@^^@D*zlja;13KOI)MbJ?Eama-u!W7wy%7*YC*npw`YZFo{Gi&hE z#ZAj;FhjeDsAOx36*wkqLbJQUXKyT6?!M<7JqX8+JN`AK%c2+PN z&{PI3ItL(|Ftq>pvp<8D+-O^Z(ykFR`~q;IVl9z!YGbu-jf2+{SWoSSYp3=r=di`B z6)AG@S`WQ&k1tQz#dWn|J~Rh}TTDyu56Vdnb=>-7 z+A&7qX{O~Hd(3|rIu7t_5(xbkyX?Z#6DErK`rB*It*CrG=s$4?m}O$YqnMlg6kz4p{cT&I zK-TGV1FzObAUw2V#X$!!68|9pro5F9P)IXhA5j$MndP9~l^aNUO`mBic;Aue8w?OS zHi*<2IKgbvBo0SK#+eIA3lgbL#Ij$GQ-Crf-)K~t!W}k95R&ppwwA$Y2DpCa$ z@p^KKNAw-8`kWqlrTwKK_z#97K!PfS6iT*0T$|0f5F&}pteP8I@Sq}N8+xN-@q&d- zLz;+^f8WmTtt0ZE<`g(Jrj;%zwK=#Uv|76y=j@#Nf@bqe#0(QY%LRpsGUK|DC~}0i z9Rs%FT8?iV4ml;EMgMQHU@iz&Y({HOx-5_wqH3q&6{H_|1&lb~h;`-HRc~-Cl=vBH z)_U)D@JExd{0fDaqj(~mM<#g@x+-vsMZncC#jl71nr-A>ihMy6t>xjU1#K?8)i;`zI-E?~P^#0nJ^H@?%tj zZyQPd2eec8#S_?iwl0D`t#6oNJIL6<91K?vB36z@F-ZWL@i})Qj`@v8Qw+1Tk8yJg z2i6NM`#KN*vRFz(pwM>1cq!HT<~PB7A})>*c0~>@!+B0FR8#jk+r@$wY!sZGO~Uv~ zm5N(^0z#4bv!yKvwc9`-5)sJ0eYGQKTJR5O4MnA4wn#(e!r7X`o?40!FU63xs&k4` z|0-5l)74XlLmh>YVta%RuCtXgk(s@L{IOZdafUx5?7h*o2c>9MjY{;(1&;X-aW^lD z=m(QCL;9u#ge###c}X|s|G|#{t>M@Q%+QpUD*apbOYqAUenmUStzbGf1&UdevuyTk z@8WOjFE;jC?N4Ek=SoTFx|(}bqQ~JUBa|8rB98UI}J-5ziSX7{!Cwn!|}ljmY~@3>kRH|6n@|Y!KW%B zeV%<8{zxPO>f#gps9G%3m}Z3}&|=L96@D{luFtHbYWds3>3Lp+o$tPdN{X`f7PGqb z#8UV7M?-aUL`YC zbaCAxQi(1YQ6eJ5Vho7D-aCuLG|N!BOX(PgBTVx#fZE~$Qf9R{i9%ibgqxXSEf&`_ zGlIcdRJfq!3#Aa^zBi>gbUwA_P*PYK!%2o4P==?7jBR>#v( z44CW#D8iVcp+R=AE`g4#c@CQlG?=P^PED}8j^ijc*jERcA4W+-vaRxB@loqzo-Blz z?N^@f{R(ikeR#6iQ1j1%{^Giyp887vp7qgqmnTnNaVG$w-74-+?RE6ozuf6#f0H*2 z70ifO)Qq7ad_c`+VYL<(y=W;KMcAD~Vbb?1ryc;yfwv#F@6fg_%Zv4TnwiRvW}(u7f!!MUEp1s;do?k3YwhTAVWCm%Yj z$ykGtgmt;Dd4IKZE@otZyW2(h3`$_Y^tgR} zi6rH6-djRLmr)LGQvn~A@4nk?xrRpS`WmPD1gMKODZ$qE@>Ui&X8RLu8xz4bF%kf$ zwxiuM9X#}n)mXpmrBdPmB0tI%0vidJI(un;%-^aUyDjCgEyBoIHML43i#x?2i@X4C zVRu`ysea6%7S(ZnLFRUSg}+zeQZ7^9o~F8jVuYCBQn;adU981 zwXN)okdUDOw{>B@$4JgVW9Iy{hn5YitpZ8bA(zBAD>M7DMW+4r>VzRxdphyC=L)2L zjzl71$vq*H%Te?`rPy)w24GX`+meSLp7Uer9c}WlB)4G1Xf7$>Hh#2@ zZ4)Thd5_Jfm4F3&dUjX(nf!+kkLX_eWget$cuK%y)Mwz^!sv}uUN6Wmxr<_c^9Y{=5*8Y_n!3BYBAKC%gMd1{;V{K9>$n?xrl61t>b(`WGT345Rr&0KQ2?8Xh|y9ojHBomv7tkCHJ* z|DVAkbeDsTK3{2$=={SQ^z*+-wq0}_I>JZjw_J0DgI2C-GGE<# zvl802q5@TCP2L`GLW-|gBFgxGnij7`3VntISy9Tl{l|0=c<27@}m=e8-T!8^VTQnh7dgwJ5y9L{n+ z8GA*BJ4Yw~682(PQ^n^c9&d2ZIVdxdQemqv61dEo-z_N4FjgRxuj@sDsg58ZC(9dtABk6|6l+c}5d8O_gv=_gX{`0AY=^zeMQd<@%6 zAA@v!1o^T8<#_-G`-nXqTOYe|q}xAmVsKSHqgQw_%je2A^++d2%_Fw*?Q^e?guqFc z!V2C%M53A}>u|vHya<&~Jxo7a3oMNE><4iDJ|6%Q&lc$uF{!?$v@FyT`lA;oR3u3g z>rGs&%y8u$kvws!=Dx_golC98&1GgjULZHpU70?aZ(9U+p{rr@yp!pYr^k<##hFMv z%W@-c)bN*m%#cSik{Mqb1yR`l;n4~#sc!=vB-sD9W{640*M7_0TjxydTEUW)#3#Ce z0C~NQn9-r;o#mL|MG8}3h0!HmFqod~_LxId<7%aIaG)QzBanxugVtL&TEt2$vT@dL zO}&=9;JN|;I9U2-HX9@!^-!Z_Ffg}y;8lgSD{i-XKbms9scBx)r8_AbnEj3bt1-%Z z;l_$wj8(}DE;(I79gj}{$q1C?!Ir&t0FzCfa(e+Eec!^X540quETEE1)^h2+?||Jp z#fE`bB%nL2((9P~?3>>4CW8{|R*;RuXZK*9ttm5wir+M&>a`(d9w@_y-OQi3276bo zyA5#hd+mUZVTf9Q;{ErP*CDeGtUqh=s?jCLYPxP*4m6B=(b~a+7|Q8jFr5R2c_#Dh z_PHi9D;K$wv6!eNv)2@!8-&Ko1HEBX;f~2Q?=^Ms2wL}96(J$izWm`Yi$ml&d^H}o z-zCfJC%HptCi4bXz~1(F!ZiEt3f;H9gz0!@P4;`ML90r$1X0g9Zogu*n+@&*Uez-( z9ki}0b$ur=jU`_jA}QiB(g}rl)n-L4VtjhWe_Hqf)VXO{U}@&}RjB|~%Jx^rK1myK zxfg&#mTI%Jz~B`71klj^p9U*Ja2L?VH=6xuLieGH)G>efvYn^Bz@ni*fz~LeJ)>LStEcq6G zrap_a!dGEi=dQvrC9wPno)ANrAujm_1;OsUiIe=h%N6Bf%v=R~>Ni++<$Ln3Y^=u? zsAo{|^&2!avx$bNRWYIv1P<@i>w18Kt7)R`O2RR05w*^QGhq{{)<6S6qs8gO-+_r) z0(H=@)&;VrX-{cSqPY(_f6A@V!;-DqMe@FGK_kP4(oIfGLZo}^;-b7^it{JBpo5a_ zvW8wja@-e_N&G5_4{+@|=kP67<&^CJ+1W_yNek+Kb#1IPfjDsN`-P%; zyo1vDSa`5RhGC@p5r+$N3n2?O$iRgwi`(+KY6nC2Kzv0o?^*s|lTsy)hh=Bf68n9f z;PJ&l7N6}%e$bD5j;<=1?NuB!C=-7Qkcjf3)_tVe-nYX>)Nnim$DI(srht)QXnE9Y z3Ho>PNKDQIY>RVZ0&f8S{^Fjpf*^^L;r5JYl}Veid%+88VPl7-%L&;Rlu66nz+tK} z!)lW#3eZz0mz}k8je)N~Yq2EU2fHZkC)3`Qyzs6y-#~oFNFKg)fP{^%YVIc3;Yl!~ zBUH9cYS6n;36B+IqAMPipf%mUw@|JTNL)BI%hZoDMA~yl_IVsIfdzm!(FiSXHLkX@ zQU%<4q*8#7qe`zmUP1+nbO!MQPs=j~kf?ZQ0zyl#+*J8TN!YVv6FS=xKP-|0y8QPx z`6*RSqBnnDW@VozlgJ#X!T1T;>D6j2sUo0SaL}LF-D@epx_aJJ4xxEJesgCD za+MsQOXh{!UuGjac)S_s85nyh7z^IhV3w!-oPXf*@62oG6laReo@&yYLHNoTuYz)W zM`0Uqc}|xmT+SVFscdMy&`}Z!8i}qwfvYqw0d+lH2>?F?)9|0T{xc?KByL8!fXqaq zm!Xc4*=GA(7Es;$339eq!a%s&v!H9`?)eei)p16kV^{)3S*hiZXLaC%9OCX^z9-gC z=nKUEfx4`&Z_hzW#Y3M(5MQ>sI89jk|(o? z4z&|3#KaN3)uh~2##NPg&>8|$+lEudU6){}MfMYn02mRbpwVE7s2*cR?5{%U$ArQi z5RAUMycX8E5N8FhK^Xq@gY3f$-8nZuKk&=SR9VZ^t7Vb-xAT0K>kYNL0|06on?|;n zxL^Y%t*r9}jn1oU;EKOSgUL=4A6RLo|Lk;N-o&frIY@QnI7`yfPB+W3y4=$3*0*A} zkXo|T56xx-)3Cs2T$QlR?$Cwd0)JvFvr*|#gqv82kY>=Q%=+K)~#&nNE>IPgpAyNhL!AczW@WBQAyVxtp+ihdJfC< zJ>4i+i>y9phLj=(@BmozDb3YaSR3T1Ov%Ee4O5=?2H8y2+TRJZ>*y=- zcaWQ}x@M1aZow9+fPDzw^HuS0?^!P@or)I5H+ZU$6;!SA7*Amg^}FBKbtnOpCma6B4uo{;_^xnA z9ODIZ4j%%pQW9n#UsWb$f0X zr1Bk<*<4~^)hxik%T)i=hfOuwwB32~zoMliTarFY!TQl?{1L~_#(+FMcYSo%Q*%->TYpzw*rqgrN*l*G@532oaW z&eRv{1ME&;CUth3wEX?!j=Q2DyW>}x&-1;KFw2{Af10P)UOXugLFY^9tm=$yrQO%wD#!zx~^QD6L|L_a71xfJp3rJglVge z;^OGMH)A2V^`;MAawA%*rTwcd(T*y6r73|x@UfR>evy7il;*FXS~o3E=@+)R{LNG6 zh|@qo&C(W$G%&Fba2b^q_LECa&Frk#8TXn03I~kG2LYyB3aQAtQ-&Kf{`901tk0TmMwx_5a+|&|vy$tDt zh@k9{h3;TxYEJMdy1uma&rly!Q24wo%aLP0Qu;O>UOqDiWHlrno|sqF6$l_tjAy-k zbv<_MAf;z{^9}A^+TQX)XGe+k^C84Kl$jNA_#3m4Gy0wk~47x^n~(~VaYS7 znWSMMz*&)JbvGsJXR(K)Ca@hCqT5vGlDcz!FtMg*?7mKfv+g#!kqemquS57@pqL7ssFEIsCym=BON zqBh^50=_lLFUf$&*4PZy7 zM7N2EtdAF+cL7h{*4D(6^ofUBf*Bj32U>?T0@%sqXQQrFE7ICBi`R;*&;l8dm>d;k z8;V@>P{?VRJ#bY^$y{Eu;**6tP3YU=K4GO|AQv$zcO=a;=GBGv&$pu#NZ|yu zXodPRWY~&b*+E=u`oYDzokDr#Za8za#4{0Uq9BsQKAxlXVV!lpR<|_1k$#uJt65YY?iJ z4UQi4?t?d3g1xBX$aR+kJ94lC?d7vnzcR~|S_L5@zKjl7L+y1{r4^7_>Ci;}rVYQ= m7jaiqU9k?ZKFc*iRQZw6uVn$?R=YcLA`JJh3nX1k%*E)K@-j34 diff --git a/packages/google-auth/tests/test_pluggable.py b/packages/google-auth/tests/test_pluggable.py index 0c0ebeb061bd..cd553da832cb 100644 --- a/packages/google-auth/tests/test_pluggable.py +++ b/packages/google-auth/tests/test_pluggable.py @@ -232,6 +232,21 @@ def make_pluggable( interactive=interactive, ) + def test_from_constructor_and_injection(self): + credentials = pluggable.Credentials( + audience=AUDIENCE, + subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + token_info_url=TOKEN_INFO_URL, + credential_source=self.CREDENTIAL_SOURCE, + interactive=True, + ) + setattr(credentials, "_tokeninfo_username", "mock_external_account_id") + + assert isinstance(credentials, pluggable.Credentials) + assert credentials.interactive + assert credentials.external_account_id == "mock_external_account_id" + @mock.patch.object(pluggable.Credentials, "__init__", return_value=None) def test_from_info_full_options(self, mock_init): credentials = pluggable.Credentials.from_info( @@ -1064,23 +1079,6 @@ def test_credential_source_timeout_large(self): assert excinfo.match(r"Timeout must be between 5 and 120 seconds.") - @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) - def test_credential_source_interactive_timeout_missing_will_use_default_interactive_timeout_value( - self - ): - CREDENTIAL_SOURCE = { - "executable": { - "command": self.CREDENTIAL_SOURCE_EXECUTABLE_COMMAND, - "output_file": self.CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, - } - } - credentials = self.make_pluggable(credential_source=CREDENTIAL_SOURCE) - - assert ( - credentials._credential_source_executable_interactive_timeout_millis - == pluggable.EXECUTABLE_INTERACTIVE_TIMEOUT_MILLIS_DEFAULT - ) - @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) def test_credential_source_interactive_timeout_small(self): with pytest.raises(ValueError) as excinfo: @@ -1093,7 +1091,9 @@ def test_credential_source_interactive_timeout_small(self): } _ = self.make_pluggable(credential_source=CREDENTIAL_SOURCE) - assert excinfo.match(r"Interactive timeout must be between 5 and 30 minutes.") + assert excinfo.match( + r"Interactive timeout must be between 30 seconds and 30 minutes." + ) @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) def test_credential_source_interactive_timeout_large(self): @@ -1107,7 +1107,9 @@ def test_credential_source_interactive_timeout_large(self): } _ = self.make_pluggable(credential_source=CREDENTIAL_SOURCE) - assert excinfo.match(r"Interactive timeout must be between 5 and 30 minutes.") + assert excinfo.match( + r"Interactive timeout must be between 30 seconds and 30 minutes." + ) @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) def test_retrieve_subject_token_executable_fail(self): @@ -1136,6 +1138,25 @@ def test_retrieve_subject_token_non_workforce_fail_interactive_mode(self): assert excinfo.match(r"Interactive mode is only enabled for workforce pool.") + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_retrieve_subject_token_fail_on_validation_missing_interactive_timeout( + self + ): + CREDENTIAL_SOURCE_EXECUTABLE = { + "command": self.CREDENTIAL_SOURCE_EXECUTABLE_COMMAND, + "output_file": self.CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE, + } + CREDENTIAL_SOURCE = {"executable": CREDENTIAL_SOURCE_EXECUTABLE} + credentials = self.make_pluggable( + credential_source=CREDENTIAL_SOURCE, interactive=True + ) + with pytest.raises(ValueError) as excinfo: + _ = credentials.retrieve_subject_token(None) + + assert excinfo.match( + r"Interactive mode cannot run without an interactive timeout." + ) + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) def test_retrieve_subject_token_executable_fail_interactive_mode(self): with mock.patch( From 1b2706ed61ccbaf75657f7f410038b28373a8f64 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Fri, 11 Nov 2022 14:53:03 -0800 Subject: [PATCH 648/966] feat: add api_key credentials (#1184) --- packages/google-auth/google/auth/_default.py | 7 ++ packages/google-auth/google/auth/api_key.py | 75 ++++++++++++++++++++ packages/google-auth/tests/test__default.py | 7 ++ packages/google-auth/tests/test_api_key.py | 45 ++++++++++++ 4 files changed, 134 insertions(+) create mode 100644 packages/google-auth/google/auth/api_key.py create mode 100644 packages/google-auth/tests/test_api_key.py diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index 67a1c369d464..0860c67fe437 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -479,6 +479,13 @@ def _get_gdch_service_account_credentials(filename, info): return credentials, info.get("project") +def get_api_key_credentials(key): + """Return credentials with the given API key.""" + from google.auth import api_key + + return api_key.Credentials(key) + + def _apply_quota_project_id(credentials, quota_project_id): if quota_project_id: credentials = credentials.with_quota_project(quota_project_id) diff --git a/packages/google-auth/google/auth/api_key.py b/packages/google-auth/google/auth/api_key.py new file mode 100644 index 000000000000..49c6ffd2d561 --- /dev/null +++ b/packages/google-auth/google/auth/api_key.py @@ -0,0 +1,75 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google API key support. +This module provides authentication using the `API key`_. +.. _API key: + https://cloud.google.com/docs/authentication/api-keys/ +""" + +from google.auth import _helpers +from google.auth import credentials + + +class Credentials(credentials.Credentials): + """API key credentials. + These credentials use API key to provide authorization to applications. + """ + + def __init__(self, token): + """ + Args: + token (str): API key string + Raises: + ValueError: If the provided API key is not a non-empty string. + """ + super(Credentials, self).__init__() + if not token: + raise ValueError("Token must be a non-empty API key string") + self.token = token + + @property + def expired(self): + return False + + @property + def valid(self): + return True + + @_helpers.copy_docstring(credentials.Credentials) + def refresh(self, request): + return + + def apply(self, headers, token=None): + """Apply the API key token to the x-goog-api-key header. + Args: + headers (Mapping): The HTTP request headers. + token (Optional[str]): If specified, overrides the current access + token. + """ + headers["x-goog-api-key"] = token or self.token + + def before_request(self, request, method, url, headers): + """Performs credential-specific before request logic. + Refreshes the credentials if necessary, then calls :meth:`apply` to + apply the token to the x-goog-api-key header. + Args: + request (google.auth.transport.Request): The object used to make + HTTP requests. + method (str): The request's HTTP method or the RPC method being + invoked. + url (str): The request's URI or the RPC service's URI. + headers (Mapping): The request's headers. + """ + self.apply(headers) diff --git a/packages/google-auth/tests/test__default.py b/packages/google-auth/tests/test__default.py index 7f19656b943b..b10fb192cf85 100644 --- a/packages/google-auth/tests/test__default.py +++ b/packages/google-auth/tests/test__default.py @@ -19,6 +19,7 @@ import pytest # type: ignore from google.auth import _default +from google.auth import api_key from google.auth import app_engine from google.auth import aws from google.auth import compute_engine @@ -683,6 +684,12 @@ def test__get_gdch_service_account_credentials_invalid_format_version(): assert excinfo.match("Failed to load GDCH service account credentials") +def test_get_api_key_credentials(): + creds = _default.get_api_key_credentials("api_key") + assert isinstance(creds, api_key.Credentials) + assert creds.token == "api_key" + + class _AppIdentityModule(object): """The interface of the App Idenity app engine module. See https://cloud.google.com/appengine/docs/standard/python/refdocs\ diff --git a/packages/google-auth/tests/test_api_key.py b/packages/google-auth/tests/test_api_key.py new file mode 100644 index 000000000000..9ba7b1426b1e --- /dev/null +++ b/packages/google-auth/tests/test_api_key.py @@ -0,0 +1,45 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest # type: ignore + +from google.auth import api_key + + +def test_credentials_constructor(): + with pytest.raises(ValueError) as excinfo: + api_key.Credentials("") + + assert excinfo.match(r"Token must be a non-empty API key string") + + +def test_expired_and_valid(): + credentials = api_key.Credentials("api-key") + + assert credentials.valid + assert credentials.token == "api-key" + assert not credentials.expired + + credentials.refresh(None) + assert credentials.valid + assert credentials.token == "api-key" + assert not credentials.expired + + +def test_before_request(): + credentials = api_key.Credentials("api-key") + headers = {} + + credentials.before_request(None, "http://example.com", "GET", headers) + assert headers["x-goog-api-key"] == "api-key" From 48a9658d67b7da28e1180d97cac986293184b973 Mon Sep 17 00:00:00 2001 From: Riccardo Schirone <562321+ret2libc@users.noreply.github.com> Date: Tue, 15 Nov 2022 19:49:00 +0100 Subject: [PATCH 649/966] fix: ensure JWT segments have the right types (#1162) * fix: ensure JWT header is a dict before accessing its methods --- packages/google-auth/google/auth/jwt.py | 15 ++++++++++-- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes packages/google-auth/tests/test_jwt.py | 23 ++++++++++++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/google/auth/jwt.py b/packages/google-auth/google/auth/jwt.py index 9d7b76e7370a..21de8fe9555d 100644 --- a/packages/google-auth/google/auth/jwt.py +++ b/packages/google-auth/google/auth/jwt.py @@ -133,11 +133,12 @@ def _unverified_decode(token): token (Union[str, bytes]): The encoded JWT. Returns: - Tuple[str, str, str, str]: header, payload, signed_section, and + Tuple[Mapping, Mapping, str, str]: header, payload, signed_section, and signature. Raises: - ValueError: if there are an incorrect amount of segments in the token. + ValueError: if there are an incorrect amount of segments in the token or + segments of the wrong type. """ token = _helpers.to_bytes(token) @@ -152,6 +153,16 @@ def _unverified_decode(token): header = _decode_jwt_segment(encoded_header) payload = _decode_jwt_segment(encoded_payload) + if not isinstance(header, Mapping): + raise ValueError( + "Header segment should be a JSON object: {0}".format(encoded_header) + ) + + if not isinstance(payload, Mapping): + raise ValueError( + "Payload segment should be a JSON object: {0}".format(encoded_payload) + ) + return header, payload, signed_section, signature diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 93947b26481b915183644c6704f2abb62927ffb7..a36de1bfb2cc66f3475979d7c0efd88608abb84f 100644 GIT binary patch literal 10324 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 literal 10324 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 diff --git a/packages/google-auth/tests/test_jwt.py b/packages/google-auth/tests/test_jwt.py index bc01ebfc760a..d45d5daa9c4a 100644 --- a/packages/google-auth/tests/test_jwt.py +++ b/packages/google-auth/tests/test_jwt.py @@ -126,6 +126,29 @@ def test_decode_valid(token_factory): assert payload["metadata"]["meta"] == "data" +def test_decode_header_object(token_factory): + payload = token_factory() + # Create a malformed JWT token with a number as a header instead of a + # dictionary (3 == base64d(M7==)) + payload = b"M7." + b".".join(payload.split(b".")[1:]) + + with pytest.raises(ValueError) as excinfo: + jwt.decode(payload, certs=PUBLIC_CERT_BYTES) + assert excinfo.match(r"Header segment should be a JSON object: " + str(b"M7")) + + +def test_decode_payload_object(signer): + # Create a malformed JWT token with a payload containing both "iat" and + # "exp" strings, although not as fields of a dictionary + payload = jwt.encode(signer, "iatexp") + + with pytest.raises(ValueError) as excinfo: + jwt.decode(payload, certs=PUBLIC_CERT_BYTES) + assert excinfo.match( + r"Payload segment should be a JSON object: " + str(b"ImlhdGV4cCI") + ) + + def test_decode_valid_es256(token_factory): payload = jwt.decode( token_factory(use_es256_signer=True), certs=EC_PUBLIC_CERT_BYTES From 61d5f7ae828cb5cc20287cae7a9815edb22dc7e7 Mon Sep 17 00:00:00 2001 From: Carl Lundin <108372512+clundin25@users.noreply.github.com> Date: Wed, 16 Nov 2022 22:00:13 +0000 Subject: [PATCH 650/966] fix: Allow mtls sts endpoint for external account token urls. (#1185) * fix: Allow mtls sts endpoint for external account token urls. --- .../google/auth/external_account.py | 10 +++++----- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../tests/test_external_account.py | 12 ++++++++++++ 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/packages/google-auth/google/auth/external_account.py b/packages/google-auth/google/auth/external_account.py index 7edb55f63b6d..4249529e8453 100644 --- a/packages/google-auth/google/auth/external_account.py +++ b/packages/google-auth/google/auth/external_account.py @@ -437,11 +437,11 @@ def _initialize_impersonated_credentials(self): @staticmethod def validate_token_url(token_url, url_type="token"): _TOKEN_URL_PATTERNS = [ - "^[^\\.\\s\\/\\\\]+\\.sts\\.googleapis\\.com$", - "^sts\\.googleapis\\.com$", - "^sts\\.[^\\.\\s\\/\\\\]+\\.googleapis\\.com$", - "^[^\\.\\s\\/\\\\]+\\-sts\\.googleapis\\.com$", - "^sts\\-[^\\.\\s\\/\\\\]+\\.p\\.googleapis\\.com$", + "^[^\\.\\s\\/\\\\]+\\.sts(?:\\.mtls)?\\.googleapis\\.com$", + "^sts(?:\\.mtls)?\\.googleapis\\.com$", + "^sts\\.[^\\.\\s\\/\\\\]+(?:\\.mtls)?\\.googleapis\\.com$", + "^[^\\.\\s\\/\\\\]+\\-sts(?:\\.mtls)?\\.googleapis\\.com$", + "^sts\\-[^\\.\\s\\/\\\\]+\\.p(?:\\.mtls)?\\.googleapis\\.com$", ] if not Credentials.is_valid_url(_TOKEN_URL_PATTERNS, token_url): diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index a36de1bfb2cc66f3475979d7c0efd88608abb84f..345e4426c1fb59a310374c7d227fcede6527f4b6 100644 GIT binary patch literal 10324 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 literal 10324 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 diff --git a/packages/google-auth/tests/test_external_account.py b/packages/google-auth/tests/test_external_account.py index 18ac75511e36..78a272b6a21c 100644 --- a/packages/google-auth/tests/test_external_account.py +++ b/packages/google-auth/tests/test_external_account.py @@ -67,23 +67,31 @@ VALID_TOKEN_URLS = [ "https://sts.googleapis.com", + "https://sts.mtls.googleapis.com", "https://us-east-1.sts.googleapis.com", + "https://us-east-1.sts.mtls.googleapis.com", "https://US-EAST-1.sts.googleapis.com", "https://sts.us-east-1.googleapis.com", "https://sts.US-WEST-1.googleapis.com", "https://us-east-1-sts.googleapis.com", "https://US-WEST-1-sts.googleapis.com", + "https://US-WEST-1-sts.mtls.googleapis.com", "https://us-west-1-sts.googleapis.com/path?query", "https://sts-us-east-1.p.googleapis.com", + "https://sts-us-east-1.p.mtls.googleapis.com", ] INVALID_TOKEN_URLS = [ "https://iamcredentials.googleapis.com", + "https://mtls.iamcredentials.googleapis.com", "sts.googleapis.com", + "mtls.sts.googleapis.com", + "mtls.googleapis.com", "https://", "http://sts.googleapis.com", "https://st.s.googleapis.com", "https://us-eas\t-1.sts.googleapis.com", "https:/us-east-1.sts.googleapis.com", + "https:/us-east-1.mtls.sts.googleapis.com", "https://US-WE/ST-1-sts.googleapis.com", "https://sts-us-east-1.googleapis.com", "https://sts-US-WEST-1.googleapis.com", @@ -95,16 +103,20 @@ "hhttps://us-east-1.sts.googleapis.com", "https://us- -1.sts.googleapis.com", "https://-sts.googleapis.com", + "https://-mtls.googleapis.com", "https://us-east-1.sts.googleapis.com.evil.com", "https://sts.pgoogleapis.com", "https://p.googleapis.com", "https://sts.p.com", + "https://sts.p.mtls.com", "http://sts.p.googleapis.com", "https://xyz-sts.p.googleapis.com", "https://sts-xyz.123.p.googleapis.com", "https://sts-xyz.p1.googleapis.com", "https://sts-xyz.p.foo.com", "https://sts-xyz.p.foo.googleapis.com", + "https://sts-xyz.mtls.p.foo.googleapis.com", + "https://sts-xyz.p.mtls.foo.googleapis.com", ] VALID_SERVICE_ACCOUNT_IMPERSONATION_URLS = [ "https://iamcredentials.googleapis.com", From 252107b09cb0303de3ba51ac522ce6209092e201 Mon Sep 17 00:00:00 2001 From: Carl Lundin <108372512+clundin25@users.noreply.github.com> Date: Thu, 1 Dec 2022 18:21:52 +0000 Subject: [PATCH 651/966] fix: CI broken by removal of py.path (#1194) * fix: CI broken by removal of py.path Use Python standard lib instead. --- packages/google-auth/system_tests/noxfile.py | 32 ++++++++++-------- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/packages/google-auth/system_tests/noxfile.py b/packages/google-auth/system_tests/noxfile.py index 88f2e040ebbd..592f52183681 100644 --- a/packages/google-auth/system_tests/noxfile.py +++ b/packages/google-auth/system_tests/noxfile.py @@ -23,11 +23,13 @@ """ import os +import pathlib import subprocess +import shutil +import tempfile from nox.command import which import nox -import py.path HERE = os.path.abspath(os.path.dirname(__file__)) LIBRARY_DIR = os.path.abspath(os.path.dirname(HERE)) @@ -59,16 +61,18 @@ CLOUD_SDK_ROOT = os.environ.get("CLOUD_SDK_ROOT") if CLOUD_SDK_ROOT is not None: - CLOUD_SDK_ROOT = py.path.local(CLOUD_SDK_ROOT) - CLOUD_SDK_ROOT.ensure(dir=True) # Makes sure the directory exists. + CLOUD_SDK_ROOT = pathlib.Path(CLOUD_SDK_ROOT) + if not CLOUD_SDK_ROOT.exists() or not CLOUD_SDK_ROOT.is_dir(): + print("{} did not exist! Please set the CLOUD_SDK_ROOT environment variable to a directory that exists".format(CLOUD_SDK_ROOT)) + exit(1) else: - CLOUD_SDK_ROOT = py.path.local.mkdtemp() + CLOUD_SDK_ROOT = pathlib.Path(tempfile.mkdtemp()) # The full path the cloud sdk install directory -CLOUD_SDK_INSTALL_DIR = CLOUD_SDK_ROOT.join("google-cloud-sdk") +CLOUD_SDK_INSTALL_DIR = CLOUD_SDK_ROOT.joinpath("google-cloud-sdk") # The full path to the gcloud cli executable. -GCLOUD = str(CLOUD_SDK_INSTALL_DIR.join("bin", "gcloud")) +GCLOUD = str(CLOUD_SDK_INSTALL_DIR.joinpath("bin", "gcloud")) # gcloud requires Python 2 and doesn't work on 3, so we need to tell it # where to find 2 when we're running in a 3 environment. @@ -90,26 +94,26 @@ def install_cloud_sdk(session): # This set the $PATH for the subprocesses so they can find the gcloud # executable. session.env["PATH"] = ( - str(CLOUD_SDK_INSTALL_DIR.join("bin")) + os.pathsep + os.environ["PATH"] + str(CLOUD_SDK_INSTALL_DIR.joinpath("bin")) + os.pathsep + os.environ["PATH"] ) # If gcloud cli executable already exists, just update it. - if py.path.local(GCLOUD).exists(): + if pathlib.Path(GCLOUD).exists(): session.run(GCLOUD, "components", "update", "-q") return - tar_path = CLOUD_SDK_ROOT.join(CLOUD_SDK_DIST_FILENAME) + tar_path = CLOUD_SDK_ROOT.joinpath(CLOUD_SDK_DIST_FILENAME) # Download the release. session.run("wget", CLOUD_SDK_DOWNLOAD_URL, "-O", str(tar_path), silent=True) # Extract the release. session.run("tar", "xzf", str(tar_path), "-C", str(CLOUD_SDK_ROOT)) - session.run(tar_path.remove) + tar_path.unlink() # Run the install script. session.run( - str(CLOUD_SDK_INSTALL_DIR.join("install.sh")), + str(CLOUD_SDK_INSTALL_DIR.joinpath("install.sh")), "--usage-reporting", "false", "--path-update", @@ -123,10 +127,10 @@ def install_cloud_sdk(session): def copy_credentials(credentials_path): """Copies credentials into the SDK root as the application default credentials.""" - dest = CLOUD_SDK_ROOT.join("application_default_credentials.json") + dest = CLOUD_SDK_ROOT.joinpath("application_default_credentials.json") if dest.exists(): - dest.remove() - py.path.local(credentials_path).copy(dest) + dest.unlink() + shutil.copyfile(pathlib.Path(credentials_path), dest) def configure_cloud_sdk(session, application_default_credentials, project=False): diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 345e4426c1fb59a310374c7d227fcede6527f4b6..6ad7ea0142eec8f6617e688fd13a1dd0ccf961c0 100644 GIT binary patch literal 10324 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

_U*%UX>a}5!|9#ujoBu zTI`Aga@>p~Vky%n+D|WMGnX#Ik!kfiwJdaoPUk)Tg%2iIlnX9xCiX~}QnM4T`Ab+n z%V;-8TiSp^6~3x4x?y7Jl2gzH`V&s->gS>~G)H_Ct7l9nsNh4WpsINmEgNwwbxyci zgPj*Ln=LdYP|h%#||Pk#hi5pVSuu8Ymbk6I7y$G%}F|8TOFzD8S?5ItiK%faO=RQSm zUeTPQ6jIlDU%sgk~n{xw)y zS>c5drnj|&6C6qnub3$CyVZV{kEEfJJjdUw6-p!jElS|AqU=$BY(4ZUjgMMX9=2!IXI&OSAUL73}=O1u1z ziGJ$wq3`@C?0FtaC&KBz+r0$*;{1=$5(!0e51)7EwI{46JB!Wn_al0r+Aa$!%Txo= zAIZ*BbmPuI{4OWjyj0HintkpUReTdrd($V|ouVDI|Fmkr> z8^A(G-1{^qw9_&u3kl_S=3d6zATnF-4ocEf4wf_`VeC}vKI!PZQvSnHz6_tD zjAbz8t<(09;=l*6+Ku=(+}AD(Yw3mEvpO&3@(;q%oI*EFv0kkNK*n5Z7T04FEN zs|si8m#=MmX3;su(wvxNyX=`xu5b}SG7n3uE$GeGa0Vh)zY=od!YWkD_-4dO8;gN} zrru)IH)q{9yYl)GrEu-OD0hVWuu=O~>C~2RR2F`QpG^uT*l@5-gB#3>D`Ag6obkPz32W18`YWE>j}v+t7PSVX+-hw32PkcswQe(s^?j_45zF;U+`8iGrDU zmEY3`27G|!#Z2@1w@K+68=Zq2xfHn~J@RhlrU`*xWG0TxfzII15I1*<+7G7@$Z8<+ zmh)m>B`ba2R)Md>p!gUyAZYEYPkfGbl#9vh7rV$zIy8uS3>;u1Kao@Z#Oc9K@-$sP!hv?bIJJl zC9Ld1!WuE^-bD9EojBzx;eSmA(<2i4n{u#5q`S~=)oHRDGB*L;^y9th9Hh(CwTI7j z6+?IlBnlsNDS_NhW3+((11aCWb=LjeRMOm49=t-MHaqUy}d zlJnQsPWX9XYqLp9nSlfsr%^&!Q>l>CX)p=?Mn@M$r5hrl%&q!k#VbUcaieRQ$; zZV2PRn9Vz?y_!dZxvaK3v-AsfM3yd%>iWuKvpweSMlqZ-Om_y==x+s8GDgLmbju$U zjnvWXG;zqnmY3GNJ!9s=sNgzOl4QmUb=l~DMyXD4X_bCF6!)#Q3$U&^==zKnsuD$d z@T^RptWI2rUMzA3%NksVxF8}(u)lq2jKYJ`<+ZZ66&-z3m!PT?fg9hY$KRD5;E#Rs zphK{QpN{^$O_!8s%ZILdRL+EV4>}3EbpZP1J*Kg8%Ch_3WsYZ-3$b9?KVw-YO346< zdrBd5Xuf4OgXDjSnEZ=o8qW~i#H-vtI3-N2w5#ppL;UZxqx_b|wih50j-V|8@u(Ml zQiP*5N@k`=Cx?=RI1Fgt7kAbEV^A}N#@md22BuBh`X-pwJfiBGLhBFDg{`7d6XiBh zNJd(EDizueDFp>w)xgA;Qn{i6M)U$|#h&PFQjNp9kUNOUs|ppAg*dyBkl=&T)k*E( zNIY{pe^sWC@!rjA4LN{Rjd&&v!|`p;>+ zp=NNKkLG4xvkyPhdfxUgGag)+LNg+%>iYlO%*>7qEM<9<%{>(F1$D^5<4CIn8rdKm zVFJxbR=FW5Qb8`%QZs;2D3OUgKfnz}L#Cd)d>fd1T)kqpXvc6US(ZO?nBI;?R?a@K zX)<3oxL$T(^?q=-ZkTy^#gVHk5N?y*4#&9`_4T}c2QEp!Q%Sp(55a?<$aRbg5n!uO z;u**zNHBIdG3$ebh;_+)!U|nGc=_L4YB?Jgj#9RV-F@VnkK6%y)+Mi>7_tWZ44mnC z3ax!wPLTS~?aG($yT!0OOhd+`Hi3FTQwXRL14DgEY3;V-#0s_c5$m#YfufQ&V3rX5 zWCTTArE}2wb!~EEOd&rkxQu8oFZ?je&J6M9d8yBdYZUD64@JY`coubZsxkIx&2-IL-gE9b%K4z&)5rj16Yt3vnc z`{4L`fI(@`0b3?Pbuv;B?aGzyq-_t1N#^pKY@lp$6YgbP71VUO7xGK`09kcWs|=$G z-9Do4&{?Jhj$W`=k`64KV$JmTE|+mGkX?4z!bh4J-PamZFn|8{#s*~)!tDNv8l_SF z=<&6=cYAi>aVY!lUFgm@QAwc4jBq~k2#GmtySx_ivuLfb7X_)A=f(-h)flb(Jpcm< zAh&QX?O&d*AtDBczLhh@bo-AEUb-@DjJWjXylb$m(uG!}H1g_)#xI&Btoc=V*^BvvMqzpKW;`PLjym7P(M?&yz zs>40A0LX7vHKUYKvnwef?az#|&4a>tqGnU*39rJl61+2hIc4 zP-VSV9IPr{;g~fLe^{*L1wyxVn#Z$5;^(yHgdeDQ04fjR;<1fcWKs`*dR&lOb}^)8 zM2dm(4F1mU+B$;~aaTV&`Sk z^5ivG(Y8UUmDt+Qmnf_Vt2sw}-}BbkvcONbe85g4Y*~Ve4ih~?->s^7x=I(lBmt5{)JIWG2zKX9cFO zNQ+o2mIOU^YLO+=^g%VNp9uyMsLZUj(fkO~h+J56iGQ_S-e-{_x^j=fPN#pbS{2DkAzffUkg4aV`o3`h zOZV;6^2(iSbNbW)1if%{amNx)JnB9lBkz`z@Q!7=0t(1uFLGU*4Fdo_4F%@XR^Y1j zvjjbmuW!*7tFO@`P=6`|C#N;x1RtM(lNmeqoj~B#X0(00_7o|AszSPgw$tolQ=A^S zY13!b$*`G_=}9v0Uv_{}>lkQDt%CydFqsN)N4PMIF6_BpsnJb=)n$}YqEJ_fYBDdo z$POZ?<^)n@P7B)VCTa=b3P^1fWeEG}X+Nr7<=++oV-8?{9L`N!KA&40Ksi}F~Cal%7i z{(Wn4uq7@T7&&MaRr#9GM2=~_AVezv8l@l6Oq>w?39{vB-l+%g)jXHI4n=~OAKDwB z+2}m)9R4&om@WdPE`pVIwpECY2pT5Hbt*2sN;!RkZmyu>IL#XSCZ8_-1;6^-gf)Nl zlyq^H9nQyF3zoRP$feG=+7m4B+n!Sotvrm|5jUwGB@9jfaZw-QPsVGGVq@2%NI*7N z%GsaV_W4bz(n{m^DwHiBFr#^K>HVqWR@XZOq9;t#*AT8}dH!}(cDqiidvFl4=E~T< z3tuLs0DrbTU{Kp0S@=9imn`u{S#E=N?+X~N@c92gzyxNL35%V)t!yd%0ALl)d1^WY zP;ft(jVo7Y%A+P!_6OwR&8f3j#lmX|(g7foD;WYBoXrZM!hjLKe=BZJlQ$3x+AD=9@l$Z+(g6nPW^DzE3|8|nzW6(imlev(R z>FRFXiy#xV`V6@Dz>%4+9n(nsaa=np7U2f^gB@*FThho$?A4DIqWu`@W?>Z5B7ivm zL$KLdoolu7hAeGepoHuDObw(mlr^1Ce%Cb*lrIza@_LZqutzKwiq+lk)VbetQwiRl zQNa^9r`tZrI!jQ@XSPrItn?RY4MU?QnvcyLa8{ZkKTv-aeN$wN^$|?%`v=G zdSx3a05Kk3DypTe8CIPE=3kh$2}#FaT`c3p6k1$OemB<1eIG>d5GCx8O78KEc6{fcnZ+HalK!B z=pA%q+P%{$yJU|U+NW1C+cD07-C9%0#pzyXZ`Uke;P4L@%AmWl2#227Q!LsQ@Ft9~ zC5Eu6_OE*N2~nl>%k!q7GNnjD5W}ZY;lqzw4{=B{E0ssMwD_p>lyJ+JKZKoy+t;{% zDEuerC#$62lmK;&$^;W;UMVo#WL3s65+0!(X4B5-^kYI+%AQ(5SCf9uAaW??>_rAA zZ*|p{lL4HdAgfbo#R;Dpebw0sh6L??xGS)XZ+v(f?KXx~K?e1co+zo! zUs1H?G7PgtF6uOq%XM42Sd8hU!?Q;QP}(g54&Xi)BJj=ykOV<|tB`$QG1CCgig+RM z$f8LfooCp%rbAO>i(pZeBL5>GnD+#?7m30cUf4p#78zbvO!r*@IrpiYNr=Ac&Y8fU zmF?J86vs?X5rIRt+42Q{)Y*gg5OER~;-2TD=hy~ZFXJD$ zd&81us2SyVBRShQP3mWid989XiZ6C$gvR)k=ViLv#?mkVpD3#v4v9F+OJ1bgiH}PNvLFQAqLl5gIEtV(5t1@Kkq_!{=#&RY0C-A~fBO0y_n=Kf!G)ZwQ2=kVI_6rv$W?PceCF2Z(z(z&8ljr15N zb=%xgy}g#HY6y-StOKvG*KTvMt`D#BeQT+}G&}XZm`a_?jiluO@B+|9)}kxm@X)ha zJgF;YjPVj1JgZKP7EQ9GVM#dWQfLcGS9wq?v3igRtu+mgDGSus8Dv|qNJio!n(4rJ zg+EyCIv^gtNUOZDlCoQ7IRfZ&Xx&dv+UAOPf3v4Jf z1)tmXN=y185VxZ}h0&23LNOC)%xzpyCDXIU$}-ODK4mD9yICONnB)lbdc>=+fX&oqfE?NqGCHY1Oby?@;8Pog>Je_WIzey5^`K1rw$ zO9W)*PJu}Uk|8@506<&8sM#F=Q4VOhe281gDn7GM{QRP)MAgX2DFm0!hF4r8z9N`J zi#RHVS7BxT;`k{yE>8(16}aBGU-0vKV7 z@LnOKhTUC^({lX5^`RFstO7)4XPkun#=GwR|rL1(#FU_AP z#J{w84TE}Jp9^Ey*bk;gW}r&x*e?9XsdN>vHB4W1?r4G@p)2J(Y+p_9&a9KS&@||! z3m+i%0cu-9y8xsOn*i8z@olWOxGTXF80jkg^y^Ln%8)4fo!dC{-vpMvw_DGv&aUR? mt=0(S8dVJQx+(^fUDK~N0)hlf_M82tKxK6-ReaTx9KJ0*Yz8p^ literal 10324 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 From 4d50b47ec8f9f87458b2fa555afa1d3cd85e1352 Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Thu, 1 Dec 2022 20:13:34 +0000 Subject: [PATCH 652/966] feat: Introduce a way to provide scopes granted by user (#1189) * feat: Introduce a way to provide scopes granted by user * nits * add rt * update rt --- .../google-auth/google/oauth2/credentials.py | 25 +++++- .../tests/oauth2/test_credentials.py | 83 ++++++++++++++++--- 2 files changed, 96 insertions(+), 12 deletions(-) diff --git a/packages/google-auth/google/oauth2/credentials.py b/packages/google-auth/google/oauth2/credentials.py index 8f1c3dda470b..457db76ae344 100644 --- a/packages/google-auth/google/oauth2/credentials.py +++ b/packages/google-auth/google/oauth2/credentials.py @@ -34,6 +34,7 @@ from datetime import datetime import io import json +import logging import six @@ -43,6 +44,8 @@ from google.auth import exceptions from google.oauth2 import reauth +_LOGGER = logging.getLogger(__name__) + # The Google OAuth 2.0 token endpoint. Used for authorized user credentials. _GOOGLE_OAUTH2_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token" @@ -79,6 +82,7 @@ def __init__( rapt_token=None, refresh_handler=None, enable_reauth_refresh=False, + granted_scopes=None, ): """ Args: @@ -117,6 +121,9 @@ def __init__( retrieving downscoped tokens from a token broker. enable_reauth_refresh (Optional[bool]): Whether reauth refresh flow should be used. This flag is for gcloud to use only. + granted_scopes (Optional[Sequence[str]]): The scopes that were consented/granted by the user. + This could be different from the requested scopes and it could be empty if granted + and requested scopes were same. """ super(Credentials, self).__init__() self.token = token @@ -125,6 +132,7 @@ def __init__( self._id_token = id_token self._scopes = scopes self._default_scopes = default_scopes + self._granted_scopes = granted_scopes self._token_uri = token_uri self._client_id = client_id self._client_secret = client_secret @@ -155,6 +163,7 @@ def __setstate__(self, d): self._id_token = d.get("_id_token") self._scopes = d.get("_scopes") self._default_scopes = d.get("_default_scopes") + self._granted_scopes = d.get("_granted_scopes") self._token_uri = d.get("_token_uri") self._client_id = d.get("_client_id") self._client_secret = d.get("_client_secret") @@ -174,6 +183,11 @@ def scopes(self): """Optional[str]: The OAuth 2.0 permission scopes.""" return self._scopes + @property + def granted_scopes(self): + """Optional[Sequence[str]]: The OAuth 2.0 permission scopes that were granted by the user.""" + return self._granted_scopes + @property def token_uri(self): """Optional[str]: The OAuth 2.0 authorization server's token endpoint @@ -249,6 +263,7 @@ def with_quota_project(self, quota_project_id): client_secret=self.client_secret, scopes=self.scopes, default_scopes=self.default_scopes, + granted_scopes=self.granted_scopes, quota_project_id=quota_project_id, rapt_token=self.rapt_token, enable_reauth_refresh=self._enable_reauth_refresh, @@ -266,6 +281,7 @@ def with_token_uri(self, token_uri): client_secret=self.client_secret, scopes=self.scopes, default_scopes=self.default_scopes, + granted_scopes=self.granted_scopes, quota_project_id=self.quota_project_id, rapt_token=self.rapt_token, enable_reauth_refresh=self._enable_reauth_refresh, @@ -335,10 +351,15 @@ def refresh(self, request): if scopes and "scope" in grant_response: requested_scopes = frozenset(scopes) - granted_scopes = frozenset(grant_response["scope"].split()) + self._granted_scopes = grant_response["scope"].split() + granted_scopes = frozenset(self._granted_scopes) scopes_requested_but_not_granted = requested_scopes - granted_scopes if scopes_requested_but_not_granted: - raise exceptions.RefreshError( + # User might be presented with unbundled scopes at the time of + # consent. So it is a valid scenario to not have all the requested + # scopes as part of granted scopes but log a warning in case the + # developer wants to debug the scenario. + _LOGGER.warning( "Not all requested scopes were granted by the " "authorization server, missing scopes {}.".format( ", ".join(scopes_requested_but_not_granted) diff --git a/packages/google-auth/tests/oauth2/test_credentials.py b/packages/google-auth/tests/oauth2/test_credentials.py index c8301078d4dc..5a63224fd6e2 100644 --- a/packages/google-auth/tests/oauth2/test_credentials.py +++ b/packages/google-auth/tests/oauth2/test_credentials.py @@ -449,6 +449,7 @@ def test_credentials_with_scopes_requested_refresh_success( assert creds.id_token == mock.sentinel.id_token assert creds.has_scopes(scopes) assert creds.rapt_token == new_rapt_token + assert creds.granted_scopes == scopes # Check that the credentials are valid (have a token and are not # expired.) @@ -466,7 +467,7 @@ def test_credentials_with_only_default_scopes_requested( token = "token" new_rapt_token = "new_rapt_token" expiry = _helpers.utcnow() + datetime.timedelta(seconds=500) - grant_response = {"id_token": mock.sentinel.id_token} + grant_response = {"id_token": mock.sentinel.id_token, "scope": "email profile"} refresh_grant.return_value = ( # Access token token, @@ -513,6 +514,7 @@ def test_credentials_with_only_default_scopes_requested( assert creds.id_token == mock.sentinel.id_token assert creds.has_scopes(default_scopes) assert creds.rapt_token == new_rapt_token + assert creds.granted_scopes == default_scopes # Check that the credentials are valid (have a token and are not # expired.) @@ -530,10 +532,7 @@ def test_credentials_with_scopes_returned_refresh_success( token = "token" new_rapt_token = "new_rapt_token" expiry = _helpers.utcnow() + datetime.timedelta(seconds=500) - grant_response = { - "id_token": mock.sentinel.id_token, - "scopes": " ".join(scopes), - } + grant_response = {"id_token": mock.sentinel.id_token, "scope": " ".join(scopes)} refresh_grant.return_value = ( # Access token token, @@ -580,6 +579,7 @@ def test_credentials_with_scopes_returned_refresh_success( assert creds.id_token == mock.sentinel.id_token assert creds.has_scopes(scopes) assert creds.rapt_token == new_rapt_token + assert creds.granted_scopes == scopes # Check that the credentials are valid (have a token and are not # expired.) @@ -590,7 +590,72 @@ def test_credentials_with_scopes_returned_refresh_success( "google.auth._helpers.utcnow", return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD, ) - def test_credentials_with_scopes_refresh_failure_raises_refresh_error( + def test_credentials_with_only_default_scopes_requested_different_granted_scopes( + self, unused_utcnow, refresh_grant + ): + default_scopes = ["email", "profile"] + token = "token" + new_rapt_token = "new_rapt_token" + expiry = _helpers.utcnow() + datetime.timedelta(seconds=500) + grant_response = {"id_token": mock.sentinel.id_token, "scope": "email"} + refresh_grant.return_value = ( + # Access token + token, + # New refresh token + None, + # Expiry, + expiry, + # Extra data + grant_response, + # rapt token + new_rapt_token, + ) + + request = mock.create_autospec(transport.Request) + creds = credentials.Credentials( + token=None, + refresh_token=self.REFRESH_TOKEN, + token_uri=self.TOKEN_URI, + client_id=self.CLIENT_ID, + client_secret=self.CLIENT_SECRET, + default_scopes=default_scopes, + rapt_token=self.RAPT_TOKEN, + enable_reauth_refresh=True, + ) + + # Refresh credentials + creds.refresh(request) + + # Check jwt grant call. + refresh_grant.assert_called_with( + request, + self.TOKEN_URI, + self.REFRESH_TOKEN, + self.CLIENT_ID, + self.CLIENT_SECRET, + default_scopes, + self.RAPT_TOKEN, + True, + ) + + # Check that the credentials have the token and expiry + assert creds.token == token + assert creds.expiry == expiry + assert creds.id_token == mock.sentinel.id_token + assert creds.has_scopes(default_scopes) + assert creds.rapt_token == new_rapt_token + assert creds.granted_scopes == ["email"] + + # Check that the credentials are valid (have a token and are not + # expired.) + assert creds.valid + + @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True) + @mock.patch( + "google.auth._helpers.utcnow", + return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD, + ) + def test_credentials_with_scopes_refresh_different_granted_scopes( self, unused_utcnow, refresh_grant ): scopes = ["email", "profile"] @@ -628,10 +693,7 @@ def test_credentials_with_scopes_refresh_failure_raises_refresh_error( ) # Refresh credentials - with pytest.raises( - exceptions.RefreshError, match="Not all requested scopes were granted" - ): - creds.refresh(request) + creds.refresh(request) # Check jwt grant call. refresh_grant.assert_called_with( @@ -651,6 +713,7 @@ def test_credentials_with_scopes_refresh_failure_raises_refresh_error( assert creds.id_token == mock.sentinel.id_token assert creds.has_scopes(scopes) assert creds.rapt_token == new_rapt_token + assert creds.granted_scopes == scopes_returned # Check that the credentials are valid (have a token and are not # expired.) From 35c642357bd4c2f23a67eecbce69c89f337b7b5b Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 1 Dec 2022 12:39:36 -0800 Subject: [PATCH 653/966] chore(main): release 2.15.0 (#1183) --- packages/google-auth/CHANGELOG.md | 16 ++++++++++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index f65921b5df6b..5a3cc94cff0a 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,22 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.15.0](https://github.com/googleapis/google-auth-library-python/compare/v2.14.1...v2.15.0) (2022-12-01) + + +### Features + +* Add api_key credentials ([#1184](https://github.com/googleapis/google-auth-library-python/issues/1184)) ([370293e](https://github.com/googleapis/google-auth-library-python/commit/370293e84a14af0d6c6b34287bdcad020e0580e4)) +* Introduce a way to provide scopes granted by user ([#1189](https://github.com/googleapis/google-auth-library-python/issues/1189)) ([189f504](https://github.com/googleapis/google-auth-library-python/commit/189f504cbdfe043949688dfe55f3f449befad991)) + + +### Bug Fixes + +* Allow mtls sts endpoint for external account token urls. ([#1185](https://github.com/googleapis/google-auth-library-python/issues/1185)) ([c86dd69](https://github.com/googleapis/google-auth-library-python/commit/c86dd69cf79809e2d532a745a236db840fd8bc5d)) +* CI broken by removal of py.path ([#1194](https://github.com/googleapis/google-auth-library-python/issues/1194)) ([f719415](https://github.com/googleapis/google-auth-library-python/commit/f719415475e10e5af9ec75b3b13c57c25682bea0)) +* Ensure JWT segments have the right types ([#1162](https://github.com/googleapis/google-auth-library-python/issues/1162)) ([fc843cd](https://github.com/googleapis/google-auth-library-python/commit/fc843cd318e4ac4f40cf83bbcd7c6eae2b597ff8)) +* Updated the lower bound of interactive timeout and fix the kwarg… ([#1182](https://github.com/googleapis/google-auth-library-python/issues/1182)) ([50c0fd2](https://github.com/googleapis/google-auth-library-python/commit/50c0fd29a3b6a4fd6dc4b801d883f5d2b6de88c6)) + ## [2.14.1](https://github.com/googleapis/google-auth-library-python/compare/v2.14.0...v2.14.1) (2022-11-07) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 5a2a1ee060f6..f0ecd5d63c45 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.14.1" +__version__ = "2.15.0" From ee0ce8da40f36a7985101aa5489503026c398839 Mon Sep 17 00:00:00 2001 From: Carl Lundin <108372512+clundin25@users.noreply.github.com> Date: Fri, 2 Dec 2022 23:34:00 +0000 Subject: [PATCH 654/966] chore: Add requests as an extra dependency. (#1137) This allows users of pip to specify a dependency on requests during the pip install process, e.g. `pip install google-auth[requests]`. This resolves https://github.com/googleapis/google-auth-library-python/issues/1069. --- packages/google-auth/setup.py | 1 + .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10323 bytes 2 files changed, 1 insertion(+) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 79d31b87c240..c89b05d1d5c5 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -37,6 +37,7 @@ "requests >= 2.20.0, < 3.0.0dev", ], "pyopenssl": ["pyopenssl>=20.0.0", "cryptography>=38.0.3"], + "requests": "requests >= 2.20.0, < 3.0.0dev", "reauth": "pyu2f>=0.1.5", # Enterprise cert only works for OpenSSL 1.1.1. Newer versions of these # dependencies are built with OpenSSL 3.0 so we need to fix the version. diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 6ad7ea0142eec8f6617e688fd13a1dd0ccf961c0..00a0898dac293b0195d0d66d7cc326193f2b7b24 100644 GIT binary patch literal 10323 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 literal 10324 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

_U*%UX>a}5!|9#ujoBu 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%0ALl)d1^WY zP;ft(jVo7Y%A+P!_6OwR&8f3j#lmX|(g7foD;WYBoXrZM!hjLKe=BZJlQ$3x+AD=9@l$Z+(g6nPW^DzE3|8|nzW6(imlev(R z>FRFXiy#xV`V6@Dz>%4+9n(nsaa=np7U2f^gB@*FThho$?A4DIqWu`@W?>Z5B7ivm zL$KLdoolu7hAeGepoHuDObw(mlr^1Ce%Cb*lrIza@_LZqutzKwiq+lk)VbetQwiRl zQNa^9r`tZrI!jQ@XSPrItn?RY4MU?QnvcyLa8{ZkKTv-aeN$wN^$|?%`v=G zdSx3a05Kk3DypTe8CIPE=3kh$2}#FaT`c3p6k1$OemB<1eIG>d5GCx8O78KEc6{fcnZ+HalK!B z=pA%q+P%{$yJU|U+NW1C+cD07-C9%0#pzyXZ`Uke;P4L@%AmWl2#227Q!LsQ@Ft9~ zC5Eu6_OE*N2~nl>%k!q7GNnjD5W}ZY;lqzw4{=B{E0ssMwD_p>lyJ+JKZKoy+t;{% zDEuerC#$62lmK;&$^;W;UMVo#WL3s65+0!(X4B5-^kYI+%AQ(5SCf9uAaW??>_rAA zZ*|p{lL4HdAgfbo#R;Dpebw0sh6L??xGS)XZ+v(f?KXx~K?e1co+zo! zUs1H?G7PgtF6uOq%XM42Sd8hU!?Q;QP}(g54&Xi)BJj=ykOV<|tB`$QG1CCgig+RM z$f8LfooCp%rbAO>i(pZeBL5>GnD+#?7m30cUf4p#78zbvO!r*@IrpiYNr=Ac&Y8fU zmF?J86vs?X5rIRt+42Q{)Y*gg5OER~;-2TD=hy~ZFXJD$ zd&81us2SyVBRShQP3mWid989XiZ6C$gvR)k=ViLv#?mkVpD3#v4v9F+OJ1bgiH}PNvLFQAqLl5gIEtV(5t1@Kkq_!{=#&RY0C-A~fBO0y_n=Kf!G)ZwQ2=kVI_6rv$W?PceCF2Z(z(z&8ljr15N zb=%xgy}g#HY6y-StOKvG*KTvMt`D#BeQT+}G&}XZm`a_?jiluO@B+|9)}kxm@X)ha zJgF;YjPVj1JgZKP7EQ9GVM#dWQfLcGS9wq?v3igRtu+mgDGSus8Dv|qNJio!n(4rJ zg+EyCIv^gtNUOZDlCoQ7IRfZ&Xx&dv+UAOPf3v4Jf z1)tmXN=y185VxZ}h0&23LNOC)%xzpyCDXIU$}-ODK4mD9yICONnB)lbdc>=+fX&oqfE?NqGCHY1Oby?@;8Pog>Je_WIzey5^`K1rw$ zO9W)*PJu}Uk|8@506<&8sM#F=Q4VOhe281gDn7GM{QRP)MAgX2DFm0!hF4r8z9N`J zi#RHVS7BxT;`k{yE>8(16}aBGU-0vKV7 z@LnOKhTUC^({lX5^`RFstO7)4XPkun#=GwR|rL1(#FU_AP z#J{w84TE}Jp9^Ey*bk;gW}r&x*e?9XsdN>vHB4W1?r4G@p)2J(Y+p_9&a9KS&@||! z3m+i%0cu-9y8xsOn*i8z@olWOxGTXF80jkg^y^Ln%8)4fo!dC{-vpMvw_DGv&aUR? mt=0(S8dVJQx+(^fUDK~N0)hlf_M82tKxK6-ReaTx9KJ0*Yz8p^ From 040c5b420089428cbf8a0ee67247a0baf161e4d2 Mon Sep 17 00:00:00 2001 From: Jin Date: Mon, 5 Dec 2022 16:54:56 -0800 Subject: [PATCH 655/966] chore: update token (#1197) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10323 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 00a0898dac293b0195d0d66d7cc326193f2b7b24..b6409753b4a1f81a87317494d650feb5b7630589 100644 GIT binary patch literal 10324 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 From 8302da23e27ade358e213e2e8f9f2b033c3eead6 Mon Sep 17 00:00:00 2001 From: Jin Date: Mon, 5 Dec 2022 20:19:48 -0800 Subject: [PATCH 656/966] feat: wrap all python built-in exceptions into library excpetions (#1191) * feat: wrap all python built-in exceptions into library excpetions * remove wrapped StopIteration since it will never thrown --- packages/google-auth/google/auth/_default.py | 4 +- packages/google-auth/google/auth/_helpers.py | 17 ++++-- .../google/auth/_service_account_info.py | 5 +- packages/google-auth/google/auth/api_key.py | 3 +- .../google-auth/google/auth/app_engine.py | 13 ++-- packages/google-auth/google/auth/aws.py | 10 +-- .../google/auth/compute_engine/credentials.py | 6 +- .../google-auth/google/auth/credentials.py | 9 +-- .../google/auth/crypt/_python_rsa.py | 7 ++- .../google-auth/google/auth/crypt/base.py | 3 +- .../google-auth/google/auth/downscoped.py | 61 ++++++++++--------- .../google-auth/google/auth/exceptions.py | 24 ++++++++ .../google/auth/external_account.py | 10 +-- .../auth/external_account_authorized_user.py | 2 +- .../google-auth/google/auth/identity_pool.py | 10 +-- packages/google-auth/google/auth/jwt.py | 52 ++++++++++------ packages/google-auth/google/auth/pluggable.py | 46 ++++++++------ .../auth/transport/_aiohttp_requests.py | 2 +- .../google-auth/google/auth/transport/grpc.py | 2 +- 19 files changed, 177 insertions(+), 109 deletions(-) diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index 0860c67fe437..195388c9d8e1 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -434,7 +434,7 @@ def _get_impersonated_service_account_credentials(filename, info, scopes): filename, source_credentials_info ) else: - raise ValueError( + raise exceptions.InvalidType( "source credential of type {} is not supported.".format( source_credentials_type ) @@ -443,7 +443,7 @@ def _get_impersonated_service_account_credentials(filename, info, scopes): start_index = impersonation_url.rfind("/") end_index = impersonation_url.find(":generateAccessToken") if start_index == -1 or end_index == -1 or start_index > end_index: - raise ValueError( + raise exceptions.InvalidValue( "Cannot extract target principal from {}".format(impersonation_url) ) target_principal = impersonation_url[start_index + 1 : end_index] diff --git a/packages/google-auth/google/auth/_helpers.py b/packages/google-auth/google/auth/_helpers.py index 1b08ab87f83c..30fbafb64739 100644 --- a/packages/google-auth/google/auth/_helpers.py +++ b/packages/google-auth/google/auth/_helpers.py @@ -22,6 +22,7 @@ import six from six.moves import urllib +from google.auth import exceptions # Token server doesn't provide a new a token when doing refresh unless the # token is expiring within 30 seconds, so refresh threshold should not be @@ -51,10 +52,10 @@ def decorator(method): Callable: the same method passed in with an updated docstring. Raises: - ValueError: if the method already has a docstring. + google.auth.exceptions.InvalidOperation: if the method already has a docstring. """ if method.__doc__: - raise ValueError("Method already has a docstring.") + raise exceptions.InvalidOperation("Method already has a docstring.") source_method = getattr(source_class, method.__name__) method.__doc__ = source_method.__doc__ @@ -101,13 +102,15 @@ def to_bytes(value, encoding="utf-8"): passed in if it started out as bytes. Raises: - ValueError: If the value could not be converted to bytes. + google.auth.exceptions.InvalidValue: If the value could not be converted to bytes. """ result = value.encode(encoding) if isinstance(value, six.text_type) else value if isinstance(result, six.binary_type): return result else: - raise ValueError("{0!r} could not be converted to bytes".format(value)) + raise exceptions.InvalidValue( + "{0!r} could not be converted to bytes".format(value) + ) def from_bytes(value): @@ -121,13 +124,15 @@ def from_bytes(value): if it started out as unicode. Raises: - ValueError: If the value could not be converted to unicode. + google.auth.exceptions.InvalidValue: If the value could not be converted to unicode. """ result = value.decode("utf-8") if isinstance(value, six.binary_type) else value if isinstance(result, six.text_type): return result else: - raise ValueError("{0!r} could not be converted to unicode".format(value)) + raise exceptions.InvalidValue( + "{0!r} could not be converted to unicode".format(value) + ) def update_query(url, params, remove=None): diff --git a/packages/google-auth/google/auth/_service_account_info.py b/packages/google-auth/google/auth/_service_account_info.py index 15709927385e..b17f34f5c255 100644 --- a/packages/google-auth/google/auth/_service_account_info.py +++ b/packages/google-auth/google/auth/_service_account_info.py @@ -20,6 +20,7 @@ import six from google.auth import crypt +from google.auth import exceptions def from_dict(data, require=None, use_rsa_signer=True): @@ -40,7 +41,7 @@ def from_dict(data, require=None, use_rsa_signer=True): service account file. Raises: - ValueError: if the data was in the wrong format, or if one of the + MalformedError: if the data was in the wrong format, or if one of the required keys is missing. """ keys_needed = set(require if require is not None else []) @@ -48,7 +49,7 @@ def from_dict(data, require=None, use_rsa_signer=True): missing = keys_needed.difference(six.iterkeys(data)) if missing: - raise ValueError( + raise exceptions.MalformedError( "Service account info was not in the expected format, missing " "fields {}.".format(", ".join(missing)) ) diff --git a/packages/google-auth/google/auth/api_key.py b/packages/google-auth/google/auth/api_key.py index 49c6ffd2d561..4fdf7f2769ca 100644 --- a/packages/google-auth/google/auth/api_key.py +++ b/packages/google-auth/google/auth/api_key.py @@ -20,6 +20,7 @@ from google.auth import _helpers from google.auth import credentials +from google.auth import exceptions class Credentials(credentials.Credentials): @@ -36,7 +37,7 @@ def __init__(self, token): """ super(Credentials, self).__init__() if not token: - raise ValueError("Token must be a non-empty API key string") + raise exceptions.InvalidValue("Token must be a non-empty API key string") self.token = token @property diff --git a/packages/google-auth/google/auth/app_engine.py b/packages/google-auth/google/auth/app_engine.py index 1460a7d1ae0c..7083ee61436b 100644 --- a/packages/google-auth/google/auth/app_engine.py +++ b/packages/google-auth/google/auth/app_engine.py @@ -27,6 +27,7 @@ from google.auth import _helpers from google.auth import credentials from google.auth import crypt +from google.auth import exceptions # pytype: disable=import-error try: @@ -67,13 +68,13 @@ def get_project_id(): str: The project ID Raises: - EnvironmentError: If the App Engine APIs are unavailable. + google.auth.exceptions.OSError: If the App Engine APIs are unavailable. """ # pylint: disable=missing-raises-doc - # Pylint rightfully thinks EnvironmentError is OSError, but doesn't + # Pylint rightfully thinks google.auth.exceptions.OSError is OSError, but doesn't # realize it's a valid alias. if app_identity is None: - raise EnvironmentError("The App Engine APIs are not available.") + raise exceptions.OSError("The App Engine APIs are not available.") return app_identity.get_application_id() @@ -107,13 +108,13 @@ def __init__( and billing. Raises: - EnvironmentError: If the App Engine APIs are unavailable. + google.auth.exceptions.OSError: If the App Engine APIs are unavailable. """ # pylint: disable=missing-raises-doc - # Pylint rightfully thinks EnvironmentError is OSError, but doesn't + # Pylint rightfully thinks google.auth.exceptions.OSError is OSError, but doesn't # realize it's a valid alias. if app_identity is None: - raise EnvironmentError("The App Engine APIs are not available.") + raise exceptions.OSError("The App Engine APIs are not available.") super(Credentials, self).__init__() self._scopes = scopes diff --git a/packages/google-auth/google/auth/aws.py b/packages/google-auth/google/auth/aws.py index 04c5e7c5da3a..d3038b2816c4 100644 --- a/packages/google-auth/google/auth/aws.py +++ b/packages/google-auth/google/auth/aws.py @@ -123,7 +123,7 @@ def get_request_options( ) # Validate provided URL. if not uri.hostname or uri.scheme != "https": - raise ValueError("Invalid AWS service URL") + raise exceptions.InvalidResource("Invalid AWS service URL") header_map = _generate_authentication_header_map( host=uri.hostname, @@ -408,9 +408,11 @@ def __init__( env_id, env_version = (None, None) if env_id != "aws" or self._cred_verification_url is None: - raise ValueError("No valid AWS 'credential_source' provided") + raise exceptions.InvalidResource( + "No valid AWS 'credential_source' provided" + ) elif int(env_version or "") != 1: - raise ValueError( + raise exceptions.InvalidValue( "aws version '{}' is not supported in the current build.".format( env_version ) @@ -428,7 +430,7 @@ def validate_metadata_server_url_if_any(url_string, name_of_data): if url_string: url = urlparse(url_string) if url.hostname != "169.254.169.254" and url.hostname != "fd00:ec2::254": - raise ValueError( + raise exceptions.InvalidResource( "Invalid hostname '{}' for '{}'".format(url.hostname, name_of_data) ) diff --git a/packages/google-auth/google/auth/compute_engine/credentials.py b/packages/google-auth/google/auth/compute_engine/credentials.py index e97fabea94a2..618fa5a2da92 100644 --- a/packages/google-auth/google/auth/compute_engine/credentials.py +++ b/packages/google-auth/google/auth/compute_engine/credentials.py @@ -221,7 +221,7 @@ def __init__( if use_metadata_identity_endpoint: if token_uri or additional_claims or service_account_email or signer: - raise ValueError( + raise exceptions.MalformedError( "If use_metadata_identity_endpoint is set, token_uri, " "additional_claims, service_account_email, signer arguments" " must not be set" @@ -312,7 +312,7 @@ def with_token_uri(self, token_uri): # since the signer is already instantiated, # the request is not needed if self._use_metadata_identity_endpoint: - raise ValueError( + raise exceptions.MalformedError( "If use_metadata_identity_endpoint is set, token_uri" " must not be set" ) else: @@ -423,7 +423,7 @@ def sign_bytes(self, message): Signer is not available if metadata identity endpoint is used. """ if self._use_metadata_identity_endpoint: - raise ValueError( + raise exceptions.InvalidOperation( "Signer is not available if metadata identity endpoint is used" ) return self._signer.sign(message) diff --git a/packages/google-auth/google/auth/credentials.py b/packages/google-auth/google/auth/credentials.py index ca1032a14579..4c0af7a6b999 100644 --- a/packages/google-auth/google/auth/credentials.py +++ b/packages/google-auth/google/auth/credentials.py @@ -21,6 +21,7 @@ import six from google.auth import _helpers, environment_vars +from google.auth import exceptions @six.add_metaclass(abc.ABCMeta) @@ -190,9 +191,9 @@ def valid(self): return True def refresh(self, request): - """Raises :class:`ValueError``, anonymous credentials cannot be + """Raises :class:``InvalidOperation``, anonymous credentials cannot be refreshed.""" - raise ValueError("Anonymous credentials cannot be refreshed.") + raise exceptions.InvalidOperation("Anonymous credentials cannot be refreshed.") def apply(self, headers, token=None): """Anonymous credentials do nothing to the request. @@ -200,10 +201,10 @@ def apply(self, headers, token=None): The optional ``token`` argument is not supported. Raises: - ValueError: If a token was specified. + google.auth.exceptions.InvalidValue: If a token was specified. """ if token is not None: - raise ValueError("Anonymous credentials don't support tokens.") + raise exceptions.InvalidValue("Anonymous credentials don't support tokens.") def before_request(self, request, method, url, headers): """Anonymous credentials do nothing to the request.""" diff --git a/packages/google-auth/google/auth/crypt/_python_rsa.py b/packages/google-auth/google/auth/crypt/_python_rsa.py index 797a2592b84d..e8595440cd79 100644 --- a/packages/google-auth/google/auth/crypt/_python_rsa.py +++ b/packages/google-auth/google/auth/crypt/_python_rsa.py @@ -29,6 +29,7 @@ import six from google.auth import _helpers +from google.auth import exceptions from google.auth.crypt import base _POW2 = (128, 64, 32, 16, 8, 4, 2, 1) @@ -101,7 +102,7 @@ def from_string(cls, public_key): der = rsa.pem.load_pem(public_key, "CERTIFICATE") asn1_cert, remaining = decoder.decode(der, asn1Spec=Certificate()) if remaining != b"": - raise ValueError("Unused bytes", remaining) + raise exceptions.InvalidValue("Unused bytes", remaining) cert_info = asn1_cert["tbsCertificate"]["subjectPublicKeyInfo"] key_bytes = _bit_list_to_bytes(cert_info["subjectPublicKey"]) @@ -162,12 +163,12 @@ def from_string(cls, key, key_id=None): elif marker_id == 1: key_info, remaining = decoder.decode(key_bytes, asn1Spec=_PKCS8_SPEC) if remaining != b"": - raise ValueError("Unused bytes", remaining) + raise exceptions.InvalidValue("Unused bytes", remaining) private_key_info = key_info.getComponentByName("privateKey") private_key = rsa.key.PrivateKey.load_pkcs1( private_key_info.asOctets(), format="DER" ) else: - raise ValueError("No key could be detected.") + raise exceptions.MalformedError("No key could be detected.") return cls(private_key, key_id=key_id) diff --git a/packages/google-auth/google/auth/crypt/base.py b/packages/google-auth/google/auth/crypt/base.py index c98d5bf64fc4..573211d7c233 100644 --- a/packages/google-auth/google/auth/crypt/base.py +++ b/packages/google-auth/google/auth/crypt/base.py @@ -20,6 +20,7 @@ import six +from google.auth import exceptions _JSON_FILE_PRIVATE_KEY = "private_key" _JSON_FILE_PRIVATE_KEY_ID = "private_key_id" @@ -106,7 +107,7 @@ def from_service_account_info(cls, info): ValueError: If the info is not in the expected format. """ if _JSON_FILE_PRIVATE_KEY not in info: - raise ValueError( + raise exceptions.MalformedError( "The private_key field was not found in the service account " "info." ) diff --git a/packages/google-auth/google/auth/downscoped.py b/packages/google-auth/google/auth/downscoped.py index a1d7b6e46e39..a84ac4af6de2 100644 --- a/packages/google-auth/google/auth/downscoped.py +++ b/packages/google-auth/google/auth/downscoped.py @@ -54,6 +54,7 @@ from google.auth import _helpers from google.auth import credentials +from google.auth import exceptions from google.oauth2 import sts # The maximum number of access boundary rules a Credential Access Boundary can @@ -86,8 +87,8 @@ def __init__(self, rules=[]): access boundary rules limiting the access that a downscoped credential will have. Raises: - TypeError: If any of the rules are not a valid type. - ValueError: If the provided rules exceed the maximum allowed. + InvalidType: If any of the rules are not a valid type. + InvalidValue: If the provided rules exceed the maximum allowed. """ self.rules = rules @@ -113,18 +114,18 @@ def rules(self, value): access boundary rules limiting the access that a downscoped credential will have. Raises: - TypeError: If any of the rules are not a valid type. - ValueError: If the provided rules exceed the maximum allowed. + InvalidType: If any of the rules are not a valid type. + InvalidValue: If the provided rules exceed the maximum allowed. """ if len(value) > _MAX_ACCESS_BOUNDARY_RULES_COUNT: - raise ValueError( + raise exceptions.InvalidValue( "Credential access boundary rules can have a maximum of {} rules.".format( _MAX_ACCESS_BOUNDARY_RULES_COUNT ) ) for access_boundary_rule in value: if not isinstance(access_boundary_rule, AccessBoundaryRule): - raise TypeError( + raise exceptions.InvalidType( "List of rules provided do not contain a valid 'google.auth.downscoped.AccessBoundaryRule'." ) # Make a copy of the original list. @@ -138,17 +139,17 @@ def add_rule(self, rule): limiting the access that a downscoped credential will have, to be added to the existing rules. Raises: - TypeError: If any of the rules are not a valid type. - ValueError: If the provided rules exceed the maximum allowed. + InvalidType: If any of the rules are not a valid type. + InvalidValue: If the provided rules exceed the maximum allowed. """ if len(self.rules) == _MAX_ACCESS_BOUNDARY_RULES_COUNT: - raise ValueError( + raise exceptions.InvalidValue( "Credential access boundary rules can have a maximum of {} rules.".format( _MAX_ACCESS_BOUNDARY_RULES_COUNT ) ) if not isinstance(rule, AccessBoundaryRule): - raise TypeError( + raise exceptions.InvalidType( "The provided rule does not contain a valid 'google.auth.downscoped.AccessBoundaryRule'." ) self._rules.append(rule) @@ -197,8 +198,8 @@ def __init__( specific Cloud Storage objects. Raises: - TypeError: If any of the parameters are not of the expected types. - ValueError: If any of the parameters are not of the expected values. + InvalidType: If any of the parameters are not of the expected types. + InvalidValue: If any of the parameters are not of the expected values. """ self.available_resource = available_resource self.available_permissions = available_permissions @@ -221,10 +222,12 @@ def available_resource(self, value): value (str): The updated value of the available resource. Raises: - TypeError: If the value is not a string. + google.auth.exceptions.InvalidType: If the value is not a string. """ if not isinstance(value, six.string_types): - raise TypeError("The provided available_resource is not a string.") + raise exceptions.InvalidType( + "The provided available_resource is not a string." + ) self._available_resource = value @property @@ -245,16 +248,16 @@ def available_permissions(self, value): value (Sequence[str]): The updated value of the available permissions. Raises: - TypeError: If the value is not a list of strings. - ValueError: If the value is not valid. + InvalidType: If the value is not a list of strings. + InvalidValue: If the value is not valid. """ for available_permission in value: if not isinstance(available_permission, six.string_types): - raise TypeError( + raise exceptions.InvalidType( "Provided available_permissions are not a list of strings." ) if available_permission.find("inRole:") != 0: - raise ValueError( + raise exceptions.InvalidValue( "available_permissions must be prefixed with 'inRole:'." ) # Make a copy of the original list. @@ -279,11 +282,11 @@ def availability_condition(self, value): value of the availability condition. Raises: - TypeError: If the value is not of type google.auth.downscoped.AvailabilityCondition + google.auth.exceptions.InvalidType: If the value is not of type google.auth.downscoped.AvailabilityCondition or None. """ if not isinstance(value, AvailabilityCondition) and value is not None: - raise TypeError( + raise exceptions.InvalidType( "The provided availability_condition is not a 'google.auth.downscoped.AvailabilityCondition' or None." ) self._availability_condition = value @@ -326,8 +329,8 @@ def __init__(self, expression, title=None, description=None): description (Optional[str]): Optional details about the purpose of the condition. Raises: - TypeError: If any of the parameters are not of the expected types. - ValueError: If any of the parameters are not of the expected values. + InvalidType: If any of the parameters are not of the expected types. + InvalidValue: If any of the parameters are not of the expected values. """ self.expression = expression self.title = title @@ -350,10 +353,10 @@ def expression(self, value): value (str): The updated value of the condition expression. Raises: - TypeError: If the value is not of type string. + google.auth.exceptions.InvalidType: If the value is not of type string. """ if not isinstance(value, six.string_types): - raise TypeError("The provided expression is not a string.") + raise exceptions.InvalidType("The provided expression is not a string.") self._expression = value @property @@ -373,10 +376,10 @@ def title(self, value): value (Optional[str]): The updated value of the title. Raises: - TypeError: If the value is not of type string or None. + google.auth.exceptions.InvalidType: If the value is not of type string or None. """ if not isinstance(value, six.string_types) and value is not None: - raise TypeError("The provided title is not a string or None.") + raise exceptions.InvalidType("The provided title is not a string or None.") self._title = value @property @@ -396,10 +399,12 @@ def description(self, value): value (Optional[str]): The updated value of the description. Raises: - TypeError: If the value is not of type string or None. + google.auth.exceptions.InvalidType: If the value is not of type string or None. """ if not isinstance(value, six.string_types) and value is not None: - raise TypeError("The provided description is not a string or None.") + raise exceptions.InvalidType( + "The provided description is not a string or None." + ) self._description = value def to_json(self): diff --git a/packages/google-auth/google/auth/exceptions.py b/packages/google-auth/google/auth/exceptions.py index 7760c87b897b..fcbe61b74623 100644 --- a/packages/google-auth/google/auth/exceptions.py +++ b/packages/google-auth/google/auth/exceptions.py @@ -74,3 +74,27 @@ def __init__(self, message=None, **kwargs): class ReauthSamlChallengeFailError(ReauthFailError): """An exception for SAML reauth challenge failures.""" + + +class MalformedError(DefaultCredentialsError, ValueError): + """An exception for malformed data.""" + + +class InvalidResource(DefaultCredentialsError, ValueError): + """An exception for URL error.""" + + +class InvalidOperation(DefaultCredentialsError, ValueError): + """An exception for invalid operation.""" + + +class InvalidValue(DefaultCredentialsError, ValueError): + """Used to wrap general ValueError of python.""" + + +class InvalidType(DefaultCredentialsError, TypeError): + """Used to wrap general TypeError of python.""" + + +class OSError(DefaultCredentialsError, EnvironmentError): + """Used to wrap EnvironmentError(OSError after python3.3).""" diff --git a/packages/google-auth/google/auth/external_account.py b/packages/google-auth/google/auth/external_account.py index 4249529e8453..d24b22837766 100644 --- a/packages/google-auth/google/auth/external_account.py +++ b/packages/google-auth/google/auth/external_account.py @@ -151,7 +151,7 @@ def __init__( if not self.is_workforce_pool and self._workforce_pool_user_project: # Workload identity pools do not support workforce pool user projects. - raise ValueError( + raise exceptions.InvalidValue( "workforce_pool_user_project should not be set for non-workforce pool " "credentials" ) @@ -445,7 +445,9 @@ def validate_token_url(token_url, url_type="token"): ] if not Credentials.is_valid_url(_TOKEN_URL_PATTERNS, token_url): - raise ValueError("The provided {} URL is invalid.".format(url_type)) + raise exceptions.InvalidResource( + "The provided {} URL is invalid.".format(url_type) + ) @staticmethod def validate_service_account_impersonation_url(url): @@ -460,7 +462,7 @@ def validate_service_account_impersonation_url(url): if not Credentials.is_valid_url( _SERVICE_ACCOUNT_IMPERSONATION_URL_PATTERNS, url ): - raise ValueError( + raise exceptions.InvalidResource( "The provided service account impersonation URL is invalid." ) @@ -498,7 +500,7 @@ def from_info(cls, info, **kwargs): credentials. Raises: - ValueError: For invalid parameters. + InvalidValue: For invalid parameters. """ return cls( audience=info.get("audience"), diff --git a/packages/google-auth/google/auth/external_account_authorized_user.py b/packages/google-auth/google/auth/external_account_authorized_user.py index 51e7f2058fff..04a7f5b918ac 100644 --- a/packages/google-auth/google/auth/external_account_authorized_user.py +++ b/packages/google-auth/google/auth/external_account_authorized_user.py @@ -118,7 +118,7 @@ def __init__( self._scopes = scopes if not self.valid and not self.can_refresh: - raise ValueError( + raise exceptions.InvalidOperation( "Token should be created with fields to make it valid (`token` and " "`expiry`), or fields to allow it to refresh (`refresh_token`, " "`token_url`, `client_id`, `client_secret`)." diff --git a/packages/google-auth/google/auth/identity_pool.py b/packages/google-auth/google/auth/identity_pool.py index 5fa9faef99c0..ebe50883c4f3 100644 --- a/packages/google-auth/google/auth/identity_pool.py +++ b/packages/google-auth/google/auth/identity_pool.py @@ -122,11 +122,11 @@ def __init__( # environment_id is only supported in AWS or dedicated future external # account credentials. if "environment_id" in credential_source: - raise ValueError( + raise exceptions.MalformedError( "Invalid Identity Pool credential_source field 'environment_id'" ) if self._credential_source_format_type not in ["text", "json"]: - raise ValueError( + raise exceptions.MalformedError( "Invalid credential_source format '{}'".format( self._credential_source_format_type ) @@ -137,18 +137,18 @@ def __init__( "subject_token_field_name" ) if self._credential_source_field_name is None: - raise ValueError( + raise exceptions.MalformedError( "Missing subject_token_field_name for JSON credential_source format" ) else: self._credential_source_field_name = None if self._credential_source_file and self._credential_source_url: - raise ValueError( + raise exceptions.MalformedError( "Ambiguous credential_source. 'file' is mutually exclusive with 'url'." ) if not self._credential_source_file and not self._credential_source_url: - raise ValueError( + raise exceptions.MalformedError( "Missing credential_source. A 'file' or 'url' must be provided." ) diff --git a/packages/google-auth/google/auth/jwt.py b/packages/google-auth/google/auth/jwt.py index 21de8fe9555d..39036895829c 100644 --- a/packages/google-auth/google/auth/jwt.py +++ b/packages/google-auth/google/auth/jwt.py @@ -122,7 +122,9 @@ def _decode_jwt_segment(encoded_section): try: return json.loads(section_bytes.decode("utf-8")) except ValueError as caught_exc: - new_exc = ValueError("Can't parse segment: {0}".format(section_bytes)) + new_exc = exceptions.MalformedError( + "Can't parse segment: {0}".format(section_bytes) + ) six.raise_from(new_exc, caught_exc) @@ -137,13 +139,14 @@ def _unverified_decode(token): signature. Raises: - ValueError: if there are an incorrect amount of segments in the token or - segments of the wrong type. + google.auth.exceptions.MalformedError: if there are an incorrect amount of segments in the token or segments of the wrong type. """ token = _helpers.to_bytes(token) if token.count(b".") != 2: - raise ValueError("Wrong number of segments in token: {0}".format(token)) + raise exceptions.MalformedError( + "Wrong number of segments in token: {0}".format(token) + ) encoded_header, encoded_payload, signature = token.split(b".") signed_section = encoded_header + b"." + encoded_payload @@ -154,12 +157,12 @@ def _unverified_decode(token): payload = _decode_jwt_segment(encoded_payload) if not isinstance(header, Mapping): - raise ValueError( + raise exceptions.MalformedError( "Header segment should be a JSON object: {0}".format(encoded_header) ) if not isinstance(payload, Mapping): - raise ValueError( + raise exceptions.MalformedError( "Payload segment should be a JSON object: {0}".format(encoded_payload) ) @@ -193,14 +196,17 @@ def _verify_iat_and_exp(payload, clock_skew_in_seconds=0): validation. Raises: - ValueError: if any checks failed. + google.auth.exceptions.InvalidValue: if value validation failed. + google.auth.exceptions.MalformedError: if schema validation failed. """ now = _helpers.datetime_to_secs(_helpers.utcnow()) # Make sure the iat and exp claims are present. for key in ("iat", "exp"): if key not in payload: - raise ValueError("Token does not contain required claim {}".format(key)) + raise exceptions.MalformedError( + "Token does not contain required claim {}".format(key) + ) # Make sure the token wasn't issued in the future. iat = payload["iat"] @@ -208,7 +214,7 @@ def _verify_iat_and_exp(payload, clock_skew_in_seconds=0): # for clock skew. earliest = iat - clock_skew_in_seconds if now < earliest: - raise ValueError( + raise exceptions.InvalidValue( "Token used too early, {} < {}. Check that your computer's clock is set correctly.".format( now, iat ) @@ -220,7 +226,7 @@ def _verify_iat_and_exp(payload, clock_skew_in_seconds=0): # to account for clow skew. latest = exp + clock_skew_in_seconds if latest < now: - raise ValueError("Token expired, {} < {}".format(latest, now)) + raise exceptions.InvalidValue("Token expired, {} < {}".format(latest, now)) def decode(token, certs=None, verify=True, audience=None, clock_skew_in_seconds=0): @@ -246,7 +252,8 @@ def decode(token, certs=None, verify=True, audience=None, clock_skew_in_seconds= Mapping[str, str]: The deserialized JSON payload in the JWT. Raises: - ValueError: if any verification checks failed. + google.auth.exceptions.InvalidValue: if value validation failed. + google.auth.exceptions.MalformedError: if schema validation failed. """ header, payload, signed_section, signature = _unverified_decode(token) @@ -263,7 +270,7 @@ def decode(token, certs=None, verify=True, audience=None, clock_skew_in_seconds= except KeyError as exc: if key_alg in _CRYPTOGRAPHY_BASED_ALGORITHMS: six.raise_from( - ValueError( + exceptions.InvalidValue( "The key algorithm {} requires the cryptography package " "to be installed.".format(key_alg) ), @@ -271,7 +278,10 @@ def decode(token, certs=None, verify=True, audience=None, clock_skew_in_seconds= ) else: six.raise_from( - ValueError("Unsupported signature algorithm {}".format(key_alg)), exc + exceptions.InvalidValue( + "Unsupported signature algorithm {}".format(key_alg) + ), + exc, ) # If certs is specified as a dictionary of key IDs to certificates, then @@ -279,7 +289,9 @@ def decode(token, certs=None, verify=True, audience=None, clock_skew_in_seconds= if isinstance(certs, Mapping): if key_id: if key_id not in certs: - raise ValueError("Certificate for key id {} not found.".format(key_id)) + raise exceptions.MalformedError( + "Certificate for key id {} not found.".format(key_id) + ) certs_to_check = [certs[key_id]] # If there's no key id in the header, check against all of the certs. else: @@ -291,7 +303,7 @@ def decode(token, certs=None, verify=True, audience=None, clock_skew_in_seconds= if not crypt.verify_signature( signed_section, signature, certs_to_check, verifier_cls ): - raise ValueError("Could not verify token signature.") + raise exceptions.MalformedError("Could not verify token signature.") # Verify the issued at and created times in the payload. _verify_iat_and_exp(payload, clock_skew_in_seconds) @@ -302,7 +314,7 @@ def decode(token, certs=None, verify=True, audience=None, clock_skew_in_seconds= if isinstance(audience, str): audience = [audience] if claim_audience not in audience: - raise ValueError( + raise exceptions.InvalidValue( "Token has wrong audience {}, expected one of {}".format( claim_audience, audience ) @@ -414,7 +426,7 @@ def _from_signer_and_info(cls, signer, info, **kwargs): google.auth.jwt.Credentials: The constructed credentials. Raises: - ValueError: If the info is not in the expected format. + google.auth.exceptions.MalformedError: If the info is not in the expected format. """ kwargs.setdefault("subject", info["client_email"]) kwargs.setdefault("issuer", info["client_email"]) @@ -433,7 +445,7 @@ def from_service_account_info(cls, info, **kwargs): google.auth.jwt.Credentials: The constructed credentials. Raises: - ValueError: If the info is not in the expected format. + google.auth.exceptions.MalformedError: If the info is not in the expected format. """ signer = _service_account_info.from_dict(info, require=["client_email"]) return cls._from_signer_and_info(signer, info, **kwargs) @@ -651,7 +663,7 @@ def _from_signer_and_info(cls, signer, info, **kwargs): google.auth.jwt.OnDemandCredentials: The constructed credentials. Raises: - ValueError: If the info is not in the expected format. + google.auth.exceptions.MalformedError: If the info is not in the expected format. """ kwargs.setdefault("subject", info["client_email"]) kwargs.setdefault("issuer", info["client_email"]) @@ -670,7 +682,7 @@ def from_service_account_info(cls, info, **kwargs): google.auth.jwt.OnDemandCredentials: The constructed credentials. Raises: - ValueError: If the info is not in the expected format. + google.auth.exceptions.MalformedError: If the info is not in the expected format. """ signer = _service_account_info.from_dict(info, require=["client_email"]) return cls._from_signer_and_info(signer, info, **kwargs) diff --git a/packages/google-auth/google/auth/pluggable.py b/packages/google-auth/google/auth/pluggable.py index b4fa448b87f0..7fef36112627 100644 --- a/packages/google-auth/google/auth/pluggable.py +++ b/packages/google-auth/google/auth/pluggable.py @@ -93,7 +93,8 @@ def __init__( Raises: google.auth.exceptions.RefreshError: If an error is encountered during access token retrieval logic. - ValueError: For invalid parameters. + google.auth.exceptions.InvalidValue: For invalid parameters. + google.auth.exceptions.MalformedError: For invalid parameters. .. note:: Typically one of the helper constructors :meth:`from_file` or @@ -111,12 +112,12 @@ def __init__( ) if not isinstance(credential_source, Mapping): self._credential_source_executable = None - raise ValueError( + raise exceptions.MalformedError( "Missing credential_source. The credential_source is not a dict." ) self._credential_source_executable = credential_source.get("executable") if not self._credential_source_executable: - raise ValueError( + raise exceptions.MalformedError( "Missing credential_source. An 'executable' must be provided." ) self._credential_source_executable_command = self._credential_source_executable.get( @@ -136,7 +137,7 @@ def __init__( self._tokeninfo_username = "" if not self._credential_source_executable_command: - raise ValueError( + raise exceptions.MalformedError( "Missing command field. Executable command must be provided." ) if not self._credential_source_executable_timeout_millis: @@ -149,7 +150,7 @@ def __init__( or self._credential_source_executable_timeout_millis > EXECUTABLE_TIMEOUT_MILLIS_UPPER_BOUND ): - raise ValueError("Timeout must be between 5 and 120 seconds.") + raise exceptions.InvalidValue("Timeout must be between 5 and 120 seconds.") if self._credential_source_executable_interactive_timeout_millis: if ( @@ -158,7 +159,7 @@ def __init__( or self._credential_source_executable_interactive_timeout_millis > EXECUTABLE_INTERACTIVE_TIMEOUT_MILLIS_UPPER_BOUND ): - raise ValueError( + raise exceptions.InvalidValue( "Interactive timeout must be between 30 seconds and 30 minutes." ) @@ -183,7 +184,7 @@ def retrieve_subject_token(self, request): "expiration_time" not in response ): # Always treat missing expiration_time as expired and proceed to executable run. raise exceptions.RefreshError - except ValueError: + except (exceptions.MalformedError, exceptions.InvalidValue): raise except exceptions.RefreshError: pass @@ -247,7 +248,9 @@ def revoke(self, request): """ if not self.interactive: - raise ValueError("Revoke is only enabled under interactive mode.") + raise exceptions.InvalidValue( + "Revoke is only enabled under interactive mode." + ) self._validate_running_mode() if not _helpers.is_python_3(): @@ -307,7 +310,8 @@ def from_info(cls, info, **kwargs): credentials. Raises: - ValueError: For invalid parameters. + google.auth.exceptions.InvalidValue: For invalid parameters. + google.auth.exceptions.MalformedError: For invalid parameters. """ return super(Credentials, cls).from_info(info, **kwargs) @@ -344,7 +348,7 @@ def _parse_subject_token(self, response): self._validate_response_schema(response) if not response["success"]: if "code" not in response or "message" not in response: - raise ValueError( + raise exceptions.MalformedError( "Error code and message fields are required in the response." ) raise exceptions.RefreshError( @@ -357,7 +361,9 @@ def _parse_subject_token(self, response): "The token returned by the executable is expired." ) if "token_type" not in response: - raise ValueError("The executable response is missing the token_type field.") + raise exceptions.MalformedError( + "The executable response is missing the token_type field." + ) if ( response["token_type"] == "urn:ietf:params:oauth:token-type:jwt" or response["token_type"] == "urn:ietf:params:oauth:token-type:id_token" @@ -375,7 +381,9 @@ def _validate_revoke_response(self, response): def _validate_response_schema(self, response): if "version" not in response: - raise ValueError("The executable response is missing the version field.") + raise exceptions.MalformedError( + "The executable response is missing the version field." + ) if response["version"] > EXECUTABLE_SUPPORTED_MAX_VERSION: raise exceptions.RefreshError( "Executable returned unsupported version {}.".format( @@ -384,19 +392,21 @@ def _validate_response_schema(self, response): ) if "success" not in response: - raise ValueError("The executable response is missing the success field.") + raise exceptions.MalformedError( + "The executable response is missing the success field." + ) def _validate_running_mode(self): env_allow_executables = os.environ.get( "GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES" ) if env_allow_executables != "1": - raise ValueError( + raise exceptions.MalformedError( "Executables need to be explicitly allowed (set GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES to '1') to run." ) if self.interactive and not self._credential_source_executable_output_file: - raise ValueError( + raise exceptions.MalformedError( "An output_file must be specified in the credential configuration for interactive mode." ) @@ -404,9 +414,11 @@ def _validate_running_mode(self): self.interactive and not self._credential_source_executable_interactive_timeout_millis ): - raise ValueError( + raise exceptions.InvalidOperation( "Interactive mode cannot run without an interactive timeout." ) if self.interactive and not self.is_workforce_pool: - raise ValueError("Interactive mode is only enabled for workforce pool.") + raise exceptions.InvalidValue( + "Interactive mode is only enabled for workforce pool." + ) diff --git a/packages/google-auth/google/auth/transport/_aiohttp_requests.py b/packages/google-auth/google/auth/transport/_aiohttp_requests.py index 179cadbdfb1e..364570e311ac 100644 --- a/packages/google-auth/google/auth/transport/_aiohttp_requests.py +++ b/packages/google-auth/google/auth/transport/_aiohttp_requests.py @@ -140,7 +140,7 @@ class Request(transport.Request): def __init__(self, session=None): # TODO: Use auto_decompress property for aiohttp 3.7+ if session is not None and session._auto_decompress: - raise ValueError( + raise exceptions.InvalidOperation( "Client sessions with auto_decompress=True are not supported." ) self.session = session diff --git a/packages/google-auth/google/auth/transport/grpc.py b/packages/google-auth/google/auth/transport/grpc.py index 87fa5042fdac..55764b6f6109 100644 --- a/packages/google-auth/google/auth/transport/grpc.py +++ b/packages/google-auth/google/auth/transport/grpc.py @@ -255,7 +255,7 @@ def my_client_cert_callback(): google_auth_credentials = grpc.metadata_call_credentials(metadata_plugin) if ssl_credentials and client_cert_callback: - raise ValueError( + raise exceptions.MalformedError( "Received both ssl_credentials and client_cert_callback; " "these are mutually exclusive." ) From 21515b531e6ce9cdcf8b08ff8f7028cafd5b7c2d Mon Sep 17 00:00:00 2001 From: Jin Date: Wed, 7 Dec 2022 13:57:20 -0800 Subject: [PATCH 657/966] chore: update token (#1199) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index b6409753b4a1f81a87317494d650feb5b7630589..a78477bccb99625e34bab3b31711a27964dad1ab 100644 GIT binary patch literal 10324 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 literal 10324 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 mx7@$qHT@t4^8o6*!Gn Date: Wed, 7 Dec 2022 15:47:03 -0800 Subject: [PATCH 658/966] feat: AwsCredentials should not call metadata server if security creds and region are retrievable through the environment variables (#1195) * feat: AwsCredentials should not call metadata server if security creds and region are retrievable through the environment variables * add verification of metadata token request not called * Adding new coverage of test and address comments * add previous missing period Co-authored-by: Leo <39062083+lsirac@users.noreply.github.com> --- packages/google-auth/google/auth/aws.py | 27 ++- packages/google-auth/tests/test_aws.py | 208 +++++++++++++++++++++++- 2 files changed, 232 insertions(+), 3 deletions(-) diff --git a/packages/google-auth/google/auth/aws.py b/packages/google-auth/google/auth/aws.py index d3038b2816c4..f651433f070c 100644 --- a/packages/google-auth/google/auth/aws.py +++ b/packages/google-auth/google/auth/aws.py @@ -466,8 +466,12 @@ def retrieve_subject_token(self, request): Returns: str: The retrieved subject token. """ - # Fetch the session token required to make meta data endpoint calls to aws - if request is not None and self._imdsv2_session_token_url is not None: + # Fetch the session token required to make meta data endpoint calls to aws. + if ( + request is not None + and self._imdsv2_session_token_url is not None + and self._should_use_metadata_server() + ): headers = {"X-aws-ec2-metadata-token-ttl-seconds": "300"} imdsv2_session_token_response = request( @@ -738,6 +742,25 @@ def _get_metadata_role_name(self, request, imdsv2_session_token): return response_body + def _should_use_metadata_server(self): + # The AWS region can be provided through AWS_REGION or AWS_DEFAULT_REGION. + # The metadata server should be used if it cannot be retrieved from one of + # these environment variables. + if not os.environ.get(environment_vars.AWS_REGION) and not os.environ.get( + environment_vars.AWS_DEFAULT_REGION + ): + return True + + # AWS security credentials can be retrieved from the AWS_ACCESS_KEY_ID + # and AWS_SECRET_ACCESS_KEY environment variables. The metadata server + # should be used if either of these are not available. + if not os.environ.get(environment_vars.AWS_ACCESS_KEY_ID) or not os.environ.get( + environment_vars.AWS_SECRET_ACCESS_KEY + ): + return True + + return False + @classmethod def from_info(cls, info, **kwargs): """Creates an AWS Credentials instance from parsed external account info. diff --git a/packages/google-auth/tests/test_aws.py b/packages/google-auth/tests/test_aws.py index d059487f4352..4004126602cc 100644 --- a/packages/google-auth/tests/test_aws.py +++ b/packages/google-auth/tests/test_aws.py @@ -14,6 +14,7 @@ import datetime import json +import os import mock import pytest # type: ignore @@ -1224,6 +1225,7 @@ def test_retrieve_subject_token_success_temp_creds_no_environment_vars( ) @mock.patch("google.auth._helpers.utcnow") + @mock.patch.dict(os.environ, {}) def test_retrieve_subject_token_success_temp_creds_no_environment_vars_idmsv2( self, utcnow ): @@ -1300,7 +1302,7 @@ def test_retrieve_subject_token_success_temp_creds_no_environment_vars_idmsv2( # Only 3 requests should be sent as the region is cached. assert len(new_request.call_args_list) == 3 - # Assert session token request + # Assert session token request. self.assert_aws_metadata_request_kwargs( request.call_args_list[0][1], IMDSV2_SESSION_TOKEN_URL, @@ -1323,6 +1325,210 @@ def test_retrieve_subject_token_success_temp_creds_no_environment_vars_idmsv2( }, ) + @mock.patch("google.auth._helpers.utcnow") + @mock.patch.dict( + os.environ, + { + environment_vars.AWS_REGION: AWS_REGION, + environment_vars.AWS_ACCESS_KEY_ID: ACCESS_KEY_ID, + }, + ) + def test_retrieve_subject_token_success_temp_creds_environment_vars_missing_secret_access_key_idmsv2( + self, utcnow + ): + utcnow.return_value = datetime.datetime.strptime( + self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ" + ) + request = self.make_mock_request( + role_status=http_client.OK, + role_name=self.AWS_ROLE, + security_credentials_status=http_client.OK, + security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE, + imdsv2_session_token_status=http_client.OK, + imdsv2_session_token_data=self.AWS_IMDSV2_SESSION_TOKEN, + ) + credential_source_token_url = self.CREDENTIAL_SOURCE.copy() + credential_source_token_url[ + "imdsv2_session_token_url" + ] = IMDSV2_SESSION_TOKEN_URL + credentials = self.make_credentials( + credential_source=credential_source_token_url + ) + + subject_token = credentials.retrieve_subject_token(request) + assert subject_token == self.make_serialized_aws_signed_request( + { + "access_key_id": ACCESS_KEY_ID, + "secret_access_key": SECRET_ACCESS_KEY, + "security_token": TOKEN, + } + ) + # Assert session token request. + self.assert_aws_metadata_request_kwargs( + request.call_args_list[0][1], + IMDSV2_SESSION_TOKEN_URL, + {"X-aws-ec2-metadata-token-ttl-seconds": "300"}, + "PUT", + ) + # Assert role request. + self.assert_aws_metadata_request_kwargs( + request.call_args_list[1][1], + SECURITY_CREDS_URL, + {"X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN}, + ) + # Assert security credentials request. + self.assert_aws_metadata_request_kwargs( + request.call_args_list[2][1], + "{}/{}".format(SECURITY_CREDS_URL, self.AWS_ROLE), + { + "Content-Type": "application/json", + "X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN, + }, + ) + + @mock.patch("google.auth._helpers.utcnow") + @mock.patch.dict( + os.environ, + { + environment_vars.AWS_REGION: AWS_REGION, + environment_vars.AWS_SECRET_ACCESS_KEY: SECRET_ACCESS_KEY, + }, + ) + def test_retrieve_subject_token_success_temp_creds_environment_vars_missing_access_key_id_idmsv2( + self, utcnow + ): + utcnow.return_value = datetime.datetime.strptime( + self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ" + ) + request = self.make_mock_request( + role_status=http_client.OK, + role_name=self.AWS_ROLE, + security_credentials_status=http_client.OK, + security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE, + imdsv2_session_token_status=http_client.OK, + imdsv2_session_token_data=self.AWS_IMDSV2_SESSION_TOKEN, + ) + credential_source_token_url = self.CREDENTIAL_SOURCE.copy() + credential_source_token_url[ + "imdsv2_session_token_url" + ] = IMDSV2_SESSION_TOKEN_URL + credentials = self.make_credentials( + credential_source=credential_source_token_url + ) + + subject_token = credentials.retrieve_subject_token(request) + assert subject_token == self.make_serialized_aws_signed_request( + { + "access_key_id": ACCESS_KEY_ID, + "secret_access_key": SECRET_ACCESS_KEY, + "security_token": TOKEN, + } + ) + # Assert session token request. + self.assert_aws_metadata_request_kwargs( + request.call_args_list[0][1], + IMDSV2_SESSION_TOKEN_URL, + {"X-aws-ec2-metadata-token-ttl-seconds": "300"}, + "PUT", + ) + # Assert role request. + self.assert_aws_metadata_request_kwargs( + request.call_args_list[1][1], + SECURITY_CREDS_URL, + {"X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN}, + ) + # Assert security credentials request. + self.assert_aws_metadata_request_kwargs( + request.call_args_list[2][1], + "{}/{}".format(SECURITY_CREDS_URL, self.AWS_ROLE), + { + "Content-Type": "application/json", + "X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN, + }, + ) + + @mock.patch("google.auth._helpers.utcnow") + @mock.patch.dict(os.environ, {environment_vars.AWS_REGION: AWS_REGION}) + def test_retrieve_subject_token_success_temp_creds_environment_vars_missing_creds_idmsv2( + self, utcnow + ): + utcnow.return_value = datetime.datetime.strptime( + self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ" + ) + request = self.make_mock_request( + role_status=http_client.OK, + role_name=self.AWS_ROLE, + security_credentials_status=http_client.OK, + security_credentials_data=self.AWS_SECURITY_CREDENTIALS_RESPONSE, + imdsv2_session_token_status=http_client.OK, + imdsv2_session_token_data=self.AWS_IMDSV2_SESSION_TOKEN, + ) + credential_source_token_url = self.CREDENTIAL_SOURCE.copy() + credential_source_token_url[ + "imdsv2_session_token_url" + ] = IMDSV2_SESSION_TOKEN_URL + credentials = self.make_credentials( + credential_source=credential_source_token_url + ) + + subject_token = credentials.retrieve_subject_token(request) + assert subject_token == self.make_serialized_aws_signed_request( + { + "access_key_id": ACCESS_KEY_ID, + "secret_access_key": SECRET_ACCESS_KEY, + "security_token": TOKEN, + } + ) + # Assert session token request. + self.assert_aws_metadata_request_kwargs( + request.call_args_list[0][1], + IMDSV2_SESSION_TOKEN_URL, + {"X-aws-ec2-metadata-token-ttl-seconds": "300"}, + "PUT", + ) + # Assert role request. + self.assert_aws_metadata_request_kwargs( + request.call_args_list[1][1], + SECURITY_CREDS_URL, + {"X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN}, + ) + # Assert security credentials request. + self.assert_aws_metadata_request_kwargs( + request.call_args_list[2][1], + "{}/{}".format(SECURITY_CREDS_URL, self.AWS_ROLE), + { + "Content-Type": "application/json", + "X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN, + }, + ) + + @mock.patch("google.auth._helpers.utcnow") + @mock.patch.dict( + os.environ, + { + environment_vars.AWS_REGION: AWS_REGION, + environment_vars.AWS_ACCESS_KEY_ID: ACCESS_KEY_ID, + environment_vars.AWS_SECRET_ACCESS_KEY: SECRET_ACCESS_KEY, + }, + ) + def test_retrieve_subject_token_success_temp_creds_idmsv2(self, utcnow): + utcnow.return_value = datetime.datetime.strptime( + self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ" + ) + request = self.make_mock_request( + role_status=http_client.OK, role_name=self.AWS_ROLE + ) + credential_source_token_url = self.CREDENTIAL_SOURCE.copy() + credential_source_token_url[ + "imdsv2_session_token_url" + ] = IMDSV2_SESSION_TOKEN_URL + credentials = self.make_credentials( + credential_source=credential_source_token_url + ) + + credentials.retrieve_subject_token(request) + assert not request.called + def test_validate_metadata_server_url_if_any(self): aws.Credentials.validate_metadata_server_url_if_any( "http://[fd00:ec2::254]/latest/meta-data/placement/availability-zone", "url" From 5e67599d073272bd13c7a2ddce7a6ad7b30f7b0c Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Wed, 14 Dec 2022 10:10:16 -0500 Subject: [PATCH 659/966] build(deps): bump certifi from 2022.9.24 to 2022.12.7 in /synthtool/gcp/templates/python_library/.kokoro (#1200) Source-Link: https://github.com/googleapis/synthtool/commit/b4fe62efb5114b6738ad4b13d6f654f2bf4b7cc0 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:3bf87e47c2173d7eed42714589dc4da2c07c3268610f1e47f8e1a30decbfc7f1 Co-authored-by: Owl Bot --- .../google-auth/.github/.OwlBot.lock.yaml | 2 +- .../.kokoro/docker/docs/Dockerfile | 12 ++-- packages/google-auth/.kokoro/requirements.in | 4 +- packages/google-auth/.kokoro/requirements.txt | 67 ++++++++++--------- 4 files changed, 45 insertions(+), 40 deletions(-) diff --git a/packages/google-auth/.github/.OwlBot.lock.yaml b/packages/google-auth/.github/.OwlBot.lock.yaml index 12edee77695a..fccaa8e84449 100644 --- a/packages/google-auth/.github/.OwlBot.lock.yaml +++ b/packages/google-auth/.github/.OwlBot.lock.yaml @@ -13,4 +13,4 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:452901c74a22f9b9a3bd02bce780b8e8805c97270d424684bff809ce5be8c2a2 + digest: sha256:3bf87e47c2173d7eed42714589dc4da2c07c3268610f1e47f8e1a30decbfc7f1 diff --git a/packages/google-auth/.kokoro/docker/docs/Dockerfile b/packages/google-auth/.kokoro/docker/docs/Dockerfile index 238b87b9d1c9..f8137d0ae497 100644 --- a/packages/google-auth/.kokoro/docker/docs/Dockerfile +++ b/packages/google-auth/.kokoro/docker/docs/Dockerfile @@ -60,16 +60,16 @@ RUN apt-get update \ && rm -rf /var/lib/apt/lists/* \ && rm -f /var/cache/apt/archives/*.deb -###################### Install python 3.8.11 +###################### Install python 3.9.13 -# Download python 3.8.11 -RUN wget https://www.python.org/ftp/python/3.8.11/Python-3.8.11.tgz +# Download python 3.9.13 +RUN wget https://www.python.org/ftp/python/3.9.13/Python-3.9.13.tgz # Extract files -RUN tar -xvf Python-3.8.11.tgz +RUN tar -xvf Python-3.9.13.tgz -# Install python 3.8.11 -RUN ./Python-3.8.11/configure --enable-optimizations +# Install python 3.9.13 +RUN ./Python-3.9.13/configure --enable-optimizations RUN make altinstall ###################### Install pip diff --git a/packages/google-auth/.kokoro/requirements.in b/packages/google-auth/.kokoro/requirements.in index 7718391a34d7..cbd7e77f44db 100644 --- a/packages/google-auth/.kokoro/requirements.in +++ b/packages/google-auth/.kokoro/requirements.in @@ -5,4 +5,6 @@ typing-extensions twine wheel setuptools -nox \ No newline at end of file +nox +charset-normalizer<3 +click<8.1.0 diff --git a/packages/google-auth/.kokoro/requirements.txt b/packages/google-auth/.kokoro/requirements.txt index 31425f164783..05dc4672edaa 100644 --- a/packages/google-auth/.kokoro/requirements.txt +++ b/packages/google-auth/.kokoro/requirements.txt @@ -20,9 +20,9 @@ cachetools==5.2.0 \ --hash=sha256:6a94c6402995a99c3970cc7e4884bb60b4a8639938157eeed436098bf9831757 \ --hash=sha256:f9f17d2aec496a9aa6b76f53e3b614c965223c061982d434d160f930c698a9db # via google-auth -certifi==2022.9.24 \ - --hash=sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14 \ - --hash=sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382 +certifi==2022.12.7 \ + --hash=sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3 \ + --hash=sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18 # via requests cffi==1.15.1 \ --hash=sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5 \ @@ -93,11 +93,14 @@ cffi==1.15.1 \ charset-normalizer==2.1.1 \ --hash=sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845 \ --hash=sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f - # via requests + # via + # -r requirements.in + # requests click==8.0.4 \ --hash=sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1 \ --hash=sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb # via + # -r requirements.in # gcp-docuploader # gcp-releasetool colorlog==6.7.0 \ @@ -156,9 +159,9 @@ gcp-docuploader==0.6.4 \ --hash=sha256:01486419e24633af78fd0167db74a2763974765ee8078ca6eb6964d0ebd388af \ --hash=sha256:70861190c123d907b3b067da896265ead2eeb9263969d6955c9e0bb091b5ccbf # via -r requirements.in -gcp-releasetool==1.9.1 \ - --hash=sha256:952f4055d5d986b070ae2a71c4410b250000f9cc5a1e26398fcd55a5bbc5a15f \ - --hash=sha256:d0d3c814a97c1a237517e837d8cfa668ced8df4b882452578ecef4a4e79c583b +gcp-releasetool==1.10.0 \ + --hash=sha256:72a38ca91b59c24f7e699e9227c90cbe4dd71b789383cb0164b088abae294c83 \ + --hash=sha256:8c7c99320208383d4bb2b808c6880eb7a81424afe7cdba3c8d84b25f4f0e097d # via -r requirements.in google-api-core==2.10.2 \ --hash=sha256:10c06f7739fe57781f87523375e8e1a3a4674bf6392cd6131a3222182b971320 \ @@ -166,9 +169,9 @@ google-api-core==2.10.2 \ # via # google-cloud-core # google-cloud-storage -google-auth==2.14.0 \ - --hash=sha256:1ad5b0e6eba5f69645971abb3d2c197537d5914070a8c6d30299dfdb07c5c700 \ - --hash=sha256:cf24817855d874ede2efd071aa22125445f555de1685b739a9782fcf408c2a3d +google-auth==2.14.1 \ + --hash=sha256:ccaa901f31ad5cbb562615eb8b664b3dd0bf5404a67618e642307f00613eda4d \ + --hash=sha256:f5d8701633bebc12e0deea4df8abd8aff31c28b355360597f7f2ee60f2e4d016 # via # gcp-releasetool # google-api-core @@ -178,9 +181,9 @@ google-cloud-core==2.3.2 \ --hash=sha256:8417acf6466be2fa85123441696c4badda48db314c607cf1e5d543fa8bdc22fe \ --hash=sha256:b9529ee7047fd8d4bf4a2182de619154240df17fbe60ead399078c1ae152af9a # via google-cloud-storage -google-cloud-storage==2.5.0 \ - --hash=sha256:19a26c66c317ce542cea0830b7e787e8dac2588b6bfa4d3fd3b871ba16305ab0 \ - --hash=sha256:382f34b91de2212e3c2e7b40ec079d27ee2e3dbbae99b75b1bcd8c63063ce235 +google-cloud-storage==2.6.0 \ + --hash=sha256:104ca28ae61243b637f2f01455cc8a05e8f15a2a18ced96cb587241cdd3820f5 \ + --hash=sha256:4ad0415ff61abdd8bb2ae81c1f8f7ec7d91a1011613f2db87c614c550f97bfe9 # via gcp-docuploader google-crc32c==1.5.0 \ --hash=sha256:024894d9d3cfbc5943f8f230e23950cd4906b2fe004c72e29b209420a1e6b05a \ @@ -256,9 +259,9 @@ google-resumable-media==2.4.0 \ --hash=sha256:2aa004c16d295c8f6c33b2b4788ba59d366677c0a25ae7382436cb30f776deaa \ --hash=sha256:8d5518502f92b9ecc84ac46779bd4f09694ecb3ba38a3e7ca737a86d15cbca1f # via google-cloud-storage -googleapis-common-protos==1.56.4 \ - --hash=sha256:8eb2cbc91b69feaf23e32452a7ae60e791e09967d81d4fcc7fc388182d1bd394 \ - --hash=sha256:c25873c47279387cfdcbdafa36149887901d36202cb645a0e4f29686bf6e4417 +googleapis-common-protos==1.57.0 \ + --hash=sha256:27a849d6205838fb6cc3c1c21cb9800707a661bb21c6ce7fb13e99eb1f8a0c46 \ + --hash=sha256:a9f4a1d7f6d9809657b7f1316a1aa527f6664891531bcfcc13b6696e685f443c # via google-api-core idna==3.4 \ --hash=sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4 \ @@ -269,6 +272,7 @@ importlib-metadata==5.0.0 \ --hash=sha256:ddb0e35065e8938f867ed4928d0ae5bf2a53b7773871bfe6bcc7e4fcdc7dea43 # via # -r requirements.in + # keyring # twine jaraco-classes==3.2.3 \ --hash=sha256:2353de3288bc6b82120752201c6b1c1a14b058267fa424ed5ce5984e3b922158 \ @@ -284,9 +288,9 @@ jinja2==3.1.2 \ --hash=sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852 \ --hash=sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61 # via gcp-releasetool -keyring==23.9.3 \ - --hash=sha256:69732a15cb1433bdfbc3b980a8a36a04878a6cfd7cb99f497b573f31618001c0 \ - --hash=sha256:69b01dd83c42f590250fe7a1f503fc229b14de83857314b1933a3ddbf595c4a5 +keyring==23.11.0 \ + --hash=sha256:3dd30011d555f1345dec2c262f0153f2f0ca6bca041fb1dc4588349bb4c0ac1e \ + --hash=sha256:ad192263e2cdd5f12875dedc2da13534359a7e760e77f8d04b50968a821c2361 # via # gcp-releasetool # twine @@ -350,9 +354,9 @@ pkginfo==1.8.3 \ --hash=sha256:848865108ec99d4901b2f7e84058b6e7660aae8ae10164e015a6dcf5b242a594 \ --hash=sha256:a84da4318dd86f870a9447a8c98340aa06216bfc6f2b7bdc4b8766984ae1867c # via twine -platformdirs==2.5.2 \ - --hash=sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788 \ - --hash=sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19 +platformdirs==2.5.4 \ + --hash=sha256:1006647646d80f16130f052404c6b901e80ee4ed6bef6792e1f238a8969106f7 \ + --hash=sha256:af0276409f9a02373d540bf8480021a048711d572745aef4b7842dad245eba10 # via virtualenv protobuf==3.20.3 \ --hash=sha256:03038ac1cfbc41aa21f6afcbcd357281d7521b4157926f30ebecc8d4ea59dcb7 \ @@ -381,7 +385,6 @@ protobuf==3.20.3 \ # gcp-docuploader # gcp-releasetool # google-api-core - # googleapis-common-protos py==1.11.0 \ --hash=sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719 \ --hash=sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378 @@ -476,17 +479,17 @@ urllib3==1.26.12 \ # via # requests # twine -virtualenv==20.16.6 \ - --hash=sha256:186ca84254abcbde98180fd17092f9628c5fe742273c02724972a1d8a2035108 \ - --hash=sha256:530b850b523c6449406dfba859d6345e48ef19b8439606c5d74d7d3c9e14d76e +virtualenv==20.16.7 \ + --hash=sha256:8691e3ff9387f743e00f6bb20f70121f5e4f596cae754531f2b3b3a1b1ac696e \ + --hash=sha256:efd66b00386fdb7dbe4822d172303f40cd05e50e01740b19ea42425cbe653e29 # via nox webencodings==0.5.1 \ --hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \ --hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923 # via bleach -wheel==0.37.1 \ - --hash=sha256:4bdcd7d840138086126cd09254dc6195fb4fc6f01c050a1d7236f2630db1d22a \ - --hash=sha256:e9a504e793efbca1b8e0e9cb979a249cf4a0a7b5b8c9e8b65a5e39d49529c1c4 +wheel==0.38.4 \ + --hash=sha256:965f5259b566725405b05e7cf774052044b1ed30119b5d586b2703aafe8719ac \ + --hash=sha256:b60533f3f5d530e971d6737ca6d58681ee434818fab630c83a734bb10c083ce8 # via -r requirements.in zipp==3.10.0 \ --hash=sha256:4fcb6f278987a6605757302a6e40e896257570d11c51628968ccb2a47e80c6c1 \ @@ -494,7 +497,7 @@ zipp==3.10.0 \ # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: -setuptools==65.5.0 \ - --hash=sha256:512e5536220e38146176efb833d4a62aa726b7bbff82cfbc8ba9eaa3996e0b17 \ - --hash=sha256:f62ea9da9ed6289bfe868cd6845968a2c854d1427f8548d52cae02a42b4f0356 +setuptools==65.5.1 \ + --hash=sha256:d0b9a8433464d5800cbe05094acf5c6d52a91bfac9b52bcfc4d41382be5d5d31 \ + --hash=sha256:e197a19aa8ec9722928f2206f8de752def0e4c9fc6953527360d1c36d94ddb2f # via -r requirements.in From 8d63bd93df6f4c45db805c40c9049cfe20eb9317 Mon Sep 17 00:00:00 2001 From: Jin Date: Thu, 5 Jan 2023 18:11:33 -0800 Subject: [PATCH 660/966] chore: update token (#1209) * chore: update token * pin the cryptography library version * pin the cryptography library version * using less operator to try --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes packages/google-auth/testing/requirements.txt | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index a78477bccb99625e34bab3b31711a27964dad1ab..83e30b78074dbe0c53fd03dd4fec3493316554f0 100644 GIT binary patch literal 10324 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 literal 10324 zcmV-aD67{BB>?tKRTBuc9@fQ|!h2N^0hlWAJ6RK6>!4`~m_NswyC=)jsHzgGPyni{ zhrHJ+k0E&SA`6aKcUl8D2Nma4rR~-gS6`hpP$)Awh(&%jybb9b6Q&Oe_#AqD7)orGk6{ja5a75gNwibh#C z@FT?Ts!@96GG9TUP?OjPRcA{D|ENLyOm(6TL)7PKt2HQAUpPxwUsZdLvsPIcP8AgY z%L$Mxy3Rb8t6?6QQXAw0Rcm)RVX9+C)$U+Bq7VwzQZ zF|J?U5Ke3bCLh=x{RCyy%nAW;O(%)wAm@Lw&BT{plnI{0?%AttCCi*qw7)Gf{hRSv zo!(YKRr*p7`~rf^0y{}OX9Vo{Ef}`R>jUGd6cF}Q&hRg?X4>3~4(pR(k0mtEf65 z2G=Cvmc@P%OQv}NTbCoPy{%QARTG9aI3&QGu><%sCeks@SS1%jj!DrRQB$eaHkM9b z<|8NNeW4uvw@GWyEnEEV@>;x&$2PC{U{8I(XV)ABs`}Ye-|(e$UX(w?|@y!QXS(q*+=2`1a3+j9cY=PWVOHGbfZGG)XMOf#0w z@%n|pe0e@f3JhP&(QB~NSn~HBW)48z|358jpVLg`<7I0dz?BY;7Y* z5$t&*eNy8+lOW+H&ackO>a&7DGk?wk7rc`D%i-2_sy*F(FA-tej58&yLLS zML`Kl=$!7{VDLiY*;VTLv-ZF*?HidB#9BrGd(D7&BE+ezEZ&Nf7W)qhMh}8PYKFtr z`PB!auN9o9Qo8TZWTw|+;2*bJ);Yy+RF<~>IqqH0BXR@iGu99H-aiiY{=g6JD6i}K z@;?Jp23sy0X7@y(g$>c`yvlMNh;~gGL^5N1MMiH?bdH-KuR#PzMm4iw3>@s{0hdj) zLUjB5BD!iF>`)!P5|G|9OZq8Sb6KRY~O)V3shJZmqe@D5U#V@5bWwZO2 zO;#jyKg?g~#E)Q%<|od6s8QjdqWB{pyJN;}aZvIpYe~(O1NU*2w@vd@O}oV8;@MgeGRC>}V{=96(_7q&gkQW@&W)zqnnX83u>jtHjthtS zQvtwSm>HHwmjZW6$lZ$xz;Lf@K}o`@1vPKXJeGi8huz{2L7vZ~dg0Yu*TN334Vz`y zj8K^cfxkM(+VgB~m^yOjdf7xu5L=!&FBD^RO)!kmpG=oCy>%9zv*;aan3A< zkjx>=AU-%na;G19_7ZQ%3M;|T5@@aR1qjJ-!oTUslF`@4)IVld?psru`>jQhx1&A; z2bR5jTUed{33V7y#g%>@_%Z@3Z@I?N4I*K$e~?_13OGMY-F^nZg^%g|6+ar;r*V0U z77re@@P=9oj;!x?Qas>>HdrBq8c-2-thPO=pk+pNc4N!$D}lgH+W=dD7bHxcE6dV1 zOrg@{6@TWgkZy84@AeLIl4(?S{(N&TP1H;Kp^e|F$7e-fuu*Cz^U(12N^6mP?pN^U zNsfq2Fk&fXr%+l|g5wv(;pQNuenW%feFqL=8%W);&lOrW<`SORh4!OiwmcHlIyDLM zgOzvQDAC#8 zR8$k*fp?rV6-u+Aet4?!eY8vMyP=*B|2@+k^;iQ$ZOIsY+kaZ5ZaKSGQ{*K+&>$F`C?m;)v z``a>}bi$42^Nc1l_d~bN^%V*j8+d%qozZ7h@~2^((rc)DH@dHZB|Nb4GJui7I*nz2 zTV61P_^>fbT!rF{{~e2hZ>=wO(>PTyW0m?yg$VP-7;Hfg%I-T|8kH) zu1)M~nJSLb4~;=}huWa`eH(np;2K@T6ZKQJN~N$03+BY8uofqYby?=DD6xmmVG3{m zL3nV^Cv^m!x4}CfCkr*NGPV3}7+0uas)8cd4bH+mRzQ&;I^)#E)(s&?=z6au|A?2< ziNuRbdmwC4!%B=<45yRHqu@&Wu8FdHSsJbQCF+j)MhWOdaBF{%CT);Is3H7&ys-}gaj4MgOy7bk7 zU~R*XWiOWX2G^Ks$^H}U3&Bdq0~{Fn#|q!6-=u@E94SxakB##1g|JU1j^3t`h6Vl` zj}`^F&E(D#baH3EmU1^)@~gY9SqOG9?@yb1fL$z_4weoYBJDf?8{=hQn2dh-rt_bf ziv0GXzFyIFRu`Ewz3v-e^@2}^4##!_po&49G_JUm^!>*oc za`I4u0<GD6AGAJWrT*JRl9bD#EwKnsg3Q@!C7rw#$k4i7g1m7+) z^=i&yF_tK2<8~o@2qp7HoY2q?7u~#=O&UeV9|{7c8MZ3;yiC(=XZAR}A@)W@Ci8YT#A8jA z-&<(8#jel*pIqC_oeHDIh(z-}pc(UXkGeCKJ-pY`|b` zO2Vd^fA@V6G55u%M!Z4HM`o16&MKSV+e9+J*DsJ?MWeqCAW;=Pcoi9ON-1XxlQwL9 zlYNm!_mxE#n$XuZWG2tajS(u%I@>F%Fqrn&)Y3P3$;Uvhm%gL>K0p&Ff5S_}F%hcy z@>R?R)_pM{oH|vgQ~%8MS8J{b>NJIVY;jb>?oWtl1eC}ya8_!Xn;5XtSLZ5EiOV2X zSztR4bu=pITTh&7a#`w5G08$)x!`HTlv-l5gFY9%Rv|=gLD)F-1!Cg3u%>?JZ#1b- zhYRDMI$qw78U5=tj78zz_Nv~Low2@p6uUbdk^1R+VqA)qJTw$^GC2(%E|FKBu0v;Yj`25% zy8EdpVXX=f{>hqFXpgAoTxgJHw>?3}=XB2+Dd^9Dyhsm2S`}leNuE+7lu0sIVT=0x ztfsVXw>`lN%aJtLIL933bmy8t`#<>U8`zG5TFAO-Q&DzLb{)9lH0Ge=D%luWH zG`Uj%mt=&=dVgm|39Fnp+r5%J9(P~FPG-bqZ3pu7J3ZcW! zs|(ilKY$$kdReLtemWLiUD9A`8!R6|isjSzvJbs`k(|V^3925LyH52-V5M1yS(UAl zDgUV0AJtckO&SXBW7+4)3Gn$WJRaYzS;9^?d?EIlxa<)6NKa#?+33-(5~H#R%`9)vc z8dTOwX!`xP@!X;z;F~z&&tsAhrmL(OBYLARnK>t_PF8jITpQX-xT?(4Lieu_+$}Z@ zq}V(;`2Q~h-ur#e)0(@e#82E$_Bi|V!;`7SCaPy!l*f2{yF6*cyFH7yLZiB)hBg%f zk7o$oBbJ&fKdRSF^AYNK4mFUi)$-tAfxk1J-m|l0JV4S;u`XSx$xpsFB|EB`G%k3V zs$09KOD7N%6q20-);HSREvRR3%d4uPUY`e0&jVvWNdC!NV;x_xh+h%IJKMC|#8@R* zrPE&)fyuMnte`XkhGj7cVxWw${hZ#>lyjwxH}aWm%O~}a&ShZV)$4_NRVTlb0kkKM z4E9Qo!b04V7a_+-Cx*?Fc+kPFX=v?Rx_FtEFdGogwMGk%g6zV29;(J1*6|AOlH$oY z%YP3zb+b8(hCEp}vg{sm#NweIk}70On>vmRIr9cgVud zkE$Gzs>a=o9z72?G}zt2hGejPdkQ~upqYM^Wiv5{4| zzG%{7gxlwtFa^?ByO9YKeH(HRaR`|GAj{Nvs+MWdrEV_M2H7fOZ=O9Tia zk5|Q~vDeR{HC4=#>!C|0+-;{4m}$Az#DsypA)tqo+Mqj4Y-h_wX?zFF!5sjt4&$lt zxrLt#XHf~?U3LKHrpI(-Am&S;?4Q^@unoW8l~F=pZh#pzuPLhjCc$`K9j3bQj7`YC z$==`Bx$lszx~3<=r{K3sutItD*ZO2JQ_JKaWOZdMX1QbDU|jCgR+~`DxGxZh?sv6? z#_KtnEmE<0v0s-Dm|;fv9G-IphMK51%&TSSjN`e5HTr0L*L44u#vc8-D|xRDM25C%I*G-M)Fyu5uccNSWK7_ zF~#Q4k9_>QFKZ*`ABd;L2Nh%V4m>=S}wr!+b7 z*r`#m)k`-R_`6=?lKqYNA|h+Xd%FfgG4GD z{3ddcPtp;<^CxhwLg|#2Zegp_;m*{ij+=pUQK-clZ1--Lbby7Ods)s6-cEcNuo+Dl z`1mM?ma|Wg?E08gxb+q|%XTng|AdlUBHqo)Z<UqbaB<<1XR#9u(rbfED;ogf9mw|rB#2A8VhPr+xH-j3jg*ic_R0Vp(Q z8<*Wz7^3w(y{)6g_BPvO-fW-<%8aW%XZqnBCNMhV=o{#pZ;?2B@3H*g+g9w)15 zSAUNBR?>}P(4F{Z*37$D# zjPeBP;sjR?*GCT8THK=YHbp{=&=Oa0(cN#W-u;6=T!)zi{TU=`nAk9l+%nL51`m9= zh-y4lzOL$(mXnj7`IvPlr%3MYV9sdx!AE%L>mibQHDx4|Wh>cc)T_Y8-a=weixU?o zQ#!mGax+q6HQOAaJwXYn=UKMfNKwhwq)`pE=d=0vU;!Y!%v~E z=Ai&ly(kl3h|;5emm4f|A`lcc`FwHIJQDlz^G+f=qR;fWuy2QrG$GNHI_o)`Rp(zG z^UZrL1OHSFi`Otd=svPN-WWH{ktmiX%c;czHbLp$zUnW;vp^ZK&(-O9Vov z+ti^P;8q(Jt@iNPPecK`(vjT35paD@v*+6cbDOp~WcCX*QY`P^1!;2J)5+=!AYs;o z_e}hrU<(ydM52V_i9Gzp;adx9*>!k=7T4V73Js{F zi?xOP(5`L%(&u<&Tboplq5V2L?zc0ZEKa2J+$VxF-a*ycQ zG1=IiJ58Gt0)#WK)SKE{bEgrnY@q^4|6i|Hc7?8Q?CPC9{6@ehhfB8=yZwe1n8Q<` zVwr;wHVbO!O)3h|eSu^OoHkRr5Sx1(4~;wCpq}L@KB6l1W%VQrVal&m&$AJ4Nhia4 z)-t%8c|5ZLf*da(kBRZf6j55;u-MuUl_7&!++68l)-?sPC8D0prG9!HGYDHDyC4p7#%sf!9T?96Sg@o@R;l2%{m@|ZB6f^LzVH6${nFKSvZcuL!k4H-=gGhjWdl9 zQm<}aCclu!qq&=C4FY?U{lX@?a{f zZ?+#@$ZfhWSM@wd)?sPYDqaSxi8pI!cRZAi#?=3?dPv*IIVEJXv_)PE=%F2CaJS)}YmB1vbqmhk1_VMmZHrbf0rwDKuUiZe(s@1jyhUjlIB* z8z~`mDu)FHSW!h44Grc;JQxvPqhg6_y-c@3riXYr&z)~;P4Ysu*GbCY{b%lx zBgHi$4PbzI;?C&TFz*~9)Hq5Y!>Q$W9=ZCf`n5l3aL5Wk2up_r+C%t@Kn7o6>0y9_ z>zG0(A}D%?4|eXMVaSvW0ZVknJ~_42UjWMa_rEOFD|$x~gMcHpW45qSzHd9{A#re} zJHF{zLdnXUK38%oF2^_|N{<1V-Q1WA_~X_V7zuHFzKRlMCx_w}W${1pPC4JU^+ zgnRwrVXl~Og!bs~ps&l5Gla3Ic7QQ9;mDZ)fO!p7nDO0mgmf8G2LAmb7VH82t@1Mw zu3Y~)8(Il9-})4X^Kyu5=a7?bK=7)J!8a@3Kv0FA70#`_cMIYnQhASb6WSXEK{aDz zsp$#AyfbFKe}5Kw>*`**jm-i>sLO!PfQr#qp-{xXHFjn4MHS2NtEsK8r%?J3E3 z(!|!%rk&j*U`f(HV$?bRb3DK`n!z3Z!b>&Mjt4OJGuFrKXxaBbU~SGI5aJ@HY+^}n zqAej%2bg9zSQh~Xf-n6Gy#GoQ-hgjlHjAuwQGpHU?4lj1P1CGLRS^*DIfBhBU40{x zKHUZ-l7Dpr>L#CQc_3wW$@*x=sgTf81)nLLFPG@N7|^6XJ17poY32X~?e9vZ4HEn} zC|7foI)KwM7|D>)rs^d-f7Y1N@y0g1zLIVys{xFF!(d{NaE;7P;g1yb+}G8Ib4s?#q79oQm%gU{8VJ zggLBU5Gpi0C^uF>L>yxB=hRapLbS+Q*Nj^13_xa;*;R(Z?eu(5j(O&v>ogFWx`sT#-sB5XyikA^qyU6*yj(@I@ z0)o^8ige*TFtgXZvhkToZW1p4TceBcEGWj&Z+~@JvOLMSFI_O@{uI%({&t0JCEn$s zHa*V&%sk!Y8mD5jlo8s$9y{Tki_y-dQlBoBX_u7VZJW~0D?rV;2GkeFt6m)o8|ZRU zivkHcHu)z}~5kkh5P9+s%&Oy9`5!GmC-4zEEieZ1eH6XQe~iBNaJs(-YVs zMiHd>>Vr%GePJ)zhdtU6)D=zC(CW%eJ8?lcerc5^_i{QAPzeW%4qVG=Vk=aauiAaQ zvHd9)z5qMVgBK>1R0gK!El~-}%~UXu1*?3~6p5dj>FE=Sg;;_)Dd8m5CVoop=xxc!iQ z&{ILw<9*hZW_kn@Lg=r}2ttc{mV`lPAuK~)<;YeCcj|DuOr?IP0?I8vbwos50KY-8 z1X4I&%SQW+PlJ@`zMXA5IzZ?vkB)Cj2D|{nwDN|nGaw0op1xCDZJw!R1X(hv2l27z z3C{w8s84J49*VsYXfrsUt2`p_F`$;3KZdXselp zQwncR`ok*T!0MiN@E$3YPzozRr}173E1n)+`r%&?`#iUvuDJ+c7SX1}y+Qr=IH;@Q z=oYR>e%-aFv7||OsAb6K5 zIoh;Gxy)0gE?T7&}cwySqxPv$)ytsnI3w%c%eX$0_F`o1TO>4i~2bHYiOZ39c< zh-8BhWpUr@98!fV2|rF*GEdS*bJ%Fc)zrvWK!OWnuK+>AKVO4gTaP>0@eg>D@vS2z zWD6)!jYuGzI!KhFUP$Az2RE|CXD^pOlFlW1<~;XjtJ^TmVa|NqMWbV%UB{)QNi91T z;v&`ZJd`iPjV&_2af6B&q>z3{PU1x&8TC_7Bcwfd$m8z}sGUZ(wyHB@Y&hH-Wmy)) z#F>$5zG&6NE4X}WkspuMOYj+lO)R(prh}A}q?@6zwN9i&DGND*cJ_L`i}>J;y@I&) z!(uqGEZtv(J{RhL^`j%OjEK8d?LhYiKT#*PZg9g>l4LpAm8#W5T%5Ruu&7kqt*5(s zhR{z|;ab|21P>DR;z2g0L~mO!86rcZi(y;K)*FIowd{JdAc>M|N&|v5S`h5rq8`;H zpH0@bpM@~8&Fhfd@`T#cQxjQknh&~kd8~F{^$x>+7o9=38U7mXd}0ppHoad@LsHM`q8h%7 zP}`jxu-D1KG?0UC)3TN0vv7eu9Gp?PmA*kAwG4jMU%elo0R+?dXrK@T^tbRr*%R002wy_r~D{I&phT$X0EbO95h*#~Cng#~sxaVO3TFp02wCHYL+$nQS(uCe1J@?T2bL_iLW|>kp zin!wnC6CC}d3vJ&#FkC)#Mwc+`Qf9k_#7E?f>Yf+Amz&(+6_?dXC>^xaXOJYs61;V m(HthaG*+3rvm&v@$42d@21M0f#%f&$cM8ZCVv(xwfCqn^U^G_% diff --git a/packages/google-auth/testing/requirements.txt b/packages/google-auth/testing/requirements.txt index 299c8f2ef534..27a0b3cb798b 100644 --- a/packages/google-auth/testing/requirements.txt +++ b/packages/google-auth/testing/requirements.txt @@ -10,7 +10,7 @@ pytest-localserver pyu2f requests urllib3 -cryptography +cryptography < 39.0.0 responses grpcio # Async Dependencies From 9ff767bbdef488f8228ee1d74a86f4f044fd73b1 Mon Sep 17 00:00:00 2001 From: Carl Lundin <108372512+clundin25@users.noreply.github.com> Date: Fri, 6 Jan 2023 08:11:46 -0800 Subject: [PATCH 661/966] fix: Make OAUTH2.0 client resistant to string type 'expires_in' responses from non-compliant services (#1208) This fixes https://github.com/googleapis/google-auth-library-python/issues/1207. --- packages/google-auth/google/oauth2/_client.py | 5 +++++ packages/google-auth/tests/oauth2/test__client.py | 5 +++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/google/oauth2/_client.py b/packages/google-auth/google/oauth2/_client.py index 7f866d446a31..c2eb6443f820 100644 --- a/packages/google-auth/google/oauth2/_client.py +++ b/packages/google-auth/google/oauth2/_client.py @@ -120,6 +120,11 @@ def _parse_expiry(response_data): expires_in = response_data.get("expires_in", None) if expires_in is not None: + # Some services do not respect the OAUTH2.0 RFC and send expires_in as a + # JSON String. + if isinstance(expires_in, str): + expires_in = int(expires_in) + return _helpers.utcnow() + datetime.timedelta(seconds=expires_in) else: return None diff --git a/packages/google-auth/tests/oauth2/test__client.py b/packages/google-auth/tests/oauth2/test__client.py index 13c42dc52140..b322eefed8af 100644 --- a/packages/google-auth/tests/oauth2/test__client.py +++ b/packages/google-auth/tests/oauth2/test__client.py @@ -99,9 +99,10 @@ def test__can_retry_no_retry_message(response_data): assert not _client._can_retry(http_client.OK, response_data) +@pytest.mark.parametrize("mock_expires_in", [500, "500"]) @mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) -def test__parse_expiry(unused_utcnow): - result = _client._parse_expiry({"expires_in": 500}) +def test__parse_expiry(unused_utcnow, mock_expires_in): + result = _client._parse_expiry({"expires_in": mock_expires_in}) assert result == datetime.datetime.min + datetime.timedelta(seconds=500) From 5a143974aa35a10ccc6505feb1e7499f53973c28 Mon Sep 17 00:00:00 2001 From: Carl Lundin <108372512+clundin25@users.noreply.github.com> Date: Fri, 6 Jan 2023 09:48:11 -0800 Subject: [PATCH 662/966] chore: Update CODEOWNERS file to enforce AION SDK team reviews on shared files. (#1206) --- packages/google-auth/.github/CODEOWNERS | 29 ++++++++++++++++++++----- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/packages/google-auth/.github/CODEOWNERS b/packages/google-auth/.github/CODEOWNERS index b76778cd16a8..4ddae6fb2afe 100644 --- a/packages/google-auth/.github/CODEOWNERS +++ b/packages/google-auth/.github/CODEOWNERS @@ -4,10 +4,27 @@ # For syntax help see: # https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners#codeowners-syntax -# The @googleapis/yoshi-python is the default owner for changes in this repo -* @arithmetic1728 @sai-sunder-s @googleapis/googleapis-auth @googleapis/yoshi-python +# The @googleapis/googleapis-auth and @googleapis/yoshi-python is the default owner for changes in this repo +* @googleapis/googleapis-auth @googleapis/yoshi-python +google/auth/_default.py @googleapis/googleapis-auth @googleapis/aion-sdk +google/auth/aws.py @googleapis/googleapis-auth @googleapis/aion-sdk +google/auth/credentials.py @googleapis/googleapis-auth @googleapis/aion-sdk +google/auth/downscoped.py @googleapis/googleapis-auth @googleapis/aion-sdk +google/auth/external_account.py @googleapis/googleapis-auth @googleapis/aion-sdk +google/auth/external_account_authorized_user.py @googleapis/googleapis-auth @googleapis/aion-sdk +google/auth/identity_pool.py @googleapis/googleapis-auth @googleapis/aion-sdk +google/auth/pluggable.py @googleapis/googleapis-auth @googleapis/aion-sdk +google/auth/sts.py @googleapis/googleapis-auth @googleapis/aion-sdk +google/auth/impersonated_credentials.py @googleapis/googleapis-auth @googleapis/aion-sdk +tests/test__default.py @googleapis/googleapis-auth @googleapis/aion-sdk +tests/test_aws.py @googleapis/googleapis-auth @googleapis/aion-sdk +tests/test_credentials.py @googleapis/googleapis-auth @googleapis/aion-sdk +tests/test_downscoped.py @googleapis/googleapis-auth @googleapis/aion-sdk +tests/test_external_account.py @googleapis/googleapis-auth @googleapis/aion-sdk +tests/test_external_account_authorized_user.py @googleapis/googleapis-auth @googleapis/aion-sdk +tests/test_identity_pool.py @googleapis/googleapis-auth @googleapis/aion-sdk +tests/test_pluggable.py @googleapis/googleapis-auth @googleapis/aion-sdk +tests/test_sts.py @googleapis/googleapis-auth @googleapis/aion-sdk +tests/test_impersonated_credentials.py @googleapis/googleapis-auth @googleapis/aion-sdk +/samples/ @googleapis/python-samples-owners system_tests/secrets.tar.enc # Remove noise from test creds. - - -# The python-samples-reviewers team is the default owner for samples changes -/samples/ @googleapis/python-samples-owners From a27295ffbafcded587a255dd37324b55209a2530 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Mon, 9 Jan 2023 10:43:23 -0800 Subject: [PATCH 663/966] chore: update systest creds and fix unit test syntax (#1210) * chore: update systest creds * chore: fix unit test --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../tests/oauth2/test_service_account.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 83e30b78074dbe0c53fd03dd4fec3493316554f0..7323421d0ac1ca5a6d837ad5cf004bc8d31b2bcc 100644 GIT binary patch literal 10324 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 literal 10324 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 diff --git a/packages/google-auth/tests/oauth2/test_service_account.py b/packages/google-auth/tests/oauth2/test_service_account.py index 4bd194b35797..ed281fcfa34f 100644 --- a/packages/google-auth/tests/oauth2/test_service_account.py +++ b/packages/google-auth/tests/oauth2/test_service_account.py @@ -373,7 +373,7 @@ def test_refresh_with_jwt_credentials(self, make_jwt): assert credentials.valid # Assert make_jwt was called - assert make_jwt.called_once() + assert make_jwt.call_count == 1 assert credentials.token == token assert credentials.expiry == expiry From 27f73420d3e88734c11ed784aeb2aded26dfa025 Mon Sep 17 00:00:00 2001 From: Ryan Kohler Date: Mon, 9 Jan 2023 12:18:50 -0800 Subject: [PATCH 664/966] fix: allow get_project_id to take a request (#1203) * fix: allow get_project_id to take a request * Adding Args/Return * Updating documentation Co-authored-by: Jin Co-authored-by: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> --- .../google/auth/external_account_authorized_user.py | 9 ++++++++- .../tests/test_external_account_authorized_user.py | 5 ++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/google/auth/external_account_authorized_user.py b/packages/google-auth/google/auth/external_account_authorized_user.py index 04a7f5b918ac..a2d4edf6ffd2 100644 --- a/packages/google-auth/google/auth/external_account_authorized_user.py +++ b/packages/google-auth/google/auth/external_account_authorized_user.py @@ -222,12 +222,19 @@ def can_refresh(self): (self._refresh_token, self._token_url, self._client_id, self._client_secret) ) - def get_project_id(self): + def get_project_id(self, request=None): """Retrieves the project ID corresponding to the workload identity or workforce pool. For workforce pool credentials, it returns the project ID corresponding to the workforce_pool_user_project. When not determinable, None is returned. + + Args: + request (google.auth.transport.requests.Request): Request object. + Unused here, but passed from _default.default(). + + Return: + str: project ID is not determinable for this credential type so it returns None """ return None diff --git a/packages/google-auth/tests/test_external_account_authorized_user.py b/packages/google-auth/tests/test_external_account_authorized_user.py index c97d087b3b40..db18450a8326 100644 --- a/packages/google-auth/tests/test_external_account_authorized_user.py +++ b/packages/google-auth/tests/test_external_account_authorized_user.py @@ -424,7 +424,10 @@ def test_to_json_full_with_strip(self): def test_get_project_id(self): creds = self.make_credentials() - assert creds.get_project_id() is None + request = mock.create_autospec(transport.Request) + + assert creds.get_project_id(request) is None + request.assert_not_called() def test_with_quota_project(self): creds = self.make_credentials( From 8abb00d74111931a62bc7ab7f87d107e02bcfcc2 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 9 Jan 2023 12:45:17 -0800 Subject: [PATCH 665/966] chore(main): release 2.16.0 (#1198) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 14 ++++++++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 5a3cc94cff0a..b3c2aee94cab 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,20 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.16.0](https://github.com/googleapis/google-auth-library-python/compare/v2.15.0...v2.16.0) (2023-01-09) + + +### Features + +* AwsCredentials should not call metadata server if security creds and region are retrievable through the environment variables ([#1195](https://github.com/googleapis/google-auth-library-python/issues/1195)) ([5e27c8f](https://github.com/googleapis/google-auth-library-python/commit/5e27c8f213b2e19ec504a04e1f95fc1333ea9e1e)) +* Wrap all python built-in exceptions into library excpetions ([#1191](https://github.com/googleapis/google-auth-library-python/issues/1191)) ([a83af39](https://github.com/googleapis/google-auth-library-python/commit/a83af399fe98764ee851997bf3078ec45a9b51c9)) + + +### Bug Fixes + +* Allow get_project_id to take a request ([#1203](https://github.com/googleapis/google-auth-library-python/issues/1203)) ([9a4d23a](https://github.com/googleapis/google-auth-library-python/commit/9a4d23a28eb4b9aa9e457ad053c087a0450eb298)) +* Make OAUTH2.0 client resistant to string type 'expires_in' responses from non-compliant services ([#1208](https://github.com/googleapis/google-auth-library-python/issues/1208)) ([9fc7b1c](https://github.com/googleapis/google-auth-library-python/commit/9fc7b1c5613366cc1ad7186f894cec26a5f2231e)) + ## [2.15.0](https://github.com/googleapis/google-auth-library-python/compare/v2.14.1...v2.15.0) (2022-12-01) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index f0ecd5d63c45..6ab5ecc4cbfb 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.15.0" +__version__ = "2.16.0" From 187a7cb1b02e429c9484d709a0608057167ee97b Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Wed, 18 Jan 2023 09:43:24 -0800 Subject: [PATCH 666/966] chore: update sys test creds (#1214) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 7323421d0ac1ca5a6d837ad5cf004bc8d31b2bcc..ddf9045d793090c6ed26378ddb55cdde134a065d 100644 GIT binary patch literal 10324 zcmV-aD67{BB>?tKRTB%fYOT4eS_l}Jm9c`c0e4@2Xprl#TWA8qv^BKYmlqPMPyl~- z0+!>4>o;F`Hf+q~m?v8NYrq!6zPRIsEdJ=({qT?JmL8Vzdd3dG$|YF1`?^iFNf$?R z6p1CT^hzJ3q7ZX-JttdN*u(PfF0qwo6*0aGy&vvXHdubxW{JGQmiU53pTk?ESAoIE zKu{aDruN33y}R`-K|-7K+zmfC}&gvdXrG9$Oht5H!u@Un1gMQf&yc4SUVW zq=8a?JyjXhlfM8R`UB9-Mt2`6nBl=*%GC#l*Erx!aZyHQjL)jEjfZry`~P!^Q3{FR z)M)@B2?}e(i>;R~gfz9I4myi7+&c5aqmhi_tvvv4`XlMwHf*%AW72D$7QBYygLI(F zEw5;~06|VI&=qITqJ}dGgKyC#vbdAO*I};#jI2Xfpb|$hdn2-}`#~15z%2Qh2^3HM z6nMu(cYdpzSXe_d%@+qUNd1!06X~Q^IW6c1f~{nOaUg_Wc8|#H^^<>JQ3!Ce)GSSe z6!d2W#UQ+J^tHdzauW>iwZ}c#I6Q#{Vqpk|?+9xTEE=P%mJmii#y023mb>dbwHlQ| z^xf~_6ex#LR84%$hfkumd!VmkL{cv#>ef)2%S-jdvZ^FaiaF3mU~ND}!oRv@yt^n> ze43dd1pLUEf?s%O-iYH*BuIBx*h+&isIhHVeXua_3AWd$*Cw_=LVq&?ol@u!zq z{42xn*f4ELd5w^D-AW5EznS8oueDqfuP01uu-a=M%urwCv_f{!QjujS7~LOKRGf8I z9c!%0fI59Em^x1m1LazLeWRTBLx9noK%Tp@;z;o0JvJ;KPs0)^Ms(iPV&T^${XT!r(u7Or-8hNk5_kbNr=-XxKLLn?-D*C97Vhaj1 zpg6MT)0q+h>S?ct69Z%u(Mq5HDh}W%*-3*tWTbu%`}4O7)lIvco7g9;mu$m37WkA1 z`&qY|eBdKrp^9kGUjtPw{My4lhGrBSsl|Qze_&tx43q%n>D89bGT%+{6kS38B*nlL z^$GrJWeY~4`M3FUzUn#(<|*DH`V1w7C|4x?*tOZ{8kO%Nq<4pGw&%QNDW(^#2mpLeG|e&1XJ?M_;1WXeA}Vo z)eiQ4lQj?MRRr%<&}T;~z>^Stb4TC62XUB;U(P%34paF9Bq zAElr0-RzZ{J%vr$8lFUhW$FGbD>!sP8a-Tiod-0wX5|2YQ@O7!4%P`0as|wFgq!)@ zjCw&c>dVu#3@0^Kxvm&gz*cZ#3XpgKnhnE3uDv>5jL(Erf<)ZZwC`FT@1=Sfr$}v| zNU7sW5_r3kAXJuv1(tUQutG}4*vDW42XsOs`Emd-c`m4?+O`d1_z-PCRCLn(iu0X1 zYQR>;Gl!xN9nJ8re!tCOwPBi!po|T~>aVdg4BG2)hl%uo+F=yGS(9pJ>l2r0&;?sM zI(9L%T3Q8zwx-fr496sf01A3fRq88kuDSIf&Db^NJ;(TBrrDVTms%P@Zqr`>Sf=Z+ zvO1cF=wB0M=ZAM0@SvSKbAxDKtz~Qb63Rq?Dq=99zeRG(Z_zxnhr?}T{^@ue)@?Cd z?*!i+1W;8!$+qR4qr-!!!Pr&o9QNO-D<2$e7DG zj_KScq)7|UTE>=7^St;Hr`4CyPS$UsuUHn~er-y+@}jPdl4}7wd_utoH_gGiBk_iE z36tn>x%~C@Z4^{RE~XrlY?pi$NdOM|{C6NYj5&VuntfRRKCn3on?f`R>^$@z!Do)v z9s-Eo|8`{m7{aYTt#6mp+GTy57e#fS8A=SM!S!YVKzm|66+NT~Cxw8mC)MbLcSHbt z4ievB<*jV1Uu`K>qMRax9Ac~(8|zgeute=u-qw&=w#X1dFXbnWST(X zaNfWcvLB{W*PT5?b=^7qjPUAn(}c<1Yb;unGSv(&1n(mEx{M>n1%Fs{YZOi&vV9>N zbieq-#=bU2kTP|4Op5Xqzh&r!sE8kPII`zn!z%lg?0`lHz4!f7(IZ>W5{TQ?mluY< zQ8s!9=;Q>2Z9V5kWd|e>D#Z>%3w1;A@nN4urmsQ%kRcOx{Evs4f1$alN@0RvQ2xsUfa<4#9U5V$Z@6F)k^ed(MD$V`j6=A`V5D>!)8G-!pfrRvQ- zL<9jwA}?YvHgNY`=s@sQ$R;>ByHl81&HIB7(5fk#w}2n)aChHxn2dS_q0n^vqT6i{vAh6|U%gmhYa$wu%q1k@4L8Im z>#o>0?rImIp}l_qb2}mYqK9LQLX7Esf(C>%CB`*^ z8Z6AU<35a(HU5Lwz?%@$8p+#GE%=LGX^w9msVHR)j@KAF`YApS( z+k$6oF#$TuJlLB>C|L*bIN^UUn_IFb0!fglkF#dTg7w-&{lG6~tz_D*# zT11hkS+j)?XBh|%xlJ`3W3wnkgYq4`nk!Hny`I0nzT(x7DB*qiQ+Ym;xFPt1-W%m- z;Rfe}P`7oYa}wu`=Cz-H97ziejLVoHqunhhh|Bfn_Q>^K=@Nj*HEi#I{7nndIm+%a zr$Gz*$w>@=VUSnJ;uwcrlk|I7d!p+m)w!<9+!=|W@3D4>H&;@~BiU>nxi4j+KLR!z zLm1?!vZA`7j#}w?nhHg6%eYrU=-|z~m{Yk_bK{oN_nAWV9H#Ts&G#26?GBDYu1pib zHK;v+eNDhQjKYlikn)(7MIByJsnaH~LFtRt-FV^Bh&=^xY$rTmOT^E)so70Bh$L{F z`z06`Dm*>`cE;30VeqNB>Jf$1q`0&;5Z)86gsn;z*z@Nr8no0$;jD+ePIQg|Xmj3n z6)iEgv@6{G9CM$*D;cKPf0++a`2yk8*p*jmN%*B08pKRg{m@%-ieXD7mw;4f{vLqF8RW2!mFj?o0{A&F)$u`ACq3k=J83y}>3VQmUhbn$!V783#;+lrO>B1t{LWT{_)w3hE~&K^0(BdZrcW|!(Xg*18(rw4t~x@ zzKNoG8sYeugRpw&XTWOY3CIW8{29h4z2x?akFW{;6y7IeahQ)x1FTq>i$*b&q1IHz zH>e1F#zYPgnVc}vTXZTaZfrlbR8jXo`<^0HOR2Nh% z{5o`+EJT{h1h&K>G;hI+e3uKnJFQ(}BIM%@=zNs{Y;2VmlDXjrZFbZ;PW5#R~ALp!;@UH-QRe>4p}+n%Qd z`!!8}*~O68>FoCnbL7-xO~~XLuo@*}`TaA}?6p{xBs?jI(3{oG$DZ&7HBc+_9T$58 zYNT3*1C*T>l<_BZqM-uR)*jPRgWNH}M8N^_-?6^Q%jw1*LwPJL;uR|f_*2s;arsU3Y(fz$F(p*GyoZDU$D+E2s3DTnjN#-H~~zOqsT(E=Mab1L(ZV^QFKnwhUf&^{4Zz%@Fhn!j=?hGs;>Mxx` zVr>ExedEjQ^CqKc>*z5}m2vS-#ZPm3^mmAo3{*OxPHm!gWgd|ZI%+36kI9;$Z~>(i z*Gcn^zGgcMwdxSz5?N@O?0B#xr(%1&f{BR(Z8vddbHK?Tzq{V*QkvO?D@j-P>_BzG zXv;JONT{OL$@491V}K0Pf;w21xsX3gET#IgZ|1sKSg##LyqauD3i=o|K)p(6wH{xx zPU)holt3zo=-?-+#gz~PWeyAlb@D>Nw<~Msa6*5!H<-bVVuF%%l0$m39h=kW)&z?C zoVTAjKaBwtPG44DJtIEMAEd$}h*Ma=nB1IEfs(%uHA6e?A}-xrObuw85JZmC@{+kcWjJB4%e#^L2C-&)=hHj zVROw}n-s$dr_P(MjGRL~&k;0bASli;LB{UQZcHnjVh#TQ0>zL}Ai@6&`8#v{@GBbZ z(RqT62wNEnIE?`a zGKM+u9dAx|n}qpkMC2Bk#7+p~gDpme+J(AVjh9#%fUj(q>0+r)VQ)k#j}lB6Q2~~(vUc3ldHN`+4xw^AdTP{ zh2AoA)g6NEY1uH?pdCUyueK~bb&6}Dy$fD5C@{v#-ZCYIYCedv*QvP(KzNExPgc?flA(Ag=Mb-TxlNJde$lvu6C$o=;0d5WO0=98!pwFyo0w*rEKtfDKx<6#=@5=le7-qN}c0@cYp(srv{ZyMnoa zpR>%N`H`3yFlOXSG7m5!`%DY& z)KKjHKH1DgOa0w9=wuc)31D5tPz=Z8SE_44Fnj2N;HOjC6&$&AZIqc7;97bgYYKV$ z(o%wjO|2T&&@04!cy?I_lOb#XAiU*>C9WwPqrUSITX9nt-tKUur1>uw#S>)Av zDH$?-X3i^n3xaa`5ia@_m7~@B*~Vjh6OC!cHtie_f}5&sS@c@hD}SS8F1(s*3z>Bt>p2;P+bF?>0!h+SSX!CKi={h=49*VwjBy&(8FGhINR zuWdr&(?oIF!#5Sl4A-gVmx&qcNhY=dM+A~(OHo31=v6u_Y}(%V9%_)T({7e+9;Qzk5mF8S+P$mTn2BH#@M?!! zv2rFK;kdX!y!^phR-g6e3v)gJ@+|p6oi=_Ab@8xO*I~BO{&=y6PxJQ+5}IQZC_^N1 z+PW}g_}A0?tskvp_~{f(YYq(6NWlSgoa~>=TGD`O1Lkr#y4@WU9xxCwIJVj%8oUMA zJBoM0o{M0#Vq(12z}g|`+9s`c#D8KUTVc2n#;y!U94OxPNM5;5B#S<9_rE`b43MMCN z_|T%6wcq@DE05HK0M53mUMV3s@aY3EE3QMAvL@OsI7o$F!HRM1)+KJhIdnbuW!z+6 z&ek!jEHGHa?!j6^XZ4n%c?S>J!kS6^sus&#E#54$Xu17q*x+>lb<-4ST&yglIRuj0 z;*Ib?O*Z(S96gWl3nLc8B~#a3>E*YF;+x-TzU!?1EnEyXlUoZ>yn|O2G%?4ui{1>| z=wA^M{7Jy9#D?u_;i!wmGjKmOHXzSw<$2PR%P-@AN$6XeJkWfI|4smE zKVrFpd6mQkci#KiO{G*yhHA(T(y@~G&v22);3-uzk85(MYFBx*Fmc}w4;fng%B%kArgr;mUl9G9+=#tH57!j}2HOy1) zAc2&A9B^nPG$mk79Nj5w-qFa}v^wrRK~B}g@Mz!-dKSx_=qA~;dnMWO+Oun4Fo??} zeE`C7Cj~uGwbgHFXI!ZLt(;ID)NBfI%kvIvG!CbpzN8#JbALKxUX>Hn=6Y59gjB^8jbys zpYd4|X9&Xdc3FCoSq{jA;#_R-#~FA!K+_&QwtRBP5OI+eZ+^c48s=DTwA9@2rzSJ$ zr%1d~pJB2Gqlv`o;t%+^Ad4fB>qgHD~-oMrI&cI>Ed1OKON1v5=i23tVONwLJo z&JIaRZ^_k5G{(4KS2{qrPlS_IE0M7y`L!n>I4RyxOUYz_x8fkDSP0gd0^-lnF{%j6 zN=X%ZLJt{)Ox`C0(B_4WO#s1#+63|`H${^PP>qqVA@gd1?L}JI`=A#omoJ+Tj!8=h zGD}=uY+5K&)OPV>sv{;@1568pm8aF|FZYq989S9GyQBR27+@_CM965}XwpRUhlzf1 zSx4PPiG6Zoip7G1iI>3`U7{9QkT*?awLFzk79GS60s!IDFeLgjvi1~U#|z0iu-{aLPZRib?tafJ|IpS-@!M`#8e zCbuV3cGHF0GO*E@BCyyMI$R~2M&7T3O()*3cjQlPe^W}aDU4%4eBWnpr#OKKr zG`Jt_iuR74n_X%IUCKNVO2!`4n#6n#W(S3g?;!YkfbC|Wxebs~Qe3*gs$~*5L-mbe z5uXpvkk`a@sqn{w;+oa)H+X?VG_4S=-&h(6!)qc_gIscc9~s3&wLj9~{-<(#@2jAv z?q>flTUuIXixPEMtjP0IoSnLyw7<9x@u1w*G8@hvZlHE)r zaKV?@AHFl2qbXlxVaXZd)V5+KJfHV?cFX6J9&3*^@Vn_2Q!;(m%A(oef`m7mPe7^R z;U>Y2)Q_)vL%!Nj)=d!pYnLhsG{>NKd_UFfR?D^#{*6mgY&RZH1rf~y@QGOH`9en4 z$BM=q7Idfd<7jeO*pT>x2Y=~?va}OUV%;f_$ViBLFU8aD;cUB2RVk!i7RvEs_s4iC2o0T3UY(q|S&T6R z(z78=Ke?DhZj|vsjzD}kI~e$y;Ew!f41cG+6@a-IyxLQ;IZy*-aW6S z?H|0PYEuQ8UrWk)`mR-HzTUGT{4d#zs?EYL{~4%5W_hjiEY>Z+f(gPP%pM zxv4FjD;&N4l*^;g2SV}mD?*Htc@Wv^I{pmMYu~%?mp);QkZQf>JyOD4VZ>uB0CEot zY5unUlLQ35$7w8Vbpm3e{*ccb3^AtSQ2$adfybUF>#rS#8aRvOtu!m2nu0Pb9teqp z0u_aXFQH#1G;h>6FAfHRJ;S6H^#k;iycy%+nS;d<=fKeiyu8bl(S9XA(*u6BZo|Kb zA7b+HFi|fqWy1z15M(%_TT~1C@VH{*&7IGVI(vAX{^pQ&HV*yKkgO&zNjZu8dT!b{KlK*Ik5d?YjpR9C1#J^sMv~v?9|F8{%2d>F zdz9K_v7wqNjls;26-HhhQMY?I$};8&C)Ss$ibKqq`9TDXhCr5+_iZ%C40yZ?<^KyH z=_@~iraVnJ5h<&-k=0Ur(?`ir%~AIN#P!EddiD{pt!~WJ(aQiUG}^~I^fng@bdq^6 zlUl0jlzpJ}yF)7lLsds~_9YvJ@|MYIZ$9sjKzYlX8xuk9{F3vppOgC=p2wHijWZlS z3@T-s5PJ#xYF7CK2GA~7*C@ErZm5erQ;3?IyHg8WUXzvlu*B!^agOA=0v7Txl)?O- z@3BY-3K}w7r6g{dYik;7OjOS1)(!O+<~WCDINlV8^vU&qlwHh|-g3rWCJ#6t&v`|V z4YqUSaS1d59lY0V8f$-kckgCz^8{v`V0f7)X;_vQ(#sNO%HQ_K$1RC!l`OtAs&G780a4!OT~nzo}u& znTDJA-L(ngkIUk4-KLq&E!U|BEIpL(d=Eb~d0-JWZib_5v*>@VC-n#TqP^Px&Zz#z z-@UQDZ_o9i5| zaoRaW?LXKEV>MJk{@tp&!&eF(+@#It+rjp`g6-UDFu4?D^lUGeGRMTbAGvPhQTc?d ze!e6dyo9avw_%f`pF6+>lhy8O&>8?QbXMzcnx=O3=wz^&uFqrk7sN4sVbd`gB`i$R-96GM|3;zVX0h{^yf|sht z`ShPZueXkETUjURAEgel5MpQ_yy^T6K_OF>OYWs@TY=!yr!!PSV6w?m*1TNSdM-dw z`_K*65nPRDaQ~_KFW~JB2}Xb+OInV)w<_ z^Ia>9d#ca}%<}aqP6njPWi1+#04yW-jnliFRgR^5)%`RqK;*{A_}_d0c_lsT>O_|l z5F{N5V1;-UI+2#P&;dd^Gsf*&VP^jdAejQ8vP(3B*tBH7+Yd8ZgsZs6wxkOsafCS9 zCdA>rG=s2YntIfvkY*b=x#BrqQS!tYtx=vy8U!{%^{TO8is>^gZS4%D`3hbViwGT3 zo=x74;Ys^K@zjp>>|#DLQ-#fx?-$QEiJ5+;`g9#RZN51j2q32oU0$7g(j|_lva7p6 zlTw<2wT(z!Ctq?0%Cr9_afav3k8U+jEkalll}B6t_QzXsnld` z@9m3`BA?~ILNC4^yf|xQf1DVz6W3jTa+~_7lyZBnok}F#Q4d%oZjyA(5jJ?j`|x$> mJiT!{w+@;_#{M;d9p5GIQ`V$0X}Fjh5mdZ>MC&x#Z>hcLj}~+Q literal 10324 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 From ddde8178f20de6618c2d0406cdf6c786531ec505 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Wed, 18 Jan 2023 13:20:36 -0500 Subject: [PATCH 667/966] fix: add support for python 3.11 (#1212) Co-authored-by: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> --- packages/google-auth/noxfile.py | 2 +- packages/google-auth/setup.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index 18a7232e43e5..0898278603d0 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -85,7 +85,7 @@ def mypy(session): session.run("mypy", "google/", "tests/", "tests_async/") -@nox.session(python=["3.6", "3.7", "3.8", "3.9", "3.10"]) +@nox.session(python=["3.6", "3.7", "3.8", "3.9", "3.10", "3.11"]) def unit(session): constraints_path = str( CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt" diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index c89b05d1d5c5..50ac473baf0d 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -76,6 +76,7 @@ "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", From bfc0cdb9bfd067d379cbf4f7fac24c45d4bb5882 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Wed, 18 Jan 2023 10:51:07 -0800 Subject: [PATCH 668/966] chore: update current maintainer (#1213) --- packages/google-auth/README.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/README.rst b/packages/google-auth/README.rst index 1f6b9affb9e9..d8f28b39afc1 100644 --- a/packages/google-auth/README.rst +++ b/packages/google-auth/README.rst @@ -52,7 +52,7 @@ Google Auth Python Library has usage and reference documentation at https://goog Current Maintainers ------------------- -- `@busunkim96 `_ (Bu Sun Kim) +- googleapis-auth@google.com Authors ------- @@ -60,6 +60,7 @@ Authors - `@theacodes `_ (Thea Flowers) - `@dhermes `_ (Danny Hermes) - `@lukesneeringer `_ (Luke Sneeringer) +- `@busunkim96 `_ (Bu Sun Kim) Contributing ------------ From f7c52e34178ec190ff98df3f5a07ba2cb7182d8c Mon Sep 17 00:00:00 2001 From: Carl Lundin <108372512+clundin25@users.noreply.github.com> Date: Wed, 25 Jan 2023 14:28:01 -0800 Subject: [PATCH 669/966] fix: Update the docs generator interpreter to unblock documentation build (#1218) * fix: Update the docs generator interpreter to unblock documentation build * chore: Refresh system test creds. --- packages/google-auth/noxfile.py | 2 +- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index 0898278603d0..417a66500a82 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -140,7 +140,7 @@ def cover(session): session.run("coverage", "report", "--show-missing", "--fail-under=100") -@nox.session(python="3.8") +@nox.session(python="3.9") def docs(session): """Build the docs for this library.""" diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index ddf9045d793090c6ed26378ddb55cdde134a065d..58ce7ccfbe78f379fdabd2c42f4cce63e31400f7 100644 GIT binary patch literal 10324 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 literal 10324 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 From 151b7b01fd0c21faffafdc013e69ed3258f96729 Mon Sep 17 00:00:00 2001 From: Jin Date: Wed, 1 Feb 2023 12:42:35 -0800 Subject: [PATCH 670/966] chore: update token (#1221) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 58ce7ccfbe78f379fdabd2c42f4cce63e31400f7..d803e5a4496123d2521498ab5634c6cdd8fd1eac 100644 GIT binary patch literal 10324 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 literal 10324 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 From 0f7f5886b14f7b03f51bcb02644f8bb1c6236143 Mon Sep 17 00:00:00 2001 From: Jin Date: Fri, 3 Feb 2023 14:47:13 -0800 Subject: [PATCH 671/966] chore: update token (#1222) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index d803e5a4496123d2521498ab5634c6cdd8fd1eac..59bf95731b3fe3a68db4d4a43e94c9c4fb5e359c 100644 GIT binary patch literal 10324 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 literal 10324 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 From dc415622306fec21139ec4ac8db415dd484521d1 Mon Sep 17 00:00:00 2001 From: Jin Date: Wed, 8 Feb 2023 08:23:01 -0800 Subject: [PATCH 672/966] chore: update token (#1224) * chore: update token * chore: update token --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 59bf95731b3fe3a68db4d4a43e94c9c4fb5e359c..6501d80ad3f859aff0dcc7a362a4c1da88f413ea 100644 GIT binary patch literal 10324 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 literal 10324 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 From cad1abf1cad26c1dff59a42ff0e571475d01c922 Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Wed, 8 Feb 2023 16:50:17 +0000 Subject: [PATCH 673/966] build(deps): bump cryptography from 38.0.3 to 39.0.1 in /synthtool/gcp/templates/python_library/.kokoro (#1226) Source-Link: https://togithub.com/googleapis/synthtool/commit/bb171351c3946d3c3c32e60f5f18cee8c464ec51 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:f62c53736eccb0c4934a3ea9316e0d57696bb49c1a7c86c726e9bb8a2f87dadf --- .../google-auth/.github/.OwlBot.lock.yaml | 4 +- packages/google-auth/.kokoro/requirements.txt | 49 +++++++++---------- 2 files changed, 24 insertions(+), 29 deletions(-) diff --git a/packages/google-auth/.github/.OwlBot.lock.yaml b/packages/google-auth/.github/.OwlBot.lock.yaml index fccaa8e84449..894fb6bc9b47 100644 --- a/packages/google-auth/.github/.OwlBot.lock.yaml +++ b/packages/google-auth/.github/.OwlBot.lock.yaml @@ -1,4 +1,4 @@ -# Copyright 2022 Google LLC +# Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,4 +13,4 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:3bf87e47c2173d7eed42714589dc4da2c07c3268610f1e47f8e1a30decbfc7f1 + digest: sha256:f62c53736eccb0c4934a3ea9316e0d57696bb49c1a7c86c726e9bb8a2f87dadf diff --git a/packages/google-auth/.kokoro/requirements.txt b/packages/google-auth/.kokoro/requirements.txt index 05dc4672edaa..096e4800a9ac 100644 --- a/packages/google-auth/.kokoro/requirements.txt +++ b/packages/google-auth/.kokoro/requirements.txt @@ -113,33 +113,28 @@ commonmark==0.9.1 \ --hash=sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60 \ --hash=sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9 # via rich -cryptography==38.0.3 \ - --hash=sha256:068147f32fa662c81aebab95c74679b401b12b57494872886eb5c1139250ec5d \ - --hash=sha256:06fc3cc7b6f6cca87bd56ec80a580c88f1da5306f505876a71c8cfa7050257dd \ - --hash=sha256:25c1d1f19729fb09d42e06b4bf9895212292cb27bb50229f5aa64d039ab29146 \ - --hash=sha256:402852a0aea73833d982cabb6d0c3bb582c15483d29fb7085ef2c42bfa7e38d7 \ - --hash=sha256:4e269dcd9b102c5a3d72be3c45d8ce20377b8076a43cbed6f660a1afe365e436 \ - --hash=sha256:5419a127426084933076132d317911e3c6eb77568a1ce23c3ac1e12d111e61e0 \ - --hash=sha256:554bec92ee7d1e9d10ded2f7e92a5d70c1f74ba9524947c0ba0c850c7b011828 \ - --hash=sha256:5e89468fbd2fcd733b5899333bc54d0d06c80e04cd23d8c6f3e0542358c6060b \ - --hash=sha256:65535bc550b70bd6271984d9863a37741352b4aad6fb1b3344a54e6950249b55 \ - --hash=sha256:6ab9516b85bebe7aa83f309bacc5f44a61eeb90d0b4ec125d2d003ce41932d36 \ - --hash=sha256:6addc3b6d593cd980989261dc1cce38263c76954d758c3c94de51f1e010c9a50 \ - --hash=sha256:728f2694fa743a996d7784a6194da430f197d5c58e2f4e278612b359f455e4a2 \ - --hash=sha256:785e4056b5a8b28f05a533fab69febf5004458e20dad7e2e13a3120d8ecec75a \ - --hash=sha256:78cf5eefac2b52c10398a42765bfa981ce2372cbc0457e6bf9658f41ec3c41d8 \ - --hash=sha256:7f836217000342d448e1c9a342e9163149e45d5b5eca76a30e84503a5a96cab0 \ - --hash=sha256:8d41a46251bf0634e21fac50ffd643216ccecfaf3701a063257fe0b2be1b6548 \ - --hash=sha256:984fe150f350a3c91e84de405fe49e688aa6092b3525f407a18b9646f6612320 \ - --hash=sha256:9b24bcff7853ed18a63cfb0c2b008936a9554af24af2fb146e16d8e1aed75748 \ - --hash=sha256:b1b35d9d3a65542ed2e9d90115dfd16bbc027b3f07ee3304fc83580f26e43249 \ - --hash=sha256:b1b52c9e5f8aa2b802d48bd693190341fae201ea51c7a167d69fc48b60e8a959 \ - --hash=sha256:bbf203f1a814007ce24bd4d51362991d5cb90ba0c177a9c08825f2cc304d871f \ - --hash=sha256:be243c7e2bfcf6cc4cb350c0d5cdf15ca6383bbcb2a8ef51d3c9411a9d4386f0 \ - --hash=sha256:bfbe6ee19615b07a98b1d2287d6a6073f734735b49ee45b11324d85efc4d5cbd \ - --hash=sha256:c46837ea467ed1efea562bbeb543994c2d1f6e800785bd5a2c98bc096f5cb220 \ - --hash=sha256:dfb4f4dd568de1b6af9f4cda334adf7d72cf5bc052516e1b2608b683375dd95c \ - --hash=sha256:ed7b00096790213e09eb11c97cc6e2b757f15f3d2f85833cd2d3ec3fe37c1722 +cryptography==39.0.1 \ + --hash=sha256:0f8da300b5c8af9f98111ffd512910bc792b4c77392a9523624680f7956a99d4 \ + --hash=sha256:35f7c7d015d474f4011e859e93e789c87d21f6f4880ebdc29896a60403328f1f \ + --hash=sha256:5aa67414fcdfa22cf052e640cb5ddc461924a045cacf325cd164e65312d99502 \ + --hash=sha256:5d2d8b87a490bfcd407ed9d49093793d0f75198a35e6eb1a923ce1ee86c62b41 \ + --hash=sha256:6687ef6d0a6497e2b58e7c5b852b53f62142cfa7cd1555795758934da363a965 \ + --hash=sha256:6f8ba7f0328b79f08bdacc3e4e66fb4d7aab0c3584e0bd41328dce5262e26b2e \ + --hash=sha256:706843b48f9a3f9b9911979761c91541e3d90db1ca905fd63fee540a217698bc \ + --hash=sha256:807ce09d4434881ca3a7594733669bd834f5b2c6d5c7e36f8c00f691887042ad \ + --hash=sha256:83e17b26de248c33f3acffb922748151d71827d6021d98c70e6c1a25ddd78505 \ + --hash=sha256:96f1157a7c08b5b189b16b47bc9db2332269d6680a196341bf30046330d15388 \ + --hash=sha256:aec5a6c9864be7df2240c382740fcf3b96928c46604eaa7f3091f58b878c0bb6 \ + --hash=sha256:b0afd054cd42f3d213bf82c629efb1ee5f22eba35bf0eec88ea9ea7304f511a2 \ + --hash=sha256:ced4e447ae29ca194449a3f1ce132ded8fcab06971ef5f618605aacaa612beac \ + --hash=sha256:d1f6198ee6d9148405e49887803907fe8962a23e6c6f83ea7d98f1c0de375695 \ + --hash=sha256:e124352fd3db36a9d4a21c1aa27fd5d051e621845cb87fb851c08f4f75ce8be6 \ + --hash=sha256:e422abdec8b5fa8462aa016786680720d78bdce7a30c652b7fadf83a4ba35336 \ + --hash=sha256:ef8b72fa70b348724ff1218267e7f7375b8de4e8194d1636ee60510aae104cd0 \ + --hash=sha256:f0c64d1bd842ca2633e74a1a28033d139368ad959872533b1bab8c80e8240a0c \ + --hash=sha256:f24077a3b5298a5a06a8e0536e3ea9ec60e4c7ac486755e5fb6e6ea9b3500106 \ + --hash=sha256:fdd188c8a6ef8769f148f88f859884507b954cc64db6b52f66ef199bb9ad660a \ + --hash=sha256:fe913f20024eb2cb2f323e42a64bdf2911bb9738a15dba7d3cce48151034e3a8 # via # gcp-releasetool # secretstorage From b6009919a7e8cdb52bec41166e602cb31c600bec Mon Sep 17 00:00:00 2001 From: Jin Date: Thu, 9 Feb 2023 14:27:04 -0800 Subject: [PATCH 674/966] fix: Remove 3PI config url validation (#1220) * fix: Remove 3PI config url validation * add security consideration in doc * Break the security consideration into workload and woorkforce section --------- Co-authored-by: Leo <39062083+lsirac@users.noreply.github.com> --- packages/google-auth/docs/user-guide.rst | 17 ++ .../google/auth/external_account.py | 61 ------- packages/google-auth/tests/test_aws.py | 34 ---- .../tests/test_external_account.py | 158 ------------------ .../google-auth/tests/test_identity_pool.py | 34 ---- packages/google-auth/tests/test_pluggable.py | 34 ---- 6 files changed, 17 insertions(+), 321 deletions(-) diff --git a/packages/google-auth/docs/user-guide.rst b/packages/google-auth/docs/user-guide.rst index 682b58a76725..0cb119127c32 100644 --- a/packages/google-auth/docs/user-guide.rst +++ b/packages/google-auth/docs/user-guide.rst @@ -548,6 +548,16 @@ For AWS providers, use :meth:`aws.Credentials.from_info ['https://www.googleapis.com/auth/cloud-platform']) +Security considerations +~~~~~~~~~~~~~~~~~~~~~~~ + +Note that this library does not perform any validation on the token_url, +token_info_url, or service_account_impersonation_url fields of the credential +configuration. It is not recommended to use a credential configuration that you +did not generate with the gcloud CLI unless you verify that the URL fields point +to a googleapis.com domain. + + External credentials (Workforce identity federation) ++++++++++++++++++++++++++++++++++++++++++++++++++++ @@ -793,6 +803,13 @@ Cloud resources from an OIDC or SAML provider. https://cloud.google.com/iam/docs/workforce-identity-federation#workforce-pools-user-project +Note that this library does not perform any validation on the token_url, +token_info_url, or service_account_impersonation_url fields of the credential +configuration. It is not recommended to use a credential configuration that you +did not generate with the gcloud CLI unless you verify that the URL fields point +to a googleapis.com domain. + + Impersonated credentials ++++++++++++++++++++++++ diff --git a/packages/google-auth/google/auth/external_account.py b/packages/google-auth/google/auth/external_account.py index d24b22837766..646e31340179 100644 --- a/packages/google-auth/google/auth/external_account.py +++ b/packages/google-auth/google/auth/external_account.py @@ -35,7 +35,6 @@ import re import six -from urllib3.util import parse_url from google.auth import _helpers from google.auth import credentials @@ -127,14 +126,6 @@ def __init__( self._default_scopes = default_scopes self._workforce_pool_user_project = workforce_pool_user_project - Credentials.validate_token_url(token_url) - if token_info_url: - Credentials.validate_token_url(token_info_url, url_type="token info") - if service_account_impersonation_url: - Credentials.validate_service_account_impersonation_url( - service_account_impersonation_url - ) - if self._client_id: self._client_auth = utils.ClientAuthentication( utils.ClientAuthType.basic, self._client_id, self._client_secret @@ -434,58 +425,6 @@ def _initialize_impersonated_credentials(self): ), ) - @staticmethod - def validate_token_url(token_url, url_type="token"): - _TOKEN_URL_PATTERNS = [ - "^[^\\.\\s\\/\\\\]+\\.sts(?:\\.mtls)?\\.googleapis\\.com$", - "^sts(?:\\.mtls)?\\.googleapis\\.com$", - "^sts\\.[^\\.\\s\\/\\\\]+(?:\\.mtls)?\\.googleapis\\.com$", - "^[^\\.\\s\\/\\\\]+\\-sts(?:\\.mtls)?\\.googleapis\\.com$", - "^sts\\-[^\\.\\s\\/\\\\]+\\.p(?:\\.mtls)?\\.googleapis\\.com$", - ] - - if not Credentials.is_valid_url(_TOKEN_URL_PATTERNS, token_url): - raise exceptions.InvalidResource( - "The provided {} URL is invalid.".format(url_type) - ) - - @staticmethod - def validate_service_account_impersonation_url(url): - _SERVICE_ACCOUNT_IMPERSONATION_URL_PATTERNS = [ - "^[^\\.\\s\\/\\\\]+\\.iamcredentials\\.googleapis\\.com$", - "^iamcredentials\\.googleapis\\.com$", - "^iamcredentials\\.[^\\.\\s\\/\\\\]+\\.googleapis\\.com$", - "^[^\\.\\s\\/\\\\]+\\-iamcredentials\\.googleapis\\.com$", - "^iamcredentials\\-[^\\.\\s\\/\\\\]+\\.p\\.googleapis\\.com$", - ] - - if not Credentials.is_valid_url( - _SERVICE_ACCOUNT_IMPERSONATION_URL_PATTERNS, url - ): - raise exceptions.InvalidResource( - "The provided service account impersonation URL is invalid." - ) - - @staticmethod - def is_valid_url(patterns, url): - """ - Returns True if the provided URL's scheme is HTTPS and the host comforms to at least one of the provided patterns. - """ - # Check specifically for whitespcaces: - # Some python3.6 will parse the space character into %20 and pass the regex check which shouldn't be passed - if not url or len(str(url).split()) > 1: - return False - - try: - uri = parse_url(url) - except Exception: - return False - - if not uri.scheme or uri.scheme != "https" or not uri.hostname: - return False - - return any(re.compile(p).match(uri.hostname.lower()) for p in patterns) - @classmethod def from_info(cls, info, **kwargs): """Creates a Credentials instance from parsed external account info. diff --git a/packages/google-auth/tests/test_aws.py b/packages/google-auth/tests/test_aws.py index 4004126602cc..7d87bdba2c87 100644 --- a/packages/google-auth/tests/test_aws.py +++ b/packages/google-auth/tests/test_aws.py @@ -1085,16 +1085,6 @@ def test_token_info_url_custom(self): assert credentials.token_info_url == (url + "/introspect") - def test_token_info_url_bad(self): - for url in INVALID_TOKEN_URLS: - with pytest.raises(ValueError) as excinfo: - self.make_credentials( - credential_source=self.CREDENTIAL_SOURCE.copy(), - token_info_url=(url + "/introspect"), - ) - - assert excinfo.match(r"The provided token info URL is invalid\.") - def test_token_info_url_negative(self): credentials = self.make_credentials( credential_source=self.CREDENTIAL_SOURCE.copy(), token_info_url=None @@ -1111,16 +1101,6 @@ def test_token_url_custom(self): assert credentials._token_url == (url + "/token") - def test_token_url_bad(self): - for url in INVALID_TOKEN_URLS: - with pytest.raises(ValueError) as excinfo: - self.make_credentials( - credential_source=self.CREDENTIAL_SOURCE.copy(), - token_url=(url + "/token"), - ) - - assert excinfo.match(r"The provided token URL is invalid\.") - def test_service_account_impersonation_url_custom(self): for url in VALID_SERVICE_ACCOUNT_IMPERSONATION_URLS: credentials = self.make_credentials( @@ -1134,20 +1114,6 @@ def test_service_account_impersonation_url_custom(self): url + SERVICE_ACCOUNT_IMPERSONATION_URL_ROUTE ) - def test_service_account_impersonation_url_bad(self): - for url in INVALID_SERVICE_ACCOUNT_IMPERSONATION_URLS: - with pytest.raises(ValueError) as excinfo: - self.make_credentials( - credential_source=self.CREDENTIAL_SOURCE.copy(), - service_account_impersonation_url=( - url + SERVICE_ACCOUNT_IMPERSONATION_URL_ROUTE - ), - ) - - assert excinfo.match( - r"The provided service account impersonation URL is invalid\." - ) - def test_retrieve_subject_token_missing_region_url(self): # When AWS_REGION envvar is not available, region_url is required for # determining the current AWS region. diff --git a/packages/google-auth/tests/test_external_account.py b/packages/google-auth/tests/test_external_account.py index 78a272b6a21c..c8900a493674 100644 --- a/packages/google-auth/tests/test_external_account.py +++ b/packages/google-auth/tests/test_external_account.py @@ -65,101 +65,6 @@ "//iam.googleapis.com/locations//workforcePool/pool-id/providers/provider-id", ] -VALID_TOKEN_URLS = [ - "https://sts.googleapis.com", - "https://sts.mtls.googleapis.com", - "https://us-east-1.sts.googleapis.com", - "https://us-east-1.sts.mtls.googleapis.com", - "https://US-EAST-1.sts.googleapis.com", - "https://sts.us-east-1.googleapis.com", - "https://sts.US-WEST-1.googleapis.com", - "https://us-east-1-sts.googleapis.com", - "https://US-WEST-1-sts.googleapis.com", - "https://US-WEST-1-sts.mtls.googleapis.com", - "https://us-west-1-sts.googleapis.com/path?query", - "https://sts-us-east-1.p.googleapis.com", - "https://sts-us-east-1.p.mtls.googleapis.com", -] -INVALID_TOKEN_URLS = [ - "https://iamcredentials.googleapis.com", - "https://mtls.iamcredentials.googleapis.com", - "sts.googleapis.com", - "mtls.sts.googleapis.com", - "mtls.googleapis.com", - "https://", - "http://sts.googleapis.com", - "https://st.s.googleapis.com", - "https://us-eas\t-1.sts.googleapis.com", - "https:/us-east-1.sts.googleapis.com", - "https:/us-east-1.mtls.sts.googleapis.com", - "https://US-WE/ST-1-sts.googleapis.com", - "https://sts-us-east-1.googleapis.com", - "https://sts-US-WEST-1.googleapis.com", - "testhttps://us-east-1.sts.googleapis.com", - "https://us-east-1.sts.googleapis.comevil.com", - "https://us-east-1.us-east-1.sts.googleapis.com", - "https://us-ea.s.t.sts.googleapis.com", - "https://sts.googleapis.comevil.com", - "hhttps://us-east-1.sts.googleapis.com", - "https://us- -1.sts.googleapis.com", - "https://-sts.googleapis.com", - "https://-mtls.googleapis.com", - "https://us-east-1.sts.googleapis.com.evil.com", - "https://sts.pgoogleapis.com", - "https://p.googleapis.com", - "https://sts.p.com", - "https://sts.p.mtls.com", - "http://sts.p.googleapis.com", - "https://xyz-sts.p.googleapis.com", - "https://sts-xyz.123.p.googleapis.com", - "https://sts-xyz.p1.googleapis.com", - "https://sts-xyz.p.foo.com", - "https://sts-xyz.p.foo.googleapis.com", - "https://sts-xyz.mtls.p.foo.googleapis.com", - "https://sts-xyz.p.mtls.foo.googleapis.com", -] -VALID_SERVICE_ACCOUNT_IMPERSONATION_URLS = [ - "https://iamcredentials.googleapis.com", - "https://us-east-1.iamcredentials.googleapis.com", - "https://US-EAST-1.iamcredentials.googleapis.com", - "https://iamcredentials.us-east-1.googleapis.com", - "https://iamcredentials.US-WEST-1.googleapis.com", - "https://us-east-1-iamcredentials.googleapis.com", - "https://US-WEST-1-iamcredentials.googleapis.com", - "https://us-west-1-iamcredentials.googleapis.com/path?query", - "https://iamcredentials-us-east-1.p.googleapis.com", -] -INVALID_SERVICE_ACCOUNT_IMPERSONATION_URLS = [ - "https://sts.googleapis.com", - "iamcredentials.googleapis.com", - "https://", - "http://iamcredentials.googleapis.com", - "https://iamcre.dentials.googleapis.com", - "https://us-eas\t-1.iamcredentials.googleapis.com", - "https:/us-east-1.iamcredentials.googleapis.com", - "https://US-WE/ST-1-iamcredentials.googleapis.com", - "https://iamcredentials-us-east-1.googleapis.com", - "https://iamcredentials-US-WEST-1.googleapis.com", - "testhttps://us-east-1.iamcredentials.googleapis.com", - "https://us-east-1.iamcredentials.googleapis.comevil.com", - "https://us-east-1.us-east-1.iamcredentials.googleapis.com", - "https://us-ea.s.t.iamcredentials.googleapis.com", - "https://iamcredentials.googleapis.comevil.com", - "hhttps://us-east-1.iamcredentials.googleapis.com", - "https://us- -1.iamcredentials.googleapis.com", - "https://-iamcredentials.googleapis.com", - "https://us-east-1.iamcredentials.googleapis.com.evil.com", - "https://iamcredentials.pgoogleapis.com", - "https://p.googleapis.com", - "https://iamcredentials.p.com", - "http://iamcredentials.p.googleapis.com", - "https://xyz-iamcredentials.p.googleapis.com", - "https://iamcredentials-xyz.123.p.googleapis.com", - "https://iamcredentials-xyz.p1.googleapis.com", - "https://iamcredentials-xyz.p.foo.com", - "https://iamcredentials-xyz.p.foo.googleapis.com", -] - class CredentialsImpl(external_account.Credentials): def __init__(self, **kwargs): @@ -350,44 +255,6 @@ def assert_resource_manager_request_kwargs( assert request_kwargs["headers"] == headers assert "body" not in request_kwargs - def test_valid_token_url_shall_pass_validation(self): - valid_urls = VALID_TOKEN_URLS - - for url in valid_urls: - # A valid url shouldn't throw exception and a None value should be returned - external_account.Credentials.validate_token_url(url) - - def test_invalid_token_url_shall_throw_exceptions(self): - invalid_urls = INVALID_TOKEN_URLS - - for url in invalid_urls: - # An invalid url should throw a ValueError exception - with pytest.raises(ValueError) as excinfo: - external_account.Credentials.validate_token_url(url) - - assert excinfo.match("The provided token URL is invalid.") - - def test_valid_service_account_impersonation_url_shall_pass_validation(self): - valid_urls = VALID_SERVICE_ACCOUNT_IMPERSONATION_URLS - - for url in valid_urls: - # A valid url shouldn't throw exception and a None value should be returned - external_account.Credentials.validate_service_account_impersonation_url(url) - - def test_invalid_service_account_impersonate_url_shall_throw_exceptions(self): - invalid_urls = INVALID_SERVICE_ACCOUNT_IMPERSONATION_URLS - - for url in invalid_urls: - # An invalid url should throw a ValueError exception - with pytest.raises(ValueError) as excinfo: - external_account.Credentials.validate_service_account_impersonation_url( - url - ) - - assert excinfo.match( - "The provided service account impersonation URL is invalid." - ) - def test_default_state(self): credentials = self.make_credentials( service_account_impersonation_url=self.SERVICE_ACCOUNT_IMPERSONATION_URL @@ -409,31 +276,6 @@ def test_default_state(self): # Token info url not set yet assert not credentials.token_info_url - def test_invalid_token_url(self): - with pytest.raises(ValueError) as excinfo: - CredentialsImpl( - audience=self.AUDIENCE, - subject_token_type=self.SUBJECT_TOKEN_TYPE, - token_url="https:///v1/token", - credential_source=self.CREDENTIAL_SOURCE, - ) - - assert excinfo.match("The provided token URL is invalid.") - - def test_invalid_service_account_impersonate_url(self): - with pytest.raises(ValueError) as excinfo: - CredentialsImpl( - audience=self.AUDIENCE, - subject_token_type=self.SUBJECT_TOKEN_TYPE, - token_url=self.TOKEN_URL, - credential_source=self.CREDENTIAL_SOURCE, - service_account_impersonation_url=12345, # create an exception by sending to parse url - ) - - assert excinfo.match( - "The provided service account impersonation URL is invalid." - ) - def test_nonworkforce_with_workforce_pool_user_project(self): with pytest.raises(ValueError) as excinfo: CredentialsImpl( diff --git a/packages/google-auth/tests/test_identity_pool.py b/packages/google-auth/tests/test_identity_pool.py index 0b0156eb0a1d..6651f0b5cdba 100644 --- a/packages/google-auth/tests/test_identity_pool.py +++ b/packages/google-auth/tests/test_identity_pool.py @@ -759,16 +759,6 @@ def test_token_info_url_custom(self): assert credentials.token_info_url == url + "/introspect" - def test_token_info_url_bad(self): - for url in INVALID_TOKEN_URLS: - with pytest.raises(ValueError) as excinfo: - self.make_credentials( - credential_source=self.CREDENTIAL_SOURCE_JSON.copy(), - token_info_url=(url + "/introspect"), - ) - - assert excinfo.match(r"The provided token info URL is invalid.") - def test_token_info_url_negative(self): credentials = self.make_credentials( credential_source=self.CREDENTIAL_SOURCE_JSON.copy(), token_info_url=None @@ -785,16 +775,6 @@ def test_token_url_custom(self): assert credentials._token_url == (url + "/token") - def test_token_url_bad(self): - for url in INVALID_TOKEN_URLS: - with pytest.raises(ValueError) as excinfo: - self.make_credentials( - credential_source=self.CREDENTIAL_SOURCE_JSON.copy(), - token_url=(url + "/token"), - ) - - assert excinfo.match(r"The provided token URL is invalid\.") - def test_service_account_impersonation_url_custom(self): for url in VALID_SERVICE_ACCOUNT_IMPERSONATION_URLS: credentials = self.make_credentials( @@ -808,20 +788,6 @@ def test_service_account_impersonation_url_custom(self): url + SERVICE_ACCOUNT_IMPERSONATION_URL_ROUTE ) - def test_service_account_impersonation_url_bad(self): - for url in INVALID_SERVICE_ACCOUNT_IMPERSONATION_URLS: - with pytest.raises(ValueError) as excinfo: - self.make_credentials( - credential_source=self.CREDENTIAL_SOURCE_JSON.copy(), - service_account_impersonation_url=( - url + SERVICE_ACCOUNT_IMPERSONATION_URL_ROUTE - ), - ) - - assert excinfo.match( - r"The provided service account impersonation URL is invalid\." - ) - def test_refresh_text_file_success_without_impersonation_ignore_default_scopes( self, ): diff --git a/packages/google-auth/tests/test_pluggable.py b/packages/google-auth/tests/test_pluggable.py index cd553da832cb..e9b3d9a86dcf 100644 --- a/packages/google-auth/tests/test_pluggable.py +++ b/packages/google-auth/tests/test_pluggable.py @@ -413,16 +413,6 @@ def test_token_info_url_custom(self): assert credentials.token_info_url == url + "/introspect" - def test_token_info_url_bad(self): - for url in INVALID_TOKEN_URLS: - with pytest.raises(ValueError) as excinfo: - self.make_pluggable( - credential_source=self.CREDENTIAL_SOURCE.copy(), - token_info_url=(url + "/introspect"), - ) - - assert excinfo.match(r"The provided token info URL is invalid.") - def test_token_info_url_negative(self): credentials = self.make_pluggable( credential_source=self.CREDENTIAL_SOURCE.copy(), token_info_url=None @@ -439,16 +429,6 @@ def test_token_url_custom(self): assert credentials._token_url == (url + "/token") - def test_token_url_bad(self): - for url in INVALID_TOKEN_URLS: - with pytest.raises(ValueError) as excinfo: - self.make_pluggable( - credential_source=self.CREDENTIAL_SOURCE.copy(), - token_url=(url + "/token"), - ) - - assert excinfo.match(r"The provided token URL is invalid\.") - def test_service_account_impersonation_url_custom(self): for url in VALID_SERVICE_ACCOUNT_IMPERSONATION_URLS: credentials = self.make_pluggable( @@ -462,20 +442,6 @@ def test_service_account_impersonation_url_custom(self): url + SERVICE_ACCOUNT_IMPERSONATION_URL_ROUTE ) - def test_service_account_impersonation_url_bad(self): - for url in INVALID_SERVICE_ACCOUNT_IMPERSONATION_URLS: - with pytest.raises(ValueError) as excinfo: - self.make_pluggable( - credential_source=self.CREDENTIAL_SOURCE.copy(), - service_account_impersonation_url=( - url + SERVICE_ACCOUNT_IMPERSONATION_URL_ROUTE - ), - ) - - assert excinfo.match( - r"The provided service account impersonation URL is invalid\." - ) - @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) def test_retrieve_subject_token_successfully(self, tmpdir): ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE = tmpdir.join( From 9b30d33a56aabbc56902dd613bf73ce03239d675 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Fri, 17 Feb 2023 12:07:51 -0800 Subject: [PATCH 675/966] chore: update sys test cred (#1230) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 6501d80ad3f859aff0dcc7a362a4c1da88f413ea..1e7949e8e6c86ba8bab84169e4d1a2bbe401141b 100644 GIT binary patch literal 10324 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 literal 10324 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 From f942e62e6466408bbdb19fb5a3b028be7b85022a Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Fri, 17 Feb 2023 12:35:44 -0800 Subject: [PATCH 676/966] chore(main): release 2.16.1 (#1215) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 9 +++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index b3c2aee94cab..e97486714e09 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,15 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.16.1](https://github.com/googleapis/google-auth-library-python/compare/v2.16.0...v2.16.1) (2023-02-17) + + +### Bug Fixes + +* Add support for python 3.11 ([#1212](https://github.com/googleapis/google-auth-library-python/issues/1212)) ([1fc95e3](https://github.com/googleapis/google-auth-library-python/commit/1fc95e3c3ecfbceb16c1be28725e8bc9eefe8bb0)) +* Remove 3PI config url validation ([#1220](https://github.com/googleapis/google-auth-library-python/issues/1220)) ([8b95515](https://github.com/googleapis/google-auth-library-python/commit/8b95515718d50b028c43ea9d6a7220489ffb5da0)) +* Update the docs generator interpreter to unblock documentation build ([#1218](https://github.com/googleapis/google-auth-library-python/issues/1218)) ([9d36c2f](https://github.com/googleapis/google-auth-library-python/commit/9d36c2f1f9e1eac8fbff4be504986dff5e7d4da2)) + ## [2.16.0](https://github.com/googleapis/google-auth-library-python/compare/v2.15.0...v2.16.0) (2023-01-09) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 6ab5ecc4cbfb..a982b4bb6eba 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.16.0" +__version__ = "2.16.1" From 785bd4ec294fbe079839fbee7fcee61d0d2a63af Mon Sep 17 00:00:00 2001 From: Carl Lundin <108372512+clundin25@users.noreply.github.com> Date: Thu, 23 Feb 2023 11:44:00 -0800 Subject: [PATCH 677/966] chore: Refresh system test creds. (#1234) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 1e7949e8e6c86ba8bab84169e4d1a2bbe401141b..cd0e37f156e8f7a31d0abd3526f0987835dcc70b 100644 GIT binary patch literal 10324 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 From 0289abb53053d892b922109446967daa0a097be2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C4=9Bj=20Cepl?= Date: Thu, 23 Feb 2023 23:39:25 +0100 Subject: [PATCH 678/966] fix: Do not use hardcoded string 'python', when you mean sys.executable. (#1233) Perhaps fixes #1120 --- packages/google-auth/tests/test__cloud_sdk.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/tests/test__cloud_sdk.py b/packages/google-auth/tests/test__cloud_sdk.py index c05c443201d0..160e1f1a4dc8 100644 --- a/packages/google-auth/tests/test__cloud_sdk.py +++ b/packages/google-auth/tests/test__cloud_sdk.py @@ -16,6 +16,7 @@ import json import os import subprocess +import sys import mock import pytest # type: ignore @@ -73,7 +74,7 @@ def test_get_project_id_call_error(check_output): def test__run_subprocess_ignore_stderr(): command = [ - "python", + sys.executable, "-c", "from __future__ import print_function;" + "import sys;" From 3357336a0072629ea6256164a0328749f8a30fd4 Mon Sep 17 00:00:00 2001 From: wangyutongg <120605768+wangyutongg@users.noreply.github.com> Date: Mon, 27 Feb 2023 19:00:01 -0800 Subject: [PATCH 679/966] fix: Improve ADC related errors and warnings (#1237) * fix: Improve ADC related errors and warnings * Run lint and blacken * Refresh system test credentials --- packages/google-auth/google/auth/_default.py | 21 +++++++----------- .../google-auth/google/auth/_default_async.py | 2 +- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes packages/google-auth/tests/test__default.py | 6 +++-- 4 files changed, 13 insertions(+), 16 deletions(-) diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index 195388c9d8e1..997f2b4e880b 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -48,23 +48,18 @@ ) # Help message when no credentials can be found. -_HELP_MESSAGE = """\ -Could not automatically determine credentials. Please set {env} or \ -explicitly create credentials and re-run the application. For more \ -information, please see \ -https://cloud.google.com/docs/authentication/getting-started -""".format( - env=environment_vars.CREDENTIALS -).strip() +_CLOUD_SDK_MISSING_CREDENTIALS = """\ +Your default credentials were not found. To set up Application Default Credentials, \ +see https://cloud.google.com/docs/authentication/external/set-up-adc for more information.\ +""" # Warning when using Cloud SDK user credentials _CLOUD_SDK_CREDENTIALS_WARNING = """\ Your application has authenticated using end user credentials from Google \ Cloud SDK without a quota project. You might receive a "quota exceeded" \ -or "API not enabled" error. We recommend you rerun \ -`gcloud auth application-default login` and make sure a quota project is \ -added. Or you can use service accounts instead. For more information \ -about service accounts, see https://cloud.google.com/docs/authentication/""" +or "API not enabled" error. See the following page for troubleshooting: \ +https://cloud.google.com/docs/authentication/adc-troubleshooting/user-creds. \ +""" # The subject token type used for AWS external_account credentials. _AWS_SUBJECT_TOKEN_TYPE = "urn:ietf:params:aws:token-type:aws4_request" @@ -650,4 +645,4 @@ def default(scopes=None, request=None, quota_project_id=None, default_scopes=Non ) return credentials, effective_project_id - raise exceptions.DefaultCredentialsError(_HELP_MESSAGE) + raise exceptions.DefaultCredentialsError(_CLOUD_SDK_MISSING_CREDENTIALS) diff --git a/packages/google-auth/google/auth/_default_async.py b/packages/google-auth/google/auth/_default_async.py index 5a41f2a6ea16..93a570c77f95 100644 --- a/packages/google-auth/google/auth/_default_async.py +++ b/packages/google-auth/google/auth/_default_async.py @@ -281,4 +281,4 @@ def default_async(scopes=None, request=None, quota_project_id=None): ) return credentials, effective_project_id - raise exceptions.DefaultCredentialsError(_default._HELP_MESSAGE) + raise exceptions.DefaultCredentialsError(_default._CLOUD_SDK_MISSING_CREDENTIALS) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index cd0e37f156e8f7a31d0abd3526f0987835dcc70b..e7037d4e38871217c64281b284f68fcbf21c3e00 100644 GIT binary patch literal 10324 zcmV-aD67{BB>?tKRTH=iSIlAp!t5I3jx({(MS9Why}yEh&dWU?(o(QqRX7r=Pyl~- z0+unZIJHF#IDkkedvO&#nPMgo&qqMub^WS+edgV8SRmSo1-?t${Soau4;Ry1Ov9Dk z+;JAsa#NmJqrHiUAgdr%ka=3y+aUq9&yB@W7!54gI}1-xEV z0A=a*l!yFNn(Y;Xm_B~9(Sxr5jY5k5*Ygq+={S(2t}YGSQ42qm@OF@*KbJF%1<};3 z!Bg4V8_K9kHJ{HaRb|y`M8kKFu>;Xe^gibiay~Kb=R+I9;bmfTyl9DvKoC*8JM2_a z;eQVrw7V{chL{&&49_GnNNo8(U(HUEL^fY{f?gje_JImhSrkkkmev?m^f*UD%!Db| zmA#S@m!If=5}txF6VtkTi7@M(zswrLQ#7&!9U*6bq*1?M4^0s?Hr3q>{I*xF-a}ne ztlPf|BZbe|Bqk`yVT9F=li|Ya&gXh@f&lq*+o7a4lmdnflJ}^P} zJ1WdRXFsN_N?^x*F~Fv&SFT3$`n(SmAogq*vQl(O3*k;$i^96d*{v#!8)YUEo+x<} z+<{>)P&M4GQhqAb63nCtfC~EIbAKd}(jF}A!aK4ry^sxPEDSIEmMSSM)B%c*X!?_g zzDhB{^Zc9Q2 zk|C;gNsNa00B^Wq#GB@i0mG0S-yUHc1oTimo>~Ve2l0V)(i(rA(WfE(WWfJm`3HH6 z8x!#TFHJ0NPDGIVuyf}0H{;Lb{$2CRe=|Fa=b9BAX)ufMI!@L!8_X-MJpBqxC+}XG z$eR~E=MXWL9;(t-Ju%t!4-d1?q8f`$o7+8K!NDX&!1YP><<7M5)dqLoAqFrj~Q!drZ>;w7qqxpSS_2t$N@6T z^+}KhDndnwjoHgm>~XPpbq#|sbO{+hgG<6paiH<6X*-5?qtY1t>l>o(+V1^!Tx0P1 zy?{`Mn2v%Dsj?t&o?H4S>TJWL=XO;FPV zTp9DrgSq?x$#%DI0dZqq(&dGJ9`h5YgeQF`6C5Zc$-h3p>8dx{vx&aYY${dg%2Z67 z$xWyHH6<1qRu|(!xK1l0oO^}2QMhKWEM*iJ!V8$Vz6Gn+&6YKObvw$fXhZfIN`IW@ zfvaezn0R8+4lg+AyvCrofJrXYv<73vlkZO}7W2e;b7&WZ1W5^Y<;3)mvl&REoreA-HuDLp&Jf5Vn|E=_LO4duISMYKf4JwmD z8sq(*BbG3ON1oMrEa%29cIW7)Ev8Q4t$krsD$S}vrrXI;bb*O=bP za0N^jcSuL?ee`qXU}b#+M9Dp3)TXk8o6O+z+uWN2+v14GiL7NnW0UR8>_})@8-W5aGX_f2{ z)!{}IuqZo=QBsGG7 z!lx6IrH6I|-08Ac&tW-;qTRx}6-rxT_)t6T=ZntKJE0paAmb6dJPI7&!-M0+kwY4w zmgM(jyIn}{3hi$U*_Z--ffL0~jtbu*>b|FsiN#4?K@H9{)f$HI6VsI5QQody4k+dG z$BS==Gf>Yeb`|AjBHKdX{?qk@2XgkF;!js7r5eML?-C3rb;2v31IP;=|Eex&@jv|G zAc{SC{_xqZlJCy!?Lxnd#tAs*Ad_$|2^6J%aHAvaU4x07eph@iU= z%*H zH&(%M_()Q~XSm+v`Nwr>DuYe#_Az2QEz~QyE0D)-_dGi`=4`sjHH}DK$-=7Scv>hH zHVoEu=PMfu3-4j9pGU8^TyWjHt*hdshSaFui`tmq^h3-lb1bL_zFL{S9ioR6s##lD&cjH ztrb^;aO@C1)nW=@khYYGDI>;W^xSY8M3(}4ap9X9^B%mIVECxz3=;y~x53LNXYH&# zM|;xGz4>GCKZk0fvw?w`x!ekf{%rDh9iZZ=W`wzo- zE1&raYrWB+ezrQlDS6tE^ZmhY%R_HgUHLG05`v}frp@NB?(1rDv(@NU`<^0#Q5Wk} zNkeHDy^6y?*5$Y1(ZyFfng!x54r+z=Ef}W$msJ_Z6lUFOw{Q?t+S<_&>YK~iEe^@{1NDe<*tF zJ;I9^R9i4Z@;(ddV>?0Ng+dr%a+vo2mwTRUX!fQ)*6J3ne>F5I#y#c(Kf|XRj9@NgL-j2kGEAF!usKitG zc1<$brCE7sT}#-h>I= z*%c{{LSR8&^GPoWq#q3pu#}?K%L|`cuTA}*mxZ#_idF&nubJ{W?MVYLjKljJgb+eE z6CFkAu2e+_L4<6y1*#8UVf=PHm*oO239BJvF)~9#MQi~eWQxXqL7yZ;An~JFoI4{l zf!@k$#t`-d+~q``C&Cb0lc+{p`r-N6Hi>I~TW*Y|S@S`=dkqrY>1X|nH@|yQ+P7Ko zFfxZhqnP%N`EA z=|UeXiOaF%Oh7S1Q~CvdQy!ttgd;~fLFCB3oom3))+8(%->8+!CHZH!Qb+)6QGBcQSh`0rRQ|{PTdx>U6b#U;LK|!g=4T(rH>u=%rW+B6#w+QRk)XUyfLVhjLmR zvS&_2pVzn9F-{xdy!Yl$3@KT7tU<((IcV||9~hhykeqwi~Q7(M!mgv`uz$&c=T#9AXza>p+otj zBayIjKd$`Mu#9BbSVM}#shkK@re+>i{+)awR}wqUjj^4qV`5>o(?1~|rS9k|rda{~ zWGNG&=UKza)WcFeaWx8-?IzQ}$mw4miQGpM>Y1ESf{=%EeQw)hSYHrB2=KW(;80E- zyoZxzm5&l~gRN5HqyKx^$(Pb*+9%r6bvVb85c;NP;^2DUPO$GS#CL81$2>0mJf4a% zd?1z%wn5|>Hz;=yD&S=nIvS`she&JM&5G8s2=VQ!F_-@Es+of6kd{JXIf|YYvMk|Z zn7NJK>P?I6R}RuoVZI16fW$)2&ka`ZNx~ShE?l0(bW-#Eis@8~NU+Q>p_k5Vf&(`* zZ1vGTpjInuVzGGSMb!5csp*Hi}jN~(}-@Qa^@@^}vq9`Muk0Al=y832W^kIP4e2?@$XMCz7z#8nGg3^U&B;Evemc>5j?h zIY{`xrS{AM+N*;Oe&rdkjf1&}0L$CTf~CV;`ar3YrLTx<#|(0!{qr{ah-llZn~xzD zT&nNJ-@C_$)H+RmflDz-0v;a*Cy;%G>;vf;T;~>UpGq#DbD!UhZe)&<&JdiPv+wS- zYVRuc0x9JnGh0Qq3!K*K_Dl+7Sa5wCK$Q0lgBx{r9Nje#jN=o;&hk$dO^)sidhz85 zAes~Tkv~hhG)Hb^&`rn%b6aZ-j$csUt!pi!fRJxjTnP=elI7?j5HVgH+I`)ZD?m_L zH1N?(3cK$*-hVnuS?=+eS9ve|j@U3NPUSn@jraUnp%d55yfr3O_2r-7jsE}_k@P15 zo#~eNo_SJY{a^`VMF?dZB<5A_Oq{niujbO(K=Jtsg0?KTYy_s>&0mdrJx8tn|7DeF z<`v%A9i~)A8L1Q|2J}Agk}6d%wnnJmqa9$8%vE}XgG~SuF5+1$rGgj$h>oQ=j3Cr1 z^Z!tCUryVQHTB8TL!ANXmA50+T_=A@92a@}PZK2Z=!|=*b@5bsAv==1s;B&VqC#^* z_m+r@*W6oX9!QHW*^7M(JHDCY*?SFZK42&qyeZVcCkZS;d|txgz=SJ6OQ7EoE=YuO zf3=^cnD&u@`KjZUrw6S;uijp0>-_vv5@I5ZF<+wRZsKX4cxC}C4!o|Ov*By(bas=6i6Ev~Ol$~Ko#OA?}k zC%ly>DBBgoGvbjCw2>1_0->xF=is%lOL}H-x9LE&TZ;DyK((3$PSDpx4VFBKCVfs(Z_KlBlQ3kg7*+oU&uTZCE-y07$c2G|7k@{6 z)qm!|!pR@RbhEN@KD_X71=USYI9T+v)qF4?KGA56)fAk9h0C5bxm}={uC^=NXf-`> zbL^~xsrJBQ)VASg*f>zQTbb#e4hOBATjCgFp0L8M<1tAoG*5%;UBa-}(AOxx=Kv@~ z1<$>5K2dF4NjWpkW9IPX(o)nUrv_DmyFU+tSS5khf$w6U+s0gjq7buc_ij%^l-4MT z0=Y>nH%3`t+v4hhR~mvQ&0j%x{M&9M1wVX-{Zk`ktfJ#r5|sY;a1{BO)|@>2(fvnH z)Rs?w!Liwv+}a#JaKV;~8Kn_*r?1eDf4zuK3U+7@<_0@aD z+5|Bi;#Xj7_u~a|9rU7}w9+czF!j{rz=`}oKiUSbfQ zP7e!pCL9>+iiB1|Q0Y1Rw*Kw2dHdHRW!v_4X-u8W<19cn8=I6ld|(L;+%z~IpDSz3 za@33O#BuSi;t=mw!=#=C2mTC(B$B2ynEiqdV!@9QlKeeg4VGLyEVOuEGW6IWn}zjr zy3x*@^ONuACb`2)#sQ{oZp_5ao=cWM4X&4@-s|uf(BTuP4?j1W4rkk#RlT0mJymsS z9MgZ}%ukw(FaFVchmXa`$XsOm7x!c{-M^Q=8YtYHeRRdC+i4#-kbEGvj&qqAguDE>B-CS~zxBcO(ut?cCe zYQq-3+j$(=qNc$ry0$NJ7KoqAgpb z(pCBB+E^Dum$E0FrhyLZN0cugx4Fjzhfa>{T#&0mCQn$(^kR%Kw;|o)HC28)9pB4N=Gn` zg>%M@3u45+9>cN$;+7|d6&QNeRuvz>q0br4Z0ndHCf~~xTA7RSH>36%%f+w&F_IW5 z=}@t8+j$U}O7a{T@FKGlQZ8kI^>rf2Xo_!|tdo28qnTBS``m7KIg8f5U!7BZYLQf| z)JY;R!|Zpfo&(P-v5l* zG{t3d;;@kAiCe9A3*76a@#O#6Rad~?Sm5>nODc<0IB`C|y(<|)z1|*ne+5Oiap|ty zmM85}!4rmAuJUGe)Eb0=b1Of{)`w;N__=JK&UFMeZDEj$%G-iV{ZcG=!ENm&B^dP{V{^2Ma&a>}bFa5h8x@u!Y9Y0Il6Y~JN=3jm#upEJeUE#R;c zr%A1l(Wk;IlT?}DNy;GvJ4X=+9u_rFitj~tnA+m^-a8M#?Ptu$Ji3M6LJNH%wm7Dw zdzcCQ((4@qqCJMk`{&yDs5gFN^;L;j@ZTO=_&tzJot3n{+(>}tj0lKrBI4GX=KbC5%o>%G{lp+`8y3w&fbsI<7H zC<}hE24UQ(QGE1WDsX4`K{(=_S9ec1E{Bx?vV>z*!gt_Zxvgt7}f@DLpSx90^}!~ zDWD$fNWM-R+h%xMLuW&GB{jCw-~=xRnpNd8!!orcOFT7?N%%*HR9IDzU22|UF8($_ z%{(4D4O$irN2i#f(A#_Y}BKX zbG>YTIXCH$&>q`m98R#4=O!5`9wykmMAf~ghhsN3PF<}o>Gk~dcKDKP9}?0QZ1TLA zoN6vO@lv^XXs&5Ys%XXoJr>Z!Lv?6INMipw2S#qnSd2SuGA4Z_)VsFDy@ zeojU`PLmu`tw9&Ep@R8Z|4>`UHzku-QqVK?#2~hRpwiXzwcI;>A=I73Q<#g0%B{DG zHJj#h64HJzfJI3oHfj3>CN>(5DFishWUpI*dDk`Go+G&bl*VS^H)#O1pNr`quPwK8 zFUaelMav!U{R|O>r<<*!Qtmtkz?CBhuoKq)3SQh%WkG^*R)iK8=miPNKLO2b5xpde zCA_B)UE&m=3pXTtPHYC>HbNk%&)FbxjDkO#rzrM7Cj&f^KO*u0n;(tFX8iYT4bwE2 zeS1VZ5ZDUFLCkYz`;FGL=V$O3;L4;i&X+IP{zJ@W(&4Ku#6qNzVc;sb^>G_YZTE{7 zCdZA4{3-9HEsjWX7KaY-t#XP5!>U=#YV)lnV*i7Q!;`~yEZhq1#u!q6bk=&I_U7rk zj&~i>NGjNAedizFXOE=M%P*_}!-)FLRp8X{5YVMerGaa$t}ngc8W!ov)z&i^`5-%S zMgsVwir>1@uIzWMTbXbZzK`O@I9d`gD{x2^{R^^Em+R<%bV;e}=~CRoHVqF$^H|zd zFu`}L-9GdLNKw}-5FOJLh1w291Qm_!JB8EbraisR?S&+iFWC>6Q(nrSv*6ty3y zxvYfPEGI^Aq;ck=xnAIj`+jn*W|#=Xs|OsEXBz_En7{_ z4^?m^+yQ>k&||JES!j3(x2LSXCF?KY6^mB&MvqnjHRT#+5Wo5Vo6oQNO4+8OuKQJG z?bio^Whf3B#>Sf4vZ+X_FMf?!@T@k5I{+pl53iz<#b-(eVU+Xko#u@sU3oxI{iBa# zScv6tfq#`Lee2wobksa7^Sprw+%!V=geow*?c^c0$aOQTl0&t$KES$X)psLqT{Azy zl&On^li(unk(6P08$+oHFhW;kO8Mk8Xl6kf&5N~Jal+#`pm*6Aw)I-tW6`qct)oV5 z?<_4Tu4t1ZqJ%_y$sy=t1U-F9D z2^4YNdri}hxpdz526(+)24l1i^6e_6>qqYkuR5^KwVv~s>K?1WAb?r1pZTHtx4zeG z-kg6GA?S_G_4LnM6gLdD8jK+4L^_y0&jt~fT}yiX_w{a~tbS9!kvh2i^(gA+T z-)7yzf`XsGC4?mg=WO7>cHu@k5^dIFcf8^QcUy8u1OMF1&p)kYuAZCPqNYmw+>BF{ zESya{cwGY*4uh-NH_~Ku!^kI0kLIb()Cqka`3z)%j*8as$}iPf{oO=;n%ua#&>+=h zI_!$_!0>*xDPXS8OYG0ksMazfahGHhO(5#fL6MWI-3)tiEl}i4ZfQyEe+ zVi7Q<-YOxYTgsX3uVO-=kjW!LTX?lFNfH(X@IEvXMRXB2kTq6bI$FUpg6R!UK< z>Y98Zpoak->-18>a0~sw30Z2i(a zuTyB;yjK!I|mlR*Y@jsO%Out>6kOMB_>%Cuj_Om>9&h`4K1jA8hq{$sbv-3XGf)!&=^ptq*;hSH!R(ipFRRr3pzumnn{7T6pS*Yz)P zyJJ)6a88U(J(r5C3Y@{>>;N<7UXWca6cG{G zY(+6w@l}9+5 zHbe}R{N2uYQ|B!Tp*VdWq8!2ZG`oymbT?9_n^hqZO-$(Wm8Ck$;a^~Ctw~f-o^)iZ zm2J$r)Ui;VGOjt^5cVA5i?uScFoW(rH9D#xEVfJ*2Su(?h7O9AQ-7P3N>E-_AZ22_ z?rub6gO8#Bo0ab;G=e0_1kSfY3ZguU4Ueq8FSs-Il_irkwW;P-R~u*vd`+<9B9hO& z0@c2_o2Q_JA`(?Q6c0-7PpdgD-hY`+n9LNm%W>InZu{M}7FyqjNBr$$^iP<;(+fnc zwkx_W>MXjr*=@{IG%uo7AKkv(l?;q=`~fue5*NT$5J@_N7dCOIhGW=77+IX)h3S99 zCG;Ga*gWrU)7}wyN=0RA`%K|bB}BE$2vz6@x(F+`t@}t&C}IL;P4+8_W>Jn|N^2S> zw%Y~#;|_XTxLyL&!wUX28M7D8na5%mm{Q^4v^Zt?9Qf#1Z>r+}tnv8QROlqqJ|DV08-JcoYv9ME@vM5xqEtlqm>k6_n%1kHs%;kJ9WUdt9K^FKDUoe^n~hv< zvVsl#WC*~AcSjQ)F*9r4%{0ic5D!^O59x^a>}+`th)?1kwnY=EZE5K8x3pzjnRAIZ z@DQvJfPD2_11$g_3_iH^WSR2sc7y~x-K^^=r1p3!H~ub&Ch!&L&$AGOCi@0M95p~e zOlMxem4Ho!@El)aRz(n(dyk*43c{QAwP^H!bIZSS}t0L6bHq(Q&Q}*uEvQE4cx7-B?JIY4?o&z zTGDTit!)eUtFQLmS=X7#4o~F1%&VWAoLELrfxFmHtEKR#2UK8~E)#asX-^hTa^s>F mufg)j*pST+-ds;}X}=e1lDpOo-;$RYAaNGx#hj^;u{gkI+EC^I literal 10324 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 Date: Tue, 28 Feb 2023 06:05:56 -0500 Subject: [PATCH 680/966] chore(python): upgrade gcp-releasetool in .kokoro [autoapprove] (#1240) Source-Link: https://github.com/googleapis/synthtool/commit/5f2a6089f73abf06238fe4310f6a14d6f6d1eed3 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:8555f0e37e6261408f792bfd6635102d2da5ad73f8f09bcb24f25e6afb5fac97 Co-authored-by: Owl Bot Co-authored-by: Anthonios Partheniou --- packages/google-auth/.github/.OwlBot.lock.yaml | 2 +- packages/google-auth/.kokoro/requirements.in | 2 +- packages/google-auth/.kokoro/requirements.txt | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/google-auth/.github/.OwlBot.lock.yaml b/packages/google-auth/.github/.OwlBot.lock.yaml index 894fb6bc9b47..5fc5daa31783 100644 --- a/packages/google-auth/.github/.OwlBot.lock.yaml +++ b/packages/google-auth/.github/.OwlBot.lock.yaml @@ -13,4 +13,4 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:f62c53736eccb0c4934a3ea9316e0d57696bb49c1a7c86c726e9bb8a2f87dadf + digest: sha256:8555f0e37e6261408f792bfd6635102d2da5ad73f8f09bcb24f25e6afb5fac97 diff --git a/packages/google-auth/.kokoro/requirements.in b/packages/google-auth/.kokoro/requirements.in index cbd7e77f44db..882178ce6001 100644 --- a/packages/google-auth/.kokoro/requirements.in +++ b/packages/google-auth/.kokoro/requirements.in @@ -1,5 +1,5 @@ gcp-docuploader -gcp-releasetool +gcp-releasetool>=1.10.5 # required for compatibility with cryptography>=39.x importlib-metadata typing-extensions twine diff --git a/packages/google-auth/.kokoro/requirements.txt b/packages/google-auth/.kokoro/requirements.txt index 096e4800a9ac..fa99c12908f0 100644 --- a/packages/google-auth/.kokoro/requirements.txt +++ b/packages/google-auth/.kokoro/requirements.txt @@ -154,9 +154,9 @@ gcp-docuploader==0.6.4 \ --hash=sha256:01486419e24633af78fd0167db74a2763974765ee8078ca6eb6964d0ebd388af \ --hash=sha256:70861190c123d907b3b067da896265ead2eeb9263969d6955c9e0bb091b5ccbf # via -r requirements.in -gcp-releasetool==1.10.0 \ - --hash=sha256:72a38ca91b59c24f7e699e9227c90cbe4dd71b789383cb0164b088abae294c83 \ - --hash=sha256:8c7c99320208383d4bb2b808c6880eb7a81424afe7cdba3c8d84b25f4f0e097d +gcp-releasetool==1.10.5 \ + --hash=sha256:174b7b102d704b254f2a26a3eda2c684fd3543320ec239baf771542a2e58e109 \ + --hash=sha256:e29d29927fe2ca493105a82958c6873bb2b90d503acac56be2c229e74de0eec9 # via -r requirements.in google-api-core==2.10.2 \ --hash=sha256:10c06f7739fe57781f87523375e8e1a3a4674bf6392cd6131a3222182b971320 \ From 633a84cb99221e9e370f865f7260d3f8475898f0 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Wed, 1 Mar 2023 12:05:50 -0800 Subject: [PATCH 681/966] fix: don't retry if error or error_description is not string (#1241) * fix: don't retry if error or error_description is not string * chore: fix flaky sample test * chore: update sys test cred --- packages/google-auth/google/oauth2/_client.py | 5 +++++ .../snippets/verify_google_idtoken.py | 2 +- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../google-auth/tests/oauth2/test__client.py | 9 ++++++++- 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/google/oauth2/_client.py b/packages/google-auth/google/oauth2/_client.py index c2eb6443f820..428993646af9 100644 --- a/packages/google-auth/google/oauth2/_client.py +++ b/packages/google-auth/google/oauth2/_client.py @@ -90,6 +90,11 @@ def _can_retry(status_code, response_data): error_desc = response_data.get("error_description") or "" error_code = response_data.get("error") or "" + if not isinstance(error_code, six.string_types) or not isinstance( + error_desc, six.string_types + ): + return False + # Per Oauth 2.0 RFC https://www.rfc-editor.org/rfc/rfc6749.html#section-4.1.2.1 # This is needed because a redirect will not return a 500 status code. retryable_error_descriptions = { diff --git a/packages/google-auth/samples/cloud-client/snippets/verify_google_idtoken.py b/packages/google-auth/samples/cloud-client/snippets/verify_google_idtoken.py index 4d2216efdc1d..35b88c99ecd4 100644 --- a/packages/google-auth/samples/cloud-client/snippets/verify_google_idtoken.py +++ b/packages/google-auth/samples/cloud-client/snippets/verify_google_idtoken.py @@ -48,7 +48,7 @@ def verify_google_idtoken(idtoken: str, audience="iap.googleapis.com", request = google.auth.transport.requests.Request() # Set the parameters and verify the token. # Setting "certs_url" is optional. When verifying a Google ID token, this is set by default. - result = id_token.verify_token(idtoken, request, audience) + result = id_token.verify_token(idtoken, request, audience, clock_skew_in_seconds=10) # Verify that the token contains subject and email claims. # Get the User id. diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index e7037d4e38871217c64281b284f68fcbf21c3e00..522223f61ca3696db2f340bbc08c7b0909105d50 100644 GIT binary patch literal 10324 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 mjFhreSB+?MujahT90N$2$(rsMys6wr1X=y{F4 literal 10324 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 diff --git a/packages/google-auth/tests/oauth2/test__client.py b/packages/google-auth/tests/oauth2/test__client.py index b322eefed8af..ff3096057721 100644 --- a/packages/google-auth/tests/oauth2/test__client.py +++ b/packages/google-auth/tests/oauth2/test__client.py @@ -94,7 +94,14 @@ def test__can_retry_message(response_data): assert _client._can_retry(http_client.OK, response_data) -@pytest.mark.parametrize("response_data", [{"error": "invalid_scope"}]) +@pytest.mark.parametrize( + "response_data", + [ + {"error": "invalid_scope"}, + {"error": {"foo": "bar"}}, + {"error_description": {"foo", "bar"}}, + ], +) def test__can_retry_no_retry_message(response_data): assert not _client._can_retry(http_client.OK, response_data) From fc23d9e67e54b42861768f316f3e5a3e1842d12b Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Thu, 2 Mar 2023 10:12:24 -0800 Subject: [PATCH 682/966] fix: call gcloud config get project to get project for user cred (#1243) --- .../google-auth/google/auth/_cloud_sdk.py | 22 ++++++++----------- .../tests/data/cloud_sdk_config.json | 19 ---------------- packages/google-auth/tests/test__cloud_sdk.py | 13 ++--------- 3 files changed, 11 insertions(+), 43 deletions(-) delete mode 100644 packages/google-auth/tests/data/cloud_sdk_config.json diff --git a/packages/google-auth/google/auth/_cloud_sdk.py b/packages/google-auth/google/auth/_cloud_sdk.py index 40e6aec13a8e..36c5b015802f 100644 --- a/packages/google-auth/google/auth/_cloud_sdk.py +++ b/packages/google-auth/google/auth/_cloud_sdk.py @@ -14,12 +14,12 @@ """Helpers for reading the Google Cloud SDK's configuration.""" -import json import os import subprocess import six +from google.auth import _helpers from google.auth import environment_vars from google.auth import exceptions @@ -35,7 +35,7 @@ _CLOUD_SDK_POSIX_COMMAND = "gcloud" _CLOUD_SDK_WINDOWS_COMMAND = "gcloud.cmd" # The command to get the Cloud SDK configuration -_CLOUD_SDK_CONFIG_COMMAND = ("config", "config-helper", "--format", "json") +_CLOUD_SDK_CONFIG_GET_PROJECT_COMMAND = ("config", "get", "project") # The command to get google user access token _CLOUD_SDK_USER_ACCESS_TOKEN_COMMAND = ("auth", "print-access-token") # Cloud SDK's application-default client ID @@ -105,18 +105,14 @@ def get_project_id(): try: # Ignore the stderr coming from gcloud, so it won't be mixed into the output. # https://github.com/googleapis/google-auth-library-python/issues/673 - output = _run_subprocess_ignore_stderr((command,) + _CLOUD_SDK_CONFIG_COMMAND) - except (subprocess.CalledProcessError, OSError, IOError): - return None - - try: - configuration = json.loads(output.decode("utf-8")) - except ValueError: - return None + project = _run_subprocess_ignore_stderr( + (command,) + _CLOUD_SDK_CONFIG_GET_PROJECT_COMMAND + ) - try: - return configuration["configuration"]["properties"]["core"]["project"] - except KeyError: + # Turn bytes into a string and remove "\n" + project = _helpers.from_bytes(project).strip() + return project if project else None + except (subprocess.CalledProcessError, OSError, IOError): return None diff --git a/packages/google-auth/tests/data/cloud_sdk_config.json b/packages/google-auth/tests/data/cloud_sdk_config.json deleted file mode 100644 index a5fe4a9a475e..000000000000 --- a/packages/google-auth/tests/data/cloud_sdk_config.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "configuration": { - "active_configuration": "default", - "properties": { - "core": { - "account": "user@example.com", - "disable_usage_reporting": "False", - "project": "example-project" - } - } - }, - "credential": { - "access_token": "don't use me", - "token_expiry": "2017-03-23T23:09:49Z" - }, - "sentinels": { - "config_sentinel": "/Users/example/.config/gcloud/config_sentinel" - } -} diff --git a/packages/google-auth/tests/test__cloud_sdk.py b/packages/google-auth/tests/test__cloud_sdk.py index 160e1f1a4dc8..e45c65bd96e1 100644 --- a/packages/google-auth/tests/test__cloud_sdk.py +++ b/packages/google-auth/tests/test__cloud_sdk.py @@ -37,17 +37,10 @@ with io.open(SERVICE_ACCOUNT_FILE, "rb") as fh: SERVICE_ACCOUNT_FILE_DATA = json.load(fh) -with io.open(os.path.join(DATA_DIR, "cloud_sdk_config.json"), "rb") as fh: - CLOUD_SDK_CONFIG_FILE_DATA = fh.read() - @pytest.mark.parametrize( "data, expected_project_id", - [ - (CLOUD_SDK_CONFIG_FILE_DATA, "example-project"), - (b"I am some bad json", None), - (b"{}", None), - ], + [(b"example-project\n", "example-project"), (b"", None)], ) def test_get_project_id(data, expected_project_id): check_output_patch = mock.patch( @@ -94,9 +87,7 @@ def test__run_subprocess_ignore_stderr(): @mock.patch("os.name", new="nt") def test_get_project_id_windows(): check_output_patch = mock.patch( - "subprocess.check_output", - autospec=True, - return_value=CLOUD_SDK_CONFIG_FILE_DATA, + "subprocess.check_output", autospec=True, return_value=b"example-project\n" ) with check_output_patch as check_output: From 6903f96b60cbcb2673061a4f6073923937c15778 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 2 Mar 2023 10:39:40 -0800 Subject: [PATCH 683/966] chore(main): release 2.16.2 (#1236) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 10 ++++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index e97486714e09..171fc3e7aa5a 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,16 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.16.2](https://github.com/googleapis/google-auth-library-python/compare/v2.16.1...v2.16.2) (2023-03-02) + + +### Bug Fixes + +* Call gcloud config get project to get project for user cred ([#1243](https://github.com/googleapis/google-auth-library-python/issues/1243)) ([c078a13](https://github.com/googleapis/google-auth-library-python/commit/c078a13f3d7a6bda69efab11f11c1120e20137ef)) +* Do not use hardcoded string 'python', when you mean sys.executable. ([#1233](https://github.com/googleapis/google-auth-library-python/issues/1233)) ([91ac8e6](https://github.com/googleapis/google-auth-library-python/commit/91ac8e66396fd2335f2be6e7b40dc5c4f6e47bc2)) +* Don't retry if error or error_description is not string ([#1241](https://github.com/googleapis/google-auth-library-python/issues/1241)) ([e2d263a](https://github.com/googleapis/google-auth-library-python/commit/e2d263a2e79a35e8cc90aa338780d07c3313dc76)) +* Improve ADC related errors and warnings ([#1237](https://github.com/googleapis/google-auth-library-python/issues/1237)) ([2dfa213](https://github.com/googleapis/google-auth-library-python/commit/2dfa21371185340404d5d739723a8cd434886e02)) + ## [2.16.1](https://github.com/googleapis/google-auth-library-python/compare/v2.16.0...v2.16.1) (2023-02-17) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index a982b4bb6eba..0b9b05843ed7 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.16.1" +__version__ = "2.16.2" From 3a3ea3809caea8d1ac4f4bd5bece84964ed7f154 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Fri, 10 Mar 2023 13:58:05 -0800 Subject: [PATCH 684/966] chore: update system test cred (#1245) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10323 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 522223f61ca3696db2f340bbc08c7b0909105d50..e51e690cca1842b5370d32c90b37dbfd55d15b20 100644 GIT binary patch literal 10323 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 -{ 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 literal 10324 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 mjFhreSB+?MujahT90N$2$(rsMys6wr1X=y{F4 From 69e905870d182367019f29e3e1adc656ded454ea Mon Sep 17 00:00:00 2001 From: Jin Date: Thu, 23 Mar 2023 16:43:29 -0700 Subject: [PATCH 685/966] chore: update token (#1256) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10323 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index e51e690cca1842b5370d32c90b37dbfd55d15b20..5cff634c1fb5fed6fafa4bc34d32409a98fed1f3 100644 GIT binary patch literal 10324 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 literal 10323 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 -{ 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 From 01ff4cc13cd69c0b5d3efd35f3f377c0f34188c7 Mon Sep 17 00:00:00 2001 From: cpisunyer <124388466+cpisunyer@users.noreply.github.com> Date: Thu, 23 Mar 2023 20:09:53 -0700 Subject: [PATCH 686/966] fix: Read both applicationId and relyingPartyId. (#1246) If they are the same, use applicationId. If they are different, first use relyingPartyId and retry with applicationId. This allows security keys enrolled with both u2f and WebAuthN to work with the Reauth API. Co-authored-by: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> --- .../google-auth/google/oauth2/challenges.py | 60 +++++++++++++------ .../tests/oauth2/test_challenges.py | 60 ++++++++++++++++++- 2 files changed, 100 insertions(+), 20 deletions(-) diff --git a/packages/google-auth/google/oauth2/challenges.py b/packages/google-auth/google/oauth2/challenges.py index bb523e6cafe3..4eb665b8a7a9 100644 --- a/packages/google-auth/google/oauth2/challenges.py +++ b/packages/google-auth/google/oauth2/challenges.py @@ -124,7 +124,16 @@ def obtain_challenge_input(self, metadata): ) sk = metadata["securityKey"] challenges = sk["challenges"] - app_id = sk["applicationId"] + # Read both 'applicationId' and 'relyingPartyId', if they are the same, use + # applicationId, if they are different, use relyingPartyId first and retry + # with applicationId + application_id = sk["applicationId"] + relying_party_id = sk["relyingPartyId"] + + if application_id != relying_party_id: + application_parameters = [relying_party_id, application_id] + else: + application_parameters = [application_id] challenge_data = [] for c in challenges: @@ -134,24 +143,37 @@ def obtain_challenge_input(self, metadata): challenge = base64.urlsafe_b64decode(challenge) challenge_data.append({"key": key, "challenge": challenge}) - try: - api = pyu2f.convenience.authenticator.CreateCompositeAuthenticator( - REAUTH_ORIGIN - ) - response = api.Authenticate( - app_id, challenge_data, print_callback=sys.stderr.write - ) - return {"securityKey": response} - except pyu2f.errors.U2FError as e: - if e.code == pyu2f.errors.U2FError.DEVICE_INELIGIBLE: - sys.stderr.write("Ineligible security key.\n") - elif e.code == pyu2f.errors.U2FError.TIMEOUT: - sys.stderr.write("Timed out while waiting for security key touch.\n") - else: - raise e - except pyu2f.errors.NoDeviceFoundError: - sys.stderr.write("No security key found.\n") - return None + # Track number of tries to suppress error message until all application_parameters + # are tried. + tries = 0 + for app_id in application_parameters: + try: + tries += 1 + api = pyu2f.convenience.authenticator.CreateCompositeAuthenticator( + REAUTH_ORIGIN + ) + response = api.Authenticate( + app_id, challenge_data, print_callback=sys.stderr.write + ) + return {"securityKey": response} + except pyu2f.errors.U2FError as e: + if e.code == pyu2f.errors.U2FError.DEVICE_INELIGIBLE: + # Only show error if all app_ids have been tried + if tries == len(application_parameters): + sys.stderr.write("Ineligible security key.\n") + return None + continue + if e.code == pyu2f.errors.U2FError.TIMEOUT: + sys.stderr.write( + "Timed out while waiting for security key touch.\n" + ) + else: + raise e + except pyu2f.errors.PluginError: + continue + except pyu2f.errors.NoDeviceFoundError: + sys.stderr.write("No security key found.\n") + return None class SamlChallenge(ReauthChallenge): diff --git a/packages/google-auth/tests/oauth2/test_challenges.py b/packages/google-auth/tests/oauth2/test_challenges.py index 9e35d88afecf..a06f5528377b 100644 --- a/packages/google-auth/tests/oauth2/test_challenges.py +++ b/packages/google-auth/tests/oauth2/test_challenges.py @@ -45,13 +45,15 @@ def test_security_key(): ).decode("ascii"), } ], + "relyingPartyId": "security_key_application_id", }, } mock_key = mock.Mock() challenge = challenges.SecurityKeyChallenge() - # Test the case that security key challenge is passed. + # Test the case that security key challenge is passed with applicationId and + # relyingPartyId the same. with mock.patch("pyu2f.model.RegisteredKey", return_value=mock_key): with mock.patch( "pyu2f.convenience.authenticator.CompositeAuthenticator.Authenticate" @@ -68,6 +70,56 @@ def test_security_key(): print_callback=sys.stderr.write, ) + # Test the case that security key challenge is passed with applicationId and + # relyingPartyId different, first call works. + metadata["securityKey"]["relyingPartyId"] = "security_key_relying_party_id" + sys.stderr.write("metadata=" + str(metadata) + "\n") + with mock.patch("pyu2f.model.RegisteredKey", return_value=mock_key): + with mock.patch( + "pyu2f.convenience.authenticator.CompositeAuthenticator.Authenticate" + ) as mock_authenticate: + mock_authenticate.return_value = "security key response" + assert challenge.name == "SECURITY_KEY" + assert challenge.is_locally_eligible + assert challenge.obtain_challenge_input(metadata) == { + "securityKey": "security key response" + } + mock_authenticate.assert_called_with( + "security_key_relying_party_id", + [{"key": mock_key, "challenge": b"some_challenge"}], + print_callback=sys.stderr.write, + ) + + # Test the case that security key challenge is passed with applicationId and + # relyingPartyId different, first call fails, requires retry. + metadata["securityKey"]["relyingPartyId"] = "security_key_relying_party_id" + with mock.patch("pyu2f.model.RegisteredKey", return_value=mock_key): + with mock.patch( + "pyu2f.convenience.authenticator.CompositeAuthenticator.Authenticate" + ) as mock_authenticate: + assert challenge.name == "SECURITY_KEY" + assert challenge.is_locally_eligible + mock_authenticate.side_effect = [ + pyu2f.errors.U2FError(pyu2f.errors.U2FError.DEVICE_INELIGIBLE), + "security key response", + ] + assert challenge.obtain_challenge_input(metadata) == { + "securityKey": "security key response" + } + calls = [ + mock.call( + "security_key_relying_party_id", + [{"key": mock_key, "challenge": b"some_challenge"}], + print_callback=sys.stderr.write, + ), + mock.call( + "security_key_application_id", + [{"key": mock_key, "challenge": b"some_challenge"}], + print_callback=sys.stderr.write, + ), + ] + mock_authenticate.assert_has_calls(calls) + # Test various types of exceptions. with mock.patch("pyu2f.model.RegisteredKey", return_value=mock_key): with mock.patch( @@ -86,6 +138,12 @@ def test_security_key(): ) assert challenge.obtain_challenge_input(metadata) is None + with mock.patch( + "pyu2f.convenience.authenticator.CompositeAuthenticator.Authenticate" + ) as mock_authenticate: + mock_authenticate.side_effect = pyu2f.errors.PluginError() + assert challenge.obtain_challenge_input(metadata) is None + with mock.patch( "pyu2f.convenience.authenticator.CompositeAuthenticator.Authenticate" ) as mock_authenticate: From 77c9b346ef097b6ec9b4a8e795f39019937fc64c Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 23 Mar 2023 20:38:28 -0700 Subject: [PATCH 687/966] chore(main): release 2.16.3 (#1257) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 171fc3e7aa5a..c8d64758c17d 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.16.3](https://github.com/googleapis/google-auth-library-python/compare/v2.16.2...v2.16.3) (2023-03-24) + + +### Bug Fixes + +* Read both applicationId and relyingPartyId. ([#1246](https://github.com/googleapis/google-auth-library-python/issues/1246)) ([e125dfe](https://github.com/googleapis/google-auth-library-python/commit/e125dfe1e345bf5f6cef4efee8215de129401c51)) + ## [2.16.2](https://github.com/googleapis/google-auth-library-python/compare/v2.16.1...v2.16.2) (2023-03-02) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 0b9b05843ed7..e239d71c9ab2 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.16.2" +__version__ = "2.16.3" From e238f0d8e0f3c92a22b401b04ff32af63e510848 Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Fri, 24 Mar 2023 09:17:09 -0400 Subject: [PATCH 688/966] chore(deps): Update nox in .kokoro/requirements.in [autoapprove] (#1253) Source-Link: https://github.com/googleapis/synthtool/commit/92006bb3cdc84677aa93c7f5235424ec2b157146 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:2e247c7bf5154df7f98cce087a20ca7605e236340c7d6d1a14447e5c06791bd6 Co-authored-by: Owl Bot Co-authored-by: Anthonios Partheniou --- packages/google-auth/.github/.OwlBot.lock.yaml | 2 +- packages/google-auth/.kokoro/requirements.in | 2 +- packages/google-auth/.kokoro/requirements.txt | 14 +++++--------- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/packages/google-auth/.github/.OwlBot.lock.yaml b/packages/google-auth/.github/.OwlBot.lock.yaml index 5fc5daa31783..b8edda51cf46 100644 --- a/packages/google-auth/.github/.OwlBot.lock.yaml +++ b/packages/google-auth/.github/.OwlBot.lock.yaml @@ -13,4 +13,4 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:8555f0e37e6261408f792bfd6635102d2da5ad73f8f09bcb24f25e6afb5fac97 + digest: sha256:2e247c7bf5154df7f98cce087a20ca7605e236340c7d6d1a14447e5c06791bd6 diff --git a/packages/google-auth/.kokoro/requirements.in b/packages/google-auth/.kokoro/requirements.in index 882178ce6001..ec867d9fd65a 100644 --- a/packages/google-auth/.kokoro/requirements.in +++ b/packages/google-auth/.kokoro/requirements.in @@ -5,6 +5,6 @@ typing-extensions twine wheel setuptools -nox +nox>=2022.11.21 # required to remove dependency on py charset-normalizer<3 click<8.1.0 diff --git a/packages/google-auth/.kokoro/requirements.txt b/packages/google-auth/.kokoro/requirements.txt index fa99c12908f0..66a2172a76a8 100644 --- a/packages/google-auth/.kokoro/requirements.txt +++ b/packages/google-auth/.kokoro/requirements.txt @@ -1,6 +1,6 @@ # -# This file is autogenerated by pip-compile with python 3.10 -# To update, run: +# This file is autogenerated by pip-compile with Python 3.9 +# by the following command: # # pip-compile --allow-unsafe --generate-hashes requirements.in # @@ -335,9 +335,9 @@ more-itertools==9.0.0 \ --hash=sha256:250e83d7e81d0c87ca6bd942e6aeab8cc9daa6096d12c5308f3f92fa5e5c1f41 \ --hash=sha256:5a6257e40878ef0520b1803990e3e22303a41b5714006c32a3fd8304b26ea1ab # via jaraco-classes -nox==2022.8.7 \ - --hash=sha256:1b894940551dc5c389f9271d197ca5d655d40bdc6ccf93ed6880e4042760a34b \ - --hash=sha256:96cca88779e08282a699d672258ec01eb7c792d35bbbf538c723172bce23212c +nox==2022.11.21 \ + --hash=sha256:0e41a990e290e274cb205a976c4c97ee3c5234441a8132c8c3fd9ea3c22149eb \ + --hash=sha256:e21c31de0711d1274ca585a2c5fde36b1aa962005ba8e9322bf5eeed16dcd684 # via -r requirements.in packaging==21.3 \ --hash=sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb \ @@ -380,10 +380,6 @@ protobuf==3.20.3 \ # gcp-docuploader # gcp-releasetool # google-api-core -py==1.11.0 \ - --hash=sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719 \ - --hash=sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378 - # via nox pyasn1==0.4.8 \ --hash=sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d \ --hash=sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba From fe6a89f6d53e85e9b2a90ad4f0b203f3586d7986 Mon Sep 17 00:00:00 2001 From: Jin Date: Fri, 24 Mar 2023 14:14:58 -0700 Subject: [PATCH 689/966] fix: Python: Remove aws url validation (#1254) * fix: Python: Remove awl url validation * fix: lint failed due to an unused import * update token --- packages/google-auth/google/auth/aws.py | 19 ---------- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes packages/google-auth/tests/test_aws.py | 33 ------------------ 3 files changed, 52 deletions(-) diff --git a/packages/google-auth/google/auth/aws.py b/packages/google-auth/google/auth/aws.py index f651433f070c..13644c4c22d5 100644 --- a/packages/google-auth/google/auth/aws.py +++ b/packages/google-auth/google/auth/aws.py @@ -47,7 +47,6 @@ from six.moves import http_client from six.moves import urllib from six.moves.urllib.parse import urljoin -from six.moves.urllib.parse import urlparse from google.auth import _helpers from google.auth import environment_vars @@ -398,8 +397,6 @@ def __init__( self._request_signer = None self._target_resource = audience - self.validate_metadata_server_urls() - # Get the environment ID. Currently, only one version supported (v1). matches = re.match(r"^(aws)([\d]+)$", self._environment_id) if matches: @@ -418,22 +415,6 @@ def __init__( ) ) - def validate_metadata_server_urls(self): - self.validate_metadata_server_url_if_any(self._region_url, "region_url") - self.validate_metadata_server_url_if_any(self._security_credentials_url, "url") - self.validate_metadata_server_url_if_any( - self._imdsv2_session_token_url, "imdsv2_session_token_url" - ) - - @staticmethod - def validate_metadata_server_url_if_any(url_string, name_of_data): - if url_string: - url = urlparse(url_string) - if url.hostname != "169.254.169.254" and url.hostname != "fd00:ec2::254": - raise exceptions.InvalidResource( - "Invalid hostname '{}' for '{}'".format(url.hostname, name_of_data) - ) - def retrieve_subject_token(self, request): """Retrieves the subject token using the credential_source object. The subject token is a serialized `AWS GetCallerIdentity signed request`_. diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 5cff634c1fb5fed6fafa4bc34d32409a98fed1f3..96d3dc4fd094ce615f4d10685b63cf256bf2a97f 100644 GIT binary patch literal 10324 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 diff --git a/packages/google-auth/tests/test_aws.py b/packages/google-auth/tests/test_aws.py index 7d87bdba2c87..805aa3ce2577 100644 --- a/packages/google-auth/tests/test_aws.py +++ b/packages/google-auth/tests/test_aws.py @@ -1495,39 +1495,6 @@ def test_retrieve_subject_token_success_temp_creds_idmsv2(self, utcnow): credentials.retrieve_subject_token(request) assert not request.called - def test_validate_metadata_server_url_if_any(self): - aws.Credentials.validate_metadata_server_url_if_any( - "http://[fd00:ec2::254]/latest/meta-data/placement/availability-zone", "url" - ) - aws.Credentials.validate_metadata_server_url_if_any( - "http://169.254.169.254/latest/meta-data/placement/availability-zone", "url" - ) - - with pytest.raises(ValueError) as excinfo: - aws.Credentials.validate_metadata_server_url_if_any( - "http://fd00:ec2::254/latest/meta-data/placement/availability-zone", - "url", - ) - assert excinfo.match("Invalid hostname 'fd00' for 'url'") - - with pytest.raises(ValueError) as excinfo: - aws.Credentials.validate_metadata_server_url_if_any( - "http://abc.com/latest/meta-data/placement/availability-zone", "url" - ) - assert excinfo.match("Invalid hostname 'abc.com' for 'url'") - - def test_retrieve_subject_token_invalid_hosts(self): - keys = ["url", "region_url", "imdsv2_session_token_url"] - for key in keys: - credential_source = self.CREDENTIAL_SOURCE.copy() - credential_source[ - key - ] = "http://abc.com/latest/meta-data/iam/security-credentials" - - with pytest.raises(ValueError) as excinfo: - self.make_credentials(credential_source=credential_source) - assert excinfo.match("Invalid hostname 'abc.com' for '{}'".format(key)) - @mock.patch("google.auth._helpers.utcnow") def test_retrieve_subject_token_success_ipv6(self, utcnow): utcnow.return_value = datetime.datetime.strptime( From 94ca09f34f409dba2003bcca9f315c4aae082b72 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Tue, 28 Mar 2023 09:54:52 -0700 Subject: [PATCH 690/966] chore: update sys test cred (#1262) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 96d3dc4fd094ce615f4d10685b63cf256bf2a97f..06bd272d0cc6b0c4e9e84d0245e49ce404a7bd35 100644 GIT binary patch literal 10324 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 literal 10324 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 Date: Tue, 28 Mar 2023 11:59:02 -0700 Subject: [PATCH 691/966] feat: experimental service account iam endpoint flow for id token (#1258) * feat: experimental service account iam endpoint flow for id token * update * update * update test * address comment --- packages/google-auth/google/oauth2/_client.py | 42 +++++++++++ .../google/oauth2/service_account.py | 74 +++++++++++++++++-- .../google-auth/tests/oauth2/test__client.py | 44 +++++++++++ .../tests/oauth2/test_service_account.py | 29 ++++++++ 4 files changed, 183 insertions(+), 6 deletions(-) diff --git a/packages/google-auth/google/oauth2/_client.py b/packages/google-auth/google/oauth2/_client.py index 428993646af9..74e769fa12ae 100644 --- a/packages/google-auth/google/oauth2/_client.py +++ b/packages/google-auth/google/oauth2/_client.py @@ -40,6 +40,10 @@ _JSON_CONTENT_TYPE = "application/json" _JWT_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer" _REFRESH_GRANT_TYPE = "refresh_token" +_IAM_IDTOKEN_ENDPOINT = ( + "https://iamcredentials.googleapis.com/v1/" + + "projects/-/serviceAccounts/{}:generateIdToken" +) def _handle_error_response(response_data, retryable_error): @@ -313,6 +317,44 @@ def jwt_grant(request, token_uri, assertion, can_retry=True): return access_token, expiry, response_data +def call_iam_generate_id_token_endpoint(request, signer_email, audience, access_token): + """Call iam.generateIdToken endpoint to get ID token. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + signer_email (str): The signer email used to form the IAM + generateIdToken endpoint. + audience (str): The audience for the ID token. + access_token (str): The access token used to call the IAM endpoint. + + Returns: + Tuple[str, datetime]: The ID token and expiration. + """ + body = {"audience": audience, "includeEmail": "true"} + + response_data = _token_endpoint_request( + request, + _IAM_IDTOKEN_ENDPOINT.format(signer_email), + body, + access_token=access_token, + use_json=True, + ) + + try: + id_token = response_data["token"] + except KeyError as caught_exc: + new_exc = exceptions.RefreshError( + "No ID token in response.", response_data, retryable=False + ) + six.raise_from(new_exc, caught_exc) + + payload = jwt.decode(id_token, verify=False) + expiry = datetime.datetime.utcfromtimestamp(payload["exp"]) + + return id_token, expiry + + def id_token_jwt_grant(request, token_uri, assertion, can_retry=True): """Implements the JWT Profile for OAuth 2.0 Authorization Grants, but requests an OpenID Connect ID Token instead of an access token. diff --git a/packages/google-auth/google/oauth2/service_account.py b/packages/google-auth/google/oauth2/service_account.py index 0989750db566..618ab538b92a 100644 --- a/packages/google-auth/google/oauth2/service_account.py +++ b/packages/google-auth/google/oauth2/service_account.py @@ -554,6 +554,7 @@ def __init__( self._token_uri = token_uri self._target_audience = target_audience self._quota_project_id = quota_project_id + self._use_iam_endpoint = False if additional_claims is not None: self._additional_claims = additional_claims @@ -639,6 +640,31 @@ def with_target_audience(self, target_audience): quota_project_id=self.quota_project_id, ) + def _with_use_iam_endpoint(self, use_iam_endpoint): + """Create a copy of these credentials with the use_iam_endpoint value. + + Args: + use_iam_endpoint (bool): If True, IAM generateIdToken endpoint will + be used instead of the token_uri. Note that + iam.serviceAccountTokenCreator role is required to use the IAM + endpoint. The default value is False. This feature is currently + experimental and subject to change without notice. + + Returns: + google.auth.service_account.IDTokenCredentials: A new credentials + instance. + """ + cred = self.__class__( + self._signer, + service_account_email=self._service_account_email, + token_uri=self._token_uri, + target_audience=self._target_audience, + additional_claims=self._additional_claims.copy(), + quota_project_id=self.quota_project_id, + ) + cred._use_iam_endpoint = use_iam_endpoint + return cred + @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) def with_quota_project(self, quota_project_id): return self.__class__( @@ -692,14 +718,50 @@ def _make_authorization_grant_assertion(self): return token + def _refresh_with_iam_endpoint(self, request): + """Use IAM generateIdToken endpoint to obtain an ID token. + + It works as follows: + + 1. First we create a self signed jwt with + https://www.googleapis.com/auth/iam being the scope. + + 2. Next we use the self signed jwt as the access token, and make a POST + request to IAM generateIdToken endpoint. The request body is: + { + "audience": self._target_audience, + "includeEmail": "true" + } + TODO: add "set_azp_to_email": "true" once it's ready from server side. + https://github.com/googleapis/google-auth-library-python/issues/1263 + + If the request is succesfully, it will return {"token":"the ID token"}, + and we can extract the ID token and compute its expiry. + """ + jwt_credentials = jwt.Credentials.from_signing_credentials( + self, + None, + additional_claims={"scope": "https://www.googleapis.com/auth/iam"}, + ) + jwt_credentials.refresh(request) + self.token, self.expiry = _client.call_iam_generate_id_token_endpoint( + request, + self.signer_email, + self._target_audience, + jwt_credentials.token.decode(), + ) + @_helpers.copy_docstring(credentials.Credentials) def refresh(self, request): - assertion = self._make_authorization_grant_assertion() - access_token, expiry, _ = _client.id_token_jwt_grant( - request, self._token_uri, assertion - ) - self.token = access_token - self.expiry = expiry + if self._use_iam_endpoint: + self._refresh_with_iam_endpoint(request) + else: + assertion = self._make_authorization_grant_assertion() + access_token, expiry, _ = _client.id_token_jwt_grant( + request, self._token_uri, assertion + ) + self.token = access_token + self.expiry = expiry @property def service_account_email(self): diff --git a/packages/google-auth/tests/oauth2/test__client.py b/packages/google-auth/tests/oauth2/test__client.py index ff3096057721..4997d2401747 100644 --- a/packages/google-auth/tests/oauth2/test__client.py +++ b/packages/google-auth/tests/oauth2/test__client.py @@ -305,6 +305,50 @@ def test_jwt_grant_no_access_token(): assert not excinfo.value.retryable +def test_call_iam_generate_id_token_endpoint(): + now = _helpers.utcnow() + id_token_expiry = _helpers.datetime_to_secs(now) + id_token = jwt.encode(SIGNER, {"exp": id_token_expiry}).decode("utf-8") + request = make_request({"token": id_token}) + + token, expiry = _client.call_iam_generate_id_token_endpoint( + request, "fake_email", "fake_audience", "fake_access_token" + ) + + assert ( + request.call_args[1]["url"] + == "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/fake_email:generateIdToken" + ) + assert request.call_args[1]["headers"]["Content-Type"] == "application/json" + assert ( + request.call_args[1]["headers"]["Authorization"] == "Bearer fake_access_token" + ) + response_body = json.loads(request.call_args[1]["body"]) + assert response_body["audience"] == "fake_audience" + assert response_body["includeEmail"] == "true" + + # Check result + assert token == id_token + # JWT does not store microseconds + now = now.replace(microsecond=0) + assert expiry == now + + +def test_call_iam_generate_id_token_endpoint_no_id_token(): + request = make_request( + { + # No access token. + "error": "no token" + } + ) + + with pytest.raises(exceptions.RefreshError) as excinfo: + _client.call_iam_generate_id_token_endpoint( + request, "fake_email", "fake_audience", "fake_access_token" + ) + assert excinfo.match("No ID token in response") + + def test_id_token_jwt_grant(): now = _helpers.utcnow() id_token_expiry = _helpers.datetime_to_secs(now) diff --git a/packages/google-auth/tests/oauth2/test_service_account.py b/packages/google-auth/tests/oauth2/test_service_account.py index ed281fcfa34f..74102797364c 100644 --- a/packages/google-auth/tests/oauth2/test_service_account.py +++ b/packages/google-auth/tests/oauth2/test_service_account.py @@ -428,6 +428,7 @@ def test_from_service_account_info(self): assert credentials.service_account_email == SERVICE_ACCOUNT_INFO["client_email"] assert credentials._token_uri == SERVICE_ACCOUNT_INFO["token_uri"] assert credentials._target_audience == self.TARGET_AUDIENCE + assert not credentials._use_iam_endpoint def test_from_service_account_file(self): info = SERVICE_ACCOUNT_INFO.copy() @@ -440,6 +441,7 @@ def test_from_service_account_file(self): assert credentials._signer.key_id == info["private_key_id"] assert credentials._token_uri == info["token_uri"] assert credentials._target_audience == self.TARGET_AUDIENCE + assert not credentials._use_iam_endpoint def test_default_state(self): credentials = self.make_credentials() @@ -466,6 +468,11 @@ def test_with_target_audience(self): new_credentials = credentials.with_target_audience("https://new.example.com") assert new_credentials._target_audience == "https://new.example.com" + def test__with_use_iam_endpoint(self): + credentials = self.make_credentials() + new_credentials = credentials._with_use_iam_endpoint(True) + assert new_credentials._use_iam_endpoint + def test_with_quota_project(self): credentials = self.make_credentials() new_credentials = credentials.with_quota_project("project-foo") @@ -517,6 +524,28 @@ def test_refresh_success(self, id_token_jwt_grant): # expired) assert credentials.valid + @mock.patch( + "google.oauth2._client.call_iam_generate_id_token_endpoint", autospec=True + ) + def test_refresh_iam_flow(self, call_iam_generate_id_token_endpoint): + credentials = self.make_credentials() + credentials._use_iam_endpoint = True + token = "id_token" + call_iam_generate_id_token_endpoint.return_value = ( + token, + _helpers.utcnow() + datetime.timedelta(seconds=500), + ) + request = mock.Mock() + credentials.refresh(request) + req, signer_email, target_audience, access_token = call_iam_generate_id_token_endpoint.call_args[ + 0 + ] + assert req == request + assert signer_email == "service-account@example.com" + assert target_audience == "https://example.com" + decoded_access_token = jwt.decode(access_token, verify=False) + assert decoded_access_token["scope"] == "https://www.googleapis.com/auth/iam" + @mock.patch("google.oauth2._client.id_token_jwt_grant", autospec=True) def test_before_request_refreshes(self, id_token_jwt_grant): credentials = self.make_credentials() From f1df8f761303524aad6c2b29bc6b5e5900e54965 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 28 Mar 2023 12:46:54 -0700 Subject: [PATCH 692/966] chore(main): release 2.17.0 (#1259) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 12 ++++++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index c8d64758c17d..5fb35c8a2a1a 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,18 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.17.0](https://github.com/googleapis/google-auth-library-python/compare/v2.16.3...v2.17.0) (2023-03-28) + + +### Features + +* Experimental service account iam endpoint flow for id token ([#1258](https://github.com/googleapis/google-auth-library-python/issues/1258)) ([8ff0de5](https://github.com/googleapis/google-auth-library-python/commit/8ff0de5f6c26c8778e24e57d6b7f449856357f81)) + + +### Bug Fixes + +* Python: Remove aws url validation ([#1254](https://github.com/googleapis/google-auth-library-python/issues/1254)) ([20a966b](https://github.com/googleapis/google-auth-library-python/commit/20a966bbbfc66932f471e0bfd191769f40332233)) + ## [2.16.3](https://github.com/googleapis/google-auth-library-python/compare/v2.16.2...v2.16.3) (2023-03-24) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index e239d71c9ab2..0e7fb575aa29 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.16.3" +__version__ = "2.17.0" From 743115b1617bb05d690c02c3d233259ae2ac3964 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Thu, 30 Mar 2023 13:08:11 -0700 Subject: [PATCH 693/966] fix: print out reauth plugin error and raise if challenge output is None (#1265) * fix: print out reauth plugin error and raise if challenge output is None * chore: add test * chore: update sys test cred --- .../google-auth/google/oauth2/challenges.py | 3 ++- packages/google-auth/google/oauth2/reauth.py | 2 ++ .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../google-auth/tests/oauth2/test_reauth.py | 14 ++++++++++++++ 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/google/oauth2/challenges.py b/packages/google-auth/google/oauth2/challenges.py index 4eb665b8a7a9..6fe982d1c31a 100644 --- a/packages/google-auth/google/oauth2/challenges.py +++ b/packages/google-auth/google/oauth2/challenges.py @@ -169,7 +169,8 @@ def obtain_challenge_input(self, metadata): ) else: raise e - except pyu2f.errors.PluginError: + except pyu2f.errors.PluginError as e: + sys.stderr.write("Plugin error: {}.\n".format(e)) continue except pyu2f.errors.NoDeviceFoundError: sys.stderr.write("No security key found.\n") diff --git a/packages/google-auth/google/oauth2/reauth.py b/packages/google-auth/google/oauth2/reauth.py index ad2ad1b2ee25..84fd0be2b3c6 100644 --- a/packages/google-auth/google/oauth2/reauth.py +++ b/packages/google-auth/google/oauth2/reauth.py @@ -225,6 +225,8 @@ def _obtain_rapt(request, access_token, requested_scopes): msg = _run_next_challenge(msg, request, access_token) + if not msg: + raise exceptions.ReauthFailError("Failed to obtain rapt token.") if msg["status"] == _AUTHENTICATED: return msg["encodedProofOfReauthToken"] diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 06bd272d0cc6b0c4e9e84d0245e49ce404a7bd35..6590ed21953de7e0ddec2b5f1b13bab01ee801eb 100644 GIT binary patch literal 10324 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 diff --git a/packages/google-auth/tests/oauth2/test_reauth.py b/packages/google-auth/tests/oauth2/test_reauth.py index df0636b18cff..47aa8fa95cbe 100644 --- a/packages/google-auth/tests/oauth2/test_reauth.py +++ b/packages/google-auth/tests/oauth2/test_reauth.py @@ -204,6 +204,20 @@ def test__obtain_rapt_unsupported_status(): assert excinfo.match(r"API error: STATUS_UNSPECIFIED") +def test__obtain_rapt_no_challenge_output(): + challenges_response = copy.deepcopy(CHALLENGES_RESPONSE_TEMPLATE) + with mock.patch( + "google.oauth2.reauth._get_challenges", return_value=challenges_response + ): + with mock.patch("google.oauth2.reauth.is_interactive", return_value=True): + with mock.patch( + "google.oauth2.reauth._run_next_challenge", return_value=None + ): + with pytest.raises(exceptions.ReauthFailError) as excinfo: + reauth._obtain_rapt(MOCK_REQUEST, "token", None) + assert excinfo.match(r"Failed to obtain rapt token") + + def test__obtain_rapt_not_interactive(): with mock.patch( "google.oauth2.reauth._get_challenges", From 966dd54e7573721e328a1b5b2e304cd6af783f14 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 30 Mar 2023 15:20:25 -0700 Subject: [PATCH 694/966] chore(main): release 2.17.1 (#1266) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 5fb35c8a2a1a..d4f5ec993e3e 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.17.1](https://github.com/googleapis/google-auth-library-python/compare/v2.17.0...v2.17.1) (2023-03-30) + + +### Bug Fixes + +* Print out reauth plugin error and raise if challenge output is None ([#1265](https://github.com/googleapis/google-auth-library-python/issues/1265)) ([08d22fe](https://github.com/googleapis/google-auth-library-python/commit/08d22fe805e0401c3a902ce96f9c3797e7f166b1)) + ## [2.17.0](https://github.com/googleapis/google-auth-library-python/compare/v2.16.3...v2.17.0) (2023-03-28) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 0e7fb575aa29..f26ee8008b7b 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.17.0" +__version__ = "2.17.1" From 562845c1ef4716bd0e6408b5e17596d507cabf66 Mon Sep 17 00:00:00 2001 From: Carl Lundin <108372512+clundin25@users.noreply.github.com> Date: Wed, 5 Apr 2023 09:59:37 -0700 Subject: [PATCH 695/966] fix: Do not create new JWT credentials if they make the same claims as the existing. (#1267) * fix: Do not create new JWT credentials if they make the same claims as the existing. --- packages/google-auth/google/auth/jwt.py | 5 ++ .../google/oauth2/service_account.py | 35 +++++++---- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../tests/oauth2/test_service_account.py | 57 ++++++++++++++++++ packages/google-auth/tests/test_jwt.py | 1 + .../tests/transport/test_urllib3.py | 3 + 6 files changed, 90 insertions(+), 11 deletions(-) diff --git a/packages/google-auth/google/auth/jwt.py b/packages/google-auth/google/auth/jwt.py index 39036895829c..026aec554e6e 100644 --- a/packages/google-auth/google/auth/jwt.py +++ b/packages/google-auth/google/auth/jwt.py @@ -590,6 +590,11 @@ def signer_email(self): def signer(self): return self._signer + @property # type: ignore + def additional_claims(self): + """ Additional claims the JWT object was created with.""" + return self._additional_claims + class OnDemandCredentials( google.auth.credentials.Signing, google.auth.credentials.CredentialsWithQuotaProject diff --git a/packages/google-auth/google/oauth2/service_account.py b/packages/google-auth/google/oauth2/service_account.py index 618ab538b92a..152e05814f20 100644 --- a/packages/google-auth/google/oauth2/service_account.py +++ b/packages/google-auth/google/oauth2/service_account.py @@ -441,19 +441,32 @@ def _create_self_signed_jwt(self, audience): # https://google.aip.dev/auth/4111 if self._always_use_jwt_access: if self._scopes: - self._jwt_credentials = jwt.Credentials.from_signing_credentials( - self, None, additional_claims={"scope": " ".join(self._scopes)} - ) + additional_claims = {"scope": " ".join(self._scopes)} + if ( + self._jwt_credentials is None + or self._jwt_credentials.additional_claims != additional_claims + ): + self._jwt_credentials = jwt.Credentials.from_signing_credentials( + self, None, additional_claims=additional_claims + ) elif audience: - self._jwt_credentials = jwt.Credentials.from_signing_credentials( - self, audience - ) + if ( + self._jwt_credentials is None + or self._jwt_credentials._audience != audience + ): + + self._jwt_credentials = jwt.Credentials.from_signing_credentials( + self, audience + ) elif self._default_scopes: - self._jwt_credentials = jwt.Credentials.from_signing_credentials( - self, - None, - additional_claims={"scope": " ".join(self._default_scopes)}, - ) + additional_claims = {"scope": " ".join(self._default_scopes)} + if ( + self._jwt_credentials is None + or additional_claims != self._jwt_credentials.additional_claims + ): + self._jwt_credentials = jwt.Credentials.from_signing_credentials( + self, None, additional_claims=additional_claims + ) elif not self._scopes and audience: self._jwt_credentials = jwt.Credentials.from_signing_credentials( self, audience diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 6590ed21953de7e0ddec2b5f1b13bab01ee801eb..bdea3fdb79c69af52c062bb9b0eb378fc1da793b 100644 GIT binary patch literal 10324 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 literal 10324 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 m*q*t@7_`vj_gOw-2^zB&K&hgIuYhb8=% Date: Wed, 5 Apr 2023 10:30:43 -0700 Subject: [PATCH 696/966] chore(main): release 2.17.2 (#1269) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index d4f5ec993e3e..836edd0b8e2f 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.17.2](https://github.com/googleapis/google-auth-library-python/compare/v2.17.1...v2.17.2) (2023-04-05) + + +### Bug Fixes + +* Do not create new JWT credentials if they make the same claims as the existing. ([#1267](https://github.com/googleapis/google-auth-library-python/issues/1267)) ([eebb7b6](https://github.com/googleapis/google-auth-library-python/commit/eebb7b6630a7fc1e943a4d5f8fc76e9dd6dbe687)) + ## [2.17.1](https://github.com/googleapis/google-auth-library-python/compare/v2.17.0...v2.17.1) (2023-03-30) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index f26ee8008b7b..d73c4346e2d7 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.17.1" +__version__ = "2.17.2" From cdd13c0f611459fbd073e8aaa7f6c08cf7e145de Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Mon, 10 Apr 2023 13:33:27 -0700 Subject: [PATCH 697/966] fix: add useEmailAzp claim for id token iam flow (#1270) --- packages/google-auth/google/oauth2/_client.py | 2 +- .../google/oauth2/service_account.py | 5 ++--- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10323 bytes .../google-auth/tests/oauth2/test__client.py | 1 + 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/google-auth/google/oauth2/_client.py b/packages/google-auth/google/oauth2/_client.py index 74e769fa12ae..03f6e8f03e85 100644 --- a/packages/google-auth/google/oauth2/_client.py +++ b/packages/google-auth/google/oauth2/_client.py @@ -331,7 +331,7 @@ def call_iam_generate_id_token_endpoint(request, signer_email, audience, access_ Returns: Tuple[str, datetime]: The ID token and expiration. """ - body = {"audience": audience, "includeEmail": "true"} + body = {"audience": audience, "includeEmail": "true", "useEmailAzp": "true"} response_data = _token_endpoint_request( request, diff --git a/packages/google-auth/google/oauth2/service_account.py b/packages/google-auth/google/oauth2/service_account.py index 152e05814f20..37e1e568a768 100644 --- a/packages/google-auth/google/oauth2/service_account.py +++ b/packages/google-auth/google/oauth2/service_account.py @@ -743,10 +743,9 @@ def _refresh_with_iam_endpoint(self, request): request to IAM generateIdToken endpoint. The request body is: { "audience": self._target_audience, - "includeEmail": "true" + "includeEmail": "true", + "useEmailAzp": "true", } - TODO: add "set_azp_to_email": "true" once it's ready from server side. - https://github.com/googleapis/google-auth-library-python/issues/1263 If the request is succesfully, it will return {"token":"the ID token"}, and we can extract the ID token and compute its expiry. diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index bdea3fdb79c69af52c062bb9b0eb378fc1da793b..ba6703d1de2b9978a91017a79244a4106cbf5064 100644 GIT binary patch literal 10323 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 literal 10324 zcmV-aD67{BB>?tKRTEupW4gXU4CAfnu5=1LZa1-vy!xdxIDwU5$$q|Tza|o@PypAf zZWHt8Nqzgi%oPLgg!{OaqSxOt?dtP9fI=H#Y1e9;h){*Nm_bG2%n6XQCsVll#g06f zYhZ9v+IT8m;Wj7ga6&q*G?98vc|XS+#*t95^WoW zn_ZI^me`~|xb8!53`KD0DtjViF5F4k9~U7=#l zrX^Gp#f}f640-nFg$U~6p|>X#-fnwRsyaoGnT2*NV2|x2do&Whgi5UV!jocdG-ZKZ zX!^3^wz4t}yZFqZbZ%Bn6}QtsKHqX<6`Axm@8Z#6yy!Ip-xoF~i4b`XJg3P?5fO^! zAGK7r;r<3`5hC~;G^SG>wUZV?h{XI?Jgl{H3|Hif#24r?9P|ISMaG8iZ;XJ3>MhVB z@}K>)Is&15oos`?`r&JU7Nn>FUvL)*jfkqI#wG^GOONQ_01`}naQv%1gQK#noR1fc zs8Cr+=c zmuEU6UQ3}K^KyTx8JR!t^gM~}^no-8$|SAvp;w*SQzjuhA{(_tO8Pm`H z2k*6Y86M2A8J(|xOQa`4lnOz>vT$Q9(HIhh`ee`N7-Wab!1Q9p7Ve#+`lYnl>hGkT zR7ZA0y8(8drbCfC7kI9ZD%aA_$s1du?EftWC(YGLNQzaXz2>kU+`IR~i$Fa8UexKv zxOHmWvolv+QX@z`2c=)#sw1O|S6reLrk>s1DRL5flv3yYqMUy*@0h|=Ji7Ae! zsR@!4(-=JD17z6DPmH>{L+oOmJ>vY^JugMhQydWV+Pt?h=xD~Bfg-K~ae&6DqVRW3 zG^R+Q{#*p&SZL` zf6}A}3e{C*FvJ(b1L8Ff-(_+qn5tf07*!5%H1%_1+^da{)(~pAHBHPczvFnm*822+ z10mWkSd!v2-xzVxw{bAd(fTf6+2M?X)?@=A49pV@y(6))_CU-kY7>uyCfD+`9c4*r z7~yKxEqEDirlRhx3U)pZ*23VGdnsFCf4>-(n{+Ca3k~HHCB!%SS!09IyErCB;mA@M ze2c&!x7dMb}vX8k&Sjdqla<$f9 zrdyS3)b3zM1=NlBPqy7>fSa%nI5n+}!dm|3Cv#(zLlr3DzIwwo{Z6POB4^-)gH~v% zHt!`@KxE{lg%~{Q^?XNVC^Htu1Z1+yWPT4S>mW0-rv~j=vH+^+H%!$ZcKGsUh$M9z zC5r<4=tF%06qO-+9+fb^5Jbl}A3kehtR@iTh(Sn>>zmolFUR`PhH%RHlEt?^7nb%6Qb@2 zn~+@H*&})lu$JWlm9f0L@}4teXR)N%je1FX;GIgp2l3K>k>Y&<<1SY68HG_Q0QBOj zwUe}e(3OLb2Psn$M`-|UNMnU_(R1)%c^g8$))Qy1^km0oTrV%|f5UbEN{H05&3m)v zx|m0hoFJ=@%l6}@pK4#PHh1;4?x_@j;8aCZ)wuxcX+yYM9;w>~Bti?7xl~={={`OA zvf_kL25FeBHoyk^xU(`!u^{yg9@#M$FB6Ts<;$89Q4|}bhDRjP?{a!v^hijCdbl=1 zRK3CNG>2Whk~GAs+}9hz8Z`pM8=mG_C0vMU)6nv!d*@!8ftp5b+p_P!KaU~)<`@yK zhU_qwW&o%~GHiA2%DXSSii7H@DIFcx9cZ~kojFWkj)j-A&^P|U3#g)Y?~|L_=x4h} z2=B^!{{NwxsZfA4yKLKDuo=oUZ>J)uq_YvpRB|uiNN8iXf5;zfn`|$h*4~Kdn3lbu zGJE^@i!Ds2SoxW{_{m2xiBgY}B+8$XggqMuFooTH68}f12YqK`D=9A6gX6u7~PJ60LAb|e>k4z@SJI?`VYQ%OFZ%7lee%We_U#C;WG#l9Twtd%m!s1Y) z*1N!Qpc3~)EoVxo&CW3Ntj#%Nb#X}$LeUzfK#)Xm2;3~h0xCA%8=d)Gt32gY7Z2;) z8%2Q;0#5GYk5<|rV)OP83_^*dy=MgjWi-0JJ-r#sqAGnX=VaMKYKk2?1My^%21&rq zzPKj9V;Eeapj9Qext{xFu=Mh8=nbXw85{PGF-ol17@1nv3^6NV4@udZf;z|+GIJdI ztgS79;K?5^s&CM7p_LOty!*O(p>JH}^YnJyx_@W~+`L!;+l?BkNG$Pl&Y+4MV-l#! zk;a1}h9AeL2g?6*g-BW1V~OH|cT6@wt3dUdcj_1dw)PbWY z=SxRy`-jge@Vd0Vwp-+I>9vuOogcPXU=otklYsRvp-cahAnF_%G2jR6ybTL{L2l=H z7Po;~RCKG{P%IzLKUEo2(tw87M=DbTy(s(|Z&jGAbbnA;prkt_MuWxigir>pG~GSI z#hGzllsP7i4)!&;?k^BKwj13-KHjUms7@qjG#ZyIHtNCWX~y)d7BdR=!h>Vc+q*xj zL;)`#u(@F4AYevCLoAffxBGCv3f~WoRkWASYgtffWsd#bjI3E>gx=gHqu6bfvG1Q+ z$!RW|GXC?!0+4|FV);MT_<^eF#iIsO0Z@5H3`PxLV;QL&aLRzi#oTx$YIcJwbMzd*X}F1_8EvpzshnTOkJF3R zc`UG)zo!&nrcVcV$BM;TyTn=^Y2Uc09hNMRFv8$Z`iW^ejHv8H*6q;k2ey&}3_>n7 zmAiNHO8VgoWn?w$ahAz>m$2eS0lFyAm?aevW60(U#;OuWgg2KL6SFw`2!Kt!5Q?Z{ zcNX&+tcWyDScF}zeaA-lwACE$^*fLT$IB|hGLV$zQiJ$xubKZ>33P$CimnA3DRcS_ zKiiOe>xoSe@vz!_&l24(4eVu@?bRl1cI;p)lwiO=Lmq!eFtDZwzH%l}5SctNy5FAs*rRQ!_w zAN8F9-)%n+Lhaun%@sayOW-?!iyp`kA)?D+{3rru77u|?71io+I7)sdbSFD0n7o88 zc@t|C3y`j`0D`EK{%%J)e^Q7w0Y;BvglIhIU!IurX6$&m596|Je3TgsFeip)8L`O3 z`EEkSTE5$VZ7M;Q1^}MPr_;e-Nt>4pisd)MlasbohSlF=^hi@32cGDFVWf&<2s(?en!AfYyy`AOVxEB$t#N)`Kr93g_(Hs^P3BF$ zjF(VB*m{X4YOp3-6@Sb&M*|GG=eRA5au8-aWG<$e4OUO!8QptWC^J>-o4SHmj&v#G zq(|`aPNTBd^Hz95iYNRdL%yS()yv5`C_WZZzr89~xG*Xp1!Ga8R1AUCsHYSSQkm4sg zy7T(aV^>PVBwtU-`)dEFl>3lVNjV_{5tB*6K8rO1D2il744P}sE{OU?$eT$nGhHFc z`xrS7hD0SX;BG&^OeqdCCYgimU@^3-vwlQwdRAODl|G6dMI$XQz-dFHA_wosS^ymC zxDP9wZIrzW@DE~Fyt#praAg^qwbx14?BnCZU{zAF7lCH60(IQ0zqA}#mKk!Bx#+mRTpH+eaMBjg=(78`Y+^QW!)+%VW5nD+oOp|a9J25h` zBU8Ad27HxeB(@nP{e!puw0x~6&XZkpl+@&l;<2FiwPSU8O@g}44yVgFdo z!0oLi;coO-3P1*R%ipHA=Q~)vS#MG|QSQaI{{a}cSa z$0R<^>N<}vn3^8rv*O3zq0dBI!h)^PpVpDew23Fl#dD+wqiI#gw6^8l+@iss)dKw} z9%EKoGec8|z4T2bpgK*zg8WEIlTr9mMks&fWWlLV`In{*R>9O`qe!3fT0l3P`hq1{ znBIcu?N<1&caN5dhT#(OT%Ez~Malp%*f8^_F!1lbuHb~uZ0ourNEpU3u*Oa@a{Ygz z0Z9k2SIR3C1v)b|^k328CG@zDoKB;Dn5hbflIQxT05&IuDccPq`~L(QAhBTaV)k^5 z@TDPTa2MkVb631B{rrQkT}rw0&T|TW&F~WZmq>bua0~)cFq-2pTm21;5Q3p9QfEQl z3a%dO6&mG14tm~c3GbtYB5@cyca9Wr<@8I5m+9fgMbRe?h_j{^-l!G6hqWSsN798q ze*B%x-0!)gqupfvbx`bg^kSd*U z;g6zB!3JFT!sKkEKSPEZ(%PeXaF?w6aCw_uf2XF?2HXv#n&DwIC$Auzs3*~08_Qug_yRC-0lTy(JYN|!bU*FS38nSN(&FDH zWA8aJ78e??nb;y5eOrc9`xyJs(@}`xB74z{>U}XDYsUjxLi>>KgnE%E!^6$QH%IDU zSqme)RQ3qDeNL4?R8=K-h({gmGWFjnVNkE4aLjuV-l^RsWBEqp^Soyk2V02`M@wf0 z$~2JZ69g8gke2DSTa>h7d854_ZBM2Z>@&~M7I7IIJ{uvr>=oZg*BbGt&6mF{E?rRK zZIa=XXT<t?U+ObEg{~6v2H;ftmGA-a@8G838cvqd}0dH zYF~V-mcU6V!4pfeD2=q1VaObsR2YcxsT`VW;g;j3(w85T2uS&+?Awf6z0XLhnxFV9`$?Jk(c=s3G@P}3V1z@XhBUr8xSHP=r&zkX0`tBqP2fB z5%z=xNf6NG2|>(My^dnLdvMv|QxYRnu-=;lDV7V$I+}+_X9qoCrxSrrnEn&+=q2gEMinxV5Z?c8p<2+?cz_l z34ajDN!5_zH~zypqWKa;#iiWJN>sHFoCFWwjidA|rcLfAttkQi{VtOIIYCBVF(?|K zD{sFBi`&i##KyL~XUUdY>IiID^^y)BVDz2do9L_wl05E(qDe7r`X10#H_QGmwGw;Z zEl{bvv$!c|YE;%=K)?-?k=Gbf6X-x%mc3B&)J5ePbIpF`qh#&s&(bNyrnb8o=I+iG zNDpg~WAzD+Q)R#v2P*+vf3btXJ@SwKarCZI-K>UqRkd|!#u*+zA3W&6OK)Umj6Yl! zL-@B%!xCRqln(IGrzK?a}iUb~P-t<|QiADu3`|CM0jL#$Z+6Bd8B=US5l%;mKOd+Y7nC z0A!B$QVq3Aj9OPCW7xI?oiZvS#3;3uK64#)Wc49h+G64z@Lw~(S|*cp8HoLs{Hak5y86~ z0jT|#cl@xLG(aELNUY=})O_0OCQH@GX#CL?)SQe51}kLj5O($k$N>T4kp2C!Vxgfu z3J>KVt9lP&(h9Sd&kFky27m3V(tB5krKzToJ!4kR5XkUPgO8&k`S0%Y!=Bz{MMmBN z(G94<#&%pxOpR}#rNLc}4?S z&zQp*)W8XaF-N?uTqnW~XfHYjwKeVzS~JqI33@1-8*G8kA8vf%5GYzRRquilZ_+YpY(1Q;2W2xMn+)6Zu}Ft2WU!MdCDb7; z849(L(wU0%Syd?>E43;ihF$hGpcXP=nlmz@S@wexW1z24w9XF)%j-lAF|WxJudjl% zZ5vK%5RvS>ad<>>04Aa~3MfBvrvGFQ+T0`lhooS3kgXc~8!mVwL^f-Gl}%J#-f^qv zvPIr>X9S691`ZHD1z86sRU*_$G&DTb%&dm(-Suur;hmv#9$w)ahMD4Z35!-E9+YXP zVd`T8P6D-^SfJ#5$vc5&njok7sOR@r+EA1y zaQ8coo1a#Af)tn^Z?c?xjaatiwb_cNuZOZw`^w@QRME?DH2#w6`V6qyZ#8Ey89mn7yR>oi))z! zk}Kp*WCI_QCpHdx`=ev(R-stqdM?5YDzgi!Rpmt)xwC!x!8#CVMlRihhxt1_Jf2L1!d3+^Yth*Y39ETJ)(H;9V!;!y;=R8~ta)MVa^ zCo3x4Vt5*6gwu~R((G@%VLfA2Y;O=>9k9#=C|NcOe=HdXqwz)9jh+ieLWx@>xn~EI4A_nh_t~9nU3XEPD(I-ujJ)TaO9E zwiWu<)Bg_o zP>tx;&KUxQY^5+!lfGEvtV@g=XR!$|UT94BVk%m82#&Y5i+#f8HxZRcJ1M4+(2$To zlbhY>3>TwRLLI30E1ab!ZkSvhW78)EZdBRb9Pwzs-Tc4_;ox%BTT+ldpM|9C*Lo;z z6_d!psR#hX%c144mfoqrP?kr;_qmDjq8{cUo|n3YC_U3;9I1qku|_Jyg<0buwst>D z{=gfK-r)BU4Y>0iAA%UPHtaJO7OD=oVq_^PEn9*=;A3KB(l5fy>-^F` z4ucIlf;$|vrcB#A2OqeS4Zz~HC9CR6Th){t4@80&Amg_FmMM!AR{ri%rhbi8Oq|Fy zKF>=AZ>`Owm;9=xa8j4Lb>lDCs%$Nai!`@R$DYegqro9DxuDjgn|q+<`N$|?@iY7) zzH4`8e?V~>nc^wY@w=rH2mqK?AU8eYpUp(56TRC22UIlFMjORswxNHn1-2XRzV62o5MKz!2XpC3l z+Kguk^>(ouE%}{xQ-%G%kL8P2-~HjVNE>q;9uvfH4n4wR4Wu%#6npR~o4mJ9ke$Qn z0#am5_5_VACJjLwpKDGTjzHws((BM$zxpKaB%ti2*%gFn^@X{IC|s-fEBK));$4@*QzFF($63D!cg5T#cm{Qt}&0=;7D>0LnNtHphB z9b9do zBgK!~b~tEe*g2R3%AwWE$G30KTg15~E$AiOn&CROhZ5dY7wN|iC1il{U3EdfG%K$E z;cVxT7@FQLSG{+8K;G#x#=U|6+1^>5x04&VS;UVkXCDF%-r+PF0N=7$wTC!zJFrt6 zEaz{)`o}-Ym$tn(N<`SOPAHP*No$?PZBxJYI$T~P?sH=$)DjV62;RFzIVn;+F^&z~2_z$;iE4+YLjv#8VqDCL_LV3%(5Ln>35Zru39dL3O|$0Ouo!WRxA!)KAsg z%tlg+l^iLdVbfW@Yc8`##No#ADa9kD<~Igy$m!?XGmwNflH&Pw+0Ao#6339%3tKg z_C*unkoS#z{{qad)e=R`*f|v}tCfYr;Ks-4LBIxWyiC?}IzKtt1>qv()hzQsBzAEI zH6)c%)_zO)>Q3}stL|6V27$ZNu~q0o@OSwl?ud-Ri&A|1H)m(KwpZ(jl3n$PPcgAE zOMCjxHc>Et{LB(4=YY2D{(9od`0L{28r+z=~{qY%fS3emq(ZS5^7g`KJ z8s!65lXUp;z81UL$TMabbP4~)hW0LnPOaNq*kkEclt(LpBdx!ov8|#h-t5X z?;aUM75PHV$6Qxf1lF8sl51={fMo}jIFNT}%Yt&XNgi9lT4(;Uy67E3qFjnk+n1|_ zKWKLVMR`4Opq^HK-K7wM#y%Je$NLBGqa45i7_3{-M9nPZJ8~_tYkR1jWT0O?oS5kc zLTWW?t`x->-BR1~;B4&I`~lNV%c-pqWg^^aJYhMVbzp3^20j(*TT02V+r+`ma?^|V z%K3BK0OI45P!r5>(xC`Oh0RPl%gM2PW+v9~CREMHxY@m(-pO%h_QW$wCQ`^3S zo05bGkb#hbf30K$;_lWWwY`sNZqeZ%Yd0psug4N@UK&e>`Y2K{>Mb)<5$|_bty`yj zGP%ZWW=#hWo4i8Hv>(0lQ~{NXyq(oyN)x>Yh33ru{OT72VEdT3Z?3qJ6|ee}(7%ju z9DgbA_Q6u@vkwHrOCSjnlkPgl0;7}!9v}Zb!|}fN`3yrOs|Dpi2 z_E>iP!^5%Mf%2tQbhOr9eE8H&ors=)#EkyDMOL0wZHfc1gTH zW7Yg2IIGF6IdO_&)`4#4)? zh$}FCCq#-~riiISQ^c$31yE=dpF##}WZ3ayluK*+?5&X=C`gN=#E5kmRW`>w7wg>p zUV*lqkJXo`jD<_8Z(JNEw41Tak>Ejf{XtVQ=S-w4wZ|OAhu)tOxsv^^`kieKswD&2 zK;k030)ZUueW&RSTLR3)^9ySD!E|+aP)e^4f^&792Z{rkAMOVpYpz$s;9_eN+L8`% z8Pi;Ww2=HYAH|uKv*eYUpzXCs(*_%CM7K4UJiqSp&!^!t$pG->)Fvl6zQ=6y%wkp^ z0RO0UQd>tl9Ja`Sh~Kbc0X00q5mfR0z*qqid+J~VGeSj6O#=9}mL;qhHMzv360XN#B(Z4A@NZA1oxy1q5%|}g zDyvhL@|~M9#$eL#yA<7uQWzWF&X_#U$q{R~_so#wMrGJeBJ}g(c>ga^aR?d;rfC*k zT2ml=z!O`Y50(bSp_Fqql@hT9%xG8TN%CJw&2ZgRbIdR)UW2KzVjn$23v4MgU;eTH zdwKa65{nxAGgXTBgDm5Q)Kt6yv&7@WcC^fUt7V|2A?A4vA=}57k033fc{E4w-9kb zZ}ZcdfVQWXlj=$=QLvH1w9RK1^WkL;=t5u?gB{E? zoP{^i%N3b@mD7L!{}z}OK0Srq^B?XDuVqut86pw$wN6ZZuLNrDg6j;>A~#=b+1b2 diff --git a/packages/google-auth/tests/oauth2/test__client.py b/packages/google-auth/tests/oauth2/test__client.py index 4997d2401747..b34d2eb35ab1 100644 --- a/packages/google-auth/tests/oauth2/test__client.py +++ b/packages/google-auth/tests/oauth2/test__client.py @@ -326,6 +326,7 @@ def test_call_iam_generate_id_token_endpoint(): response_body = json.loads(request.call_args[1]["body"]) assert response_body["audience"] == "fake_audience" assert response_body["includeEmail"] == "true" + assert response_body["useEmailAzp"] == "true" # Check result assert token == id_token From 098c24c7114c369e6f472321a969f2bb9c8d28ca Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Wed, 12 Apr 2023 11:23:27 -0700 Subject: [PATCH 698/966] chore: update sys test cred (#1272) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10323 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index ba6703d1de2b9978a91017a79244a4106cbf5064..49e5275f9c49de6fd60c78acb35166bcd567a5ee 100644 GIT binary patch literal 10324 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 From 8f5742b318b10d8e6cc6bcae1327e0e7246290cf Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Wed, 12 Apr 2023 15:27:30 -0700 Subject: [PATCH 699/966] chore(main): release 2.17.3 (#1271) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 836edd0b8e2f..233565475c02 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.17.3](https://github.com/googleapis/google-auth-library-python/compare/v2.17.2...v2.17.3) (2023-04-12) + + +### Bug Fixes + +* Add useEmailAzp claim for id token iam flow ([#1270](https://github.com/googleapis/google-auth-library-python/issues/1270)) ([7a9c6f2](https://github.com/googleapis/google-auth-library-python/commit/7a9c6f2d90688e57583437c0872eb12dc5b0d833)) + ## [2.17.2](https://github.com/googleapis/google-auth-library-python/compare/v2.17.1...v2.17.2) (2023-04-05) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index d73c4346e2d7..c2e4d1d4985a 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.17.2" +__version__ = "2.17.3" From f9eb0bbcf6bedd8282b20fa4c7dad5e1f38e611d Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Fri, 21 Apr 2023 11:28:18 -0700 Subject: [PATCH 700/966] test: remove app engine sys test, disable test_aws_based_external_account sys test (#1278) * test: remove app engine sys test * test: disable test_aws_based_external_account sys test * update tests --- packages/google-auth/system_tests/noxfile.py | 58 +------- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../app_engine_test_app/.gitignore | 1 - .../app_engine_test_app/app.yaml | 12 -- .../app_engine_test_app/appengine_config.py | 30 ---- .../app_engine_test_app/main.py | 129 ------------------ .../app_engine_test_app/requirements.txt | 3 - .../system_tests_sync/test_app_engine.py | 22 --- .../test_external_accounts.py | 4 +- 9 files changed, 4 insertions(+), 255 deletions(-) delete mode 100644 packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/.gitignore delete mode 100644 packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/app.yaml delete mode 100644 packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/appengine_config.py delete mode 100644 packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/main.py delete mode 100644 packages/google-auth/system_tests/system_tests_sync/app_engine_test_app/requirements.txt delete mode 100644 packages/google-auth/system_tests/system_tests_sync/test_app_engine.py diff --git a/packages/google-auth/system_tests/noxfile.py b/packages/google-auth/system_tests/noxfile.py index 592f52183681..e44dc5660a08 100644 --- a/packages/google-auth/system_tests/noxfile.py +++ b/packages/google-auth/system_tests/noxfile.py @@ -24,11 +24,9 @@ import os import pathlib -import subprocess import shutil import tempfile -from nox.command import which import nox HERE = os.path.abspath(os.path.dirname(__file__)) @@ -41,9 +39,6 @@ EXPECT_PROJECT_ENV = "EXPECT_PROJECT_ID" ALLOW_PLUGGABLE_ENV = "GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES" -SKIP_GAE_TEST_ENV = "SKIP_APP_ENGINE_SYSTEM_TEST" -GAE_APP_URL_TMPL = "https://{}-dot-{}.appspot.com" -GAE_TEST_APP_SERVICE = "google-auth-system-tests" # The download location for the Cloud SDK CLOUD_SDK_DIST_FILENAME = "google-cloud-sdk.tar.gz" @@ -74,11 +69,6 @@ # The full path to the gcloud cli executable. GCLOUD = str(CLOUD_SDK_INSTALL_DIR.joinpath("bin", "gcloud")) -# gcloud requires Python 2 and doesn't work on 3, so we need to tell it -# where to find 2 when we're running in a 3 environment. -CLOUD_SDK_PYTHON_ENV = "CLOUDSDK_PYTHON" -CLOUD_SDK_PYTHON = which("python2", None) - # Cloud SDK helpers @@ -89,8 +79,6 @@ def install_cloud_sdk(session): # our tests from clobbering a developer's configuration when running # these tests locally. session.env[CLOUD_SDK_CONFIG_ENV] = str(CLOUD_SDK_ROOT) - # This tells gcloud which Python interpreter to use (always use 2.7) - session.env[CLOUD_SDK_PYTHON_ENV] = CLOUD_SDK_PYTHON # This set the $PATH for the subprocesses so they can find the gcloud # executable. session.env["PATH"] = ( @@ -176,7 +164,7 @@ def configure_cloud_sdk(session, application_default_credentials, project=False) TEST_DEPENDENCIES_ASYNC = ["aiohttp", "pytest-asyncio", "nest-asyncio", "mock"] TEST_DEPENDENCIES_SYNC = ["pytest", "requests", "mock"] PYTHON_VERSIONS_ASYNC = ["3.7"] -PYTHON_VERSIONS_SYNC = ["2.7", "3.7"] +PYTHON_VERSIONS_SYNC = ["3.7"] def default(session, *test_paths): @@ -292,50 +280,6 @@ def compute_engine(session): ) -@nox.session(python=["2.7"]) -def app_engine(session): - if SKIP_GAE_TEST_ENV in os.environ: - session.log("Skipping App Engine tests.") - return - - session.install(LIBRARY_DIR) - # Unlike the default tests above, the App Engine system test require a - # 'real' gcloud sdk installation that is configured to deploy to an - # app engine project. - # Grab the project ID from the cloud sdk. - project_id = ( - subprocess.check_output( - ["gcloud", "config", "list", "project", "--format", "value(core.project)"] - ) - .decode("utf-8") - .strip() - ) - - if not project_id: - session.error( - "The Cloud SDK must be installed and configured to deploy to App " "Engine." - ) - - application_url = GAE_APP_URL_TMPL.format(GAE_TEST_APP_SERVICE, project_id) - - # Vendor in the test application's dependencies - session.chdir(os.path.join(HERE, "system_tests_sync/app_engine_test_app")) - session.install(*TEST_DEPENDENCIES_SYNC) - session.run( - "pip", "install", "--target", "lib", "-r", "requirements.txt", silent=True - ) - - # Deploy the application. - session.run("gcloud", "app", "deploy", "-q", "app.yaml") - - # Run the tests - session.env["TEST_APP_URL"] = application_url - session.chdir(HERE) - default( - session, "system_tests_sync/test_app_engine.py", - ) - - @nox.session(python=PYTHON_VERSIONS_SYNC) def grpc(session): session.install(LIBRARY_DIR) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 49e5275f9c49de6fd60c78acb35166bcd567a5ee..9889b85c991a1937c6879e3170b99c911fc935f3 100644 GIT binary patch literal 10324 zcmV-aD67{BB>?tKRTGol5JO{a=tGzC^8!r>t>W+&Iwo}9SOJDD!&ZUehIkUHPypAf zZWG_Tv1|*((yW8K=*^lyu1DcxUCHEEjey#XKsjAyg3y1D(I_HCO#(i`UMKin!?)=r z48)dQ(N<~fJ;$w7e8?z>jNi_G&=c?5{U6(r!q$MsDK(0I;m;n>f(gh+X?}%Rio%EY zSM7_UKG79uuheA6PI7Rn@8_Yv!7j7ZwZu0BjGrSW>|C(sdm5AN;4bxlwRtvah3wM^ z_4ON_Lo|`pMduOc-ru~ubqbzFy0?-!Orv%h0;-Zv8LB{9@v7UrFsCCP?7eIeNY*m$ zJYGB!&xB{}I{)lK-YRVtc&DI$6Y-tkyHC$jw*A!yiODZwJo#OVT-;BunF39yX=F8Y z1uT0Ho;S0hRGHP{hLCZNjT1hzeU+0{0jO0{%@=v;#t?9uJzCx&@1gTI!%6JfDqsyJS2YA85>F!K`YP zH|Gq}iH&)0fZ)1Za6tOMT-*=vV#ir`l&mWvP|!zfw<7!ii54_~ij=Wyc0_@)OIjx) zc8HkN1^Ig8sMPfk?J zNN{@6sYr*-MZ}_;lxh3pUyvaiml4h{+_FGL6Q?52PKk|rV3;DAh90O%YN81|ZW9H!TQ+g77f%M}6KL&vrWaX{YuQ1H^o^ zF`yo)9sAULX6nhh*jRF@ZCHyUzrC&B(v!)*G`JO^b}K*47B^PR?;9k&xvA7z?E&Q$ z;lL2KJM|IGz8#LgisZ!$4m3MFFo{heycB2WuDHbJcz6lLcjLlPf6e!shks7vTVDY! z%Dxb~@RCMn6~SbsM5UIKRyS>+X;x!FzVjRpVWR52mWhN-I z27yy_$v@{<(ogzAc-K^D=un6uj8E&Ob+l8!@zx%ZeUS%PoKJWnf)P+`>?<`5IJT!` zQiQjs(k_UhIvqYyTrouQYiHpx&{4h+Os6)KmqQR9m+ejRbzwV*`WVsWoCnu@M%BZK z>w!wBgq|26t=E*p@Dzt-g2#p>jdp7bJvbeEvI*<$dSw8z7oGzf3X!aieV^$1@mZCS zcSqL8#m4OUok+GW2QBtf)_qjh8lY9?a!M-ia8zrC^iVTZcvDm##h4+oN?_ zK&re`lAHBx+Uh*1+r+wf_u#Y)fjLZ%nZl^#`D}7u={CgSSKmS^ca@Q>5Eu?+xi0H2~6%Av?-;C>Nn9((z79pD+jZ7VB;x9%Jy;DILFT6Il zjo4*R$Yv^E1AgjY05ROH4{j|Kc=9n#0JhqbjZ37t|IGGsfZG-w6}YVPk(&3uHy;6u zGnw=Suvq;;MVp5wUby&?pnsq%Nd8TbmENIIQO9UCMjEKDK+k%Ate<6q0Pla-@J@2l zH^B?hmkw~}D;jVS2e;p3b4vl~ugp1O1ODacOp6SpkQt;ycDWNhDd`+zMH|<wDtQE7ofm#mn~IZQ3#4z+ z-#AeuZ>k|stM8s9af;hS=qY-7HBW9#Ku!%qQc%FdJHnz{DM;dbG6Nn;2S-SIs1qG% zf4zSOZ9o0;&n2ZT_3a3IeL}a-kZ^cZ0Sc{3T$+CLhqMNYg$L7HcUVgjVgSn?NdWO* z@Qaoin4H3CHUjk-@Gy~Bjudz_-;#&H9bR1nbd;&jq-r(K>vvL;={*7oHgW8_vIyvq zV#p`Z-xp5uMn!Mtf}ZF}v9q>$e*sz73sIv@z;CoC6Wt}28+4wM>jhCV0D#%S7@u;q z$z$pSj@#!PrDKG2rLN96AXujDdiHI?9r%AvukKU*yI#T(nL&h?xeIah(cx*hYh;`o zbv_>Fowx8eog<4@hJh}8C2xTgt%|;-o+eQ7!yDGIgg$KceO+u;gCsxg?ibF_U1C;* zmC|r}PU*}tT$4quQ)XL=Jj&0aAX4=83;Gxy5ZPl7!puhF*7RdQFskZ{whNL7?qiJ+)5qnf++vLkn$p@|NAMseH0(|#X zy=aG?6vPZG$`f`ZiqDqA#xvnt2dWE0h25ii+EVPVepdYt0P zppTNWSloVfhp{l9{)W)>2Dp0IfmoY@#G%o%WK}=DS~F@((%3BD=ndw&36Dnws3`C? z?f|UUNCo+@9Liet9tCLD!?Q_D zTV~ekeH0UGa&0To!i$T=Iqb^%6{o!}bFGrwmz8l3NI>uJ6I0VY4@qAwxf$^%BVsAf zmvWswj48E;WR}-#QxKnF1Dx)slbp>^r4tA0XH}6*HIfJ(lf>3ur@qj9=oEYd>Vn<+uud{hUDc~_@j13NjTQQUkzqQ77s%J!BQuCy@YbWBG2(wtSWS8fTUq362sgfv#5PgPQMx? z5|@xS_F+>|crOsX;gE7HnqzVTPuc_s089AQ;D5JH71yM6n9%2A*Eh=~rdHLGl-%G! z3ClQw>P$79#g6A0dd63HiWwTW#4>dXMUo7ARhCU?@&{ikSr@e$uIGif*Qy=x8thC6=n558k`4MO6CCg3~{D8U}|O7 zYun=E+!{8c#cYVB_qn+~3G&`sj;#d*GUDo`V`jwfXuJl#cOglO&3e_mvoc(f2RYXm zRk+Y0N9s>h6cck(eidb?JZ_-}0ra-sluIOL*)ml+9gF0P`)qEXMyOz168#hgk%z zxu+*LGFO2^1ZIY725%Dgg2D}6>}@(^sfuY?$m{#$qtEOzEBh-MR(Qw(0s_G;kHLNi zpk$#3Ab7DfeY;obT7su5ydT5}uD)hcb`{U(kAS@MCvMQ4okGuknJe0B9(!2qIVmkju^K)NnWnR~6EsjuT)AXlf`dfD7YJ5R@ z1!$+XT-Sj^0+W&X+;{0pHYTD)aA>*Vw*RGS1bvMFW1=YlEqoOFFfN>w+IAMrYvC#i zT!an~P(h0RrrPKp%H>;?-P#`iKa|&9NI&<AFE%TGgos<5U)=#+ds zgt2R*xNz3(4$6Z}RY#B&MGI*{^m>U8LfO$=D30!^W47Q_ySjlTU=VkoNerzOfHfLZ z>yrn|JcreX;pXLbvI_?IzVhz08H^9$!0Rp(yvn6;IstB zB^uYRG#O2Qe{stkP~x}ThWP|2aTv6X?Q9v>`y5YqlUrwESW4ou6=gLw+zH?AP8WZ@ zyVhiGLqh;+7LHV)NP@K0g8b!&>TirjVqQL{ppN#{M*11BOn}bFFTL4t5R}K7Xhj%; zDJYVAMBdsF4-ZuC$j}*qt}ks_w{2*P#q_BH(Tyh^L6TH=%3$ZMimyhE+No8om@agG zT@(0$#j7^3Zt^+Td*~W0eZ$+i(n>9CK=taYQghR}kP$BXO|sCS29mY&c_`=4IkpiG zB!m(NYP8VTAMc?eTnSBQZu7A6)Ok1PhqtZ|Cp<<_oqKBY5nOJ$Ris4k#0vJUAb%WE ztz9L=K$GPlyJY3w-CPi}!d6-owVykGFAa3T zZK`UsB{+AEPYYHd^HwMrB-N=OxT}Lj$5&MEHE|MTqx_>l2P=y9?tetFT6-TeSa-fj zJ*n}-D8p;lwAyYEDDt!RZI)I(2V)JfH&{qzodK$UPacH+>^tn4zXFXdABFC~jh&C! zJDmqBZJL!s!;SDBjAF!9ZH?m3VwQLd7N}M>cK>;*d#1E}oo!WF0x+gTy*h-~LSbSg zJo{{G(u%FDz0jZSVY2Pq&P&|U`M(BBz&Eq@JQ+NsiKJ-5eQ04>AnDZr$QzMw(;IUN8=PLXf}zZzH4REv}*_TD2J>z|VZk zPheKkA9{&v2ZW^0=&vDaOgFgO2{S|t{T13W$s|lj6SfwK^fZ9(bBl^cr;__Y5fr{q zgF%-oQporKuW)9cVh}k-r^!s+WC>@~chUq)?gc71-K3n{Qm!XPAw=z z57pi(aC|mx#;--EvNBz(s}`6BhDLPmBuhgR8zJN}so=!t zTWM-Xs+6F~upAl#1owRLrAEgGni>KEt7qjXHI90JRkY?QlA3D^7{>SA`RQPEiAAG| znaJVwB16BxGUl`}UGK{^IKmW^m?C2IS|z9eWE!o|D}E(Ap^wH7{bwRZ^;iOlu67n!RHg$4pG8yjQYb5qDxS?>TV%AkzPDb*j9C?cI0ow3H7RF6r=5IjPM)|OlZxqiqqFGhk%=T0|y>jJ2OZ(K@h)gv!e4tIf#K!@8y)b z3kLpMNG+@w;Bpl7Bs+#&p%wZAkg0r*L6v&Q zU(Wjpw5nQbdc}we5GPCC4E8C%E@m@@PlKAl*^HG7zaaO2QuPwGBBG*1-;8=XZyj)SGH#P`EFl>z(Wx7Z^9$VdLk5TO5sC;XZ>RrE_U`Vw_*-VW#4LvQRp!fHXWA2}x zaZ*U?C4FuB!@T~JPCseXBMGu4hr${YnA+KQDLF7sGZ?RuTNUg`wAgnCvKyE2ZI&Tc z;%sd>9`|PH$hdNy01)wM*#ee}zDPoUN7D;rvggV9I+cf=*S`j&HsUgeF-`-f%}jxd zejIDGJONmKF}U5tVjC{<>D6NA?7O8SE1K%gXmElwsNWPrQF_8*rO7(sx^0Dj21;fn zc)Z>Xe1Ete2y6t65igel2l0Jv}AnvBznk0ea$OjiD zqmBCpyY;dFrSTc|SiCS5In5WSR#1k>GClrY?lJT%-z#qC&VI5w$KZO$1uAX%_Cnl+ zG{btLI4?Y0yYlA4Re&mXNJ8Ege*O-bxf2 z@OE94<=U**S+7*@erSGbqvqWe5CkPMB{>;M?JWg+94Q@@4;Qq{YQ32fpo?eAewxXS1Bpfv(PV!9FoucK;C!4>|ahsOv#4#9S_zp{wPg4?bFDn$VlkhTA6QFe8OCaqGv27Y{qP1I5VPkQ1%*@4r$z=m(?u4$Q)0% z*w+YwWVqLi4bK3-PN5P3!{#@`%?~TrY+O`aGUeL$W8?{wD1*_9##oKp_zQ<4sR_Xi zx8f&SmI*_8a@eK@;ZNM+7~+N~I9C!vJTZ;mU^EM!ob@3ehs>yhZwdLHDJ82jRB5G; z!dKPd59Isfr66M8EE<{B9_}1uF?L16lm|-P1Fnq zrnnjMQtg+-HM2vFLHpocQI=*e^(t zq|n0e_e3B_VxeS>Fk21`dAx1IW*6BDWwUD0{WUD@Do~(t+$1<4wKF0 z=(^Y2+}(8LWIPCo7K?3q%?dkT(aC0`l^<9^{T@URk|Z~CIhgbKGR=omxgV3jbXVfu z$=yacDTA8nB&YO+>mE;0hW{X_lDzZs3z3W-Q^*V)Us;_dBoq2tDe{rEVwgNB_DU;2 z0lm47kT{_e2J+Pr#20FFqoz%ugEfDv;q`2=(%zLHcVSAge5KGc2MC_uCI2g+OG#u~ z`PtAU(#a-`FY&1qM;S#H0vNp}S^Wzeobw(~o_(%@7oI!$m+Qaz;fEbvt}{K@CH3%f z>)b{ipd~R0tw(G|1iMcs+|n~@gANYzYj11-5v0fY9Z*ZU0j}@W9eY~w^O3JaAQ3(a0fI1xTkG;xU=#tc@E<$O)%*_Dp2Z~ z3JqrY0xM6uXh_#x2aVkXVg~ehMC(|V%QW8Mw+Q&VB*^$mVE0AOfj=C#yai0N=XB*~ zL~=;JvTAb(bT^fl^k_Ky7A?^U94lF6VS`x>o5T*X*YyZ?O;7#Z^xZ;tAooq<>XEJ# zHtvQ2@R~fi!$+yQ#60r^s!YrV*5D7gWE8SC0e$biez3^gFM>6hl|+cT2M8M*BZ}87 zWcf#gF%Q^(9f`ND5nKF|&ZDbVFM$OEEl*Z<6FX{o*{+viEAY0#@x8wnFCj_Eu7JoI z)TzxSYyvwn2n3-4veXB7=hGKDv^7Y+{Kn3by9L}f&XHBEBXpUUL+gNna} z7+J}J2hjqXl|>kQV9FLN7^MD64T&yu+^hCmYoHc?jTktu!pq4rFMU%|3FodF;S>D4 zi3|9{8A0?}W>2P#HwuyjmUy7A03P|c2y)D=LNrZdK+pGTw zvIED?!iACeK%&ctP*1azYLHn^4>WS#`oaq%ra7+Y_6%VHTIeI0n|ZhkRfTA*60yjGq*s!x3E5k zne(KX%ax=ixiwzvwk1WOCVuD${_|gTYL9qfj<58rskok^Nr?l06FnhGQJjoKc_@1m1+*vC2$hZ{rTFh*ti))zg?`XpSm@%=&Bm&|CyPn zYHK0q$EJIZlo;0Tu^n3DQeU3hwon2*7~J3h&@H~=XTS;sc$|31y-jFLbWb@>{g94d zB2){mHNDHdDp)x#9o@uuEL3mhOt@p;SRfIZgSt(xad(waswW_h*&b@U^VwHYrIe)2 z6}1bV6}s^jU^2RG5S%e@$~Jonv{HFtNo}+Ay@1(e-^avp$0#6-<=Tbb5v#Y<3uJE6 zBakz7&<7Zj(%dp8-cvl~r4J|NwgQz%4n-XyS7E5Q3TW;fq^s7K8Zbo&=H0}hKxMzV6?7J4)vYrOQsOm7C)kl4 za({j{9E9?m9q0)v?SnGAAP;PN^?60l?^Tj26;=mcIQ=&m`Sz-$H&HAQuJH{qw1h(S@T}OOafZr5l2DeL6s44_wGJO z3tje4wt7}uxAa@K0a~jno}A$2OO5E%`(Ol7SB8c90qcM-w>Fi$Yz`3Y(CV1t7*HmU zSF~L#tWDsbWRsKJrb{y0%|9(PM!iK9KZnZKZ%1JiR*5{}l>MIn#l^IUPGA0faUh~G zF4Qthv z!Cy)Rk?_V(xre6N-gkq%(W(Cvem{VW!c-*Iy0P}HSdpwmpgEV5v)96$Bnj~AYU5~A zop?>E?5D`C9CAD-RC`mD=W$!FavvcQf=nv+*I5b%Zu5b7`ikYMI=j6TWUfSCZ3)98 zzI1>jFZarqdtI;w%%bT%`9bz28l+?ZdJi+Y!I*N>)sD`vS~Pm*N^wy%2^m$}wlqkd z@RB`ExgS)VjV=^(Hh$j$k}_;Wc#H4q>pE-4Ow`=lRtxQ`qLx}$!U@p>P7~W4Fi{Q8 z%fhG>S?l4;9h2w%>+?dSii!A!f5wY}{EGw}Je~&c8B|14$(8OfQPB2@*ukk;U}<{F z?R6Zy#7gz0T+4B~fBhZ1r^RZ=3ERed*P*>9s@W05Yh-M5`Rc$_o<2WN85#wSARx6Asj12J$t5y<}FBJq&Z zq84FSMR_VRs!&Jh>Lr=15&Dg+zGV$}&a=XoieMrWtbHZc-)LVfu!?3) ziLfy>rO@4bAoumG7b4y&R){F{VBl_-FnsbbGkp@V+jo%47`hDPrueh|F?p`%x+Ox* zW}R(r2a-8YJ9ZKs&p;g&{k}PsZpUMw-zYJZb?lLt(rZhcLa7t{(&i7dJl1uw2R=|l zh#9wKNqFy*Yg%L>`g4*P-eNkGDEE94$!gOHf@0Y8moX9UlMO9Nh$ai)EgyRs&rizZ z(2Co4e}e~sS71Rc*6nNSxRY(quC|^$>ChD$u$EThLAwFiRZb64)hB2>kd}(yl@Y~^ zBzplj5R)^H@LoezJF}3}k|LIbXuu@qmt;!;L=c#n5fXrASq_LRg`1H=w~+%AJ|a)s zMm>|x7B9afIVR%{p&}EK9s4@y^|I#aESm(jZ?|!(=dSCzR@Uv>?-raVsqw5BKy#dac@Ki`kTL-?@78o| zgY95m;QeK-Lx%oRk%H7Ku9A+4K^nojwq%BJ)!^{~GV^^n!3`uEA2ZUYoe~xAF$4YG z8n&0Y(oe5AZ>#&Nc0>v@XuH-*ragW1mU?1V@pKX593mlct#ezkX5FHSBW~NoQc;?W^|Bj&y@*YPWB(;ZOf$e>Igsufk?N=3rTP>($M7f=F|Z zL=*HDPaedi1my;6lxM+Q04|N(spQZ-jeaRCqj4_%F#B#)6m>u}b#UVv?)Y}~-^sq7 zYpa(O?9ybl7nlE1netxq5F=3+n!^JD+XKn;zPBW8%Jm2(=P@(BO0iyFibYTmfU4gn zx48T9BPGZ|XmvhT{6w@%^CQAg>1=XnRWhNScl^42eKL@9TIKP)XcFjBeLn$e~jb)}NIE!UFr}xvNM`V=O2?(x@*E_TzUe5JR4$FySjQ zZcwxbRS-JF5LamY?gymd`$(=#IeBI^`OJ5b=f5Nx45K|kNMgk zv1M;_AnhyW`ku0xkRV_gvgFx1g~E?Xl#D3m2MZW=2t9S!hKGfxxcJZ*ZrJ& zWq;2~zVSFaQ*K~VQANnnA$((mM`#Z~?ldd_PY)VPcqpG78nH;R!?ddn16JrQyYb(3 z*daP*c4G=A8NkicW)V4ySr#u^7_?*@JnR}|E_v;UH2>!F_zJErU;m{nJMSEEYk=|( zgZ2+|8cBq`192C9M<9DV*_*0^8!9JzP#kZ@ zc(ANh8;$3XX=q*l#PUoP1flr6*Q9_})b9P-I0vUAqcBnPx&jd9?(whAc}5#dOQA#~ z&a3u)PzxO~XVz2$voM;=cJU|n3>irfIij1WFWQF4H*~^eeu1v}I>Gy!*NfT$(n2#= zLb7epe{^7f#>jWQq(S~ISw%YASJ6Q{8*cr>41Hd+zu=mQXt*hH3F{-PnTD2%i;IO9TcoQnFPR`QIw;rG z9f_`hDkS;NM?nih(*X%c*!Xo33&7bQ+;#DE8q6pOUTaaWv)%-r!ML0X6~e)J0f literal 10324 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 Date: Thu, 27 Apr 2023 19:00:21 -0700 Subject: [PATCH 701/966] feat: Add smbios check to detect GCE residency (#1276) * feat: smbios check for GCE residency * Refresh test token --- packages/google-auth/google/auth/_default.py | 2 +- .../google/auth/compute_engine/_metadata.py | 41 +++++++++++++++++ .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../compute_engine/data/smbios_product_name | 1 + .../data/smbios_product_name_non_google | 1 + .../tests/compute_engine/test__metadata.py | 42 ++++++++++++++++++ packages/google-auth/tests/test__default.py | 10 ++--- .../tests_async/test__default_async.py | 8 ++-- 8 files changed, 95 insertions(+), 10 deletions(-) create mode 100644 packages/google-auth/tests/compute_engine/data/smbios_product_name create mode 100644 packages/google-auth/tests/compute_engine/data/smbios_product_name_non_google diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index 997f2b4e880b..4effeec9edf9 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -281,7 +281,7 @@ def _get_gce_credentials(request=None, quota_project_id=None): if request is None: request = google.auth.transport._http_client.Request() - if _metadata.ping(request=request): + if _metadata.is_on_gce(request=request): # Get the project ID. try: project_id = _metadata.get_project_id(request=request) diff --git a/packages/google-auth/google/auth/compute_engine/_metadata.py b/packages/google-auth/google/auth/compute_engine/_metadata.py index 16c5e2138ffc..87e79a9e9e9a 100644 --- a/packages/google-auth/google/auth/compute_engine/_metadata.py +++ b/packages/google-auth/google/auth/compute_engine/_metadata.py @@ -59,6 +59,47 @@ except ValueError: # pragma: NO COVER _METADATA_DEFAULT_TIMEOUT = 3 +# Detect GCE Residency +_GOOGLE = "Google" +_GCE_PRODUCT_NAME_FILE = "/sys/class/dmi/id/product_name" + + +def is_on_gce(request): + """Checks to see if the code runs on Google Compute Engine + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + + Returns: + bool: True if the code runs on Google Compute Engine, False otherwise. + """ + if ping(request): + return True + + if os.name == "nt": + # TODO: implement GCE residency detection on Windows + return False + + # Detect GCE residency on Linux + return detect_gce_residency_linux() + + +def detect_gce_residency_linux(): + """Detect Google Compute Engine residency by smbios check on Linux + + Returns: + bool: True if the GCE product name file is detected, False otherwise. + """ + try: + with open(_GCE_PRODUCT_NAME_FILE, "r") as file_obj: + content = file_obj.read().strip() + + except Exception: + return False + + return content.startswith(_GOOGLE) + def ping(request, timeout=_METADATA_DEFAULT_TIMEOUT, retry_count=3): """Checks to see if the metadata server is available. diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 9889b85c991a1937c6879e3170b99c911fc935f3..97ff27317279d0c167b27e58bc3ddc8b538ff641 100644 GIT binary patch literal 10324 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 diff --git a/packages/google-auth/tests/compute_engine/data/smbios_product_name b/packages/google-auth/tests/compute_engine/data/smbios_product_name new file mode 100644 index 000000000000..2ca735d9b34e --- /dev/null +++ b/packages/google-auth/tests/compute_engine/data/smbios_product_name @@ -0,0 +1 @@ +Google Compute Engine diff --git a/packages/google-auth/tests/compute_engine/data/smbios_product_name_non_google b/packages/google-auth/tests/compute_engine/data/smbios_product_name_non_google new file mode 100644 index 000000000000..9fd177038edd --- /dev/null +++ b/packages/google-auth/tests/compute_engine/data/smbios_product_name_non_google @@ -0,0 +1 @@ +ABC Compute Engine diff --git a/packages/google-auth/tests/compute_engine/test__metadata.py b/packages/google-auth/tests/compute_engine/test__metadata.py index 568812056a9f..7f3386276309 100644 --- a/packages/google-auth/tests/compute_engine/test__metadata.py +++ b/packages/google-auth/tests/compute_engine/test__metadata.py @@ -29,6 +29,15 @@ PATH = "instance/service-accounts/default" +DATA_DIR = os.path.join(os.path.dirname(__file__), "data") +SMBIOS_PRODUCT_NAME_FILE = os.path.join(DATA_DIR, "smbios_product_name") +SMBIOS_PRODUCT_NAME_NONEXISTENT_FILE = os.path.join( + DATA_DIR, "smbios_product_name_nonexistent" +) +SMBIOS_PRODUCT_NAME_NON_GOOGLE = os.path.join( + DATA_DIR, "smbios_product_name_non_google" +) + def make_request(data, status=http_client.OK, headers=None, retry=False): response = mock.create_autospec(transport.Response, instance=True) @@ -45,6 +54,39 @@ def make_request(data, status=http_client.OK, headers=None, retry=False): return request +def test_detect_gce_residency_linux_success(): + _metadata._GCE_PRODUCT_NAME_FILE = SMBIOS_PRODUCT_NAME_FILE + assert _metadata.detect_gce_residency_linux() + + +def test_detect_gce_residency_linux_non_google(): + _metadata._GCE_PRODUCT_NAME_FILE = SMBIOS_PRODUCT_NAME_NON_GOOGLE + assert not _metadata.detect_gce_residency_linux() + + +def test_detect_gce_residency_linux_nonexistent(): + _metadata._GCE_PRODUCT_NAME_FILE = SMBIOS_PRODUCT_NAME_NONEXISTENT_FILE + assert not _metadata.detect_gce_residency_linux() + + +def test_is_on_gce_ping_success(): + request = make_request("", headers=_metadata._METADATA_HEADERS) + assert _metadata.is_on_gce(request) + + +@mock.patch("os.name", new="nt") +def test_is_on_gce_windows_success(): + request = make_request("", headers={_metadata._METADATA_FLAVOR_HEADER: "meep"}) + assert not _metadata.is_on_gce(request) + + +@mock.patch("os.name", new="posix") +def test_is_on_gce_linux_success(): + request = make_request("", headers={_metadata._METADATA_FLAVOR_HEADER: "meep"}) + _metadata._GCE_PRODUCT_NAME_FILE = SMBIOS_PRODUCT_NAME_FILE + assert _metadata.is_on_gce(request) + + def test_ping_success(): request = make_request("", headers=_metadata._METADATA_HEADERS) diff --git a/packages/google-auth/tests/test__default.py b/packages/google-auth/tests/test__default.py index ccd4b2b9a63c..36adf0d33af0 100644 --- a/packages/google-auth/tests/test__default.py +++ b/packages/google-auth/tests/test__default.py @@ -781,7 +781,7 @@ def test__get_gae_credentials_no_apis(): @mock.patch( - "google.auth.compute_engine._metadata.ping", return_value=True, autospec=True + "google.auth.compute_engine._metadata.is_on_gce", return_value=True, autospec=True ) @mock.patch( "google.auth.compute_engine._metadata.get_project_id", @@ -796,7 +796,7 @@ def test__get_gce_credentials(unused_get, unused_ping): @mock.patch( - "google.auth.compute_engine._metadata.ping", return_value=False, autospec=True + "google.auth.compute_engine._metadata.is_on_gce", return_value=False, autospec=True ) def test__get_gce_credentials_no_ping(unused_ping): credentials, project_id = _default._get_gce_credentials() @@ -806,7 +806,7 @@ def test__get_gce_credentials_no_ping(unused_ping): @mock.patch( - "google.auth.compute_engine._metadata.ping", return_value=True, autospec=True + "google.auth.compute_engine._metadata.is_on_gce", return_value=True, autospec=True ) @mock.patch( "google.auth.compute_engine._metadata.get_project_id", @@ -831,7 +831,7 @@ def test__get_gce_credentials_no_compute_engine(): @mock.patch( - "google.auth.compute_engine._metadata.ping", return_value=False, autospec=True + "google.auth.compute_engine._metadata.is_on_gce", return_value=False, autospec=True ) def test__get_gce_credentials_explicit_request(ping): _default._get_gce_credentials(mock.sentinel.request) @@ -1246,7 +1246,7 @@ def test_quota_project_from_environment(get_adc_path): @mock.patch( - "google.auth.compute_engine._metadata.ping", return_value=True, autospec=True + "google.auth.compute_engine._metadata.is_on_gce", return_value=True, autospec=True ) @mock.patch( "google.auth.compute_engine._metadata.get_project_id", diff --git a/packages/google-auth/tests_async/test__default_async.py b/packages/google-auth/tests_async/test__default_async.py index bf1a129a8216..e1dbb1c09c20 100644 --- a/packages/google-auth/tests_async/test__default_async.py +++ b/packages/google-auth/tests_async/test__default_async.py @@ -366,7 +366,7 @@ def test__get_gae_credentials_no_apis(): @mock.patch( - "google.auth.compute_engine._metadata.ping", return_value=True, autospec=True + "google.auth.compute_engine._metadata.is_on_gce", return_value=True, autospec=True ) @mock.patch( "google.auth.compute_engine._metadata.get_project_id", @@ -381,7 +381,7 @@ def test__get_gce_credentials(unused_get, unused_ping): @mock.patch( - "google.auth.compute_engine._metadata.ping", return_value=False, autospec=True + "google.auth.compute_engine._metadata.is_on_gce", return_value=False, autospec=True ) def test__get_gce_credentials_no_ping(unused_ping): credentials, project_id = _default._get_gce_credentials() @@ -391,7 +391,7 @@ def test__get_gce_credentials_no_ping(unused_ping): @mock.patch( - "google.auth.compute_engine._metadata.ping", return_value=True, autospec=True + "google.auth.compute_engine._metadata.is_on_gce", return_value=True, autospec=True ) @mock.patch( "google.auth.compute_engine._metadata.get_project_id", @@ -416,7 +416,7 @@ def test__get_gce_credentials_no_compute_engine(): @mock.patch( - "google.auth.compute_engine._metadata.ping", return_value=False, autospec=True + "google.auth.compute_engine._metadata.is_on_gce", return_value=False, autospec=True ) def test__get_gce_credentials_explicit_request(ping): _default._get_gce_credentials(mock.sentinel.request) From 86f5ecc37d3b704c20ca6b21c45c21049b281722 Mon Sep 17 00:00:00 2001 From: Jin Date: Wed, 3 May 2023 12:09:15 -0700 Subject: [PATCH 702/966] chore: pin urllib3 < 2.0 to avoid breaking changes (#1283) * chore: update token * fix: avoid urllib3 breaking change --- packages/google-auth/setup.py | 1 + .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 2 files changed, 1 insertion(+) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 50ac473baf0d..e45512a02935 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -29,6 +29,7 @@ # install enum34 to support 2.7. enum34 only works up to python version 3.3. 'enum34>=1.1.10; python_version < "3.4"', "six>=1.9.0", + "urllib3<2.0", ) extras = { diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 97ff27317279d0c167b27e58bc3ddc8b538ff641..674e0059e115cf4275d644f97c5d3c8655ffd7e1 100644 GIT binary patch literal 10324 zcmV-aD67{BB>?tKRTF(c)eVw^^P`(oMLxFX5D^O3uh^V9g}bBEN1_)}W-$_~PypAf zZWEw+I9+LP62~blZc_ai!IqCB#^(vQ^sU~CeSl6oxSD&+lqn}UB5qs51uQ#_V@g`| z<@lA>K=nCzIxW(}A6XQ5%hz2ibObbI?&eKIS>{E@;3vWiZ4k|*({$uG5P;Uv8}PBw z3R!fp4$m`ps}|P~C@IV^L_AeOhLQnAJPsb!DlKmv@{Bpf_7Ml^BJk%+7)1x98S39( z(kJ*oSg$eR_r~zh=xTdc=cjusI4_WGlS_Fp&uH;y0h*|RKy`d#M)U2}2?@gzbznc+ zGfVmyZrX!dwU1i-nCXxOA(k$zsIK1xMSWC@|VJ7w;569mlNygd33|PlagQ{Ft^;3xp}!?S@A z&Sa&NO4CpHmLZsGfFBU0wvUHJx2REMF=->I-htFW#HsIMP zN-%0ON`w(`ISUfCZrr%=%11xPI|fKSv)=#X7BJL2+R5wzTwP}Itp2m#N7J#+ z+e`|iWc1RO9P<+FW3}S<{i!t06Vk+>lrr_RT?kfhH6&m;7-_jQ@F%da7(ay70N%9W zyiH7o)P)WU7H+|dui_)i@XoQJpzGac4P9vk^J1p^-sxZOo$}z#aciW19=}ou zIY|S>E908)W&6Q4O@RA!d@so9;re;7aJf@9&Xd)pToeUqb%s-MD=W z#{go5HEw8RN}*>z#|Qr`D}vaSsxme(^8^){=+7<05Oyi9A^JZLxkMbb)ZUW_ZRlhj zXV799^@{%-CuZ3&g;1l{Ezw$1ve8RI<^EMl?ute?kGQbFc(!MBM9ysOugzc`1qv#? z)A25=7-h#k@rhj+3LI_q7|m+4%*B*MY=SA)K(*|)WX`C&)6dAV6*B@VC@M(^l4h3o zI!SOkZ9Ka7WNVNzOdw9!#C{uo4ym(bbzgBkr(U|@zd~(b zE&1ZOp2~p@Vf@bD+@=p&AejDb1XN5unfietaqyE!+zjBb6lO6A;@)ylPD)ZaWey3U8?3@sD z>DwftXDbHO7~uZjytDm%+Qn3!GeQ8znD0>KnTlxpbr755sf%4J#U<)FgAQa3`0q~X|HRWR z;Fd>eR$4Gp&fp|Bf3_oFk&yx;UxjyuOVY6-iQXAFo^Sf^T~&NhF;~fx2%j6%8qQ9O8jcAx1oKTQ@yxVb zy(3UZ0Q0%M??IVnh~+qiy&j^-0s@qYA@)W~Cu>FhQUvCjk!BWihhBV2M3xZW0)a0x z1?*fyB;gE?Ymj~KEBF-}lZ4>0xPY$%gES_|BuOm&1t-#kaJJSm5*2}=ialI@IYH*d zD{wJNWbpDMoASodx0;{F-Wrwr?brfv)lLn zCG-|1CG{<}b%~30!QJwy28j<(z=bSCE>MIc(5fze`gZ}UH+UG^0htLvIl8)mzdgapSbzwTSR~5kZwOjtgc%m*vbpbhsK$mp2eK6 z=Gs8&vCy&J!I|@W=T%789YywuYH?A=kJgB$O^KIBs~^Qzp?y4BpdTM82qj~Cw&$$_ zRu`2+9Sks{$@6tu)a7Hc+IzO4sFtw0my|PiZP;OHWv#5NlZeqx{j32}1rbhs^Adqq zbc2C{??8QFyAjg`elTv)LY7dx+Nbfs5ukQMBF1vy`fllRvL`ffhku4++BgVa-R%;2 z{`#MAIoz?)J|902#*1vgC?islG}Ol#)9AX?&SOK=`?Tq^LB4^WS?0hi(6KTeW0yDa zpLC+qo>3nc22C-iMCMs#{Qji+`m4;-T=IpXl8OWPTu+|h6)HYrT4!Skt)@cJU|j|- z^mPG5oAnBS;NP`=ondCK$cT_JU&(-~z#NsvRho!cND9X18L+n071kug&ccV0g$BBg z37Qs=0&JaC7TVLSO>}2iE(43>?*F44)Lr#eKq`K69_ac9)C$N>_-)od3yhs>{Kg(q zC5j`Va@q~CuRv2Tjd0;9G@LbR^N)dnMLC~IPOJ|wa)z*lg8Zm6;&8AA6-f2gG~euv zVe1X?@|ZK)95{dklN*eH+#KvO#~-rLGG~+2w~2C?{08J>cIkKc_4##RUeNdFRO0Nx zxOBg|@GU1Yc`kN}vK0C3eaj+&vWM~*m*X?pE*4hu;Au0O??V!Jp4td}eSwvR!719ePpeH-gn`C_qrZyd$xy1j!PWTF*8N|#ioveIdPWdm1fpIHbaqMcZObf4Ky~H z$D6VIr4?sgkyLEN?Z|!$*-}Spg57se*y3eg>8P?{Rm4M1yL(h!^yecqmzFkc{a*Pl zD+%0=wT8n=>XY9{WZTCrZl=Xj`d9T<9zO3kE1rYwQzk?+a#OZ0{>4>2!^$l$yvKf$ zOCdJJ)!x=e%{(A@tGGx%fty)9l29-DbFE!fGn{-jL^?>t~=4aa~5RJAuPPmz{q@Ebz}hc8FZhZ&2%nS`ZI0; zo2gZt{FrElwE{BF_@b+A3Gfd5Sfaz(2}RElO!0FIfxS7r8iqm(JN^k+DaUTU!K=~> za!_+3mOFKWqchi8r4}4>bvKcn-*d`a+v(a@J=!Q_GCy+HSwaqA_#OohVgrIugX+KU z7zTklP+p<&8Wp+-gn@A+6EhqDIbU3DH>l;fPui7)_U{64yjOMbcCFEs=9JRo9k{Kt zIwDVgto4Q9YH-bP)J?t$7sc-!x>y;>ZD!!Sh4E}i>?=xlG^?KWdR0Zu;mv6_vLGp~ zo`Nt%GDH`)^p!~VmZ4jTxcqq`|omXuuzgW3%(MF{1dU4=ja+rR9CBHT>GkYQ%k&9lWCj)(0MhK|5GBd`lGZ9_5}vxGLO zabeV{nUu4Sp*~x+d9ze-k8+=UY9T`563$tVCM@@&6)lWSp!*lCo>62lqM+UDTJGP+ zqY*{u7DQz1<^1)QOWh#QF-sG?$am+lK0$0ep*uhR!wxh_$Y6)@=1)lI$$=E7??J3A z)bac+9~fGrUt!3*0a^3H=q1&lLFWkWS+sV3E3vJYSSzk9tSMRi=D z0P5?7TAimnIZfnKq*h_ag@}q`cWP{6@$buT>DaH-A5FGl8JK5}{-TZr?)k4HM8M-#+%*cblmU);1xfrV zXtK8`*IW&Q=gSQ{?CB#|3ksKXVUt(EDo5A zjx^Wwq$zD4>*UJ#*Cy^nkuy`!moyQj+=gOYWQ3abYpP)Lp|l<0{7wx zq)$hJ7^TmQnw|AH9envSqnw!6ht<3FB)7B+@CJc2S6-AHedd(AC~nviYy+)s+la4Z zcPjWbhA?+M)k4VUQZ(GvOyvM{$|`a0gGg4b0qlQy9aX^ay&z2WX?edV(AAtCob*ow1WX!f)0@goaT>MHUG^P zQw_-k2gQ%mO@;I?QrpG^^j$Pi@@qSE3wQ!ggTBR^X_1iFva@ zYh1cbL}SSbqzT1GBOqcQI?rqx&}eNP74v<_gLhk(VU3D)iL5-5DSt^7ti?JGg=DyU z7r8s+9(Q62TbJ+WfYgm2o^x*jn^ABs%m$zG7-YNbY-UH`E@OpsZJmbf%xsZZPvHH( z1kOT)CnKw}bd`sD--*%hACH1@FVHk*S`3j2uZvz;+qOc)UipgDQKe`C#jY(GI1-dN zDzK?%0`tdvO=s4|li=|9tD*so%j05-^3En!X5aKq*zV3W>GD5m$wWMWq?5gmrc`p< z0s}!WFProqizvB3<5pf%g!%doxW>PjBeAOvPw{CQ5~CRDpN>*|ay7w3Aw%P6xKmrB zasq^d)VZW{^BrY#H&ENUBwBlG6I!H?RR{cQ(|~j>kRaD0U4F;Z97$&i2Y`hcW!*qt?UMyc=_ z`At-+Qtl`jtB%DdX6W=;BwY2l4PUdUJgNZcTuMGtchQI_ZMLv zwyWnC6iO^9RIm_k3(!>E2j@vd)gg3o^%g0+cyb8hwPx`YjEC)kE2)?$19wRfVkWu4 z2O0cH26SP(2_9iQ=&NV@df5$&^GV#|vF^`a!#~J;8Z~eWg#bQ;7M`ImB{mI7QB+q< zlCw|J19+SsxG+@KHseY4F5gf+GJt6kN&^tp<_zoU&JCXN_3Ak$nOIl(59`H*lZ0UZ zH167&WoT^O^)=ed`Ea=OlLVp2Ru>QzGSTYdp_KNW91isgT);J2loxE#hu;%0{3D?D zAI5M`&!G4bc;u3Q^LghbvK^^J%s`T%3&5?yNlvL2P=yIXcl@Oj7=P!x4%ndsp@S0D%d@i4#lp9OEgxbT$f=rui6x-xX9q1$&I+EKRmVDdb}G2t zMCV!$FN4MMa3cpWNZ*p=(h;_B}1m4EUG2O&)#SEJ78v5-mRFWMm`si zt3lFMpY+R=d!XFYe(ZWE6!w-9F3~REz^XKD*>?F|caOrv>yVn6qxE$Ypz201%+G92 z*Mf(4g8L(Ghs#^vLN1x<^m}m6M^Vs0VNW1`CA50UB^f!-BGWZ3&N2~9PLu^(eWESE z=5>BJNdq&z$Gw8pHdw1MN_2P)@4YE)707MV^q^K2iTlXPShUsIOt`3X?9P;6<#>|E z?m%Ld5_jMV>ioGnK}`n{p=@%X^zTM6rA#X-2@}f0H0w_{mK_3r%uSwVN;!YNUVB$2 zb<9jCU2x^ZEgMe-f{DS5P9~S#yMNZ+@ZgKhf_)6hGrWg~V5FAnRa1k*&qV14Q-!3x z?2+wXt5;?O^bBj4mzR1v{caxXmP5BdM zK1$PVgb*G=f))P4}3sjd1qeu5Cm0Z;$IX=szIZn=el;S*c+%)9|eI#Cqmi zKGtAlAj~?9cW^WaNupi@K$~4uy`PNXF-Ih=t(TaAx_Yq}|6y=B$!u!p(+@fCeN_Z| zDpB83Qn|oD9cJj{xJ>w7zbmmLYJTHE6ve1%B#ic0*?muOAHUQcQV95nu(@wdGY!?U z6740UPdB>up*^$b*yfS0v%``(FS(V!qfCq*@@HEbcs>|381#GWs~cI^d&HaWKT$_3ah^|xWT1z{3D?O6HGe|J47Y$Jy(j@k#p7h7nxlR zXT0&z(Xmtja-?mC z{4v~W>qaUHJ}16wz#I@BWbFUFz{7F&ymTAxg)|Wy?T+Q~=d_Sf2>UfX0x270M;fe`rYN$+itk|Si)v;$GQ8R}GBcfTA=r++{3<#Q*Du(mTdU$0&4S!V?s zFDMe=x;f+fWo|-;oY`({*O0@m`=zJH#%p@A5q5m*GOd8>-?+$tLZh42T^f?QFRf=* z&9vy0Iom?q&Nh|BlQ1+}0a3lXB6tdh;QW%V7BIsN=;*#u+(SJ(z z8=W(o8^R|q35sh0u!n1!H%e(tLs2zz+vvuaUXHOn2%Qc1q~)qm^Z^p0{U})p?*5MLx!LmSbkGJu4uuMOQV>>OxvomV?{d^!4Rui zp2_y)G5E^bWUq#>BjgqroGHj zg1oS8jPoFm8UszYm{Kz@R$#}iF!b(zsW4Po&U`vD!dbDINt4x}g zd0lO^8>>`^@P9Mqt!JSKvgjpu)B~+@DE$HbjTI(c%H{D}>2wO#NiSlAtne}b@z-OK=PO;ka z%~Nk^1VBO@T?LM2is29oc9SBnxs$2<4j8{>9yR`dS1zUtf>Rjo2$A0@PS|@QH0hv@ zPz^tpCxmxy=17Y25|%I55Xg^rXNH?F!$nYUm6ZW{dRu+%g^!*mhxkR)w^f52Sb@y; zOE6;%E(9`(KwI6)R?t9cM_jvi_f4N`az>YkI8Z znpTBW(gMnLQb3Bne~K|Gn`yL|>BjM;5MfRLO~LN+v_3c95}gK22DfMgKI2}VgcH;G zsHUG-s@b<8+^}NrWyu4jCd+wNsH>)CSdjScK=T);29$hA&{d;F^pAg9%Vvhd&YlP5 zVpa*JQB=r*C&OdE;bfMWKbLiam+fs;&u^#Q#oL^#sX|ba3D%avp)y))oDsDrRyh;J zig_uv&N}nv9i&vt_5;9byay!_{y3kh^H!DQvMwb)viR5%e;V7XN;};_xZU+RC8nK$ z2b^MTLj0ZMX!Wu3ep?yEepmH|sAmpE1Ds?~M5&7W}J4?68 zU}${8Zf{O;XITYl_7X;gIqb`DwIMz(C_j#x%vVSZxwu(!rXAz*cnqL2AB-Is&Vy+Y>Rv7DAOjPRp4Z>!@Ct zG9azpPN~D#9akS2RQ-NiP>kI>EWZRK+2#svIOwVgQ=DjZ$Lgk=@IBD}ldpj-brt$E zI`d*Ta~+8xd4Yky8WW3lKJ3TSb!nF)M)^GyXp>w6e#Xui~KLN8qq=*vBQV6yC`?d5g z(-2lQ(RsggR(p;Dw=+Y$UaI#JX#-8G=ebT=zafI%QPy5=~pvkEjX$|E<*#QeDv^(Ut;_ zL6=3cLeNTC38NLI0a~P9`p9`m_}ZKgb**jP)2H52(Ri3eAe>!V`Ny3|#a;>Bov82> z!{Q}H|4efo*Z%9-xSK{^psfZ$x(lpP^x6LpA^cLbJh>^Oi$;>OVD+X5+YNf_bFkq^ zR9wRqe-VH}R!svK!)TsH$7uIHgzOL*U)A(>NEYZ`kJXvL5I_Y}{s1_TUE%bdivtH@JNt3g(q;9ZVxzV44I* z4<-Z#C9CRMzS{c0>g3;D-iECYnP>Y!iO5e#G`pb7<=G7AWSjH-A{ovlMxUWgsnwCt z@QU753Way`k~B7>Sywqw!(PhaC(D8FBcIou-e-)3nhf3x*fmP^(fpQc?K>M-=L%dV zqmdwCjy6*-a76zc;mY=8>7OJP`2K18ci$=28b`Fjcw6q-9R=n*7TLShfVn7fhAA-2 zdmeBiIbe}>Ae~c*tyHeH&p<#ciW|lYEO#X`T;3BBC5b!~QoeBvKg$ZK;&?SUOx*}Z zE&eE;9ZBT?f!QdsM5)di#x1LQ(Su0e7|)B|;rNtitDoaw|6+WbVVCESCnK5pI&=M3 z=Y_S?j^h0&z=@(}MhZi6a4tLGm^){2Ds7f`)*2)MJ;XaCSCf&;yRFAk*sFFdy~G?p zOwSNONqQSIhQifg=b>%rhEeXcu_p8pUyJx zp6vKxk~@UZ$qnP!pj^CSTg2+kvFLKd%n*~3Pu!C+WimC8>t5ZE_27cqr2E_s{L_=mXJD7K%LETt7B~j$e<1;k`EsH_vvQ`9(xYWotjwhy8>Jn%8fD)(!#qs& z_T>>djLJgW63?~p1*`W}C`+<7^FQ0zGLSJaux=7tvoqP#>dQ?<(V2G~5vKkNiUi94 z=ZF5r6kiSrErNSVBGJp@Ct9lLu&=~yUGVRjgdlc&Tq2l7+oP`iUt4 z5F$o#RM==-+(O+MP-<0o!A%~cI1^k9jfO0*d4tL#-uV>kyO$lJWov|VT7lx! z3M8>aDW;hr+b7@o9&&rCiG8#b#o`mwW_Yyk*;u|9$d;;MX;R7obn27K#AqB5{ z(4pE=dcj}iE*<`)wPeIIhX^~iW*=E&qqh6t6Smig?%3F2X%SZ^g24E)JN2e`7+G5envBO z;KD7d?oO7?R;860HmLpW9TDPlEifYoh4&u65OeXt@U-h0i&FrhN2pQhti|sx0kWrzu;V{^?I|(?N~GPYmv{c;E>G7Ej>vp7wdA zGLKtp3Y+m)MPZsz-ND|7*<3x>3X5IwuInXTzDn(Cehc z8u-V~MP$~cPQpM1(z-}3?Hp$8lEbV+S{{d7Ew-(c^#FJP!6s*XT(udXA$8#v_dfD& zUqASuRwjEQ5amVFsb(q<)kDjBui5%lV^17`Rk`keU2z~Ia~b5l@n?@(J zk?eXw*iEygw&fQna|a_TpZ#BgL=u)pG+@B4tIfb;OaU1Q&m{4}VW35^xQBFf!$Eoc zmC_Dnfl^o|Mu~jLoZ)y`pp(8v=NC4_Mt%tnYo8pWp@u*~z{Ux*I=n>6u*zikrHpgw zVH^6J8PADEx(mew4CjLCe%e7du3CW?d~6gJ} zWaaz>O#xzP5zAu)yc?wxD?K6^WU|)N`OLr`QD2sYy@4l#tQJ(lGIo z%OQ`1A-j3^7>{3?EY$}%HOHZx@J@McVOqwMp>G2Rdo@>A8(yfw967u8oDu`EjhB0VjE5({az~@;TF^hm+PN7rV^?s<3fr6c?}|~+tT!Y8 literal 10324 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 m Date: Mon, 8 May 2023 14:27:09 -0700 Subject: [PATCH 703/966] chore: update token (#1289) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 674e0059e115cf4275d644f97c5d3c8655ffd7e1..f253a74cb4cc9fa00854cc2b9496d0ee1a2763a9 100644 GIT binary patch literal 10324 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 literal 10324 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 From 6f1a466ad3c31f50266d8181445fdd95f069acf9 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Wed, 10 May 2023 02:14:27 -0700 Subject: [PATCH 704/966] feat: universe domain support for service account (#1286) * feat: universe domain support for service account * update * update * update * update token --- .../google/oauth2/service_account.py | 211 +++++++++--------- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../tests/data/service_account_non_gdu.json | 15 ++ .../tests/oauth2/test_service_account.py | 128 ++++++++++- 4 files changed, 250 insertions(+), 104 deletions(-) create mode 100644 packages/google-auth/tests/data/service_account_non_gdu.json diff --git a/packages/google-auth/google/oauth2/service_account.py b/packages/google-auth/google/oauth2/service_account.py index 37e1e568a768..bb2670525ee4 100644 --- a/packages/google-auth/google/oauth2/service_account.py +++ b/packages/google-auth/google/oauth2/service_account.py @@ -76,10 +76,12 @@ from google.auth import _helpers from google.auth import _service_account_info from google.auth import credentials +from google.auth import exceptions from google.auth import jwt from google.oauth2 import _client _DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds +_DEFAULT_UNIVERSE_DOMAIN = "googleapis.com" _GOOGLE_OAUTH2_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token" @@ -136,6 +138,7 @@ def __init__( quota_project_id=None, additional_claims=None, always_use_jwt_access=False, + universe_domain=_DEFAULT_UNIVERSE_DOMAIN, ): """ Args: @@ -156,6 +159,9 @@ def __init__( the JWT assertion used in the authorization grant. always_use_jwt_access (Optional[bool]): Whether self signed JWT should be always used. + universe_domain (str): The universe domain. The default + universe domain is googleapis.com. For default value self + signed jwt is used for token refresh. .. note:: Typically one of the helper constructors :meth:`from_service_account_file` or @@ -173,6 +179,13 @@ def __init__( self._quota_project_id = quota_project_id self._token_uri = token_uri self._always_use_jwt_access = always_use_jwt_access + if not universe_domain: + self._universe_domain = _DEFAULT_UNIVERSE_DOMAIN + else: + self._universe_domain = universe_domain + + if universe_domain != _DEFAULT_UNIVERSE_DOMAIN: + self._always_use_jwt_access = True self._jwt_credentials = None @@ -202,6 +215,7 @@ def _from_signer_and_info(cls, signer, info, **kwargs): service_account_email=info["client_email"], token_uri=info["token_uri"], project_id=info.get("project_id"), + universe_domain=info.get("universe_domain", _DEFAULT_UNIVERSE_DOMAIN), **kwargs ) @@ -262,20 +276,28 @@ def requires_scopes(self): """ return True if not self._scopes else False - @_helpers.copy_docstring(credentials.Scoped) - def with_scopes(self, scopes, default_scopes=None): - return self.__class__( + def _make_copy(self): + cred = self.__class__( self._signer, service_account_email=self._service_account_email, - scopes=scopes, - default_scopes=default_scopes, + scopes=copy.copy(self._scopes), + default_scopes=copy.copy(self._default_scopes), token_uri=self._token_uri, subject=self._subject, project_id=self._project_id, quota_project_id=self._quota_project_id, additional_claims=self._additional_claims.copy(), always_use_jwt_access=self._always_use_jwt_access, + universe_domain=self._universe_domain, ) + return cred + + @_helpers.copy_docstring(credentials.Scoped) + def with_scopes(self, scopes, default_scopes=None): + cred = self._make_copy() + cred._scopes = scopes + cred._default_scopes = default_scopes + return cred def with_always_use_jwt_access(self, always_use_jwt_access): """Create a copy of these credentials with the specified always_use_jwt_access value. @@ -286,19 +308,20 @@ def with_always_use_jwt_access(self, always_use_jwt_access): Returns: google.auth.service_account.Credentials: A new credentials instance. + Raises: + google.auth.exceptions.InvalidValue: If the universe domain is not + default and always_use_jwt_access is False. """ - return self.__class__( - self._signer, - service_account_email=self._service_account_email, - scopes=self._scopes, - default_scopes=self._default_scopes, - token_uri=self._token_uri, - subject=self._subject, - project_id=self._project_id, - quota_project_id=self._quota_project_id, - additional_claims=self._additional_claims.copy(), - always_use_jwt_access=always_use_jwt_access, - ) + cred = self._make_copy() + if ( + cred._universe_domain != _DEFAULT_UNIVERSE_DOMAIN + and not always_use_jwt_access + ): + raise exceptions.InvalidValue( + "always_use_jwt_access should be True for non-default universe domain" + ) + cred._always_use_jwt_access = always_use_jwt_access + return cred def with_subject(self, subject): """Create a copy of these credentials with the specified subject. @@ -310,18 +333,9 @@ def with_subject(self, subject): google.auth.service_account.Credentials: A new credentials instance. """ - return self.__class__( - self._signer, - service_account_email=self._service_account_email, - scopes=self._scopes, - default_scopes=self._default_scopes, - token_uri=self._token_uri, - subject=subject, - project_id=self._project_id, - quota_project_id=self._quota_project_id, - additional_claims=self._additional_claims.copy(), - always_use_jwt_access=self._always_use_jwt_access, - ) + cred = self._make_copy() + cred._subject = subject + return cred def with_claims(self, additional_claims): """Returns a copy of these credentials with modified claims. @@ -337,51 +351,21 @@ def with_claims(self, additional_claims): """ new_additional_claims = copy.deepcopy(self._additional_claims) new_additional_claims.update(additional_claims or {}) - - return self.__class__( - self._signer, - service_account_email=self._service_account_email, - scopes=self._scopes, - default_scopes=self._default_scopes, - token_uri=self._token_uri, - subject=self._subject, - project_id=self._project_id, - quota_project_id=self._quota_project_id, - additional_claims=new_additional_claims, - always_use_jwt_access=self._always_use_jwt_access, - ) + cred = self._make_copy() + cred._additional_claims = new_additional_claims + return cred @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) def with_quota_project(self, quota_project_id): - - return self.__class__( - self._signer, - service_account_email=self._service_account_email, - default_scopes=self._default_scopes, - scopes=self._scopes, - token_uri=self._token_uri, - subject=self._subject, - project_id=self._project_id, - quota_project_id=quota_project_id, - additional_claims=self._additional_claims.copy(), - always_use_jwt_access=self._always_use_jwt_access, - ) + cred = self._make_copy() + cred._quota_project_id = quota_project_id + return cred @_helpers.copy_docstring(credentials.CredentialsWithTokenUri) def with_token_uri(self, token_uri): - - return self.__class__( - self._signer, - service_account_email=self._service_account_email, - default_scopes=self._default_scopes, - scopes=self._scopes, - token_uri=token_uri, - subject=self._subject, - project_id=self._project_id, - quota_project_id=self._quota_project_id, - additional_claims=self._additional_claims.copy(), - always_use_jwt_access=self._always_use_jwt_access, - ) + cred = self._make_copy() + cred._token_uri = token_uri + return cred def _make_authorization_grant_assertion(self): """Create the OAuth 2.0 assertion. @@ -418,6 +402,18 @@ def _make_authorization_grant_assertion(self): @_helpers.copy_docstring(credentials.Credentials) def refresh(self, request): + if ( + self._universe_domain != _DEFAULT_UNIVERSE_DOMAIN + and not self._jwt_credentials + ): + raise exceptions.RefreshError( + "self._jwt_credentials is missing for non-default universe domain" + ) + if self._universe_domain != _DEFAULT_UNIVERSE_DOMAIN and self._subject: + raise exceptions.RefreshError( + "domain wide delegation is not supported for non-default universe domain" + ) + # Since domain wide delegation doesn't work with self signed JWT. If # subject exists, then we should not use self signed JWT. if self._subject is None and self._jwt_credentials is not None: @@ -544,6 +540,7 @@ def __init__( target_audience, additional_claims=None, quota_project_id=None, + universe_domain=_DEFAULT_UNIVERSE_DOMAIN, ): """ Args: @@ -556,6 +553,11 @@ def __init__( additional_claims (Mapping[str, str]): Any additional claims for the JWT assertion used in the authorization grant. quota_project_id (Optional[str]): The project ID used for quota and billing. + universe_domain (str): The universe domain. The default + universe domain is googleapis.com. For default value IAM ID + token endponint is used for token refresh. Note that + iam.serviceAccountTokenCreator role is required to use the IAM + endpoint. .. note:: Typically one of the helper constructors :meth:`from_service_account_file` or :meth:`from_service_account_info` are used instead of calling the @@ -569,6 +571,14 @@ def __init__( self._quota_project_id = quota_project_id self._use_iam_endpoint = False + if not universe_domain: + self._universe_domain = _DEFAULT_UNIVERSE_DOMAIN + else: + self._universe_domain = universe_domain + + if universe_domain != _DEFAULT_UNIVERSE_DOMAIN: + self._use_iam_endpoint = True + if additional_claims is not None: self._additional_claims = additional_claims else: @@ -592,6 +602,8 @@ def _from_signer_and_info(cls, signer, info, **kwargs): """ kwargs.setdefault("service_account_email", info["client_email"]) kwargs.setdefault("token_uri", info["token_uri"]) + if "universe_domain" in info: + kwargs["universe_domain"] = info["universe_domain"] return cls(signer, **kwargs) @classmethod @@ -632,6 +644,20 @@ def from_service_account_file(cls, filename, **kwargs): ) return cls._from_signer_and_info(signer, info, **kwargs) + def _make_copy(self): + cred = self.__class__( + self._signer, + service_account_email=self._service_account_email, + token_uri=self._token_uri, + target_audience=self._target_audience, + additional_claims=self._additional_claims.copy(), + quota_project_id=self.quota_project_id, + universe_domain=self._universe_domain, + ) + # _use_iam_endpoint is not exposed in the constructor + cred._use_iam_endpoint = self._use_iam_endpoint + return cred + def with_target_audience(self, target_audience): """Create a copy of these credentials with the specified target audience. @@ -644,14 +670,9 @@ def with_target_audience(self, target_audience): google.auth.service_account.IDTokenCredentials: A new credentials instance. """ - return self.__class__( - self._signer, - service_account_email=self._service_account_email, - token_uri=self._token_uri, - target_audience=target_audience, - additional_claims=self._additional_claims.copy(), - quota_project_id=self.quota_project_id, - ) + cred = self._make_copy() + cred._target_audience = target_audience + return cred def _with_use_iam_endpoint(self, use_iam_endpoint): """Create a copy of these credentials with the use_iam_endpoint value. @@ -666,39 +687,29 @@ def _with_use_iam_endpoint(self, use_iam_endpoint): Returns: google.auth.service_account.IDTokenCredentials: A new credentials instance. + Raises: + google.auth.exceptions.InvalidValue: If the universe domain is not + default and use_iam_endpoint is False. """ - cred = self.__class__( - self._signer, - service_account_email=self._service_account_email, - token_uri=self._token_uri, - target_audience=self._target_audience, - additional_claims=self._additional_claims.copy(), - quota_project_id=self.quota_project_id, - ) + cred = self._make_copy() + if cred._universe_domain != _DEFAULT_UNIVERSE_DOMAIN and not use_iam_endpoint: + raise exceptions.InvalidValue( + "use_iam_endpoint should be True for non-default universe domain" + ) cred._use_iam_endpoint = use_iam_endpoint return cred @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) def with_quota_project(self, quota_project_id): - return self.__class__( - self._signer, - service_account_email=self._service_account_email, - token_uri=self._token_uri, - target_audience=self._target_audience, - additional_claims=self._additional_claims.copy(), - quota_project_id=quota_project_id, - ) + cred = self._make_copy() + cred._quota_project_id = quota_project_id + return cred @_helpers.copy_docstring(credentials.CredentialsWithTokenUri) def with_token_uri(self, token_uri): - return self.__class__( - self._signer, - service_account_email=self._service_account_email, - token_uri=token_uri, - target_audience=self._target_audience, - additional_claims=self._additional_claims.copy(), - quota_project_id=self._quota_project_id, - ) + cred = self._make_copy() + cred._token_uri = token_uri + return cred def _make_authorization_grant_assertion(self): """Create the OAuth 2.0 assertion. diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index f253a74cb4cc9fa00854cc2b9496d0ee1a2763a9..8a3cf40107a31dd6d46914ddac7d4194da68c2c6 100644 GIT binary patch literal 10324 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 diff --git a/packages/google-auth/tests/data/service_account_non_gdu.json b/packages/google-auth/tests/data/service_account_non_gdu.json new file mode 100644 index 000000000000..976184f8c249 --- /dev/null +++ b/packages/google-auth/tests/data/service_account_non_gdu.json @@ -0,0 +1,15 @@ +{ + "type": "service_account", + "universe_domain": "universe.foo", + "project_id": "example_project", + "private_key_id": "1", + "private_key": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEA4ej0p7bQ7L/r4rVGUz9RN4VQWoej1Bg1mYWIDYslvKrk1gpj\n7wZgkdmM7oVK2OfgrSj/FCTkInKPqaCR0gD7K80q+mLBrN3PUkDrJQZpvRZIff3/\nxmVU1WeruQLFJjnFb2dqu0s/FY/2kWiJtBCakXvXEOb7zfbINuayL+MSsCGSdVYs\nSliS5qQpgyDap+8b5fpXZVJkq92hrcNtbkg7hCYUJczt8n9hcCTJCfUpApvaFQ18\npe+zpyl4+WzkP66I28hniMQyUlA1hBiskT7qiouq0m8IOodhv2fagSZKjOTTU2xk\nSBc//fy3ZpsL7WqgsZS7Q+0VRK8gKfqkxg5OYQIDAQABAoIBAQDGGHzQxGKX+ANk\nnQi53v/c6632dJKYXVJC+PDAz4+bzU800Y+n/bOYsWf/kCp94XcG4Lgsdd0Gx+Zq\nHD9CI1IcqqBRR2AFscsmmX6YzPLTuEKBGMW8twaYy3utlFxElMwoUEsrSWRcCA1y\nnHSDzTt871c7nxCXHxuZ6Nm/XCL7Bg8uidRTSC1sQrQyKgTPhtQdYrPQ4WZ1A4J9\nIisyDYmZodSNZe5P+LTJ6M1SCgH8KH9ZGIxv3diMwzNNpk3kxJc9yCnja4mjiGE2\nYCNusSycU5IhZwVeCTlhQGcNeV/skfg64xkiJE34c2y2ttFbdwBTPixStGaF09nU\nZ422D40BAoGBAPvVyRRsC3BF+qZdaSMFwI1yiXY7vQw5+JZh01tD28NuYdRFzjcJ\nvzT2n8LFpj5ZfZFvSMLMVEFVMgQvWnN0O6xdXvGov6qlRUSGaH9u+TCPNnIldjMP\nB8+xTwFMqI7uQr54wBB+Poq7dVRP+0oHb0NYAwUBXoEuvYo3c/nDoRcZAoGBAOWl\naLHjMv4CJbArzT8sPfic/8waSiLV9Ixs3Re5YREUTtnLq7LoymqB57UXJB3BNz/2\neCueuW71avlWlRtE/wXASj5jx6y5mIrlV4nZbVuyYff0QlcG+fgb6pcJQuO9DxMI\naqFGrWP3zye+LK87a6iR76dS9vRU+bHZpSVvGMKJAoGAFGt3TIKeQtJJyqeUWNSk\nklORNdcOMymYMIlqG+JatXQD1rR6ThgqOt8sgRyJqFCVT++YFMOAqXOBBLnaObZZ\nCFbh1fJ66BlSjoXff0W+SuOx5HuJJAa5+WtFHrPajwxeuRcNa8jwxUsB7n41wADu\nUqWWSRedVBg4Ijbw3nWwYDECgYB0pLew4z4bVuvdt+HgnJA9n0EuYowVdadpTEJg\nsoBjNHV4msLzdNqbjrAqgz6M/n8Ztg8D2PNHMNDNJPVHjJwcR7duSTA6w2p/4k28\nbvvk/45Ta3XmzlxZcZSOct3O31Cw0i2XDVc018IY5be8qendDYM08icNo7vQYkRH\n504kQQKBgQDjx60zpz8ozvm1XAj0wVhi7GwXe+5lTxiLi9Fxq721WDxPMiHDW2XL\nYXfFVy/9/GIMvEiGYdmarK1NW+VhWl1DC5xhDg0kvMfxplt4tynoq1uTsQTY31Mx\nBeF5CT/JuNYk3bEBF0H/Q3VGO1/ggVS+YezdFbLWIRoMnLj6XCFEGg==\n-----END RSA PRIVATE KEY-----\n", + "client_email": "testsa@foo.iam.gserviceaccount.com", + "client_id": "1234", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.universe.foo/token", + "auth_provider_x509_cert_url": "https://www.universe.foo/oauth2/v1/certs", + "client_x509_cert_url": "https://www.universe.foo/robot/v1/metadata/x509/foo.iam.gserviceaccount.com" +} + + \ No newline at end of file diff --git a/packages/google-auth/tests/oauth2/test_service_account.py b/packages/google-auth/tests/oauth2/test_service_account.py index 6b0d1dcce6cd..c48635d4d891 100644 --- a/packages/google-auth/tests/oauth2/test_service_account.py +++ b/packages/google-auth/tests/oauth2/test_service_account.py @@ -17,9 +17,11 @@ import os import mock +import pytest # type: ignore from google.auth import _helpers from google.auth import crypt +from google.auth import exceptions from google.auth import jwt from google.auth import transport from google.oauth2 import service_account @@ -37,10 +39,17 @@ OTHER_CERT_BYTES = fh.read() SERVICE_ACCOUNT_JSON_FILE = os.path.join(DATA_DIR, "service_account.json") +SERVICE_ACCOUNT_NON_GDU_JSON_FILE = os.path.join( + DATA_DIR, "service_account_non_gdu.json" +) +FAKE_UNIVERSE_DOMAIN = "universe.foo" with open(SERVICE_ACCOUNT_JSON_FILE, "rb") as fh: SERVICE_ACCOUNT_INFO = json.load(fh) +with open(SERVICE_ACCOUNT_NON_GDU_JSON_FILE, "rb") as fh: + SERVICE_ACCOUNT_INFO_NON_GDU = json.load(fh) + SIGNER = crypt.RSASigner.from_string(PRIVATE_KEY_BYTES, "1") @@ -49,11 +58,20 @@ class TestCredentials(object): TOKEN_URI = "https://example.com/oauth2/token" @classmethod - def make_credentials(cls): + def make_credentials(cls, universe_domain=service_account._DEFAULT_UNIVERSE_DOMAIN): return service_account.Credentials( - SIGNER, cls.SERVICE_ACCOUNT_EMAIL, cls.TOKEN_URI + SIGNER, + cls.SERVICE_ACCOUNT_EMAIL, + cls.TOKEN_URI, + universe_domain=universe_domain, ) + def test_constructor_no_universe_domain(self): + credentials = service_account.Credentials( + SIGNER, self.SERVICE_ACCOUNT_EMAIL, self.TOKEN_URI, universe_domain=None + ) + assert credentials._universe_domain == service_account._DEFAULT_UNIVERSE_DOMAIN + def test_from_service_account_info(self): credentials = service_account.Credentials.from_service_account_info( SERVICE_ACCOUNT_INFO @@ -62,6 +80,16 @@ def test_from_service_account_info(self): assert credentials._signer.key_id == SERVICE_ACCOUNT_INFO["private_key_id"] assert credentials.service_account_email == SERVICE_ACCOUNT_INFO["client_email"] assert credentials._token_uri == SERVICE_ACCOUNT_INFO["token_uri"] + assert credentials._universe_domain == service_account._DEFAULT_UNIVERSE_DOMAIN + assert not credentials._always_use_jwt_access + + def test_from_service_account_info_non_gdu(self): + credentials = service_account.Credentials.from_service_account_info( + SERVICE_ACCOUNT_INFO_NON_GDU + ) + + assert credentials._universe_domain == FAKE_UNIVERSE_DOMAIN + assert credentials._always_use_jwt_access def test_from_service_account_info_args(self): info = SERVICE_ACCOUNT_INFO.copy() @@ -80,6 +108,7 @@ def test_from_service_account_info_args(self): assert credentials._scopes == scopes assert credentials._subject == subject assert credentials._additional_claims == additional_claims + assert not credentials._always_use_jwt_access def test_from_service_account_file(self): info = SERVICE_ACCOUNT_INFO.copy() @@ -93,6 +122,20 @@ def test_from_service_account_file(self): assert credentials._signer.key_id == info["private_key_id"] assert credentials._token_uri == info["token_uri"] + def test_from_service_account_file_non_gdu(self): + info = SERVICE_ACCOUNT_INFO_NON_GDU.copy() + + credentials = service_account.Credentials.from_service_account_file( + SERVICE_ACCOUNT_NON_GDU_JSON_FILE + ) + + assert credentials.service_account_email == info["client_email"] + assert credentials.project_id == info["project_id"] + assert credentials._signer.key_id == info["private_key_id"] + assert credentials._token_uri == info["token_uri"] + assert credentials._universe_domain == FAKE_UNIVERSE_DOMAIN + assert credentials._always_use_jwt_access + def test_from_service_account_file_args(self): info = SERVICE_ACCOUNT_INFO.copy() scopes = ["email", "profile"] @@ -169,6 +212,15 @@ def test__with_always_use_jwt_access(self): new_credentials = credentials.with_always_use_jwt_access(True) assert new_credentials._always_use_jwt_access + def test__with_always_use_jwt_access_non_default_universe_domain(self): + credentials = self.make_credentials(universe_domain=FAKE_UNIVERSE_DOMAIN) + with pytest.raises(exceptions.InvalidValue) as excinfo: + credentials.with_always_use_jwt_access(False) + + assert excinfo.match( + "always_use_jwt_access should be True for non-default universe domain" + ) + def test__make_authorization_grant_assertion(self): credentials = self.make_credentials() token = credentials._make_authorization_grant_assertion() @@ -464,6 +516,22 @@ def test_refresh_jwt_not_used_for_domain_wide_delegation( assert jwt_grant.called assert not self_signed_jwt_refresh.called + def test_refresh_non_gdu_missing_jwt_credentials(self): + credentials = self.make_credentials(universe_domain="foo") + + with pytest.raises(exceptions.RefreshError) as excinfo: + credentials.refresh(None) + assert excinfo.match("self._jwt_credentials is missing") + + def test_refresh_non_gdu_domain_wide_delegation_not_supported(self): + credentials = self.make_credentials(universe_domain="foo") + credentials._subject = "bar@example.com" + credentials._create_self_signed_jwt("https://pubsub.googleapis.com") + + with pytest.raises(exceptions.RefreshError) as excinfo: + credentials.refresh(None) + assert excinfo.match("domain wide delegation is not supported") + class TestIDTokenCredentials(object): SERVICE_ACCOUNT_EMAIL = "service-account@example.com" @@ -471,10 +539,24 @@ class TestIDTokenCredentials(object): TARGET_AUDIENCE = "https://example.com" @classmethod - def make_credentials(cls): + def make_credentials(cls, universe_domain=service_account._DEFAULT_UNIVERSE_DOMAIN): return service_account.IDTokenCredentials( - SIGNER, cls.SERVICE_ACCOUNT_EMAIL, cls.TOKEN_URI, cls.TARGET_AUDIENCE + SIGNER, + cls.SERVICE_ACCOUNT_EMAIL, + cls.TOKEN_URI, + cls.TARGET_AUDIENCE, + universe_domain=universe_domain, + ) + + def test_constructor_no_universe_domain(self): + credentials = service_account.IDTokenCredentials( + SIGNER, + self.SERVICE_ACCOUNT_EMAIL, + self.TOKEN_URI, + self.TARGET_AUDIENCE, + universe_domain=None, ) + assert credentials._universe_domain == service_account._DEFAULT_UNIVERSE_DOMAIN def test_from_service_account_info(self): credentials = service_account.IDTokenCredentials.from_service_account_info( @@ -487,6 +569,22 @@ def test_from_service_account_info(self): assert credentials._target_audience == self.TARGET_AUDIENCE assert not credentials._use_iam_endpoint + def test_from_service_account_info_non_gdu(self): + credentials = service_account.IDTokenCredentials.from_service_account_info( + SERVICE_ACCOUNT_INFO_NON_GDU, target_audience=self.TARGET_AUDIENCE + ) + + assert ( + credentials._signer.key_id == SERVICE_ACCOUNT_INFO_NON_GDU["private_key_id"] + ) + assert ( + credentials.service_account_email + == SERVICE_ACCOUNT_INFO_NON_GDU["client_email"] + ) + assert credentials._token_uri == SERVICE_ACCOUNT_INFO_NON_GDU["token_uri"] + assert credentials._target_audience == self.TARGET_AUDIENCE + assert credentials._use_iam_endpoint + def test_from_service_account_file(self): info = SERVICE_ACCOUNT_INFO.copy() @@ -500,6 +598,19 @@ def test_from_service_account_file(self): assert credentials._target_audience == self.TARGET_AUDIENCE assert not credentials._use_iam_endpoint + def test_from_service_account_file_non_gdu(self): + info = SERVICE_ACCOUNT_INFO_NON_GDU.copy() + + credentials = service_account.IDTokenCredentials.from_service_account_file( + SERVICE_ACCOUNT_NON_GDU_JSON_FILE, target_audience=self.TARGET_AUDIENCE + ) + + assert credentials.service_account_email == info["client_email"] + assert credentials._signer.key_id == info["private_key_id"] + assert credentials._token_uri == info["token_uri"] + assert credentials._target_audience == self.TARGET_AUDIENCE + assert credentials._use_iam_endpoint + def test_default_state(self): credentials = self.make_credentials() assert not credentials.valid @@ -530,6 +641,15 @@ def test__with_use_iam_endpoint(self): new_credentials = credentials._with_use_iam_endpoint(True) assert new_credentials._use_iam_endpoint + def test__with_use_iam_endpoint_non_default_universe_domain(self): + credentials = self.make_credentials(universe_domain=FAKE_UNIVERSE_DOMAIN) + with pytest.raises(exceptions.InvalidValue) as excinfo: + credentials._with_use_iam_endpoint(False) + + assert excinfo.match( + "use_iam_endpoint should be True for non-default universe domain" + ) + def test_with_quota_project(self): credentials = self.make_credentials() new_credentials = credentials.with_quota_project("project-foo") From f486716b4dc597d96592a4f4fb28ee717d048abf Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Wed, 10 May 2023 11:33:28 -0700 Subject: [PATCH 705/966] chore(main): release 2.18.0 (#1280) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 8 ++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 233565475c02..6e6cb4d16cf5 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,14 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.18.0](https://github.com/googleapis/google-auth-library-python/compare/v2.17.3...v2.18.0) (2023-05-10) + + +### Features + +* Add smbios check to detect GCE residency ([#1276](https://github.com/googleapis/google-auth-library-python/issues/1276)) ([22d241b](https://github.com/googleapis/google-auth-library-python/commit/22d241b2060cc8fcc8c5005ccc8fbc932a7f66f6)) +* Universe domain support for service account ([#1286](https://github.com/googleapis/google-auth-library-python/issues/1286)) ([821c1b6](https://github.com/googleapis/google-auth-library-python/commit/821c1b65289a23855855f16b9c7a651fdbc1e4a9)) + ## [2.17.3](https://github.com/googleapis/google-auth-library-python/compare/v2.17.2...v2.17.3) (2023-04-12) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index c2e4d1d4985a..fa3257aba561 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.17.3" +__version__ = "2.18.0" From 0c3b2ad2768cf64a37fd5a2b0524f2989171325b Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Wed, 17 May 2023 13:52:02 -0700 Subject: [PATCH 706/966] fix: self signed jwt token should be string type (#1294) * fix: self signed jwt token should be string type * chore: update sys test --- .../google/oauth2/service_account.py | 2 +- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../system_tests_sync/test_requests.py | 2 +- .../system_tests_sync/test_urllib3.py | 2 +- .../tests/oauth2/test_service_account.py | 13 ++++++++++++- 5 files changed, 15 insertions(+), 4 deletions(-) diff --git a/packages/google-auth/google/oauth2/service_account.py b/packages/google-auth/google/oauth2/service_account.py index bb2670525ee4..a0268970ceb9 100644 --- a/packages/google-auth/google/oauth2/service_account.py +++ b/packages/google-auth/google/oauth2/service_account.py @@ -418,7 +418,7 @@ def refresh(self, request): # subject exists, then we should not use self signed JWT. if self._subject is None and self._jwt_credentials is not None: self._jwt_credentials.refresh(request) - self.token = self._jwt_credentials.token + self.token = self._jwt_credentials.token.decode() self.expiry = self._jwt_credentials.expiry else: assertion = self._make_authorization_grant_assertion() diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 8a3cf40107a31dd6d46914ddac7d4194da68c2c6..3e07328e601d6b13602dda5e8e85195e5f9e4de1 100644 GIT binary patch literal 10324 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 z Date: Wed, 17 May 2023 21:18:13 +0000 Subject: [PATCH 707/966] chore(main): release 2.18.1 (#1295) :robot: I have created a release *beep* *boop* --- ## [2.18.1](https://togithub.com/googleapis/google-auth-library-python/compare/v2.18.0...v2.18.1) (2023-05-17) ### Bug Fixes * Self signed jwt token should be string type ([#1294](https://togithub.com/googleapis/google-auth-library-python/issues/1294)) ([17356fd](https://togithub.com/googleapis/google-auth-library-python/commit/17356fdd92da2123f989f67a5cebf82c78a3bc12)) --- This PR was generated with [Release Please](https://togithub.com/googleapis/release-please). See [documentation](https://togithub.com/googleapis/release-please#release-please). --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 6e6cb4d16cf5..249b0ea177b0 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.18.1](https://github.com/googleapis/google-auth-library-python/compare/v2.18.0...v2.18.1) (2023-05-17) + + +### Bug Fixes + +* Self signed jwt token should be string type ([#1294](https://github.com/googleapis/google-auth-library-python/issues/1294)) ([17356fd](https://github.com/googleapis/google-auth-library-python/commit/17356fdd92da2123f989f67a5cebf82c78a3bc12)) + ## [2.18.0](https://github.com/googleapis/google-auth-library-python/compare/v2.17.3...v2.18.0) (2023-05-10) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index fa3257aba561..b766f25e12e6 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.18.0" +__version__ = "2.18.1" From 79b6ffa770a148dcda73e19e92faa813086a89dc Mon Sep 17 00:00:00 2001 From: Jin Date: Sat, 20 May 2023 11:15:22 -0700 Subject: [PATCH 708/966] chore: update token (#1297) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 3e07328e601d6b13602dda5e8e85195e5f9e4de1..2a6ac251a043b67ac57870e279ed2eb2525eb943 100644 GIT binary patch literal 10324 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 literal 10324 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 Date: Mon, 22 May 2023 11:25:31 -0700 Subject: [PATCH 709/966] chore: token update (#1299) * chore: update token * chore: update token --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 2a6ac251a043b67ac57870e279ed2eb2525eb943..033c2df24d9e356914d0baf3270af31014e68dc7 100644 GIT binary patch literal 10324 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 From 0e86351656e50bc6a109ecf2bdeb30acdc5bf56e Mon Sep 17 00:00:00 2001 From: Jin Date: Mon, 22 May 2023 11:57:57 -0700 Subject: [PATCH 710/966] feat: expose `universe_domain` for external account creds (#1296) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: expose for external account creds * add universe_domain as info property * fix info error * adding coverage of explicit universe_domain assigning * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot --- packages/google-auth/google/auth/external_account.py | 8 ++++++++ packages/google-auth/tests/test_aws.py | 7 +++++++ packages/google-auth/tests/test_external_account.py | 9 ++++++++- packages/google-auth/tests/test_identity_pool.py | 10 ++++++++++ packages/google-auth/tests/test_pluggable.py | 6 ++++++ 5 files changed, 39 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/external_account.py b/packages/google-auth/google/auth/external_account.py index 646e31340179..436cb34ccd6f 100644 --- a/packages/google-auth/google/auth/external_account.py +++ b/packages/google-auth/google/auth/external_account.py @@ -52,6 +52,8 @@ # Cloud resource manager URL used to retrieve project information. _CLOUD_RESOURCE_MANAGER = "https://cloudresourcemanager.googleapis.com/v1/projects/" +_DEFAULT_UNIVERSE_DOMAIN = "googleapis.com" + @six.add_metaclass(abc.ABCMeta) class Credentials( @@ -82,6 +84,7 @@ def __init__( scopes=None, default_scopes=None, workforce_pool_user_project=None, + universe_domain=_DEFAULT_UNIVERSE_DOMAIN, ): """Instantiates an external account credentials object. @@ -105,6 +108,8 @@ def __init__( a workload identity pool. The underlying principal must still have serviceusage.services.use IAM permission to use the project for billing/quota. + universe_domain (str): The universe domain. The default universe + domain is googleapis.com. Raises: google.auth.exceptions.RefreshError: If the generateAccessToken endpoint returned an error. @@ -125,6 +130,7 @@ def __init__( self._scopes = scopes self._default_scopes = default_scopes self._workforce_pool_user_project = workforce_pool_user_project + self._universe_domain = universe_domain or _DEFAULT_UNIVERSE_DOMAIN if self._client_id: self._client_auth = utils.ClientAuthentication( @@ -186,6 +192,7 @@ def _constructor_args(self): "workforce_pool_user_project": self._workforce_pool_user_project, "scopes": self._scopes, "default_scopes": self._default_scopes, + "universe_domain": self._universe_domain, } if not self.is_workforce_pool: args.pop("workforce_pool_user_project") @@ -458,6 +465,7 @@ def from_info(cls, info, **kwargs): credential_source=info.get("credential_source"), quota_project_id=info.get("quota_project_id"), workforce_pool_user_project=info.get("workforce_pool_user_project"), + universe_domain=info.get("universe_domain", _DEFAULT_UNIVERSE_DOMAIN), **kwargs ) diff --git a/packages/google-auth/tests/test_aws.py b/packages/google-auth/tests/test_aws.py index 805aa3ce2577..d50a8f4e1ee7 100644 --- a/packages/google-auth/tests/test_aws.py +++ b/packages/google-auth/tests/test_aws.py @@ -69,6 +69,7 @@ # Each tuple contains the following entries: # region, time, credentials, original_request, signed_request +DEFAULT_UNIVERSE_DOMAIN = "googleapis.com" VALID_TOKEN_URLS = [ "https://sts.googleapis.com", "https://us-east-1.sts.googleapis.com", @@ -925,6 +926,7 @@ def test_from_info_full_options(self, mock_init): credential_source=self.CREDENTIAL_SOURCE, quota_project_id=QUOTA_PROJECT_ID, workforce_pool_user_project=None, + universe_domain=DEFAULT_UNIVERSE_DOMAIN, ) @mock.patch.object(aws.Credentials, "__init__", return_value=None) @@ -952,6 +954,7 @@ def test_from_info_required_options_only(self, mock_init): credential_source=self.CREDENTIAL_SOURCE, quota_project_id=None, workforce_pool_user_project=None, + universe_domain=DEFAULT_UNIVERSE_DOMAIN, ) @mock.patch.object(aws.Credentials, "__init__", return_value=None) @@ -967,6 +970,7 @@ def test_from_file_full_options(self, mock_init, tmpdir): "client_secret": CLIENT_SECRET, "quota_project_id": QUOTA_PROJECT_ID, "credential_source": self.CREDENTIAL_SOURCE, + "universe_domain": DEFAULT_UNIVERSE_DOMAIN, } config_file = tmpdir.join("config.json") config_file.write(json.dumps(info)) @@ -986,6 +990,7 @@ def test_from_file_full_options(self, mock_init, tmpdir): credential_source=self.CREDENTIAL_SOURCE, quota_project_id=QUOTA_PROJECT_ID, workforce_pool_user_project=None, + universe_domain=DEFAULT_UNIVERSE_DOMAIN, ) @mock.patch.object(aws.Credentials, "__init__", return_value=None) @@ -1014,6 +1019,7 @@ def test_from_file_required_options_only(self, mock_init, tmpdir): credential_source=self.CREDENTIAL_SOURCE, quota_project_id=None, workforce_pool_user_project=None, + universe_domain=DEFAULT_UNIVERSE_DOMAIN, ) def test_constructor_invalid_credential_source(self): @@ -1067,6 +1073,7 @@ def test_info(self): "token_url": TOKEN_URL, "token_info_url": TOKEN_INFO_URL, "credential_source": self.CREDENTIAL_SOURCE, + "universe_domain": DEFAULT_UNIVERSE_DOMAIN, } def test_token_info_url(self): diff --git a/packages/google-auth/tests/test_external_account.py b/packages/google-auth/tests/test_external_account.py index c8900a493674..598c3760cb2f 100644 --- a/packages/google-auth/tests/test_external_account.py +++ b/packages/google-auth/tests/test_external_account.py @@ -144,6 +144,7 @@ def make_credentials( default_scopes=None, service_account_impersonation_url=None, service_account_impersonation_options={}, + universe_domain=external_account._DEFAULT_UNIVERSE_DOMAIN, ): return CredentialsImpl( audience=cls.AUDIENCE, @@ -158,6 +159,7 @@ def make_credentials( quota_project_id=quota_project_id, scopes=scopes, default_scopes=default_scopes, + universe_domain=universe_domain, ) @classmethod @@ -378,6 +380,7 @@ def test_with_scopes_full_options_propagated(self): quota_project_id=self.QUOTA_PROJECT_ID, scopes=["email"], default_scopes=["default2"], + universe_domain=external_account._DEFAULT_UNIVERSE_DOMAIN, ) def test_with_token_uri(self): @@ -465,6 +468,7 @@ def test_with_quota_project_full_options_propagated(self): quota_project_id="project-foo", scopes=self.SCOPES, default_scopes=["default1"], + universe_domain=external_account._DEFAULT_UNIVERSE_DOMAIN, ) def test_with_invalid_impersonation_target_principal(self): @@ -478,7 +482,7 @@ def test_with_invalid_impersonation_target_principal(self): ) def test_info(self): - credentials = self.make_credentials() + credentials = self.make_credentials(universe_domain="dummy_universe.com") assert credentials.info == { "type": "external_account", @@ -486,6 +490,7 @@ def test_info(self): "subject_token_type": self.SUBJECT_TOKEN_TYPE, "token_url": self.TOKEN_URL, "credential_source": self.CREDENTIAL_SOURCE.copy(), + "universe_domain": "dummy_universe.com", } def test_info_workforce_pool(self): @@ -500,6 +505,7 @@ def test_info_workforce_pool(self): "token_url": self.TOKEN_URL, "credential_source": self.CREDENTIAL_SOURCE.copy(), "workforce_pool_user_project": self.WORKFORCE_POOL_USER_PROJECT, + "universe_domain": external_account._DEFAULT_UNIVERSE_DOMAIN, } def test_info_with_full_options(self): @@ -524,6 +530,7 @@ def test_info_with_full_options(self): "quota_project_id": self.QUOTA_PROJECT_ID, "client_id": CLIENT_ID, "client_secret": CLIENT_SECRET, + "universe_domain": external_account._DEFAULT_UNIVERSE_DOMAIN, } def test_service_account_email_without_impersonation(self): diff --git a/packages/google-auth/tests/test_identity_pool.py b/packages/google-auth/tests/test_identity_pool.py index 6651f0b5cdba..6c1c6623c3b1 100644 --- a/packages/google-auth/tests/test_identity_pool.py +++ b/packages/google-auth/tests/test_identity_pool.py @@ -66,6 +66,7 @@ WORKFORCE_SUBJECT_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:id_token" WORKFORCE_POOL_USER_PROJECT = "WORKFORCE_POOL_USER_PROJECT_NUMBER" +DEFAULT_UNIVERSE_DOMAIN = "googleapis.com" VALID_TOKEN_URLS = [ "https://sts.googleapis.com", @@ -410,6 +411,7 @@ def test_from_info_full_options(self, mock_init): credential_source=self.CREDENTIAL_SOURCE_TEXT, quota_project_id=QUOTA_PROJECT_ID, workforce_pool_user_project=None, + universe_domain=DEFAULT_UNIVERSE_DOMAIN, ) @mock.patch.object(identity_pool.Credentials, "__init__", return_value=None) @@ -437,6 +439,7 @@ def test_from_info_required_options_only(self, mock_init): credential_source=self.CREDENTIAL_SOURCE_TEXT, quota_project_id=None, workforce_pool_user_project=None, + universe_domain=DEFAULT_UNIVERSE_DOMAIN, ) @mock.patch.object(identity_pool.Credentials, "__init__", return_value=None) @@ -465,6 +468,7 @@ def test_from_info_workforce_pool(self, mock_init): credential_source=self.CREDENTIAL_SOURCE_TEXT, quota_project_id=None, workforce_pool_user_project=WORKFORCE_POOL_USER_PROJECT, + universe_domain=DEFAULT_UNIVERSE_DOMAIN, ) @mock.patch.object(identity_pool.Credentials, "__init__", return_value=None) @@ -499,6 +503,7 @@ def test_from_file_full_options(self, mock_init, tmpdir): credential_source=self.CREDENTIAL_SOURCE_TEXT, quota_project_id=QUOTA_PROJECT_ID, workforce_pool_user_project=None, + universe_domain=DEFAULT_UNIVERSE_DOMAIN, ) @mock.patch.object(identity_pool.Credentials, "__init__", return_value=None) @@ -527,6 +532,7 @@ def test_from_file_required_options_only(self, mock_init, tmpdir): credential_source=self.CREDENTIAL_SOURCE_TEXT, quota_project_id=None, workforce_pool_user_project=None, + universe_domain=DEFAULT_UNIVERSE_DOMAIN, ) @mock.patch.object(identity_pool.Credentials, "__init__", return_value=None) @@ -556,6 +562,7 @@ def test_from_file_workforce_pool(self, mock_init, tmpdir): credential_source=self.CREDENTIAL_SOURCE_TEXT, quota_project_id=None, workforce_pool_user_project=WORKFORCE_POOL_USER_PROJECT, + universe_domain=DEFAULT_UNIVERSE_DOMAIN, ) def test_constructor_nonworkforce_with_workforce_pool_user_project(self): @@ -639,6 +646,7 @@ def test_info_with_workforce_pool_user_project(self): "token_info_url": TOKEN_INFO_URL, "credential_source": self.CREDENTIAL_SOURCE_TEXT_URL, "workforce_pool_user_project": WORKFORCE_POOL_USER_PROJECT, + "universe_domain": DEFAULT_UNIVERSE_DOMAIN, } def test_info_with_file_credential_source(self): @@ -653,6 +661,7 @@ def test_info_with_file_credential_source(self): "token_url": TOKEN_URL, "token_info_url": TOKEN_INFO_URL, "credential_source": self.CREDENTIAL_SOURCE_TEXT_URL, + "universe_domain": DEFAULT_UNIVERSE_DOMAIN, } def test_info_with_url_credential_source(self): @@ -667,6 +676,7 @@ def test_info_with_url_credential_source(self): "token_url": TOKEN_URL, "token_info_url": TOKEN_INFO_URL, "credential_source": self.CREDENTIAL_SOURCE_JSON_URL, + "universe_domain": DEFAULT_UNIVERSE_DOMAIN, } def test_retrieve_subject_token_missing_subject_token(self, tmpdir): diff --git a/packages/google-auth/tests/test_pluggable.py b/packages/google-auth/tests/test_pluggable.py index e9b3d9a86dcf..7d601dfd4a27 100644 --- a/packages/google-auth/tests/test_pluggable.py +++ b/packages/google-auth/tests/test_pluggable.py @@ -53,6 +53,7 @@ TOKEN_INFO_URL = "https://sts.googleapis.com/v1/introspect" SUBJECT_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:jwt" AUDIENCE = "//iam.googleapis.com/projects/123456/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID" +DEFAULT_UNIVERSE_DOMAIN = "googleapis.com" VALID_TOKEN_URLS = [ "https://sts.googleapis.com", @@ -278,6 +279,7 @@ def test_from_info_full_options(self, mock_init): credential_source=self.CREDENTIAL_SOURCE, quota_project_id=QUOTA_PROJECT_ID, workforce_pool_user_project=None, + universe_domain=DEFAULT_UNIVERSE_DOMAIN, ) @mock.patch.object(pluggable.Credentials, "__init__", return_value=None) @@ -305,6 +307,7 @@ def test_from_info_required_options_only(self, mock_init): credential_source=self.CREDENTIAL_SOURCE, quota_project_id=None, workforce_pool_user_project=None, + universe_domain=DEFAULT_UNIVERSE_DOMAIN, ) @mock.patch.object(pluggable.Credentials, "__init__", return_value=None) @@ -339,6 +342,7 @@ def test_from_file_full_options(self, mock_init, tmpdir): credential_source=self.CREDENTIAL_SOURCE, quota_project_id=QUOTA_PROJECT_ID, workforce_pool_user_project=None, + universe_domain=DEFAULT_UNIVERSE_DOMAIN, ) @mock.patch.object(pluggable.Credentials, "__init__", return_value=None) @@ -367,6 +371,7 @@ def test_from_file_required_options_only(self, mock_init, tmpdir): credential_source=self.CREDENTIAL_SOURCE, quota_project_id=None, workforce_pool_user_project=None, + universe_domain=DEFAULT_UNIVERSE_DOMAIN, ) def test_constructor_invalid_options(self): @@ -395,6 +400,7 @@ def test_info_with_credential_source(self): "token_url": TOKEN_URL, "token_info_url": TOKEN_INFO_URL, "credential_source": self.CREDENTIAL_SOURCE, + "universe_domain": DEFAULT_UNIVERSE_DOMAIN, } def test_token_info_url(self): From b589d6cbbea4a1b999f426a0ad105150ab056fd2 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Tue, 23 May 2023 14:22:12 -0700 Subject: [PATCH 711/966] feat: add metrics (part 1) (#1298) This PR: (1) list the metrics values needed (2) add the `_metric_header_for_usage` method to the base credential class, which is used by the `before_request` method to add the metrics header for token usage. Children credentials classes can override this method for token usage metrics. internal doc: go/googleapis-auth-metric-design --- .../google-auth/google/auth/credentials.py | 17 +++ packages/google-auth/google/auth/metrics.py | 142 ++++++++++++++++++ .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../google-auth/tests/test_credentials.py | 17 +++ packages/google-auth/tests/test_metrics.py | 79 ++++++++++ 5 files changed, 255 insertions(+) create mode 100644 packages/google-auth/google/auth/metrics.py create mode 100644 packages/google-auth/tests/test_metrics.py diff --git a/packages/google-auth/google/auth/credentials.py b/packages/google-auth/google/auth/credentials.py index 4c0af7a6b999..134c182d31df 100644 --- a/packages/google-auth/google/auth/credentials.py +++ b/packages/google-auth/google/auth/credentials.py @@ -22,6 +22,7 @@ from google.auth import _helpers, environment_vars from google.auth import exceptions +from google.auth import metrics @six.add_metaclass(abc.ABCMeta) @@ -100,6 +101,21 @@ def refresh(self, request): # (pylint doesn't recognize that this is abstract) raise NotImplementedError("Refresh must be implemented") + def _metric_header_for_usage(self): + """The x-goog-api-client header for token usage metric. + + This header will be added to the API service requests in before_request + method. For example, "cred-type/sa-jwt" means service account self + signed jwt access token is used in the API service request + authorization header. Children credentials classes need to override + this method to provide the header value, if the token usage metric is + needed. + + Returns: + str: The x-goog-api-client header value. + """ + return None + def apply(self, headers, token=None): """Apply the token to the authentication header. @@ -133,6 +149,7 @@ def before_request(self, request, method, url, headers): # the http request.) if not self.valid: self.refresh(request) + metrics.add_metric_header(headers, self._metric_header_for_usage()) self.apply(headers) diff --git a/packages/google-auth/google/auth/metrics.py b/packages/google-auth/google/auth/metrics.py new file mode 100644 index 000000000000..f7303282c941 --- /dev/null +++ b/packages/google-auth/google/auth/metrics.py @@ -0,0 +1,142 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" We use x-goog-api-client header to report metrics. This module provides +the constants and helper methods to construct x-goog-api-client header. +""" + +import platform + +from google.auth import version + + +API_CLIENT_HEADER = "x-goog-api-client" + +# Auth request type +REQUEST_TYPE_ACCESS_TOKEN = "auth-request-type/at" +REQUEST_TYPE_ID_TOKEN = "auth-request-type/it" +REQUEST_TYPE_MDS_PING = "auth-request-type/mds" +REQUEST_TYPE_REAUTH_START = "auth-request-type/re-start" +REQUEST_TYPE_REAUTH_CONTINUE = "auth-request-type/re-cont" + +# Credential type +CRED_TYPE_USER = "cred-type/u" +CRED_TYPE_SA_ASSERTION = "cred-type/sa" +CRED_TYPE_SA_JWT = "cred-type/jwt" +CRED_TYPE_SA_MDS = "cred-type/mds" +CRED_TYPE_SA_IMPERSONATE = "cred-type/imp" + + +# Versions +def python_and_auth_lib_version(): + return "gl-python/{} auth/{}".format(platform.python_version(), version.__version__) + + +# Token request metric header values + +# x-goog-api-client header value for access token request via metadata server. +# Example: "gl-python/3.7 auth/1.1 auth-request-type/at cred-type/mds" +def token_request_access_token_mds(): + return "{} {} {}".format( + python_and_auth_lib_version(), REQUEST_TYPE_ACCESS_TOKEN, CRED_TYPE_SA_MDS + ) + + +# x-goog-api-client header value for ID token request via metadata server. +# Example: "gl-python/3.7 auth/1.1 auth-request-type/it cred-type/mds" +def token_request_id_token_mds(): + return "{} {} {}".format( + python_and_auth_lib_version(), REQUEST_TYPE_ID_TOKEN, CRED_TYPE_SA_MDS + ) + + +# x-goog-api-client header value for impersonated credentials access token request. +# Example: "gl-python/3.7 auth/1.1 auth-request-type/at cred-type/imp" +def token_request_access_token_impersonate(): + return "{} {} {}".format( + python_and_auth_lib_version(), + REQUEST_TYPE_ACCESS_TOKEN, + CRED_TYPE_SA_IMPERSONATE, + ) + + +# x-goog-api-client header value for impersonated credentials ID token request. +# Example: "gl-python/3.7 auth/1.1 auth-request-type/it cred-type/imp" +def token_request_id_token_impersonate(): + return "{} {} {}".format( + python_and_auth_lib_version(), REQUEST_TYPE_ID_TOKEN, CRED_TYPE_SA_IMPERSONATE + ) + + +# x-goog-api-client header value for service account credentials access token +# request (assertion flow). +# Example: "gl-python/3.7 auth/1.1 auth-request-type/at cred-type/sa" +def token_request_access_token_sa_assertion(): + return "{} {} {}".format( + python_and_auth_lib_version(), REQUEST_TYPE_ACCESS_TOKEN, CRED_TYPE_SA_ASSERTION + ) + + +# x-goog-api-client header value for service account credentials ID token +# request (assertion flow). +# Example: "gl-python/3.7 auth/1.1 auth-request-type/it cred-type/sa" +def token_request_id_token_sa_assertion(): + return "{} {} {}".format( + python_and_auth_lib_version(), REQUEST_TYPE_ID_TOKEN, CRED_TYPE_SA_ASSERTION + ) + + +# x-goog-api-client header value for user credentials token request. +# Example: "gl-python/3.7 auth/1.1 cred-type/u" +def token_request_user(): + return "{} {}".format(python_and_auth_lib_version(), CRED_TYPE_USER) + + +# Miscellenous metrics + +# x-goog-api-client header value for metadata server ping. +# Example: "gl-python/3.7 auth/1.1 auth-request-type/mds" +def mds_ping(): + return "{} {}".format(python_and_auth_lib_version(), REQUEST_TYPE_MDS_PING) + + +# x-goog-api-client header value for reauth start endpoint calls. +# Example: "gl-python/3.7 auth/1.1 auth-request-type/re-start" +def reauth_start(): + return "{} {}".format(python_and_auth_lib_version(), REQUEST_TYPE_REAUTH_START) + + +# x-goog-api-client header value for reauth continue endpoint calls. +# Example: "gl-python/3.7 auth/1.1 cred-type/re-cont" +def reauth_continue(): + return "{} {}".format(python_and_auth_lib_version(), REQUEST_TYPE_REAUTH_CONTINUE) + + +def add_metric_header(headers, metric_header_value): + """Add x-goog-api-client header with the given value. + + Args: + headers (Mapping[str, str]): The headers to which we will add the + metric header. + metric_header_value (Optional[str]): If value is None, do nothing; + if headers already has a x-goog-api-client header, append the value + to the existing header; otherwise add a new x-goog-api-client + header with the given value. + """ + if not metric_header_value: + return + if API_CLIENT_HEADER not in headers: + headers[API_CLIENT_HEADER] = metric_header_value + else: + headers[API_CLIENT_HEADER] += " " + metric_header_value diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 033c2df24d9e356914d0baf3270af31014e68dc7..2f20ce4078f7b94e1eba5662d26e5f147aef5f03 100644 GIT binary patch literal 10324 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 mIS9h9r&p5?WQwpd+Um0A Date: Wed, 24 May 2023 13:30:24 -0700 Subject: [PATCH 712/966] feat: add metrics (part 2) (#1303) --- .../google/auth/compute_engine/credentials.py | 4 +++ .../google/auth/impersonated_credentials.py | 4 +++ .../google-auth/google/oauth2/credentials.py | 4 +++ .../google/oauth2/service_account.py | 15 +++++++-- .../tests/compute_engine/test_credentials.py | 9 ++++++ .../tests/oauth2/test_credentials.py | 10 ++++++ .../tests/oauth2/test_service_account.py | 31 +++++++++++++++++++ .../tests/test_impersonated_credentials.py | 10 ++++++ 8 files changed, 84 insertions(+), 3 deletions(-) diff --git a/packages/google-auth/google/auth/compute_engine/credentials.py b/packages/google-auth/google/auth/compute_engine/credentials.py index 618fa5a2da92..ed935c17c4f7 100644 --- a/packages/google-auth/google/auth/compute_engine/credentials.py +++ b/packages/google-auth/google/auth/compute_engine/credentials.py @@ -28,6 +28,7 @@ from google.auth import exceptions from google.auth import iam from google.auth import jwt +from google.auth import metrics from google.auth.compute_engine import _metadata from google.oauth2 import _client @@ -94,6 +95,9 @@ def _retrieve_info(self, request): if self._scopes is None: self._scopes = info["scopes"] + def _metric_header_for_usage(self): + return metrics.CRED_TYPE_SA_MDS + def refresh(self, request): """Refresh the access token and scopes. diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index f978b64ef4ca..c596222a47aa 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -37,6 +37,7 @@ from google.auth import credentials from google.auth import exceptions from google.auth import jwt +from google.auth import metrics _DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds @@ -238,6 +239,9 @@ def __init__( self._quota_project_id = quota_project_id self._iam_endpoint_override = iam_endpoint_override + def _metric_header_for_usage(self): + return metrics.CRED_TYPE_SA_IMPERSONATE + @_helpers.copy_docstring(credentials.Credentials) def refresh(self, request): self._update_token(request) diff --git a/packages/google-auth/google/oauth2/credentials.py b/packages/google-auth/google/oauth2/credentials.py index 457db76ae344..dd54edfb3da6 100644 --- a/packages/google-auth/google/oauth2/credentials.py +++ b/packages/google-auth/google/oauth2/credentials.py @@ -42,6 +42,7 @@ from google.auth import _helpers from google.auth import credentials from google.auth import exceptions +from google.auth import metrics from google.oauth2 import reauth _LOGGER = logging.getLogger(__name__) @@ -287,6 +288,9 @@ def with_token_uri(self, token_uri): enable_reauth_refresh=self._enable_reauth_refresh, ) + def _metric_header_for_usage(self): + return metrics.CRED_TYPE_USER + @_helpers.copy_docstring(credentials.Credentials) def refresh(self, request): scopes = self._scopes if self._scopes is not None else self._default_scopes diff --git a/packages/google-auth/google/oauth2/service_account.py b/packages/google-auth/google/oauth2/service_account.py index a0268970ceb9..c80627398d12 100644 --- a/packages/google-auth/google/oauth2/service_account.py +++ b/packages/google-auth/google/oauth2/service_account.py @@ -78,6 +78,7 @@ from google.auth import credentials from google.auth import exceptions from google.auth import jwt +from google.auth import metrics from google.oauth2 import _client _DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds @@ -400,6 +401,16 @@ def _make_authorization_grant_assertion(self): return token + def _use_self_signed_jwt(self): + # Since domain wide delegation doesn't work with self signed JWT. If + # subject exists, then we should not use self signed JWT. + return self._subject is None and self._jwt_credentials is not None + + def _metric_header_for_usage(self): + if self._use_self_signed_jwt(): + return metrics.CRED_TYPE_SA_JWT + return metrics.CRED_TYPE_SA_ASSERTION + @_helpers.copy_docstring(credentials.Credentials) def refresh(self, request): if ( @@ -414,9 +425,7 @@ def refresh(self, request): "domain wide delegation is not supported for non-default universe domain" ) - # Since domain wide delegation doesn't work with self signed JWT. If - # subject exists, then we should not use self signed JWT. - if self._subject is None and self._jwt_credentials is not None: + if self._use_self_signed_jwt(): self._jwt_credentials.refresh(request) self.token = self._jwt_credentials.token.decode() self.expiry = self._jwt_credentials.expiry diff --git a/packages/google-auth/tests/compute_engine/test_credentials.py b/packages/google-auth/tests/compute_engine/test_credentials.py index 6a2f8cc20093..4234d98e48cb 100644 --- a/packages/google-auth/tests/compute_engine/test_credentials.py +++ b/packages/google-auth/tests/compute_engine/test_credentials.py @@ -185,6 +185,15 @@ def test_with_scopes(self): assert self.credentials._scopes == scopes + def test_token_usage_metrics(self): + self.credentials.token = "token" + self.credentials.expiry = None + + headers = {} + self.credentials.before_request(mock.Mock(), None, None, headers) + assert headers["authorization"] == "Bearer token" + assert headers["x-goog-api-client"] == "cred-type/mds" + class TestIDTokenCredentials(object): credentials = None diff --git a/packages/google-auth/tests/oauth2/test_credentials.py b/packages/google-auth/tests/oauth2/test_credentials.py index 5a63224fd6e2..feb7e4311882 100644 --- a/packages/google-auth/tests/oauth2/test_credentials.py +++ b/packages/google-auth/tests/oauth2/test_credentials.py @@ -69,6 +69,16 @@ def test_default_state(self): assert credentials.rapt_token == self.RAPT_TOKEN assert credentials.refresh_handler is None + def test_token_usage_metrics(self): + credentials = self.make_credentials() + credentials.token = "token" + credentials.expiry = None + + headers = {} + credentials.before_request(mock.Mock(), None, None, headers) + assert headers["authorization"] == "Bearer token" + assert headers["x-goog-api-client"] == "cred-type/u" + def test_refresh_handler_setter_and_getter(self): scopes = ["email", "profile"] original_refresh_handler = mock.Mock(return_value=("ACCESS_TOKEN_1", None)) diff --git a/packages/google-auth/tests/oauth2/test_service_account.py b/packages/google-auth/tests/oauth2/test_service_account.py index 8388acd06f47..36565b721a6a 100644 --- a/packages/google-auth/tests/oauth2/test_service_account.py +++ b/packages/google-auth/tests/oauth2/test_service_account.py @@ -408,6 +408,37 @@ def test__create_self_signed_jwt_always_use_jwt_access(self, jwt): credentials._create_self_signed_jwt(None) jwt.from_signing_credentials.assert_not_called() + def test_token_usage_metrics_assertion(self): + credentials = service_account.Credentials( + SIGNER, + self.SERVICE_ACCOUNT_EMAIL, + self.TOKEN_URI, + always_use_jwt_access=False, + ) + credentials.token = "token" + credentials.expiry = None + + headers = {} + credentials.before_request(mock.Mock(), None, None, headers) + assert headers["authorization"] == "Bearer token" + assert headers["x-goog-api-client"] == "cred-type/sa" + + def test_token_usage_metrics_self_signed_jwt(self): + credentials = service_account.Credentials( + SIGNER, + self.SERVICE_ACCOUNT_EMAIL, + self.TOKEN_URI, + always_use_jwt_access=True, + ) + credentials._create_self_signed_jwt("foo.googleapis.com") + credentials.token = "token" + credentials.expiry = None + + headers = {} + credentials.before_request(mock.Mock(), None, None, headers) + assert headers["authorization"] == "Bearer token" + assert headers["x-goog-api-client"] == "cred-type/jwt" + @mock.patch("google.oauth2._client.jwt_grant", autospec=True) def test_refresh_success(self, jwt_grant): credentials = self.make_credentials() diff --git a/packages/google-auth/tests/test_impersonated_credentials.py b/packages/google-auth/tests/test_impersonated_credentials.py index 0e4dc08d7cf3..8f2e1fdbeea8 100644 --- a/packages/google-auth/tests/test_impersonated_credentials.py +++ b/packages/google-auth/tests/test_impersonated_credentials.py @@ -162,6 +162,16 @@ def make_request( return request + def test_token_usage_metrics(self): + credentials = self.make_credentials() + credentials.token = "token" + credentials.expiry = None + + headers = {} + credentials.before_request(mock.Mock(), None, None, headers) + assert headers["authorization"] == "Bearer token" + assert headers["x-goog-api-client"] == "cred-type/imp" + @pytest.mark.parametrize("use_data_bytes", [True, False]) def test_refresh_success(self, use_data_bytes, mock_donor_credentials): credentials = self.make_credentials(lifetime=None) From 3eff54145660ed6d30e3874271992c791c3880ae Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Thu, 25 May 2023 09:02:34 -0700 Subject: [PATCH 713/966] chore: update sys test cred (#1306) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 2f20ce4078f7b94e1eba5662d26e5f147aef5f03..8b9e40871e4bd58f39c47d665e8f71c60add17c4 100644 GIT binary patch literal 10324 zcmV-aD67{BB>?tKRTE$~vJYa&|8SBT{({p;2U%zOi?lKZ>E6I1H@gtg%r0n@7fA$pLg#gM-aUs99lD_GP}i4 zyJ_m^vFthhd`E}dT~w>aJ&*siRt;{rBlAO4qLsFUxF3mRklA04le-)3lK~5NKuGbt z*f-7@*Q7=)p`^kx+~mK>NmKEVhC8Hm$Qn=U(-Kl}PZfN3g^i_7^-)3s(UFzBd` z=5Z>N1HS=n3y&q3(%o!K`4WN#w4vy(e0$D2NxOFQ6(*Au+h&}C!2r+;{=eTqnM4qg zVvwlOdM3aP1cHanp@nL(NzThWf)_n&%Lb@&nkwX>$@So@N6KJ-c(gusA}T4hviSOT z^J|rIYueDgdL7XIdocif&~6#g`jVp^hN@K=|7iG63_toO0vFDp?hjVun;W8Y=aAc4 zk41EXbg!rjj>Eck8-tpHVHvn>6%eyf=ba3?tF0VVHVyMe$WHTk5cWXLL)TbRKRO09 zXhJcvo(lF!#Usgy4Q}v*2!M%iX?#9Dx8`pqY!&EQgx-5#Q1zx2q(Lk)K#-3c2&l0bxI%~(_|A*TtM)p_xz z$Xe}?bBqjxSnybaseM<_nDhvewv0wX{}N19g`3)=yjqqrT5wAaaP5|7l`8iIG;0f% zPYQlVv1ohWa5ae^f-HKx{7q<}N)Xl4km`{4c-F00hx~}zXSHpJs`}{0;kkMbXtjM^ z6I-2G1~3r2t2vXWK<_s*Q%&@O|7uxlhn(80lltcafjbUvOqIP%3{{jNcg6I*mkEk_ zg;@}59U9L`J(6mUvlUt(Xa~SY3;>+yGNtZ{H5K%7IlZIIzIFY}okTyl96t~yhi3l0 z6i5cYeE*kGal-CL<3Bpb1%2a zg}LLL&77p&q{k$H^Tr%@LkbF2<$9>>r>TRmKge-haIb`2M8>{T8|o84uj&Cg{%!Y_}ocqG0=VB^%R$h z)E-IQlq*Xv;Yv)UkfXxbSCf;rSZupyQFVTN|5L3WJGbnt`7%ZIJiL{kSClZKrO2xW zp4Jtov|C&knbaj>1tdk+ogA;K9~T5ZhxJB5_=p(rLpW!RLosbv>}HW(+{xW7BngJn z>WyH!xLBP3=+3|-SUWmq_68AUMP);lF~+4qVtZ z^o`Cn^E9S2p9t+QS2e-k>0-z;eKj z43XM>F8gp0lQD%0KDQ#_GO&T}3XQiF)y8FEBDYkFwtR-E#{C{k3`e-W9N=^luZ0NK z6W$HQHA8BSjSe<>E?w6Vx}J5^1$d4^l9bmY6~#2M=EGpvyE>IsIt^`!2RS!1eL2*p zW{3XJ(C8r@IeL1XtErt`5{ifO+K)ZL6#Q}Hr7GmINt&1vhj#CU&6r)IfmXUi;qHnLVyi&tIGtApQ_;EKzNezN$HJO5hW%{6`y9{AS>4 zRRjhNUi&&J1fs_@Nik+4*t|9f2Gk&^apei$!O&`eFs=2`UhW=Z)wb>f0Vj!A>?12X zMzsGYZ4+P!_1$&K!K2q55QAiYSSF8(d2`0;PP-Am&JYl3&#gk*eW-VSNJW6^IV?mEXI$N))C+AQ^Bj9i7@$b$s1 z1$r=|2%9y#9La{0IwB~GR2=V~tLa`G6_WLDE2HeiA7jhO| zv&&TdFEKcysdFTa7@oo#W1kg(C!B7_tOa680CNjdOkH2Jr&(%G7f;#Kjf7Yl65 zZv=bDNjK!r%MQfk{v$SKMSE!UA6>}e;5b&a!``X+jPF2xpmS$ z-U*f)O+Qsz2!Go-fO&?YE6PFTws~e+C1tYA4_ofelH~}K+5%k?b|HLN3Y_t?zwNP+ zid%5RM53)49!TPbN8TAZnN#r?yr3ZL}kt~%f0=leC_WJjNHX(aUUw21NAZ`l& zNAj5X4bm6gkAuUZ28vd->Q45EucoV7?&2<~X+83b%bLKs*iH$(d#B(E-8_^dj zqw+VFQwt&cP<4)8fhV;g&G(JxP(Z@kwr^Q3Xr1`!bEJbl4QKY<-?<&n!dyuCv!3nt zjgdBET#TFwz-Ju>C^x&j7fdsk<3muab9kYL{p5q$JOMj=k=@DFrY9uI)U`lQ+;+0a zU{ciU6yjNsPVbW!h0ax*p!qJp8T658jJz+HiE$_{&67@Hs~U>847zvljit}Euwi8s zswX(?ABR{*9}wE+wivz2GA?-4ng7r7Mx-!=Y$;H0)`*sQara|!OLMMPRnK;3R+vIx z6U!nJmX?nTtgC3yWLsnV^QQ4tdNOBx%sHw=vj@d)&>&$v6c~|A+xKu3_T0D?(>s$d zf?^imVHl=}gHsh|A^9HIi?CK9A$mF@Tlm==<*tCz*#mRb+y_;*(;y*S-UqKhewae) zUP;k#*0++7Xf)}#&l(vN8<1%xg**L7MzWZZwfZ|E5JF`&2t$(tU(oMYj?{ zXz&x9g`>NtT3IJ(d7h0pB~MQ*P1ZR(xgx=~4H!*izpnV8emt>ocC#U?1H&3Wm!v)W z-&@!s?(idhD?05E1U@jYt4*(>=R|9E9l8z{iiBaV8X>*qV;^JA%PBz|L#Ur#t;WxL zHUvPv)dxx`k#$-8tHI_D=k>bG zP99}slrOYNdForaK%#$FVB{`sl^zYXd~b=*Y2zk|{tCa*gWO1J7`+G*>E>!Qph0Br z1-EzH%%X#?qbu{^)A%s0RiA6PD7C1EQ?P5Uz52 za{I*92vwUmE1{3f>@~m#BdR+`9wXbIhupu~N|}$btKd*X&FZV)6lV*zQy+#x3sC zXR$Cvju`^B>-98ONy{D;+2=*E4>=M~(kTbnwF zyIjm;QcWyHJ^}bf@)Jc)<4J;+Q(ms$*0Afdc?)ppWDpjmK zTr*w*2S&fqz>yT5jOL&a7othmi%KZVAS`3NHiWvMybmG{&C+ak)sQ~XWsWGrJn(W} z;m(sz)^$DBhRPcR_(~#Ue|pk`^-8TJI{!d$38euRlS%T3$fULuqbW=d+u$7l_j;id z)XTIMz|u!5>ZV2)lh2ydVO;{0gopo4mXb4TGlNKNHI`uIJfANc$VPDi$oEsyUcWm( zuRh(mDBX!K4N9nDTubg0UB1^2eijZxA*F>>A8;>O*e>TR0ts3Fze<(K>TCJ|Cd10U z%N}U*EXW$`ANz|#+-i})v}e*A3pn%BeKaN!gzBfUZpWC;3~kq2Y%4@#vtQ}};^l-v z7t!qJI~E7YAj0}=GWDz@7!F;6(lSbVZ13BL%A8wq5EVb$Xh8j&Ksz(O4b%)B0EI@h z5FgdGtw*<`r6h=cEex;JG?Fbm)1^kg4_mh>JkvFf+o+yz4TPfZJUvVcR!g#Qo75P6 zh-I-Fa}xbr zd57wW5H5>PA9EpJ>(q&_XdfO9$_T~6<$w)8;drvp4SIBqmd}oC7*2$Sb4P&aIZlxQ zp$`;x`H&3-m)mE0Oq$0vdlitqjfkXWC9vve_=r?LhOe^dMBa->0oYM&bxRPrc`n|2 zEc*XG78r)~kT&x#yoNm|g zU^;$w5fPkt4z$)V&_qg#+XB_txh7Mud$uyW6(f61sIp>zidUh8+f&|v*MZi4W0wVl zqXIDgl*~XH_oC{5U%-!J!VBUdKH9r>j9541-h)L8xJdVP<~Ef|!^k9XH>Z$EvM>J6 zwrd0O=&(F;@F-;0myIIT7EG#5Zx%i^9EEgFs%Y}K;cJ7L(Cd@WjZ)<8;Ov3l#@yfw z(~Nk(U)`+5RcWjPX*&*pTv-484jnRf{ix>MmUXA%h5cF=3;EC~a$6yALovNx{y?G= zJxMS*kH@`)rF?OhcyTU#7d$Y1ENTtSbIK}>^xJcJQ?6sZARrUe0k(#19!^nd^eoB> zUjj*%K0;Hu(eB6q@K>dg{Q{q}`stSAZRAfjHGCKd)7nmxFzaZ^6SYZ-(D*_f0uY#kK8hf+c>?pEbHJIiYhOyy&o-2c61mcVB1^cVVQ4ZBC9qC^|t}qrJ$)xA21c?>Z-y< zqwpH7ua%7*bSfcgk}aadNPFGGE-z^}>8Z%OAXw;3oCxkp&r*ro*^M~4`%C^k> z44iae)OCDfP#QGW;wLpx^c(7sz;ph!DffguC4$sI=-n(qO~LQi!)Eu_JYViQ=*po^ zKk7r7+0R$?eFVO_ceT39G&mgg?&WP|bDe0j88!FS`mXc@Z`R7P#b9hzUvl{ykV*7* zi;HGvygQR0idpnip6i;S_(ULM!KBNxMWR-5rf^lwoYd0HqRLzG5L=(apm&U!eOZ#5 zmnw8=vp^{;9Lf*gfuQQx; zU|Q8d7#`Ey&JWIh4(qir*}Y=Qhgf)Vq|Pi6b;B zAi(&Rqh@?pfPGM?^}`wPgNvw$wMlN+yD+e0ztC1hB)}pqd&AigdTsmSUjTv+_dd;! z6;~sLQxL;+Iw9e;6z;T4P|F6nPZ{}@ISWrBrNH`ScW_UN8{Q=VRdw+2)26%C457SNB;eP?1#Olfp(UwSn4>J)wA4Be8QG;4g6Kz!1Mr!=>-q`EErb_Tptj4tu#j)jzP}G`5gtAxJXW z49HG=eJhSj1H?es(R|l94E!Dtu6jxE`D@Z{5<)igX=%NzYKEITMiz^UlVI^#^+O8` zFEpPovzVLn@04ug&h{AZ)n#B)I2@Ftl7lFK@8U?SQC8mlZ^w|?Mz%+28m+;CH-X6o z2VJx6RnguN5%@UC5+WS(OmTC@(6ppYBmU=}Zx8vr|H=tmEq68rI`GEUzv%<&R(zu)Y@rCt{b+bTI7X4h39Dmd9=JU#}&^QR8uqUnSB=+{~)`M=D0GY|WF z#KHx+jL$XxBS!?Qmp1gA1aQd4(2E9tZ+?=KS2+EN#xOLk$4{Oy^oUQ~Q;d0rkkSjL z?h!mTA{DtQ(4@V94ki7iK_MF+iXLNgOcsEGj%xe8s-39Ax)cbRL9WbrxZZ#ytHx#l z??)N>H$v0Gcq7;?xE`tTxbG|yLohwKYV72)w?gyp(R0xg54z{*1=b3 z*L8o-X(I<3LJsir(eQ?E5Mvcd!g~C=EXWqC5KquuB+(}aY?dDZ0LjT<4QZ%A6yV}h zh8J|1Ybfv!o=jPw`69f>To-!Kp0~!lFkyVIK3q)X-9&vT!loQw)l+F7P>Zen{)%t^8 z>FwTsKF#6Qmr^RUsF*aq8{#5mpn~#95FnZ`^E{dyuk7ih=B0=(A46PgY*1MJLAHqn z55U31sPigW zj>WME2~??#q2zf>^gJ~2o4ruXfbn0w9OMCz#tGOAhX>Qtet(2wk3y|tI?%K5l#ui- zb+G;lO=qtkN`L)!6}U~ac1lYUxyO!nZF=*g%@%^+!kvV;-ES8-Pe%N;4SG#jsbx9I`vDP=&J;EKUq|xdcTK~+`6w0csx;*tUA-|E9ktG6ePUf64sG+VQKd2giw11luo<4 z(QZ3Jy&@m6QgXNzzt>6BK0moGTDi6*P=;W{}yp}Ks5CXpHF_MNNSHXzdG|4(!rk&8tWBIVYD+woBJO3 z*Eru376ByVLD6eC(?0VTl>d9chT*@ZRjU6d_r(Fz!^Qlg-T}wwQHeMA7E2zNy#?xJ ztQ=3yKKaC6q`SfCO^V(zk}M+o@VR6enfJI1F)Bs$JH^}7dIz-hI}GkF5b2+-F-{dD>5pV=?>?<|3%~9)jVc$F}Eb*D-Nxa&Nq# zvi>|&LzZ~xjuE6D2&yi&d9dd3Ple)Fa=r=!r zDLcRFV`V1Ao)jWj=7>Jg%gtWZ_$gwQwB|V(g7ZAUe_3-Y*CwhvWeWlRmG)%1l5;xF zN&}gCM=`<6JJ7b_A9l0SJXE-a?kRq(^sUU=afa>*)sG!k1xDQBUwKk`P`NPUY#_+= zU9lZk&Lp5enb7wtTRX+0t=mt6yLUQC=d#&jt=osE-QeHEAh?>ETq2M=e&$!Lr2(?J zw9t)6*r9NqfJOp)B*z4kMq}1-uOT$!t{S`n z#cbvoqg{zr>u_bxk$8c1pD*l4x;W-^+?+#7el3>KwSppbc1--M-i~A&;K%%PcUjIn z`jndn>;z9C5ka$FRuSlULq&N+^mgK8I>g7RG!vyim~-|ipcaB0M|4!AREE~8|= z@>C`avg2@G!XlD6r^|n=>~F4qYO_llXP`~>QcralSLhepXuU4hmiZ2a3z;C+Pou_I z8Hbdm$nMUplKk-$w%NLFJs!CUbe&jY0Na&EZ^r^zNq5%_nVViq?>;-SiwtN|gx2d=Uh#E=iDg&`}5%SJ_DY4~6 z9pIwCT&u*gh}P1r5(fd*4`e%_^(4{pqp(h8g5*GkAc=92$=f$=P)h!>h!oOiCNr=Z zC;PBXGSfY0b=9(ykcrPUh&rkAp>w0=xLjQ`^scHcNm&ujq{k_4r?2x<^nW zY3J=?k%cs+oln|vXdg19SdHs-!1aFz6-Cv@NShA}dRe5YuPMTES&#^ofHiBGk++jYBTCWTCZdKhN7JWw z3taH9UyoZ*Sb81K@5N`aZxca@sh*c5wK~PM*TNa88=%3qx9zh(*MvZv|wQtwR&Ezfo@S{%bSYuXRMx z60IVGCV;rMubd(Tq!M5J<5G$Lrwa<);hYv;dYppU@jW&kvq5JAE<|Mp+( z-Nxn_Cw&7h7@(2sxvu^4>IOdmqQeta@GW# z45SAw2BIR-C%P#Q&s;Gp+nu_s>-XN<&`I1+yRgkxqAET1N1=zL3bBnRGBZ7c(o!W z>Ns!O3lGA44609@tUAy^lWwWk;{hxY8#tn1mdkHxKuYkm;#*WFb3PzNRK`C|R@kuQ z(Hz{Ei<5tZzW>jF=5Ze49&wNW$-w-9JN;fT6_a9Cvm@dVtsHk10gf4e0SPyG^? zdSBWFV==Wchp7nSqz79C${I1JZ0>ittXv4CCV6QqLuZ|u4Qs*Z=4&-x!X{+~CWcKk z+42jks8czuJiC{f9ufUGCJSx6@CP<+NY(oDFl{}EE5HP;=OR8S4O;6Bz+oT;MZ~Yz zt##uah=~U$j2KRk#o%Ed5Zhw({=lWl|*v`C5>+tKH~WKsF&{vz1TA%XBP zra4F`OpUR)wt17eE=@x-3{SUIh%)~9HkOwFT(!@@*}K~1qrE0v6Y!csZg#0B3=7xN zWrlH&RyR?XQfaX$BHOfTkY+PJ`07TJkYCrU6|}C={jZYj>#7X35TnVP;|9t^@w=GC z-AEog{L3d;5dPqM0UW_-MFf=IzUBa&|EIj95&pp(q1u$izNL%s6A?g}H5Nw$H46UqNg96HQV!4k~Zqp-4fqiQgV|hM&2JqURu73 z4#E019`qKc(Cg}*byf{PG<7m)l^f}lvMcNnV|bza?XYZzCgvInpl+qoQfL3};=mL( zWC1Sa%hbE<{W4v#GR{@oq3&MIYtACby3qF91&=s$QxfUQ9amT#aZdAj1U2M5FwQWw_BP5 z$u{~=1@Za|3@`n%Z%RLZ(&+aYM&!K5WZc$}E zx%@dQ`i-yUu5xl&)w3_sWj`_}{VuFvi|w{B*W-xCh%iIuL6U zH$Jamrg3E!R<=gEDIa=2pSUp%yb&qUxAF+4ybw-d=EM)Q68;9y)l|QY_A?3Efh#m< mItQ!T>QI#%G)=vLM&F)aO-d5)-w5-dg&jy)gOL?bYrK%j+6krr literal 10324 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 Date: Thu, 25 May 2023 09:25:46 -0700 Subject: [PATCH 714/966] feat: remove python 2.7 from setup.py and nox tests (#1301) * feat: remove python 2.7 from setup.py and nox tests. Originally done in https://github.com/googleapis/google-auth-library-python/pull/892 --- packages/google-auth/noxfile.py | 19 ------------------- packages/google-auth/setup.py | 11 +++-------- 2 files changed, 3 insertions(+), 27 deletions(-) diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index 417a66500a82..30b10574ab97 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -104,25 +104,6 @@ def unit(session): ) -@nox.session(python=["2.7"]) -def unit_prev_versions(session): - constraints_path = str( - CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt" - ) - session.install("-r", "testing/requirements.txt", "-c", constraints_path) - session.install("-e", ".", "-c", constraints_path) - session.run( - "pytest", - f"--junitxml=unit_{session.python}_sponge_log.xml", - "--cov=google.auth", - "--cov=google.oauth2", - "--cov=tests", - "--ignore=tests/test_pluggable.py", # Pluggable auth only support 3.6+ for now. - "tests", - "--ignore=tests/transport/test__custom_tls_signer.py", # enterprise cert is for python 3.6+ - ) - - @nox.session(python="3.8") def cover(session): session.install("-r", "testing/requirements.txt") diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index e45512a02935..7e96b5757aa7 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -24,19 +24,14 @@ "pyasn1-modules>=0.2.1", # rsa==4.5 is the last version to support 2.7 # https://github.com/sybrenstuvel/python-rsa/issues/152#issuecomment-643470233 - 'rsa<4.6; python_version < "3.6"', - 'rsa>=3.1.4,<5; python_version >= "3.6"', + "rsa>=3.1.4,<5", # install enum34 to support 2.7. enum34 only works up to python version 3.3. - 'enum34>=1.1.10; python_version < "3.4"', "six>=1.9.0", "urllib3<2.0", ) extras = { - "aiohttp": [ - "aiohttp >= 3.6.2, < 4.0.0dev; python_version>='3.6'", - "requests >= 2.20.0, < 3.0.0dev", - ], + "aiohttp": ["aiohttp >= 3.6.2, < 4.0.0dev", "requests >= 2.20.0, < 3.0.0dev"], "pyopenssl": ["pyopenssl>=20.0.0", "cryptography>=38.0.3"], "requests": "requests >= 2.20.0, < 3.0.0dev", "reauth": "pyu2f>=0.1.5", @@ -67,7 +62,7 @@ namespace_packages=("google",), install_requires=DEPENDENCIES, extras_require=extras, - python_requires=">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*", + python_requires=">=3.6", license="Apache 2.0", keywords="google auth oauth client", classifiers=[ From 0bd523abd12403d22feef6221740d0bd4cf2154e Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Thu, 25 May 2023 15:23:13 -0400 Subject: [PATCH 715/966] build(deps): bump requests from 2.28.1 to 2.31.0 in /synthtool/gcp/templates/python_library/.kokoro (#1307) Source-Link: https://github.com/googleapis/synthtool/commit/30bd01b4ab78bf1b2a425816e15b3e7e090993dd Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:9bc5fa3b62b091f60614c08a7fb4fd1d3e1678e326f34dd66ce1eefb5dc3267b Co-authored-by: Owl Bot Co-authored-by: Anthonios Partheniou --- packages/google-auth/.github/.OwlBot.lock.yaml | 3 ++- packages/google-auth/.kokoro/requirements.txt | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/google-auth/.github/.OwlBot.lock.yaml b/packages/google-auth/.github/.OwlBot.lock.yaml index b8edda51cf46..32b3c486591a 100644 --- a/packages/google-auth/.github/.OwlBot.lock.yaml +++ b/packages/google-auth/.github/.OwlBot.lock.yaml @@ -13,4 +13,5 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:2e247c7bf5154df7f98cce087a20ca7605e236340c7d6d1a14447e5c06791bd6 + digest: sha256:9bc5fa3b62b091f60614c08a7fb4fd1d3e1678e326f34dd66ce1eefb5dc3267b +# created: 2023-05-25T14:56:16.294623272Z diff --git a/packages/google-auth/.kokoro/requirements.txt b/packages/google-auth/.kokoro/requirements.txt index 66a2172a76a8..3b8d7ee81848 100644 --- a/packages/google-auth/.kokoro/requirements.txt +++ b/packages/google-auth/.kokoro/requirements.txt @@ -419,9 +419,9 @@ readme-renderer==37.3 \ --hash=sha256:cd653186dfc73055656f090f227f5cb22a046d7f71a841dfa305f55c9a513273 \ --hash=sha256:f67a16caedfa71eef48a31b39708637a6f4664c4394801a7b0d6432d13907343 # via twine -requests==2.28.1 \ - --hash=sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983 \ - --hash=sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349 +requests==2.31.0 \ + --hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \ + --hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1 # via # gcp-releasetool # google-api-core From 1a80fbbd54a2fc1b6a2037f456c0eea905d84bc4 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Thu, 25 May 2023 12:48:14 -0700 Subject: [PATCH 716/966] feat: add metrics (part 3) (#1305) This PR adds `x-goog-api-client` header to - access token and id token refresh requests, for compute engine credentials / user credentials / service account credentials / impersonated credentials - reauth start and continue requests - metadata server ping requests Previous PRs: Part 1: #1298 Part 2: #1303 --- .../google/auth/compute_engine/_metadata.py | 30 +++++++--- .../google/auth/compute_engine/credentials.py | 7 ++- .../google/auth/impersonated_credentials.py | 10 +++- packages/google-auth/google/oauth2/_client.py | 33 +++++++++-- packages/google-auth/google/oauth2/reauth.py | 18 +++++- .../tests/compute_engine/test__metadata.py | 59 +++++++++++++++---- .../tests/compute_engine/test_credentials.py | 30 +++++++++- .../google-auth/tests/oauth2/test__client.py | 59 ++++++++++++++++--- .../google-auth/tests/oauth2/test_reauth.py | 32 ++++++++-- packages/google-auth/tests/test_aws.py | 22 ++++++- .../tests/test_external_account.py | 54 +++++++++++++++-- .../google-auth/tests/test_identity_pool.py | 10 +++- .../tests/test_impersonated_credentials.py | 47 ++++++++++++++- 13 files changed, 355 insertions(+), 56 deletions(-) diff --git a/packages/google-auth/google/auth/compute_engine/_metadata.py b/packages/google-auth/google/auth/compute_engine/_metadata.py index 87e79a9e9e9a..c5b9d5a2bbfa 100644 --- a/packages/google-auth/google/auth/compute_engine/_metadata.py +++ b/packages/google-auth/google/auth/compute_engine/_metadata.py @@ -29,6 +29,7 @@ from google.auth import _helpers from google.auth import environment_vars from google.auth import exceptions +from google.auth import metrics _LOGGER = logging.getLogger(__name__) @@ -121,13 +122,13 @@ def ping(request, timeout=_METADATA_DEFAULT_TIMEOUT, retry_count=3): # the metadata resolution was particularly slow. The latter case is # "unlikely". retries = 0 + headers = _METADATA_HEADERS.copy() + headers[metrics.API_CLIENT_HEADER] = metrics.mds_ping() + while retries < retry_count: try: response = request( - url=_METADATA_IP_ROOT, - method="GET", - headers=_METADATA_HEADERS, - timeout=timeout, + url=_METADATA_IP_ROOT, method="GET", headers=headers, timeout=timeout ) metadata_flavor = response.headers.get(_METADATA_FLAVOR_HEADER) @@ -150,7 +151,13 @@ def ping(request, timeout=_METADATA_DEFAULT_TIMEOUT, retry_count=3): def get( - request, path, root=_METADATA_ROOT, params=None, recursive=False, retry_count=5 + request, + path, + root=_METADATA_ROOT, + params=None, + recursive=False, + retry_count=5, + headers=None, ): """Fetch a resource from the metadata server. @@ -167,6 +174,7 @@ def get( details. retry_count (int): How many times to attempt connecting to metadata server using above timeout. + headers (Optional[Mapping[str, str]]): Headers for the request. Returns: Union[Mapping, str]: If the metadata server returns JSON, a mapping of @@ -180,6 +188,10 @@ def get( base_url = urlparse.urljoin(root, path) query_params = {} if params is None else params + headers_to_use = _METADATA_HEADERS.copy() + if headers: + headers_to_use.update(headers) + if recursive: query_params["recursive"] = "true" @@ -188,7 +200,7 @@ def get( retries = 0 while retries < retry_count: try: - response = request(url=url, method="GET", headers=_METADATA_HEADERS) + response = request(url=url, method="GET", headers=headers_to_use) break except exceptions.TransportError as e: @@ -300,8 +312,12 @@ def get_service_account_token(request, service_account="default", scopes=None): else: params = None + metrics_header = { + metrics.API_CLIENT_HEADER: metrics.token_request_access_token_mds() + } + path = "instance/service-accounts/{0}/token".format(service_account) - token_json = get(request, path, params=params) + token_json = get(request, path, params=params, headers=metrics_header) token_expiry = _helpers.utcnow() + datetime.timedelta( seconds=token_json["expires_in"] ) diff --git a/packages/google-auth/google/auth/compute_engine/credentials.py b/packages/google-auth/google/auth/compute_engine/credentials.py index ed935c17c4f7..30ffb162bca4 100644 --- a/packages/google-auth/google/auth/compute_engine/credentials.py +++ b/packages/google-auth/google/auth/compute_engine/credentials.py @@ -378,7 +378,12 @@ def _call_metadata_identity_endpoint(self, request): try: path = "instance/service-accounts/default/identity" params = {"audience": self._target_audience, "format": "full"} - id_token = _metadata.get(request, path, params=params) + metrics_header = { + metrics.API_CLIENT_HEADER: metrics.token_request_id_token_mds() + } + id_token = _metadata.get( + request, path, params=params, headers=metrics_header + ) except exceptions.TransportError as caught_exc: new_exc = exceptions.RefreshError(caught_exc) six.raise_from(new_exc, caught_exc) diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index c596222a47aa..ddafc08eed5f 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -265,7 +265,10 @@ def _update_token(self, request): "lifetime": str(self._lifetime) + "s", } - headers = {"Content-Type": "application/json"} + headers = { + "Content-Type": "application/json", + metrics.API_CLIENT_HEADER: metrics.token_request_access_token_impersonate(), + } # Apply the source credentials authentication info. self._source_credentials.apply(headers) @@ -426,7 +429,10 @@ def refresh(self, request): "includeEmail": self._include_email, } - headers = {"Content-Type": "application/json"} + headers = { + "Content-Type": "application/json", + metrics.API_CLIENT_HEADER: metrics.token_request_id_token_impersonate(), + } authed_session = AuthorizedSession( self._target_credentials._source_credentials, auth_request=request diff --git a/packages/google-auth/google/oauth2/_client.py b/packages/google-auth/google/oauth2/_client.py index 03f6e8f03e85..e2c9509a9347 100644 --- a/packages/google-auth/google/oauth2/_client.py +++ b/packages/google-auth/google/oauth2/_client.py @@ -34,6 +34,7 @@ from google.auth import _helpers from google.auth import exceptions from google.auth import jwt +from google.auth import metrics from google.auth import transport _URLENCODED_CONTENT_TYPE = "application/x-www-form-urlencoded" @@ -146,6 +147,7 @@ def _token_endpoint_request_no_throw( access_token=None, use_json=False, can_retry=True, + headers=None, **kwargs ): """Makes a request to the OAuth 2.0 authorization server's token endpoint. @@ -161,6 +163,7 @@ def _token_endpoint_request_no_throw( use_json (Optional(bool)): Use urlencoded format or json format for the content type. The default value is False. can_retry (bool): Enable or disable request retry behavior. + headers (Optional[Mapping[str, str]]): The headers for the request. kwargs: Additional arguments passed on to the request method. The kwargs will be passed to `requests.request` method, see: https://docs.python-requests.org/en/latest/api/#requests.request. @@ -176,18 +179,21 @@ def _token_endpoint_request_no_throw( is retryable. """ if use_json: - headers = {"Content-Type": _JSON_CONTENT_TYPE} + headers_to_use = {"Content-Type": _JSON_CONTENT_TYPE} body = json.dumps(body).encode("utf-8") else: - headers = {"Content-Type": _URLENCODED_CONTENT_TYPE} + headers_to_use = {"Content-Type": _URLENCODED_CONTENT_TYPE} body = urllib.parse.urlencode(body).encode("utf-8") if access_token: - headers["Authorization"] = "Bearer {}".format(access_token) + headers_to_use["Authorization"] = "Bearer {}".format(access_token) + + if headers: + headers_to_use.update(headers) def _perform_request(): response = request( - method="POST", url=token_uri, headers=headers, body=body, **kwargs + method="POST", url=token_uri, headers=headers_to_use, body=body, **kwargs ) response_body = ( response.data.decode("utf-8") @@ -231,6 +237,7 @@ def _token_endpoint_request( access_token=None, use_json=False, can_retry=True, + headers=None, **kwargs ): """Makes a request to the OAuth 2.0 authorization server's token endpoint. @@ -245,6 +252,7 @@ def _token_endpoint_request( use_json (Optional(bool)): Use urlencoded format or json format for the content type. The default value is False. can_retry (bool): Enable or disable request retry behavior. + headers (Optional[Mapping[str, str]]): The headers for the request. kwargs: Additional arguments passed on to the request method. The kwargs will be passed to `requests.request` method, see: https://docs.python-requests.org/en/latest/api/#requests.request. @@ -268,6 +276,7 @@ def _token_endpoint_request( access_token=access_token, use_json=use_json, can_retry=can_retry, + headers=headers, **kwargs ) if not response_status_ok: @@ -301,7 +310,13 @@ def jwt_grant(request, token_uri, assertion, can_retry=True): body = {"assertion": assertion, "grant_type": _JWT_GRANT_TYPE} response_data = _token_endpoint_request( - request, token_uri, body, can_retry=can_retry + request, + token_uri, + body, + can_retry=can_retry, + headers={ + metrics.API_CLIENT_HEADER: metrics.token_request_access_token_sa_assertion() + }, ) try: @@ -384,7 +399,13 @@ def id_token_jwt_grant(request, token_uri, assertion, can_retry=True): body = {"assertion": assertion, "grant_type": _JWT_GRANT_TYPE} response_data = _token_endpoint_request( - request, token_uri, body, can_retry=can_retry + request, + token_uri, + body, + can_retry=can_retry, + headers={ + metrics.API_CLIENT_HEADER: metrics.token_request_id_token_sa_assertion() + }, ) try: diff --git a/packages/google-auth/google/oauth2/reauth.py b/packages/google-auth/google/oauth2/reauth.py index 84fd0be2b3c6..114c47c4805d 100644 --- a/packages/google-auth/google/oauth2/reauth.py +++ b/packages/google-auth/google/oauth2/reauth.py @@ -37,6 +37,7 @@ from six.moves import range from google.auth import exceptions +from google.auth import metrics from google.oauth2 import _client from google.oauth2 import challenges @@ -94,9 +95,15 @@ def _get_challenges( body = {"supportedChallengeTypes": supported_challenge_types} if requested_scopes: body["oauthScopesForDomainPolicyLookup"] = requested_scopes + metrics_header = {metrics.API_CLIENT_HEADER: metrics.reauth_start()} return _client._token_endpoint_request( - request, _REAUTH_API + ":start", body, access_token=access_token, use_json=True + request, + _REAUTH_API + ":start", + body, + access_token=access_token, + use_json=True, + headers=metrics_header, ) @@ -123,6 +130,7 @@ def _send_challenge_result( "action": "RESPOND", "proposalResponse": client_input, } + metrics_header = {metrics.API_CLIENT_HEADER: metrics.reauth_continue()} return _client._token_endpoint_request( request, @@ -130,6 +138,7 @@ def _send_challenge_result( body, access_token=access_token, use_json=True, + headers=metrics_header, ) @@ -320,9 +329,10 @@ def refresh_grant( body["scope"] = " ".join(scopes) if rapt_token: body["rapt"] = rapt_token + metrics_header = {metrics.API_CLIENT_HEADER: metrics.token_request_user()} response_status_ok, response_data, retryable_error = _client._token_endpoint_request_no_throw( - request, token_uri, body + request, token_uri, body, headers=metrics_header ) if ( not response_status_ok @@ -345,7 +355,9 @@ def refresh_grant( response_status_ok, response_data, retryable_error, - ) = _client._token_endpoint_request_no_throw(request, token_uri, body) + ) = _client._token_endpoint_request_no_throw( + request, token_uri, body, headers=metrics_header + ) if not response_status_ok: _client._handle_error_response(response_data, retryable_error) diff --git a/packages/google-auth/tests/compute_engine/test__metadata.py b/packages/google-auth/tests/compute_engine/test__metadata.py index 7f3386276309..f543426d39f0 100644 --- a/packages/google-auth/tests/compute_engine/test__metadata.py +++ b/packages/google-auth/tests/compute_engine/test__metadata.py @@ -38,6 +38,15 @@ DATA_DIR, "smbios_product_name_non_google" ) +ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE = ( + "gl-python/3.7 auth/1.1 auth-request-type/at cred-type/mds" +) +MDS_PING_METRICS_HEADER_VALUE = "gl-python/3.7 auth/1.1 auth-request-type/mds" +MDS_PING_REQUEST_HEADER = { + "metadata-flavor": "Google", + "x-goog-api-client": MDS_PING_METRICS_HEADER_VALUE, +} + def make_request(data, status=http_client.OK, headers=None, retry=False): response = mock.create_autospec(transport.Response, instance=True) @@ -87,7 +96,8 @@ def test_is_on_gce_linux_success(): assert _metadata.is_on_gce(request) -def test_ping_success(): +@mock.patch("google.auth.metrics.mds_ping", return_value=MDS_PING_METRICS_HEADER_VALUE) +def test_ping_success(mock_metrics_header_value): request = make_request("", headers=_metadata._METADATA_HEADERS) assert _metadata.ping(request) @@ -95,12 +105,13 @@ def test_ping_success(): request.assert_called_once_with( method="GET", url=_metadata._METADATA_IP_ROOT, - headers=_metadata._METADATA_HEADERS, + headers=MDS_PING_REQUEST_HEADER, timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) -def test_ping_success_retry(): +@mock.patch("google.auth.metrics.mds_ping", return_value=MDS_PING_METRICS_HEADER_VALUE) +def test_ping_success_retry(mock_metrics_header_value): request = make_request("", headers=_metadata._METADATA_HEADERS, retry=True) assert _metadata.ping(request) @@ -108,7 +119,7 @@ def test_ping_success_retry(): request.assert_called_with( method="GET", url=_metadata._METADATA_IP_ROOT, - headers=_metadata._METADATA_HEADERS, + headers=MDS_PING_REQUEST_HEADER, timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) assert request.call_count == 2 @@ -127,7 +138,8 @@ def test_ping_failure_connection_failed(): assert not _metadata.ping(request) -def test_ping_success_custom_root(): +@mock.patch("google.auth.metrics.mds_ping", return_value=MDS_PING_METRICS_HEADER_VALUE) +def test_ping_success_custom_root(mock_metrics_header_value): request = make_request("", headers=_metadata._METADATA_HEADERS) fake_ip = "1.2.3.4" @@ -143,7 +155,7 @@ def test_ping_success_custom_root(): request.assert_called_once_with( method="GET", url="http://" + fake_ip, - headers=_metadata._METADATA_HEADERS, + headers=MDS_PING_REQUEST_HEADER, timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) @@ -341,8 +353,12 @@ def test_get_project_id(): assert project_id == project +@mock.patch( + "google.auth.metrics.token_request_access_token_mds", + return_value=ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, +) @mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) -def test_get_service_account_token(utcnow): +def test_get_service_account_token(utcnow, mock_metrics_header_value): ttl = 500 request = make_request( json.dumps({"access_token": "token", "expires_in": ttl}), @@ -354,14 +370,21 @@ def test_get_service_account_token(utcnow): request.assert_called_once_with( method="GET", url=_metadata._METADATA_ROOT + PATH + "/token", - headers=_metadata._METADATA_HEADERS, + headers={ + "metadata-flavor": "Google", + "x-goog-api-client": ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, + }, ) assert token == "token" assert expiry == utcnow() + datetime.timedelta(seconds=ttl) +@mock.patch( + "google.auth.metrics.token_request_access_token_mds", + return_value=ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, +) @mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) -def test_get_service_account_token_with_scopes_list(utcnow): +def test_get_service_account_token_with_scopes_list(utcnow, mock_metrics_header_value): ttl = 500 request = make_request( json.dumps({"access_token": "token", "expires_in": ttl}), @@ -373,14 +396,23 @@ def test_get_service_account_token_with_scopes_list(utcnow): request.assert_called_once_with( method="GET", url=_metadata._METADATA_ROOT + PATH + "/token" + "?scopes=foo%2Cbar", - headers=_metadata._METADATA_HEADERS, + headers={ + "metadata-flavor": "Google", + "x-goog-api-client": ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, + }, ) assert token == "token" assert expiry == utcnow() + datetime.timedelta(seconds=ttl) +@mock.patch( + "google.auth.metrics.token_request_access_token_mds", + return_value=ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, +) @mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) -def test_get_service_account_token_with_scopes_string(utcnow): +def test_get_service_account_token_with_scopes_string( + utcnow, mock_metrics_header_value +): ttl = 500 request = make_request( json.dumps({"access_token": "token", "expires_in": ttl}), @@ -392,7 +424,10 @@ def test_get_service_account_token_with_scopes_string(utcnow): request.assert_called_once_with( method="GET", url=_metadata._METADATA_ROOT + PATH + "/token" + "?scopes=foo%2Cbar", - headers=_metadata._METADATA_HEADERS, + headers={ + "metadata-flavor": "Google", + "x-goog-api-client": ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, + }, ) assert token == "token" assert expiry == utcnow() + datetime.timedelta(seconds=ttl) diff --git a/packages/google-auth/tests/compute_engine/test_credentials.py b/packages/google-auth/tests/compute_engine/test_credentials.py index 4234d98e48cb..009e42848cdd 100644 --- a/packages/google-auth/tests/compute_engine/test_credentials.py +++ b/packages/google-auth/tests/compute_engine/test_credentials.py @@ -43,6 +43,13 @@ b"bsxbLa6Fp0SYeYwO8ifEnkRvasVpc1WTQqfRB2JCj5pTBDzJpIpFCMmnQ" ) +ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE = ( + "gl-python/3.7 auth/1.1 auth-request-type/at cred-type/mds" +) +ID_TOKEN_REQUEST_METRICS_HEADER_VALUE = ( + "gl-python/3.7 auth/1.1 auth-request-type/it cred-type/mds" +) + class TestCredentials(object): credentials = None @@ -96,12 +103,16 @@ def test_refresh_success(self, get, utcnow): # expired) assert self.credentials.valid + @mock.patch( + "google.auth.metrics.token_request_access_token_mds", + return_value=ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, + ) @mock.patch( "google.auth._helpers.utcnow", return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD, ) @mock.patch("google.auth.compute_engine._metadata.get", autospec=True) - def test_refresh_success_with_scopes(self, get, utcnow): + def test_refresh_success_with_scopes(self, get, utcnow, mock_metrics_header_value): get.side_effect = [ { # First request is for sevice account info. @@ -133,7 +144,10 @@ def test_refresh_success_with_scopes(self, get, utcnow): assert self.credentials.valid kwargs = get.call_args[1] - assert kwargs == {"params": {"scopes": "three,four"}} + assert kwargs["params"] == {"scopes": "three,four"} + assert kwargs["headers"] == { + "x-goog-api-client": ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE + } @mock.patch("google.auth.compute_engine._metadata.get", autospec=True) def test_refresh_error(self, get): @@ -732,11 +746,17 @@ def test_sign_bytes(self, sign, get): # The JWT token signature is 'signature' encoded in base 64: assert signature == b"signature" + @mock.patch( + "google.auth.metrics.token_request_id_token_mds", + return_value=ID_TOKEN_REQUEST_METRICS_HEADER_VALUE, + ) @mock.patch( "google.auth.compute_engine._metadata.get_service_account_info", autospec=True ) @mock.patch("google.auth.compute_engine._metadata.get", autospec=True) - def test_get_id_token_from_metadata(self, get, get_service_account_info): + def test_get_id_token_from_metadata( + self, get, get_service_account_info, mock_metrics_header_value + ): get.return_value = SAMPLE_ID_TOKEN get_service_account_info.return_value = {"email": "foo@example.com"} @@ -745,6 +765,10 @@ def test_get_id_token_from_metadata(self, get, get_service_account_info): ) cred.refresh(request=mock.Mock()) + assert get.call_args.kwargs["headers"] == { + "x-goog-api-client": ID_TOKEN_REQUEST_METRICS_HEADER_VALUE + } + assert cred.token == SAMPLE_ID_TOKEN assert cred.expiry == datetime.datetime.fromtimestamp(SAMPLE_ID_TOKEN_EXP) assert cred._use_metadata_identity_endpoint diff --git a/packages/google-auth/tests/oauth2/test__client.py b/packages/google-auth/tests/oauth2/test__client.py index b34d2eb35ab1..9450fa1fd7d5 100644 --- a/packages/google-auth/tests/oauth2/test__client.py +++ b/packages/google-auth/tests/oauth2/test__client.py @@ -46,6 +46,13 @@ " https://www.googleapis.com/auth/logging.write" ) +ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE = ( + "gl-python/3.7 auth/1.1 auth-request-type/at cred-type/sa" +) +ID_TOKEN_REQUEST_METRICS_HEADER_VALUE = ( + "gl-python/3.7 auth/1.1 auth-request-type/it cred-type/sa" +) + @pytest.mark.parametrize("retryable", [True, False]) def test__handle_error_response(retryable): @@ -483,47 +490,83 @@ def test_refresh_grant_no_access_token(): assert not excinfo.value.retryable +@mock.patch( + "google.auth.metrics.token_request_access_token_sa_assertion", + return_value=ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, +) @mock.patch("google.oauth2._client._parse_expiry", return_value=None) @mock.patch.object(_client, "_token_endpoint_request", autospec=True) -def test_jwt_grant_retry_default(mock_token_endpoint_request, mock_expiry): +def test_jwt_grant_retry_default( + mock_token_endpoint_request, mock_expiry, mock_metrics_header_value +): _client.jwt_grant(mock.Mock(), mock.Mock(), mock.Mock()) mock_token_endpoint_request.assert_called_with( - mock.ANY, mock.ANY, mock.ANY, can_retry=True + mock.ANY, + mock.ANY, + mock.ANY, + can_retry=True, + headers={"x-goog-api-client": ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE}, ) @pytest.mark.parametrize("can_retry", [True, False]) +@mock.patch( + "google.auth.metrics.token_request_access_token_sa_assertion", + return_value=ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, +) @mock.patch("google.oauth2._client._parse_expiry", return_value=None) @mock.patch.object(_client, "_token_endpoint_request", autospec=True) def test_jwt_grant_retry_with_retry( - mock_token_endpoint_request, mock_expiry, can_retry + mock_token_endpoint_request, mock_expiry, mock_metrics_header_value, can_retry ): _client.jwt_grant(mock.Mock(), mock.Mock(), mock.Mock(), can_retry=can_retry) mock_token_endpoint_request.assert_called_with( - mock.ANY, mock.ANY, mock.ANY, can_retry=can_retry + mock.ANY, + mock.ANY, + mock.ANY, + can_retry=can_retry, + headers={"x-goog-api-client": ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE}, ) +@mock.patch( + "google.auth.metrics.token_request_id_token_sa_assertion", + return_value=ID_TOKEN_REQUEST_METRICS_HEADER_VALUE, +) @mock.patch("google.auth.jwt.decode", return_value={"exp": 0}) @mock.patch.object(_client, "_token_endpoint_request", autospec=True) -def test_id_token_jwt_grant_retry_default(mock_token_endpoint_request, mock_jwt_decode): +def test_id_token_jwt_grant_retry_default( + mock_token_endpoint_request, mock_jwt_decode, mock_metrics_header_value +): _client.id_token_jwt_grant(mock.Mock(), mock.Mock(), mock.Mock()) mock_token_endpoint_request.assert_called_with( - mock.ANY, mock.ANY, mock.ANY, can_retry=True + mock.ANY, + mock.ANY, + mock.ANY, + can_retry=True, + headers={"x-goog-api-client": ID_TOKEN_REQUEST_METRICS_HEADER_VALUE}, ) @pytest.mark.parametrize("can_retry", [True, False]) +@mock.patch( + "google.auth.metrics.token_request_id_token_sa_assertion", + return_value=ID_TOKEN_REQUEST_METRICS_HEADER_VALUE, +) @mock.patch("google.auth.jwt.decode", return_value={"exp": 0}) @mock.patch.object(_client, "_token_endpoint_request", autospec=True) def test_id_token_jwt_grant_retry_with_retry( - mock_token_endpoint_request, mock_jwt_decode, can_retry + mock_token_endpoint_request, mock_jwt_decode, mock_metrics_header_value, can_retry ): _client.id_token_jwt_grant( mock.Mock(), mock.Mock(), mock.Mock(), can_retry=can_retry ) mock_token_endpoint_request.assert_called_with( - mock.ANY, mock.ANY, mock.ANY, can_retry=can_retry + mock.ANY, + mock.ANY, + mock.ANY, + can_retry=can_retry, + headers={"x-goog-api-client": ID_TOKEN_REQUEST_METRICS_HEADER_VALUE}, ) diff --git a/packages/google-auth/tests/oauth2/test_reauth.py b/packages/google-auth/tests/oauth2/test_reauth.py index 47aa8fa95cbe..54f59422dc28 100644 --- a/packages/google-auth/tests/oauth2/test_reauth.py +++ b/packages/google-auth/tests/oauth2/test_reauth.py @@ -40,6 +40,12 @@ "encodedProofOfReauthToken": "new_rapt_token", } +REAUTH_START_METRICS_HEADER_VALUE = "gl-python/3.7 auth/1.1 auth-request-type/re-start" +REAUTH_CONTINUE_METRICS_HEADER_VALUE = ( + "gl-python/3.7 auth/1.1 auth-request-type/re-cont" +) +TOKEN_REQUEST_METRICS_HEADER_VALUE = "gl-python/3.7 auth/1.1 cred-type/u" + class MockChallenge(object): def __init__(self, name, locally_eligible, challenge_input): @@ -56,7 +62,10 @@ def test_is_interactive(): assert reauth.is_interactive() -def test__get_challenges(): +@mock.patch( + "google.auth.metrics.reauth_start", return_value=REAUTH_START_METRICS_HEADER_VALUE +) +def test__get_challenges(mock_metrics_header_value): with mock.patch( "google.oauth2._client._token_endpoint_request" ) as mock_token_endpoint_request: @@ -67,10 +76,14 @@ def test__get_challenges(): {"supportedChallengeTypes": ["SAML"]}, access_token="token", use_json=True, + headers={"x-goog-api-client": REAUTH_START_METRICS_HEADER_VALUE}, ) -def test__get_challenges_with_scopes(): +@mock.patch( + "google.auth.metrics.reauth_start", return_value=REAUTH_START_METRICS_HEADER_VALUE +) +def test__get_challenges_with_scopes(mock_metrics_header_value): with mock.patch( "google.oauth2._client._token_endpoint_request" ) as mock_token_endpoint_request: @@ -86,10 +99,15 @@ def test__get_challenges_with_scopes(): }, access_token="token", use_json=True, + headers={"x-goog-api-client": REAUTH_START_METRICS_HEADER_VALUE}, ) -def test__send_challenge_result(): +@mock.patch( + "google.auth.metrics.reauth_continue", + return_value=REAUTH_CONTINUE_METRICS_HEADER_VALUE, +) +def test__send_challenge_result(mock_metrics_header_value): with mock.patch( "google.oauth2._client._token_endpoint_request" ) as mock_token_endpoint_request: @@ -107,6 +125,7 @@ def test__send_challenge_result(): }, access_token="token", use_json=True, + headers={"x-goog-api-client": REAUTH_CONTINUE_METRICS_HEADER_VALUE}, ) @@ -270,7 +289,11 @@ def test_get_rapt_token(): ) -def test_refresh_grant_failed(): +@mock.patch( + "google.auth.metrics.token_request_user", + return_value=TOKEN_REQUEST_METRICS_HEADER_VALUE, +) +def test_refresh_grant_failed(mock_metrics_header_value): with mock.patch( "google.oauth2._client._token_endpoint_request_no_throw" ) as mock_token_request: @@ -299,6 +322,7 @@ def test_refresh_grant_failed(): "scope": "foo bar", "rapt": "rapt_token", }, + headers={"x-goog-api-client": TOKEN_REQUEST_METRICS_HEADER_VALUE}, ) diff --git a/packages/google-auth/tests/test_aws.py b/packages/google-auth/tests/test_aws.py index d50a8f4e1ee7..45397d34f4c7 100644 --- a/packages/google-auth/tests/test_aws.py +++ b/packages/google-auth/tests/test_aws.py @@ -28,6 +28,10 @@ from google.auth import transport +IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE = ( + "gl-python/3.7 auth/1.1 auth-request-type/at cred-type/imp" +) + CLIENT_ID = "username" CLIENT_SECRET = "password" # Base64 encoding of "username:password". @@ -1901,8 +1905,14 @@ def test_refresh_success_without_impersonation_use_default_scopes(self, utcnow): assert credentials.scopes is None assert credentials.default_scopes == SCOPES + @mock.patch( + "google.auth.metrics.token_request_access_token_impersonate", + return_value=IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, + ) @mock.patch("google.auth._helpers.utcnow") - def test_refresh_success_with_impersonation_ignore_default_scopes(self, utcnow): + def test_refresh_success_with_impersonation_ignore_default_scopes( + self, utcnow, mock_metrics_header_value + ): utcnow.return_value = datetime.datetime.strptime( self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ" ) @@ -1937,6 +1947,7 @@ def test_refresh_success_with_impersonation_ignore_default_scopes(self, utcnow): "Content-Type": "application/json", "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]), "x-goog-user-project": QUOTA_PROJECT_ID, + "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, } impersonation_request_data = { "delegates": None, @@ -1985,8 +1996,14 @@ def test_refresh_success_with_impersonation_ignore_default_scopes(self, utcnow): assert credentials.scopes == SCOPES assert credentials.default_scopes == ["ignored"] + @mock.patch( + "google.auth.metrics.token_request_access_token_impersonate", + return_value=IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, + ) @mock.patch("google.auth._helpers.utcnow") - def test_refresh_success_with_impersonation_use_default_scopes(self, utcnow): + def test_refresh_success_with_impersonation_use_default_scopes( + self, utcnow, mock_metrics_header_value + ): utcnow.return_value = datetime.datetime.strptime( self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ" ) @@ -2021,6 +2038,7 @@ def test_refresh_success_with_impersonation_use_default_scopes(self, utcnow): "Content-Type": "application/json", "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]), "x-goog-user-project": QUOTA_PROJECT_ID, + "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, } impersonation_request_data = { "delegates": None, diff --git a/packages/google-auth/tests/test_external_account.py b/packages/google-auth/tests/test_external_account.py index 598c3760cb2f..0e8017eeee75 100644 --- a/packages/google-auth/tests/test_external_account.py +++ b/packages/google-auth/tests/test_external_account.py @@ -26,6 +26,8 @@ from google.auth import transport +METRICS_HEADER_VALUE = "gl-python/3.7 auth/1.1 auth-request-type/at cred-type/imp" + CLIENT_ID = "username" CLIENT_SECRET = "password" # Base64 encoding of "username:password" @@ -751,7 +753,13 @@ def test_refresh_workforce_with_client_auth_and_no_workforce_project_success( assert not credentials.expired assert credentials.token == response["access_token"] - def test_refresh_impersonation_without_client_auth_success(self): + @mock.patch( + "google.auth.metrics.token_request_access_token_impersonate", + return_value=METRICS_HEADER_VALUE, + ) + def test_refresh_impersonation_without_client_auth_success( + self, mock_metrics_header_value + ): # Simulate service account access token expires in 2800 seconds. expire_time = ( _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=2800) @@ -776,6 +784,7 @@ def test_refresh_impersonation_without_client_auth_success(self): impersonation_headers = { "Content-Type": "application/json", "authorization": "Bearer {}".format(token_response["access_token"]), + "x-goog-api-client": METRICS_HEADER_VALUE, } impersonation_request_data = { "delegates": None, @@ -815,7 +824,13 @@ def test_refresh_impersonation_without_client_auth_success(self): assert not credentials.expired assert credentials.token == impersonation_response["accessToken"] - def test_refresh_workforce_impersonation_without_client_auth_success(self): + @mock.patch( + "google.auth.metrics.token_request_access_token_impersonate", + return_value=METRICS_HEADER_VALUE, + ) + def test_refresh_workforce_impersonation_without_client_auth_success( + self, mock_metrics_header_value + ): # Simulate service account access token expires in 2800 seconds. expire_time = ( _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=2800) @@ -843,6 +858,7 @@ def test_refresh_workforce_impersonation_without_client_auth_success(self): impersonation_headers = { "Content-Type": "application/json", "authorization": "Bearer {}".format(token_response["access_token"]), + "x-goog-api-client": METRICS_HEADER_VALUE, } impersonation_request_data = { "delegates": None, @@ -1000,7 +1016,13 @@ def test_refresh_with_client_auth_success(self): assert not credentials.expired assert credentials.token == self.SUCCESS_RESPONSE["access_token"] - def test_refresh_impersonation_with_client_auth_success_ignore_default_scopes(self): + @mock.patch( + "google.auth.metrics.token_request_access_token_impersonate", + return_value=METRICS_HEADER_VALUE, + ) + def test_refresh_impersonation_with_client_auth_success_ignore_default_scopes( + self, mock_metrics_header_value + ): # Simulate service account access token expires in 2800 seconds. expire_time = ( _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=2800) @@ -1028,6 +1050,7 @@ def test_refresh_impersonation_with_client_auth_success_ignore_default_scopes(se impersonation_headers = { "Content-Type": "application/json", "authorization": "Bearer {}".format(token_response["access_token"]), + "x-goog-api-client": METRICS_HEADER_VALUE, } impersonation_request_data = { "delegates": None, @@ -1071,7 +1094,13 @@ def test_refresh_impersonation_with_client_auth_success_ignore_default_scopes(se assert not credentials.expired assert credentials.token == impersonation_response["accessToken"] - def test_refresh_impersonation_with_client_auth_success_use_default_scopes(self): + @mock.patch( + "google.auth.metrics.token_request_access_token_impersonate", + return_value=METRICS_HEADER_VALUE, + ) + def test_refresh_impersonation_with_client_auth_success_use_default_scopes( + self, mock_metrics_header_value + ): # Simulate service account access token expires in 2800 seconds. expire_time = ( _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=2800) @@ -1099,6 +1128,7 @@ def test_refresh_impersonation_with_client_auth_success_use_default_scopes(self) impersonation_headers = { "Content-Type": "application/json", "authorization": "Bearer {}".format(token_response["access_token"]), + "x-goog-api-client": METRICS_HEADER_VALUE, } impersonation_request_data = { "delegates": None, @@ -1488,7 +1518,13 @@ def test_project_id_without_scopes(self): assert credentials.get_project_id(None) is None - def test_get_project_id_cloud_resource_manager_success(self): + @mock.patch( + "google.auth.metrics.token_request_access_token_impersonate", + return_value=METRICS_HEADER_VALUE, + ) + def test_get_project_id_cloud_resource_manager_success( + self, mock_metrics_header_value + ): # STS token exchange request/response. token_response = self.SUCCESS_RESPONSE.copy() token_headers = {"Content-Type": "application/x-www-form-urlencoded"} @@ -1513,6 +1549,7 @@ def test_get_project_id_cloud_resource_manager_success(self): "Content-Type": "application/json", "x-goog-user-project": self.QUOTA_PROJECT_ID, "authorization": "Bearer {}".format(token_response["access_token"]), + "x-goog-api-client": METRICS_HEADER_VALUE, } impersonation_request_data = { "delegates": None, @@ -1638,7 +1675,11 @@ def test_workforce_pool_get_project_id_cloud_resource_manager_success(self): # No additional requests. assert len(request.call_args_list) == 2 - def test_refresh_impersonation_with_lifetime(self): + @mock.patch( + "google.auth.metrics.token_request_access_token_impersonate", + return_value=METRICS_HEADER_VALUE, + ) + def test_refresh_impersonation_with_lifetime(self, mock_metrics_header_value): # Simulate service account access token expires in 2800 seconds. expire_time = ( _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=2800) @@ -1663,6 +1704,7 @@ def test_refresh_impersonation_with_lifetime(self): impersonation_headers = { "Content-Type": "application/json", "authorization": "Bearer {}".format(token_response["access_token"]), + "x-goog-api-client": METRICS_HEADER_VALUE, } impersonation_request_data = { "delegates": None, diff --git a/packages/google-auth/tests/test_identity_pool.py b/packages/google-auth/tests/test_identity_pool.py index 6c1c6623c3b1..561af35ac04e 100644 --- a/packages/google-auth/tests/test_identity_pool.py +++ b/packages/google-auth/tests/test_identity_pool.py @@ -286,6 +286,9 @@ def assert_underlying_credentials_refresh( json.dumps({"userProject": workforce_pool_user_project}) ) + metrics_header_value = ( + "gl-python/3.7 auth/1.1 auth-request-type/at cred-type/imp" + ) if service_account_impersonation_url: # Service account impersonation request/response. expire_time = ( @@ -299,6 +302,7 @@ def assert_underlying_credentials_refresh( impersonation_headers = { "Content-Type": "application/json", "authorization": "Bearer {}".format(token_response["access_token"]), + "x-goog-api-client": metrics_header_value, } impersonation_request_data = { "delegates": None, @@ -321,7 +325,11 @@ def assert_underlying_credentials_refresh( request = cls.make_mock_request(*[el for req in requests for el in req]) - credentials.refresh(request) + with mock.patch( + "google.auth.metrics.token_request_access_token_impersonate", + return_value=metrics_header_value, + ): + credentials.refresh(request) assert len(request.call_args_list) == len(requests) if credential_data: diff --git a/packages/google-auth/tests/test_impersonated_credentials.py b/packages/google-auth/tests/test_impersonated_credentials.py index 8f2e1fdbeea8..dc091fe610fc 100644 --- a/packages/google-auth/tests/test_impersonated_credentials.py +++ b/packages/google-auth/tests/test_impersonated_credentials.py @@ -55,6 +55,13 @@ SIGNER = crypt.RSASigner.from_string(PRIVATE_KEY_BYTES, "1") TOKEN_URI = "https://example.com/oauth2/token" +ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE = ( + "gl-python/3.7 auth/1.1 auth-request-type/at cred-type/imp" +) +ID_TOKEN_REQUEST_METRICS_HEADER_VALUE = ( + "gl-python/3.7 auth/1.1 auth-request-type/it cred-type/imp" +) + @pytest.fixture def mock_donor_credentials(): @@ -188,10 +195,18 @@ def test_refresh_success(self, use_data_bytes, mock_donor_credentials): use_data_bytes=use_data_bytes, ) - credentials.refresh(request) + with mock.patch( + "google.auth.metrics.token_request_access_token_impersonate", + return_value=ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, + ): + credentials.refresh(request) assert credentials.valid assert not credentials.expired + assert ( + request.call_args.kwargs["headers"]["x-goog-api-client"] + == ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE + ) @pytest.mark.parametrize("use_data_bytes", [True, False]) def test_refresh_success_iam_endpoint_override( @@ -454,6 +469,36 @@ def test_id_token_success( assert id_creds.token == ID_TOKEN_DATA assert id_creds.expiry == datetime.datetime.fromtimestamp(ID_TOKEN_EXPIRY) + def test_id_token_metrics(self, mock_donor_credentials): + credentials = self.make_credentials(lifetime=None) + credentials.token = "token" + credentials.expiry = None + target_audience = "https://foo.bar" + + id_creds = impersonated_credentials.IDTokenCredentials( + credentials, target_audience=target_audience + ) + + with mock.patch( + "google.auth.metrics.token_request_id_token_impersonate", + return_value=ID_TOKEN_REQUEST_METRICS_HEADER_VALUE, + ): + with mock.patch( + "google.auth.transport.requests.AuthorizedSession.post", autospec=True + ) as mock_post: + data = {"token": ID_TOKEN_DATA} + mock_post.return_value = MockResponse(data, http_client.OK) + id_creds.refresh(None) + + assert id_creds.token == ID_TOKEN_DATA + assert id_creds.expiry == datetime.datetime.fromtimestamp( + ID_TOKEN_EXPIRY + ) + assert ( + mock_post.call_args.kwargs["headers"]["x-goog-api-client"] + == ID_TOKEN_REQUEST_METRICS_HEADER_VALUE + ) + def test_id_token_from_credential( self, mock_donor_credentials, mock_authorizedsession_idtoken ): From 4e9e344dd0481b1000d08e23ed1aef7e8b61d873 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Thu, 25 May 2023 15:37:46 -0700 Subject: [PATCH 717/966] test: fix test_external_account.py constant name (#1308) --- .../tests/test_external_account.py | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/packages/google-auth/tests/test_external_account.py b/packages/google-auth/tests/test_external_account.py index 0e8017eeee75..5d9cec7cb204 100644 --- a/packages/google-auth/tests/test_external_account.py +++ b/packages/google-auth/tests/test_external_account.py @@ -26,7 +26,9 @@ from google.auth import transport -METRICS_HEADER_VALUE = "gl-python/3.7 auth/1.1 auth-request-type/at cred-type/imp" +IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE = ( + "gl-python/3.7 auth/1.1 auth-request-type/at cred-type/imp" +) CLIENT_ID = "username" CLIENT_SECRET = "password" @@ -755,7 +757,7 @@ def test_refresh_workforce_with_client_auth_and_no_workforce_project_success( @mock.patch( "google.auth.metrics.token_request_access_token_impersonate", - return_value=METRICS_HEADER_VALUE, + return_value=IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, ) def test_refresh_impersonation_without_client_auth_success( self, mock_metrics_header_value @@ -784,7 +786,7 @@ def test_refresh_impersonation_without_client_auth_success( impersonation_headers = { "Content-Type": "application/json", "authorization": "Bearer {}".format(token_response["access_token"]), - "x-goog-api-client": METRICS_HEADER_VALUE, + "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, } impersonation_request_data = { "delegates": None, @@ -826,7 +828,7 @@ def test_refresh_impersonation_without_client_auth_success( @mock.patch( "google.auth.metrics.token_request_access_token_impersonate", - return_value=METRICS_HEADER_VALUE, + return_value=IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, ) def test_refresh_workforce_impersonation_without_client_auth_success( self, mock_metrics_header_value @@ -858,7 +860,7 @@ def test_refresh_workforce_impersonation_without_client_auth_success( impersonation_headers = { "Content-Type": "application/json", "authorization": "Bearer {}".format(token_response["access_token"]), - "x-goog-api-client": METRICS_HEADER_VALUE, + "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, } impersonation_request_data = { "delegates": None, @@ -1018,7 +1020,7 @@ def test_refresh_with_client_auth_success(self): @mock.patch( "google.auth.metrics.token_request_access_token_impersonate", - return_value=METRICS_HEADER_VALUE, + return_value=IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, ) def test_refresh_impersonation_with_client_auth_success_ignore_default_scopes( self, mock_metrics_header_value @@ -1050,7 +1052,7 @@ def test_refresh_impersonation_with_client_auth_success_ignore_default_scopes( impersonation_headers = { "Content-Type": "application/json", "authorization": "Bearer {}".format(token_response["access_token"]), - "x-goog-api-client": METRICS_HEADER_VALUE, + "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, } impersonation_request_data = { "delegates": None, @@ -1096,7 +1098,7 @@ def test_refresh_impersonation_with_client_auth_success_ignore_default_scopes( @mock.patch( "google.auth.metrics.token_request_access_token_impersonate", - return_value=METRICS_HEADER_VALUE, + return_value=IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, ) def test_refresh_impersonation_with_client_auth_success_use_default_scopes( self, mock_metrics_header_value @@ -1128,7 +1130,7 @@ def test_refresh_impersonation_with_client_auth_success_use_default_scopes( impersonation_headers = { "Content-Type": "application/json", "authorization": "Bearer {}".format(token_response["access_token"]), - "x-goog-api-client": METRICS_HEADER_VALUE, + "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, } impersonation_request_data = { "delegates": None, @@ -1520,7 +1522,7 @@ def test_project_id_without_scopes(self): @mock.patch( "google.auth.metrics.token_request_access_token_impersonate", - return_value=METRICS_HEADER_VALUE, + return_value=IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, ) def test_get_project_id_cloud_resource_manager_success( self, mock_metrics_header_value @@ -1549,7 +1551,7 @@ def test_get_project_id_cloud_resource_manager_success( "Content-Type": "application/json", "x-goog-user-project": self.QUOTA_PROJECT_ID, "authorization": "Bearer {}".format(token_response["access_token"]), - "x-goog-api-client": METRICS_HEADER_VALUE, + "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, } impersonation_request_data = { "delegates": None, @@ -1677,7 +1679,7 @@ def test_workforce_pool_get_project_id_cloud_resource_manager_success(self): @mock.patch( "google.auth.metrics.token_request_access_token_impersonate", - return_value=METRICS_HEADER_VALUE, + return_value=IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, ) def test_refresh_impersonation_with_lifetime(self, mock_metrics_header_value): # Simulate service account access token expires in 2800 seconds. @@ -1704,7 +1706,7 @@ def test_refresh_impersonation_with_lifetime(self, mock_metrics_header_value): impersonation_headers = { "Content-Type": "application/json", "authorization": "Bearer {}".format(token_response["access_token"]), - "x-goog-api-client": METRICS_HEADER_VALUE, + "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, } impersonation_request_data = { "delegates": None, From 44267b70edf372d6e6b7eea951d2b36d475a6131 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 25 May 2023 16:17:16 -0700 Subject: [PATCH 718/966] chore(main): release 2.19.0 (#1300) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> Co-authored-by: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 11 +++++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 249b0ea177b0..2e327a05eb8a 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,17 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.19.0](https://github.com/googleapis/google-auth-library-python/compare/v2.18.1...v2.19.0) (2023-05-25) + + +### Features + +* Add metrics (part 1) ([#1298](https://github.com/googleapis/google-auth-library-python/issues/1298)) ([246dd07](https://github.com/googleapis/google-auth-library-python/commit/246dd079388e21036831e2ebc7586c9f596acbec)) +* Add metrics (part 2) ([#1303](https://github.com/googleapis/google-auth-library-python/issues/1303)) ([ebd5af7](https://github.com/googleapis/google-auth-library-python/commit/ebd5af7d372d4cde892601138de282217586135f)) +* Add metrics (part 3) ([#1305](https://github.com/googleapis/google-auth-library-python/issues/1305)) ([c7011b6](https://github.com/googleapis/google-auth-library-python/commit/c7011b6d3dba479390ce08a96ac1923de2a4e8b4)) +* Expose `universe_domain` for external account creds ([#1296](https://github.com/googleapis/google-auth-library-python/issues/1296)) ([ee07053](https://github.com/googleapis/google-auth-library-python/commit/ee070535ce06661eeb12e407e782c155b142cecf)) +* Remove python 2.7 from setup.py and nox tests ([#1301](https://github.com/googleapis/google-auth-library-python/issues/1301)) ([8437490](https://github.com/googleapis/google-auth-library-python/commit/84374903f418535d17811690632be9395403afaf)) + ## [2.18.1](https://github.com/googleapis/google-auth-library-python/compare/v2.18.0...v2.18.1) (2023-05-17) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index b766f25e12e6..1f384f80e9d1 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.18.1" +__version__ = "2.19.0" From 83f0d5edc6a8b0d52efcebec2c3d6d3b5b1fdf62 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Wed, 31 May 2023 15:28:23 -0700 Subject: [PATCH 719/966] fix: check id token error response (#1315) --- .../google/auth/impersonated_credentials.py | 18 ++++++++++----- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../tests/test_impersonated_credentials.py | 21 ++++++++++++++++++ 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index ddafc08eed5f..7c2f18d742f3 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -438,11 +438,19 @@ def refresh(self, request): self._target_credentials._source_credentials, auth_request=request ) - response = authed_session.post( - url=iam_sign_endpoint, - headers=headers, - data=json.dumps(body).encode("utf-8"), - ) + try: + response = authed_session.post( + url=iam_sign_endpoint, + headers=headers, + data=json.dumps(body).encode("utf-8"), + ) + finally: + authed_session.close() + + if response.status_code != http_client.OK: + raise exceptions.RefreshError( + "Error getting ID token: {}".format(response.json()) + ) id_token = response.json()["token"] self.token = id_token diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 8b9e40871e4bd58f39c47d665e8f71c60add17c4..04a24e425f30530e7b7cf1d224069734fecaf08a 100644 GIT binary patch literal 10324 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 diff --git a/packages/google-auth/tests/test_impersonated_credentials.py b/packages/google-auth/tests/test_impersonated_credentials.py index dc091fe610fc..0c6ca0ce9cc3 100644 --- a/packages/google-auth/tests/test_impersonated_credentials.py +++ b/packages/google-auth/tests/test_impersonated_credentials.py @@ -318,6 +318,27 @@ def test_refresh_failure_unauthorzed(self, mock_donor_credentials): assert not credentials.valid assert credentials.expired + def test_refresh_failure(self): + credentials = self.make_credentials(lifetime=None) + credentials.expiry = None + credentials.token = "token" + id_creds = impersonated_credentials.IDTokenCredentials( + credentials, target_audience="audience" + ) + + response = mock.create_autospec(transport.Response, instance=False) + response.status_code = http_client.UNAUTHORIZED + response.json = mock.Mock(return_value="failed to get ID token") + + with mock.patch( + "google.auth.transport.requests.AuthorizedSession.post", + return_value=response, + ): + with pytest.raises(exceptions.RefreshError) as excinfo: + id_creds.refresh(None) + + assert excinfo.match("Error getting ID token") + def test_refresh_failure_http_error(self, mock_donor_credentials): credentials = self.make_credentials(lifetime=None) From b255dc8478c2c4bc57796db6d9f638c0f8fd1110 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Thu, 1 Jun 2023 11:43:06 -0700 Subject: [PATCH 720/966] fix: misc fixes (#1316) * fix: misc fixes * update --- packages/google-auth/google/oauth2/reauth.py | 4 ++++ .../snippets/idtoken_from_metadata_server.py | 2 +- .../tests/compute_engine/test_credentials.py | 6 +++--- .../google-auth/tests/oauth2/test_reauth.py | 20 +++++++++++++++++++ 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/packages/google-auth/google/oauth2/reauth.py b/packages/google-auth/google/oauth2/reauth.py index 114c47c4805d..c679a9e8b734 100644 --- a/packages/google-auth/google/oauth2/reauth.py +++ b/packages/google-auth/google/oauth2/reauth.py @@ -334,6 +334,10 @@ def refresh_grant( response_status_ok, response_data, retryable_error = _client._token_endpoint_request_no_throw( request, token_uri, body, headers=metrics_header ) + + if not response_status_ok and isinstance(response_data, str): + raise exceptions.RefreshError(response_data, retryable=False) + if ( not response_status_ok and response_data.get("error") == _REAUTH_NEEDED_ERROR diff --git a/packages/google-auth/samples/cloud-client/snippets/idtoken_from_metadata_server.py b/packages/google-auth/samples/cloud-client/snippets/idtoken_from_metadata_server.py index 00b6545cf50e..b59a055447a1 100644 --- a/packages/google-auth/samples/cloud-client/snippets/idtoken_from_metadata_server.py +++ b/packages/google-auth/samples/cloud-client/snippets/idtoken_from_metadata_server.py @@ -28,7 +28,7 @@ def idtoken_from_metadata_server(url: str): Args: url: The url or target audience to obtain the ID token for. - Examples: http://www.abc.com + Examples: http://www.example.com """ request = google.auth.transport.requests.Request() diff --git a/packages/google-auth/tests/compute_engine/test_credentials.py b/packages/google-auth/tests/compute_engine/test_credentials.py index 009e42848cdd..f56bada2d527 100644 --- a/packages/google-auth/tests/compute_engine/test_credentials.py +++ b/packages/google-auth/tests/compute_engine/test_credentials.py @@ -525,8 +525,8 @@ def test_with_token_uri(self, sign, get, utcnow): token_uri="http://xyz.com", ) assert self.credentials._token_uri == "http://xyz.com" - creds_with_token_uri = self.credentials.with_token_uri("http://abc.com") - assert creds_with_token_uri._token_uri == "http://abc.com" + creds_with_token_uri = self.credentials.with_token_uri("http://example.com") + assert creds_with_token_uri._token_uri == "http://example.com" @mock.patch( "google.auth._helpers.utcnow", @@ -548,7 +548,7 @@ def test_with_token_uri_exception(self, sign, get, utcnow): ) assert self.credentials._token_uri is None with pytest.raises(ValueError): - self.credentials.with_token_uri("http://abc.com") + self.credentials.with_token_uri("http://example.com") @responses.activate def test_with_quota_project_integration(self): diff --git a/packages/google-auth/tests/oauth2/test_reauth.py b/packages/google-auth/tests/oauth2/test_reauth.py index 54f59422dc28..a95367a2b2f6 100644 --- a/packages/google-auth/tests/oauth2/test_reauth.py +++ b/packages/google-auth/tests/oauth2/test_reauth.py @@ -326,6 +326,26 @@ def test_refresh_grant_failed(mock_metrics_header_value): ) +def test_refresh_grant_failed_with_string_type_response(): + with mock.patch( + "google.oauth2._client._token_endpoint_request_no_throw" + ) as mock_token_request: + mock_token_request.return_value = (False, "string type error", False) + with pytest.raises(exceptions.RefreshError) as excinfo: + reauth.refresh_grant( + MOCK_REQUEST, + "token_uri", + "refresh_token", + "client_id", + "client_secret", + scopes=["foo", "bar"], + rapt_token="rapt_token", + enable_reauth_refresh=True, + ) + assert excinfo.match(r"string type error") + assert not excinfo.value.retryable + + def test_refresh_grant_success(): with mock.patch( "google.oauth2._client._token_endpoint_request_no_throw" From e768c705aef2d70f7e2d11a0d8644771ca584c89 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 1 Jun 2023 12:23:13 -0700 Subject: [PATCH 721/966] chore(main): release 2.19.1 (#1317) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 13 +++++++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 2e327a05eb8a..94d776566ecf 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,19 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.19.1](https://github.com/googleapis/google-auth-library-python/compare/v2.19.0...v2.19.1) (2023-06-01) + + +### Bug Fixes + +* Check id token error response ([#1315](https://github.com/googleapis/google-auth-library-python/issues/1315)) ([2a71f7b](https://github.com/googleapis/google-auth-library-python/commit/2a71f7b2e9058efe5bfa42eaf58baa3228baf1bc)) +* Fix "AttributeError: 'str' object has no attribute 'get'" ([dac7cc3](https://github.com/googleapis/google-auth-library-python/commit/dac7cc3a126d8bd21cd20ccc536a2923f4cdecec)) + + +### Documentation + +* Replacing abc.com with example.com ([dac7cc3](https://github.com/googleapis/google-auth-library-python/commit/dac7cc3a126d8bd21cd20ccc536a2923f4cdecec)) + ## [2.19.0](https://github.com/googleapis/google-auth-library-python/compare/v2.18.1...v2.19.0) (2023-05-25) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 1f384f80e9d1..ede81dc184c8 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.19.0" +__version__ = "2.19.1" From 2d7878a88c0af493fa7e2d88e80ad71b84e676a7 Mon Sep 17 00:00:00 2001 From: Carl Lundin <108372512+clundin25@users.noreply.github.com> Date: Thu, 8 Jun 2023 09:56:01 -0700 Subject: [PATCH 722/966] feat: Add public API load_credentials_from_dict (#1326) feat: Add public API load_credentials_from_dict to allow creating a default credential object from a dictionary. This resolves https://github.com/googleapis/google-auth-library-python/issues/1313. --- packages/google-auth/google/auth/__init__.py | 8 +++- packages/google-auth/google/auth/_default.py | 44 ++++++++++++++++++ .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes packages/google-auth/tests/test__default.py | 22 +++++++++ 4 files changed, 72 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/google/auth/__init__.py b/packages/google-auth/google/auth/__init__.py index 861abe7ea60f..2875772b375f 100644 --- a/packages/google-auth/google/auth/__init__.py +++ b/packages/google-auth/google/auth/__init__.py @@ -17,13 +17,17 @@ import logging from google.auth import version as google_auth_version -from google.auth._default import default, load_credentials_from_file +from google.auth._default import ( + default, + load_credentials_from_dict, + load_credentials_from_file, +) __version__ = google_auth_version.__version__ -__all__ = ["default", "load_credentials_from_file"] +__all__ = ["default", "load_credentials_from_file", "load_credentials_from_dict"] # Set default logging handler to avoid "No handler found" warnings. logging.getLogger(__name__).addHandler(logging.NullHandler()) diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index 4effeec9edf9..1ae26b4eb97e 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -130,6 +130,50 @@ def load_credentials_from_file( ) +def load_credentials_from_dict( + info, scopes=None, default_scopes=None, quota_project_id=None, request=None +): + """Loads Google credentials from a dict. + + The credentials file must be a service account key, stored authorized + user credentials, external account credentials, or impersonated service + account credentials. + + Args: + info (Dict[str, Any]): A dict object containing the credentials + scopes (Optional[Sequence[str]]): The list of scopes for the credentials. If + specified, the credentials will automatically be scoped if + necessary + default_scopes (Optional[Sequence[str]]): Default scopes passed by a + Google client library. Use 'scopes' for user-defined scopes. + quota_project_id (Optional[str]): The project ID used for + quota and billing. + request (Optional[google.auth.transport.Request]): An object used to make + HTTP requests. This is used to determine the associated project ID + for a workload identity pool resource (external account credentials). + If not specified, then it will use a + google.auth.transport.requests.Request client to make requests. + + Returns: + Tuple[google.auth.credentials.Credentials, Optional[str]]: Loaded + credentials and the project ID. Authorized user credentials do not + have the project ID information. External account credentials project + IDs may not always be determined. + + Raises: + google.auth.exceptions.DefaultCredentialsError: if the file is in the + wrong format or is missing. + """ + if not isinstance(info, dict): + raise exceptions.DefaultCredentialsError( + "info object was of type {} but dict type was expected.".format(type(info)) + ) + + return _load_credentials_from_info( + "dict object", info, scopes, default_scopes, quota_project_id, request + ) + + def _load_credentials_from_info( filename, info, scopes, default_scopes, quota_project_id, request ): diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 04a24e425f30530e7b7cf1d224069734fecaf08a..efa90e0ea2da034b4fc538ba64aa8f4c2e9c9674 100644 GIT binary patch literal 10324 zcmV-aD67{BB>?tKRTBvDeUW>s(4Ts55;;ufqkf0rpQn?@omOHXIKJ#>x(E`gPypAf zZWDxqr?fbDQ{!8U?!^ZSFbpJe`*Xo6wQ% zP*zG=P^f|>58d{Yn>QBvbbL4?y~A-Y&{WUKCt!u3%7`2kG{>li*|fY_2T{X$T-#cw zTnHr)U0ld~tCr$OS&I9y#>UYCSgCl-55iSNBMhBN#Z1DhpR?x8FK#E$(~W{kHDswq_{K2N&i@1W)mPtbpcFtTrX=>HaUczMJRh!kMrJ8nSvxbF z@qw)x<+aZGcG+;788mJMQQ2HbZzD2Q-r8qMy^Vps3zjvsMf8JewZ0hgbJq>O8|ABp zpK&j$5o(s#DcDjpWuuj#mijK`G417R0(>+S5aY!D9{py+bR=>GYF=8*w{{t;Rv7&H=b2b@x?_DQuJazuIGpk0)T;hYL(glnGp7T;WLMJ0j>^^dR9sR*4wlJ|o$n z!A0GC0>>&X2tszcKsCDpByG+A25m^9c;*Oo2I@Hoin zqqcS95^v*y3bi?S3wp2P%43tO53qnq?5BS7}`&v`8Ll zS6N>boxmjw`0=UK7v{8MXh{2qYP*33e79PHxl(Y=TuCDh`_v||$jeH)b|Y*a7;t;^ zG)!TmpoD<`OHB*j%y4T&`qXBXWjA`ZwN{zHVou6g2Ouz}9TiJ(zP!DS8Wo1H(z>*2f0r@Dp_?5yTrMTVvOrGxF-JQ5(gT)p?LFTrTZVXDYGu4-IL5)nYm zzG%6XH-O_%tn;rkE4ZU(l~h{7VS(9W#UFrSKTGTK6eopwLwih*YWP-lDhM6sj6`Zf zfmi@;>J^02l6OXdKl(S}Iy`M^yRDnPjb?Dai|J;SiJEE6*}6LdbEpyWOiORTewNi& zjwT>(9OF&4@m@Rbm3b3qFqmuolLfFock3VE3H1Y3JYoQ+$auCGA6-S@%OHrIIJO`( zJ0&EBD}W3NVGQ<#561Z{N^v{-Vfu7@9_>d8c=}x6siNsmH$_Cpde+4|$*Pvkxh?h* z#AJHkVb(gbrPMGmD}hRn9gEC;>%}^2!!J=O%U(I2?6T}I0pn3{9H!2a4a}ym7Q>IA zzjpKaRT>Xn*<2N_HVoIQN1sZPHBHE@sctZ$m589;&UY?S%KC8j(cNUJ20EgiMb$$6 zTL*cZhsFMnQX&&kka=9-%-o1~pxtSm)EYGp2K^0suGwjBQ@;21g?$+>q1UsMH+N&F zpK@9EZ#;(eBb6W0Ms*816zQo2FZeBH&P7ttAQCK)Mp%f7^M1)jWv2^QdwbMWpO5lN zB|wBE!Lm_xc1_USZI^_n1UK;2Vw;mQ0-u4h$^o2ev-{)D%R15Au~HO=#M6V6b&#m4 z-A5fxT=Z?{xU8t$!P5b8>*fA_Y|6kJDT4;D(OkL1&}inS9-rk3f+)Y=+9Llo*&!pw zHC#g7*30$vqzr}0x@fvI_%>Vhnn;tZ7h2sIBkqCvh}4zlTAKrgu<|Fi6dC@A7mglt zklrxb0F+upgI2tm4$DKC|8Iu1cFB5dum#t?aANOp9A2j}($6vK=UiouU(H?KkIOwO zYzFw6O(z1>EVppCzmQ`NM$+3t+IDL1Ep4Kk#);lgoH%KQHeZaq$ewJQryB92uv>%Gd@yzVlrY&8zV<7Q8`Ga5SY10 z3;7Y=;wsV7+{pL7cb^8aMLlnjH)f}c^YD|+vJT`dx(FW-$cJD0@HNM!e_Hwq6Q$Vl zA5bQNQ#wmnzv-EQOeP@{RVC-Flu_g3Jt8$e|W@-(0rcVmK zpPX8IU(+SoZNl`YPROcS+q^gjTtfMBemNZHULJyZse&N*m*O@hZ_eXBY4Q<5Jgr&6 zi?5EzBXvBaM(Umwt+YjxcqYDq8rzG(UKga&3q4>jAiLzPgV3Gy=Eh&q(3gBLmB9;N z2TdpWo$$q1KmOrSn?P6Pc*ohf(Ye!ij%t94&20EJDWL9{T5{%%8;kN_No5A4tRHT* zu0INutWmFt<=v+D7fA%4XVrx+BR9s>3d}zSB1Tc|1`7U7njn*wOg4G^BjA=xL6`5f zjH6{}^z&qf=Z~{Llu(cVc;=bCkwuxS{-TXbC6ij8yN+7yl+2iiklRIUS zDLPtBe{iONAZ)ePcGMaFMpGN<3Ut&y3^*RDILnRs$hFjUBChTp8MoOeOf~-Xppl3R zRE=wpa-K))k6*&pEU>)I{r%FaSdOltxdb3^$8(gp+8zJ#MxHCqS~(*5xpXIU9=w9w zru%4FdQH`?EvJ>D_u}+efgCw@@e&DDB8$g{UaU>ijkKhvckY z@G`2S@rf^^?f}2)cIX$h6PR3RlH0A_*;Ez4npxt?Ap$TDVJw*1E>qIB;;G*E(q9pM zC&-W<5HRcd(C)UmE-8q){Q4NF7Ti*$9W68dBUz%&@zP3hJ%YGm+o8OUezAO$wkP8{ z5yy@ww0P>9z>~O@7Pw#D=HactFR$<&p%!%==R`eN+aZ4%(oKj(RC*gHicPIO^t1dA zqitk_h_DqYI>_Fzfc&EEtTkP5?S z1isDYVciqc-in(qE7cc}33C$br(}ZMUJPZt?3tfg?cl)ik{hX35I~qioI5XhDw*TD zJ!&#W%w&)KCm?VwTq5HodYXe4mC^k~Ov%8);DVlZu%sH809p4u00eka-OHyqsgP?Z z4;|WKt2dhbVL8KlbdkC8tSb)sq9e!CKENuZzi6*``1V?|e>)6KZwys!kKO5s*S5}dbG~6d6BrJ z4uu4)%b=^5S}Ygxo9p7u>Ww`pM1m0{=`~W zJA5?L+S!nz)MfxEHwWTi=ci!vsyvQ0jtAY-N=Ah*& z+DKuTUx_AmdsfeDv;3Y{nld^@6QniAJ_XrGeGb)171ZxUyFuq_j*{}0RLYc(kdcwj_c)Dm`z#P1u z6*`8h1_^NOfSC>)<+{e7SjRKB;|lnXUs*@ED~n>{c5o(Nbgyb%e4#D+DFGN8d+304 z(RTD=UxI1Om~u1yt>Kq7qe+BLI9NK$m!<6EuM@s!H&H+ZhsP2t$UB~d62EwIF6$tgK;iMtBk-{BqOP_JlUuCf97KzUppb*u?GNFF zDPLB+u%%zjLw{-wdJZDu&*+isR^h@c>B}4Dm?$x=llm()014EODhA^kfTh`GBry#! zdxwgzDb5lt82j055X+td62f!4)D6GB%i$u5e*Z{P&pa4`3$z^N0L3m~5ZfVEANKm! zT7Ft8=+mcJ*)o^5E*uf#9$nCyS5fV-sAsHEa6rb?Z= z$RMbA3Yvd(6!OenoeqcxA;zPuX;j+vuA2!7CJnX_<#lQ9BXq$a(;75 z5<=O3c%uxHfgn4Wtb<1O5kYTuN@BSEx(SEeGj5kpxi4AN$3^E zPR^ghH1#IAVzeY{XxKnZ);0H1_5hRye-y1>^ug|^ZAG->K841j^GvfVa6Zby?!E@R zY_8Ri{pm~~3qQyl(#iOHDoXnC=oytn(Gp5Nf6k#WA4hG0;}rqA>Qv#=jLp}n8~KT~ z$()|S$?%^O>4;CDm8>4mO?|4Q;vIxi{Zh*{XmX^PA8h#-^dMRUWrBy2c*li|ECndJ z2H8kqba^c~;?&L2iB*{^$=Y~+Df|Ri6EsN);lq=fPj}{Divje5GX-giF+HmD9qB-C z9y$r48$KK?0A-ca*LisvJYAio---qIcHx#YFqdelJZ4b=OJPJv9)27}Y^zZG*hl9K zr4IdGzZaUAdnEkwZS(+~R3$`dGUA)atxa8@`8EV*1KVWmDLQ!6n=jmua1tVW+e?6k zyEmk|E}w+y#*DET7|7Yq5U>QZpG|^rPV``Rp1%}&P%Htd^)N8m*9|oLFDCnJf(ty{ z`B6oJda-hb%U67=z+p_{c{xn65iT{FXsysF#{5sM#@n`V=X$q$g8;Sim2SukRjVH} z(y6CNrd{oy9@NXtv<0QLIXWBw^<<9cWBDEnzZSyxn_B}f5pES~QX{(CqLS|0kDVv_ zL7Rf)A?yL;buw>v2S#tePE4#UB0MT$%nIl)Vm(oABd_LSaumYu#Of&$yE@6iZFg*o z%sMDmCp#crl^`%#fv_Pij%wg>3b!_Hwfj(7PDxz4;SUb-c000=*cgJm9ef`340xb^ znD%y-A3dcfk_~c5P`)0oXV<9^i5xAc>GLT%=OWKV5ETmkl_$jf9%DgI7{wU(gmh&< z&>dKxWWo~>C2B+kAz+LJu-kXTL&qL;SgNIVa<2${#E2&{L;W5%f`KmZ0D2UK$U2(n zj1Q%gK_OA30876fbTD)<*T3#4@L&8L2GdwNc)k%}r1Ma}T|hKbYwt^dO=#W4B_->c)fa025y9O?)%u=a^%^>e#$^aH8eB>D7&DG%*9t30Hi>=J$|+xB5L}6*)jZ! zK#kd20t;i>#!=K$m-EbZLjdQ=Ps7wEmxUoq`&926XYU+*dd{agWfvjCX=9uHE zU~Zpzkf@b}uTz@RSe}VpcMJQ|k;59MO*8*XZgyvxO$ojRfsE9OZu6 zbPQx69Oou}QV6Xzb@Yhce>w8etYN}Vl+ol1f(@~t&7{TI54Xhu^zF_*iYQ>+>^$IX zWc_A@`z9TjHA*|(6@Ku&kR@|cBsO+vi%-^v&dsi%uSc&Rd z>pBB8?GC0l^&XgGWYZX$=hLh|cfwFeV#?EA6}t97IqOP87bkktUs#oz1jd)yP^ zVJcp=O5(yN)C6QPgdYux1L7G1ePRSk9B242+7+j~5!8`FqAcjsCq31GS^&wk5YB7A zj_P2`IXv(yH84X-j@#b8 zu($3Ik+5sTQ3Vm=aBcJmX`o_^L@^W5aK>kf2-Oo`!d}bEtA(regNAP4G>PGFp|DuI z?Q`7+Ijj?90xp(>VaM$XL|f2OZv}Eb?1i%OyE@wmnw$i$w&uKigS$`L0x?(7TF4d>nJX{fMsV_SAwbp&G}e&Z9N}q$=~&u( z(<~fo4GC6f9Gh0>aglXC>1(@w2}^fPqb9jzcuZNtsc(ZU={FaHO~J(Gu!v;rAc*qy z6gT3kZW&hX4Cgz6jiGcPhThortN~AvvgUJR0^Bcgg_YC%`B@}8KiZ(q9m`bB@| zl)3FMYhLQ}F&^3`b(u^ua~4-e@jPCj=WetnGZC|^du;zM@>Z`%gfsLO#LWKXf0xJ@4u>oC;@{iYgc)vRtpGQzIU znK2U;n!SeGqnR{9X5AE)5sSP)qQU|wm$ky5^Q0i)5NGY)bXms$1h9lm4w+XoxQ|@T z1C91(YUHVO;R0kF<#;mKq~iu#$T{M$tq{Q~7nS7y3EVc~MDr~v27soL1)$Fjb`ab# zYrUt>iE&49wi+jjbg0~&3J&h!mnL@67#yP&4JPSPg37p9z}@yA>D84mS4D$hS{gOS zuhP*ut3kMO0<};pA}cDq=1}e>5H2%*i2_a9!;e5!j#8r~H+C_PB{GY_l0wof(cL_5 zLR|zB32e{4MU2*?kBCG^A^puw4~zyek<{OjO29L?d1~Z77OZ`k3VmY;B`Tb1{+SC6 z5`RiNUPWK~R~CH^*I4B&KLD*_6QHBX2VSpo&u4%nN%4LZ6OEi)c0D0eo!LT&$o@eT zuohI(5#JAeU!g?Z!`CGS#S+|HJO*eLiYnSJ?OdYt#zxWUrS1=c5)U(AoIGv!&H9b0 z<=ms>i}`N}CF#3J zfWT!5V4|fma=cq)yynR=D1ujkx|Eae-F`fu-+O4*ATGvGKL&pUkI+hXJs zXz9AIcHwwLQSbEzZip;wb6=Cfng$`#l9L&6bg?>_!+Q?KP0t7rInBhxKZ{I-cs+5t z{OQbn;9;(Gamhhaey3j4qCZ0ZQRZU$@<&J2Js3K-xx}L^m`c8vSdbDhPR@)Ns=G2v zHM{yRw@a}Km86n4U9Wb$&*6zMu6Oa{<-QPxcd(74#^~nn)LG| zf`ZL{^wN8OIFy4DAX!9*dg|R2p)Joe$YDzn2t69m01bx(H-D8l;oot$!Sv%q^FxUt-rnQC!<6E zMqXim?1z3vzvZF!Q~3Z5aq>(LIHZY2Nx>X7~%nLa@&5G5i?T^DdDiJ(c`P zZW-Iy+B5elG(;bptJaVF(}VUJ1mq z+(=pqBPv8J_hx1X?8>-d5OMAVD3qvvYf+ zb)9okov~L!F-3V|N5mEfxsZX4(!mjg>U)96B!&^Uy7*`uGp*w={MS5V@$uMjB|&KazP50 zRtjad0E+II9I?pX;6FvW13*x!DY>Tnc#1k?K2EKqZ@pd9Hw0mdTb}gIkXFi}{+0_Df1A_nX22~jP5f|*((;9=FF zR~THF${+b1)9V!1AS5xK1|~$zXhvdeBAavw@GmZGuToCC9WEFYl@A`ludQ0MgRWy7DMY>vsulGgd zjmT4us4;X}vmPLJvBRyLH?!N`t$FnMS#{LPR7G*h(Bconw#z_<@%lc$JMo7Q_jq`@ z1IGyf#KU-=t@(7BnSvmBXX!8HRzaGSc@w6^Y(rfES-#~4DjnJHy2r^p92pGgGj5^x z^2gyzdNbtC{M0OR)`aIC@A_c{U>>qITaaP8D9o#jV~|@JpW6nmHyZA4k0+eeX#v*3 zPxB;ljOeoMm4>&Nse?@QLBMX0m*3Vt%#)nc96{dJq7R(pHZPp>W>l zQY5b3!npF|y+#jd{q}_V6069*Lx7^#;&Lmn0|tF4CPQt|1Xhd_MT`L!LVkpM49{mg zAwL=$8#(qSCVdc3>{EC9q|Py1hYOF>+_w&6LFS7NA-u8c%GY+!Um{8=c4%f8KQX|O zZ0efB_8L0}cvjcVb^2+WQMDmLGN9{6a<6|EjJ#%kEgMI2$jLUdt~N1pidZAC9gB(& z{Ux)Pa|9sqc{!RSN?*q2xAkyJLC};(z7DVq?HI$j86v2&Ui#6hON}cd+y-8NViOZU zWt#f(yiQ`v^7j!5VCq)@)H>*=2Pe1_bvCi^a+4TMjq9v^Jvx`_#{lRy(E3a-&^AeN zh*4Fj@J1`O%5Xr+)2)T;6$G5%*|X*ceQk^r)R0;zhis_SG@EvHC>Cz~wRc=J!e?Fh z6^SxP#&ux{gn&dd82WvNNYAx{n$_Uo)6t8U*cetlq*GE{bP~|=DPTp6x%}hM>4iF@DLld;5%BAEOSlYkW6^&kpH6kDpypRnD9%+VIE8D zYm$V^SMNWRsbRsZa_NL_W>CSj6X%>i0&wBoDUDQ3?+N%o7s1*>WB-h_1Q6!y>eE`L z%~wa#v+QQJiFFqhyesghkfyF)b*M_>ELu{E`AHXALJ6tNM(DcD zUx13%m$*tKa@Dtv*cNOOf-x!@0pV`6z#6OIu|x&q+MA+eh;X2YoWhjoDd%c5RCSj% zciP&$wn_npVjQni<3X1RgOrsx=ntvT?Ng3w4X~~fRE?dcU7O6dR!2(O!sggHH|IFb zyZc(E4XDpuPFvcP`e+A+{Jn9sFk((r_J6$pc?ERtAIJ^HigA|s+8;Wrvi2UBQBuru zwWACnzQYP7!&tfh$fTHDboV*^q8}~GAsJfhF=v)$2GBhQp_XB3` zXRiT}2;>QNLWtxnS4SI3VIRiDg3O9j)3vlm%U|sXK!=^+g__8G6r9CKh0@eTiK`q? z7^r!a{MP(^-B-PVaPvViykz3~2C5_Y=Vu72y?E~i#y#(P+xmatgE4czB|?;=m&wd$T=MT{A!inb&kP& z-_}B9O`D9!qQlWm`t0e~W#3aBHbRj!f`2v$(kxTwySXUvJ6*fHDq1nYoe1`o(QOjq z1_)&?c=&|=ZE#};UP*mBoZh41Vya5U`DAP!lBhDIvS?;0$0-Bk$sv~%9RJAB$K@SP0~@!(^gC>|CPn}KEUF3 zb_G57z~^aXz#s~m;Ya+Ah9aC9^NX=4BSO=@p$aGW*AyX%*FUu*OgCLRGpdQ9uh)*p zznt--(?FZz$!-sSGF}ZXkkd~_2In`9R6S}*-neW`W2%^t?egFoK5&R(YDfJK?r6)( z}P3bh^p15b@5iQE9e9zWi9HOsvc z=?*D4!ZA+Z385qP)!~vk!#A-iWNb{!{gF3AZ^pJdQX?6{BaQf|mx7?MSbT8xIu0w? z3>wc)f#{wH#s=Xr+;T}W?Shj$Hn(stxK50_?_iV8C(2ufna&wcd`(u$WH7qAu?a61{e$SJ%)_> zfo17T-No6|R5@EtZGc_1;0TPz&1?6g`wZb7_vbTT$jVo8$6?ei3-lL|&#l9(&fijy zjnP?wYLV~~6^<<`GNY^QVP35!nBnY+@e}oYhnx@hL_gK3J~J2EeSNr4r}aKFG0wVO msKpM;tM~o558>0>fX1+E%WJ<~gRQ)&`dWl`6RW)C4$GLyUlrc~ literal 10324 zcmV-aD67{BB>?tKRTEu4bged)gR$|J)TRpO4~P*u?X(Y(3YAm5prSU%@n<@?|Z zi0l-s5+#AUjp%RhnhJl&VV+oo^-bxApj(!9^dcgz2D4()Y=b({g=(i%TF6>{BjOA^ z3I};7a&Ssv4h_JWPcfv@Vi`hbd=6;r5J4}jW{}(y zz;!73=)r}NJZ-_sugc3>f|E_(<8AoY_$AK{5$^PzCoXnK8Ny9cqi-;ir<)BAzx~JR zi`l#){K^`n#XOf(q?TD6&^`1X>chN!EtS%5jAtZ1C0 z`}M&dLEY)SZsUz+qHLD*>XlsdHO3db5Yx80J0*y4&b04abXAeJ;4T5h1vdB2l40Ly zxolz-JP5emYSS|5Y@xF5P~Pk(Un-l1NnV0bvLp-{-9!9VY!(CDd~e^SGAWKKQ|m*e zUmQ4yXC?3Uym+TJFTI>u>^Txd^f=wq0|iN35!(zKpS_q7H-V{Y#A>;|iv_$qvM4kN zjwURM!li|55P2*b&{UXH0o0vurdQO{!Hpe+`y2$t;oVO(A@zM08eqEWF-c%~J5`|x=pgLr)#JO%7#6tLU9HqK=4J=a*Riq&y2!Dic&gX!t$Ef6&0V3D-Lh0+W$ zqxq*-3l}+RZP3l*3wFuxU+6+}*y2SY~Q_N}>2eWhqbKQ~dOiv#!B5>1ls5E)xz zMsyDU22N`h7=D zvLu~bJ){|#=(iv}!&x*@ejlzP7l{%SG_8ZHxKt2@;h*zZTMoyz|lxUxUFa~g-V_@+UI|@s)F9wTyA7m zQg%RSopSqpejb&mzdpXUeNoM^!fNfuJxtS`VcG^be z8iRlyooyk}G)eniIB7tr1ju_?8!tqv`g?5`LqWQN0QlVp&hHgXf|j5H{s>OcnUbn8Al2LJ7|_Mt zZfKR64MBWEWd(f31G1nZ!A5@sI=3MwdZe0zt^6hfoHpil{tUSOj~KFetxrV>8E?5J zp3O;pp{bb%ka@d{6=>}ixp4R|e}z7{!YM!?E_q_-I?Pl3hHPvd?PqTKKePANrj;nI zKm!nWuTHV~o^(t2jrew#9%CHR`Z6%58RkCLiyicI$PuXeDA06<;Yzj4lnd)W18TwI0@EOUmikLQ;lGQmTmO3weEfS4*I73 zWK6wh#+}0aAVbsutzPrc)(cb>Bf{^V&K&9~;xs2BAiCPx{wi!(oK2i|g9rcI+0ZhA zjxXO=QU|6ik+qmQr6ZR|7Wqf0P?1w(TH<*vJQCByG!{fYJ~UBRNYQ} zu=7zp#!zylz5YZbShx%cbMNSSNRn|1+s6{B`$n@v%)j_ zYr-Z| zGmuq~q;4qa9BNhDsNBoQKaT~RI{-{b(TEBkxkJ@;@5*SJJGiLa^l|qYi8p-%i=KL} z4Jab-?RO<*jmi1$bx2F&K-&x)RPj24cnm=Wo8csWHhqxbi`=FDbC2}!U~t|P-S*YC z4P>j)V5NM2spPeO?OTa$?DH0OSyu(*l=+zA8whTsHDj$?V2~AfkDQf$2pwji_VsHV zIagN^Cg_w6bEOY1yudf9$wo=};`{5!$)||mhyR(qHK0R81}49J`@}}83)v>=M#&;E zfLR)0>xmm&-5k%_^~PI+pvOP;I}Zu;dRW9k^@foBDsQRT^65ZVqU;Q1#Zpp|1Y|_2q8);S)iRkYA4tL5C6()J^waxnC27 z)*0~6!PZe))o!knSef2Sq-c7Vkko%v9FQVR(9sW(@_%HNa_#$hzR7uaIgp%SM1jYS zydZ@LoO5@92Pam%bzo}y0svP0YF76$>HsYPgntj#|8#2c!d3Zzup6TiOveU7yFI%! z@1Q6cm{ntPL{$*}##Wz~lwuMs#gPzT4$!r!2|Z)k`7g~aqoHZw$e3j0KE^3EcI#laprypA>~_W_TU|b% zSogQylGCRSZ#RB};5+v>Pw0w6ZQWMWh$YxpJ*Nt4=0WjO@GTZbw8}(8@XdvQ;x$O< zy+J9yz%M~x^4VQ-!#9zB-?Y66_&?$`bQlvJ{HWftrTXzhorzn>Ka_6iCBKs{w5~h{5?g&uo(92;;LdJ82Isv*x6m#T%bc_3b^zj zCtHqs^HkeTqQBgykCZk34Yv6_={}xo0d8{RE}XMW-@9yQCY{%!XcX$aa#8(=Q3kbZ zV5iA1+s|w$O3Y$!6&r>V7XD+~*k-l(!)?IPpXhcAY&FV`uMWte|0V(6ZolDVBUSAr zV@4~HDT;``u_b*9#UnLbH+;Ulq7DRJGT6txZ%i|I9lMLl@L=MWjljog;tWHD77K#oRb%PI9Wy2A#)`riB;kw-rt|pDZHIbD(=S**j%gvYh z@kcBkBgshE{LE|)%e+A4J?E`)G*qQkN1R1UU= zjQE`ZsUz1fb2sg;<4vXxV-_;rTZjYP5c6~3r<`Dd6^r68^E)&_{ zYL7eng4tsuPNr>yA%n?w&24(Q0W^E!{|${F>6JC-Mei4`V-ww77sjfovG{RGDJq-p zos|MFt1=Eb)AkI(D%pS_(kfj0D~HgW}F*Rh>L5xi?NS%n7(f8kh#r$ zf?g3;Uc-E*z@jCylhY49#2#v$W-N&@#+b@xNMBpgZspz!Rs=V}5F?}s2`;3?PG2=j zAwV;FPA*SnlSSjS#G#YoiYLhrtAZYNe_p>P2J2tzc0R)iSj5Pu%pbST#>G7}(k)LA zYPzNQ#YE1Ar^)aP2Sf5UJ8sFYMw%=%EGKX7pqBS8!TqD_1bF;Gl-OD<7Se;SYS)0a zLTb%TJ*bP<3}vBw{QM)2_Em|oT@YoW*Rk@Aji9|J1g8`?L{PRh4QFZPWN-dS3MrSZ z?B=xGbI?<7tNypoBgX%kG>3qU@Yw@fUs`7b$s?vtG6l|6<=iWo7J!83dl!?K|H4BE zG+XVP$Q1JGg9fZdRn&rC(+VT0v^|5Pft`;)L4&IltDKp{oFEJTd80?IFYgpVn#9Ra3hfXG? zJx8^4ua7N;(CInizc{Rt*l{*LCbq4o2zGpC{JK1J7E<3#{Mi=DF06&sr@P}EhMB); zC_aE@Eb5JIs(#94Swr_v$;h&uI2a1h;4?;>M~`O(vJ?g1AYtBRw!A040lEwnq%0o$ z1lL`jIznMkt3!TmlNap&m|7E?Ill)~_v4|}yw6vArV~IF!{?9wJu5t>4Tr46W}0Ys_9N#^uz^yf}!Z~RV{Sd*=?-JoR*-UuDn>Y6g6JO z_Ehl5^N%TJ*<7;q{lr!m&5Tw=F=e|`1Q$Y?#auZDtlLT3tmur8I4y31LC(2pda7$V zh))w-Xn#*~LynSbw2Fa~ozzBvh2Ulktidk|dg!L|n}zbuR!gpV1T3s|SwLoP7E1!a zV-`UUCE9T0j7yghKCx;uYP^XLh^3BEZv#s&QW6m*|BTxdGEOJpq~5KPjaHeycXsZi zxhu$K?4$^(hDxeu_Rf#xz)^<#(jyHUVrckjaOJaR{~xH8jFq2vI_ zr9`sQXlvzSEl-ByxbWVVAI*rbb_d;Z#j2N*>{Z5k*hLX&dg+7JoD-Xle!ebuHDQ~E zMtRoGAK!6~0)2Itr^$Wjlc`suL12#(+I)J1eMc=NHg7vAwT>0d(X- zwSW+WJVr(t_2(8;LxbrN7xp~PM}blS*x!lIsBJN9leh0B%e1-34VX?+>8twPnMz4I zL8$(@iPfz7TWKiF0F+WhU!aizlnhq6*>QVmycg7=OP2(9lP9=V|H70cYV)eYwb^|s z2xzs3QO>)@%D2$M9{`T$cFw^l405b6;+lPz7k3lG%6nd@>DiD*XgwzGMeWgG_wjRm ztMT@=Rlw3LF>?GyIu#pqCw#HmObsTnqJfMa-5Y*b?1Th>;kbJr#ytbKFPmue*QK?C zcAzGr+(g`RsKKc%NMh;9J&fS(2ce$EO~0sz6F4@I-^&s)fZ;uQmTSfOH(~a6@l~64 zqUoXFzoej=toD?%dQAkQphW8NS*kr}Aen=$X<4GLjF&h)qF|)pA>RB`@!A1~WJ-n{ z9#L5Dq~2kGlCGhdl}*=V-llbYr{ZSl=i0WwJ$MS5s+TKPl6Th8Pw;ACA4G{S8{hGz z@NIpi8inqE#-}kOz%G?)evN3NG-zBe=RsBz)N&xW-(FjGUF6)>*auPr@^okF32jFw zu9T~(e@3J`2y}}(#)R`$uvCp4iyZOZN0q0BINu%sn_m3J%wBfX%N)gP#CU*A7jCRo zrME+Io`9GYQw}VASd*8@Ov4^mzBf@ExJKDN?`E;%%1zrEP0WWLz|!BXGxd|l=bZ73T_tH1CJe*|~Rf%d4kk(Me!Yu73E&VRJkS4?8W4#<>5l<~t zSqCGS)eWaKg$^NLW~5QQ7G~oTUXPEe8{aPP?qLNt1+1a7m2LO8zu&evQcMhn-?e{Q zqxHnW1NEDW}6cU%aYG_`H@O%D zk@#l{&x}eHLq}Eb#CTG392zt1n)~*h8e_jk6l?x<>`@G;fx=^ly+ z1T2gqMI#Z9@@DA1bJ)B3ZY zumkkRy2R_2Uqa9VlpkvLpabe@!fo?En`~qETnnzh{ZJqd8_Wk{LT2%Y@EI50wM_q* zb0TZa2hn4S;PiCNM^o938&-N39!Mx4p-T%X(l z%e~{ll;gElI+()5eG=9FC84czHS|(%{+iE)7faS%h(=X>`gC{U%0Z{zUEHWYX#ROH zf*f0s|1^S$P?U(?L^E_a^CMrM0Rc7f7VCe>` z(%RtXl6@7|@eFKDRvIVBWXJb97zt(s0YKixctl*6X5KRICE`btXK=icp#ISk-iFq7 zYi+->xm}KuwVK-b$Hu(3!2+x-gSklyIu#XNXDHXnNLGSDZ0B2!OKoE{o|v)CGGzMI z?dGWS$+44|hiqRkGu733&ZBd5K^v@m&A4iA?&(j)8%xz2C+!s2bbM;%;9jI_oF7+= z$Y6wJ5gVV!;}hdS*HF<~vN%qtQJW{m^tqtKlZ+Jjq$%$bj}0w{6`#&E;_34liRbBN zrd6;d0bD_Owps8`YcBZIOf6$vjolH~p~tLQWyv15#2@(sXik!n5?u}4+1oUTy1A-k zNEz<`ssr2~Sbri%H#`xcXfpaij#)uWa$Nj*iNpohk0C{g!5*F+K*&rPwp3L96NxMe7nv|PfBxbst&dd~ zDfuE%i4t$BIGsYo&QZF4KfrDsrpMFxSDx-=L(n>l+IVl@L`x2&mXW&C_G>vdSbnSj zL=rzxB|{s`1eaj22oz+%gj3aWDgBPk>)L@h;qfi~wXHrlY4HB`5<>5d8v3v7W3rfR zA&aUFI`CNihy&lvr0RmN9O+P=vv{5=I(d1~IsVNtaH6RufyBhW;M>fLzJj}W0^Eich_IhJT**iJ?dtRUz|&=hR5jk9G!PDnPpW~ z7|luj-#l9NhtwMv#Du1~>Y^~KBUn!9wzT_J7O7QhNuKldJ5 z?}EEXkrX1YF3I~Ur2{}lKxo&k`s3f@Dakyf{?DBj*{1*_Hpjd)0yxv4Cx-Kp;ncS3 zL@HYMyi-+a>`tR=uJ48QvX7tFR+o_X{ZlJSpv1yeRj!c(PWXSzL9(u)#KswK zK?k6HlC%cJT5H%TOMRVCJ<%LJ>YT(ZlRkYlgwhPA1z%$NIUf@6o6!0X!SQn*So0$E z#e|WD?|-HXJHkbd?RF>M2HL|{Nt7wq1S`{-N`$)kqV=oIVNu}tjF{$~75}xG*?Iml zSQ{1;LlQqfu8mJ&6#!65i?1KQs6L*^`WFiwD-qpYY!_ohF-V$e>V!sC4o`uV8`Wkb zM=?TdjOW*8^^M{He9VWJ3pr4_1Mryy(tCb0PYRq=Xx8wtv%g1KIFJ&8Ckzd57q|@> z?cdqu*lSCSK6DjNr6#SPIpuL<#kiaN0V?(}St7`mxu)INcyP%<7Dr`R*`HlTBX64S z^nL4_puaKw$U)z$E~e>q+|@s8iO#$(HCKM$t`c8ypiJuNM)R>__BRDXiCJX9R9q*G zr~RsaNV&iw+uCkJ00t2Mp2kOgm}=X-Hm`mGpBTxf7vrl~y_;yf+l5jLVr&#k{nsxzE6AAJ?9+(xF>gsYELHQ6o1I zZ?KGX$_fhX1RfJKFd6WZh+deR8mol!xPsV zB!kHl%xl10?ni;dVN>Jy<``$%h2k~;EUJ$le5(Fmjsa>2xJA?{dn3F$v)(H%zTC0z z%DLG!ek9OV#YLGZ_znTibMYwQX28u$4}qKf)Dt$L)c^gwhjY??sUXD;GsHDve2yw? za?lv1ZSP=orvf^02d?d_`sKL1~$x0=|-JEkLkZGyB=Y zjzI!(oe`gbeZCXk-b45&6l#MINFos#5fCP`2U|4DkjWJjIlu68Q&QcVu${fs1Zg-XP!?iSe2x6y=tN0Y)gnB7QPX^cLFOA|n2iW;|8zHCf6<}`^q8D18QUI#d*YPc3Le@ubK7t(M$gS;ZD zsQKPIZbNZ8pk}W%`qG$_ppU8|y%h3_Z%QPivUT+H9?N;T1{snBh=rTRcjaK+6VK9aYl^`-mi8DaP!;8q`bAIjiRn$(W zFE8WEYQmNg$cw%Qefy$3^bI0u8lcqgoeEc80Hq09TjYr*AShQ_Qc1%%L70h$nP+86<02{wOl=$QF+};qkM~kHO$pLeJ~bac{M%9k@swiKtrN*x76< zO`mcmyz)z(9MflJmxrL^MLI87d6;nok)f+4$u`#a(F6kt%kIcy-F~f09P8hyR@k@l zhct}IwnMth?6$nms=A4BC|@>??IUPvCTKkOa$9iBZgIlHQ&&c+{>uk+tMPYTMczN!7+ez^DmB&d3#>;_5 z=~CoHES>CoqFAfd8Gt4s?rQmE##KMG6Vb;~HYE%;Wvqll>_N759(n;C=}}4i5F{3R zDVlR^^lp&9=mmXus&^hqvMPh$MG7^KP2+C;wTdsiJx`t2sZ0j=L|j$a){`>tCVR37 z`-1U2vOWq*A9(+J;L533Z97w`8CDLd-sme)F#pIg2L!g#`Ks@XecQ2SSBbDo%q96| zFn;GaucL)>X}y^oG9V5tDj6ua(5kfPNW{QM=O5UHVq5Z5gcKk5N`a5l*+S+^xU4T2 zw#b893Xc_sMw^*vFHti!5kBFmA*_RS1%}9^Ca9MC;eg}GQUWt^2!?d98RCCkxi7jP z&h;4)VubDwOGg`|z?9u}*GD^{qd-wfv+=BrRuKpCh(T zBA5s?;86+N_BVQ;g#+;=>OBM@$<{7PI(W8W#!i$|4=QN;9yR52kkxf5Rj@=H1Uu(U zObWQZGi!}CTzKn8sxYhY7KHGnr5SaS9I&Gp@+i9W?sPh{3Kr|&`cFF^6GQ$q+h?l0 z8EsQ@4#lDY0r>>g{+O_&+FGTE0%D)0fiC|x@d!9ZO+cm-5Pxw6MGC6bcRjxD?HGjh zF!HmLXeVsVH+b5x_$=E( z7sXvfBEIM-Sx{oeaPA=i^!B&h!ZJB;fI0tLrgSH&hppI|Q521T$hvTYM2(N8Bekj~#kdO?wH1X~;0pd|4k^6kl^$tok^D-AdzlmmKib_#O^ZiV z(9=}nL7W?lVv{%zwx+B58ie^TTD+bdN#1_REE_3*X&jVgQGv_m5EFy}GMCBS=_)h3|~ zU%$3G?QgO@n45q2Mr9Rg%mKWaPYnd9rVpS_ip^FR!qjT&f7R? z`&O!9XnD7Ar^J$dPo(a7+R(+_?aD#MU3ZK7>Biemr7Hu0uCSDT;72s2|r8+_Lhty+>YajRP-z7im|Nh za{BlX`g4|}a}30ZG|{Ei!wLhWYRU&R^_kqCw7G-u@jl!{qcA_DlszXqTH-CfAgaOKIgO7TWT%f z)#^+8IAyvNE~vdU9+Eioo)+1~7@3z9ik|bd`?}&F6$lXt?gjjPqpKi_a)|8NjaT7Uh6LJV Date: Thu, 8 Jun 2023 10:47:08 -0700 Subject: [PATCH 723/966] chore(test): replace AWS creds with fictitious samples (#1324) (#1325) --- packages/google-auth/tests/test_aws.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/google-auth/tests/test_aws.py b/packages/google-auth/tests/test_aws.py index 45397d34f4c7..8b18584b0a62 100644 --- a/packages/google-auth/tests/test_aws.py +++ b/packages/google-auth/tests/test_aws.py @@ -63,10 +63,10 @@ CRED_VERIFICATION_URL = ( "https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15" ) -# Sample AWS security credentials to be used with tests that require a session token. -ACCESS_KEY_ID = "ASIARD4OQDT6A77FR3CL" -SECRET_ACCESS_KEY = "Y8AfSaucF37G4PpvfguKZ3/l7Id4uocLXxX0+VTx" -TOKEN = "IQoJb3JpZ2luX2VjEIz//////////wEaCXVzLWVhc3QtMiJGMEQCIH7MHX/Oy/OB8OlLQa9GrqU1B914+iMikqWQW7vPCKlgAiA/Lsv8Jcafn14owfxXn95FURZNKaaphj0ykpmS+Ki+CSq0AwhlEAAaDDA3NzA3MTM5MTk5NiIMx9sAeP1ovlMTMKLjKpEDwuJQg41/QUKx0laTZYjPlQvjwSqS3OB9P1KAXPWSLkliVMMqaHqelvMF/WO/glv3KwuTfQsavRNs3v5pcSEm4SPO3l7mCs7KrQUHwGP0neZhIKxEXy+Ls//1C/Bqt53NL+LSbaGv6RPHaX82laz2qElphg95aVLdYgIFY6JWV5fzyjgnhz0DQmy62/Vi8pNcM2/VnxeCQ8CC8dRDSt52ry2v+nc77vstuI9xV5k8mPtnaPoJDRANh0bjwY5Sdwkbp+mGRUJBAQRlNgHUJusefXQgVKBCiyJY4w3Csd8Bgj9IyDV+Azuy1jQqfFZWgP68LSz5bURyIjlWDQunO82stZ0BgplKKAa/KJHBPCp8Qi6i99uy7qh76FQAqgVTsnDuU6fGpHDcsDSGoCls2HgZjZFPeOj8mmRhFk1Xqvkbjuz8V1cJk54d3gIJvQt8gD2D6yJQZecnuGWd5K2e2HohvCc8Fc9kBl1300nUJPV+k4tr/A5R/0QfEKOZL1/k5lf1g9CREnrM8LVkGxCgdYMxLQow1uTL+QU67AHRRSp5PhhGX4Rek+01vdYSnJCMaPhSEgcLqDlQkhk6MPsyT91QMXcWmyO+cAZwUPwnRamFepuP4K8k2KVXs/LIJHLELwAZ0ekyaS7CptgOqS7uaSTFG3U+vzFZLEnGvWQ7y9IPNQZ+Dffgh4p3vF4J68y9049sI6Sr5d5wbKkcbm8hdCDHZcv4lnqohquPirLiFQ3q7B17V9krMPu3mz1cg4Ekgcrn/E09NTsxAqD8NcZ7C7ECom9r+X3zkDOxaajW6hu3Az8hGlyylDaMiFfRbBJpTIlxp7jfa7CxikNgNtEKLH9iCzvuSg2vhA==" +# Sample fictitious AWS security credentials to be used with tests that require a session token. +ACCESS_KEY_ID = "AKIAIOSFODNN7EXAMPLE" +SECRET_ACCESS_KEY = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" +TOKEN = "AQoEXAMPLEH4aoAH0gNCAPyJxz4BlCFFxWNE1OPTgk5TthT+FvwqnKwRcOIfrRh3c/LTo6UDdyJwOOvEVPvLXCrrrUtdnniCEXAMPLE/IvU1dYUg2RVAJBanLiHb4IgRmpRV3zrkuWJOgQs8IZZaIv2BXIa2R4OlgkBN9bkUDNCJiBeb/AXlzBBko7b15fjrBs2+cTQtpZ3CYWFXG8C5zqx37wnOE49mRl/+OtkIKGO7fAE" # To avoid json.dumps() differing behavior from one version to other, # the JSON payload is hardcoded. REQUEST_PARAMS = '{"KeySchema":[{"KeyType":"HASH","AttributeName":"Id"}],"TableName":"TestTable","AttributeDefinitions":[{"AttributeName":"Id","AttributeType":"S"}],"ProvisionedThroughput":{"WriteCapacityUnits":5,"ReadCapacityUnits":5}}' @@ -514,7 +514,7 @@ "headers": { "Authorization": "AWS4-HMAC-SHA256 Credential=" + ACCESS_KEY_ID - + "/20200811/us-east-2/ec2/aws4_request, SignedHeaders=host;x-amz-date;x-amz-security-token, Signature=631ea80cddfaa545fdadb120dc92c9f18166e38a5c47b50fab9fce476e022855", + + "/20200811/us-east-2/ec2/aws4_request, SignedHeaders=host;x-amz-date;x-amz-security-token, Signature=41e226f997bf917ec6c9b2b14218df0874225f13bb153236c247881e614fafc9", "host": "ec2.us-east-2.amazonaws.com", "x-amz-date": "20200811T065522Z", "x-amz-security-token": TOKEN, @@ -540,7 +540,7 @@ "headers": { "Authorization": "AWS4-HMAC-SHA256 Credential=" + ACCESS_KEY_ID - + "/20200811/us-east-2/sts/aws4_request, SignedHeaders=host;x-amz-date;x-amz-security-token, Signature=73452984e4a880ffdc5c392355733ec3f5ba310d5e0609a89244440cadfe7a7a", + + "/20200811/us-east-2/sts/aws4_request, SignedHeaders=host;x-amz-date;x-amz-security-token, Signature=596aa990b792d763465d73703e684ca273c45536c6d322c31be01a41d02e5b60", "host": "sts.us-east-2.amazonaws.com", "x-amz-date": "20200811T065522Z", "x-amz-security-token": TOKEN, @@ -562,7 +562,7 @@ "headers": { "Authorization": "AWS4-HMAC-SHA256 Credential=" + ACCESS_KEY_ID - + "/20200811/us-east-2/sts/aws4_request, SignedHeaders=host;x-amz-date, Signature=d095ba304919cd0d5570ba8a3787884ee78b860f268ed040ba23831d55536d56", + + "/20200811/us-east-2/sts/aws4_request, SignedHeaders=host;x-amz-date, Signature=9e722e5b7bfa163447e2a14df118b45ebd283c5aea72019bdf921d6e7dc01a9a", "host": "sts.us-east-2.amazonaws.com", "x-amz-date": "20200811T065522Z", }, @@ -592,7 +592,7 @@ "headers": { "Authorization": "AWS4-HMAC-SHA256 Credential=" + ACCESS_KEY_ID - + "/20200811/us-east-2/dynamodb/aws4_request, SignedHeaders=content-type;host;x-amz-date;x-amz-security-token;x-amz-target, Signature=fdaa5b9cc9c86b80fe61eaf504141c0b3523780349120f2bd8145448456e0385", + + "/20200811/us-east-2/dynamodb/aws4_request, SignedHeaders=content-type;host;x-amz-date;x-amz-security-token;x-amz-target, Signature=eb8bce0e63654bba672d4a8acb07e72d69210c1797d56ce024dbbc31beb2a2c7", "host": "dynamodb.us-east-2.amazonaws.com", "x-amz-date": "20200811T065522Z", "Content-Type": "application/x-amz-json-1.0", From dce4746aec51ce49de01d1146b28b5b53c11dff9 Mon Sep 17 00:00:00 2001 From: con-f-use Date: Fri, 9 Jun 2023 01:44:12 +0200 Subject: [PATCH 724/966] fix: invalid `dev` version identifiers in `setup.py` (#1322) Not ~PEP 440~ distlib compliant. Fixes #1321 --- packages/google-auth/setup.py | 4 ++-- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 7e96b5757aa7..01cf80806272 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -31,9 +31,9 @@ ) extras = { - "aiohttp": ["aiohttp >= 3.6.2, < 4.0.0dev", "requests >= 2.20.0, < 3.0.0dev"], + "aiohttp": ["aiohttp >= 3.6.2, < 4.0.0.dev0", "requests >= 2.20.0, < 3.0.0.dev0"], "pyopenssl": ["pyopenssl>=20.0.0", "cryptography>=38.0.3"], - "requests": "requests >= 2.20.0, < 3.0.0dev", + "requests": "requests >= 2.20.0, < 3.0.0.dev0", "reauth": "pyu2f>=0.1.5", # Enterprise cert only works for OpenSSL 1.1.1. Newer versions of these # dependencies are built with OpenSSL 3.0 so we need to fix the version. diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index efa90e0ea2da034b4fc538ba64aa8f4c2e9c9674..8740e8ffe97261ec908ae628a44ed57af78bf610 100644 GIT binary patch literal 10324 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 literal 10324 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 From 0759332f2975aeece1439bdfef037537a1362d09 Mon Sep 17 00:00:00 2001 From: Jan Dolecek Date: Fri, 9 Jun 2023 23:32:12 +0200 Subject: [PATCH 725/966] fix: expiry in compute_engine.IDTokenCredentials (#1327) fixes #1323 --- packages/google-auth/google/auth/compute_engine/credentials.py | 2 +- packages/google-auth/tests/compute_engine/test_credentials.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/google/auth/compute_engine/credentials.py b/packages/google-auth/google/auth/compute_engine/credentials.py index 30ffb162bca4..930d8861702f 100644 --- a/packages/google-auth/google/auth/compute_engine/credentials.py +++ b/packages/google-auth/google/auth/compute_engine/credentials.py @@ -389,7 +389,7 @@ def _call_metadata_identity_endpoint(self, request): six.raise_from(new_exc, caught_exc) _, payload, _, _ = jwt._unverified_decode(id_token) - return id_token, datetime.datetime.fromtimestamp(payload["exp"]) + return id_token, datetime.datetime.utcfromtimestamp(payload["exp"]) def refresh(self, request): """Refreshes the ID token. diff --git a/packages/google-auth/tests/compute_engine/test_credentials.py b/packages/google-auth/tests/compute_engine/test_credentials.py index f56bada2d527..507fea9fcc92 100644 --- a/packages/google-auth/tests/compute_engine/test_credentials.py +++ b/packages/google-auth/tests/compute_engine/test_credentials.py @@ -770,7 +770,7 @@ def test_get_id_token_from_metadata( } assert cred.token == SAMPLE_ID_TOKEN - assert cred.expiry == datetime.datetime.fromtimestamp(SAMPLE_ID_TOKEN_EXP) + assert cred.expiry == datetime.datetime.utcfromtimestamp(SAMPLE_ID_TOKEN_EXP) assert cred._use_metadata_identity_endpoint assert cred._signer is None assert cred._token_uri is None From e40adb959d406576cb6137fe1bae21285eec95cf Mon Sep 17 00:00:00 2001 From: Jan Dolecek Date: Sat, 10 Jun 2023 02:59:17 +0200 Subject: [PATCH 726/966] fix: expiry in impersonated_credentials.IDTokenCredentials (#1330) --- .../google-auth/google/auth/impersonated_credentials.py | 4 +++- packages/google-auth/tests/test_impersonated_credentials.py | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index 7c2f18d742f3..ba6012123f9a 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -454,4 +454,6 @@ def refresh(self, request): id_token = response.json()["token"] self.token = id_token - self.expiry = datetime.fromtimestamp(jwt.decode(id_token, verify=False)["exp"]) + self.expiry = datetime.utcfromtimestamp( + jwt.decode(id_token, verify=False)["exp"] + ) diff --git a/packages/google-auth/tests/test_impersonated_credentials.py b/packages/google-auth/tests/test_impersonated_credentials.py index 0c6ca0ce9cc3..f79db8f6a08b 100644 --- a/packages/google-auth/tests/test_impersonated_credentials.py +++ b/packages/google-auth/tests/test_impersonated_credentials.py @@ -488,7 +488,7 @@ def test_id_token_success( id_creds.refresh(request) assert id_creds.token == ID_TOKEN_DATA - assert id_creds.expiry == datetime.datetime.fromtimestamp(ID_TOKEN_EXPIRY) + assert id_creds.expiry == datetime.datetime.utcfromtimestamp(ID_TOKEN_EXPIRY) def test_id_token_metrics(self, mock_donor_credentials): credentials = self.make_credentials(lifetime=None) @@ -512,7 +512,7 @@ def test_id_token_metrics(self, mock_donor_credentials): id_creds.refresh(None) assert id_creds.token == ID_TOKEN_DATA - assert id_creds.expiry == datetime.datetime.fromtimestamp( + assert id_creds.expiry == datetime.datetime.utcfromtimestamp( ID_TOKEN_EXPIRY ) assert ( @@ -581,7 +581,7 @@ def test_id_token_with_target_audience( id_creds.refresh(request) assert id_creds.token == ID_TOKEN_DATA - assert id_creds.expiry == datetime.datetime.fromtimestamp(ID_TOKEN_EXPIRY) + assert id_creds.expiry == datetime.datetime.utcfromtimestamp(ID_TOKEN_EXPIRY) assert id_creds._include_email is True def test_id_token_invalid_cred( From e1d829bd14b515f084fdfd2f3db5309b4d7ed6fe Mon Sep 17 00:00:00 2001 From: Jin Date: Mon, 12 Jun 2023 11:32:13 -0700 Subject: [PATCH 727/966] chore: token update (#1333) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 8740e8ffe97261ec908ae628a44ed57af78bf610..09cf805e8b02b863d14308bbe8701b9f7bd3d6ec 100644 GIT binary patch literal 10324 zcmV-aD67{BB>?tKRTDuFSqXG`$wo|)rBHVO6J6`xOO%fpH7D};W$fv`Q|1z?PypAf zZWENm6lNSpZrFa1(1kkCa|;hxbxylrjjL_a`?O8JURhIHKF1@jQkBA>LH#W|G~|>T zV;-`G)>DML40#AGK_)$L@73@k%9|LXOHaReEeHlv_@@`y;#@w&o=*aAL#~$TQ&DAY zgGv&|mWPY=EaHKDG0Q*VYFcIY1#eraB(F+%sS?2?8@?{QZ@Zailj(s*0uU-{u;00z zqe=xsNE)F>)8q0yCu$Olv^d371G%x#q5Zv$i~FaV_0)ynwv~D~>@;~_5LdxK!$o_0 zEY}VluPZ=}H(071zQe}*ueZ6n)l?Ac9J8(t*C5hdV8_SK{a`b*}^2>vQDT{PGG=-qwsGCWQ`XaRd)b%)xm)&dMruOm4K^OR*| zSu?(2i!dmE9`m}?BJG0IBx}QxOqg#;7h-cXx!fQV9BjIMKeGvc1vZKwGfxH!R5{~W z5BY^Rr?ZDCS#aL1zlOgxje`yeEX}Qu+>RYnAPZi8-sZ)hf}B?Ftb@U-sc#Zo-H0)45q~+LU;X zDh7cT@O3YzX<9z+O-~$Q#x56)mvX3!gp=ja#^Q61LMS}su^_G|pZrL|N{~%*{;vUCEv9T?tA>%gr|W{*&&W7Z^|%$nOzxN zaVm|?X|qlxxbLfzch`=q9s^6y^L`ajFIS)?t+FQ;_1zp|Ofbj3zz!E-w=K6HdjBv`rc^gxGA2aHoVhk!O^XC$;qg32G&!=DF!z zm5Yz`4y>E3pJIX2K=7i1S*5p_f176c$AElXoPxOfE0($9(dQTERezrXHYimBvR5`d z$mu?4kjt=+#`L&67+CK*up-b~eZcZm@OMObU$FzXtb+YpjKA9dRFk2y+s(S|S;^jc za-NR&3BB(uAcxxevhiQ3*1@F^#fl>$|5ON~d%Ikz+IVHa_8r)IcE<$AD$D*HFfZ!? zv6Ijq+zw`rcy#;S`$C$L;)?kGG7bGmK!43cgR%7Cqm===5@6qxFiG#tCBL8(=1U)2!rs7wZC23{1L;@+C+3htQ``BhD{aqa-_EiftcQK&?QyrZ?C33%Ne z#e#4Yp6U{bRWq=_B)>)35p*9qTbKz777!4J|I0{5T~irfBgc@Dw5zY9IEmT(RQBmkz#d@p60KPQC@Uf1_V&kQ{aHGO}g9J&aJi% z1e{GCTH-{OZ4{VDK+yg?L$ACYdZV&cx2Vt$Ru@_-CJd!yQ#~CrDA%{%&uf5#zMp)$ zmefgi}Re;}VK^396X%uW;Us%Tn=;3c-$!mi z@K2r@9}rftn|e=M2O7YG_AAa@<)~mh5AGe}E*Hk{s zScXruD;-hUZ3}t-MdM!`noKBE922$GoC;T~nKG6sZ)!}j)5AikA;Vqh`{x}v5Mi(| zmy$z2ymtL3VsB9ES$*^754#zzrqpt`R54X1C#TUQ$%QL34>AF%tUuE3wb7O;u_5U~cZn3Dy{yNygpIWz5f%HmtpEK)<%%~O`^xIm;%+SrA3a9Uu6G$qlV~>S_sTlpshnz0lfSY}OAC2>2}}oj!yn)h^;#Ls z-BW5FQCbDq43(iqgbuVbRYUI%%W)fbxw^!j0@K<;n{x4u_XIoELC=5TQNp>6gbbKJ zNQ<5LmB@qBOp`Z%r2rBY5dv4_3A|<{)BI$3iws(*Kj?uGmOG0LK2i`OQFWnrHq*je zUA1Dh37ucF-ufYA&7L-Xh-Y)ECVsVqRxVKS3hwnOp9 z%BRt~#lDzvYMumYNCXCK2K;&tuamP-CR3uzMN-qJb`t+gk*p$=i^8c3)Tj%mQ+Jc@ zZyZ4GD~bWW6fY_Mbn5zbhd_rX___^vlK=(J1$%UZA*tvk2KWh1p{KsxI(3Wr3{t2z zWDw2QNfoAQt075pD=%_oWys=u--P9`@6L2I<)=}v@_tpl!B=KhCnJ<`8+mRqH0a?C z+WiwRD)q*YCc`1kyLIPhipkO=9h`S2j8p=?0~g+p-f7$eNz(vchG47WefK(WTSotp zm3roPIM6c{r$HK?#ANAVzO&(it)QBfNyi#(z7O9%YN;q9+Y-|e$bmeJuJ38b>21mq z{lx7x6WjzT7+gUm;HrCQbRll|`Gomw6H-UR3(VQ6_1*hR4>&IWufozkAiY#!)FOoB zbm)w6Z%Wq$z|Vbw5Ph+)t!51?_P5>aNHnjt%u>YyDwNB+6RBWB?>O&%C?fWmbX_4x zA-BZaFER!=4Qcnc*lCAnOVmK!`lHe(T@{Br!BE;Pd$s5aj6P`+TPTr-64MDM3?}yw zJvr^&4@}7VF*foDlTu@f;)odcr-3nM+rHwG*%F1l@xs(`x}3;P-X@$OY_!}u2JeMt zt0MvXdgAElld=qNKr@6hOz!Y_qcV?yrwdJl^9E(X^Q(}6cftO>Ft*zX@6g!cF(X#D z)IhwE1Jx9OMF?Xc_=*+35}DBgBW)m^~eE&KKKp4F3<*fl(7`J;(U zFXwbTrZH=#a%|+aNn~y{3vQIp(F~B#J2b^1Z7`Rll{V+?#e{+heGn!bdB?($o5Kz> zgJ=p$G3~sJun-Z1GZf7TpNtnVxS5!ADgyn*UHz5bxe_@jievbY&(PQFCe5FSC`^O* zH|8dFRa`^y9vOTm2uyU8eby~VpEXW-in{9Z53AcoRF8HPh5|EC-f`tJR4t7K(OtsL zQ#?9VY+FdQ-Im_rJ3hUC(KGUhR5^Vob&DEpE??)Y*m+x7nicV7(eiB+JbxY(03;WY znoh298KeXa1$#Fs&f{ZFGlmv=+Twux7!_vq{aW4^fm9bGSlN*Nw^*vu$`Js1)SUvn zi>Vtv5GDp)ya;t+uJcU8Kmc{lo8**77U>dVc=R~qbpNO!Dls*^c`|e)^O!&#pLgq6 zd`#OV6zL-?rHJtkk&!5QFPOKXV}4&n8|c0S2}7}pJu#+(V9?bM#!(k8WR9am8|k6i z>G=45Fp!Q0DO$NbEZ>Z=s^KREj5O;{7r5UjD7>oNi?jjRK-7G_`Ku%DL^O*>mGcLt z9xRXvoN*B(mI0b|XRWlG&KhQnBfTwxO;q@WbB=5cXbsp_hJgpg!;u9L$y?fsarUgju9TG|p zu>3U#DuVG~aK47|d}Uxo9mVq&jKnxZh12|s@omGi)xZalQUt#NP-id0;Q}HY@>;MC zW^@sKa#~%M0$t;Lli+6SccyBF579EA1ukT{epim{hVm%e@Gj9N+sP-l`jyyj(7PCy zsAaKv-lw)r=gec35*@*SCJ;oKK=sfY(;wF%Y!CmkW*N&vcvg!J9uD-isXg8=_Tdjs z@0yw)ZR7lTA?`!kRq$Fq?sDxxS4b>bP^pC)2X@5YA=VU^VMo9~oAHDYH0l)slOB0g zcuK)XH#YLJ7Qyh+<=KXldzW`R5h*!6{#`V`)iH&|j}pxTTQLxITl<^9JK^Jf_xl(Q z^y>pH5Ogc!&=SL34@XWhm}jUh5QVUeSD_Ie!w)OY%Z@D}zA%eKLtzh9p?G zu%YwS$>D|ONM5~5PV5LYo1HHe{%Cug!v*Vu`YD~(@*)Rl)};Fdp5q*(Mq|?!q+{95 zh=3D&qf{%rtm%1xl>iQ=yw%Ri3jkryniYFq)3+&Z8RcA4Ec2LF!9lq=LsL4@N35(*`&4 z8=A3+CqNCF4F(W88&}+^PUG2I>=7iiNwmmQ2|5(m@z6}OjqY*bzz=Qc%vSZi^j#~4 z?sDPxnyj@#(`{MKNjr1Ho4J4H% zV4W7u%w6^>l|?;6d3`a4nw&Ii@6!LxYsR`}V z;#)lGu0e@@=+L7bN)?z?+?RX1?ibtuf>E+u8Sxo?2LO^{(V zefLY0)zKS%zOg|#_nvGr{t?{ZN~U3`K?Kk@@+h?w+OP*)Cjl{0W!E80pNF)#vC=3< zFg1D+-Gg|B@*}4J6%>>wlFZ0k;5dTcm4HrN5esX+uB!JS0tY^r_Tk||&|RLCtv>^p zenILfWP+ax0FG)jteX}XF2m#PIcbkOq2f8?w^OM0}T&RPl$=G$>Jh2bDBvmQ2~*2e#(Ib7FK+%Avb0Z zmdqi@yClLHe4QKhTMoK39Q>ufx38vI)s$3gei1QM@d@kj%)6( zTVzZI2Q$EvB=O3XYqtK+`P@>`p(tFw2SR`yD8(U)iBEt@a`n0R{$>Qj6PQ$VI%WX7 zH~!1|h&3&2#p&OMqz!_KQ!CPz>jd3&O}7BCw}K6Gkjko#!@`Lqzq*J{bCc8T%r@rV z@mjsX#I+4Ne~?os#!Fn}A6tBR$!6QqN1Bcw&=STMwyF8ICu>-xA8dpa898WAsuWAc!a1p{06iOA7vbJwubF|TVOk~I})Zq)nQD4}OXa zUudO`qL(WCavuH#56nqrv}Kv*&~Dl z-v{6VA~~UL@gLqRta!clrycf1GR1`?ljZ`XH@c|uXki#`eof9Yg)607gd@02eoiY8 zDRXUnF&;+YMZuM2BX@nxxIU9Wf)~;N<$tnfok}k=p^K?)&sB6E_?b&s4v(zm?YMn= z{fb-G*xM;_KstsSrd&)?QADmu<7}vfeFHkz?aU5yu+m?C#`+~Z`#mk>G z&(Gf}z-OHXKZ>*@I>9xbj^ZESRQj#UH}eTz_O21{J(P#T40QEvQZDm$g z6zJ2%2X|AXW&#@Rx&c76tK#rHS(vwV+3+!X3~V!P+A(80HsxXJE4%VFd)h?XK+^U) z1c%~e`zTQfdQwE^Z(Je8>(+$z?OSBx6|+C)CI2n!Em^7|&|_6T_dk0YUQdIxw0%|@ z6hTv?U|o0_Xd34;Gi3i^Qk0AH$?=hdJ= z#m<5D_%m_l`ec+X9CL?G+N=Q1AloVe`oQNE$jxtfP&dJE5e-B>7(Ir6*|;#}S)Bf{ z-yPazh>8qGMl+`3#LHm5XNubWqY3cs@>fjJ7u`xaJ?a>w%0Iv?9ac+F9qUh)<6=Q; z-{BS4vp=m&;>`pZ;{_ji;W4NQa_jb$Ov4wP@~86?TKXEa%4y1{RM&NOc$BF#=!t)kuIUBg&!S>uDB4!@QL^Lw+rha(LU@#Da z+4Sq*MmTT-LTd$3#HvXaE$N95xAKPFnwS}PUb3ZF%jxO*QD0pJF{2Fv>zs$wkM0hP zuJvj*!$r}lNIwqb;+a4Vd^e4Q$$(V3cKBx>iYjpEZo%a7Q_~PEhYT=Q2-sESW}h8e_~YeaX8;Oem+ z1k>t(Imh=O$-fryRRt{i5|L-!=sY}Ce+VL_aWWUub2QlUaCkb#Z9=Gja3dO|M=guC zF1%6y;7y?2?VY?fdeozv`nHSw_i0VS3{O=bg}MQ%v5_Y^OvH_=x-JO@nwB#URs*Pjd>p!rGu+XC2=wN`*O zQl)286U6ZD<13{0K0DP4_UfM722L;;%5-0$fmjx{2sukzL>6>ws1_4QL$^|>x*$X{ zH#qzR>d5YTFOOmWYyk>zV1oi= zEBmgt^ZNLhL;6!P&on86oORBOagccP)NT$&sqaOWDA&WP@4PPz3{a-XR2+vjk1scg zoLr&+x$X^si6uhFRUFGzep5)+wXw2LI(={=a@nvYp(-_`qkPg-z*es|><_yeq2EyI z8IC92$AeUVKhio(1}HEm4^HR3M-%GjKP}-px+j&GpHZSM;-pqzl^u%{vXy8HOA?nMFr@KF|-g+6{Q_F<|_m>l-icIb{=RXzSvg8*=Y z>QsBIDnkYgo8i^rz}wCpAaki}Hw!ye>mztU{O_d>4e8G2357bZS;5c@GhG5~nq*l* z({H~GeP5z$!r-xkZax#q+r~g5su9eQfo$?f4hNx^o(w=8N=4U?UKE#Tux&%_- zS>D2OiA}NGji*mRlQT49-8haC9#}`W>;G4WSM?2=ZVSn}Y^J6@2D0vxO0@OXfj~*z zu5>?H&fj{QkBb`LXdQeM!Osq7=I30ZnMzrToARoGyn|Z&ScmebLs4qt<^< zLKp?1adQB4Ao0fu@xAd75M1=OdGID1yYKe}T0@%4tGY^aq zADh=wYxOFE!f2otJXA5-osI&(6jNXqH=ts7AzcC{(lHUSa4Z3C8YZ1e+7HTSU^AXg ze23=1Ey|zC_7gZcf?gNJ3~1qr5wt$O1Tq zQLaNzg$8J%ECpK3r-r5~PbyWQz_?Y5;A$+02LcP``maJ`JsJHx>;4=|$|5Y@J59z9 zk(Uw-d=9eKZc;THrm}K(v5~x97t4xtta1E2lErqtTlHP6gK?(~NZnI`?P=?K%k4@C z$p#kK&e#2h@OZ||KYy_}#o>q28$-2k3|0$_Le)yKSq?FxkKQva8_= zo7fu;+Dr^xD(iK*fZVgQ)sk^5!EMwUmTO-Lbx$PDw8Q+F2^ch_Q+S_Ti#?X0+W(T> zvPZ#KYlkFDiFI<3Xid1_J!DH~w&+;c5sdE32bYhs>sk&pj8u8;P*)^M zPkxOE{OZ{;XK6Tt*i3?ypCG`_zkoNu8pR!VKw{J_UW-ii0wMvHksAYTez>{K2EbJf z2#Dy@gKK^^5b6bv7x7lgF_Z!DZXY@oo<$h9`;K@ZKj?uY*^(5otn--0Jyd;km*nVXq>JvFdtvS4+^QgcEBKOycYcbUHZ+w2H9q@OQ`5*A?SoI_lbhjX z91zX#GGFDMrmbaZM#y|32>hJ*K$WSc01nqJeW#sVr3Q6?ymb)<37OC>H z6OWq!r2O^kXR+-19WBKtFkrvt1Nwt`e(LqrZIHt2yb|mhG)%!+Bt2S$wzMmvfsLqI zM&3MxN&UH<00+_Y2A8rwKloLM!32A1P4etLL(nx*s_syV}0;9jy3 z%~gidoz}2$`aYQ8k@tx>dg%sp`l3uZkpLeUKv1IyCe^4iKAq5M?!|B%zwTASJKf9A z!DUTLa(oP_pbw9eLP*te%X_c!Eye0}y8ps*CzJ7FOdhLUVKNkY(1)!QbIF=8{fHMb z$_lgj%CzloKb;5q{Q?&6^Vo;nVhZo;&_g3#Sr!TFEf@`@^^G92WsHCe3wIq!fB^_4 z!6~_727BrH_Hz~a3;p(SA!S`j#7DPSm*=4Y6ocK#ntso`O9oX@wd^{)03N~-xr)A^ z+-y=0k1GK)rUZ&Z3CK~NimCh60S-u$J5WnN8Jm#rTtq0V)5o+dwS4e-VwYg+Bo!lv zwGKOBQ!g?qGK}oGO-kWkR|96fIIJ+yvV?rFgiY0DsLFB0#pHr8y*f4V_mEE=2@&Kc z_8($U4>SO6dlb^{w)xg>JCr5;bE|Si60wG9(Zy1pO4dmb23V#vvpF=KD5b)4SR*SV zRf_g=h9f>sk7 z19~p;=5Gu|IKAm+`d5j2rAXmo`cIYl5}aMPtV*wF72`7-$JWxJDL$BIXtEsNkGpxG zAtlJfYHP>BP)}xpzA_sD17}E^RdF%0Deu^|6aHL|etN*Wi#~;{a!8fY;_7WTk8Oed zXoUVFx1At58}bw4B`nQIcIK^Sq;fQJHyEh|P2(3m?erWEXTWFdD?@?qO119~U9(~t zdz2^8jNz5;AalAIOZ1fNaD7;i_&q-s#RSJmo9w#d_3JBEEx6}7v9q;9++qUrNuJG0%~lR(f`y5ml`GBMQ+7_39fh)kM}xmE+S zUKiz`TWar>c{FCLV1q|QXSS(PG1L!mHv{!QkBg?Pa@RF~OXOF^98{os^rhxmIVc)um@shatK|LVx>5{sM}NiTJES1lBd71z z^bw~=5}J`iD1!+ZMplc0TSK|{Hlc0PTr}SDVK{j&PBoRgFg3YDi2#F7!WgW7m8v<4 z*CnleVOdppbUWc?qU6z&h)Z-(q zC57|_`k|JUZV{XJ`5$%5*|jNu(H@MIS7A@wJdk@}UHy5-eJgljRjc(rnUl6FFg38% zyQ!(%^7A&)-aOVV%}#0p5SUQavF+f8T0*6|qhf!g*GK9)SDsVbOel9whMXGjA)<2F z2tZ-`xf+Thl(e=|>hHyZ-Mf_%xo-xq?_@h7xSHw>q*J%bjtSX&2PPN14X4L6S&e^S zE(UbPm*ZPTZKY?f=plGB0Crw@!qB4)o>8Q|%zCUwZismS-?Q#u&~R$qfevM=Y{@ST zMd#}vIeX&@r7;hCUmiihF!v`hRr-Rto_>CY40@k47mLuA$@lHUu=rD3Y*V z^jaTt4m|o*V^H_e>#`3a$sP>rJfX-Gw5*?eM3zgV4~oO=*;+wD ziC<%%TGt9S<%WBDh?j@43tTr`I%iqlU(C0f(NgAToED)dog@>@p}P$?i8v zz8S^{OzkkbV1Xs;5@Tr|i0;xj(f^Nmju~^QW$S$8#+WjGtGJycj@x#U8>q-x9@)&g zW*(@^G@up?lz#C}-F@Yik2u;ev=K{8hq^;KMFbd}b8E5+aY*S_o~d^f4#`5dVvG5( z=8HF$Sh(E9wbY-`=t&+s1Zm-!*om$Xhj+~PevVv$j<@N0Js?aVOAmPgMslS=0jM>& zt?IjqR+cNNFqtctbjo)*`_(_|ZuO7lg^bO;bzY3DDbA6FF$@V;|IlCd?2sgv(WB6V zkSk_boNJJjXGddl^Hbboz-7vA&coqFjR66}V_N%308k*s9Tyf6^&3P6)AIHF^xG(} zMcz_$rLl`OfHzlgZ9SOe#;hQt)DJDYkaA2%afopRe~4eU0aZLu@W0ar=}(M`@)K%( zca-uAOeom3{EA@35EW2vi>DB%bk9BlUvmw^aFrnVpYXRGk)r8HN_e?N@qa~(EOa!E z(tOKtQ}_Y-Ztz<%^Eo}TYd>1wnc7+>19PHRjPgAm#Xi#6;5(Tl9w5D5`Hl~rkq=bF mW(!M77g3Wq{fsqleD+du&@83{G+d7L(6|654A)ArhyTE&tlg9V literal 10324 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 From 2a1e610cd7b754453ef45c6445dc57ff680be370 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 13 Jun 2023 17:46:14 +0000 Subject: [PATCH 728/966] chore(main): release 2.20.0 (#1328) :robot: I have created a release *beep* *boop* --- ## [2.20.0](https://togithub.com/googleapis/google-auth-library-python/compare/v2.19.1...v2.20.0) (2023-06-12) ### Features * Add public API load_credentials_from_dict ([#1326](https://togithub.com/googleapis/google-auth-library-python/issues/1326)) ([5467ad7](https://togithub.com/googleapis/google-auth-library-python/commit/5467ad75334ee0b5e23522679171cda5fd4edb8a)) ### Bug Fixes * Expiry in compute_engine.IDTokenCredentials ([#1327](https://togithub.com/googleapis/google-auth-library-python/issues/1327)) ([56a6159](https://togithub.com/googleapis/google-auth-library-python/commit/56a6159444467717f5a5e3c04aa678bd0a5881da)), closes [#1323](https://togithub.com/googleapis/google-auth-library-python/issues/1323) * Expiry in impersonated_credentials.IDTokenCredentials ([#1330](https://togithub.com/googleapis/google-auth-library-python/issues/1330)) ([d1b887c](https://togithub.com/googleapis/google-auth-library-python/commit/d1b887c4bebbe4ad0df6d8f7eb6a6d50355a135d)) * Invalid `dev` version identifiers in `setup.py` ([#1322](https://togithub.com/googleapis/google-auth-library-python/issues/1322)) ([a9b8f12](https://togithub.com/googleapis/google-auth-library-python/commit/a9b8f12db0c3ff4f84939646ba0777d21e68f572)), closes [#1321](https://togithub.com/googleapis/google-auth-library-python/issues/1321) --- This PR was generated with [Release Please](https://togithub.com/googleapis/release-please). See [documentation](https://togithub.com/googleapis/release-please#release-please). --- packages/google-auth/CHANGELOG.md | 14 ++++++++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 94d776566ecf..00f0f5f2c080 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,20 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.20.0](https://github.com/googleapis/google-auth-library-python/compare/v2.19.1...v2.20.0) (2023-06-12) + + +### Features + +* Add public API load_credentials_from_dict ([#1326](https://github.com/googleapis/google-auth-library-python/issues/1326)) ([5467ad7](https://github.com/googleapis/google-auth-library-python/commit/5467ad75334ee0b5e23522679171cda5fd4edb8a)) + + +### Bug Fixes + +* Expiry in compute_engine.IDTokenCredentials ([#1327](https://github.com/googleapis/google-auth-library-python/issues/1327)) ([56a6159](https://github.com/googleapis/google-auth-library-python/commit/56a6159444467717f5a5e3c04aa678bd0a5881da)), closes [#1323](https://github.com/googleapis/google-auth-library-python/issues/1323) +* Expiry in impersonated_credentials.IDTokenCredentials ([#1330](https://github.com/googleapis/google-auth-library-python/issues/1330)) ([d1b887c](https://github.com/googleapis/google-auth-library-python/commit/d1b887c4bebbe4ad0df6d8f7eb6a6d50355a135d)) +* Invalid `dev` version identifiers in `setup.py` ([#1322](https://github.com/googleapis/google-auth-library-python/issues/1322)) ([a9b8f12](https://github.com/googleapis/google-auth-library-python/commit/a9b8f12db0c3ff4f84939646ba0777d21e68f572)), closes [#1321](https://github.com/googleapis/google-auth-library-python/issues/1321) + ## [2.19.1](https://github.com/googleapis/google-auth-library-python/compare/v2.19.0...v2.19.1) (2023-06-01) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index ede81dc184c8..f24e7e5b499e 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.19.1" +__version__ = "2.20.0" From 73e10d90576c9bd11f331ca414bace4be0098265 Mon Sep 17 00:00:00 2001 From: Carl Lundin <108372512+clundin25@users.noreply.github.com> Date: Thu, 15 Jun 2023 11:06:12 -0700 Subject: [PATCH 729/966] fix: pypy unit test build (#1335) This resolves https://togithub.com/googleapis/google-auth-library-python/issues/1320 --- packages/google-auth/noxfile.py | 2 +- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index 30b10574ab97..19e162bcc5da 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -145,7 +145,7 @@ def docs(session): @nox.session(python="pypy") def pypy(session): - session.install("-r", "test/requirements.txt") + session.install("-r", "testing/requirements.txt") session.install("-e", ".") session.run( "pytest", diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 09cf805e8b02b863d14308bbe8701b9f7bd3d6ec..d1e8a4a0f45480a4d592f677eb296030b5b0b8ce 100644 GIT binary patch literal 10324 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

  • U~m`h#{00I6`ucXnIIhH8dmc*yk(XIoVo zN^c0&w!mojM@$@6sYFItAwU$eQ^(|WazTIMiAa+${F~AFAaUvd&*7Znm{0re2of+r zJaBZ#urDhf71QPRRb2{ekd*)4ElXy@+lv}Wu?eSvJZZ5fHmn9NR`NWNFS%U&5q$GN zuij^4QbdBX(Y4C!`OWtH38|Xdh`u*8oUggJH+|fSKeYZ2Gwh`S2$YoeC53A?jbh!f zudW`eCNTVCk*i%`XmD1$u}*A8C{mOR2SGiNA0I4Trox#8>96)L3r$VZn+`W*CA(hbS&`X%a z+9y5TIQ1hXMjUSkzgrWUb}tb3L9avAeg1dQmOjX*3KxCHnM=At5WnZxakargW21|95MusG6l|AvFun6TE56u4M?mEh0- z*wX&N&s-i&KV&O{SqeWx=}^-36l+`%%*`cPCWu)zvzXPz-UKZ&pw+0Vl@>BiY}rf7 z&qC|Q?KIUJ$hrDh71$?b9D!~sD{=5C{5v1W31P)l$+nZ1|BSMuK%|eS{v86Te-?@v zq0JH+3X2CW!-|Ha96hw84J>X-r_%_^f15mU;^vArKQ%tgpIkLo0!o}Tnwq!xWFq*& zh0Hl=@vj%sdO#ZCLm9fbJ$j~bjc~*g>RB;g+(6QXMgmW83PU=!FO!MC{#`!D(tT`! zs+`N;A_xd5sApfn0wdbd3y4>l?R5~r(|h~LrZwV3l)tiL-b>n|T?=R%H#oB$&adda z`tLk#coPf$b3x_fIHL0SETr9XB0FT+ZF= zdJBqH#5~j)BfX_D#cqc7RZx*1YGKxg6vf+%a=snDkPx9W|GOue`=FE!2gJ(kR70n9 zdOi8(_(U8H2#mF&SWY zm@+y9ujQYqIb>!94c?MXTlsNMk&r!uw&M?hi`(Zx4F+~-Qn{Bmo{S^jc1C02}1S^evLVSWKd zY&)V1d5rm({Y}&H<(C~aU@oaMT*Meo${@kKeJf4of*qiV8F$5o8RvcaAodUK=S+Kk z9|WVkXH$5{I^P4W2*^wzB_Cifm@iW9B1)RXpE}C~;EJ}8j^H`YtQiLs5@tUV4eql) zwj*-+i>-V;v3DUFlF4%6hWYG(ytJF-hwf?H6F{0TuWM2HXf=;&4OMC=i;?j?d#mTi zz77Gm4*FgF-Lg1_o)4+9+GL#e>dyS1FI~eo1>~v?Mg~RPe;?kTTSu*Zg< zKre#I6#w<_VzU2aH#Ueg&}8#8t3E}ZCC<`Y7uIl;*s zt$i*IkyWvZqL6tfBU0iQnd8N7>~>)3ot}iC=c3 zcT&A_RFV(hBP0>I`em^di8eJ*v>a1SS6m}z{L7VXHfh@V{)pRgu(ry m6u3m8U6|?WdYF8?HYI`>&Jb|rK6=$sErvC=Bmew)(~|$Jt`@@p literal 10324 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%0Deu^|6aHL|etN*Wi#~;{a!8fY;_7WTk8Oed zXoUVFx1At58}bw4B`nQIcIK^Sq;fQJHyEh|P2(3m?erWEXTWFdD?@?qO119~U9(~t zdz2^8jNz5;AalAIOZ1fNaD7;i_&q-s#RSJmo9w#d_3JBEEx6}7v9q;9++qUrNuJG0%~lR(f`y5ml`GBMQ+7_39fh)kM}xmE+S zUKiz`TWar>c{FCLV1q|QXSS(PG1L!mHv{!QkBg?Pa@RF~OXOF^98{os^rhxmIVc)um@shatK|LVx>5{sM}NiTJES1lBd71z z^bw~=5}J`iD1!+ZMplc0TSK|{Hlc0PTr}SDVK{j&PBoRgFg3YDi2#F7!WgW7m8v<4 z*CnleVOdppbUWc?qU6z&h)Z-(q zC57|_`k|JUZV{XJ`5$%5*|jNu(H@MIS7A@wJdk@}UHy5-eJgljRjc(rnUl6FFg38% zyQ!(%^7A&)-aOVV%}#0p5SUQavF+f8T0*6|qhf!g*GK9)SDsVbOel9whMXGjA)<2F z2tZ-`xf+Thl(e=|>hHyZ-Mf_%xo-xq?_@h7xSHw>q*J%bjtSX&2PPN14X4L6S&e^S zE(UbPm*ZPTZKY?f=plGB0Crw@!qB4)o>8Q|%zCUwZismS-?Q#u&~R$qfevM=Y{@ST zMd#}vIeX&@r7;hCUmiihF!v`hRr-Rto_>CY40@k47mLuA$@lHUu=rD3Y*V z^jaTt4m|o*V^H_e>#`3a$sP>rJfX-Gw5*?eM3zgV4~oO=*;+wD ziC<%%TGt9S<%WBDh?j@43tTr`I%iqlU(C0f(NgAToED)dog@>@p}P$?i8v zz8S^{OzkkbV1Xs;5@Tr|i0;xj(f^Nmju~^QW$S$8#+WjGtGJycj@x#U8>q-x9@)&g zW*(@^G@up?lz#C}-F@Yik2u;ev=K{8hq^;KMFbd}b8E5+aY*S_o~d^f4#`5dVvG5( z=8HF$Sh(E9wbY-`=t&+s1Zm-!*om$Xhj+~PevVv$j<@N0Js?aVOAmPgMslS=0jM>& zt?IjqR+cNNFqtctbjo)*`_(_|ZuO7lg^bO;bzY3DDbA6FF$@V;|IlCd?2sgv(WB6V zkSk_boNJJjXGddl^Hbboz-7vA&coqFjR66}V_N%308k*s9Tyf6^&3P6)AIHF^xG(} zMcz_$rLl`OfHzlgZ9SOe#;hQt)DJDYkaA2%afopRe~4eU0aZLu@W0ar=}(M`@)K%( zca-uAOeom3{EA@35EW2vi>DB%bk9BlUvmw^aFrnVpYXRGk)r8HN_e?N@qa~(EOa!E z(tOKtQ}_Y-Ztz<%^Eo}TYd>1wnc7+>19PHRjPgAm#Xi#6;5(Tl9w5D5`Hl~rkq=bF mW(!M77g3Wq{fsqleD+du&@83{G+d7L(6|654A)ArhyTE&tlg9V From d4e013098f86a1e797ed60a2199b9fa5f759bd68 Mon Sep 17 00:00:00 2001 From: aeitzman <12433791+aeitzman@users.noreply.github.com> Date: Fri, 23 Jun 2023 12:51:42 -0700 Subject: [PATCH 730/966] feat: Add framework for BYOID metrics headers (#1332) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add framework for BYOID metrics headers * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * responding to PR comments * fix: changing try catch to if statement * Fix lint and test coverage issue * fix comment --------- Co-authored-by: Owl Bot Co-authored-by: Leo <39062083+lsirac@users.noreply.github.com> --- packages/google-auth/google/auth/aws.py | 5 + .../google/auth/external_account.py | 35 ++++- .../google-auth/google/auth/identity_pool.py | 12 ++ packages/google-auth/google/auth/metrics.py | 12 ++ packages/google-auth/google/auth/pluggable.py | 5 + packages/google-auth/tests/test_aws.py | 34 +++- .../tests/test_external_account.py | 147 +++++++++++++++--- .../google-auth/tests/test_identity_pool.py | 16 ++ packages/google-auth/tests/test_metrics.py | 17 ++ 9 files changed, 253 insertions(+), 30 deletions(-) diff --git a/packages/google-auth/google/auth/aws.py b/packages/google-auth/google/auth/aws.py index 13644c4c22d5..072c67bada45 100644 --- a/packages/google-auth/google/auth/aws.py +++ b/packages/google-auth/google/auth/aws.py @@ -742,6 +742,11 @@ def _should_use_metadata_server(self): return False + def _create_default_metrics_options(self): + metrics_options = super(Credentials, self)._create_default_metrics_options() + metrics_options["source"] = "aws" + return metrics_options + @classmethod def from_info(cls, info, **kwargs): """Creates an AWS Credentials instance from parsed external account info. diff --git a/packages/google-auth/google/auth/external_account.py b/packages/google-auth/google/auth/external_account.py index 436cb34ccd6f..367e25c54290 100644 --- a/packages/google-auth/google/auth/external_account.py +++ b/packages/google-auth/google/auth/external_account.py @@ -40,6 +40,7 @@ from google.auth import credentials from google.auth import exceptions from google.auth import impersonated_credentials +from google.auth import metrics from google.oauth2 import sts from google.oauth2 import utils @@ -140,6 +141,8 @@ def __init__( self._client_auth = None self._sts_client = sts.Client(self._token_url, self._client_auth) + self._metrics_options = self._create_default_metrics_options() + if self._service_account_impersonation_url: self._impersonated_credentials = self._initialize_impersonated_credentials() else: @@ -284,7 +287,9 @@ def token_info_url(self): def with_scopes(self, scopes, default_scopes=None): kwargs = self._constructor_args() kwargs.update(scopes=scopes, default_scopes=default_scopes) - return self.__class__(**kwargs) + scoped = self.__class__(**kwargs) + scoped._metrics_options = self._metrics_options + return scoped @abc.abstractmethod def retrieve_subject_token(self, request): @@ -362,6 +367,11 @@ def refresh(self, request): # is used. The client ID is sufficient for determining the user project. if self._workforce_pool_user_project and not self._client_id: additional_options = {"userProject": self._workforce_pool_user_project} + additional_headers = { + metrics.API_CLIENT_HEADER: metrics.byoid_metrics_header( + self._metrics_options + ) + } response_data = self._sts_client.exchange_token( request=request, grant_type=_STS_GRANT_TYPE, @@ -371,6 +381,7 @@ def refresh(self, request): scopes=scopes, requested_token_type=_STS_REQUESTED_TOKEN_TYPE, additional_options=additional_options, + additional_headers=additional_headers, ) self.token = response_data.get("access_token") lifetime = datetime.timedelta(seconds=response_data.get("expires_in")) @@ -381,13 +392,17 @@ def with_quota_project(self, quota_project_id): # Return copy of instance with the provided quota project ID. kwargs = self._constructor_args() kwargs.update(quota_project_id=quota_project_id) - return self.__class__(**kwargs) + new_cred = self.__class__(**kwargs) + new_cred._metrics_options = self._metrics_options + return new_cred @_helpers.copy_docstring(credentials.CredentialsWithTokenUri) def with_token_uri(self, token_uri): kwargs = self._constructor_args() kwargs.update(token_url=token_uri) - return self.__class__(**kwargs) + new_cred = self.__class__(**kwargs) + new_cred._metrics_options = self._metrics_options + return new_cred def _initialize_impersonated_credentials(self): """Generates an impersonated credentials. @@ -411,6 +426,7 @@ def _initialize_impersonated_credentials(self): service_account_impersonation_options={}, ) source_credentials = self.__class__(**kwargs) + source_credentials._metrics_options = self._metrics_options # Determine target_principal. target_principal = self.service_account_email @@ -432,6 +448,19 @@ def _initialize_impersonated_credentials(self): ), ) + def _create_default_metrics_options(self): + metrics_options = {} + if self._service_account_impersonation_url: + metrics_options["sa-impersonation"] = "true" + else: + metrics_options["sa-impersonation"] = "false" + if self._service_account_impersonation_options.get("token_lifetime_seconds"): + metrics_options["config-lifetime"] = "true" + else: + metrics_options["config-lifetime"] = "false" + + return metrics_options + @classmethod def from_info(cls, info, **kwargs): """Creates a Credentials instance from parsed external account info. diff --git a/packages/google-auth/google/auth/identity_pool.py b/packages/google-auth/google/auth/identity_pool.py index ebe50883c4f3..a515353c376e 100644 --- a/packages/google-auth/google/auth/identity_pool.py +++ b/packages/google-auth/google/auth/identity_pool.py @@ -216,6 +216,18 @@ def _parse_token_data( ) return token + def _create_default_metrics_options(self): + metrics_options = super(Credentials, self)._create_default_metrics_options() + # Check that credential source is a dict before checking for file vs url. This check needs to be done + # here because the external_account credential constructor needs to pass the metrics options to the + # impersonated credential object before the identity_pool credentials are validated. + if isinstance(self._credential_source, Mapping): + if self._credential_source.get("file"): + metrics_options["source"] = "file" + else: + metrics_options["source"] = "url" + return metrics_options + @classmethod def from_info(cls, info, **kwargs): """Creates an Identity Pool Credentials instance from parsed external account info. diff --git a/packages/google-auth/google/auth/metrics.py b/packages/google-auth/google/auth/metrics.py index f7303282c941..11e4b0773077 100644 --- a/packages/google-auth/google/auth/metrics.py +++ b/packages/google-auth/google/auth/metrics.py @@ -23,6 +23,9 @@ API_CLIENT_HEADER = "x-goog-api-client" +# BYOID Specific consts +BYOID_HEADER_SECTION = "google-byoid-sdk" + # Auth request type REQUEST_TYPE_ACCESS_TOKEN = "auth-request-type/at" REQUEST_TYPE_ID_TOKEN = "auth-request-type/it" @@ -123,6 +126,15 @@ def reauth_continue(): return "{} {}".format(python_and_auth_lib_version(), REQUEST_TYPE_REAUTH_CONTINUE) +# x-goog-api-client header value for BYOID calls to the Security Token Service exchange token endpoint. +# Example: "gl-python/3.7 auth/1.1 google-byoid-sdk source/aws sa-impersonation/true sa-impersonation/true" +def byoid_metrics_header(metrics_options): + header = "{} {}".format(python_and_auth_lib_version(), BYOID_HEADER_SECTION) + for key, value in metrics_options.items(): + header = "{} {}/{}".format(header, key, value) + return header + + def add_metric_header(headers, metric_header_value): """Add x-goog-api-client header with the given value. diff --git a/packages/google-auth/google/auth/pluggable.py b/packages/google-auth/google/auth/pluggable.py index 7fef36112627..3f8d47c94cad 100644 --- a/packages/google-auth/google/auth/pluggable.py +++ b/packages/google-auth/google/auth/pluggable.py @@ -422,3 +422,8 @@ def _validate_running_mode(self): raise exceptions.InvalidValue( "Interactive mode is only enabled for workforce pool." ) + + def _create_default_metrics_options(self): + metrics_options = super(Credentials, self)._create_default_metrics_options() + metrics_options["source"] = "executable" + return metrics_options diff --git a/packages/google-auth/tests/test_aws.py b/packages/google-auth/tests/test_aws.py index 8b18584b0a62..9a1c7fd6487d 100644 --- a/packages/google-auth/tests/test_aws.py +++ b/packages/google-auth/tests/test_aws.py @@ -32,6 +32,8 @@ "gl-python/3.7 auth/1.1 auth-request-type/at cred-type/imp" ) +LANG_LIBRARY_METRICS_HEADER_VALUE = "gl-python/3.7 auth/1.1" + CLIENT_ID = "username" CLIENT_SECRET = "password" # Base64 encoding of "username:password". @@ -1793,8 +1795,14 @@ def test_retrieve_subject_token_error_determining_aws_security_creds(self): assert excinfo.match(r"Unable to retrieve AWS security credentials") + @mock.patch( + "google.auth.metrics.python_and_auth_lib_version", + return_value=LANG_LIBRARY_METRICS_HEADER_VALUE, + ) @mock.patch("google.auth._helpers.utcnow") - def test_refresh_success_without_impersonation_ignore_default_scopes(self, utcnow): + def test_refresh_success_without_impersonation_ignore_default_scopes( + self, utcnow, mock_auth_lib_value + ): utcnow.return_value = datetime.datetime.strptime( self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ" ) @@ -1808,6 +1816,7 @@ def test_refresh_success_without_impersonation_ignore_default_scopes(self, utcno token_headers = { "Content-Type": "application/x-www-form-urlencoded", "Authorization": "Basic " + BASIC_AUTH_ENCODING, + "x-goog-api-client": "gl-python/3.7 auth/1.1 google-byoid-sdk sa-impersonation/false config-lifetime/false source/aws", } token_request_data = { "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange", @@ -1849,8 +1858,14 @@ def test_refresh_success_without_impersonation_ignore_default_scopes(self, utcno assert credentials.scopes == SCOPES assert credentials.default_scopes == ["ignored"] + @mock.patch( + "google.auth.metrics.python_and_auth_lib_version", + return_value=LANG_LIBRARY_METRICS_HEADER_VALUE, + ) @mock.patch("google.auth._helpers.utcnow") - def test_refresh_success_without_impersonation_use_default_scopes(self, utcnow): + def test_refresh_success_without_impersonation_use_default_scopes( + self, utcnow, mock_auth_lib_value + ): utcnow.return_value = datetime.datetime.strptime( self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ" ) @@ -1864,6 +1879,7 @@ def test_refresh_success_without_impersonation_use_default_scopes(self, utcnow): token_headers = { "Content-Type": "application/x-www-form-urlencoded", "Authorization": "Basic " + BASIC_AUTH_ENCODING, + "x-goog-api-client": "gl-python/3.7 auth/1.1 google-byoid-sdk sa-impersonation/false config-lifetime/false source/aws", } token_request_data = { "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange", @@ -1909,9 +1925,13 @@ def test_refresh_success_without_impersonation_use_default_scopes(self, utcnow): "google.auth.metrics.token_request_access_token_impersonate", return_value=IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, ) + @mock.patch( + "google.auth.metrics.python_and_auth_lib_version", + return_value=LANG_LIBRARY_METRICS_HEADER_VALUE, + ) @mock.patch("google.auth._helpers.utcnow") def test_refresh_success_with_impersonation_ignore_default_scopes( - self, utcnow, mock_metrics_header_value + self, utcnow, mock_metrics_header_value, mock_auth_lib_value ): utcnow.return_value = datetime.datetime.strptime( self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ" @@ -1929,6 +1949,7 @@ def test_refresh_success_with_impersonation_ignore_default_scopes( token_headers = { "Content-Type": "application/x-www-form-urlencoded", "Authorization": "Basic " + BASIC_AUTH_ENCODING, + "x-goog-api-client": "gl-python/3.7 auth/1.1 google-byoid-sdk sa-impersonation/true config-lifetime/false source/aws", } token_request_data = { "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange", @@ -2000,9 +2021,13 @@ def test_refresh_success_with_impersonation_ignore_default_scopes( "google.auth.metrics.token_request_access_token_impersonate", return_value=IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, ) + @mock.patch( + "google.auth.metrics.python_and_auth_lib_version", + return_value=LANG_LIBRARY_METRICS_HEADER_VALUE, + ) @mock.patch("google.auth._helpers.utcnow") def test_refresh_success_with_impersonation_use_default_scopes( - self, utcnow, mock_metrics_header_value + self, utcnow, mock_metrics_header_value, mock_auth_lib_value ): utcnow.return_value = datetime.datetime.strptime( self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ" @@ -2020,6 +2045,7 @@ def test_refresh_success_with_impersonation_use_default_scopes( token_headers = { "Content-Type": "application/x-www-form-urlencoded", "Authorization": "Basic " + BASIC_AUTH_ENCODING, + "x-goog-api-client": "gl-python/3.7 auth/1.1 google-byoid-sdk sa-impersonation/true config-lifetime/false source/aws", } token_request_data = { "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange", diff --git a/packages/google-auth/tests/test_external_account.py b/packages/google-auth/tests/test_external_account.py index 5d9cec7cb204..9ef61340c862 100644 --- a/packages/google-auth/tests/test_external_account.py +++ b/packages/google-auth/tests/test_external_account.py @@ -29,6 +29,7 @@ IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE = ( "gl-python/3.7 auth/1.1 auth-request-type/at cred-type/imp" ) +LANG_LIBRARY_METRICS_HEADER_VALUE = "gl-python/3.7 auth/1.1" CLIENT_ID = "username" CLIENT_SECRET = "password" @@ -624,15 +625,24 @@ def test_is_workforce_pool_with_users_and_impersonation(self, audience): # Even though impersonation is used, is_workforce_pool should still return True. assert credentials.is_workforce_pool is True + @mock.patch( + "google.auth.metrics.python_and_auth_lib_version", + return_value=LANG_LIBRARY_METRICS_HEADER_VALUE, + ) @mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) - def test_refresh_without_client_auth_success(self, unused_utcnow): + def test_refresh_without_client_auth_success( + self, unused_utcnow, mock_auth_lib_value + ): response = self.SUCCESS_RESPONSE.copy() # Test custom expiration to confirm expiry is set correctly. response["expires_in"] = 2800 expected_expiry = datetime.datetime.min + datetime.timedelta( seconds=response["expires_in"] ) - headers = {"Content-Type": "application/x-www-form-urlencoded"} + headers = { + "Content-Type": "application/x-www-form-urlencoded", + "x-goog-api-client": "gl-python/3.7 auth/1.1 google-byoid-sdk sa-impersonation/false config-lifetime/false", + } request_data = { "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange", "audience": self.AUDIENCE, @@ -651,15 +661,24 @@ def test_refresh_without_client_auth_success(self, unused_utcnow): assert not credentials.expired assert credentials.token == response["access_token"] + @mock.patch( + "google.auth.metrics.python_and_auth_lib_version", + return_value=LANG_LIBRARY_METRICS_HEADER_VALUE, + ) @mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) - def test_refresh_workforce_without_client_auth_success(self, unused_utcnow): + def test_refresh_workforce_without_client_auth_success( + self, unused_utcnow, test_auth_lib_value + ): response = self.SUCCESS_RESPONSE.copy() # Test custom expiration to confirm expiry is set correctly. response["expires_in"] = 2800 expected_expiry = datetime.datetime.min + datetime.timedelta( seconds=response["expires_in"] ) - headers = {"Content-Type": "application/x-www-form-urlencoded"} + headers = { + "Content-Type": "application/x-www-form-urlencoded", + "x-goog-api-client": "gl-python/3.7 auth/1.1 google-byoid-sdk sa-impersonation/false config-lifetime/false", + } request_data = { "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange", "audience": self.WORKFORCE_AUDIENCE, @@ -683,8 +702,14 @@ def test_refresh_workforce_without_client_auth_success(self, unused_utcnow): assert not credentials.expired assert credentials.token == response["access_token"] + @mock.patch( + "google.auth.metrics.python_and_auth_lib_version", + return_value=LANG_LIBRARY_METRICS_HEADER_VALUE, + ) @mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) - def test_refresh_workforce_with_client_auth_success(self, unused_utcnow): + def test_refresh_workforce_with_client_auth_success( + self, unused_utcnow, mock_auth_lib_value + ): response = self.SUCCESS_RESPONSE.copy() # Test custom expiration to confirm expiry is set correctly. response["expires_in"] = 2800 @@ -694,6 +719,7 @@ def test_refresh_workforce_with_client_auth_success(self, unused_utcnow): headers = { "Content-Type": "application/x-www-form-urlencoded", "Authorization": "Basic {}".format(BASIC_AUTH_ENCODING), + "x-goog-api-client": "gl-python/3.7 auth/1.1 google-byoid-sdk sa-impersonation/false config-lifetime/false", } request_data = { "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange", @@ -718,9 +744,13 @@ def test_refresh_workforce_with_client_auth_success(self, unused_utcnow): assert not credentials.expired assert credentials.token == response["access_token"] + @mock.patch( + "google.auth.metrics.python_and_auth_lib_version", + return_value=LANG_LIBRARY_METRICS_HEADER_VALUE, + ) @mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) def test_refresh_workforce_with_client_auth_and_no_workforce_project_success( - self, unused_utcnow + self, unused_utcnow, mock_lib_version_value ): response = self.SUCCESS_RESPONSE.copy() # Test custom expiration to confirm expiry is set correctly. @@ -731,6 +761,7 @@ def test_refresh_workforce_with_client_auth_and_no_workforce_project_success( headers = { "Content-Type": "application/x-www-form-urlencoded", "Authorization": "Basic {}".format(BASIC_AUTH_ENCODING), + "x-goog-api-client": "gl-python/3.7 auth/1.1 google-byoid-sdk sa-impersonation/false config-lifetime/false", } request_data = { "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange", @@ -759,8 +790,12 @@ def test_refresh_workforce_with_client_auth_and_no_workforce_project_success( "google.auth.metrics.token_request_access_token_impersonate", return_value=IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, ) + @mock.patch( + "google.auth.metrics.python_and_auth_lib_version", + return_value=LANG_LIBRARY_METRICS_HEADER_VALUE, + ) def test_refresh_impersonation_without_client_auth_success( - self, mock_metrics_header_value + self, mock_metrics_header_value, mock_auth_lib_value ): # Simulate service account access token expires in 2800 seconds. expire_time = ( @@ -769,7 +804,10 @@ def test_refresh_impersonation_without_client_auth_success( expected_expiry = datetime.datetime.strptime(expire_time, "%Y-%m-%dT%H:%M:%SZ") # STS token exchange request/response. token_response = self.SUCCESS_RESPONSE.copy() - token_headers = {"Content-Type": "application/x-www-form-urlencoded"} + token_headers = { + "Content-Type": "application/x-www-form-urlencoded", + "x-goog-api-client": "gl-python/3.7 auth/1.1 google-byoid-sdk sa-impersonation/true config-lifetime/false", + } token_request_data = { "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange", "audience": self.AUDIENCE, @@ -830,8 +868,12 @@ def test_refresh_impersonation_without_client_auth_success( "google.auth.metrics.token_request_access_token_impersonate", return_value=IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, ) + @mock.patch( + "google.auth.metrics.python_and_auth_lib_version", + return_value=LANG_LIBRARY_METRICS_HEADER_VALUE, + ) def test_refresh_workforce_impersonation_without_client_auth_success( - self, mock_metrics_header_value + self, mock_metrics_header_value, mock_auth_lib_value ): # Simulate service account access token expires in 2800 seconds. expire_time = ( @@ -840,7 +882,10 @@ def test_refresh_workforce_impersonation_without_client_auth_success( expected_expiry = datetime.datetime.strptime(expire_time, "%Y-%m-%dT%H:%M:%SZ") # STS token exchange request/response. token_response = self.SUCCESS_RESPONSE.copy() - token_headers = {"Content-Type": "application/x-www-form-urlencoded"} + token_headers = { + "Content-Type": "application/x-www-form-urlencoded", + "x-goog-api-client": "gl-python/3.7 auth/1.1 google-byoid-sdk sa-impersonation/true config-lifetime/false", + } token_request_data = { "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange", "audience": self.WORKFORCE_AUDIENCE, @@ -901,10 +946,17 @@ def test_refresh_workforce_impersonation_without_client_auth_success( assert not credentials.expired assert credentials.token == impersonation_response["accessToken"] + @mock.patch( + "google.auth.metrics.python_and_auth_lib_version", + return_value=LANG_LIBRARY_METRICS_HEADER_VALUE, + ) def test_refresh_without_client_auth_success_explicit_user_scopes_ignore_default_scopes( - self, + self, mock_auth_lib_value ): - headers = {"Content-Type": "application/x-www-form-urlencoded"} + headers = { + "Content-Type": "application/x-www-form-urlencoded", + "x-goog-api-client": "gl-python/3.7 auth/1.1 google-byoid-sdk sa-impersonation/false config-lifetime/false", + } request_data = { "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange", "audience": self.AUDIENCE, @@ -931,8 +983,17 @@ def test_refresh_without_client_auth_success_explicit_user_scopes_ignore_default assert credentials.has_scopes(["scope1", "scope2"]) assert not credentials.has_scopes(["ignored"]) - def test_refresh_without_client_auth_success_explicit_default_scopes_only(self): - headers = {"Content-Type": "application/x-www-form-urlencoded"} + @mock.patch( + "google.auth.metrics.python_and_auth_lib_version", + return_value=LANG_LIBRARY_METRICS_HEADER_VALUE, + ) + def test_refresh_without_client_auth_success_explicit_default_scopes_only( + self, mock_auth_lib_value + ): + headers = { + "Content-Type": "application/x-www-form-urlencoded", + "x-goog-api-client": "gl-python/3.7 auth/1.1 google-byoid-sdk sa-impersonation/false config-lifetime/false", + } request_data = { "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange", "audience": self.AUDIENCE, @@ -992,10 +1053,15 @@ def test_refresh_impersonation_without_client_auth_error(self): assert not credentials.expired assert credentials.token is None - def test_refresh_with_client_auth_success(self): + @mock.patch( + "google.auth.metrics.python_and_auth_lib_version", + return_value=LANG_LIBRARY_METRICS_HEADER_VALUE, + ) + def test_refresh_with_client_auth_success(self, mock_auth_lib_value): headers = { "Content-Type": "application/x-www-form-urlencoded", "Authorization": "Basic {}".format(BASIC_AUTH_ENCODING), + "x-goog-api-client": "gl-python/3.7 auth/1.1 google-byoid-sdk sa-impersonation/false config-lifetime/false", } request_data = { "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange", @@ -1022,8 +1088,12 @@ def test_refresh_with_client_auth_success(self): "google.auth.metrics.token_request_access_token_impersonate", return_value=IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, ) + @mock.patch( + "google.auth.metrics.python_and_auth_lib_version", + return_value=LANG_LIBRARY_METRICS_HEADER_VALUE, + ) def test_refresh_impersonation_with_client_auth_success_ignore_default_scopes( - self, mock_metrics_header_value + self, mock_metrics_header_value, mock_auth_lib_value ): # Simulate service account access token expires in 2800 seconds. expire_time = ( @@ -1035,6 +1105,7 @@ def test_refresh_impersonation_with_client_auth_success_ignore_default_scopes( token_headers = { "Content-Type": "application/x-www-form-urlencoded", "Authorization": "Basic {}".format(BASIC_AUTH_ENCODING), + "x-goog-api-client": "gl-python/3.7 auth/1.1 google-byoid-sdk sa-impersonation/true config-lifetime/false", } token_request_data = { "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange", @@ -1100,8 +1171,12 @@ def test_refresh_impersonation_with_client_auth_success_ignore_default_scopes( "google.auth.metrics.token_request_access_token_impersonate", return_value=IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, ) + @mock.patch( + "google.auth.metrics.python_and_auth_lib_version", + return_value=LANG_LIBRARY_METRICS_HEADER_VALUE, + ) def test_refresh_impersonation_with_client_auth_success_use_default_scopes( - self, mock_metrics_header_value + self, mock_metrics_header_value, mock_auth_lib_value ): # Simulate service account access token expires in 2800 seconds. expire_time = ( @@ -1113,6 +1188,7 @@ def test_refresh_impersonation_with_client_auth_success_use_default_scopes( token_headers = { "Content-Type": "application/x-www-form-urlencoded", "Authorization": "Basic {}".format(BASIC_AUTH_ENCODING), + "x-goog-api-client": "gl-python/3.7 auth/1.1 google-byoid-sdk sa-impersonation/true config-lifetime/false", } token_request_data = { "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange", @@ -1524,12 +1600,19 @@ def test_project_id_without_scopes(self): "google.auth.metrics.token_request_access_token_impersonate", return_value=IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, ) + @mock.patch( + "google.auth.metrics.python_and_auth_lib_version", + return_value=LANG_LIBRARY_METRICS_HEADER_VALUE, + ) def test_get_project_id_cloud_resource_manager_success( - self, mock_metrics_header_value + self, mock_metrics_header_value, mock_auth_lib_value ): # STS token exchange request/response. token_response = self.SUCCESS_RESPONSE.copy() - token_headers = {"Content-Type": "application/x-www-form-urlencoded"} + token_headers = { + "Content-Type": "application/x-www-form-urlencoded", + "x-goog-api-client": "gl-python/3.7 auth/1.1 google-byoid-sdk sa-impersonation/true config-lifetime/false", + } token_request_data = { "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange", "audience": self.AUDIENCE, @@ -1615,9 +1698,18 @@ def test_get_project_id_cloud_resource_manager_success( # No additional requests. assert len(request.call_args_list) == 3 - def test_workforce_pool_get_project_id_cloud_resource_manager_success(self): + @mock.patch( + "google.auth.metrics.python_and_auth_lib_version", + return_value=LANG_LIBRARY_METRICS_HEADER_VALUE, + ) + def test_workforce_pool_get_project_id_cloud_resource_manager_success( + self, mock_auth_lib_value + ): # STS token exchange request/response. - token_headers = {"Content-Type": "application/x-www-form-urlencoded"} + token_headers = { + "Content-Type": "application/x-www-form-urlencoded", + "x-goog-api-client": "gl-python/3.7 auth/1.1 google-byoid-sdk sa-impersonation/false config-lifetime/false", + } token_request_data = { "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange", "audience": self.WORKFORCE_AUDIENCE, @@ -1681,7 +1773,13 @@ def test_workforce_pool_get_project_id_cloud_resource_manager_success(self): "google.auth.metrics.token_request_access_token_impersonate", return_value=IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, ) - def test_refresh_impersonation_with_lifetime(self, mock_metrics_header_value): + @mock.patch( + "google.auth.metrics.python_and_auth_lib_version", + return_value=LANG_LIBRARY_METRICS_HEADER_VALUE, + ) + def test_refresh_impersonation_with_lifetime( + self, mock_metrics_header_value, mock_auth_lib_value + ): # Simulate service account access token expires in 2800 seconds. expire_time = ( _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=2800) @@ -1689,7 +1787,10 @@ def test_refresh_impersonation_with_lifetime(self, mock_metrics_header_value): expected_expiry = datetime.datetime.strptime(expire_time, "%Y-%m-%dT%H:%M:%SZ") # STS token exchange request/response. token_response = self.SUCCESS_RESPONSE.copy() - token_headers = {"Content-Type": "application/x-www-form-urlencoded"} + token_headers = { + "Content-Type": "application/x-www-form-urlencoded", + "x-goog-api-client": "gl-python/3.7 auth/1.1 google-byoid-sdk sa-impersonation/true config-lifetime/true", + } token_request_data = { "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange", "audience": self.AUDIENCE, diff --git a/packages/google-auth/tests/test_identity_pool.py b/packages/google-auth/tests/test_identity_pool.py index 561af35ac04e..8228e89e641a 100644 --- a/packages/google-auth/tests/test_identity_pool.py +++ b/packages/google-auth/tests/test_identity_pool.py @@ -24,6 +24,7 @@ from google.auth import _helpers from google.auth import exceptions from google.auth import identity_pool +from google.auth import metrics from google.auth import transport @@ -268,6 +269,21 @@ def assert_underlying_credentials_refresh( if basic_auth_encoding: token_headers["Authorization"] = "Basic " + basic_auth_encoding + metrics_options = {} + if credentials._service_account_impersonation_url: + metrics_options["sa-impersonation"] = "true" + else: + metrics_options["sa-impersonation"] = "false" + metrics_options["config-lifetime"] = "false" + if credentials._credential_source_file: + metrics_options["source"] = "file" + else: + metrics_options["source"] = "url" + + token_headers["x-goog-api-client"] = metrics.byoid_metrics_header( + metrics_options + ) + if service_account_impersonation_url: token_scopes = "https://www.googleapis.com/auth/iam" else: diff --git a/packages/google-auth/tests/test_metrics.py b/packages/google-auth/tests/test_metrics.py index 535b65451f0f..ba9389267401 100644 --- a/packages/google-auth/tests/test_metrics.py +++ b/packages/google-auth/tests/test_metrics.py @@ -77,3 +77,20 @@ def test_metric_values(mock_python_and_auth_lib_version): assert ( metrics.reauth_continue() == "gl-python/3.7 auth/1.1 auth-request-type/re-cont" ) + + +@mock.patch( + "google.auth.metrics.python_and_auth_lib_version", + return_value="gl-python/3.7 auth/1.1", +) +def test_byoid_metric_header(mock_python_and_auth_lib_version): + metrics_options = {} + assert ( + metrics.byoid_metrics_header(metrics_options) + == "gl-python/3.7 auth/1.1 google-byoid-sdk" + ) + metrics_options["testKey"] = "testValue" + assert ( + metrics.byoid_metrics_header(metrics_options) + == "gl-python/3.7 auth/1.1 google-byoid-sdk testKey/testValue" + ) From f8bbc8d9c33b62cdfd4855e4301d572168bf64cd Mon Sep 17 00:00:00 2001 From: Carl Lundin <108372512+clundin25@users.noreply.github.com> Date: Mon, 26 Jun 2023 10:26:13 -0700 Subject: [PATCH 731/966] chore: Refresh system test creds. (#1337) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index d1e8a4a0f45480a4d592f677eb296030b5b0b8ce..15422930ad7575fbd7cff99a9eb08c0ae3a586dc 100644 GIT binary patch literal 10324 zcmV-aD67{BB>?tKRTEt^Joku*XHd=O4@N|I9etRM&1LS4?{wrZF%W@E0BaJePyn$b zkPHtGfz&Wm?%$6+JZq_z7uw?Wl*PZt{W{sm*a^lNl^uddoC}<8Q^p!vPilOsgaYVE zJDz>B{hDuw%*J7kyuf2R#p%XF*-TOM&*>AyZ^*hN@fCY@NC736L}RAX?gzbw`1P z*)Q6gX7bUtZ(~d%d)W$fpL8(g(K28|C3flbC3q`v?+0tKDDcT{6VjY)4?<4 zAXPB37aeZI%o;YKR0%{}=O>_FVcYJSzoj>xAFnE;;kE|Cq0xt?%t12~w+gF_Tk9Go zx`&#&i&T|{9?J1KV4dUVLl*?c(oVOI&~x>0?Y?Tde{}!?E`75`rgCh-Nd1kVz=#gN521hVm(m1J{jeBKu+WKAZe-HgLM#^(9Nq=`S|1N1xtr2nXC@_3`C?=1{qBax) z0G8@!P4b{PC9csq(a>={f)Js`C!J;sLDTa`JxxpPt9&kW7>yP^jv_R<1C>p&1QYHj z9WcW7gGx*uz}(-BbE2xv4Vmf1Z5vB4hQD#D%7+V|<-U{pC5Kx!p&h?Ik!+Gu!2Ls? ze7Al{_r^9XEy>XR18fq9Scw#uHb@lV)Ba_^tdO=(!-l<0wPHiAxB6#gGNK6?$`Pqb zLikL#k)FlRhdDSZ^Q+Jw&9PL)B~l1=*aBTfXol%vT0|9kei6dR+hj%aFU4>YFNOuo z0n6a|olIWqcg1$bfs&)Mzcti7k^e*smCH247X_8;%rada#wUu#H`tN74z1nf6=bvt z{kuERJ71#3JQ)LaGkp+Wr?C66fxg*LJK9>LDcYGrGgSTh%BiVTNtGR3t!jC!C2Y#!#H9L1FwK;2y?8NpGaot7!< z&x9IogPntxAF&IXEsLEd#U>7~ry~L~y@Hr#jg65X6(9(sQx>M%{b?Yjy5|4*s{A_Q z`H!pK;}UGTc1bW$>fW`NlU%~vG_R8Q?^+#Px~DLvTBTl9Jeh56=mW?Rl$Tb=90ZT{ zTDXrd0$_1iU{++3geLH71i-Hpd!D?n+R8!?&bM$Z{mf+YZujkM@JJ3LP;a4=VDGvi z3C#WKkILkNz40SU5ghT;y73?;{7<(FVx_9sb}}?j?D!vBM;lL6$5lE6be;qq;>DH^ zw+oKexWaJ3)AH|eBagYKcmIKn`e8DH&$D($Qm>RSpif0hNQ2-=*-j%5I)?=WrI=>3 z-moX3mE02+w3VBpz}~X680qpx9AJJ~qqPePvL5rQ^^l>b2VrT742^sf%lm2EJ)9~L z(&M-*7?%OdG-GFI*?$|4eo&^JK*kGBm9f)6fa#Qk>Q9S2&vcO3?t)jwNHLb$EvYBv zUcY}WJFCxh1r_z?3@89@W3VXU((4w*{>N{O$mwT=mnMz@RZAciasI1{o3~EZG8^i_?-SpXj!}EZ z7tJSY8+N^4NQ{e!IK!^ftVj7pXR)FFd{_mGdx5ITEWso`8^4MNt- zBU~hX(th|qb8|ncCZnmq=+Cvwsyr$asi|8_;{Z{gVSpxXR&$$BpW8>o7c9r{0QC$_ zzGOp=!JPB=_nouP4lGj8nx@goLJT>^Qf-(bDYh-9L;{jUk}QXR#Az)h+L+{SX}iTCL8weJFpo!{n%AE3+#V zKFcQYh@7Brd~Fc0l6s5c79k*b>vSVWt1Vf{@eSXoSX|Ci3Kt*osK8YJby+pr3Q2C@ z_B!;2zX@RuTV@ddWW2(BBmY;pq+0}A*6YfU9ZSTLh}lHGFA4B5hjN8fuv)O)W}95} zU;*QfZYTCl49rZB65HH1L$g&ULz}s1?XyY9fWT&&F9}WX6`1e3OP_&)-?GLu57YFo z^*=?@R@A00Stc()0f8c>+xZKnqZXD#M&NbhnhVypqg7k-A0Weo7!{)pCRZU!1Yfz=9YR_;{S@nDg)ZH~2JdoJIB9c+zcdmJpj})IFwo(ls1) zAtk)ePpR&ST?tzvj1KC!R6r*?cc_uO(pBc;OuyIl9l&SH;zJQdRaJ|ZLKf8l=3+`8 zY@J6@r7etKjr7si-UJ3>NJyP1PpE;pIOSNXpG z(xjYl02Gr348QEc9GFjCi?V+e%MG&+iVmIBin&i|KdCNaVJS}hs=@jS?&dRaac?fo zy`{x5gl$f99%nQ>#=zcsHWnmdjK63$Fp(Kg1v|(o862f~m-a5-1}_9bi>r$4yI7Gs z{llR7k6}q7xg~a|AAwWNTBp;S&%*1KFrZ*44g!TqWffMr>V4{`DlC5bDv#8gA6($&M$&}Cw#=Dm-V~$Sl%U+7+J3oq!IrAX<}5n>|~75Fd`^?h@_nu zosyTXX+ey3^bat%fdgLP+HS94qv==7Sa$x`~jB8R& zrpf!(FuW{Td65CtgHdU3Np}-;2EzaJ^bsX=Jzte!TEhQdvgZ8dXWs%VLK!Wu7@6yu zYg!O1iWl^PRFDf!^ax&AH&FEY|EEovu7iJot@n#ocLd+SF`$B$U zDSIk@m}hIw>Lf!t&qM134rNC<$8(kHOfTo^Q!PeDD1R^2a{pOKP{w8?J&9846{Ul% z-rbD#H1^G3b4qYv&tFX{Hs}a?N$X_Vq-fVLk#-v5^@wMW9tfws9fc>MrrXj>QUM~N z*8C-{NZn{8P0JQcXGkc(JtC1#B=>HogB$I4bR?w4$s-G}cm;v^!Qbi1Pm`W!wEXha zBy4?!UC?KW%(mz&7jy9Q){`-qZ(UeH_-PhFaS)8X{RcBuVrQ2N_A9P#H|Qm@JIkUa zv9pd|0;eH980v5AR!8udDPB1#++aeaGJ4mNDuFa0TU=J0n6X0E0k)zN6q1)Zo&%VGR3^x0$3PJ3 zt?_fFq{3Rak@?Yl@cp1~YcB}7`vLv!QuJ~CaKs7G?;&=qz+n@j@@A=cSVzO-{~o}o z`I|7X#ZY7H+@>Js!$^LKKPllz|B{13NN>y)t1SH!h09ec(lqfW8+{bET{cGNw7tZn zjtm@BcPFH=YbAI##CI|wx9sG8@a3M{VFxYBG~nZ7?6xEC1fx%VIWiu1JP1Mlz%<7B zEX~xs061xw1~~gs$;t*w_%ARO60kTYcP&m z?RHJ{l&$H<-sW8R09{vFJm;JIiYdajT~(J=9P!aFoRXg%#=X~slle9Ru$&_tJAuv? z4D_6dH22&{j78ya*)yzHZQiDWO_>i>BD6$r@2f~SC6}TWb-)r9Eb^@e-MuV@S(MzG zKd3RLGNdpvK0fO|vcw!UMNPL{FxUrb{zWdV3+_ZG@VEvy$*s581dKTX05#uBi|Y~m z26?BrWq}{9pe?4(PKfJO@bDK?u49fzR>Eg=^v;Y&uFM!A`Zk{iJqe$AAvp$`y&G5S zalA2?j*c1l0Kgs{fXGgUTbpPbfOx28mL?_lnbSR4k8k04eAQR)H`$p^2hoJL6V`tm zbBkL&aj<+g4gskx9L#fvTZDEdUxj@f0R)m%u87d1qYuGyF$a-M31L+LCinIJSjdZ? zD>)}==0CSv80n3921r}$dx?BSzwf;u0dk#4VsG+aUyRx_Rx}>)kcs;LJo#y6r*pZO zc*KnrFw{b>%h*<3$75D(V1xT}eVLu=Ajyu?b{l_?;n&jJYi7Q^70X2f*u%+%6HgO< z-v-9RtB=02EtB#|E?zDlhIEyto{Df5ptgK=9#AwIv{)PTL!A_aew{>JAxIV&VjDI5 zh9gmkvcqyvvo0q8a^oOY67PN~z{$cplDc0kOi2Dtw+>R8K?#>O!m`Y59sJXRkub!C z69I>`uolkwMfo6jK2v$aFXS;eSrJnYY>2`CahV>_qZ$x*ylpy;x-g~)Dyad8PCAr1 z3%z6$*W=?nITV=(z673IAO5Vp#TO>;{NFb9wNg8QHVj`MK*7LciuwXz9&7tOR2vsq z2N;vaot+MnAL5$p$ekn^65{^nDbrIEywUuYmOPPq60<{|Y2wzIWdU~_Ws_tktcwaW z7i1PErgg*A*S-R2Sm|JvEz^h*n5Ehr8-)oQM^&|vz(g{>GjZ_E{1Dt750VS^g*t>8u_D(G~=OTJ_65<#~)%vH#2{BXI zy`c>G$#ROY8|gMOz@`VeeuJhhjsC@usTL!ZmTsqyLG)3!twSiOsOQdjz|lcajG$@e zmv#YWwG|5|RSt+lCuXm?=xFj@ad`>il2T7r6L?izj0izx`C zyJ|G<WnpC@6?w$k@a`x@la#-25x3NVkp=z4fGz|Ef>wC- z!#tbw+7O7#8VS6kVRrN;ma#w#z=`yK)8D80-D}qCbGm?;tVa3b%gCrn6K6;rP{1SC zot_d3vUr1{Wk@>It!K^ zy|QqKLPXv`4FDvEhzkOR_HzQ5_hoF6cGL$#yY*PM6tbAjdeJf)L;*LBbGhd7Y7f`; z@K(tCztJIuS$gr98n(1n{!CIv3=AG4XD&}L*8J30{acU_0!Nqqwl;da)VJckKRT$o zA9)HQcm?V86#5wE@s|o9G{e#Bfvi4eqsHJvNi#H}7rQ zNjZhyem@`GUYVPNGDX6wXSC=OA4kF+zO=@rt{0!Ze0wEBuJjShG1#)XvQ7V5q#+;) zxY!3{dwsOFNwDtNAFKP>@6`s+J&1r-7rk^17ZnI|o?`Bcf$8J|6JQ#-p;zg(KAK=fVlneM<#+=vNUVJG*lMK};3 zLNMU^C=de29UvmEb$lNeGyVd6>`AhQn(6Pbp+a%}-slKxJ~YvWgc`(305V=8Cj*dT zI0R4`J^h)`&2dUCKBpKOTICyc>^u#R>{$C{-cZf4x4yjYGHjvyOXUTunH4(Q!Gnv9 zpBvrFVNoNO@~^ zg$M$wP{SadD4Jmb2p=2@CTITws$4r!yQDZlUW2M6dY;fSf48H%?89^g+3fVhZL5XP znkfv4c&8@?K8<6^jMDgOZ3%l`KcY~u-9KGM1O&9O;fgYy)GZtvhhYmUv0#`*kDSdc zbJusJ!@%JuDaYXi_wz+vC;@2i$!Q9P)`Zr0Ve8k_!g@flFi@=O7v4e(riSS{2$0S_ zORThXbT=FN{`ll4k5-kB*5q|=SQGRnC(FPs%PC(hV08^ouz=V+Gny9a64^%$7kWdb zpu;0pTz9oO8B<3dm~y|tz@$d6<7rj?eC%}IuRm1@XnSOZL$f17 z`o-|}Mw;;iRBw@KpTAsFwsp?qb%h3ANK4ic9O_m(CAGzj2*MlC}BjWMH)vBKxSmnWhz_ zA(tFdbwnBMQa+l#lUlq|ophEYw@t8R;~i;JuviV~BTRWkE1N0l$P{T&GQ>bq9ELXZ zzF-l)uwM@8SdbFh?SHP@5#0*5tn&D@47hR;Keb)#_xM<3yn8HHTjV636fS3qOkxUA z=qRNMG5uV%Sz^}eA)y6rNK>n;{>mx(ZVQW5uP|d4B@d1}jOg3QhiP&4{+2P{DRWPS z567O&EFgQp`Ndv3O?DO|GWEmsOJr+lSmCaRj6c)q+2WWV$1xOJa&89qrm`QxfAk|l zIZ9BRjC?5)SxQ;ll*R2;JJ5|rLvaON2Op|UU#am(+9-C|i#-&o`d(`=r}c9vdh-V1 z)2=MH5d(=}lOWKK9buz_`P|u@k^sMOwYsJtAVf-aNGowiNg4qWaYA2w~ z00(9W&2b&P>>_ugazD^<@6}6n4Z0=kYStvUM=EYC9*!`0fXk!FFnAquOBv7oNAxcU zXs_cd1VIHF-6SRVC#yTR$xr*x+vB$kO<(@ae-b6n*P#A$XY3*IsA*aU_p7uLu4M z?mrC2;SP!FG4x*}GV#a%Gq<`AvIWhgftmVlbu}vaMh8>DQKL9-DtZsY6Uo`Nekha8 zLOLDv9NecCzqz{FX!70@h)Eh0Ux=Mv#^3Qi!$p#wJbN_44m#B|5eNr!gd7}uLse;L zr!g~W-PY>4{G=|;DY2x+NCaL(MMgb8A3-uxMD(G>O5J_j1Ki5B}7q>#+6lLvQr9L7|0 z_I;me4ARleZAi&lo5&LO42aX3&GIi^NYXnLphd~%sfu1dN=ig96Js2kv5wCSWXPc+ zJ6tbJqgF>|>LCX2Wwrr@R)M1M@to}~%O38~k|96aZ?)fuyWj%b>y3$o8a)dcp(+?z zKlYbff+k7g2}l$!MQ{oT5J#@Q95=kFR9T!OEdHMNZxD zs>x=bVIWM$Nu8A@RX7zVYz*hB#m0?DnOBSS4e3s(KN>0SX;aN9?DIy#hhA{y;XeIL z>-eYewz<~fL$f>yeRfc$4UlnonQ`;}9TsstaV&fCKDj-};j%uiy|0t;0ht(eVy`2aHkFpYQU@ zbpYh#pm{l;P7^Ou%WG`$_JRpr!Z&rsv~vB}DO&(P|sfevQ|2r*U`upG1(!Ta^q%lp03LuS34cxnbsd>jHoX)T5&(ER327LQ~9@*?A`-4 zH?C&hF$qc=Tg(@8DnKtP=$wHHbSnI29~4>%6FG710O4l56Ao(cOnO*h9K^7+RoW*- zUNk#7{dE&s!V!mIb!3jdYb6a45CG?MiQ_W?Enefi_#f58h>CPjW1w`P4_orb%037J z%#j69RySqb)$)*oGuvKUS{3`*ru#EzuPAfnh;@rIc6Kx`JmXM~s6E|XHHQ4pCHYue zc=OsoSC|q_73K>YvZ`i<7|iyy)3a2^J6TX^9r_+i!150uFCeY~&o;idIXN0b&Ic=P zDTCuzL46^fbEFkW+@oI3VDT+7+TBHU@5c}OW4bve+TxYTd2gWBryq#YNgC!|)yCa6 zgV-NPu%I4D9$h=t+;v`!K#c(B(l~pD{!>Y(|EV?GM$2v%h-rJ=b{)$6(A{GXFtW^5 z?(NsZ;#Ej%YmZD)_n}mh$u^2eAP`z`$++{UJGk}T%BPK z7xCDm0LSLv7gHnldUSyLM`kL(wa60BR1-bO|D)Y25hb!TVJQ-9(|IgIkCw;e5X)9v zHgvnJ%~84!&F#d!Y~OTfIlxMgPZNNG0K|_q1u#C-J<`X=v^hl0SoC|e)nx_{bQaw! zF{-Y2;zT{KOwE~3t9FDUN($uFO*Y(8K5)fs>=Dv8v{9;>vc5h&B|RB%72bc|FI?xo zB)~;p`=`{Si9kBA!jlCkQbPutdy%_cVcJkjT*dlWzrYSR*ETvg*oy1cj64lxD6tAm zD4+galNP9^VO`vxhNJYCk%q$qo9xUK%pwA*>P`g!bw7$INxAP65UL=(x%GZ$aLk^M z3S$!bYd5y;Ip}0cRmQ&<6PR02?v)LRvIyH*#*3AMhKW_&cC7XRXOZJ zPgpm;`Kslqi*1or?!r*#p!{3(^qYhiG!c4=QEac)s-?i5oBgjUN=59r)KbCkdIno8 z=EDrRE01Wd0S2JVG)er#MGb71unjQ zRGOUY-M0@w(EXa#T;%8=6t!2xQU-~az9OT4$KcbvK=~%Ru-NM9CQz)j1mV~qM}1Qn z-v%u0p3F=yXlY9#O?cutXiSbP9J&1NQ?$jdvg9f3TCE0mO`G>*YpV_Oq5-8zz^G(& zqnKYqL%|3c?730n)8xI!xTr1H#5A5c_kIHvAqFyLqN@jKO!W2I-SRb1lqIw0k2 zuG4Bn_JA867dh%l#PBM8^sifnOYnw<^bW#(-o1z$&4tUv!juF*p8)>`x^>mlt58ii zDJah*oL+X)k@UWXA|O9?>ObzyQxUo+AxVjL?YNAse@vB!awQyJCeU243 z8#0U(nj?i;G}i7p!Eg}(0(VE9MDoM~$EwqH&We~$0&8KW20i#2r^lX$o7?NR0(rX4 z&o@E6?9P`*q$w8tEJ@f!U|%KyK2d2*0+N#`z2(hQ`J#~$MzN6WTd&Hi(j&k>krMC) zc%uwLijEqP_q_Pt9AH^0V|xArCd%GT!o4?-OO0_+q`-8l2-R-hD4*VcE_eOlEvSg! z;K|*lEY*u1o@@!5nV{d_l2|$s7>`rz-a&SMexH{z11!LeRqjG=8n_+eQ{n|SQujnc zmU@Xng(G$}70sSJM1;@@SHf8o*?kQYq)_v!Ck?aL`AqypB$?)c}JT z=wfh;pI$~sd#{5VGenx*^~rog&qe>L(6Nwm2J>Ur2=>fy|jZj)V8lW|Bh>gV0KvLfTlThn#fI z=1{>1P60q`Ldn6+S;x%|6+Al=zOGP^_5Yd;1D2%c0{(9~^fH*8wR~B~i6dWmqow?t zJmLp1D07y!S%ShYyt`XqrRtzA(7N8u;_druQk_5OM;3WI%O2PF1oJ@-ZTPuQjV?=; znm2yg49+0%Y=(heY3fb5Y8)YcF`VUmkyHISTM$vexlS3Nc~&2JI6&KqoX8NGSL%NtJL8UrHH_*&L$jzBPJodVO%dNGeEr!Z`>A4dN2TN z?SVU$hT(SaIgsrjQtz(_pYU8NSIr;4?(Mc zj1-OB(?ItzK&z_pYmM77wmGpVHb4okVkORPi#~zNjcm03?FXK!!-N81Erp=CRh>vh z-9Js52r~RqVtDUj+{kIWl2dd|ePn$S3^#AuUEW9t*`_Akev&CLI5)WX%Ih$1Q1KWv zyN9m1%8US6g4`p;NL>m^eobhBk&mShAT`%w=b^=C2Z_YB5SCAsnh9rJd+5@pTYxAJ z3z8_j3yBVTWpXESVxWX={rMZn?j^$F%xDG*eaA?T3w?wyAvL+*KdbxJe0DI4_#ue+ zgHqI}Xwz^U>zXHl|9rvv=>cRQ!>(zXB^}U147)a!XNymgxZAcvpxR(&@FP0}__RFz zNcNf-7;P^ETWHgw7u?M6H5X15d1F1&n z*<{DdPNMEyb!z(thriy|D`8?CnYcH+XK@hOz4VO^;3`pQ6~1FH8HV>do)fRZ-4i;L zdqz#xE((P*9Fb)p7mJ zmGdSXXA-~U#_m@2mWKPL6-RS7S2o4s!4(sy`$T{snR3e}?X;f2>KSLR-|1(s#QU7Y m2P&a>{ApHo{NKxXxxi9Xh>2Xbs6(aDqL&Y&)r90-*vnhMfAi-6 literal 10324 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
  • U~m`h#{00I6`ucXnIIhH8dmc*yk(XIoVo zN^c0&w!mojM@$@6sYFItAwU$eQ^(|WazTIMiAa+${F~AFAaUvd&*7Znm{0re2of+r zJaBZ#urDhf71QPRRb2{ekd*)4ElXy@+lv}Wu?eSvJZZ5fHmn9NR`NWNFS%U&5q$GN zuij^4QbdBX(Y4C!`OWtH38|Xdh`u*8oUggJH+|fSKeYZ2Gwh`S2$YoeC53A?jbh!f zudW`eCNTVCk*i%`XmD1$u}*A8C{mOR2SGiNA0I4Trox#8>96)L3r$VZn+`W*CA(hbS&`X%a z+9y5TIQ1hXMjUSkzgrWUb}tb3L9avAeg1dQmOjX*3KxCHnM=At5WnZxakargW21|95MusG6l|AvFun6TE56u4M?mEh0- z*wX&N&s-i&KV&O{SqeWx=}^-36l+`%%*`cPCWu)zvzXPz-UKZ&pw+0Vl@>BiY}rf7 z&qC|Q?KIUJ$hrDh71$?b9D!~sD{=5C{5v1W31P)l$+nZ1|BSMuK%|eS{v86Te-?@v zq0JH+3X2CW!-|Ha96hw84J>X-r_%_^f15mU;^vArKQ%tgpIkLo0!o}Tnwq!xWFq*& zh0Hl=@vj%sdO#ZCLm9fbJ$j~bjc~*g>RB;g+(6QXMgmW83PU=!FO!MC{#`!D(tT`! zs+`N;A_xd5sApfn0wdbd3y4>l?R5~r(|h~LrZwV3l)tiL-b>n|T?=R%H#oB$&adda z`tLk#coPf$b3x_fIHL0SETr9XB0FT+ZF= zdJBqH#5~j)BfX_D#cqc7RZx*1YGKxg6vf+%a=snDkPx9W|GOue`=FE!2gJ(kR70n9 zdOi8(_(U8H2#mF&SWY zm@+y9ujQYqIb>!94c?MXTlsNMk&r!uw&M?hi`(Zx4F+~-Qn{Bmo{S^jc1C02}1S^evLVSWKd zY&)V1d5rm({Y}&H<(C~aU@oaMT*Meo${@kKeJf4of*qiV8F$5o8RvcaAodUK=S+Kk z9|WVkXH$5{I^P4W2*^wzB_Cifm@iW9B1)RXpE}C~;EJ}8j^H`YtQiLs5@tUV4eql) zwj*-+i>-V;v3DUFlF4%6hWYG(ytJF-hwf?H6F{0TuWM2HXf=;&4OMC=i;?j?d#mTi zz77Gm4*FgF-Lg1_o)4+9+GL#e>dyS1FI~eo1>~v?Mg~RPe;?kTTSu*Zg< zKre#I6#w<_VzU2aH#Ueg&}8#8t3E}ZCC<`Y7uIl;*s zt$i*IkyWvZqL6tfBU0iQnd8N7>~>)3ot}iC=c3 zcT&A_RFV(hBP0>I`em^di8eJ*v>a1SS6m}z{L7VXHfh@V{)pRgu(ry m6u3m8U6|?WdYF8?HYI`>&Jb|rK6=$sErvC=Bmew)(~|$Jt`@@p From 081db3f5d9850ddb351137351d1bb1830daf4546 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 26 Jun 2023 10:45:47 -0700 Subject: [PATCH 732/966] chore(main): release 2.21.0 (#1336) --- packages/google-auth/CHANGELOG.md | 12 ++++++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 00f0f5f2c080..9c516357b076 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,18 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.21.0](https://github.com/googleapis/google-auth-library-python/compare/v2.20.0...v2.21.0) (2023-06-26) + + +### Features + +* Add framework for BYOID metrics headers ([#1332](https://github.com/googleapis/google-auth-library-python/issues/1332)) ([1a8f50c](https://github.com/googleapis/google-auth-library-python/commit/1a8f50c750669b12152b7758339600471c9e3e2a)) + + +### Bug Fixes + +* Pypy unit test build ([#1335](https://github.com/googleapis/google-auth-library-python/issues/1335)) ([33e4263](https://github.com/googleapis/google-auth-library-python/commit/33e4263e34d37db2713bfb3564f57fc10a991ee4)) + ## [2.20.0](https://github.com/googleapis/google-auth-library-python/compare/v2.19.1...v2.20.0) (2023-06-12) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index f24e7e5b499e..0a5e4d6a8e63 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.20.0" +__version__ = "2.21.0" From 2a80ed364791e5017a924f4d96ae96605b12d4af Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 26 Jun 2023 14:06:16 -0400 Subject: [PATCH 733/966] feat: Introduce compatibility with native namespace packages (#1205) --- packages/google-auth/google/__init__.py | 24 ---------------- packages/google-auth/noxfile.py | 2 +- packages/google-auth/setup.py | 7 +++-- packages/google-auth/tests/test_packaging.py | 30 ++++++++++++++++++++ 4 files changed, 35 insertions(+), 28 deletions(-) delete mode 100644 packages/google-auth/google/__init__.py create mode 100644 packages/google-auth/tests/test_packaging.py diff --git a/packages/google-auth/google/__init__.py b/packages/google-auth/google/__init__.py deleted file mode 100644 index 70a7bd995f51..000000000000 --- a/packages/google-auth/google/__init__.py +++ /dev/null @@ -1,24 +0,0 @@ -# Copyright 2016 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Google namespace package.""" - -try: - import pkg_resources - - pkg_resources.declare_namespace(__name__) -except ImportError: - import pkgutil - - __path__ = pkgutil.extend_path(__path__, __name__) # type: ignore diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index 19e162bcc5da..b9d183385f7e 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -82,7 +82,7 @@ def mypy(session): "types-six", "types-mock", ) - session.run("mypy", "google/", "tests/", "tests_async/") + session.run("mypy", "-p", "google", "-p", "tests", "-p", "tests_async") @nox.session(python=["3.6", "3.7", "3.8", "3.9", "3.10", "3.11"]) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 01cf80806272..4a91925dd7d9 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -15,7 +15,7 @@ import io import os -from setuptools import find_packages +from setuptools import find_namespace_packages from setuptools import setup @@ -58,8 +58,9 @@ description="Google Authentication Library", long_description=long_description, url="https://github.com/googleapis/google-auth-library-python", - packages=find_packages(exclude=("tests*", "system_tests*")), - namespace_packages=("google",), + packages=find_namespace_packages( + exclude=("tests*", "system_tests*", "docs*", "samples*") + ), install_requires=DEPENDENCIES, extras_require=extras, python_requires=">=3.6", diff --git a/packages/google-auth/tests/test_packaging.py b/packages/google-auth/tests/test_packaging.py new file mode 100644 index 000000000000..e87b3a21b9be --- /dev/null +++ b/packages/google-auth/tests/test_packaging.py @@ -0,0 +1,30 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import subprocess +import sys + + +def test_namespace_package_compat(tmp_path): + """ + The ``google`` namespace package should not be masked + by the presence of ``google-auth``. + """ + google = tmp_path / "google" + google.mkdir() + google.joinpath("othermod.py").write_text("") + env = dict(os.environ, PYTHONPATH=str(tmp_path)) + cmd = [sys.executable, "-m", "google.othermod"] + subprocess.check_call(cmd, env=env) From 3e6cbcdf79427b38b6a6bc21a5df2349b9e4824d Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Wed, 28 Jun 2023 11:59:58 -0400 Subject: [PATCH 734/966] build(deps): bump cryptography from 39.0.1 to 41.0.0 in /synthtool/gcp/templates/python_library/.kokoro (#1319) Source-Link: https://github.com/googleapis/synthtool/commit/d0f51a0c2a9a6bcca86911eabea9e484baadf64b Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:240b5bcc2bafd450912d2da2be15e62bc6de2cf839823ae4bf94d4f392b451dc Co-authored-by: Owl Bot Co-authored-by: Anthonios Partheniou --- .../google-auth/.github/.OwlBot.lock.yaml | 4 +- packages/google-auth/.kokoro/requirements.txt | 42 +++++++++---------- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/packages/google-auth/.github/.OwlBot.lock.yaml b/packages/google-auth/.github/.OwlBot.lock.yaml index 32b3c486591a..02a4dedced74 100644 --- a/packages/google-auth/.github/.OwlBot.lock.yaml +++ b/packages/google-auth/.github/.OwlBot.lock.yaml @@ -13,5 +13,5 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:9bc5fa3b62b091f60614c08a7fb4fd1d3e1678e326f34dd66ce1eefb5dc3267b -# created: 2023-05-25T14:56:16.294623272Z + digest: sha256:240b5bcc2bafd450912d2da2be15e62bc6de2cf839823ae4bf94d4f392b451dc +# created: 2023-06-03T21:25:37.968717478Z diff --git a/packages/google-auth/.kokoro/requirements.txt b/packages/google-auth/.kokoro/requirements.txt index 3b8d7ee81848..c7929db6d152 100644 --- a/packages/google-auth/.kokoro/requirements.txt +++ b/packages/google-auth/.kokoro/requirements.txt @@ -113,28 +113,26 @@ commonmark==0.9.1 \ --hash=sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60 \ --hash=sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9 # via rich -cryptography==39.0.1 \ - --hash=sha256:0f8da300b5c8af9f98111ffd512910bc792b4c77392a9523624680f7956a99d4 \ - --hash=sha256:35f7c7d015d474f4011e859e93e789c87d21f6f4880ebdc29896a60403328f1f \ - --hash=sha256:5aa67414fcdfa22cf052e640cb5ddc461924a045cacf325cd164e65312d99502 \ - --hash=sha256:5d2d8b87a490bfcd407ed9d49093793d0f75198a35e6eb1a923ce1ee86c62b41 \ - --hash=sha256:6687ef6d0a6497e2b58e7c5b852b53f62142cfa7cd1555795758934da363a965 \ - --hash=sha256:6f8ba7f0328b79f08bdacc3e4e66fb4d7aab0c3584e0bd41328dce5262e26b2e \ - --hash=sha256:706843b48f9a3f9b9911979761c91541e3d90db1ca905fd63fee540a217698bc \ - --hash=sha256:807ce09d4434881ca3a7594733669bd834f5b2c6d5c7e36f8c00f691887042ad \ - --hash=sha256:83e17b26de248c33f3acffb922748151d71827d6021d98c70e6c1a25ddd78505 \ - --hash=sha256:96f1157a7c08b5b189b16b47bc9db2332269d6680a196341bf30046330d15388 \ - --hash=sha256:aec5a6c9864be7df2240c382740fcf3b96928c46604eaa7f3091f58b878c0bb6 \ - --hash=sha256:b0afd054cd42f3d213bf82c629efb1ee5f22eba35bf0eec88ea9ea7304f511a2 \ - --hash=sha256:ced4e447ae29ca194449a3f1ce132ded8fcab06971ef5f618605aacaa612beac \ - --hash=sha256:d1f6198ee6d9148405e49887803907fe8962a23e6c6f83ea7d98f1c0de375695 \ - --hash=sha256:e124352fd3db36a9d4a21c1aa27fd5d051e621845cb87fb851c08f4f75ce8be6 \ - --hash=sha256:e422abdec8b5fa8462aa016786680720d78bdce7a30c652b7fadf83a4ba35336 \ - --hash=sha256:ef8b72fa70b348724ff1218267e7f7375b8de4e8194d1636ee60510aae104cd0 \ - --hash=sha256:f0c64d1bd842ca2633e74a1a28033d139368ad959872533b1bab8c80e8240a0c \ - --hash=sha256:f24077a3b5298a5a06a8e0536e3ea9ec60e4c7ac486755e5fb6e6ea9b3500106 \ - --hash=sha256:fdd188c8a6ef8769f148f88f859884507b954cc64db6b52f66ef199bb9ad660a \ - --hash=sha256:fe913f20024eb2cb2f323e42a64bdf2911bb9738a15dba7d3cce48151034e3a8 +cryptography==41.0.0 \ + --hash=sha256:0ddaee209d1cf1f180f1efa338a68c4621154de0afaef92b89486f5f96047c55 \ + --hash=sha256:14754bcdae909d66ff24b7b5f166d69340ccc6cb15731670435efd5719294895 \ + --hash=sha256:344c6de9f8bda3c425b3a41b319522ba3208551b70c2ae00099c205f0d9fd3be \ + --hash=sha256:34d405ea69a8b34566ba3dfb0521379b210ea5d560fafedf9f800a9a94a41928 \ + --hash=sha256:3680248309d340fda9611498a5319b0193a8dbdb73586a1acf8109d06f25b92d \ + --hash=sha256:3c5ef25d060c80d6d9f7f9892e1d41bb1c79b78ce74805b8cb4aa373cb7d5ec8 \ + --hash=sha256:4ab14d567f7bbe7f1cdff1c53d5324ed4d3fc8bd17c481b395db224fb405c237 \ + --hash=sha256:5c1f7293c31ebc72163a9a0df246f890d65f66b4a40d9ec80081969ba8c78cc9 \ + --hash=sha256:6b71f64beeea341c9b4f963b48ee3b62d62d57ba93eb120e1196b31dc1025e78 \ + --hash=sha256:7d92f0248d38faa411d17f4107fc0bce0c42cae0b0ba5415505df72d751bf62d \ + --hash=sha256:8362565b3835ceacf4dc8f3b56471a2289cf51ac80946f9087e66dc283a810e0 \ + --hash=sha256:84a165379cb9d411d58ed739e4af3396e544eac190805a54ba2e0322feb55c46 \ + --hash=sha256:88ff107f211ea696455ea8d911389f6d2b276aabf3231bf72c8853d22db755c5 \ + --hash=sha256:9f65e842cb02550fac96536edb1d17f24c0a338fd84eaf582be25926e993dde4 \ + --hash=sha256:a4fc68d1c5b951cfb72dfd54702afdbbf0fb7acdc9b7dc4301bbf2225a27714d \ + --hash=sha256:b7f2f5c525a642cecad24ee8670443ba27ac1fab81bba4cc24c7b6b41f2d0c75 \ + --hash=sha256:b846d59a8d5a9ba87e2c3d757ca019fa576793e8758174d3868aecb88d6fc8eb \ + --hash=sha256:bf8fc66012ca857d62f6a347007e166ed59c0bc150cefa49f28376ebe7d992a2 \ + --hash=sha256:f5d0bf9b252f30a31664b6f64432b4730bb7038339bd18b1fafe129cfc2be9be # via # gcp-releasetool # secretstorage From 23513a336d103265d4c503d2a05b4dd64d7a7b96 Mon Sep 17 00:00:00 2001 From: Jin Date: Thu, 29 Jun 2023 11:33:00 -0700 Subject: [PATCH 735/966] chore: update token (#1342) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 15422930ad7575fbd7cff99a9eb08c0ae3a586dc..4e6ffb1c4f617905b05908a436e21ac7f97f4376 100644 GIT binary patch literal 10324 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 literal 10324 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 From 679a6a8e0f805e664368fe34baaf23a453ee0262 Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Thu, 29 Jun 2023 18:54:12 +0000 Subject: [PATCH 736/966] chore: store artifacts in placer [autoapprove] (#1340) Source-Link: https://togithub.com/googleapis/synthtool/commit/cb960373d12d20f8dc38beee2bf884d49627165e Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:2d816f26f728ac8b24248741e7d4c461c09764ef9f7be3684d557c9632e46dbd --- packages/google-auth/.github/.OwlBot.lock.yaml | 4 ++-- packages/google-auth/.kokoro/release/common.cfg | 9 +++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/.github/.OwlBot.lock.yaml b/packages/google-auth/.github/.OwlBot.lock.yaml index 02a4dedced74..98994f474104 100644 --- a/packages/google-auth/.github/.OwlBot.lock.yaml +++ b/packages/google-auth/.github/.OwlBot.lock.yaml @@ -13,5 +13,5 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:240b5bcc2bafd450912d2da2be15e62bc6de2cf839823ae4bf94d4f392b451dc -# created: 2023-06-03T21:25:37.968717478Z + digest: sha256:2d816f26f728ac8b24248741e7d4c461c09764ef9f7be3684d557c9632e46dbd +# created: 2023-06-28T17:03:33.371210701Z diff --git a/packages/google-auth/.kokoro/release/common.cfg b/packages/google-auth/.kokoro/release/common.cfg index 9ec8d102fa51..f1d7175f620a 100644 --- a/packages/google-auth/.kokoro/release/common.cfg +++ b/packages/google-auth/.kokoro/release/common.cfg @@ -38,3 +38,12 @@ env_vars: { key: "SECRET_MANAGER_KEYS" value: "releasetool-publish-reporter-app,releasetool-publish-reporter-googleapis-installation,releasetool-publish-reporter-pem" } + +# Store the packages we uploaded to PyPI. That way, we have a record of exactly +# what we published, which we can use to generate SBOMs and attestations. +action { + define_artifacts { + regex: "github/google-auth-library-python/**/*.tar.gz" + strip_prefix: "github/google-auth-library-python" + } +} From 4211b236e61067b542dc893308a05a4646667d8f Mon Sep 17 00:00:00 2001 From: Jin Date: Thu, 29 Jun 2023 13:52:39 -0700 Subject: [PATCH 737/966] feat: adding meta header for trust boundary (#1334) * feat: adding meta header for trust boundary * fixing lint * adding trust_boundary parameter for 3PI init * change inject header to kebab case and the value to a reasonable value --- .../google-auth/google/auth/credentials.py | 5 +++ .../google/auth/external_account.py | 3 ++ .../google-auth/google/oauth2/credentials.py | 6 +++ .../google/oauth2/service_account.py | 4 ++ packages/google-auth/tests/test_aws.py | 2 + .../google-auth/tests/test_credentials.py | 27 ++++++++++++ .../tests/test_external_account.py | 41 +++++++++++++++---- .../google-auth/tests/test_identity_pool.py | 1 + 8 files changed, 82 insertions(+), 7 deletions(-) diff --git a/packages/google-auth/google/auth/credentials.py b/packages/google-auth/google/auth/credentials.py index 134c182d31df..c27721dbbeb5 100644 --- a/packages/google-auth/google/auth/credentials.py +++ b/packages/google-auth/google/auth/credentials.py @@ -54,6 +54,9 @@ def __init__(self): If this is None, the token is assumed to never expire.""" self._quota_project_id = None """Optional[str]: Project to use for quota and billing purposes.""" + self._trust_boundary = None + """Optional[str]: Encoded string representation of credentials trust + boundary.""" @property def expired(self): @@ -127,6 +130,8 @@ def apply(self, headers, token=None): headers["authorization"] = "Bearer {}".format( _helpers.from_bytes(token or self.token) ) + if self._trust_boundary is not None: + headers["x-identity-trust-boundary"] = self._trust_boundary if self.quota_project_id: headers["x-goog-user-project"] = self.quota_project_id diff --git a/packages/google-auth/google/auth/external_account.py b/packages/google-auth/google/auth/external_account.py index 367e25c54290..fc311ac2d8b7 100644 --- a/packages/google-auth/google/auth/external_account.py +++ b/packages/google-auth/google/auth/external_account.py @@ -86,6 +86,7 @@ def __init__( default_scopes=None, workforce_pool_user_project=None, universe_domain=_DEFAULT_UNIVERSE_DOMAIN, + trust_boundary=None, ): """Instantiates an external account credentials object. @@ -111,6 +112,7 @@ def __init__( billing/quota. universe_domain (str): The universe domain. The default universe domain is googleapis.com. + trust_boundary (str): String representation of trust boundary meta. Raises: google.auth.exceptions.RefreshError: If the generateAccessToken endpoint returned an error. @@ -132,6 +134,7 @@ def __init__( self._default_scopes = default_scopes self._workforce_pool_user_project = workforce_pool_user_project self._universe_domain = universe_domain or _DEFAULT_UNIVERSE_DOMAIN + self._trust_boundary = "0" # expose a placeholder trust boundary value. if self._client_id: self._client_auth = utils.ClientAuthentication( diff --git a/packages/google-auth/google/oauth2/credentials.py b/packages/google-auth/google/oauth2/credentials.py index dd54edfb3da6..76ac878ccc00 100644 --- a/packages/google-auth/google/oauth2/credentials.py +++ b/packages/google-auth/google/oauth2/credentials.py @@ -84,6 +84,7 @@ def __init__( refresh_handler=None, enable_reauth_refresh=False, granted_scopes=None, + trust_boundary=None, ): """ Args: @@ -141,6 +142,7 @@ def __init__( self._rapt_token = rapt_token self.refresh_handler = refresh_handler self._enable_reauth_refresh = enable_reauth_refresh + self._trust_boundary = trust_boundary def __getstate__(self): """A __getstate__ method must exist for the __setstate__ to be called @@ -171,6 +173,7 @@ def __setstate__(self, d): self._quota_project_id = d.get("_quota_project_id") self._rapt_token = d.get("_rapt_token") self._enable_reauth_refresh = d.get("_enable_reauth_refresh") + self._trust_boundary = d.get("_trust_boundary") # The refresh_handler setter should be used to repopulate this. self._refresh_handler = None @@ -268,6 +271,7 @@ def with_quota_project(self, quota_project_id): quota_project_id=quota_project_id, rapt_token=self.rapt_token, enable_reauth_refresh=self._enable_reauth_refresh, + trust_boundary=self._trust_boundary, ) @_helpers.copy_docstring(credentials.CredentialsWithTokenUri) @@ -286,6 +290,7 @@ def with_token_uri(self, token_uri): quota_project_id=self.quota_project_id, rapt_token=self.rapt_token, enable_reauth_refresh=self._enable_reauth_refresh, + trust_boundary=self._trust_boundary, ) def _metric_header_for_usage(self): @@ -421,6 +426,7 @@ def from_authorized_user_info(cls, info, scopes=None): quota_project_id=info.get("quota_project_id"), # may not exist expiry=expiry, rapt_token=info.get("rapt_token"), # may not exist + trust_boundary=info.get("trust_boundary"), # may not exist ) @classmethod diff --git a/packages/google-auth/google/oauth2/service_account.py b/packages/google-auth/google/oauth2/service_account.py index c80627398d12..e08899f8e539 100644 --- a/packages/google-auth/google/oauth2/service_account.py +++ b/packages/google-auth/google/oauth2/service_account.py @@ -140,6 +140,7 @@ def __init__( additional_claims=None, always_use_jwt_access=False, universe_domain=_DEFAULT_UNIVERSE_DOMAIN, + trust_boundary=None, ): """ Args: @@ -163,6 +164,7 @@ def __init__( universe_domain (str): The universe domain. The default universe domain is googleapis.com. For default value self signed jwt is used for token refresh. + trust_boundary (str): String representation of trust boundary meta. .. note:: Typically one of the helper constructors :meth:`from_service_account_file` or @@ -194,6 +196,7 @@ def __init__( self._additional_claims = additional_claims else: self._additional_claims = {} + self._trust_boundary = "0" @classmethod def _from_signer_and_info(cls, signer, info, **kwargs): @@ -217,6 +220,7 @@ def _from_signer_and_info(cls, signer, info, **kwargs): token_uri=info["token_uri"], project_id=info.get("project_id"), universe_domain=info.get("universe_domain", _DEFAULT_UNIVERSE_DOMAIN), + trust_boundary=info.get("trust_boundary"), **kwargs ) diff --git a/packages/google-auth/tests/test_aws.py b/packages/google-auth/tests/test_aws.py index 9a1c7fd6487d..1c8c5d41a7a3 100644 --- a/packages/google-auth/tests/test_aws.py +++ b/packages/google-auth/tests/test_aws.py @@ -1969,6 +1969,7 @@ def test_refresh_success_with_impersonation_ignore_default_scopes( "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]), "x-goog-user-project": QUOTA_PROJECT_ID, "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, + "x-identity-trust-boundary": "0", } impersonation_request_data = { "delegates": None, @@ -2065,6 +2066,7 @@ def test_refresh_success_with_impersonation_use_default_scopes( "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]), "x-goog-user-project": QUOTA_PROJECT_ID, "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, + "x-identity-trust-boundary": "0", } impersonation_request_data = { "delegates": None, diff --git a/packages/google-auth/tests/test_credentials.py b/packages/google-auth/tests/test_credentials.py index d1f391806700..594c3e58a5fc 100644 --- a/packages/google-auth/tests/test_credentials.py +++ b/packages/google-auth/tests/test_credentials.py @@ -80,6 +80,7 @@ def test_before_request(): assert credentials.valid assert credentials.token == "token" assert headers["authorization"] == "Bearer token" + assert "x-identity-trust-boundary" not in headers request = "token2" headers = {} @@ -89,6 +90,32 @@ def test_before_request(): assert credentials.valid assert credentials.token == "token" assert headers["authorization"] == "Bearer token" + assert "x-identity-trust-boundary" not in headers + + +def test_before_request_with_trust_boundary(): + DUMMY_BOUNDARY = "00110101" + credentials = CredentialsImpl() + credentials._trust_boundary = DUMMY_BOUNDARY + request = "token" + headers = {} + + # First call should call refresh, setting the token. + credentials.before_request(request, "http://example.com", "GET", headers) + assert credentials.valid + assert credentials.token == "token" + assert headers["authorization"] == "Bearer token" + assert headers["x-identity-trust-boundary"] == DUMMY_BOUNDARY + + request = "token2" + headers = {} + + # Second call shouldn't call refresh. + credentials.before_request(request, "http://example.com", "GET", headers) + assert credentials.valid + assert credentials.token == "token" + assert headers["authorization"] == "Bearer token" + assert headers["x-identity-trust-boundary"] == DUMMY_BOUNDARY def test_before_request_metrics(): diff --git a/packages/google-auth/tests/test_external_account.py b/packages/google-auth/tests/test_external_account.py index 9ef61340c862..f05a5a11ac21 100644 --- a/packages/google-auth/tests/test_external_account.py +++ b/packages/google-auth/tests/test_external_account.py @@ -825,6 +825,7 @@ def test_refresh_impersonation_without_client_auth_success( "Content-Type": "application/json", "authorization": "Bearer {}".format(token_response["access_token"]), "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, + "x-identity-trust-boundary": "0", } impersonation_request_data = { "delegates": None, @@ -906,6 +907,7 @@ def test_refresh_workforce_impersonation_without_client_auth_success( "Content-Type": "application/json", "authorization": "Bearer {}".format(token_response["access_token"]), "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, + "x-identity-trust-boundary": "0", } impersonation_request_data = { "delegates": None, @@ -1124,6 +1126,7 @@ def test_refresh_impersonation_with_client_auth_success_ignore_default_scopes( "Content-Type": "application/json", "authorization": "Bearer {}".format(token_response["access_token"]), "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, + "x-identity-trust-boundary": "0", } impersonation_request_data = { "delegates": None, @@ -1207,6 +1210,7 @@ def test_refresh_impersonation_with_client_auth_success_use_default_scopes( "Content-Type": "application/json", "authorization": "Bearer {}".format(token_response["access_token"]), "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, + "x-identity-trust-boundary": "0", } impersonation_request_data = { "delegates": None, @@ -1261,7 +1265,8 @@ def test_apply_without_quota_project_id(self): credentials.apply(headers) assert headers == { - "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]) + "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]), + "x-identity-trust-boundary": "0", } def test_apply_workforce_without_quota_project_id(self): @@ -1277,7 +1282,8 @@ def test_apply_workforce_without_quota_project_id(self): credentials.apply(headers) assert headers == { - "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]) + "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]), + "x-identity-trust-boundary": "0", } def test_apply_impersonation_without_quota_project_id(self): @@ -1308,7 +1314,8 @@ def test_apply_impersonation_without_quota_project_id(self): credentials.apply(headers) assert headers == { - "authorization": "Bearer {}".format(impersonation_response["accessToken"]) + "authorization": "Bearer {}".format(impersonation_response["accessToken"]), + "x-identity-trust-boundary": "0", } def test_apply_with_quota_project_id(self): @@ -1325,6 +1332,7 @@ def test_apply_with_quota_project_id(self): "other": "header-value", "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]), "x-goog-user-project": self.QUOTA_PROJECT_ID, + "x-identity-trust-boundary": "0", } def test_apply_impersonation_with_quota_project_id(self): @@ -1359,6 +1367,7 @@ def test_apply_impersonation_with_quota_project_id(self): "other": "header-value", "authorization": "Bearer {}".format(impersonation_response["accessToken"]), "x-goog-user-project": self.QUOTA_PROJECT_ID, + "x-identity-trust-boundary": "0", } def test_before_request(self): @@ -1374,6 +1383,7 @@ def test_before_request(self): assert headers == { "other": "header-value", "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]), + "x-identity-trust-boundary": "0", } # Second call shouldn't call refresh. @@ -1382,6 +1392,7 @@ def test_before_request(self): assert headers == { "other": "header-value", "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]), + "x-identity-trust-boundary": "0", } def test_before_request_workforce(self): @@ -1399,6 +1410,7 @@ def test_before_request_workforce(self): assert headers == { "other": "header-value", "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]), + "x-identity-trust-boundary": "0", } # Second call shouldn't call refresh. @@ -1407,6 +1419,7 @@ def test_before_request_workforce(self): assert headers == { "other": "header-value", "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]), + "x-identity-trust-boundary": "0", } def test_before_request_impersonation(self): @@ -1437,6 +1450,7 @@ def test_before_request_impersonation(self): assert headers == { "other": "header-value", "authorization": "Bearer {}".format(impersonation_response["accessToken"]), + "x-identity-trust-boundary": "0", } # Second call shouldn't call refresh. @@ -1445,6 +1459,7 @@ def test_before_request_impersonation(self): assert headers == { "other": "header-value", "authorization": "Bearer {}".format(impersonation_response["accessToken"]), + "x-identity-trust-boundary": "0", } @mock.patch("google.auth._helpers.utcnow") @@ -1470,7 +1485,10 @@ def test_before_request_expired(self, utcnow): credentials.before_request(request, "POST", "https://example.com/api", headers) # Cached token should be used. - assert headers == {"authorization": "Bearer token"} + assert headers == { + "authorization": "Bearer token", + "x-identity-trust-boundary": "0", + } # Next call should simulate 1 second passed. utcnow.return_value = datetime.datetime.min + datetime.timedelta(seconds=1) @@ -1482,7 +1500,8 @@ def test_before_request_expired(self, utcnow): # New token should be retrieved. assert headers == { - "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]) + "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]), + "x-identity-trust-boundary": "0", } @mock.patch("google.auth._helpers.utcnow") @@ -1523,7 +1542,10 @@ def test_before_request_impersonation_expired(self, utcnow): credentials.before_request(request, "POST", "https://example.com/api", headers) # Cached token should be used. - assert headers == {"authorization": "Bearer token"} + assert headers == { + "authorization": "Bearer token", + "x-identity-trust-boundary": "0", + } # Next call should simulate 1 second passed. This will trigger the expiration # threshold. @@ -1536,7 +1558,8 @@ def test_before_request_impersonation_expired(self, utcnow): # New token should be retrieved. assert headers == { - "authorization": "Bearer {}".format(impersonation_response["accessToken"]) + "authorization": "Bearer {}".format(impersonation_response["accessToken"]), + "x-identity-trust-boundary": "0", } @pytest.mark.parametrize( @@ -1635,6 +1658,7 @@ def test_get_project_id_cloud_resource_manager_success( "x-goog-user-project": self.QUOTA_PROJECT_ID, "authorization": "Bearer {}".format(token_response["access_token"]), "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, + "x-identity-trust-boundary": "0", } impersonation_request_data = { "delegates": None, @@ -1688,6 +1712,7 @@ def test_get_project_id_cloud_resource_manager_success( "authorization": "Bearer {}".format( impersonation_response["accessToken"] ), + "x-identity-trust-boundary": "0", }, ) @@ -1759,6 +1784,7 @@ def test_workforce_pool_get_project_id_cloud_resource_manager_success( "authorization": "Bearer {}".format( self.SUCCESS_RESPONSE["access_token"] ), + "x-identity-trust-boundary": "0", }, ) @@ -1808,6 +1834,7 @@ def test_refresh_impersonation_with_lifetime( "Content-Type": "application/json", "authorization": "Bearer {}".format(token_response["access_token"]), "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, + "x-identity-trust-boundary": "0", } impersonation_request_data = { "delegates": None, diff --git a/packages/google-auth/tests/test_identity_pool.py b/packages/google-auth/tests/test_identity_pool.py index 8228e89e641a..7262e644c22a 100644 --- a/packages/google-auth/tests/test_identity_pool.py +++ b/packages/google-auth/tests/test_identity_pool.py @@ -319,6 +319,7 @@ def assert_underlying_credentials_refresh( "Content-Type": "application/json", "authorization": "Bearer {}".format(token_response["access_token"]), "x-goog-api-client": metrics_header_value, + "x-identity-trust-boundary": "0", } impersonation_request_data = { "delegates": None, From 399395a41ba6720d076c595cabe9baff174f3080 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Thu, 6 Jul 2023 15:15:26 -0700 Subject: [PATCH 738/966] fix: deprecate UserAccessTokenCredentials (#1344) --- .../google-auth/google/oauth2/credentials.py | 8 ++++ .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../tests/oauth2/test_credentials.py | 44 +++++++++++------- 3 files changed, 36 insertions(+), 16 deletions(-) diff --git a/packages/google-auth/google/oauth2/credentials.py b/packages/google-auth/google/oauth2/credentials.py index 76ac878ccc00..5c40ab097d65 100644 --- a/packages/google-auth/google/oauth2/credentials.py +++ b/packages/google-auth/google/oauth2/credentials.py @@ -35,6 +35,7 @@ import io import json import logging +import warnings import six @@ -498,6 +499,13 @@ class UserAccessTokenCredentials(credentials.CredentialsWithQuotaProject): """ def __init__(self, account=None, quota_project_id=None): + warnings.warn( + "UserAccessTokenCredentials is deprecated, please use " + "google.oauth2.credentials.Credentials instead. To use " + "that credential type, simply run " + "`gcloud auth application-default login` and let the " + "client libraries pick up the application default credentials." + ) super(UserAccessTokenCredentials, self).__init__() self._account = account self._quota_project_id = quota_project_id diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 4e6ffb1c4f617905b05908a436e21ac7f97f4376..0c81b08f517c8f84b4db29fb33b247d768acd7ab 100644 GIT binary patch literal 10324 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 literal 10324 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 diff --git a/packages/google-auth/tests/oauth2/test_credentials.py b/packages/google-auth/tests/oauth2/test_credentials.py index feb7e4311882..d265d22ed230 100644 --- a/packages/google-auth/tests/oauth2/test_credentials.py +++ b/packages/google-auth/tests/oauth2/test_credentials.py @@ -951,25 +951,34 @@ def test_unpickle_old_credentials_pickle(self): class TestUserAccessTokenCredentials(object): def test_instance(self): - cred = credentials.UserAccessTokenCredentials() - assert cred._account is None + with pytest.warns( + UserWarning, match="UserAccessTokenCredentials is deprecated" + ): + cred = credentials.UserAccessTokenCredentials() + assert cred._account is None - cred = cred.with_account("account") - assert cred._account == "account" + cred = cred.with_account("account") + assert cred._account == "account" @mock.patch("google.auth._cloud_sdk.get_auth_access_token", autospec=True) def test_refresh(self, get_auth_access_token): - get_auth_access_token.return_value = "access_token" - cred = credentials.UserAccessTokenCredentials() - cred.refresh(None) - assert cred.token == "access_token" + with pytest.warns( + UserWarning, match="UserAccessTokenCredentials is deprecated" + ): + get_auth_access_token.return_value = "access_token" + cred = credentials.UserAccessTokenCredentials() + cred.refresh(None) + assert cred.token == "access_token" def test_with_quota_project(self): - cred = credentials.UserAccessTokenCredentials() - quota_project_cred = cred.with_quota_project("project-foo") + with pytest.warns( + UserWarning, match="UserAccessTokenCredentials is deprecated" + ): + cred = credentials.UserAccessTokenCredentials() + quota_project_cred = cred.with_quota_project("project-foo") - assert quota_project_cred._quota_project_id == "project-foo" - assert quota_project_cred._account == cred._account + assert quota_project_cred._quota_project_id == "project-foo" + assert quota_project_cred._account == cred._account @mock.patch( "google.oauth2.credentials.UserAccessTokenCredentials.apply", autospec=True @@ -978,7 +987,10 @@ def test_with_quota_project(self): "google.oauth2.credentials.UserAccessTokenCredentials.refresh", autospec=True ) def test_before_request(self, refresh, apply): - cred = credentials.UserAccessTokenCredentials() - cred.before_request(mock.Mock(), "GET", "https://example.com", {}) - refresh.assert_called() - apply.assert_called() + with pytest.warns( + UserWarning, match="UserAccessTokenCredentials is deprecated" + ): + cred = credentials.UserAccessTokenCredentials() + cred.before_request(mock.Mock(), "GET", "https://example.com", {}) + refresh.assert_called() + apply.assert_called() From cd4859656f0b526b71c3b8f8fe5efb7319b781d9 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 11 Jul 2023 19:19:21 +0200 Subject: [PATCH 739/966] chore(main): release 2.22.0 (#1338) --- packages/google-auth/CHANGELOG.md | 13 +++++++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 9c516357b076..62b97cba8036 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,19 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.22.0](https://github.com/googleapis/google-auth-library-python/compare/v2.21.0...v2.22.0) (2023-07-06) + + +### Features + +* Adding meta header for trust boundary ([#1334](https://github.com/googleapis/google-auth-library-python/issues/1334)) ([908c8d1](https://github.com/googleapis/google-auth-library-python/commit/908c8d1f3dd0053421a03a97d450cea7b87aa36d)) +* Introduce compatibility with native namespace packages ([#1205](https://github.com/googleapis/google-auth-library-python/issues/1205)) ([2f75922](https://github.com/googleapis/google-auth-library-python/commit/2f7592259ebcbcfe496dd3d1803d5a4e16aa0d20)) + + +### Bug Fixes + +* Deprecate UserAccessTokenCredentials ([#1344](https://github.com/googleapis/google-auth-library-python/issues/1344)) ([5f97fe9](https://github.com/googleapis/google-auth-library-python/commit/5f97fe9ddb164697d5b75aa539893f54721dcc92)) + ## [2.21.0](https://github.com/googleapis/google-auth-library-python/compare/v2.20.0...v2.21.0) (2023-06-26) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 0a5e4d6a8e63..1e886a1122b9 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.21.0" +__version__ = "2.22.0" From d0ea656ab91e40716edcd0c2248f97ff07f1d8f2 Mon Sep 17 00:00:00 2001 From: Carl Lundin <108372512+clundin25@users.noreply.github.com> Date: Thu, 13 Jul 2023 06:44:12 +0000 Subject: [PATCH 740/966] chore: Remove six. (#1346) Based on https://togithub.com/googleapis/google-auth-library-python/pull/778 --- .../google-auth/google/auth/_cloud_sdk.py | 4 +-- .../google/auth/_credentials_async.py | 11 ++----- packages/google-auth/google/auth/_default.py | 12 ++++---- .../google-auth/google/auth/_default_async.py | 8 ++---- .../google/auth/_exponential_backoff.py | 4 +-- packages/google-auth/google/auth/_helpers.py | 17 ++++------- .../google-auth/google/auth/_oauth2client.py | 6 ++-- .../google/auth/_service_account_info.py | 4 +-- packages/google-auth/google/auth/aws.py | 7 ++--- .../google/auth/compute_engine/_metadata.py | 10 +++---- .../google/auth/compute_engine/credentials.py | 6 ++-- .../google-auth/google/auth/credentials.py | 11 ++----- .../google-auth/google/auth/crypt/__init__.py | 4 +-- .../google/auth/crypt/_python_rsa.py | 9 +++--- .../google-auth/google/auth/crypt/base.py | 11 ++----- .../google-auth/google/auth/downscoped.py | 12 ++++---- .../google/auth/external_account.py | 4 +-- packages/google-auth/google/auth/iam.py | 3 +- .../google/auth/impersonated_credentials.py | 6 ++-- packages/google-auth/google/auth/jwt.py | 27 +++++++----------- .../google/auth/transport/__init__.py | 14 +++------ .../auth/transport/_aiohttp_requests.py | 5 ++-- .../auth/transport/_custom_tls_signer.py | 3 +- .../google/auth/transport/_http_client.py | 8 ++---- .../google/auth/transport/_mtls_helper.py | 6 ++-- .../google-auth/google/auth/transport/grpc.py | 16 ++++------- .../google-auth/google/auth/transport/mtls.py | 6 ++-- .../google/auth/transport/requests.py | 19 ++++-------- .../google/auth/transport/urllib3.py | 19 +++++------- packages/google-auth/google/oauth2/_client.py | 20 ++++++------- .../google/oauth2/_client_async.py | 10 +++---- .../google/oauth2/_id_token_async.py | 6 ++-- .../google/oauth2/_reauth_async.py | 2 -- .../google-auth/google/oauth2/challenges.py | 5 +--- .../google-auth/google/oauth2/credentials.py | 8 ++---- .../google-auth/google/oauth2/id_token.py | 6 ++-- packages/google-auth/google/oauth2/reauth.py | 2 -- packages/google-auth/google/oauth2/sts.py | 5 ++-- packages/google-auth/google/oauth2/utils.py | 5 +--- packages/google-auth/noxfile.py | 1 - packages/google-auth/setup.py | 1 - packages/google-auth/system_tests/noxfile.py | 1 + .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../test_external_accounts.py | 7 +++-- .../tests/compute_engine/test__metadata.py | 16 +++++------ .../tests/crypt/test__python_rsa.py | 4 +-- .../google-auth/tests/oauth2/test__client.py | 7 ++--- .../tests/oauth2/test_gdch_credentials.py | 3 +- .../tests/oauth2/test_service_account.py | 3 +- packages/google-auth/tests/oauth2/test_sts.py | 4 +-- packages/google-auth/tests/test__helpers.py | 2 +- .../google-auth/tests/test__oauth2client.py | 8 +++--- .../tests/test__service_account_info.py | 3 +- packages/google-auth/tests/test_aws.py | 4 +-- packages/google-auth/tests/test_downscoped.py | 4 +-- .../tests/test_external_account.py | 4 +-- .../test_external_account_authorized_user.py | 2 +- packages/google-auth/tests/test_iam.py | 2 +- .../google-auth/tests/test_identity_pool.py | 4 +-- .../tests/test_impersonated_credentials.py | 5 +--- packages/google-auth/tests/test_pluggable.py | 8 ------ .../google-auth/tests/transport/compliance.py | 2 +- .../tests/transport/test_requests.py | 2 +- .../tests/transport/test_urllib3.py | 2 +- .../tests_async/oauth2/test__client_async.py | 7 ++--- .../tests_async/transport/async_compliance.py | 2 +- 66 files changed, 163 insertions(+), 286 deletions(-) diff --git a/packages/google-auth/google/auth/_cloud_sdk.py b/packages/google-auth/google/auth/_cloud_sdk.py index 36c5b015802f..a94411949bdf 100644 --- a/packages/google-auth/google/auth/_cloud_sdk.py +++ b/packages/google-auth/google/auth/_cloud_sdk.py @@ -17,8 +17,6 @@ import os import subprocess -import six - from google.auth import _helpers from google.auth import environment_vars from google.auth import exceptions @@ -152,4 +150,4 @@ def get_auth_access_token(account=None): new_exc = exceptions.UserAccessTokenError( "Failed to obtain access token", caught_exc ) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc diff --git a/packages/google-auth/google/auth/_credentials_async.py b/packages/google-auth/google/auth/_credentials_async.py index d4d4e2c0e45f..760758d851b0 100644 --- a/packages/google-auth/google/auth/_credentials_async.py +++ b/packages/google-auth/google/auth/_credentials_async.py @@ -18,13 +18,10 @@ import abc import inspect -import six - from google.auth import credentials -@six.add_metaclass(abc.ABCMeta) -class Credentials(credentials.Credentials): +class Credentials(credentials.Credentials, metaclass=abc.ABCMeta): """Async inherited credentials class from google.auth.credentials. The added functionality is the before_request call which requires async/await syntax. @@ -84,8 +81,7 @@ class AnonymousCredentials(credentials.AnonymousCredentials, Credentials): """ -@six.add_metaclass(abc.ABCMeta) -class ReadOnlyScoped(credentials.ReadOnlyScoped): +class ReadOnlyScoped(credentials.ReadOnlyScoped, metaclass=abc.ABCMeta): """Interface for credentials whose scopes can be queried. OAuth 2.0-based credentials allow limiting access using scopes as described @@ -171,6 +167,5 @@ def with_scopes_if_required(credentials, scopes): return credentials -@six.add_metaclass(abc.ABCMeta) -class Signing(credentials.Signing): +class Signing(credentials.Signing, metaclass=abc.ABCMeta): """Interface for credentials that can cryptographically sign messages.""" diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index 1ae26b4eb97e..f77fa180874a 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -23,8 +23,6 @@ import os import warnings -import six - from google.auth import environment_vars from google.auth import exceptions import google.auth.transport._http_client @@ -124,7 +122,7 @@ def load_credentials_from_file( new_exc = exceptions.DefaultCredentialsError( "File {} is not a valid json file.".format(filename), caught_exc ) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc return _load_credentials_from_info( filename, info, scopes, default_scopes, quota_project_id, request ) @@ -440,7 +438,7 @@ def _get_authorized_user_credentials(filename, info, scopes=None): except ValueError as caught_exc: msg = "Failed to load authorized user credentials from {}".format(filename) new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc return credentials, None @@ -454,7 +452,7 @@ def _get_service_account_credentials(filename, info, scopes=None, default_scopes except ValueError as caught_exc: msg = "Failed to load service account credentials from {}".format(filename) new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc return credentials, info.get("project_id") @@ -500,7 +498,7 @@ def _get_impersonated_service_account_credentials(filename, info, scopes): filename ) new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc return credentials, None @@ -514,7 +512,7 @@ def _get_gdch_service_account_credentials(filename, info): except ValueError as caught_exc: msg = "Failed to load GDCH service account credentials from {}".format(filename) new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc return credentials, info.get("project") diff --git a/packages/google-auth/google/auth/_default_async.py b/packages/google-auth/google/auth/_default_async.py index 93a570c77f95..2e53e2088759 100644 --- a/packages/google-auth/google/auth/_default_async.py +++ b/packages/google-auth/google/auth/_default_async.py @@ -21,8 +21,6 @@ import json import os -import six - from google.auth import _default from google.auth import environment_vars from google.auth import exceptions @@ -63,7 +61,7 @@ def load_credentials_from_file(filename, scopes=None, quota_project_id=None): new_exc = exceptions.DefaultCredentialsError( "File {} is not a valid json file.".format(filename), caught_exc ) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc # The type key should indicate that the file is either a service account # credentials file or an authorized user credentials file. @@ -79,7 +77,7 @@ def load_credentials_from_file(filename, scopes=None, quota_project_id=None): except ValueError as caught_exc: msg = "Failed to load authorized user credentials from {}".format(filename) new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc if quota_project_id: credentials = credentials.with_quota_project(quota_project_id) if not credentials.quota_project_id: @@ -96,7 +94,7 @@ def load_credentials_from_file(filename, scopes=None, quota_project_id=None): except ValueError as caught_exc: msg = "Failed to load service account credentials from {}".format(filename) new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc return credentials, info.get("project_id") else: diff --git a/packages/google-auth/google/auth/_exponential_backoff.py b/packages/google-auth/google/auth/_exponential_backoff.py index b5801bec9d40..0dd621a9492a 100644 --- a/packages/google-auth/google/auth/_exponential_backoff.py +++ b/packages/google-auth/google/auth/_exponential_backoff.py @@ -15,8 +15,6 @@ import random import time -import six - # The default amount of retry attempts _DEFAULT_RETRY_TOTAL_ATTEMPTS = 3 @@ -38,7 +36,7 @@ """ -class ExponentialBackoff(six.Iterator): +class ExponentialBackoff: """An exponential backoff iterator. This can be used in a for loop to perform requests with exponential backoff. diff --git a/packages/google-auth/google/auth/_helpers.py b/packages/google-auth/google/auth/_helpers.py index 30fbafb64739..ad2c095f28d3 100644 --- a/packages/google-auth/google/auth/_helpers.py +++ b/packages/google-auth/google/auth/_helpers.py @@ -18,9 +18,7 @@ import calendar import datetime import sys - -import six -from six.moves import urllib +import urllib from google.auth import exceptions @@ -89,9 +87,6 @@ def datetime_to_secs(value): def to_bytes(value, encoding="utf-8"): """Converts a string value to bytes, if necessary. - Unfortunately, ``six.b`` is insufficient for this task since in - Python 2 because it does not modify ``unicode`` objects. - Args: value (Union[str, bytes]): The value to be converted. encoding (str): The encoding to use to convert unicode to bytes. @@ -104,8 +99,8 @@ def to_bytes(value, encoding="utf-8"): Raises: google.auth.exceptions.InvalidValue: If the value could not be converted to bytes. """ - result = value.encode(encoding) if isinstance(value, six.text_type) else value - if isinstance(result, six.binary_type): + result = value.encode(encoding) if isinstance(value, str) else value + if isinstance(result, bytes): return result else: raise exceptions.InvalidValue( @@ -126,8 +121,8 @@ def from_bytes(value): Raises: google.auth.exceptions.InvalidValue: If the value could not be converted to unicode. """ - result = value.decode("utf-8") if isinstance(value, six.binary_type) else value - if isinstance(result, six.text_type): + result = value.decode("utf-8") if isinstance(value, bytes) else value + if isinstance(result, str): return result else: raise exceptions.InvalidValue( @@ -171,7 +166,7 @@ def update_query(url, params, remove=None): query_params.update(params) # Remove any values specified in remove. query_params = { - key: value for key, value in six.iteritems(query_params) if key not in remove + key: value for key, value in query_params.items() if key not in remove } # Re-encoded the query string. new_query = urllib.parse.urlencode(query_params, doseq=True) diff --git a/packages/google-auth/google/auth/_oauth2client.py b/packages/google-auth/google/auth/_oauth2client.py index a86ba8dd69f2..8b83ff23c1ae 100644 --- a/packages/google-auth/google/auth/_oauth2client.py +++ b/packages/google-auth/google/auth/_oauth2client.py @@ -21,8 +21,6 @@ from __future__ import absolute_import -import six - from google.auth import _helpers import google.auth.app_engine import google.auth.compute_engine @@ -34,7 +32,7 @@ import oauth2client.contrib.gce # type: ignore import oauth2client.service_account # type: ignore except ImportError as caught_exc: - six.raise_from(ImportError("oauth2client is not installed."), caught_exc) + raise ImportError("oauth2client is not installed.") from caught_exc try: import oauth2client.contrib.appengine # type: ignore @@ -166,4 +164,4 @@ def convert(credentials): return _CLASS_CONVERSION_MAP[credentials_class](credentials) except KeyError as caught_exc: new_exc = ValueError(_CONVERT_ERROR_TMPL.format(credentials_class)) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc diff --git a/packages/google-auth/google/auth/_service_account_info.py b/packages/google-auth/google/auth/_service_account_info.py index b17f34f5c255..6b64adcaebb0 100644 --- a/packages/google-auth/google/auth/_service_account_info.py +++ b/packages/google-auth/google/auth/_service_account_info.py @@ -17,8 +17,6 @@ import io import json -import six - from google.auth import crypt from google.auth import exceptions @@ -46,7 +44,7 @@ def from_dict(data, require=None, use_rsa_signer=True): """ keys_needed = set(require if require is not None else []) - missing = keys_needed.difference(six.iterkeys(data)) + missing = keys_needed.difference(data.keys()) if missing: raise exceptions.MalformedError( diff --git a/packages/google-auth/google/auth/aws.py b/packages/google-auth/google/auth/aws.py index 072c67bada45..6e0e4e864f1e 100644 --- a/packages/google-auth/google/auth/aws.py +++ b/packages/google-auth/google/auth/aws.py @@ -39,14 +39,13 @@ import hashlib import hmac +import http.client as http_client import json import os import posixpath import re - -from six.moves import http_client -from six.moves import urllib -from six.moves.urllib.parse import urljoin +import urllib +from urllib.parse import urljoin from google.auth import _helpers from google.auth import environment_vars diff --git a/packages/google-auth/google/auth/compute_engine/_metadata.py b/packages/google-auth/google/auth/compute_engine/_metadata.py index c5b9d5a2bbfa..04abe178f58a 100644 --- a/packages/google-auth/google/auth/compute_engine/_metadata.py +++ b/packages/google-auth/google/auth/compute_engine/_metadata.py @@ -18,13 +18,11 @@ """ import datetime +import http.client as http_client import json import logging import os - -import six -from six.moves import http_client -from six.moves.urllib import parse as urlparse +from urllib.parse import urljoin from google.auth import _helpers from google.auth import environment_vars @@ -185,7 +183,7 @@ def get( google.auth.exceptions.TransportError: if an error occurred while retrieving metadata. """ - base_url = urlparse.urljoin(root, path) + base_url = urljoin(root, path) query_params = {} if params is None else params headers_to_use = _METADATA_HEADERS.copy() @@ -228,7 +226,7 @@ def get( "Received invalid JSON from the Google Compute Engine " "metadata service: {:.20}".format(content) ) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc else: return content else: diff --git a/packages/google-auth/google/auth/compute_engine/credentials.py b/packages/google-auth/google/auth/compute_engine/credentials.py index 930d8861702f..7ae673880f94 100644 --- a/packages/google-auth/google/auth/compute_engine/credentials.py +++ b/packages/google-auth/google/auth/compute_engine/credentials.py @@ -21,8 +21,6 @@ import datetime -import six - from google.auth import _helpers from google.auth import credentials from google.auth import exceptions @@ -118,7 +116,7 @@ def refresh(self, request): ) except exceptions.TransportError as caught_exc: new_exc = exceptions.RefreshError(caught_exc) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc @property def service_account_email(self): @@ -386,7 +384,7 @@ def _call_metadata_identity_endpoint(self, request): ) except exceptions.TransportError as caught_exc: new_exc = exceptions.RefreshError(caught_exc) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc _, payload, _, _ = jwt._unverified_decode(id_token) return id_token, datetime.datetime.utcfromtimestamp(payload["exp"]) diff --git a/packages/google-auth/google/auth/credentials.py b/packages/google-auth/google/auth/credentials.py index c27721dbbeb5..3ae9c40114da 100644 --- a/packages/google-auth/google/auth/credentials.py +++ b/packages/google-auth/google/auth/credentials.py @@ -18,15 +18,12 @@ import abc import os -import six - from google.auth import _helpers, environment_vars from google.auth import exceptions from google.auth import metrics -@six.add_metaclass(abc.ABCMeta) -class Credentials(object): +class Credentials(metaclass=abc.ABCMeta): """Base class for all credentials. All credentials have a :attr:`token` that is used for authentication and @@ -232,8 +229,7 @@ def before_request(self, request, method, url, headers): """Anonymous credentials do nothing to the request.""" -@six.add_metaclass(abc.ABCMeta) -class ReadOnlyScoped(object): +class ReadOnlyScoped(metaclass=abc.ABCMeta): """Interface for credentials whose scopes can be queried. OAuth 2.0-based credentials allow limiting access using scopes as described @@ -374,8 +370,7 @@ def with_scopes_if_required(credentials, scopes, default_scopes=None): return credentials -@six.add_metaclass(abc.ABCMeta) -class Signing(object): +class Signing(metaclass=abc.ABCMeta): """Interface for credentials that can cryptographically sign messages.""" @abc.abstractmethod diff --git a/packages/google-auth/google/auth/crypt/__init__.py b/packages/google-auth/google/auth/crypt/__init__.py index 9f91f0d0ba67..6d147e706171 100644 --- a/packages/google-auth/google/auth/crypt/__init__.py +++ b/packages/google-auth/google/auth/crypt/__init__.py @@ -37,8 +37,6 @@ version is at least 1.4.0. """ -import six - from google.auth.crypt import base from google.auth.crypt import rsa @@ -90,7 +88,7 @@ class to use for verification. This can be used to select different Returns: bool: True if the signature is valid, otherwise False. """ - if isinstance(certs, (six.text_type, six.binary_type)): + if isinstance(certs, (str, bytes)): certs = [certs] for cert in certs: diff --git a/packages/google-auth/google/auth/crypt/_python_rsa.py b/packages/google-auth/google/auth/crypt/_python_rsa.py index e8595440cd79..e553c25ed564 100644 --- a/packages/google-auth/google/auth/crypt/_python_rsa.py +++ b/packages/google-auth/google/auth/crypt/_python_rsa.py @@ -21,12 +21,13 @@ from __future__ import absolute_import +import io + from pyasn1.codec.der import decoder # type: ignore from pyasn1_modules import pem # type: ignore from pyasn1_modules.rfc2459 import Certificate # type: ignore from pyasn1_modules.rfc5208 import PrivateKeyInfo # type: ignore import rsa # type: ignore -import six from google.auth import _helpers from google.auth import exceptions @@ -53,9 +54,9 @@ def _bit_list_to_bytes(bit_list): """ num_bits = len(bit_list) byte_vals = bytearray() - for start in six.moves.xrange(0, num_bits, 8): + for start in range(0, num_bits, 8): curr_bits = bit_list[start : start + 8] - char_val = sum(val * digit for val, digit in six.moves.zip(_POW2, curr_bits)) + char_val = sum(val * digit for val, digit in zip(_POW2, curr_bits)) byte_vals.append(char_val) return bytes(byte_vals) @@ -153,7 +154,7 @@ def from_string(cls, key, key_id=None): """ key = _helpers.from_bytes(key) # PEM expects str in Python 3 marker_id, key_bytes = pem.readPemBlocksFromFile( - six.StringIO(key), _PKCS1_MARKER, _PKCS8_MARKER + io.StringIO(key), _PKCS1_MARKER, _PKCS8_MARKER ) # Key is in pkcs1 format. diff --git a/packages/google-auth/google/auth/crypt/base.py b/packages/google-auth/google/auth/crypt/base.py index 573211d7c233..ad871c311566 100644 --- a/packages/google-auth/google/auth/crypt/base.py +++ b/packages/google-auth/google/auth/crypt/base.py @@ -18,16 +18,13 @@ import io import json -import six - from google.auth import exceptions _JSON_FILE_PRIVATE_KEY = "private_key" _JSON_FILE_PRIVATE_KEY_ID = "private_key_id" -@six.add_metaclass(abc.ABCMeta) -class Verifier(object): +class Verifier(metaclass=abc.ABCMeta): """Abstract base class for crytographic signature verifiers.""" @abc.abstractmethod @@ -47,8 +44,7 @@ def verify(self, message, signature): raise NotImplementedError("Verify must be implemented") -@six.add_metaclass(abc.ABCMeta) -class Signer(object): +class Signer(metaclass=abc.ABCMeta): """Abstract base class for cryptographic signers.""" @abc.abstractproperty @@ -71,8 +67,7 @@ def sign(self, message): raise NotImplementedError("Sign must be implemented") -@six.add_metaclass(abc.ABCMeta) -class FromServiceAccountMixin(object): +class FromServiceAccountMixin(metaclass=abc.ABCMeta): """Mix-in to enable factory constructors for a Signer.""" @abc.abstractmethod diff --git a/packages/google-auth/google/auth/downscoped.py b/packages/google-auth/google/auth/downscoped.py index a84ac4af6de2..b4d9d386e504 100644 --- a/packages/google-auth/google/auth/downscoped.py +++ b/packages/google-auth/google/auth/downscoped.py @@ -50,8 +50,6 @@ import datetime -import six - from google.auth import _helpers from google.auth import credentials from google.auth import exceptions @@ -224,7 +222,7 @@ def available_resource(self, value): Raises: google.auth.exceptions.InvalidType: If the value is not a string. """ - if not isinstance(value, six.string_types): + if not isinstance(value, str): raise exceptions.InvalidType( "The provided available_resource is not a string." ) @@ -252,7 +250,7 @@ def available_permissions(self, value): InvalidValue: If the value is not valid. """ for available_permission in value: - if not isinstance(available_permission, six.string_types): + if not isinstance(available_permission, str): raise exceptions.InvalidType( "Provided available_permissions are not a list of strings." ) @@ -355,7 +353,7 @@ def expression(self, value): Raises: google.auth.exceptions.InvalidType: If the value is not of type string. """ - if not isinstance(value, six.string_types): + if not isinstance(value, str): raise exceptions.InvalidType("The provided expression is not a string.") self._expression = value @@ -378,7 +376,7 @@ def title(self, value): Raises: google.auth.exceptions.InvalidType: If the value is not of type string or None. """ - if not isinstance(value, six.string_types) and value is not None: + if not isinstance(value, str) and value is not None: raise exceptions.InvalidType("The provided title is not a string or None.") self._title = value @@ -401,7 +399,7 @@ def description(self, value): Raises: google.auth.exceptions.InvalidType: If the value is not of type string or None. """ - if not isinstance(value, six.string_types) and value is not None: + if not isinstance(value, str) and value is not None: raise exceptions.InvalidType( "The provided description is not a string or None." ) diff --git a/packages/google-auth/google/auth/external_account.py b/packages/google-auth/google/auth/external_account.py index fc311ac2d8b7..001b26f7ff6a 100644 --- a/packages/google-auth/google/auth/external_account.py +++ b/packages/google-auth/google/auth/external_account.py @@ -34,8 +34,6 @@ import json import re -import six - from google.auth import _helpers from google.auth import credentials from google.auth import exceptions @@ -56,11 +54,11 @@ _DEFAULT_UNIVERSE_DOMAIN = "googleapis.com" -@six.add_metaclass(abc.ABCMeta) class Credentials( credentials.Scoped, credentials.CredentialsWithQuotaProject, credentials.CredentialsWithTokenUri, + metaclass=abc.ABCMeta, ): """Base class for all external account credentials. diff --git a/packages/google-auth/google/auth/iam.py b/packages/google-auth/google/auth/iam.py index 5d63dc5d8a91..e9df84417816 100644 --- a/packages/google-auth/google/auth/iam.py +++ b/packages/google-auth/google/auth/iam.py @@ -20,10 +20,9 @@ """ import base64 +import http.client as http_client import json -from six.moves import http_client - from google.auth import _helpers from google.auth import crypt from google.auth import exceptions diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index ba6012123f9a..816b9ea31734 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -28,11 +28,9 @@ import base64 import copy from datetime import datetime +import http.client as http_client import json -import six -from six.moves import http_client - from google.auth import _helpers from google.auth import credentials from google.auth import exceptions @@ -117,7 +115,7 @@ def _make_iam_token_request( ), response_body, ) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc class Credentials( diff --git a/packages/google-auth/google/auth/jwt.py b/packages/google-auth/google/auth/jwt.py index 026aec554e6e..1ebd565d4ecd 100644 --- a/packages/google-auth/google/auth/jwt.py +++ b/packages/google-auth/google/auth/jwt.py @@ -48,10 +48,9 @@ import copy import datetime import json +import urllib import cachetools -import six -from six.moves import urllib from google.auth import _helpers from google.auth import _service_account_info @@ -125,7 +124,7 @@ def _decode_jwt_segment(encoded_section): new_exc = exceptions.MalformedError( "Can't parse segment: {0}".format(section_bytes) ) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc def _unverified_decode(token): @@ -269,21 +268,15 @@ def decode(token, certs=None, verify=True, audience=None, clock_skew_in_seconds= verifier_cls = _ALGORITHM_TO_VERIFIER_CLASS[key_alg] except KeyError as exc: if key_alg in _CRYPTOGRAPHY_BASED_ALGORITHMS: - six.raise_from( - exceptions.InvalidValue( - "The key algorithm {} requires the cryptography package " - "to be installed.".format(key_alg) - ), - exc, - ) + raise exceptions.InvalidValue( + "The key algorithm {} requires the cryptography package to be installed.".format( + key_alg + ) + ) from exc else: - six.raise_from( - exceptions.InvalidValue( - "Unsupported signature algorithm {}".format(key_alg) - ), - exc, - ) - + raise exceptions.InvalidValue( + "Unsupported signature algorithm {}".format(key_alg) + ) from exc # If certs is specified as a dictionary of key IDs to certificates, then # use the certificate identified by the key ID in the token header. if isinstance(certs, Mapping): diff --git a/packages/google-auth/google/auth/transport/__init__.py b/packages/google-auth/google/auth/transport/__init__.py index 8334145a1a3a..724568e5828e 100644 --- a/packages/google-auth/google/auth/transport/__init__.py +++ b/packages/google-auth/google/auth/transport/__init__.py @@ -25,17 +25,13 @@ """ import abc - -import six -from six.moves import http_client - -TOO_MANY_REQUESTS = 429 # Python 2.7 six is missing this status code. +import http.client as http_client DEFAULT_RETRYABLE_STATUS_CODES = ( http_client.INTERNAL_SERVER_ERROR, http_client.SERVICE_UNAVAILABLE, http_client.REQUEST_TIMEOUT, - TOO_MANY_REQUESTS, + http_client.TOO_MANY_REQUESTS, ) """Sequence[int]: HTTP status codes indicating a request can be retried. """ @@ -50,8 +46,7 @@ """int: How many times to refresh the credentials and retry a request.""" -@six.add_metaclass(abc.ABCMeta) -class Response(object): +class Response(metaclass=abc.ABCMeta): """HTTP Response data.""" @abc.abstractproperty @@ -70,8 +65,7 @@ def data(self): raise NotImplementedError("data must be implemented.") -@six.add_metaclass(abc.ABCMeta) -class Request(object): +class Request(metaclass=abc.ABCMeta): """Interface for a callable that makes HTTP requests. Specific transport implementations should provide an implementation of diff --git a/packages/google-auth/google/auth/transport/_aiohttp_requests.py b/packages/google-auth/google/auth/transport/_aiohttp_requests.py index 364570e311ac..3a8da917a1dc 100644 --- a/packages/google-auth/google/auth/transport/_aiohttp_requests.py +++ b/packages/google-auth/google/auth/transport/_aiohttp_requests.py @@ -24,7 +24,6 @@ import functools import aiohttp # type: ignore -import six import urllib3 # type: ignore from google.auth import exceptions @@ -191,11 +190,11 @@ async def __call__( except aiohttp.ClientError as caught_exc: new_exc = exceptions.TransportError(caught_exc) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc except asyncio.TimeoutError as caught_exc: new_exc = exceptions.TransportError(caught_exc) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc class AuthorizedSession(aiohttp.ClientSession): diff --git a/packages/google-auth/google/auth/transport/_custom_tls_signer.py b/packages/google-auth/google/auth/transport/_custom_tls_signer.py index dfef6d00f1da..07f14df02de1 100644 --- a/packages/google-auth/google/auth/transport/_custom_tls_signer.py +++ b/packages/google-auth/google/auth/transport/_custom_tls_signer.py @@ -24,7 +24,6 @@ import sys import cffi # type: ignore -import six from google.auth import exceptions @@ -212,7 +211,7 @@ def load_libraries(self): new_exc = exceptions.MutualTLSChannelError( "enterprise cert file is invalid", caught_exc ) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc self._offload_lib = load_offload_lib(offload_library) self._signer_lib = load_signer_lib(signer_library) diff --git a/packages/google-auth/google/auth/transport/_http_client.py b/packages/google-auth/google/auth/transport/_http_client.py index c153763efa2c..cec0ab73fb31 100644 --- a/packages/google-auth/google/auth/transport/_http_client.py +++ b/packages/google-auth/google/auth/transport/_http_client.py @@ -14,12 +14,10 @@ """Transport adapter for http.client, for internal use only.""" +import http.client as http_client import logging import socket - -import six -from six.moves import http_client -from six.moves import urllib +import urllib from google.auth import exceptions from google.auth import transport @@ -109,7 +107,7 @@ def __call__( except (http_client.HTTPException, socket.error) as caught_exc: new_exc = exceptions.TransportError(caught_exc) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc finally: connection.close() diff --git a/packages/google-auth/google/auth/transport/_mtls_helper.py b/packages/google-auth/google/auth/transport/_mtls_helper.py index 4dccb1062f88..1b9b9c285c6c 100644 --- a/packages/google-auth/google/auth/transport/_mtls_helper.py +++ b/packages/google-auth/google/auth/transport/_mtls_helper.py @@ -20,8 +20,6 @@ import re import subprocess -import six - from google.auth import exceptions CONTEXT_AWARE_METADATA_PATH = "~/.secureConnect/context_aware_metadata.json" @@ -82,7 +80,7 @@ def _read_dca_metadata_file(metadata_path): metadata = json.load(f) except ValueError as caught_exc: new_exc = exceptions.ClientCertError(caught_exc) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc return metadata @@ -110,7 +108,7 @@ def _run_cert_provider_command(command, expect_encrypted_key=False): stdout, stderr = process.communicate() except OSError as caught_exc: new_exc = exceptions.ClientCertError(caught_exc) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc # Check cert provider command execution error. if process.returncode != 0: diff --git a/packages/google-auth/google/auth/transport/grpc.py b/packages/google-auth/google/auth/transport/grpc.py index 55764b6f6109..9a817976d776 100644 --- a/packages/google-auth/google/auth/transport/grpc.py +++ b/packages/google-auth/google/auth/transport/grpc.py @@ -19,8 +19,6 @@ import logging import os -import six - from google.auth import environment_vars from google.auth import exceptions from google.auth.transport import _mtls_helper @@ -29,13 +27,9 @@ try: import grpc # type: ignore except ImportError as caught_exc: # pragma: NO COVER - six.raise_from( - ImportError( - "gRPC is not installed, please install the grpcio package " - "to use the gRPC transport." - ), - caught_exc, - ) + raise ImportError( + "gRPC is not installed from please install the grpcio package to use the gRPC transport." + ) from caught_exc _LOGGER = logging.getLogger(__name__) @@ -88,7 +82,7 @@ def _get_authorization_headers(self, context): self._request, context.method_name, context.service_url, headers ) - return list(six.iteritems(headers)) + return list(headers.items()) def __call__(self, context, callback): """Passes authorization metadata into the given callback. @@ -337,7 +331,7 @@ def ssl_credentials(self): ) except exceptions.ClientCertError as caught_exc: new_exc = exceptions.MutualTLSChannelError(caught_exc) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc else: self._ssl_credentials = grpc.ssl_channel_credentials() diff --git a/packages/google-auth/google/auth/transport/mtls.py b/packages/google-auth/google/auth/transport/mtls.py index b40bfbedf97d..c5707617ff80 100644 --- a/packages/google-auth/google/auth/transport/mtls.py +++ b/packages/google-auth/google/auth/transport/mtls.py @@ -14,8 +14,6 @@ """Utilites for mutual TLS.""" -import six - from google.auth import exceptions from google.auth.transport import _mtls_helper @@ -53,7 +51,7 @@ def callback(): _, cert_bytes, key_bytes = _mtls_helper.get_client_cert_and_key() except (OSError, RuntimeError, ValueError) as caught_exc: new_exc = exceptions.MutualTLSChannelError(caught_exc) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc return cert_bytes, key_bytes @@ -98,7 +96,7 @@ def callback(): key_file.write(key_bytes) except (exceptions.ClientCertError, OSError) as caught_exc: new_exc = exceptions.MutualTLSChannelError(caught_exc) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc return cert_path, key_path, passphrase_bytes diff --git a/packages/google-auth/google/auth/transport/requests.py b/packages/google-auth/google/auth/transport/requests.py index 2c746f8d72f7..b9bcad359f05 100644 --- a/packages/google-auth/google/auth/transport/requests.py +++ b/packages/google-auth/google/auth/transport/requests.py @@ -25,21 +25,14 @@ try: import requests except ImportError as caught_exc: # pragma: NO COVER - import six - - six.raise_from( - ImportError( - "The requests library is not installed, please install the " - "requests package to use the requests transport." - ), - caught_exc, - ) + raise ImportError( + "The requests library is not installed from please install the requests package to use the requests transport." + ) from caught_exc import requests.adapters # pylint: disable=ungrouped-imports import requests.exceptions # pylint: disable=ungrouped-imports from requests.packages.urllib3.util.ssl_ import ( # type: ignore create_urllib3_context, ) # pylint: disable=ungrouped-imports -import six # pylint: disable=ungrouped-imports from google.auth import environment_vars from google.auth import exceptions @@ -196,7 +189,7 @@ def __call__( return _Response(response) except requests.exceptions.RequestException as caught_exc: new_exc = exceptions.TransportError(caught_exc) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc class _MutualTlsAdapter(requests.adapters.HTTPAdapter): @@ -465,7 +458,7 @@ def configure_mtls_channel(self, client_cert_callback=None): import OpenSSL except ImportError as caught_exc: new_exc = exceptions.MutualTLSChannelError(caught_exc) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc try: ( @@ -485,7 +478,7 @@ def configure_mtls_channel(self, client_cert_callback=None): OpenSSL.crypto.Error, ) as caught_exc: new_exc = exceptions.MutualTLSChannelError(caught_exc) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc def request( self, diff --git a/packages/google-auth/google/auth/transport/urllib3.py b/packages/google-auth/google/auth/transport/urllib3.py index 4abc26b52278..053d6f7b728e 100644 --- a/packages/google-auth/google/auth/transport/urllib3.py +++ b/packages/google-auth/google/auth/transport/urllib3.py @@ -31,19 +31,14 @@ except ImportError: # pragma: NO COVER certifi = None # type: ignore -import six - try: import urllib3 # type: ignore import urllib3.exceptions # type: ignore except ImportError as caught_exc: # pragma: NO COVER - six.raise_from( - ImportError( - "The urllib3 library is not installed, please install the " - "urllib3 package to use the urllib3 transport." - ), - caught_exc, - ) + raise ImportError( + "The urllib3 library is not installed from please install the " + "urllib3 package to use the urllib3 transport." + ) from caught_exc from google.auth import environment_vars from google.auth import exceptions @@ -141,7 +136,7 @@ def __call__( return _Response(response) except urllib3.exceptions.HTTPError as caught_exc: new_exc = exceptions.TransportError(caught_exc) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc def _make_default_http(): @@ -333,7 +328,7 @@ def configure_mtls_channel(self, client_cert_callback=None): import OpenSSL except ImportError as caught_exc: new_exc = exceptions.MutualTLSChannelError(caught_exc) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc try: found_cert_key, cert, key = transport._mtls_helper.get_client_cert_and_key( @@ -350,7 +345,7 @@ def configure_mtls_channel(self, client_cert_callback=None): OpenSSL.crypto.Error, ) as caught_exc: new_exc = exceptions.MutualTLSChannelError(caught_exc) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc if self._has_user_provided_http: self._has_user_provided_http = False diff --git a/packages/google-auth/google/oauth2/_client.py b/packages/google-auth/google/oauth2/_client.py index e2c9509a9347..d2af6c8aa85b 100644 --- a/packages/google-auth/google/oauth2/_client.py +++ b/packages/google-auth/google/oauth2/_client.py @@ -24,11 +24,9 @@ """ import datetime +import http.client as http_client import json - -import six -from six.moves import http_client -from six.moves import urllib +import urllib from google.auth import _exponential_backoff from google.auth import _helpers @@ -61,7 +59,7 @@ def _handle_error_response(response_data, retryable_error): retryable_error = retryable_error if retryable_error else False - if isinstance(response_data, six.string_types): + if isinstance(response_data, str): raise exceptions.RefreshError(response_data, retryable=retryable_error) try: error_details = "{}: {}".format( @@ -95,9 +93,7 @@ def _can_retry(status_code, response_data): error_desc = response_data.get("error_description") or "" error_code = response_data.get("error") or "" - if not isinstance(error_code, six.string_types) or not isinstance( - error_desc, six.string_types - ): + if not isinstance(error_code, str) or not isinstance(error_desc, str): return False # Per Oauth 2.0 RFC https://www.rfc-editor.org/rfc/rfc6749.html#section-4.1.2.1 @@ -325,7 +321,7 @@ def jwt_grant(request, token_uri, assertion, can_retry=True): new_exc = exceptions.RefreshError( "No access token in response.", response_data, retryable=False ) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc expiry = _parse_expiry(response_data) @@ -362,7 +358,7 @@ def call_iam_generate_id_token_endpoint(request, signer_email, audience, access_ new_exc = exceptions.RefreshError( "No ID token in response.", response_data, retryable=False ) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc payload = jwt.decode(id_token, verify=False) expiry = datetime.datetime.utcfromtimestamp(payload["exp"]) @@ -414,7 +410,7 @@ def id_token_jwt_grant(request, token_uri, assertion, can_retry=True): new_exc = exceptions.RefreshError( "No ID token in response.", response_data, retryable=False ) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc payload = jwt.decode(id_token, verify=False) expiry = datetime.datetime.utcfromtimestamp(payload["exp"]) @@ -445,7 +441,7 @@ def _handle_refresh_grant_response(response_data, refresh_token): new_exc = exceptions.RefreshError( "No access token in response.", response_data, retryable=False ) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc refresh_token = response_data.get("refresh_token", refresh_token) expiry = _parse_expiry(response_data) diff --git a/packages/google-auth/google/oauth2/_client_async.py b/packages/google-auth/google/oauth2/_client_async.py index 428084a70a8b..2858d862b0b1 100644 --- a/packages/google-auth/google/oauth2/_client_async.py +++ b/packages/google-auth/google/oauth2/_client_async.py @@ -24,11 +24,9 @@ """ import datetime +import http.client as http_client import json - -import six -from six.moves import http_client -from six.moves import urllib +import urllib from google.auth import _exponential_backoff from google.auth import exceptions @@ -183,7 +181,7 @@ async def jwt_grant(request, token_uri, assertion, can_retry=True): new_exc = exceptions.RefreshError( "No access token in response.", response_data, retryable=False ) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc expiry = client._parse_expiry(response_data) @@ -228,7 +226,7 @@ async def id_token_jwt_grant(request, token_uri, assertion, can_retry=True): new_exc = exceptions.RefreshError( "No ID token in response.", response_data, retryable=False ) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc payload = jwt.decode(id_token, verify=False) expiry = datetime.datetime.utcfromtimestamp(payload["exp"]) diff --git a/packages/google-auth/google/oauth2/_id_token_async.py b/packages/google-auth/google/oauth2/_id_token_async.py index c32dfa47d1bc..6594e416aea0 100644 --- a/packages/google-auth/google/oauth2/_id_token_async.py +++ b/packages/google-auth/google/oauth2/_id_token_async.py @@ -58,12 +58,10 @@ .. _CacheControl: https://cachecontrol.readthedocs.io """ +import http.client as http_client import json import os -import six -from six.moves import http_client - from google.auth import environment_vars from google.auth import exceptions from google.auth import jwt @@ -264,7 +262,7 @@ async def fetch_id_token(request, audience): "GOOGLE_APPLICATION_CREDENTIALS is not valid service account credentials.", caught_exc, ) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc # 2. Try to fetch ID token from metada server if it exists. The code works # for GAE and Cloud Run metadata server as well. diff --git a/packages/google-auth/google/oauth2/_reauth_async.py b/packages/google-auth/google/oauth2/_reauth_async.py index 6b69c6e67c68..de3675c5239c 100644 --- a/packages/google-auth/google/oauth2/_reauth_async.py +++ b/packages/google-auth/google/oauth2/_reauth_async.py @@ -34,8 +34,6 @@ import sys -from six.moves import range - from google.auth import exceptions from google.oauth2 import _client from google.oauth2 import _client_async diff --git a/packages/google-auth/google/oauth2/challenges.py b/packages/google-auth/google/oauth2/challenges.py index 6fe982d1c31a..c55796323ba1 100644 --- a/packages/google-auth/google/oauth2/challenges.py +++ b/packages/google-auth/google/oauth2/challenges.py @@ -20,8 +20,6 @@ import getpass import sys -import six - from google.auth import _helpers from google.auth import exceptions @@ -47,8 +45,7 @@ def get_user_password(text): return getpass.getpass(text) -@six.add_metaclass(abc.ABCMeta) -class ReauthChallenge(object): +class ReauthChallenge(metaclass=abc.ABCMeta): """Base class for reauth challenges.""" @property diff --git a/packages/google-auth/google/oauth2/credentials.py b/packages/google-auth/google/oauth2/credentials.py index 5c40ab097d65..5becb7c1df3f 100644 --- a/packages/google-auth/google/oauth2/credentials.py +++ b/packages/google-auth/google/oauth2/credentials.py @@ -37,8 +37,6 @@ import logging import warnings -import six - from google.auth import _cloud_sdk from google.auth import _helpers from google.auth import credentials @@ -307,7 +305,7 @@ def refresh(self, request): if self._refresh_token is None and self.refresh_handler: token, expiry = self.refresh_handler(request, scopes=scopes) # Validate returned data. - if not isinstance(token, six.string_types): + if not isinstance(token, str): raise exceptions.RefreshError( "The refresh_handler returned token is not a string." ) @@ -394,7 +392,7 @@ def from_authorized_user_info(cls, info, scopes=None): ValueError: If the info is not in the expected format. """ keys_needed = set(("refresh_token", "client_id", "client_secret")) - missing = keys_needed.difference(six.iterkeys(info)) + missing = keys_needed.difference(info.keys()) if missing: raise ValueError( @@ -414,7 +412,7 @@ def from_authorized_user_info(cls, info, scopes=None): # process scopes, which needs to be a seq if scopes is None and "scopes" in info: scopes = info.get("scopes") - if isinstance(scopes, six.string_types): + if isinstance(scopes, str): scopes = scopes.split(" ") return cls( diff --git a/packages/google-auth/google/oauth2/id_token.py b/packages/google-auth/google/oauth2/id_token.py index 48f5b0a59655..2b1abec2b42b 100644 --- a/packages/google-auth/google/oauth2/id_token.py +++ b/packages/google-auth/google/oauth2/id_token.py @@ -55,12 +55,10 @@ .. _CacheControl: https://cachecontrol.readthedocs.io """ +import http.client as http_client import json import os -import six -from six.moves import http_client - from google.auth import environment_vars from google.auth import exceptions from google.auth import jwt @@ -274,7 +272,7 @@ def fetch_id_token_credentials(audience, request=None): "GOOGLE_APPLICATION_CREDENTIALS is not valid service account credentials.", caught_exc, ) - six.raise_from(new_exc, caught_exc) + raise new_exc from caught_exc # 2. Try to fetch ID token from metada server if it exists. The code # works for GAE and Cloud Run metadata server as well. diff --git a/packages/google-auth/google/oauth2/reauth.py b/packages/google-auth/google/oauth2/reauth.py index c679a9e8b734..5870347739b1 100644 --- a/packages/google-auth/google/oauth2/reauth.py +++ b/packages/google-auth/google/oauth2/reauth.py @@ -34,8 +34,6 @@ import sys -from six.moves import range - from google.auth import exceptions from google.auth import metrics from google.oauth2 import _client diff --git a/packages/google-auth/google/oauth2/sts.py b/packages/google-auth/google/oauth2/sts.py index 5cf06d4d4586..ad3962735f7a 100644 --- a/packages/google-auth/google/oauth2/sts.py +++ b/packages/google-auth/google/oauth2/sts.py @@ -31,10 +31,9 @@ .. _rfc8693 section 2.2.1: https://tools.ietf.org/html/rfc8693#section-2.2.1 """ +import http.client as http_client import json - -from six.moves import http_client -from six.moves import urllib +import urllib from google.oauth2 import utils diff --git a/packages/google-auth/google/oauth2/utils.py b/packages/google-auth/google/oauth2/utils.py index 593f03236ec8..d72ff1916631 100644 --- a/packages/google-auth/google/oauth2/utils.py +++ b/packages/google-auth/google/oauth2/utils.py @@ -45,8 +45,6 @@ import enum import json -import six - from google.auth import exceptions @@ -77,8 +75,7 @@ def __init__(self, client_auth_type, client_id, client_secret=None): self.client_secret = client_secret -@six.add_metaclass(abc.ABCMeta) -class OAuthClientAuthHandler(object): +class OAuthClientAuthHandler(metaclass=abc.ABCMeta): """Abstract class for handling client authentication in OAuth-based operations. """ diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index b9d183385f7e..48b0347d1b4a 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -79,7 +79,6 @@ def mypy(session): "types-pyOpenSSL", "types-requests", "types-setuptools", - "types-six", "types-mock", ) session.run("mypy", "-p", "google", "-p", "tests", "-p", "tests_async") diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 4a91925dd7d9..b3c0e6de2240 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -26,7 +26,6 @@ # https://github.com/sybrenstuvel/python-rsa/issues/152#issuecomment-643470233 "rsa>=3.1.4,<5", # install enum34 to support 2.7. enum34 only works up to python version 3.3. - "six>=1.9.0", "urllib3<2.0", ) diff --git a/packages/google-auth/system_tests/noxfile.py b/packages/google-auth/system_tests/noxfile.py index e44dc5660a08..eb22108bb1cd 100644 --- a/packages/google-auth/system_tests/noxfile.py +++ b/packages/google-auth/system_tests/noxfile.py @@ -283,6 +283,7 @@ def compute_engine(session): @nox.session(python=PYTHON_VERSIONS_SYNC) def grpc(session): session.install(LIBRARY_DIR) + session.install("six") session.install(*TEST_DEPENDENCIES_SYNC, "google-cloud-pubsub==1.7.2") session.env[EXPLICIT_CREDENTIALS_ENV] = SERVICE_ACCOUNT_FILE default( diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 0c81b08f517c8f84b4db29fb33b247d768acd7ab..15589afb983a2fcdb5dd8972bfd455aac6483b38 100644 GIT binary patch literal 10324 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 literal 10324 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 diff --git a/packages/google-auth/system_tests/system_tests_sync/test_external_accounts.py b/packages/google-auth/system_tests/system_tests_sync/test_external_accounts.py index e7a81963bd30..837d0064b796 100644 --- a/packages/google-auth/system_tests/system_tests_sync/test_external_accounts.py +++ b/packages/google-auth/system_tests/system_tests_sync/test_external_accounts.py @@ -44,7 +44,8 @@ import google.auth from google.auth import _helpers from googleapiclient import discovery -from six.moves import BaseHTTPServer +from http.server import BaseHTTPRequestHandler +from http.server import HTTPServer from google.oauth2 import service_account import pytest from mock import patch @@ -245,7 +246,7 @@ def check_impersonation_expiration(): # This test makes sure that setting up an http server to provide credentials # works to allow access to Google resources. def test_url_based_external_account(dns_access, oidc_credentials, service_account_info): - class TestResponseHandler(BaseHTTPServer.BaseHTTPRequestHandler): + class TestResponseHandler(BaseHTTPRequestHandler): def do_GET(self): if self.headers["my-header"] != "expected-value": self.send_response(400) @@ -269,7 +270,7 @@ def do_GET(self): json.dumps({"access_token": oidc_credentials.token}).encode("utf-8") ) - class TestHTTPServer(BaseHTTPServer.HTTPServer, object): + class TestHTTPServer(HTTPServer, object): def __init__(self): self.port = self._find_open_port() super(TestHTTPServer, self).__init__(("", self.port), TestResponseHandler) diff --git a/packages/google-auth/tests/compute_engine/test__metadata.py b/packages/google-auth/tests/compute_engine/test__metadata.py index f543426d39f0..a940feb255ba 100644 --- a/packages/google-auth/tests/compute_engine/test__metadata.py +++ b/packages/google-auth/tests/compute_engine/test__metadata.py @@ -13,13 +13,13 @@ # limitations under the License. import datetime +import http.client as http_client +import importlib import json import os import mock import pytest # type: ignore -from six.moves import http_client -from six.moves import reload_module from google.auth import _helpers from google.auth import environment_vars @@ -144,13 +144,13 @@ def test_ping_success_custom_root(mock_metrics_header_value): fake_ip = "1.2.3.4" os.environ[environment_vars.GCE_METADATA_IP] = fake_ip - reload_module(_metadata) + importlib.reload(_metadata) try: assert _metadata.ping(request) finally: del os.environ[environment_vars.GCE_METADATA_IP] - reload_module(_metadata) + importlib.reload(_metadata) request.assert_called_once_with( method="GET", @@ -257,13 +257,13 @@ def test_get_success_custom_root_new_variable(): fake_root = "another.metadata.service" os.environ[environment_vars.GCE_METADATA_HOST] = fake_root - reload_module(_metadata) + importlib.reload(_metadata) try: _metadata.get(request, PATH) finally: del os.environ[environment_vars.GCE_METADATA_HOST] - reload_module(_metadata) + importlib.reload(_metadata) request.assert_called_once_with( method="GET", @@ -277,13 +277,13 @@ def test_get_success_custom_root_old_variable(): fake_root = "another.metadata.service" os.environ[environment_vars.GCE_METADATA_ROOT] = fake_root - reload_module(_metadata) + importlib.reload(_metadata) try: _metadata.get(request, PATH) finally: del os.environ[environment_vars.GCE_METADATA_ROOT] - reload_module(_metadata) + importlib.reload(_metadata) request.assert_called_once_with( method="GET", diff --git a/packages/google-auth/tests/crypt/test__python_rsa.py b/packages/google-auth/tests/crypt/test__python_rsa.py index 9d832f044f73..4a4ebe44e98c 100644 --- a/packages/google-auth/tests/crypt/test__python_rsa.py +++ b/packages/google-auth/tests/crypt/test__python_rsa.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import io import json import os @@ -19,7 +20,6 @@ from pyasn1_modules import pem # type: ignore import pytest # type: ignore import rsa # type: ignore -import six from google.auth import _helpers from google.auth.crypt import _python_rsa @@ -141,7 +141,7 @@ def test_from_string_pkcs8(self): def test_from_string_pkcs8_extra_bytes(self): key_bytes = PKCS8_KEY_BYTES _, pem_bytes = pem.readPemBlocksFromFile( - six.StringIO(_helpers.from_bytes(key_bytes)), _python_rsa._PKCS8_MARKER + io.StringIO(_helpers.from_bytes(key_bytes)), _python_rsa._PKCS8_MARKER ) key_info, remaining = None, "extra" diff --git a/packages/google-auth/tests/oauth2/test__client.py b/packages/google-auth/tests/oauth2/test__client.py index 9450fa1fd7d5..4cbd3a8adcd5 100644 --- a/packages/google-auth/tests/oauth2/test__client.py +++ b/packages/google-auth/tests/oauth2/test__client.py @@ -13,14 +13,13 @@ # limitations under the License. import datetime +import http.client as http_client import json import os +import urllib import mock import pytest # type: ignore -import six -from six.moves import http_client -from six.moves import urllib from google.auth import _helpers from google.auth import crypt @@ -273,7 +272,7 @@ def verify_request_params(request, params): request_body = request.call_args[1]["body"].decode("utf-8") request_params = urllib.parse.parse_qs(request_body) - for key, value in six.iteritems(params): + for key, value in params.items(): assert request_params[key][0] == value diff --git a/packages/google-auth/tests/oauth2/test_gdch_credentials.py b/packages/google-auth/tests/oauth2/test_gdch_credentials.py index 60944ed41109..63075aba03eb 100644 --- a/packages/google-auth/tests/oauth2/test_gdch_credentials.py +++ b/packages/google-auth/tests/oauth2/test_gdch_credentials.py @@ -20,7 +20,6 @@ import mock import pytest # type: ignore import requests -import six from google.auth import exceptions from google.auth import jwt @@ -69,7 +68,7 @@ def test__create_jwt(self): expected_iss_sub_value = ( "system:serviceaccount:project_foo:service_identity_name" ) - assert isinstance(jwt_token, six.text_type) + assert isinstance(jwt_token, str) assert header["alg"] == "ES256" assert header["kid"] == self.PRIVATE_KEY_ID assert payload["iss"] == expected_iss_sub_value diff --git a/packages/google-auth/tests/oauth2/test_service_account.py b/packages/google-auth/tests/oauth2/test_service_account.py index 36565b721a6a..058fc3f7d6f9 100644 --- a/packages/google-auth/tests/oauth2/test_service_account.py +++ b/packages/google-auth/tests/oauth2/test_service_account.py @@ -18,7 +18,6 @@ import mock import pytest # type: ignore -import six from google.auth import _helpers from google.auth import crypt @@ -525,7 +524,7 @@ def test_refresh_with_jwt_credentials_token_type_check(self): credentials.refresh(mock.Mock()) # Credentials token should be a JWT string. - assert isinstance(credentials.token, six.string_types) + assert isinstance(credentials.token, str) payload = jwt.decode(credentials.token, verify=False) assert payload["aud"] == "https://pubsub.googleapis.com" diff --git a/packages/google-auth/tests/oauth2/test_sts.py b/packages/google-auth/tests/oauth2/test_sts.py index a543d42a8db6..e0fb4ae23e66 100644 --- a/packages/google-auth/tests/oauth2/test_sts.py +++ b/packages/google-auth/tests/oauth2/test_sts.py @@ -12,12 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. +import http.client as http_client import json +import urllib import mock import pytest # type: ignore -from six.moves import http_client -from six.moves import urllib from google.auth import exceptions from google.auth import transport diff --git a/packages/google-auth/tests/test__helpers.py b/packages/google-auth/tests/test__helpers.py index 8c71f3e51f4b..c1f1d812e57e 100644 --- a/packages/google-auth/tests/test__helpers.py +++ b/packages/google-auth/tests/test__helpers.py @@ -13,9 +13,9 @@ # limitations under the License. import datetime +import urllib import pytest # type: ignore -from six.moves import urllib from google.auth import _helpers diff --git a/packages/google-auth/tests/test__oauth2client.py b/packages/google-auth/tests/test__oauth2client.py index 8802ba17f7cc..9f0c192aee7f 100644 --- a/packages/google-auth/tests/test__oauth2client.py +++ b/packages/google-auth/tests/test__oauth2client.py @@ -13,12 +13,12 @@ # limitations under the License. import datetime +import importlib import os import sys import mock import pytest # type: ignore -from six.moves import reload_module try: import oauth2client.client # type: ignore @@ -159,19 +159,19 @@ def test_convert_not_found(): @pytest.fixture def reset__oauth2client_module(): """Reloads the _oauth2client module after a test.""" - reload_module(_oauth2client) + importlib.reload(_oauth2client) def test_import_has_app_engine( mock_oauth2client_gae_imports, reset__oauth2client_module ): - reload_module(_oauth2client) + importlib.reload(_oauth2client) assert _oauth2client._HAS_APPENGINE def test_import_without_oauth2client(monkeypatch, reset__oauth2client_module): monkeypatch.setitem(sys.modules, "oauth2client", None) with pytest.raises(ImportError) as excinfo: - reload_module(_oauth2client) + importlib.reload(_oauth2client) assert excinfo.match("oauth2client") diff --git a/packages/google-auth/tests/test__service_account_info.py b/packages/google-auth/tests/test__service_account_info.py index 9ad9f0fc8b50..4fa85a5992b3 100644 --- a/packages/google-auth/tests/test__service_account_info.py +++ b/packages/google-auth/tests/test__service_account_info.py @@ -16,7 +16,6 @@ import os import pytest # type: ignore -import six from google.auth import _service_account_info from google.auth import crypt @@ -67,7 +66,7 @@ def test_from_dict_bad_format(): def test_from_filename(): info, signer = _service_account_info.from_filename(SERVICE_ACCOUNT_JSON_FILE) - for key, value in six.iteritems(SERVICE_ACCOUNT_INFO): + for key, value in SERVICE_ACCOUNT_INFO.items(): assert info[key] == value assert isinstance(signer, crypt.RSASigner) diff --git a/packages/google-auth/tests/test_aws.py b/packages/google-auth/tests/test_aws.py index 1c8c5d41a7a3..39138ab12e04 100644 --- a/packages/google-auth/tests/test_aws.py +++ b/packages/google-auth/tests/test_aws.py @@ -13,13 +13,13 @@ # limitations under the License. import datetime +import http.client as http_client import json import os +import urllib.parse import mock import pytest # type: ignore -from six.moves import http_client -from six.moves import urllib from google.auth import _helpers from google.auth import aws diff --git a/packages/google-auth/tests/test_downscoped.py b/packages/google-auth/tests/test_downscoped.py index 7d0768a18086..b011380bdbb8 100644 --- a/packages/google-auth/tests/test_downscoped.py +++ b/packages/google-auth/tests/test_downscoped.py @@ -13,12 +13,12 @@ # limitations under the License. import datetime +import http.client as http_client import json +import urllib import mock import pytest # type: ignore -from six.moves import http_client -from six.moves import urllib from google.auth import _helpers from google.auth import credentials diff --git a/packages/google-auth/tests/test_external_account.py b/packages/google-auth/tests/test_external_account.py index f05a5a11ac21..fd511aa44f04 100644 --- a/packages/google-auth/tests/test_external_account.py +++ b/packages/google-auth/tests/test_external_account.py @@ -13,12 +13,12 @@ # limitations under the License. import datetime +import http.client as http_client import json +import urllib import mock import pytest # type: ignore -from six.moves import http_client -from six.moves import urllib from google.auth import _helpers from google.auth import exceptions diff --git a/packages/google-auth/tests/test_external_account_authorized_user.py b/packages/google-auth/tests/test_external_account_authorized_user.py index db18450a8326..7ffd5078c814 100644 --- a/packages/google-auth/tests/test_external_account_authorized_user.py +++ b/packages/google-auth/tests/test_external_account_authorized_user.py @@ -13,11 +13,11 @@ # limitations under the License. import datetime +import http.client as http_client import json import mock import pytest # type: ignore -from six.moves import http_client from google.auth import exceptions from google.auth import external_account_authorized_user diff --git a/packages/google-auth/tests/test_iam.py b/packages/google-auth/tests/test_iam.py index ae482765b475..6706afb4b522 100644 --- a/packages/google-auth/tests/test_iam.py +++ b/packages/google-auth/tests/test_iam.py @@ -14,11 +14,11 @@ import base64 import datetime +import http.client as http_client import json import mock import pytest # type: ignore -from six.moves import http_client from google.auth import _helpers from google.auth import exceptions diff --git a/packages/google-auth/tests/test_identity_pool.py b/packages/google-auth/tests/test_identity_pool.py index 7262e644c22a..e469cf731595 100644 --- a/packages/google-auth/tests/test_identity_pool.py +++ b/packages/google-auth/tests/test_identity_pool.py @@ -13,13 +13,13 @@ # limitations under the License. import datetime +import http.client as http_client import json import os +import urllib import mock import pytest # type: ignore -from six.moves import http_client -from six.moves import urllib from google.auth import _helpers from google.auth import exceptions diff --git a/packages/google-auth/tests/test_impersonated_credentials.py b/packages/google-auth/tests/test_impersonated_credentials.py index f79db8f6a08b..161ff3b04ccc 100644 --- a/packages/google-auth/tests/test_impersonated_credentials.py +++ b/packages/google-auth/tests/test_impersonated_credentials.py @@ -13,15 +13,12 @@ # limitations under the License. import datetime +import http.client as http_client import json import os -# Because Python 2.7 -# from typing import List - import mock import pytest # type: ignore -from six.moves import http_client from google.auth import _helpers from google.auth import crypt diff --git a/packages/google-auth/tests/test_pluggable.py b/packages/google-auth/tests/test_pluggable.py index 7d601dfd4a27..9b0da927316f 100644 --- a/packages/google-auth/tests/test_pluggable.py +++ b/packages/google-auth/tests/test_pluggable.py @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -# import datetime import json import os import subprocess @@ -20,17 +19,10 @@ import mock import pytest # type: ignore -# from six.moves import http_client -# from six.moves import urllib - -# from google.auth import _helpers from google.auth import exceptions from google.auth import pluggable from tests.test__default import WORKFORCE_AUDIENCE -# from google.auth import transport - - CLIENT_ID = "username" CLIENT_SECRET = "password" # Base64 encoding of "username:password". diff --git a/packages/google-auth/tests/transport/compliance.py b/packages/google-auth/tests/transport/compliance.py index faf39b9bacc0..b3cd7e823469 100644 --- a/packages/google-auth/tests/transport/compliance.py +++ b/packages/google-auth/tests/transport/compliance.py @@ -12,12 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. +import http.client as http_client import time import flask # type: ignore import pytest # type: ignore from pytest_localserver.http import WSGIServer # type: ignore -from six.moves import http_client from google.auth import exceptions diff --git a/packages/google-auth/tests/transport/test_requests.py b/packages/google-auth/tests/transport/test_requests.py index 9532117b5f6a..d9628143460c 100644 --- a/packages/google-auth/tests/transport/test_requests.py +++ b/packages/google-auth/tests/transport/test_requests.py @@ -14,6 +14,7 @@ import datetime import functools +import http.client as http_client import os import sys @@ -23,7 +24,6 @@ import pytest # type: ignore import requests import requests.adapters -from six.moves import http_client from google.auth import environment_vars from google.auth import exceptions diff --git a/packages/google-auth/tests/transport/test_urllib3.py b/packages/google-auth/tests/transport/test_urllib3.py index 5bdfac95e13d..e83230032185 100644 --- a/packages/google-auth/tests/transport/test_urllib3.py +++ b/packages/google-auth/tests/transport/test_urllib3.py @@ -12,13 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. +import http.client as http_client import os import sys import mock import OpenSSL import pytest # type: ignore -from six.moves import http_client import urllib3 # type: ignore from google.auth import environment_vars diff --git a/packages/google-auth/tests_async/oauth2/test__client_async.py b/packages/google-auth/tests_async/oauth2/test__client_async.py index 402083672e1c..add1b4e60bc7 100644 --- a/packages/google-auth/tests_async/oauth2/test__client_async.py +++ b/packages/google-auth/tests_async/oauth2/test__client_async.py @@ -13,13 +13,12 @@ # limitations under the License. import datetime +import http.client as http_client import json +import urllib import mock import pytest # type: ignore -import six -from six.moves import http_client -from six.moves import urllib from google.auth import _helpers from google.auth import _jwt_async as jwt @@ -202,7 +201,7 @@ def verify_request_params(request, params): request_body = request.call_args[1]["body"].decode("utf-8") request_params = urllib.parse.parse_qs(request_body) - for key, value in six.iteritems(params): + for key, value in params.items(): assert request_params[key][0] == value diff --git a/packages/google-auth/tests_async/transport/async_compliance.py b/packages/google-auth/tests_async/transport/async_compliance.py index 36fe7a3015cc..f3a36079cfe2 100644 --- a/packages/google-auth/tests_async/transport/async_compliance.py +++ b/packages/google-auth/tests_async/transport/async_compliance.py @@ -12,12 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. +import http.client as http_client import time import flask # type: ignore import pytest # type: ignore from pytest_localserver.http import WSGIServer # type: ignore -from six.moves import http_client from google.auth import exceptions from tests.transport import compliance From af0eb18178a8cb015182e4e2c85ddc3afe4273b3 Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Thu, 13 Jul 2023 09:43:01 -0700 Subject: [PATCH 741/966] fix: Skip checking projectid on cred if env var is set (#1349) * fix: Skip checking projectid on cred if env var is set * add test for legacy project --------- Co-authored-by: Carl Lundin <108372512+clundin25@users.noreply.github.com> --- packages/google-auth/google/auth/_default.py | 7 +-- packages/google-auth/tests/test__default.py | 55 ++++++++++++++++++++ 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index f77fa180874a..63009dfb8626 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -660,24 +660,25 @@ def default(scopes=None, request=None, quota_project_id=None, default_scopes=Non credentials, scopes, default_scopes=default_scopes ) + effective_project_id = explicit_project_id or project_id + # For external account credentials, scopes are required to determine # the project ID. Try to get the project ID again if not yet # determined. - if not project_id and callable( + if not effective_project_id and callable( getattr(credentials, "get_project_id", None) ): if request is None: import google.auth.transport.requests request = google.auth.transport.requests.Request() - project_id = credentials.get_project_id(request=request) + effective_project_id = credentials.get_project_id(request=request) if quota_project_id and isinstance( credentials, CredentialsWithQuotaProject ): credentials = credentials.with_quota_project(quota_project_id) - effective_project_id = explicit_project_id or project_id if not effective_project_id: _LOGGER.warning( "No project ID could be determined. Consider running " diff --git a/packages/google-auth/tests/test__default.py b/packages/google-auth/tests/test__default.py index affbb7624d22..4f59c5497db2 100644 --- a/packages/google-auth/tests/test__default.py +++ b/packages/google-auth/tests/test__default.py @@ -1030,6 +1030,61 @@ def test_default_environ_external_credentials_identity_pool_impersonated( assert project_id is mock.sentinel.project_id assert credentials.scopes == ["https://www.google.com/calendar/feeds"] + # The credential.get_project_id should have been used in _get_external_account_credentials and default + assert get_project_id.call_count == 2 + + +@EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH +@mock.patch.dict(os.environ) +def test_default_environ_external_credentials_project_from_env( + get_project_id, monkeypatch, tmpdir +): + project_from_env = "project_from_env" + os.environ[environment_vars.PROJECT] = project_from_env + + config_file = tmpdir.join("config.json") + config_file.write(json.dumps(IMPERSONATED_IDENTITY_POOL_DATA)) + monkeypatch.setenv(environment_vars.CREDENTIALS, str(config_file)) + + credentials, project_id = _default.default( + scopes=["https://www.google.com/calendar/feeds"] + ) + + assert isinstance(credentials, identity_pool.Credentials) + assert not credentials.is_user + assert not credentials.is_workforce_pool + assert project_id == project_from_env + assert credentials.scopes == ["https://www.google.com/calendar/feeds"] + + # The credential.get_project_id should have been used only in _get_external_account_credentials + assert get_project_id.call_count == 1 + + +@EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH +@mock.patch.dict(os.environ) +def test_default_environ_external_credentials_legacy_project_from_env( + get_project_id, monkeypatch, tmpdir +): + project_from_env = "project_from_env" + os.environ[environment_vars.LEGACY_PROJECT] = project_from_env + + config_file = tmpdir.join("config.json") + config_file.write(json.dumps(IMPERSONATED_IDENTITY_POOL_DATA)) + monkeypatch.setenv(environment_vars.CREDENTIALS, str(config_file)) + + credentials, project_id = _default.default( + scopes=["https://www.google.com/calendar/feeds"] + ) + + assert isinstance(credentials, identity_pool.Credentials) + assert not credentials.is_user + assert not credentials.is_workforce_pool + assert project_id == project_from_env + assert credentials.scopes == ["https://www.google.com/calendar/feeds"] + + # The credential.get_project_id should have been used only in _get_external_account_credentials + assert get_project_id.call_count == 1 + @EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH def test_default_environ_external_credentials_aws_impersonated( From fa7a3bb77a230de7d9e604acbcdec82e61871705 Mon Sep 17 00:00:00 2001 From: wj-chen Date: Tue, 8 Aug 2023 14:17:16 -0700 Subject: [PATCH 742/966] feat: add get_bq_config_path() to _cloud_sdk.py (#1358) feat: add get_bq_config_path() to _cloud_sdk.py --- .../google-auth/google/auth/_cloud_sdk.py | 43 +++++++++--- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes packages/google-auth/tests/test__cloud_sdk.py | 61 ++++++++++++++---- 3 files changed, 83 insertions(+), 21 deletions(-) diff --git a/packages/google-auth/google/auth/_cloud_sdk.py b/packages/google-auth/google/auth/_cloud_sdk.py index a94411949bdf..7cd41434a983 100644 --- a/packages/google-auth/google/auth/_cloud_sdk.py +++ b/packages/google-auth/google/auth/_cloud_sdk.py @@ -23,7 +23,9 @@ # The ~/.config subdirectory containing gcloud credentials. -_CONFIG_DIRECTORY = "gcloud" +_CONFIG_DIRECTORY_GCLOUD = "gcloud" +# The ~/.config subdirectory containing gcloud credentials for bq. +_CONFIG_DIRECTORY_BQ = "bq" # Windows systems store config at %APPDATA%\gcloud _WINDOWS_CONFIG_ROOT_ENV_VAR = "APPDATA" # The name of the file in the Cloud SDK config that contains default @@ -42,11 +44,14 @@ ) -def get_config_path(): - """Returns the absolute path the the Cloud SDK's configuration directory. +def get_config_path(config_directory=_CONFIG_DIRECTORY_GCLOUD): + """Returns the absolute path of the given configuration directory. + + Args: + config_directory: The absolute path of the configuration directory. Returns: - str: The Cloud SDK config path. + str: The config path. """ # If the path is explicitly set, return that. try: @@ -54,20 +59,38 @@ def get_config_path(): except KeyError: pass - # Non-windows systems store this at ~/.config/gcloud + # Non-windows systems store this at ~/.config/. if os.name != "nt": - return os.path.join(os.path.expanduser("~"), ".config", _CONFIG_DIRECTORY) - # Windows systems store config at %APPDATA%\gcloud + return os.path.join(os.path.expanduser("~"), ".config", config_directory) + # Windows systems store config at %APPDATA%\. else: try: return os.path.join( - os.environ[_WINDOWS_CONFIG_ROOT_ENV_VAR], _CONFIG_DIRECTORY + os.environ[_WINDOWS_CONFIG_ROOT_ENV_VAR], config_directory ) except KeyError: # This should never happen unless someone is really # messing with things, but we'll cover the case anyway. drive = os.environ.get("SystemDrive", "C:") - return os.path.join(drive, "\\", _CONFIG_DIRECTORY) + return os.path.join(drive, "\\", config_directory) + + +def get_gcloud_config_path(): + """Returns the absolute path of Cloud CLI's configuration directory. + + Returns: + str: The Cloud CLI config path. + """ + return get_config_path(config_directory=_CONFIG_DIRECTORY_GCLOUD) + + +def get_bq_config_path(): + """Returns the absolute path of bq's configuration directory. + + Returns: + str: The bq config path. + """ + return get_config_path(config_directory=_CONFIG_DIRECTORY_BQ) def get_application_default_credentials_path(): @@ -78,7 +101,7 @@ def get_application_default_credentials_path(): Returns: str: The full path to application default credentials. """ - config_path = get_config_path() + config_path = get_gcloud_config_path() return os.path.join(config_path, _CREDENTIALS_FILENAME) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 15589afb983a2fcdb5dd8972bfd455aac6483b38..8ff3d379221c9cb03aa0f66ef25b79aa2b6eefd5 100644 GIT binary patch literal 10324 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

      l&7O?pc!E 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 literal 10324 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 diff --git a/packages/google-auth/tests/test__cloud_sdk.py b/packages/google-auth/tests/test__cloud_sdk.py index e45c65bd96e1..4eaf1b18fd72 100644 --- a/packages/google-auth/tests/test__cloud_sdk.py +++ b/packages/google-auth/tests/test__cloud_sdk.py @@ -112,40 +112,79 @@ def test_get_application_default_credentials_path(get_config_dir): ) -def test_get_config_path_env_var(monkeypatch): +def test_get_gcloud_config_path_env_var(monkeypatch): config_path_sentinel = "config_path" monkeypatch.setenv(environment_vars.CLOUD_SDK_CONFIG_DIR, config_path_sentinel) - config_path = _cloud_sdk.get_config_path() + config_path = _cloud_sdk.get_gcloud_config_path() assert config_path == config_path_sentinel @mock.patch("os.path.expanduser") -def test_get_config_path_unix(expanduser): +def test_get_gcloud_config_path_unix(expanduser): expanduser.side_effect = lambda path: path - config_path = _cloud_sdk.get_config_path() + config_path = _cloud_sdk.get_gcloud_config_path() - assert os.path.split(config_path) == ("~/.config", _cloud_sdk._CONFIG_DIRECTORY) + assert os.path.split(config_path) == ( + "~/.config", + _cloud_sdk._CONFIG_DIRECTORY_GCLOUD, + ) + + +@mock.patch("os.name", new="nt") +def test_get_gcloud_config_path_windows(monkeypatch): + appdata = "appdata" + monkeypatch.setenv(_cloud_sdk._WINDOWS_CONFIG_ROOT_ENV_VAR, appdata) + + config_path = _cloud_sdk.get_gcloud_config_path() + + assert os.path.split(config_path) == (appdata, _cloud_sdk._CONFIG_DIRECTORY_GCLOUD) + + +@mock.patch("os.name", new="nt") +def test_get_gcloud_config_path_no_appdata(monkeypatch): + monkeypatch.delenv(_cloud_sdk._WINDOWS_CONFIG_ROOT_ENV_VAR, raising=False) + monkeypatch.setenv("SystemDrive", "G:") + + config_path = _cloud_sdk.get_gcloud_config_path() + + assert os.path.split(config_path) == ("G:/\\", _cloud_sdk._CONFIG_DIRECTORY_GCLOUD) + + +def test_get_bq_config_path_env_var(monkeypatch): + config_path_sentinel = "config_path" + monkeypatch.setenv(environment_vars.CLOUD_SDK_CONFIG_DIR, config_path_sentinel) + config_path = _cloud_sdk.get_bq_config_path() + assert config_path == config_path_sentinel + + +@mock.patch("os.path.expanduser") +def test_get_bq_config_path_unix(expanduser): + expanduser.side_effect = lambda path: path + + config_path = _cloud_sdk.get_bq_config_path() + + assert os.path.split(config_path) == ("~/.config", _cloud_sdk._CONFIG_DIRECTORY_BQ) @mock.patch("os.name", new="nt") -def test_get_config_path_windows(monkeypatch): +def test_get_bq_config_path_windows(monkeypatch): appdata = "appdata" monkeypatch.setenv(_cloud_sdk._WINDOWS_CONFIG_ROOT_ENV_VAR, appdata) - config_path = _cloud_sdk.get_config_path() + config_path = _cloud_sdk.get_bq_config_path() - assert os.path.split(config_path) == (appdata, _cloud_sdk._CONFIG_DIRECTORY) + assert os.path.split(config_path) == (appdata, _cloud_sdk._CONFIG_DIRECTORY_BQ) @mock.patch("os.name", new="nt") -def test_get_config_path_no_appdata(monkeypatch): +def test_get_bq_config_path_no_appdata(monkeypatch): monkeypatch.delenv(_cloud_sdk._WINDOWS_CONFIG_ROOT_ENV_VAR, raising=False) monkeypatch.setenv("SystemDrive", "G:") - config_path = _cloud_sdk.get_config_path() + config_path = _cloud_sdk.get_bq_config_path() - assert os.path.split(config_path) == ("G:/\\", _cloud_sdk._CONFIG_DIRECTORY) + assert os.path.split(config_path) == ("G:/\\", _cloud_sdk._CONFIG_DIRECTORY_BQ) @mock.patch("os.name", new="nt") From f9f98986abd4cadd8dadefb946d77e223fe5b36e Mon Sep 17 00:00:00 2001 From: Carl Lundin <108372512+clundin25@users.noreply.github.com> Date: Tue, 8 Aug 2023 15:00:23 -0700 Subject: [PATCH 743/966] chore: Refresh system test creds. (#1366) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 8ff3d379221c9cb03aa0f66ef25b79aa2b6eefd5..2ce1a1a0e050e4920c5698a698c7a41ad31cb2dc 100644 GIT binary patch literal 10324 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
        l&7O?pc!E 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 From 9cbfae401a6bf282488a730ca8fe1e1239a152c4 Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Tue, 8 Aug 2023 21:32:17 -0400 Subject: [PATCH 744/966] build: [autoapprove] bump cryptography from 41.0.2 to 41.0.3 (#1363) Source-Link: https://github.com/googleapis/synthtool/commit/352b9d4c068ce7c05908172af128b294073bf53c Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:3e3800bb100af5d7f9e810d48212b37812c1856d20ffeafb99ebe66461b61fc7 Co-authored-by: Owl Bot Co-authored-by: Anthonios Partheniou --- .../google-auth/.github/.OwlBot.lock.yaml | 4 +- .../.kokoro/docker/docs/Dockerfile | 2 +- .../google-auth/.kokoro/populate-secrets.sh | 2 +- packages/google-auth/.kokoro/publish-docs.sh | 2 +- packages/google-auth/.kokoro/release.sh | 2 +- packages/google-auth/.kokoro/requirements.txt | 56 ++++++++++--------- .../.kokoro/test-samples-against-head.sh | 2 +- .../google-auth/.kokoro/test-samples-impl.sh | 2 +- packages/google-auth/.kokoro/test-samples.sh | 2 +- packages/google-auth/.kokoro/trampoline.sh | 2 +- packages/google-auth/.kokoro/trampoline_v2.sh | 2 +- packages/google-auth/.trampolinerc | 4 +- 12 files changed, 42 insertions(+), 40 deletions(-) diff --git a/packages/google-auth/.github/.OwlBot.lock.yaml b/packages/google-auth/.github/.OwlBot.lock.yaml index 98994f474104..a3da1b0d4cd3 100644 --- a/packages/google-auth/.github/.OwlBot.lock.yaml +++ b/packages/google-auth/.github/.OwlBot.lock.yaml @@ -13,5 +13,5 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:2d816f26f728ac8b24248741e7d4c461c09764ef9f7be3684d557c9632e46dbd -# created: 2023-06-28T17:03:33.371210701Z + digest: sha256:3e3800bb100af5d7f9e810d48212b37812c1856d20ffeafb99ebe66461b61fc7 +# created: 2023-08-02T10:53:29.114535628Z diff --git a/packages/google-auth/.kokoro/docker/docs/Dockerfile b/packages/google-auth/.kokoro/docker/docs/Dockerfile index f8137d0ae497..8e39a2cc438d 100644 --- a/packages/google-auth/.kokoro/docker/docs/Dockerfile +++ b/packages/google-auth/.kokoro/docker/docs/Dockerfile @@ -1,4 +1,4 @@ -# Copyright 2020 Google LLC +# Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/.kokoro/populate-secrets.sh b/packages/google-auth/.kokoro/populate-secrets.sh index f52514257ef0..6f3972140e80 100755 --- a/packages/google-auth/.kokoro/populate-secrets.sh +++ b/packages/google-auth/.kokoro/populate-secrets.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright 2020 Google LLC. +# Copyright 2023 Google LLC. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/.kokoro/publish-docs.sh b/packages/google-auth/.kokoro/publish-docs.sh index 1c4d62370042..9eafe0be3bba 100755 --- a/packages/google-auth/.kokoro/publish-docs.sh +++ b/packages/google-auth/.kokoro/publish-docs.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright 2020 Google LLC +# Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/.kokoro/release.sh b/packages/google-auth/.kokoro/release.sh index ce4182c3444b..020ec9baf6b6 100755 --- a/packages/google-auth/.kokoro/release.sh +++ b/packages/google-auth/.kokoro/release.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright 2020 Google LLC +# Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/.kokoro/requirements.txt b/packages/google-auth/.kokoro/requirements.txt index c7929db6d152..029bd342de94 100644 --- a/packages/google-auth/.kokoro/requirements.txt +++ b/packages/google-auth/.kokoro/requirements.txt @@ -20,9 +20,9 @@ cachetools==5.2.0 \ --hash=sha256:6a94c6402995a99c3970cc7e4884bb60b4a8639938157eeed436098bf9831757 \ --hash=sha256:f9f17d2aec496a9aa6b76f53e3b614c965223c061982d434d160f930c698a9db # via google-auth -certifi==2022.12.7 \ - --hash=sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3 \ - --hash=sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18 +certifi==2023.7.22 \ + --hash=sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082 \ + --hash=sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9 # via requests cffi==1.15.1 \ --hash=sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5 \ @@ -113,26 +113,30 @@ commonmark==0.9.1 \ --hash=sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60 \ --hash=sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9 # via rich -cryptography==41.0.0 \ - --hash=sha256:0ddaee209d1cf1f180f1efa338a68c4621154de0afaef92b89486f5f96047c55 \ - --hash=sha256:14754bcdae909d66ff24b7b5f166d69340ccc6cb15731670435efd5719294895 \ - --hash=sha256:344c6de9f8bda3c425b3a41b319522ba3208551b70c2ae00099c205f0d9fd3be \ - --hash=sha256:34d405ea69a8b34566ba3dfb0521379b210ea5d560fafedf9f800a9a94a41928 \ - --hash=sha256:3680248309d340fda9611498a5319b0193a8dbdb73586a1acf8109d06f25b92d \ - --hash=sha256:3c5ef25d060c80d6d9f7f9892e1d41bb1c79b78ce74805b8cb4aa373cb7d5ec8 \ - --hash=sha256:4ab14d567f7bbe7f1cdff1c53d5324ed4d3fc8bd17c481b395db224fb405c237 \ - --hash=sha256:5c1f7293c31ebc72163a9a0df246f890d65f66b4a40d9ec80081969ba8c78cc9 \ - --hash=sha256:6b71f64beeea341c9b4f963b48ee3b62d62d57ba93eb120e1196b31dc1025e78 \ - --hash=sha256:7d92f0248d38faa411d17f4107fc0bce0c42cae0b0ba5415505df72d751bf62d \ - --hash=sha256:8362565b3835ceacf4dc8f3b56471a2289cf51ac80946f9087e66dc283a810e0 \ - --hash=sha256:84a165379cb9d411d58ed739e4af3396e544eac190805a54ba2e0322feb55c46 \ - --hash=sha256:88ff107f211ea696455ea8d911389f6d2b276aabf3231bf72c8853d22db755c5 \ - --hash=sha256:9f65e842cb02550fac96536edb1d17f24c0a338fd84eaf582be25926e993dde4 \ - --hash=sha256:a4fc68d1c5b951cfb72dfd54702afdbbf0fb7acdc9b7dc4301bbf2225a27714d \ - --hash=sha256:b7f2f5c525a642cecad24ee8670443ba27ac1fab81bba4cc24c7b6b41f2d0c75 \ - --hash=sha256:b846d59a8d5a9ba87e2c3d757ca019fa576793e8758174d3868aecb88d6fc8eb \ - --hash=sha256:bf8fc66012ca857d62f6a347007e166ed59c0bc150cefa49f28376ebe7d992a2 \ - --hash=sha256:f5d0bf9b252f30a31664b6f64432b4730bb7038339bd18b1fafe129cfc2be9be +cryptography==41.0.3 \ + --hash=sha256:0d09fb5356f975974dbcb595ad2d178305e5050656affb7890a1583f5e02a306 \ + --hash=sha256:23c2d778cf829f7d0ae180600b17e9fceea3c2ef8b31a99e3c694cbbf3a24b84 \ + --hash=sha256:3fb248989b6363906827284cd20cca63bb1a757e0a2864d4c1682a985e3dca47 \ + --hash=sha256:41d7aa7cdfded09b3d73a47f429c298e80796c8e825ddfadc84c8a7f12df212d \ + --hash=sha256:42cb413e01a5d36da9929baa9d70ca90d90b969269e5a12d39c1e0d475010116 \ + --hash=sha256:4c2f0d35703d61002a2bbdcf15548ebb701cfdd83cdc12471d2bae80878a4207 \ + --hash=sha256:4fd871184321100fb400d759ad0cddddf284c4b696568204d281c902fc7b0d81 \ + --hash=sha256:5259cb659aa43005eb55a0e4ff2c825ca111a0da1814202c64d28a985d33b087 \ + --hash=sha256:57a51b89f954f216a81c9d057bf1a24e2f36e764a1ca9a501a6964eb4a6800dd \ + --hash=sha256:652627a055cb52a84f8c448185922241dd5217443ca194d5739b44612c5e6507 \ + --hash=sha256:67e120e9a577c64fe1f611e53b30b3e69744e5910ff3b6e97e935aeb96005858 \ + --hash=sha256:6af1c6387c531cd364b72c28daa29232162010d952ceb7e5ca8e2827526aceae \ + --hash=sha256:6d192741113ef5e30d89dcb5b956ef4e1578f304708701b8b73d38e3e1461f34 \ + --hash=sha256:7efe8041897fe7a50863e51b77789b657a133c75c3b094e51b5e4b5cec7bf906 \ + --hash=sha256:84537453d57f55a50a5b6835622ee405816999a7113267739a1b4581f83535bd \ + --hash=sha256:8f09daa483aedea50d249ef98ed500569841d6498aa9c9f4b0531b9964658922 \ + --hash=sha256:95dd7f261bb76948b52a5330ba5202b91a26fbac13ad0e9fc8a3ac04752058c7 \ + --hash=sha256:a74fbcdb2a0d46fe00504f571a2a540532f4c188e6ccf26f1f178480117b33c4 \ + --hash=sha256:a983e441a00a9d57a4d7c91b3116a37ae602907a7618b882c8013b5762e80574 \ + --hash=sha256:ab8de0d091acbf778f74286f4989cf3d1528336af1b59f3e5d2ebca8b5fe49e1 \ + --hash=sha256:aeb57c421b34af8f9fe830e1955bf493a86a7996cc1338fe41b30047d16e962c \ + --hash=sha256:ce785cf81a7bdade534297ef9e490ddff800d956625020ab2ec2780a556c313e \ + --hash=sha256:d0d651aa754ef58d75cec6edfbd21259d93810b73f6ec246436a21b7841908de # via # gcp-releasetool # secretstorage @@ -392,9 +396,9 @@ pycparser==2.21 \ --hash=sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 \ --hash=sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206 # via cffi -pygments==2.13.0 \ - --hash=sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1 \ - --hash=sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42 +pygments==2.15.0 \ + --hash=sha256:77a3299119af881904cd5ecd1ac6a66214b6e9bed1f2db16993b54adede64094 \ + --hash=sha256:f7e36cffc4c517fbc252861b9a6e4644ca0e5abadf9a113c72d1358ad09b9500 # via # readme-renderer # rich diff --git a/packages/google-auth/.kokoro/test-samples-against-head.sh b/packages/google-auth/.kokoro/test-samples-against-head.sh index ba3a707b040c..63ac41dfae1d 100755 --- a/packages/google-auth/.kokoro/test-samples-against-head.sh +++ b/packages/google-auth/.kokoro/test-samples-against-head.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright 2020 Google LLC +# Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/.kokoro/test-samples-impl.sh b/packages/google-auth/.kokoro/test-samples-impl.sh index 2c6500cae0b9..5a0f5fab6a89 100755 --- a/packages/google-auth/.kokoro/test-samples-impl.sh +++ b/packages/google-auth/.kokoro/test-samples-impl.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright 2021 Google LLC +# Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/.kokoro/test-samples.sh b/packages/google-auth/.kokoro/test-samples.sh index 11c042d342d7..50b35a48c190 100755 --- a/packages/google-auth/.kokoro/test-samples.sh +++ b/packages/google-auth/.kokoro/test-samples.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright 2020 Google LLC +# Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/.kokoro/trampoline.sh b/packages/google-auth/.kokoro/trampoline.sh index f39236e943a8..d85b1f267693 100755 --- a/packages/google-auth/.kokoro/trampoline.sh +++ b/packages/google-auth/.kokoro/trampoline.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright 2017 Google Inc. +# Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/.kokoro/trampoline_v2.sh b/packages/google-auth/.kokoro/trampoline_v2.sh index 4af6cdc26dbc..59a7cf3a9373 100755 --- a/packages/google-auth/.kokoro/trampoline_v2.sh +++ b/packages/google-auth/.kokoro/trampoline_v2.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Copyright 2020 Google LLC +# Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/.trampolinerc b/packages/google-auth/.trampolinerc index 0eee72ab62aa..a7dfeb42c6d0 100644 --- a/packages/google-auth/.trampolinerc +++ b/packages/google-auth/.trampolinerc @@ -1,4 +1,4 @@ -# Copyright 2020 Google LLC +# Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,8 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Template for .trampolinerc - # Add required env vars here. required_envvars+=( ) From 54cd466cac516dde2541e5184f9ba9d0ea7766ff Mon Sep 17 00:00:00 2001 From: Carl Lundin <108372512+clundin25@users.noreply.github.com> Date: Wed, 9 Aug 2023 10:10:47 -0700 Subject: [PATCH 745/966] Revert "feat: add get_bq_config_path() to _cloud_sdk.py (#1358)" (#1367) This reverts commit 9f52f665247ada59278ffddaaef3ada9e419154c. --- .../google-auth/google/auth/_cloud_sdk.py | 43 +++---------- packages/google-auth/tests/test__cloud_sdk.py | 61 ++++--------------- 2 files changed, 21 insertions(+), 83 deletions(-) diff --git a/packages/google-auth/google/auth/_cloud_sdk.py b/packages/google-auth/google/auth/_cloud_sdk.py index 7cd41434a983..a94411949bdf 100644 --- a/packages/google-auth/google/auth/_cloud_sdk.py +++ b/packages/google-auth/google/auth/_cloud_sdk.py @@ -23,9 +23,7 @@ # The ~/.config subdirectory containing gcloud credentials. -_CONFIG_DIRECTORY_GCLOUD = "gcloud" -# The ~/.config subdirectory containing gcloud credentials for bq. -_CONFIG_DIRECTORY_BQ = "bq" +_CONFIG_DIRECTORY = "gcloud" # Windows systems store config at %APPDATA%\gcloud _WINDOWS_CONFIG_ROOT_ENV_VAR = "APPDATA" # The name of the file in the Cloud SDK config that contains default @@ -44,14 +42,11 @@ ) -def get_config_path(config_directory=_CONFIG_DIRECTORY_GCLOUD): - """Returns the absolute path of the given configuration directory. - - Args: - config_directory: The absolute path of the configuration directory. +def get_config_path(): + """Returns the absolute path the the Cloud SDK's configuration directory. Returns: - str: The config path. + str: The Cloud SDK config path. """ # If the path is explicitly set, return that. try: @@ -59,38 +54,20 @@ def get_config_path(config_directory=_CONFIG_DIRECTORY_GCLOUD): except KeyError: pass - # Non-windows systems store this at ~/.config/. + # Non-windows systems store this at ~/.config/gcloud if os.name != "nt": - return os.path.join(os.path.expanduser("~"), ".config", config_directory) - # Windows systems store config at %APPDATA%\. + return os.path.join(os.path.expanduser("~"), ".config", _CONFIG_DIRECTORY) + # Windows systems store config at %APPDATA%\gcloud else: try: return os.path.join( - os.environ[_WINDOWS_CONFIG_ROOT_ENV_VAR], config_directory + os.environ[_WINDOWS_CONFIG_ROOT_ENV_VAR], _CONFIG_DIRECTORY ) except KeyError: # This should never happen unless someone is really # messing with things, but we'll cover the case anyway. drive = os.environ.get("SystemDrive", "C:") - return os.path.join(drive, "\\", config_directory) - - -def get_gcloud_config_path(): - """Returns the absolute path of Cloud CLI's configuration directory. - - Returns: - str: The Cloud CLI config path. - """ - return get_config_path(config_directory=_CONFIG_DIRECTORY_GCLOUD) - - -def get_bq_config_path(): - """Returns the absolute path of bq's configuration directory. - - Returns: - str: The bq config path. - """ - return get_config_path(config_directory=_CONFIG_DIRECTORY_BQ) + return os.path.join(drive, "\\", _CONFIG_DIRECTORY) def get_application_default_credentials_path(): @@ -101,7 +78,7 @@ def get_application_default_credentials_path(): Returns: str: The full path to application default credentials. """ - config_path = get_gcloud_config_path() + config_path = get_config_path() return os.path.join(config_path, _CREDENTIALS_FILENAME) diff --git a/packages/google-auth/tests/test__cloud_sdk.py b/packages/google-auth/tests/test__cloud_sdk.py index 4eaf1b18fd72..e45c65bd96e1 100644 --- a/packages/google-auth/tests/test__cloud_sdk.py +++ b/packages/google-auth/tests/test__cloud_sdk.py @@ -112,79 +112,40 @@ def test_get_application_default_credentials_path(get_config_dir): ) -def test_get_gcloud_config_path_env_var(monkeypatch): +def test_get_config_path_env_var(monkeypatch): config_path_sentinel = "config_path" monkeypatch.setenv(environment_vars.CLOUD_SDK_CONFIG_DIR, config_path_sentinel) - config_path = _cloud_sdk.get_gcloud_config_path() + config_path = _cloud_sdk.get_config_path() assert config_path == config_path_sentinel @mock.patch("os.path.expanduser") -def test_get_gcloud_config_path_unix(expanduser): +def test_get_config_path_unix(expanduser): expanduser.side_effect = lambda path: path - config_path = _cloud_sdk.get_gcloud_config_path() + config_path = _cloud_sdk.get_config_path() - assert os.path.split(config_path) == ( - "~/.config", - _cloud_sdk._CONFIG_DIRECTORY_GCLOUD, - ) - - -@mock.patch("os.name", new="nt") -def test_get_gcloud_config_path_windows(monkeypatch): - appdata = "appdata" - monkeypatch.setenv(_cloud_sdk._WINDOWS_CONFIG_ROOT_ENV_VAR, appdata) - - config_path = _cloud_sdk.get_gcloud_config_path() - - assert os.path.split(config_path) == (appdata, _cloud_sdk._CONFIG_DIRECTORY_GCLOUD) - - -@mock.patch("os.name", new="nt") -def test_get_gcloud_config_path_no_appdata(monkeypatch): - monkeypatch.delenv(_cloud_sdk._WINDOWS_CONFIG_ROOT_ENV_VAR, raising=False) - monkeypatch.setenv("SystemDrive", "G:") - - config_path = _cloud_sdk.get_gcloud_config_path() - - assert os.path.split(config_path) == ("G:/\\", _cloud_sdk._CONFIG_DIRECTORY_GCLOUD) - - -def test_get_bq_config_path_env_var(monkeypatch): - config_path_sentinel = "config_path" - monkeypatch.setenv(environment_vars.CLOUD_SDK_CONFIG_DIR, config_path_sentinel) - config_path = _cloud_sdk.get_bq_config_path() - assert config_path == config_path_sentinel - - -@mock.patch("os.path.expanduser") -def test_get_bq_config_path_unix(expanduser): - expanduser.side_effect = lambda path: path - - config_path = _cloud_sdk.get_bq_config_path() - - assert os.path.split(config_path) == ("~/.config", _cloud_sdk._CONFIG_DIRECTORY_BQ) + assert os.path.split(config_path) == ("~/.config", _cloud_sdk._CONFIG_DIRECTORY) @mock.patch("os.name", new="nt") -def test_get_bq_config_path_windows(monkeypatch): +def test_get_config_path_windows(monkeypatch): appdata = "appdata" monkeypatch.setenv(_cloud_sdk._WINDOWS_CONFIG_ROOT_ENV_VAR, appdata) - config_path = _cloud_sdk.get_bq_config_path() + config_path = _cloud_sdk.get_config_path() - assert os.path.split(config_path) == (appdata, _cloud_sdk._CONFIG_DIRECTORY_BQ) + assert os.path.split(config_path) == (appdata, _cloud_sdk._CONFIG_DIRECTORY) @mock.patch("os.name", new="nt") -def test_get_bq_config_path_no_appdata(monkeypatch): +def test_get_config_path_no_appdata(monkeypatch): monkeypatch.delenv(_cloud_sdk._WINDOWS_CONFIG_ROOT_ENV_VAR, raising=False) monkeypatch.setenv("SystemDrive", "G:") - config_path = _cloud_sdk.get_bq_config_path() + config_path = _cloud_sdk.get_config_path() - assert os.path.split(config_path) == ("G:/\\", _cloud_sdk._CONFIG_DIRECTORY_BQ) + assert os.path.split(config_path) == ("G:/\\", _cloud_sdk._CONFIG_DIRECTORY) @mock.patch("os.name", new="nt") From 2ad701d83dd946a234c75cb52091b38f963451a4 Mon Sep 17 00:00:00 2001 From: Carl Lundin <108372512+clundin25@users.noreply.github.com> Date: Wed, 23 Aug 2023 14:17:08 -0700 Subject: [PATCH 746/966] chore: Remove support for Python 3.6 (#1354) * chore: Remove support for Python 3.6 * chore: Refresh system test creds. * Revert "chore: Remove support for Python 3.6" This reverts commit 3bfd7ba2679b613e1f02e8559a7ded4abda9ef23. * Add deprecation notice for 3.6 and 3.7. * chore: Refresh system test creds. * Revert "Revert "chore: Remove support for Python 3.6"" This reverts commit c9f006b1e7e901f28f2dc52cb5377b17c89ff610. * Revert "Add deprecation notice for 3.6 and 3.7." This reverts commit fb6b619899db0229ffaf5d7889af0470cda35095. * Bump mypy Python version. * PR feedback. --- .../.kokoro/samples/python3.6/common.cfg | 40 ------------------ .../.kokoro/samples/python3.6/continuous.cfg | 7 --- .../samples/python3.6/periodic-head.cfg | 11 ----- .../.kokoro/samples/python3.6/periodic.cfg | 6 --- .../.kokoro/samples/python3.6/presubmit.cfg | 6 --- packages/google-auth/CONTRIBUTING.rst | 2 +- packages/google-auth/README.rst | 5 ++- packages/google-auth/google/auth/pluggable.py | 4 +- packages/google-auth/mypy.ini | 2 +- packages/google-auth/noxfile.py | 2 +- packages/google-auth/setup.py | 3 +- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../google-auth/testing/constraints-3.6.txt | 13 ------ packages/google-auth/tests/test_pluggable.py | 4 +- 14 files changed, 12 insertions(+), 93 deletions(-) delete mode 100644 packages/google-auth/.kokoro/samples/python3.6/common.cfg delete mode 100644 packages/google-auth/.kokoro/samples/python3.6/continuous.cfg delete mode 100644 packages/google-auth/.kokoro/samples/python3.6/periodic-head.cfg delete mode 100644 packages/google-auth/.kokoro/samples/python3.6/periodic.cfg delete mode 100644 packages/google-auth/.kokoro/samples/python3.6/presubmit.cfg delete mode 100644 packages/google-auth/testing/constraints-3.6.txt diff --git a/packages/google-auth/.kokoro/samples/python3.6/common.cfg b/packages/google-auth/.kokoro/samples/python3.6/common.cfg deleted file mode 100644 index 57feb84b3a2b..000000000000 --- a/packages/google-auth/.kokoro/samples/python3.6/common.cfg +++ /dev/null @@ -1,40 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -# Build logs will be here -action { - define_artifacts { - regex: "**/*sponge_log.xml" - } -} - -# Specify which tests to run -env_vars: { - key: "RUN_TESTS_SESSION" - value: "py-3.6" -} - -# Declare build specific Cloud project. -env_vars: { - key: "BUILD_SPECIFIC_GCLOUD_PROJECT" - value: "python-docs-samples-tests-py36" -} - -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/google-auth-library-python/.kokoro/test-samples.sh" -} - -# Configure the docker image for kokoro-trampoline. -env_vars: { - key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-kokoro-resources/python-samples-testing-docker" -} - -# Download secrets for samples -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/python-docs-samples" - -# Download trampoline resources. -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" - -# Use the trampoline script to run in docker. -build_file: "google-auth-library-python/.kokoro/trampoline_v2.sh" \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.6/continuous.cfg b/packages/google-auth/.kokoro/samples/python3.6/continuous.cfg deleted file mode 100644 index 7218af1499e5..000000000000 --- a/packages/google-auth/.kokoro/samples/python3.6/continuous.cfg +++ /dev/null @@ -1,7 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -env_vars: { - key: "INSTALL_LIBRARY_FROM_SOURCE" - value: "True" -} - diff --git a/packages/google-auth/.kokoro/samples/python3.6/periodic-head.cfg b/packages/google-auth/.kokoro/samples/python3.6/periodic-head.cfg deleted file mode 100644 index 83eace873ccb..000000000000 --- a/packages/google-auth/.kokoro/samples/python3.6/periodic-head.cfg +++ /dev/null @@ -1,11 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -env_vars: { - key: "INSTALL_LIBRARY_FROM_SOURCE" - value: "True" -} - -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/google-auth-library-python/.kokoro/test-samples-against-head.sh" -} diff --git a/packages/google-auth/.kokoro/samples/python3.6/periodic.cfg b/packages/google-auth/.kokoro/samples/python3.6/periodic.cfg deleted file mode 100644 index 71cd1e597e38..000000000000 --- a/packages/google-auth/.kokoro/samples/python3.6/periodic.cfg +++ /dev/null @@ -1,6 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -env_vars: { - key: "INSTALL_LIBRARY_FROM_SOURCE" - value: "False" -} diff --git a/packages/google-auth/.kokoro/samples/python3.6/presubmit.cfg b/packages/google-auth/.kokoro/samples/python3.6/presubmit.cfg deleted file mode 100644 index a1c8d9759c88..000000000000 --- a/packages/google-auth/.kokoro/samples/python3.6/presubmit.cfg +++ /dev/null @@ -1,6 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -env_vars: { - key: "INSTALL_LIBRARY_FROM_SOURCE" - value: "True" -} \ No newline at end of file diff --git a/packages/google-auth/CONTRIBUTING.rst b/packages/google-auth/CONTRIBUTING.rst index 255f33c74ff2..3d07261ec8ad 100644 --- a/packages/google-auth/CONTRIBUTING.rst +++ b/packages/google-auth/CONTRIBUTING.rst @@ -19,7 +19,7 @@ A few notes on making changes to ``google-auth-library-python``. using ``nox -s docgen``. - The change must work fully on the following CPython versions: - 3.6, 3.7, 3.8, 3.9, 3.10 across macOS, Linux, and Windows. + 3.7, 3.8, 3.9, 3.10 across macOS, Linux, and Windows. - The codebase *must* have 100% test statement coverage after each commit. You can test coverage via ``nox -e cover``. diff --git a/packages/google-auth/README.rst b/packages/google-auth/README.rst index d8f28b39afc1..cdd19bed50de 100644 --- a/packages/google-auth/README.rst +++ b/packages/google-auth/README.rst @@ -35,7 +35,7 @@ Note that the extras pyopenssl and enterprise_cert should not be used together b Supported Python Versions ^^^^^^^^^^^^^^^^^^^^^^^^^ -Python >= 3.6 +Python >= 3.7 Unsupported Python Versions ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -45,6 +45,9 @@ Unsupported Python Versions - Python 3.5: The last version of this library with support for Python 3.5 was `google.auth == 1.23.0`. +- Python 3.6: The last version of this library with support for Python 3.6 + was `google.auth == 2.22.0`. + Documentation ------------- diff --git a/packages/google-auth/google/auth/pluggable.py b/packages/google-auth/google/auth/pluggable.py index 3f8d47c94cad..53b4eac5b4c2 100644 --- a/packages/google-auth/google/auth/pluggable.py +++ b/packages/google-auth/google/auth/pluggable.py @@ -193,7 +193,7 @@ def retrieve_subject_token(self, request): if not _helpers.is_python_3(): raise exceptions.RefreshError( - "Pluggable auth is only supported for python 3.6+" + "Pluggable auth is only supported for python 3.7+" ) # Inject env vars. @@ -255,7 +255,7 @@ def revoke(self, request): if not _helpers.is_python_3(): raise exceptions.RefreshError( - "Pluggable auth is only supported for python 3.6+" + "Pluggable auth is only supported for python 3.7+" ) # Inject variables diff --git a/packages/google-auth/mypy.ini b/packages/google-auth/mypy.ini index 4505b485436b..574c5aed394b 100644 --- a/packages/google-auth/mypy.ini +++ b/packages/google-auth/mypy.ini @@ -1,3 +1,3 @@ [mypy] -python_version = 3.6 +python_version = 3.7 namespace_packages = True diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index 48b0347d1b4a..4ec313407987 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -84,7 +84,7 @@ def mypy(session): session.run("mypy", "-p", "google", "-p", "tests", "-p", "tests_async") -@nox.session(python=["3.6", "3.7", "3.8", "3.9", "3.10", "3.11"]) +@nox.session(python=["3.7", "3.8", "3.9", "3.10", "3.11"]) def unit(session): constraints_path = str( CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt" diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index b3c0e6de2240..922c505e637c 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -62,12 +62,11 @@ ), install_requires=DEPENDENCIES, extras_require=extras, - python_requires=">=3.6", + python_requires=">=3.7", license="Apache 2.0", keywords="google auth oauth client", classifiers=[ "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 2ce1a1a0e050e4920c5698a698c7a41ad31cb2dc..f63d2895ad921e7c4faaacbf731f8c247400e26a 100644 GIT binary patch literal 10324 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 literal 10324 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 m4@e9lSR1Rhugk~5?;yc= 1.14.0, < 2.0.0dev", -# Then this file should have foo==1.14.0 -cachetools==2.0.0 -pyasn1-modules==0.2.1 -setuptools==40.3.0 -rsa==3.1.4 -aiohttp==3.6.2 -requests==2.20.0 diff --git a/packages/google-auth/tests/test_pluggable.py b/packages/google-auth/tests/test_pluggable.py index 9b0da927316f..1773b40d409f 100644 --- a/packages/google-auth/tests/test_pluggable.py +++ b/packages/google-auth/tests/test_pluggable.py @@ -1233,7 +1233,7 @@ def test_retrieve_subject_token_python_2(self): with pytest.raises(exceptions.RefreshError) as excinfo: _ = credentials.retrieve_subject_token(None) - assert excinfo.match(r"Pluggable auth is only supported for python 3.6+") + assert excinfo.match(r"Pluggable auth is only supported for python 3.7+") @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) def test_revoke_subject_token_python_2(self): @@ -1247,4 +1247,4 @@ def test_revoke_subject_token_python_2(self): with pytest.raises(exceptions.RefreshError) as excinfo: _ = credentials.revoke(None) - assert excinfo.match(r"Pluggable auth is only supported for python 3.6+") + assert excinfo.match(r"Pluggable auth is only supported for python 3.7+") From fb0c95e78af40378ce3e8a0776b9251ae28545dd Mon Sep 17 00:00:00 2001 From: Jack Wotherspoon Date: Thu, 31 Aug 2023 13:56:13 -0400 Subject: [PATCH 747/966] chore: remove duplicate variable (#1375) The global variable [`_DEFAULT_TOKEN_LIFETIME_SECS`](https://togithub.com/googleapis/google-auth-library-python/blob/main/google/auth/impersonated_credentials.py#L40-L61) is set twice. Can we get rid of this duplicate? --- .../google/auth/impersonated_credentials.py | 2 -- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 2 files changed, 2 deletions(-) diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index 816b9ea31734..8284653674be 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -37,8 +37,6 @@ from google.auth import jwt from google.auth import metrics -_DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds - _IAM_SCOPE = ["https://www.googleapis.com/auth/iam"] _IAM_ENDPOINT = ( diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index f63d2895ad921e7c4faaacbf731f8c247400e26a..5485e9242ad16076c7cec7ff119c8175733c9176 100644 GIT binary patch literal 10324 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 From ba6b15ee6fe823145956d9a6e191cd356181d795 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Thu, 31 Aug 2023 14:38:16 -0700 Subject: [PATCH 748/966] fix: missing ssj for impersonate cred (#1377) --- .../google/auth/impersonated_credentials.py | 7 +++++++ .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../tests/test_impersonated_credentials.py | 9 +++++++++ 3 files changed, 16 insertions(+) diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index 8284653674be..c272a3ca28bd 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -226,6 +226,13 @@ def __init__( # their original scopes modified. if isinstance(self._source_credentials, credentials.Scoped): self._source_credentials = self._source_credentials.with_scopes(_IAM_SCOPE) + # If the source credential is service account and self signed jwt + # is needed, we need to create a jwt credential inside it + if ( + hasattr(self._source_credentials, "_create_self_signed_jwt") + and self._source_credentials._always_use_jwt_access + ): + self._source_credentials._create_self_signed_jwt(None) self._target_principal = target_principal self._target_scopes = target_scopes self._delegates = delegates diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 5485e9242ad16076c7cec7ff119c8175733c9176..c1792fbd9fa93c7be906b26738ffb3be57698fa5 100644 GIT binary patch literal 10324 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 mbg7HflT%c(%Ik*Dr% Date: Wed, 6 Sep 2023 18:00:24 +0200 Subject: [PATCH 749/966] fix: Make external_account resistant to string type 'expires_in' responses from non-compliant services (#1379) Make external_account resistant to string type 'expires_in' responses from non-compliant services. This is to complete [#1208](https://togithub.com/googleapis/google-auth-library-python/pull/1208/). A client run into this error. --- .../google/auth/external_account.py | 9 ++++++++- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../tests/test_external_account.py | 7 ++++--- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/google-auth/google/auth/external_account.py b/packages/google-auth/google/auth/external_account.py index 001b26f7ff6a..c45e6f2133fb 100644 --- a/packages/google-auth/google/auth/external_account.py +++ b/packages/google-auth/google/auth/external_account.py @@ -385,7 +385,14 @@ def refresh(self, request): additional_headers=additional_headers, ) self.token = response_data.get("access_token") - lifetime = datetime.timedelta(seconds=response_data.get("expires_in")) + expires_in = response_data.get("expires_in") + # Some services do not respect the OAUTH2.0 RFC and send expires_in as a + # JSON String. + if isinstance(expires_in, str): + expires_in = int(expires_in) + + lifetime = datetime.timedelta(seconds=expires_in) + self.expiry = now + lifetime @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index c1792fbd9fa93c7be906b26738ffb3be57698fa5..fd667ebc8ffeacc7e07feedd0545ec975a64bac9 100644 GIT binary patch literal 10324 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 ma9KA6ZPtOjHd4QY6X9D?`PCyKw7C3b_@0 Date: Thu, 7 Sep 2023 10:00:36 -0700 Subject: [PATCH 750/966] fix: expose universe domain in credentials (#1380) * fix: expose universe domain in credentials * update --- packages/google-auth/google/auth/credentials.py | 8 ++++++++ packages/google-auth/google/oauth2/credentials.py | 1 + packages/google-auth/tests/oauth2/test_service_account.py | 4 ++-- packages/google-auth/tests/test_credentials.py | 1 + packages/google-auth/tests/test_external_account.py | 7 +++++++ 5 files changed, 19 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/google/auth/credentials.py b/packages/google-auth/google/auth/credentials.py index 3ae9c40114da..80a2a5c0b4cd 100644 --- a/packages/google-auth/google/auth/credentials.py +++ b/packages/google-auth/google/auth/credentials.py @@ -54,6 +54,9 @@ def __init__(self): self._trust_boundary = None """Optional[str]: Encoded string representation of credentials trust boundary.""" + self._universe_domain = "googleapis.com" + """Optional[str]: The universe domain value, default is googleapis.com + """ @property def expired(self): @@ -85,6 +88,11 @@ def quota_project_id(self): """Project to use for quota and billing purposes.""" return self._quota_project_id + @property + def universe_domain(self): + """The universe domain value.""" + return self._universe_domain + @abc.abstractmethod def refresh(self, request): """Refreshes the access token. diff --git a/packages/google-auth/google/oauth2/credentials.py b/packages/google-auth/google/oauth2/credentials.py index 5becb7c1df3f..4643fdbea67a 100644 --- a/packages/google-auth/google/oauth2/credentials.py +++ b/packages/google-auth/google/oauth2/credentials.py @@ -173,6 +173,7 @@ def __setstate__(self, d): self._rapt_token = d.get("_rapt_token") self._enable_reauth_refresh = d.get("_enable_reauth_refresh") self._trust_boundary = d.get("_trust_boundary") + self._universe_domain = d.get("_universe_domain") # The refresh_handler setter should be used to repopulate this. self._refresh_handler = None diff --git a/packages/google-auth/tests/oauth2/test_service_account.py b/packages/google-auth/tests/oauth2/test_service_account.py index 058fc3f7d6f9..b963b157c5e2 100644 --- a/packages/google-auth/tests/oauth2/test_service_account.py +++ b/packages/google-auth/tests/oauth2/test_service_account.py @@ -70,7 +70,7 @@ def test_constructor_no_universe_domain(self): credentials = service_account.Credentials( SIGNER, self.SERVICE_ACCOUNT_EMAIL, self.TOKEN_URI, universe_domain=None ) - assert credentials._universe_domain == service_account._DEFAULT_UNIVERSE_DOMAIN + assert credentials.universe_domain == service_account._DEFAULT_UNIVERSE_DOMAIN def test_from_service_account_info(self): credentials = service_account.Credentials.from_service_account_info( @@ -88,7 +88,7 @@ def test_from_service_account_info_non_gdu(self): SERVICE_ACCOUNT_INFO_NON_GDU ) - assert credentials._universe_domain == FAKE_UNIVERSE_DOMAIN + assert credentials.universe_domain == FAKE_UNIVERSE_DOMAIN assert credentials._always_use_jwt_access def test_from_service_account_info_args(self): diff --git a/packages/google-auth/tests/test_credentials.py b/packages/google-auth/tests/test_credentials.py index 594c3e58a5fc..99235cda6150 100644 --- a/packages/google-auth/tests/test_credentials.py +++ b/packages/google-auth/tests/test_credentials.py @@ -42,6 +42,7 @@ def test_credentials_constructor(): assert not credentials.expiry assert not credentials.expired assert not credentials.valid + assert credentials.universe_domain == "googleapis.com" def test_expired_and_valid(): diff --git a/packages/google-auth/tests/test_external_account.py b/packages/google-auth/tests/test_external_account.py index 9e9d1f58a727..0b165bc70bba 100644 --- a/packages/google-auth/tests/test_external_account.py +++ b/packages/google-auth/tests/test_external_account.py @@ -498,6 +498,13 @@ def test_info(self): "universe_domain": "dummy_universe.com", } + def test_universe_domain(self): + credentials = self.make_credentials(universe_domain="dummy_universe.com") + assert credentials.universe_domain == "dummy_universe.com" + + credentials = self.make_credentials() + assert credentials.universe_domain == external_account._DEFAULT_UNIVERSE_DOMAIN + def test_info_workforce_pool(self): credentials = self.make_workforce_pool_credentials( workforce_pool_user_project=self.WORKFORCE_POOL_USER_PROJECT From 475849d531486705db478a2231c7a06dba80391e Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Mon, 11 Sep 2023 11:28:34 -0700 Subject: [PATCH 751/966] chore: update sys test cred (#1381) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index fd667ebc8ffeacc7e07feedd0545ec975a64bac9..1409f0b83ea76b9350d02f23b239193e70f9d695 100644 GIT binary patch literal 10324 zcmV-aD67{BB>?tKRTJ>2VnA+MwiFJ8eP-k~yD^D&BlhTpO&(@W?2{jPt#cBpPyn$b zkPKG(P@OkyK_gp`Vg`)V|4E=zF6?3-¦_n&9u@dX=Zx#b{#Q>{|+P+JQ#uST5u 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 literal 10324 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 Date: Mon, 11 Sep 2023 11:55:44 -0700 Subject: [PATCH 752/966] chore(main): release 2.23.0 (#1350) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 16 ++++++++++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 62b97cba8036..b9c164ba4d23 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,22 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.23.0](https://github.com/googleapis/google-auth-library-python/compare/v2.22.0...v2.23.0) (2023-09-11) + + +### Features + +* Add get_bq_config_path() to _cloud_sdk.py ([9f52f66](https://github.com/googleapis/google-auth-library-python/commit/9f52f665247ada59278ffddaaef3ada9e419154c)) +* Add get_bq_config_path() to _cloud_sdk.py ([#1358](https://github.com/googleapis/google-auth-library-python/issues/1358)) ([9f52f66](https://github.com/googleapis/google-auth-library-python/commit/9f52f665247ada59278ffddaaef3ada9e419154c)) + + +### Bug Fixes + +* Expose universe domain in credentials ([#1380](https://github.com/googleapis/google-auth-library-python/issues/1380)) ([8b8fce6](https://github.com/googleapis/google-auth-library-python/commit/8b8fce6a1e1ca6e0199cb5f15a90af477bf1c853)) +* Make external_account resistant to string type 'expires_in' responses from non-compliant services ([#1379](https://github.com/googleapis/google-auth-library-python/issues/1379)) ([01d3770](https://github.com/googleapis/google-auth-library-python/commit/01d37706d6750c20952cf01b6a616b23aafa5be9)) +* Missing ssj for impersonate cred ([#1377](https://github.com/googleapis/google-auth-library-python/issues/1377)) ([7d453dc](https://github.com/googleapis/google-auth-library-python/commit/7d453dc6408b908e24312a7bd3dc380ad43220be)) +* Skip checking projectid on cred if env var is set ([#1349](https://github.com/googleapis/google-auth-library-python/issues/1349)) ([a4135a3](https://github.com/googleapis/google-auth-library-python/commit/a4135a3e9620a2cbf99957858c13780b92ff707c)) + ## [2.22.0](https://github.com/googleapis/google-auth-library-python/compare/v2.21.0...v2.22.0) (2023-07-06) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 1e886a1122b9..491187e6d78d 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.22.0" +__version__ = "2.23.0" From 6443fb94664e814d2e950b13744b7c2e56371ff4 Mon Sep 17 00:00:00 2001 From: Jin Date: Tue, 19 Sep 2023 11:53:14 -0700 Subject: [PATCH 753/966] chore: update token (#1385) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 1409f0b83ea76b9350d02f23b239193e70f9d695..aa2aaf370ba9bb174f79c66dd309fd839578f736 100644 GIT binary patch literal 10324 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 literal 10324 zcmV-aD67{BB>?tKRTJ>2VnA+MwiFJ8eP-k~yD^D&BlhTpO&(@W?2{jPt#cBpPyn$b zkPKG(P@OkyK_gp`Vg`)V|4E=zF6?3-¦_n&9u@dX=Zx#b{#Q>{|+P+JQ#uST5u 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 From a81324a066e122e81e68e8db1171eb5ac52aea40 Mon Sep 17 00:00:00 2001 From: Jin Date: Thu, 21 Sep 2023 09:29:24 -0700 Subject: [PATCH 754/966] fix: Trust boundary meta header renaming and using the schema from backend team. (#1384) * fix: rename the trust boundary metaheader into * fix comments --- .../google-auth/google/auth/credentials.py | 20 ++++++-- .../google/auth/external_account.py | 5 +- .../google/oauth2/service_account.py | 2 +- packages/google-auth/tests/test_aws.py | 4 +- .../google-auth/tests/test_credentials.py | 12 ++--- .../tests/test_external_account.py | 46 +++++++++---------- .../google-auth/tests/test_identity_pool.py | 2 +- 7 files changed, 54 insertions(+), 37 deletions(-) diff --git a/packages/google-auth/google/auth/credentials.py b/packages/google-auth/google/auth/credentials.py index 80a2a5c0b4cd..800781c4089a 100644 --- a/packages/google-auth/google/auth/credentials.py +++ b/packages/google-auth/google/auth/credentials.py @@ -52,8 +52,9 @@ def __init__(self): self._quota_project_id = None """Optional[str]: Project to use for quota and billing purposes.""" self._trust_boundary = None - """Optional[str]: Encoded string representation of credentials trust - boundary.""" + """Optional[dict]: Cache of a trust boundary response which has a list + of allowed regions and an encoded string representation of credentials + trust boundary.""" self._universe_domain = "googleapis.com" """Optional[str]: The universe domain value, default is googleapis.com """ @@ -135,8 +136,21 @@ def apply(self, headers, token=None): headers["authorization"] = "Bearer {}".format( _helpers.from_bytes(token or self.token) ) + """Trust boundary value will be a cached value from global lookup. + + The response of trust boundary will be a list of regions and a hex + encoded representation. + + An example of global lookup response: + { + "locations": [ + "us-central1", "us-east1", "europe-west1", "asia-east1" + ] + "encoded_locations": "0xA30" + } + """ if self._trust_boundary is not None: - headers["x-identity-trust-boundary"] = self._trust_boundary + headers["x-allowed-locations"] = self._trust_boundary["encoded_locations"] if self.quota_project_id: headers["x-goog-user-project"] = self.quota_project_id diff --git a/packages/google-auth/google/auth/external_account.py b/packages/google-auth/google/auth/external_account.py index c45e6f2133fb..28b004c5fad6 100644 --- a/packages/google-auth/google/auth/external_account.py +++ b/packages/google-auth/google/auth/external_account.py @@ -132,7 +132,10 @@ def __init__( self._default_scopes = default_scopes self._workforce_pool_user_project = workforce_pool_user_project self._universe_domain = universe_domain or _DEFAULT_UNIVERSE_DOMAIN - self._trust_boundary = "0" # expose a placeholder trust boundary value. + self._trust_boundary = { + "locations": [], + "encoded_locations": "0x0", + } # expose a placeholder trust boundary value. if self._client_id: self._client_auth = utils.ClientAuthentication( diff --git a/packages/google-auth/google/oauth2/service_account.py b/packages/google-auth/google/oauth2/service_account.py index e08899f8e539..803b13070917 100644 --- a/packages/google-auth/google/oauth2/service_account.py +++ b/packages/google-auth/google/oauth2/service_account.py @@ -196,7 +196,7 @@ def __init__( self._additional_claims = additional_claims else: self._additional_claims = {} - self._trust_boundary = "0" + self._trust_boundary = {"locations": [], "encoded_locations": "0x0"} @classmethod def _from_signer_and_info(cls, signer, info, **kwargs): diff --git a/packages/google-auth/tests/test_aws.py b/packages/google-auth/tests/test_aws.py index 39138ab12e04..db2e984100f6 100644 --- a/packages/google-auth/tests/test_aws.py +++ b/packages/google-auth/tests/test_aws.py @@ -1969,7 +1969,7 @@ def test_refresh_success_with_impersonation_ignore_default_scopes( "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]), "x-goog-user-project": QUOTA_PROJECT_ID, "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, - "x-identity-trust-boundary": "0", + "x-allowed-locations": "0x0", } impersonation_request_data = { "delegates": None, @@ -2066,7 +2066,7 @@ def test_refresh_success_with_impersonation_use_default_scopes( "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]), "x-goog-user-project": QUOTA_PROJECT_ID, "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, - "x-identity-trust-boundary": "0", + "x-allowed-locations": "0x0", } impersonation_request_data = { "delegates": None, diff --git a/packages/google-auth/tests/test_credentials.py b/packages/google-auth/tests/test_credentials.py index 99235cda6150..5eee35c98e3a 100644 --- a/packages/google-auth/tests/test_credentials.py +++ b/packages/google-auth/tests/test_credentials.py @@ -81,7 +81,7 @@ def test_before_request(): assert credentials.valid assert credentials.token == "token" assert headers["authorization"] == "Bearer token" - assert "x-identity-trust-boundary" not in headers + assert "x-allowed-locations" not in headers request = "token2" headers = {} @@ -91,13 +91,13 @@ def test_before_request(): assert credentials.valid assert credentials.token == "token" assert headers["authorization"] == "Bearer token" - assert "x-identity-trust-boundary" not in headers + assert "x-allowed-locations" not in headers def test_before_request_with_trust_boundary(): - DUMMY_BOUNDARY = "00110101" + DUMMY_BOUNDARY = "0xA30" credentials = CredentialsImpl() - credentials._trust_boundary = DUMMY_BOUNDARY + credentials._trust_boundary = {"locations": [], "encoded_locations": DUMMY_BOUNDARY} request = "token" headers = {} @@ -106,7 +106,7 @@ def test_before_request_with_trust_boundary(): assert credentials.valid assert credentials.token == "token" assert headers["authorization"] == "Bearer token" - assert headers["x-identity-trust-boundary"] == DUMMY_BOUNDARY + assert headers["x-allowed-locations"] == DUMMY_BOUNDARY request = "token2" headers = {} @@ -116,7 +116,7 @@ def test_before_request_with_trust_boundary(): assert credentials.valid assert credentials.token == "token" assert headers["authorization"] == "Bearer token" - assert headers["x-identity-trust-boundary"] == DUMMY_BOUNDARY + assert headers["x-allowed-locations"] == DUMMY_BOUNDARY def test_before_request_metrics(): diff --git a/packages/google-auth/tests/test_external_account.py b/packages/google-auth/tests/test_external_account.py index 0b165bc70bba..6f6e18b2cf18 100644 --- a/packages/google-auth/tests/test_external_account.py +++ b/packages/google-auth/tests/test_external_account.py @@ -833,7 +833,7 @@ def test_refresh_impersonation_without_client_auth_success( "Content-Type": "application/json", "authorization": "Bearer {}".format(token_response["access_token"]), "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, - "x-identity-trust-boundary": "0", + "x-allowed-locations": "0x0", } impersonation_request_data = { "delegates": None, @@ -915,7 +915,7 @@ def test_refresh_workforce_impersonation_without_client_auth_success( "Content-Type": "application/json", "authorization": "Bearer {}".format(token_response["access_token"]), "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, - "x-identity-trust-boundary": "0", + "x-allowed-locations": "0x0", } impersonation_request_data = { "delegates": None, @@ -1134,7 +1134,7 @@ def test_refresh_impersonation_with_client_auth_success_ignore_default_scopes( "Content-Type": "application/json", "authorization": "Bearer {}".format(token_response["access_token"]), "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, - "x-identity-trust-boundary": "0", + "x-allowed-locations": "0x0", } impersonation_request_data = { "delegates": None, @@ -1218,7 +1218,7 @@ def test_refresh_impersonation_with_client_auth_success_use_default_scopes( "Content-Type": "application/json", "authorization": "Bearer {}".format(token_response["access_token"]), "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, - "x-identity-trust-boundary": "0", + "x-allowed-locations": "0x0", } impersonation_request_data = { "delegates": None, @@ -1274,7 +1274,7 @@ def test_apply_without_quota_project_id(self): assert headers == { "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]), - "x-identity-trust-boundary": "0", + "x-allowed-locations": "0x0", } def test_apply_workforce_without_quota_project_id(self): @@ -1291,7 +1291,7 @@ def test_apply_workforce_without_quota_project_id(self): assert headers == { "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]), - "x-identity-trust-boundary": "0", + "x-allowed-locations": "0x0", } def test_apply_impersonation_without_quota_project_id(self): @@ -1323,7 +1323,7 @@ def test_apply_impersonation_without_quota_project_id(self): assert headers == { "authorization": "Bearer {}".format(impersonation_response["accessToken"]), - "x-identity-trust-boundary": "0", + "x-allowed-locations": "0x0", } def test_apply_with_quota_project_id(self): @@ -1340,7 +1340,7 @@ def test_apply_with_quota_project_id(self): "other": "header-value", "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]), "x-goog-user-project": self.QUOTA_PROJECT_ID, - "x-identity-trust-boundary": "0", + "x-allowed-locations": "0x0", } def test_apply_impersonation_with_quota_project_id(self): @@ -1375,7 +1375,7 @@ def test_apply_impersonation_with_quota_project_id(self): "other": "header-value", "authorization": "Bearer {}".format(impersonation_response["accessToken"]), "x-goog-user-project": self.QUOTA_PROJECT_ID, - "x-identity-trust-boundary": "0", + "x-allowed-locations": "0x0", } def test_before_request(self): @@ -1391,7 +1391,7 @@ def test_before_request(self): assert headers == { "other": "header-value", "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]), - "x-identity-trust-boundary": "0", + "x-allowed-locations": "0x0", } # Second call shouldn't call refresh. @@ -1400,7 +1400,7 @@ def test_before_request(self): assert headers == { "other": "header-value", "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]), - "x-identity-trust-boundary": "0", + "x-allowed-locations": "0x0", } def test_before_request_workforce(self): @@ -1418,7 +1418,7 @@ def test_before_request_workforce(self): assert headers == { "other": "header-value", "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]), - "x-identity-trust-boundary": "0", + "x-allowed-locations": "0x0", } # Second call shouldn't call refresh. @@ -1427,7 +1427,7 @@ def test_before_request_workforce(self): assert headers == { "other": "header-value", "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]), - "x-identity-trust-boundary": "0", + "x-allowed-locations": "0x0", } def test_before_request_impersonation(self): @@ -1458,7 +1458,7 @@ def test_before_request_impersonation(self): assert headers == { "other": "header-value", "authorization": "Bearer {}".format(impersonation_response["accessToken"]), - "x-identity-trust-boundary": "0", + "x-allowed-locations": "0x0", } # Second call shouldn't call refresh. @@ -1467,7 +1467,7 @@ def test_before_request_impersonation(self): assert headers == { "other": "header-value", "authorization": "Bearer {}".format(impersonation_response["accessToken"]), - "x-identity-trust-boundary": "0", + "x-allowed-locations": "0x0", } @mock.patch("google.auth._helpers.utcnow") @@ -1495,7 +1495,7 @@ def test_before_request_expired(self, utcnow): # Cached token should be used. assert headers == { "authorization": "Bearer token", - "x-identity-trust-boundary": "0", + "x-allowed-locations": "0x0", } # Next call should simulate 1 second passed. @@ -1509,7 +1509,7 @@ def test_before_request_expired(self, utcnow): # New token should be retrieved. assert headers == { "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]), - "x-identity-trust-boundary": "0", + "x-allowed-locations": "0x0", } @mock.patch("google.auth._helpers.utcnow") @@ -1552,7 +1552,7 @@ def test_before_request_impersonation_expired(self, utcnow): # Cached token should be used. assert headers == { "authorization": "Bearer token", - "x-identity-trust-boundary": "0", + "x-allowed-locations": "0x0", } # Next call should simulate 1 second passed. This will trigger the expiration @@ -1567,7 +1567,7 @@ def test_before_request_impersonation_expired(self, utcnow): # New token should be retrieved. assert headers == { "authorization": "Bearer {}".format(impersonation_response["accessToken"]), - "x-identity-trust-boundary": "0", + "x-allowed-locations": "0x0", } @pytest.mark.parametrize( @@ -1666,7 +1666,7 @@ def test_get_project_id_cloud_resource_manager_success( "x-goog-user-project": self.QUOTA_PROJECT_ID, "authorization": "Bearer {}".format(token_response["access_token"]), "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, - "x-identity-trust-boundary": "0", + "x-allowed-locations": "0x0", } impersonation_request_data = { "delegates": None, @@ -1720,7 +1720,7 @@ def test_get_project_id_cloud_resource_manager_success( "authorization": "Bearer {}".format( impersonation_response["accessToken"] ), - "x-identity-trust-boundary": "0", + "x-allowed-locations": "0x0", }, ) @@ -1792,7 +1792,7 @@ def test_workforce_pool_get_project_id_cloud_resource_manager_success( "authorization": "Bearer {}".format( self.SUCCESS_RESPONSE["access_token"] ), - "x-identity-trust-boundary": "0", + "x-allowed-locations": "0x0", }, ) @@ -1842,7 +1842,7 @@ def test_refresh_impersonation_with_lifetime( "Content-Type": "application/json", "authorization": "Bearer {}".format(token_response["access_token"]), "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, - "x-identity-trust-boundary": "0", + "x-allowed-locations": "0x0", } impersonation_request_data = { "delegates": None, diff --git a/packages/google-auth/tests/test_identity_pool.py b/packages/google-auth/tests/test_identity_pool.py index e469cf731595..8821df088d6f 100644 --- a/packages/google-auth/tests/test_identity_pool.py +++ b/packages/google-auth/tests/test_identity_pool.py @@ -319,7 +319,7 @@ def assert_underlying_credentials_refresh( "Content-Type": "application/json", "authorization": "Bearer {}".format(token_response["access_token"]), "x-goog-api-client": metrics_header_value, - "x-identity-trust-boundary": "0", + "x-allowed-locations": "0x0", } impersonation_request_data = { "delegates": None, From 2f55396dd7610cc4bd5cc1db5ba6d0097cba8353 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Mon, 25 Sep 2023 11:30:36 -0700 Subject: [PATCH 755/966] chore: update sys test cred (#1388) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10323 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index aa2aaf370ba9bb174f79c66dd309fd839578f736..84904e0ad3b93139fd67b35431cab9381acdf56f 100644 GIT binary patch literal 10323 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 literal 10324 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 From 8c1f81ed107f78e82e65be7b23084311f01998fc Mon Sep 17 00:00:00 2001 From: DanieleQuasimodo <134523456+DanieleQuasimodo@users.noreply.github.com> Date: Mon, 25 Sep 2023 21:12:47 +0200 Subject: [PATCH 756/966] Fix: Less restrictive content-type header check for google authentication (ignores charset) (#1382) * fix: added function for parsing content-type headers * fix: improved too restrictive content-type check (now ignores charset param) * test: Added some new tests for the parse_content_type function in _helpers and its use in _metadata * style: Linted code --------- Co-authored-by: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> --- packages/google-auth/google/auth/_helpers.py | 23 ++++++++++++++++ .../google/auth/compute_engine/_metadata.py | 5 +++- .../tests/compute_engine/test__metadata.py | 18 +++++++++++++ packages/google-auth/tests/test__helpers.py | 26 +++++++++++++++++++ 4 files changed, 71 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/_helpers.py b/packages/google-auth/google/auth/_helpers.py index ad2c095f28d3..f321bc834d0d 100644 --- a/packages/google-auth/google/auth/_helpers.py +++ b/packages/google-auth/google/auth/_helpers.py @@ -17,6 +17,7 @@ import base64 import calendar import datetime +from email.message import Message import sys import urllib @@ -63,6 +64,28 @@ def decorator(method): return decorator +def parse_content_type(header_value): + """Parse a 'content-type' header value to get just the plain media-type (without parameters). + + This is done using the class Message from email.message as suggested in PEP 594 + (because the cgi is now deprecated and will be removed in python 3.13, + see https://peps.python.org/pep-0594/#cgi). + + Args: + header_value (str): The value of a 'content-type' header as a string. + + Returns: + str: A string with just the lowercase media-type from the parsed 'content-type' header. + If the provided content-type is not parsable, returns 'text/plain', + the default value for textual files. + """ + m = Message() + m["content-type"] = header_value + return ( + m.get_content_type() + ) # Despite the name, actually returns just the media-type + + def utcnow(): """Returns the current UTC datetime. diff --git a/packages/google-auth/google/auth/compute_engine/_metadata.py b/packages/google-auth/google/auth/compute_engine/_metadata.py index 04abe178f58a..1b2f5161a9bb 100644 --- a/packages/google-auth/google/auth/compute_engine/_metadata.py +++ b/packages/google-auth/google/auth/compute_engine/_metadata.py @@ -218,7 +218,10 @@ def get( if response.status == http_client.OK: content = _helpers.from_bytes(response.data) - if response.headers["content-type"] == "application/json": + if ( + _helpers.parse_content_type(response.headers["content-type"]) + == "application/json" + ): try: return json.loads(content) except ValueError as caught_exc: diff --git a/packages/google-auth/tests/compute_engine/test__metadata.py b/packages/google-auth/tests/compute_engine/test__metadata.py index a940feb255ba..f0e4329792b1 100644 --- a/packages/google-auth/tests/compute_engine/test__metadata.py +++ b/packages/google-auth/tests/compute_engine/test__metadata.py @@ -176,6 +176,24 @@ def test_get_success_json(): assert result[key] == value +def test_get_success_json_content_type_charset(): + key, value = "foo", "bar" + + data = json.dumps({key: value}) + request = make_request( + data, headers={"content-type": "application/json; charset=UTF-8"} + ) + + result = _metadata.get(request, PATH) + + request.assert_called_once_with( + method="GET", + url=_metadata._METADATA_ROOT + PATH, + headers=_metadata._METADATA_HEADERS, + ) + assert result[key] == value + + def test_get_success_retry(): key, value = "foo", "bar" diff --git a/packages/google-auth/tests/test__helpers.py b/packages/google-auth/tests/test__helpers.py index c1f1d812e57e..c9a3847ac48c 100644 --- a/packages/google-auth/tests/test__helpers.py +++ b/packages/google-auth/tests/test__helpers.py @@ -51,6 +51,32 @@ def func2(): # pragma: NO COVER _helpers.copy_docstring(SourceClass)(func2) +def test_parse_content_type_plain(): + assert _helpers.parse_content_type("text/html") == "text/html" + assert _helpers.parse_content_type("application/xml") == "application/xml" + assert _helpers.parse_content_type("application/json") == "application/json" + + +def test_parse_content_type_with_parameters(): + content_type_html = "text/html; charset=UTF-8" + content_type_xml = "application/xml; charset=UTF-16; version=1.0" + content_type_json = "application/json; charset=UTF-8; indent=2" + assert _helpers.parse_content_type(content_type_html) == "text/html" + assert _helpers.parse_content_type(content_type_xml) == "application/xml" + assert _helpers.parse_content_type(content_type_json) == "application/json" + + +def test_parse_content_type_missing_or_broken(): + content_type_foo = None + content_type_bar = "" + content_type_baz = "1234" + content_type_qux = " ; charset=UTF-8" + assert _helpers.parse_content_type(content_type_foo) == "text/plain" + assert _helpers.parse_content_type(content_type_bar) == "text/plain" + assert _helpers.parse_content_type(content_type_baz) == "text/plain" + assert _helpers.parse_content_type(content_type_qux) == "text/plain" + + def test_utcnow(): assert isinstance(_helpers.utcnow(), datetime.datetime) From 57c465ebecb5e461e32c2a85431a99ffd7b836cf Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Tue, 26 Sep 2023 14:00:03 -0700 Subject: [PATCH 757/966] fix: update urllib3 to >= 2.0.5 (#1389) * fix: update urllib3 to >= 2.0.5 * update * update sys test cred --- .../google/auth/transport/urllib3.py | 2 +- packages/google-auth/setup.py | 3 +-- .../google-auth/system_tests/secrets.tar.enc | Bin 10323 -> 10324 bytes 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/google-auth/google/auth/transport/urllib3.py b/packages/google-auth/google/auth/transport/urllib3.py index 053d6f7b728e..bc4de4d1414a 100644 --- a/packages/google-auth/google/auth/transport/urllib3.py +++ b/packages/google-auth/google/auth/transport/urllib3.py @@ -179,7 +179,7 @@ def _make_mutual_tls_http(cert, key): return http -class AuthorizedHttp(urllib3.request.RequestMethods): +class AuthorizedHttp(urllib3._request_methods.RequestMethods): # type: ignore """A urllib3 HTTP class with credentials. This class is used to perform requests to API endpoints that require diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 922c505e637c..4ec9b19c6568 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -25,8 +25,7 @@ # rsa==4.5 is the last version to support 2.7 # https://github.com/sybrenstuvel/python-rsa/issues/152#issuecomment-643470233 "rsa>=3.1.4,<5", - # install enum34 to support 2.7. enum34 only works up to python version 3.3. - "urllib3<2.0", + "urllib3>=2.0.5", ) extras = { diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 84904e0ad3b93139fd67b35431cab9381acdf56f..a537437340f2e4bf90d15645bcd7d60fe718e91f 100644 GIT binary patch literal 10324 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 From de5efaf1dc021f7ea4f618a7899853ee5e579638 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 26 Sep 2023 14:38:22 -0700 Subject: [PATCH 758/966] chore(main): release 2.23.1 (#1386) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 9 +++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index b9c164ba4d23..d2b69ade6b48 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,15 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.23.1](https://github.com/googleapis/google-auth-library-python/compare/v2.23.0...v2.23.1) (2023-09-26) + + +### Bug Fixes + +* Less restrictive content-type header check for google authentication (ignores charset) ([#1382](https://github.com/googleapis/google-auth-library-python/issues/1382)) ([7039beb](https://github.com/googleapis/google-auth-library-python/commit/7039beb63b8644be748cfc2fc79a2b8b643cda9f)) +* Trust boundary meta header renaming and using the schema from backend team. ([#1384](https://github.com/googleapis/google-auth-library-python/issues/1384)) ([2503d4a](https://github.com/googleapis/google-auth-library-python/commit/2503d4a50995d4f2756846a17b33997273ace5f1)) +* Update urllib3 to >= 2.0.5 ([#1389](https://github.com/googleapis/google-auth-library-python/issues/1389)) ([a99f3bb](https://github.com/googleapis/google-auth-library-python/commit/a99f3bbf97c07a87203b7471817cfb2a1662293d)) + ## [2.23.0](https://github.com/googleapis/google-auth-library-python/compare/v2.22.0...v2.23.0) (2023-09-11) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 491187e6d78d..19ff1bf7df5d 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.23.0" +__version__ = "2.23.1" From 8fe6776e696dc42376e283584099d4055aed234f Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Thu, 28 Sep 2023 10:32:04 -0700 Subject: [PATCH 759/966] fix: support urllib3<2.0 versions (#1390) * fix: support urllib3<2.0 versions * update --- .../google/auth/transport/urllib3.py | 9 ++++++++- packages/google-auth/setup.py | 1 - .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../google-auth/testing/constraints-3.10.txt | 1 + .../google-auth/testing/constraints-3.11.txt | 1 + 5 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/google/auth/transport/urllib3.py b/packages/google-auth/google/auth/transport/urllib3.py index bc4de4d1414a..63144f5fffa4 100644 --- a/packages/google-auth/google/auth/transport/urllib3.py +++ b/packages/google-auth/google/auth/transport/urllib3.py @@ -40,11 +40,18 @@ "urllib3 package to use the urllib3 transport." ) from caught_exc +from packaging import version # type: ignore + from google.auth import environment_vars from google.auth import exceptions from google.auth import transport from google.oauth2 import service_account +if version.parse(urllib3.__version__) >= version.parse("2.0.0"): # pragma: NO COVER + RequestMethods = urllib3._request_methods.RequestMethods # type: ignore +else: # pragma: NO COVER + RequestMethods = urllib3.request.RequestMethods # type: ignore + _LOGGER = logging.getLogger(__name__) @@ -179,7 +186,7 @@ def _make_mutual_tls_http(cert, key): return http -class AuthorizedHttp(urllib3._request_methods.RequestMethods): # type: ignore +class AuthorizedHttp(RequestMethods): # type: ignore """A urllib3 HTTP class with credentials. This class is used to perform requests to API endpoints that require diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 4ec9b19c6568..047818ca0612 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -25,7 +25,6 @@ # rsa==4.5 is the last version to support 2.7 # https://github.com/sybrenstuvel/python-rsa/issues/152#issuecomment-643470233 "rsa>=3.1.4,<5", - "urllib3>=2.0.5", ) extras = { diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index a537437340f2e4bf90d15645bcd7d60fe718e91f..cae671c34fccbd1f7b67ad25e1883fb4b733ca0f 100644 GIT binary patch literal 10324 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 literal 10324 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 mWHZujtIvF2uZxCPoiKB(<7vzpCx2.0.0 \ No newline at end of file From f438f4ca48420c232faa22e1679799bba06dca36 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 28 Sep 2023 10:57:39 -0700 Subject: [PATCH 760/966] chore(main): release 2.23.2 (#1391) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index d2b69ade6b48..0cffc0261263 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.23.2](https://github.com/googleapis/google-auth-library-python/compare/v2.23.1...v2.23.2) (2023-09-28) + + +### Bug Fixes + +* Support urllib3<2.0 versions ([#1390](https://github.com/googleapis/google-auth-library-python/issues/1390)) ([07c464a](https://github.com/googleapis/google-auth-library-python/commit/07c464a75fd873f23ca78016e7754697f4511f59)) + ## [2.23.1](https://github.com/googleapis/google-auth-library-python/compare/v2.23.0...v2.23.1) (2023-09-26) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 19ff1bf7df5d..22e514f07a0d 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.23.1" +__version__ = "2.23.2" From 30f519ab571b4d80c5bb9c084f1c8947117282b9 Mon Sep 17 00:00:00 2001 From: Andrew Gorcester Date: Thu, 5 Oct 2023 14:36:13 -0700 Subject: [PATCH 761/966] fix: Serialize signer keys on __getstate__ for pickling (#1394) Fixes #1383 --- .../google/auth/crypt/_cryptography_rsa.py | 15 +++++++++++++++ .../google-auth/google/auth/crypt/es256.py | 15 +++++++++++++++ .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../tests/crypt/test__cryptography_rsa.py | 15 +++++++++++++++ .../google-auth/tests/crypt/test_es256.py | 13 +++++++++++++ 5 files changed, 58 insertions(+) diff --git a/packages/google-auth/google/auth/crypt/_cryptography_rsa.py b/packages/google-auth/google/auth/crypt/_cryptography_rsa.py index 4f2d61166658..1a3e9ff52c66 100644 --- a/packages/google-auth/google/auth/crypt/_cryptography_rsa.py +++ b/packages/google-auth/google/auth/crypt/_cryptography_rsa.py @@ -134,3 +134,18 @@ def from_string(cls, key, key_id=None): key, password=None, backend=_BACKEND ) return cls(private_key, key_id=key_id) + + def __getstate__(self): + """Pickle helper that serializes the _key attribute.""" + state = self.__dict__.copy() + state["_key"] = self._key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.PKCS8, + encryption_algorithm=serialization.NoEncryption(), + ) + return state + + def __setstate__(self, state): + """Pickle helper that deserializes the _key attribute.""" + state["_key"] = serialization.load_pem_private_key(state["_key"], None) + self.__dict__.update(state) diff --git a/packages/google-auth/google/auth/crypt/es256.py b/packages/google-auth/google/auth/crypt/es256.py index 7920cc7ffba5..820e4beccee7 100644 --- a/packages/google-auth/google/auth/crypt/es256.py +++ b/packages/google-auth/google/auth/crypt/es256.py @@ -158,3 +158,18 @@ def from_string(cls, key, key_id=None): key, password=None, backend=_BACKEND ) return cls(private_key, key_id=key_id) + + def __getstate__(self): + """Pickle helper that serializes the _key attribute.""" + state = self.__dict__.copy() + state["_key"] = self._key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.PKCS8, + encryption_algorithm=serialization.NoEncryption(), + ) + return state + + def __setstate__(self, state): + """Pickle helper that deserializes the _key attribute.""" + state["_key"] = serialization.load_pem_private_key(state["_key"], None) + self.__dict__.update(state) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index cae671c34fccbd1f7b67ad25e1883fb4b733ca0f..1e35ceb608aa3ca17b8d212e889573d579769066 100644 GIT binary patch literal 10324 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 diff --git a/packages/google-auth/tests/crypt/test__cryptography_rsa.py b/packages/google-auth/tests/crypt/test__cryptography_rsa.py index 99d8fc37c5d5..1199f8d1bd18 100644 --- a/packages/google-auth/tests/crypt/test__cryptography_rsa.py +++ b/packages/google-auth/tests/crypt/test__cryptography_rsa.py @@ -14,6 +14,7 @@ import json import os +import pickle from cryptography.hazmat.primitives.asymmetric import rsa import pytest # type: ignore @@ -159,3 +160,17 @@ def test_from_service_account_file(self): assert signer.key_id == SERVICE_ACCOUNT_INFO[base._JSON_FILE_PRIVATE_KEY_ID] assert isinstance(signer._key, rsa.RSAPrivateKey) + + def test_pickle(self): + signer = _cryptography_rsa.RSASigner.from_service_account_file( + SERVICE_ACCOUNT_JSON_FILE + ) + + assert signer.key_id == SERVICE_ACCOUNT_INFO[base._JSON_FILE_PRIVATE_KEY_ID] + assert isinstance(signer._key, rsa.RSAPrivateKey) + + pickled_signer = pickle.dumps(signer) + signer = pickle.loads(pickled_signer) + + assert signer.key_id == SERVICE_ACCOUNT_INFO[base._JSON_FILE_PRIVATE_KEY_ID] + assert isinstance(signer._key, rsa.RSAPrivateKey) diff --git a/packages/google-auth/tests/crypt/test_es256.py b/packages/google-auth/tests/crypt/test_es256.py index 33465ce6d11d..f87648db4a4c 100644 --- a/packages/google-auth/tests/crypt/test_es256.py +++ b/packages/google-auth/tests/crypt/test_es256.py @@ -15,6 +15,7 @@ import base64 import json import os +import pickle from cryptography.hazmat.primitives.asymmetric import ec import pytest # type: ignore @@ -141,3 +142,15 @@ def test_from_service_account_file(self): assert signer.key_id == SERVICE_ACCOUNT_INFO[base._JSON_FILE_PRIVATE_KEY_ID] assert isinstance(signer._key, ec.EllipticCurvePrivateKey) + + def test_pickle(self): + signer = es256.ES256Signer.from_service_account_file(SERVICE_ACCOUNT_JSON_FILE) + + assert signer.key_id == SERVICE_ACCOUNT_INFO[base._JSON_FILE_PRIVATE_KEY_ID] + assert isinstance(signer._key, ec.EllipticCurvePrivateKey) + + pickled_signer = pickle.dumps(signer) + signer = pickle.loads(pickled_signer) + + assert signer.key_id == SERVICE_ACCOUNT_INFO[base._JSON_FILE_PRIVATE_KEY_ID] + assert isinstance(signer._key, ec.EllipticCurvePrivateKey) From efaf7d1c8dd4194fe72e7bbc8c31f7fa441921c5 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 9 Oct 2023 13:23:19 -0700 Subject: [PATCH 762/966] chore(main): release 2.23.3 (#1395) --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 0cffc0261263..50606fa7d9b9 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.23.3](https://github.com/googleapis/google-auth-library-python/compare/v2.23.2...v2.23.3) (2023-10-05) + + +### Bug Fixes + +* Serialize signer keys on __getstate__ for pickling ([#1394](https://github.com/googleapis/google-auth-library-python/issues/1394)) ([8b783b9](https://github.com/googleapis/google-auth-library-python/commit/8b783b904d6044433acc5ba0f0c166fc38b3ddb3)), closes [#1383](https://github.com/googleapis/google-auth-library-python/issues/1383) + ## [2.23.2](https://github.com/googleapis/google-auth-library-python/compare/v2.23.1...v2.23.2) (2023-09-28) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 22e514f07a0d..0fdb14ce3787 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.23.2" +__version__ = "2.23.3" From cdb807b1f6020e70a85f71be8df0af7784e4648e Mon Sep 17 00:00:00 2001 From: Carl Lundin <108372512+clundin25@users.noreply.github.com> Date: Mon, 23 Oct 2023 15:30:13 -0700 Subject: [PATCH 763/966] chore: Add deprecation notice for 3.7. (#1371) --- packages/google-auth/README.rst | 10 +++++++ packages/google-auth/google/auth/__init__.py | 25 ++++++++++++++++++ .../google-auth/google/oauth2/__init__.py | 25 ++++++++++++++++++ .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 4 files changed, 60 insertions(+) diff --git a/packages/google-auth/README.rst b/packages/google-auth/README.rst index cdd19bed50de..dfc1596f0f67 100644 --- a/packages/google-auth/README.rst +++ b/packages/google-auth/README.rst @@ -37,6 +37,16 @@ Supported Python Versions ^^^^^^^^^^^^^^^^^^^^^^^^^ Python >= 3.7 +**NOTE**: +Python 3.7 was marked as `unsupported`_ by the python community in June 2023. +We recommend that all developers upgrade to Python 3.8 and newer as soon as +they can. Support for Python 3.7 will be removed from this library after +January 1 2024. Previous releases that support Python 3.7 will continue to be available +for download, but releases after January 1 2024 will only target Python 3.8 and +newer. + +.. _unsupported: https://devguide.python.org/versions/#unsupported-versions + Unsupported Python Versions ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - Python == 2.7: The last version of this library with support for Python 2.7 diff --git a/packages/google-auth/google/auth/__init__.py b/packages/google-auth/google/auth/__init__.py index 2875772b375f..2d6caec714d0 100644 --- a/packages/google-auth/google/auth/__init__.py +++ b/packages/google-auth/google/auth/__init__.py @@ -15,6 +15,8 @@ """Google Auth Library for Python.""" import logging +import sys +import warnings from google.auth import version as google_auth_version from google.auth._default import ( @@ -29,5 +31,28 @@ __all__ = ["default", "load_credentials_from_file", "load_credentials_from_dict"] + +class Python37DeprecationWarning(DeprecationWarning): # pragma: NO COVER + """ + Deprecation warning raised when Python 3.7 runtime is detected. + Python 3.7 support will be dropped after January 1, 2024. See + https://cloud.google.com/python/docs/python37-sunset/ for more information. + """ + + pass + + +# Checks if the current runtime is Python 3.7. +if sys.version_info.major == 3 and sys.version_info.minor == 7: # pragma: NO COVER + message = ( + "After January 1, 2024, new releases of this library will drop support " + "for Python 3.7. More details about Python 3.7 support " + "can be found at https://cloud.google.com/python/docs/python37-sunset/" + ) + + # Configure the Python37DeprecationWarning warning so that it is only emitted once. + warnings.simplefilter("once", Python37DeprecationWarning) + warnings.warn(message, Python37DeprecationWarning) + # Set default logging handler to avoid "No handler found" warnings. logging.getLogger(__name__).addHandler(logging.NullHandler()) diff --git a/packages/google-auth/google/oauth2/__init__.py b/packages/google-auth/google/oauth2/__init__.py index 4fb71fd1ad3b..3ebf219fd78e 100644 --- a/packages/google-auth/google/oauth2/__init__.py +++ b/packages/google-auth/google/oauth2/__init__.py @@ -13,3 +13,28 @@ # limitations under the License. """Google OAuth 2.0 Library for Python.""" + +import sys +import warnings + + +class Python37DeprecationWarning(DeprecationWarning): # pragma: NO COVER + """ + Deprecation warning raised when Python 3.7 runtime is detected. + Python 3.7 support will be dropped after January 1, 2024. See + https://cloud.google.com/python/docs/python37-sunset/ for more information. + """ + + pass + + +# Checks if the current runtime is Python 3.7. +if sys.version_info.major == 3 and sys.version_info.minor == 7: # pragma: NO COVER + message = ( + "After January 1, 2024, new releases of this library will drop support " + "for Python 3.7. More details about Python 3.7 support " + "can be found at https://cloud.google.com/python/docs/python37-sunset/" + ) + # Configure the Python37DeprecationWarning warning so that it is only emitted once. + warnings.simplefilter("once", Python37DeprecationWarning) + warnings.warn(message, Python37DeprecationWarning) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 1e35ceb608aa3ca17b8d212e889573d579769066..22f4e58bf55fb43c8338e598360a3ae16a43b64e 100644 GIT binary patch literal 10324 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 literal 10324 zcmV-aD67{BB>?tKRTHq`zgcOy!rRdfB0$@F+&g!Oh^Io=QA$<+YvQ1v0@xC&Pyp}n zz;77Qbxx+#Rh)sKsG`&ZvfnZS|0lJ{$Mt4y24Im^zltD}_8$O4ob~3e1lD1e%!26W z?6?}?Vm)hlBHNOs@-h7D&F8>cn_E zjA@NV8E{Ee3Ty2XFKLQVNf{1HiRyk~$xi-Psr_^%B?Z9`{z>L_8S6#2jF6sr=`=lB zj6R14m6kcj#^w8@f%2M4ib8ZNy~v~kmIn>>Q@okhs7WKEMe7noZ5rv(hNVa- zSr(&l7gA$G#j*7s4ssM@}7iN}IPHzxyIJAU4+>5^s)Zv>Dlm*5(&a zd9nYR8v|GOutfN>{xcCj$4Ke#YmnlmE^v~ zmh|cVAc#=Gjk*9uU|Es^2j<2MNYo+g3xLV!JPJNCKU6RLXR&F~+YfoqC1VOh zyARnrtVsD|VO-UI=7Ul=MLg-QsvCePXnVK6rVrhbL&y=iu&ZFg5*4${&Drr7j;0htsI3B)iOLaZ2VPJ>W{?`NUi<7)9W@+aQD&}BE7Yf zA+Tftn!l7${fwq&_^L1uDC18wA?eNT3qY3?$X;b7LQ4*(``k|hKGfR7{Zv0sE=-> zcKfYZ98z%-bMs;@hNcdb5g3H$sYNALu?<nu~ zgu~$!uH_=yhY%ToM4_@&1b3KT`W_C}`U3$GIr_V-TGA0uxM9WUImFr?(I4zLF5}C) z(@OU~E-S7fJoqz3hD{?^q1C%FUPa&TdZ0*WHWFGBt=et*&XQd2+e*<4n*v1GcE+uq zP}1!mzQS?@HRtX4RYB$)X4~Wnai#OGYdyw*vurOhnzMvE5_0etdL!nbdFJ$D@1j`~ zLdGwrSefJswMd@md@==M@TaVF=n%m+~p+{zp;5&(B(h4_X-IQAU+37yK z;j)hX!zEmaTStWSxL>E#Yc{zXEVKI(jBp8p=t5-h@ESu$)+J|GsA7b`M89qdslUB$ zK8LDeELD{}$g8+9AwD^vDIt z)%gq$N7wd8%ZbzS;h&ZJS@8vhao_=-Z24;Ab=v%r9o$r+>3{e5&N_7iDH$qP%p`Fe zV)+)Z=zmfG@g~VZgw+jr3yp%~5?epWx3`%YtXSYRFMJCmnA-!6@4t9S1-!1U2$zJqhlV zkj)COJ7$ZuL>BqF$=!xL9$Tl@_6dRumB(|4%L*gVqGzAxbZw&h07e_+k-JiOT{N4T zr$ks?|9j2D2+36a>cW>GQ-Ch{Ifm`x;9g??2~i8?ot?fZc|9p6P(usn-y2xd!hAkr zEJq7ES^BZR7RMUm(n*)gsF%#HOBWsbFW_b8W0$Hmqh@6>MiNoU4lwCF zx)F5*1{z&f6Ziah`;a28hPKg2$jc=lfA*EV|HAadK7Lc-!Uz zts3VG6ZByAVzstQjwiuF_?afIIKkln7bto$Dr@TA$Z-I*{=J^%;HE8|;7M6KOA})= z8LhQwLt8Mo@phVF3ri7*Zvc-$efxyi{~NUw71WDpt-<5~E()f|ZGg)&!N$!}-|8mD zyyzuu@aQmKY4PWdb1-X*;-@DN>yfb@WLX6957C-7p?*&--$-nV74_GshI(P%3@lsd z8^{1Syi1tfGb;JVTH%dW41((5GZTk)zfbe@5@0IUq_Hj_bpjrCDa?Gsu;VIV6pVC^ z?4Yrc{Ikc;s@lhY)=Yw-*%*=lx0~u9e$2`&WryjR>h3JM4}S!6Xfb7SH#+gu&Ob!-R92M=3aLzxT$a)I?_nh5Wgc=sszm*;)S_wKs zQaWMH)9U!{^Wl{X7BQ(=ZCJS8zF!vn&I)jydYcYx!2sypctx7pb{m(jqvX z4k*!b4HwbO55$*hG61!opNiwzNg$`USLGgn^q^|T;K>lJS3{c1a8Qsbr6fU%CJ@|@WrdR~DhA5Z}cm>o0^=(GF5 zDWk8rxQe~Y!G>Vyi##I_(C|8*Wed8$ES?@R#efr z2ZkU2Rqt#pBhH>%a;`8jj&$bcTT*6;*bG=X4oV(5h9bVrr%jU3MX!W}^9HtVHMJk# zh!)D!EyCXgFl8R+BswU@ z71@Xg!Nu3Bpo~&cQ4K?fILCYTN`f$~62QR4fIDGAX*pWxmWrXv?OPT^!i&Wm{M5xk zM%*b6Ol3ui&~njqz_BsMykmzkZ|5jVq~P}ENJK>6k6@@?q>=vU>Yq#@GF&x=E7&Wq z@3jbb(@v(*?V^c^f^f2$Q-$1=h%ppEWPOL80^A;DV+uZ3s0-0Ee-Sfr3P9O9rIKLR zS{;pFJsW7-y#ty-zHQKf9C70@@oVT%t!0mnSJ!0ku!bGSJeAKSMLp1s!G`RJW7yw^ zFz`CQMh*YF$TR&*S!BjK$|4e*DtE%m^THX)!G& z)qWBe&Yu-xXN)}TAtxpssHU_Pw4Araiu7#KgkIQX1AvpBywAO+bybqzN9AZ!VZvd# zodbhg+;QI;eki=f*2Qt={lbY9dt_7P}b}3A>ya4t*Kx-vMgW*B<=BlblK{*)=%X& zRxu{g%!l%P><{{$ep=dE0TS{2!Gi#uFCkLx$uHCQ*7F620|Z7~t40E-l*1!_N%lOa zsO@*g5W$~6*ps`CE(?G@^nnXu#4TEb&qz=gPmrzyi2XPSh&0_eJF2j}IrlM52xx-3 zriGkZI3vcMbo^Nl*ZEspnGoh3sit{{H!CXrl)kL+NwbdX&sYVXAvrU}{s%4=$|%N_*=`se^mcUW{iPa+JK7s@NDgM0}nG;|cJSwcf9ut33VyQS{XGp&1| zCnOI+-&o9Ib-kTRPXK)ZHjS-nlD8BT)Xax#JeA`9{sLXGF9^`MN=}*u!7&fK1_r&40`dy*r$z82*+mFXd> z2|&gNg`*5U>4@IU>m!4Q=P-f4e$zBT1*Jx3E|RvlWf(-`j;LAgugkGWku-))?T@g5 zZ%^rCb~s0rsg~0Q*`W1mCMqShFC{TA{@Fj28d1>xJU+f+4el5Rjat z$6ukVYA9h5M(L0)s`ljhH%0IUl;{aG$~_$lN>71`2jEq^VY97T{1l)FAtuFx11#1R=KK z?PVcU{{N5peq+^nJ8RdZO;6Pd;FVzYgVWA9STV? zvy+s;X2@0p=d8d+#)8X5i93C#mNezwjfMa+o21cRjaaS6r@t$gdH4}wTCh%7mBFxH zSGXkJ?)P5}zVp^uSLzLJ6C|3i4J}_rTV)SmMLQ8a11Oxp{PKJLGG402-(T4NOIfNl z^HK_vHWqx-bM;Y6Q7sLH2FbcS%2LCo@*Dyd?_C$xYbYn68v!06LqXB3h*N_{Vho!T zZpi$w$-LJIfL?H<^4I^px1cDs-))3;1J=0aK$uY>+kC^QT@+ZRjymGD2AM~}=aXM6 z(bkn|X>gy3@%|8Dl-Z^FsroJkv$Uv+oaq12GOejc446uX@Q(xCosYgJe;c;GNShyH z1Qd8HYV>k^Cj+7!+S(YmtorfDA)EO;cpznQ0Hp^WLSuL$FX^Na$88ItwlQ`@=TBwS zK^>(*$t;Xrf_NDKz2n8|HzdOKBM)YC)sQ<~L{TKK!ugc5c!D*}A7< z!H#WTfZ2tJW@dszFvho&x>MejyoeC*4@^>E?p{C}62cz0s-t*P^0BE8Z|K2&|1ud3bscL$)R9cc_j!f9siHKsKvvtUn1Dpa_DFxnwa*Nfa|{Uug}9~ z{KEo^?QeFKskPE&L8bn)8Ut1hDn(-5sNVX!Yp;lx(<2gpk9eM>xcI|J?!aOwk)c+& z<|++A66F_WrAmCI&9p@dW2B0Le$l@2DQ~Cr*sC-kBB**zV{f*dx5J?tBFjRe?p{( z?NU*YkhK~U5E$2KAzlkOu+Gp?r}ih#XxEOr&TH&iS%B>)vu=U{ToQ-Mc(`L^kAL+{ z1~NR-`=L1YI*z8kc-tolu93x)SerSs-JrX^40#kC&dA;=nH~^fm(p?nD-GF=(&iV0 zV2eEgJQ3addUAvTvxkB|dgt~@yk-;OZNK9apAuLFKEEEj&E|X)IlkBRXdYD(2W088 zAGY9p26P>uhpGnk*TLY6+B?sYpKe6?)fe>DC}cK(*%UF?8euslZT8ETxLmY~r}Ss5!L;<6Vyr1E!W5-X(`~bXHC%0sK$^#b zuWzwoVu!H(R@q9+>GXdmT<0-8tys(_9UjK>J#M{KEG^Y9Lm zI6}$hFZo61%+f0IW22dY-Aa}t|;fC%9iBQ5(&~WH=xF- z@CleqXc*sVvLnOAOlf$A1mop$c_96FJx0-zL0xSh?*@YFxk;Trh$xW`m7x+#scyWQ zKqd42A68k#Lb9t5Q9ZO`oJUVW4u;5XdC*CWfFX_VmJ9#Ie_@3SeSvs{=+zp*cqZzf z3Ka3=zdJ@Ss4<3UrH3VNYP@SFN<=(!L_j!LRNG z$_r=;yGxe}7^Anoj>^6uu0ZBUkH9@3^!-7xCGn~2^Tq9OHlPZDGdII|vj4l*2uSN) zPS-rCQkp)Re;LYJwW2KyTlDyaWGp5&L!py{c5_C@X z1hOMQ4zfZ9woYzWm+0?g(En(~pt2M#!Q!e`U&-|J&=Vl7!+yxPM`pA#YK3AvXG_B? zYf-^z%*@?nw_r8CV(jN)m54BaAvlH=LYo27AsP7aF`cI<(}T0U>wCE*=Dbh%mK)R; zHZE~kgRZjtt^Y+Rf$i8lKwWPcb6D@th0iVy35|-tbiYDP%o_Q+Q_&SnITHu=E+b<=ntB z{~#aw>!9=SzMISbTIz*Ovf%i0^!_Cf0;wGk<`{SjWR<5Be{|&kf$`m}2s_q@A&@o> znKTp8@l~CZ4wcsY=-0`U-;Yi5M0mr^$-M+F(@qq&ghvxJK81%OHwT-d#<11sk8;HZ zQ92}@SU_*m0Sd4HHk)ipr{o)a-+^B(#3INCu<{ArKgEWR9hMtHD*!B>5+>}ztp$ae zgH`N#w8Uy;&GPXfkZi4)ZQr-okl=te$Yya15!rNGjgqVUwN+vp z71H&nEFOd2R<#Iy)amft(Q_VE&8g(d#Eh16?u$3#CDAET!)~;FOGT#87aDFsV-rDL z?Eqb`QOnFX;$79tW%I`A*fJ*H%+8^_o;w)8J|5#rC{!nrF7;hf5g3jPwa%cI-4!#_ zPh*00jesSiQi0qriMwZ~Gcj4;)Jh-`GtKI4UW$Veus7CnMReN_xSn{mHkyJwrFt!u z*K8@1O^-yCXOrS+9c*JS4-^F^c&gULK@qZA9CZPH5zML1=J?TN;QYfDfFE1B}mAjHa%QB+4b zZqJbRpN;7U^Ub5_y>O&K7;F~-srwuG_gC$wmB2(0R?29?qA>;Zu@d(>yMqJbBjbDtC%52p$a8u+jnK zouMAvCO69U7`KEf{P?AwQznC6)>FEkv)(3of$XmKeBlq$xpCWi^h&HUQ2M+k62?=t<#pa_)c{E zs5%DZHe7LdhCh+dO8px82d0aa5S#(SQdwchov1BITKWx*)_G1gl@?y@0xZ?``S5bP z%Pck8Cz~)Y@;|Qk_x}mw$a#w+C0#_ke9Hd$k|ySv)EPs@dJ@5V%}8_0bt8eN`UBMb{L%*Bqk>I=o|E7fF))DPK%u zxoA@_Y%q}ZNxH+RFjsl1%u%=_^j>L!DM10j!qB!B)uT6J_(Gb6hAl^@@_=>xp7gJI z7-7mO+u?@aBpNpju|4I-(16Ng`MP(0E?2P8N04$vuQWh?;Z_UV!}jlf`ucEUJFhDQ z+a(=%npSdI_`gL$Of2vVyEx)=s7RLyWLUXYr=a4E0dheOGmDNAF-u^_97ye2kTHEe zvx0Wz5;gj&PGi~O81YrewlzT+Df(vrQ- zh_6wClXAk-xkN&u*QW%m&# zXrMF%H4pd@>gg_>gqpsc8uT-#%hp+*UkWZP@)D602Zn%WZ>vV9`%ixqb0-YSI3PY6 zSJX&VvoDMw5)v>csrKug&gXzy;qeV5g}%gvbtC6Z6P(d;qvwn`yA zPfOwP{0S~_a!r%}=D(}HdS4!XqU&0_0{`k#RM!ts>A3Q=nuX8~d+W0~2iI|*#3mQX zk6YkNDOe66sqPCa}blnxBh*n9B-ur3Y!C;@2x=sG`##7i(noR87-504c2bE3y;#vAK+K^7d{s&h z%$X+LjW7UXH0$1WtwW68C4+c-&9G&YO&+jC8T3Qam8un zAa8n^@x-tiRC&HZh$Vyu%NhX<8pM|U5&?svkL(wFl(wye=giA1I=|MR`tuoeV=vZJ zeJcx@UfT6RttZHoAuk15Z(hBh0b1mi85v>E(XA`LbXdQHzSE!C44u!O zq@bM(G_|SKs{|ggqrU!=Vfp;ho-yqPUxMtz_ata<(CfP)Hrm1V-AG!A8=LJ)MUN@h-Bpn{=?4{^VKSYZmb$q z^%uarhtn?%iFRuaAtBC@psoUf7_7+P)C#07=bhB?d(Z*4J~S;K9lw@Hq?)CZ{xu~g z1-Sw#hhrBZGPj!@Na};%e0M}^L20efLWLaT92#nR_QGQ<8ep-axY!^tgsJV#w$3xp zA@Ew4JLh*3>y)YQf+jp5?W3<9_w+P0f*>VSuFW!bRf{rGOWmDHAKHUj{i30xTwtF< zL!3p@g$R2MaSE4*$NCszC=tLRaOb;v>z@Dch5^dh-iJ)oyU#ZdhA`Su#(ehsoH`Ud zwZGd|9^nI$5qr%@7a13{eE(5JB7!pGSTru*2H(`>EX#SPh8o~HNpsIt;F>w4=F*#Q zld@y806sa%moqd^dMQ(&KrFgdX0lK$Hmxz2)kpN{o&uDw4=?(mLOTb>LOEJ%bqNK( z!hn_+e!(pQT2;hJ7mtj$z7y6BSqct|ZW#}bmWu=p#?mDTRvA^(?I|{s*4|ER6jtfN z9A=u_r1fA&(h32S08~IqWh97SllkN#&aL<;eG_S z=@N|?pu+LS`t{qSs!2qw|Ajwz1bmh%qq>5OoV*KlZSk<=yX#I_QcV^R1z%}i96Y2i zg~%to7Tw$#_>n20Vqw({g#iTZT;*Uulib0*q9U*lb7)sngcQl;3x{|gk!@RTb~S!Y zgt+rJLjQR_u|$-aqNDh))69!fpLMXOR1=+nGvLI~G1LY@Q67g*alDN0A*6AP-Rhtn z5h{8JIIS&ahCBb*86q}_w!eAdU~wj!>WV`c1ZXX2@|1WdNG+Z4GWWwJaGNi8PQl|m zI%ranf$&w;>#BhhaI73euyCuzP3Wd>E93tj_8?Rfbyt{t>$E+AU9U5ea0VR6+((kRh86V? zfOAOoBI0_uGmj+Fl>W?$4m+wNp}`J7&@Fj=+{Zpy+$lb?*?rm-efL#x9i|wU zG5KBC$eDdjh0YZ%1HsRxQP$45j)|)IM^43TKIu9JAF^zh$RIm;-zWtFPa8>!NkO4 m7T>=;9*Eh&a6lB|#tBy#fcI= Date: Tue, 31 Oct 2023 12:14:36 -0700 Subject: [PATCH 764/966] fix: export detect_gce_residency_linux function (#1403) --- .../google/auth/compute_engine/__init__.py | 3 ++- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/compute_engine/__init__.py b/packages/google-auth/google/auth/compute_engine/__init__.py index 5c84234e9370..7e1206fc1b28 100644 --- a/packages/google-auth/google/auth/compute_engine/__init__.py +++ b/packages/google-auth/google/auth/compute_engine/__init__.py @@ -14,8 +14,9 @@ """Google Compute Engine authentication.""" +from google.auth.compute_engine._metadata import detect_gce_residency_linux from google.auth.compute_engine.credentials import Credentials from google.auth.compute_engine.credentials import IDTokenCredentials -__all__ = ["Credentials", "IDTokenCredentials"] +__all__ = ["Credentials", "IDTokenCredentials", "detect_gce_residency_linux"] diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 22f4e58bf55fb43c8338e598360a3ae16a43b64e..722dfcd24704ef8c0b25e9b21d2dc40c62c303fd 100644 GIT binary patch literal 10324 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

        kOw>TkL@ zd6w!l8dfI<+>HCnf`#P@D<;0(WvpoF@)~giRcv-*qU=V0UEl1R>D!gzwj_5xtix-1 zEQ34j1z$G)ld384K6Sg;rv#pWgK!|ea(!q>KO_!yT-WM1*^{xjCID+{HEH@w;o%sDxBRkT31^^EBf#@raYdGh^Z$7iZ2#Zix>>svbdq>L`Wk*Bl zypJ4&bAxsr>WIUPUkfD;gx~*WX?M_`eAKi?P=xgk{O+_E9ih1N6=qHD?#(xuDxHc` zYvxFrHVeK6^wUZF%ek3JVDFXgOWVxij_hf-3o4~=3c@N$WWY>(3JZV2ca?275ryxt zJ^ICHz9_eVW;@?07V4(Z@_6Sw#{B%JEM*5e3%=twE4`zrC6U4@J@X_z`(=g^`Xct5 z1{ph@&g*w%3a4#Ihpq&T@!#|A-oe3G9y_Mtd?|j_I~}q2`^~OcRrOobLV(w}<#0q9+wnUZ&;O8R(16;# mHER;J*$nz4)^k_vfsVh54N0^(;te%woqwrNW@$8QR1l&kg+E3B literal 10324 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 diff --git a/packages/google-auth/tests/test_external_account.py b/packages/google-auth/tests/test_external_account.py index c458b21b6438..3c372e6291c8 100644 --- a/packages/google-auth/tests/test_external_account.py +++ b/packages/google-auth/tests/test_external_account.py @@ -235,10 +235,16 @@ def make_mock_request( return request @classmethod - def assert_token_request_kwargs(cls, request_kwargs, headers, request_data): + def assert_token_request_kwargs( + cls, request_kwargs, headers, request_data, cert=None + ): assert request_kwargs["url"] == cls.TOKEN_URL assert request_kwargs["method"] == "POST" assert request_kwargs["headers"] == headers + if cert is not None: + assert request_kwargs["cert"] == cert + else: + assert "cert" not in request_kwargs assert request_kwargs["body"] is not None body_tuples = urllib.parse.parse_qsl(request_kwargs["body"]) for (k, v) in body_tuples: @@ -246,10 +252,16 @@ def assert_token_request_kwargs(cls, request_kwargs, headers, request_data): assert len(body_tuples) == len(request_data.keys()) @classmethod - def assert_impersonation_request_kwargs(cls, request_kwargs, headers, request_data): + def assert_impersonation_request_kwargs( + cls, request_kwargs, headers, request_data, cert=None + ): assert request_kwargs["url"] == cls.SERVICE_ACCOUNT_IMPERSONATION_URL assert request_kwargs["method"] == "POST" assert request_kwargs["headers"] == headers + if cert is not None: + assert request_kwargs["cert"] == cert + else: + assert "cert" not in request_kwargs assert request_kwargs["body"] is not None body_json = json.loads(request_kwargs["body"].decode("utf-8")) assert body_json == request_data @@ -665,6 +677,56 @@ def test_refresh_without_client_auth_success( assert not credentials.expired assert credentials.token == response["access_token"] + @mock.patch( + "google.auth.metrics.python_and_auth_lib_version", + return_value=LANG_LIBRARY_METRICS_HEADER_VALUE, + ) + @mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) + @mock.patch( + "google.auth.external_account.Credentials._mtls_required", return_value=True + ) + @mock.patch( + "google.auth.external_account.Credentials._get_mtls_cert_and_key_paths", + return_value=("path/to/cert.pem", "path/to/key.pem"), + ) + def test_refresh_with_mtls( + self, + mock_get_mtls_cert_and_key_paths, + mock_mtls_required, + unused_utcnow, + mock_auth_lib_value, + ): + response = self.SUCCESS_RESPONSE.copy() + # Test custom expiration to confirm expiry is set correctly. + response["expires_in"] = 2800 + expected_expiry = datetime.datetime.min + datetime.timedelta( + seconds=response["expires_in"] + ) + headers = { + "Content-Type": "application/x-www-form-urlencoded", + "x-goog-api-client": "gl-python/3.7 auth/1.1 google-byoid-sdk sa-impersonation/false config-lifetime/false", + } + request_data = { + "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange", + "audience": self.AUDIENCE, + "requested_token_type": "urn:ietf:params:oauth:token-type:access_token", + "subject_token": "subject_token_0", + "subject_token_type": self.SUBJECT_TOKEN_TYPE, + } + request = self.make_mock_request(status=http_client.OK, data=response) + credentials = self.make_credentials() + + credentials.refresh(request) + + expected_cert_path = ("path/to/cert.pem", "path/to/key.pem") + self.assert_token_request_kwargs( + request.call_args[1], headers, request_data, expected_cert_path + ) + assert credentials.valid + assert credentials.expiry == expected_expiry + assert not credentials.expired + assert credentials.token == response["access_token"] + @mock.patch( "google.auth.metrics.python_and_auth_lib_version", return_value=LANG_LIBRARY_METRICS_HEADER_VALUE, @@ -869,6 +931,101 @@ def test_refresh_impersonation_without_client_auth_success( assert not credentials.expired assert credentials.token == impersonation_response["accessToken"] + @mock.patch( + "google.auth.metrics.token_request_access_token_impersonate", + return_value=IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, + ) + @mock.patch( + "google.auth.metrics.python_and_auth_lib_version", + return_value=LANG_LIBRARY_METRICS_HEADER_VALUE, + ) + @mock.patch( + "google.auth.external_account.Credentials._mtls_required", return_value=True + ) + @mock.patch( + "google.auth.external_account.Credentials._get_mtls_cert_and_key_paths", + return_value=("path/to/cert.pem", "path/to/key.pem"), + ) + def test_refresh_impersonation_with_mtls_success( + self, + mock_get_mtls_cert_and_key_paths, + mock_mtls_required, + mock_metrics_header_value, + mock_auth_lib_value, + ): + # Simulate service account access token expires in 2800 seconds. + expire_time = ( + _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=2800) + ).isoformat("T") + "Z" + expected_expiry = datetime.datetime.strptime(expire_time, "%Y-%m-%dT%H:%M:%SZ") + # STS token exchange request/response. + token_response = self.SUCCESS_RESPONSE.copy() + token_headers = { + "Content-Type": "application/x-www-form-urlencoded", + "x-goog-api-client": "gl-python/3.7 auth/1.1 google-byoid-sdk sa-impersonation/true config-lifetime/false", + } + token_request_data = { + "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange", + "audience": self.AUDIENCE, + "requested_token_type": "urn:ietf:params:oauth:token-type:access_token", + "subject_token": "subject_token_0", + "subject_token_type": self.SUBJECT_TOKEN_TYPE, + "scope": "https://www.googleapis.com/auth/iam", + } + # Service account impersonation request/response. + impersonation_response = { + "accessToken": "SA_ACCESS_TOKEN", + "expireTime": expire_time, + } + impersonation_headers = { + "Content-Type": "application/json", + "authorization": "Bearer {}".format(token_response["access_token"]), + "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, + "x-allowed-locations": "0x0", + } + impersonation_request_data = { + "delegates": None, + "scope": self.SCOPES, + "lifetime": "3600s", + } + # Initialize mock request to handle token exchange and service account + # impersonation request. + request = self.make_mock_request( + status=http_client.OK, + data=token_response, + impersonation_status=http_client.OK, + impersonation_data=impersonation_response, + ) + # Initialize credentials with service account impersonation. + credentials = self.make_credentials( + service_account_impersonation_url=self.SERVICE_ACCOUNT_IMPERSONATION_URL, + scopes=self.SCOPES, + ) + + credentials.refresh(request) + + # Only 2 requests should be processed. + assert len(request.call_args_list) == 2 + # Verify token exchange request parameters. + expected_cert_paths = ("path/to/cert.pem", "path/to/key.pem") + self.assert_token_request_kwargs( + request.call_args_list[0][1], + token_headers, + token_request_data, + expected_cert_paths, + ) + # Verify service account impersonation request parameters. + self.assert_impersonation_request_kwargs( + request.call_args_list[1][1], + impersonation_headers, + impersonation_request_data, + expected_cert_paths, + ) + assert credentials.valid + assert credentials.expiry == expected_expiry + assert not credentials.expired + assert credentials.token == impersonation_response["accessToken"] + @mock.patch( "google.auth.metrics.token_request_access_token_impersonate", return_value=IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, diff --git a/packages/google-auth/tests/test_identity_pool.py b/packages/google-auth/tests/test_identity_pool.py index a11b1e70fe9b..b2b0063ec547 100644 --- a/packages/google-auth/tests/test_identity_pool.py +++ b/packages/google-auth/tests/test_identity_pool.py @@ -179,6 +179,12 @@ class TestCredentials(object): "url": CREDENTIAL_URL, "format": {"type": "json", "subject_token_field_name": "access_token"}, } + CREDENTIAL_SOURCE_CERTIFICATE = { + "certificate": {"use_default_certificate_config": "true"} + } + CREDENTIAL_SOURCE_CERTIFICATE_NOT_DEFAULT = { + "certificate": {"certificate_config_location": "path/to/config"} + } SUCCESS_RESPONSE = { "access_token": "ACCESS_TOKEN", "issued_token_type": "urn:ietf:params:oauth:token-type:access_token", @@ -677,6 +683,40 @@ def test_constructor_invalid_options_url_and_file(self): assert excinfo.match(r"Ambiguous credential_source") + def test_constructor_invalid_options_url_and_certificate(self): + credential_source = { + "url": self.CREDENTIAL_URL, + "certificate": {"certificate": {"use_default_certificate_config": True}}, + } + + with pytest.raises(ValueError) as excinfo: + self.make_credentials(credential_source=credential_source) + + assert excinfo.match(r"Ambiguous credential_source") + + def test_constructor_invalid_options_file_and_certificate(self): + credential_source = { + "file": SUBJECT_TOKEN_TEXT_FILE, + "certificate": {"certificate": {"use_default_certificate": True}}, + } + + with pytest.raises(ValueError) as excinfo: + self.make_credentials(credential_source=credential_source) + + assert excinfo.match(r"Ambiguous credential_source") + + def test_constructor_invalid_options_url_file_and_certificate(self): + credential_source = { + "file": SUBJECT_TOKEN_TEXT_FILE, + "url": self.CREDENTIAL_URL, + "certificate": {"certificate": {"use_default_certificate": True}}, + } + + with pytest.raises(ValueError) as excinfo: + self.make_credentials(credential_source=credential_source) + + assert excinfo.match(r"Ambiguous credential_source") + def test_constructor_invalid_options_environment_id(self): credential_source = {"url": self.CREDENTIAL_URL, "environment_id": "aws1"} @@ -716,7 +756,7 @@ def test_constructor_invalid_both_credential_source_and_supplier(self): ) def test_constructor_invalid_credential_source_format_type(self): - credential_source = {"format": {"type": "xml"}} + credential_source = {"file": "test.txt", "format": {"type": "xml"}} with pytest.raises(ValueError) as excinfo: self.make_credentials(credential_source=credential_source) @@ -724,7 +764,7 @@ def test_constructor_invalid_credential_source_format_type(self): assert excinfo.match(r"Invalid credential_source format 'xml'") def test_constructor_missing_subject_token_field_name(self): - credential_source = {"format": {"type": "json"}} + credential_source = {"file": "test.txt", "format": {"type": "json"}} with pytest.raises(ValueError) as excinfo: self.make_credentials(credential_source=credential_source) @@ -733,6 +773,27 @@ def test_constructor_missing_subject_token_field_name(self): r"Missing subject_token_field_name for JSON credential_source format" ) + def test_constructor_default_and_file_location_certificate(self): + credential_source = { + "certificate": { + "use_default_certificate_config": True, + "certificate_config_location": "test", + } + } + + with pytest.raises(ValueError) as excinfo: + self.make_credentials(credential_source=credential_source) + + assert excinfo.match(r"Invalid certificate configuration") + + def test_constructor_no_default_or_file_location_certificate(self): + credential_source = {"certificate": {"use_default_certificate_config": False}} + + with pytest.raises(ValueError) as excinfo: + self.make_credentials(credential_source=credential_source) + + assert excinfo.match(r"Invalid certificate configuration") + def test_info_with_workforce_pool_user_project(self): credentials = self.make_credentials( audience=WORKFORCE_AUDIENCE, @@ -782,6 +843,36 @@ def test_info_with_url_credential_source(self): "universe_domain": DEFAULT_UNIVERSE_DOMAIN, } + def test_info_with_certificate_credential_source(self): + credentials = self.make_credentials( + credential_source=self.CREDENTIAL_SOURCE_CERTIFICATE.copy() + ) + + assert credentials.info == { + "type": "external_account", + "audience": AUDIENCE, + "subject_token_type": SUBJECT_TOKEN_TYPE, + "token_url": TOKEN_URL, + "token_info_url": TOKEN_INFO_URL, + "credential_source": self.CREDENTIAL_SOURCE_CERTIFICATE, + "universe_domain": DEFAULT_UNIVERSE_DOMAIN, + } + + def test_info_with_non_default_certificate_credential_source(self): + credentials = self.make_credentials( + credential_source=self.CREDENTIAL_SOURCE_CERTIFICATE_NOT_DEFAULT.copy() + ) + + assert credentials.info == { + "type": "external_account", + "audience": AUDIENCE, + "subject_token_type": SUBJECT_TOKEN_TYPE, + "token_url": TOKEN_URL, + "token_info_url": TOKEN_INFO_URL, + "credential_source": self.CREDENTIAL_SOURCE_CERTIFICATE_NOT_DEFAULT, + "universe_domain": DEFAULT_UNIVERSE_DOMAIN, + } + def test_info_with_default_token_url(self): credentials = identity_pool.Credentials( audience=AUDIENCE, @@ -845,6 +936,15 @@ def test_retrieve_subject_token_json_file(self): assert subject_token == JSON_FILE_SUBJECT_TOKEN + def test_retrieve_subject_token_certificate(self): + credentials = self.make_credentials( + credential_source=self.CREDENTIAL_SOURCE_CERTIFICATE + ) + + subject_token = credentials.retrieve_subject_token(None) + + assert subject_token == "" + def test_retrieve_subject_token_json_file_invalid_field_name(self): credential_source = { "file": SUBJECT_TOKEN_JSON_FILE, @@ -1485,3 +1585,28 @@ def test_refresh_success_supplier_without_impersonation_url(self): scopes=SCOPES, default_scopes=None, ) + + @mock.patch( + "google.auth.transport._mtls_helper._get_workload_cert_and_key_paths", + return_value=("cert", "key"), + ) + def test_get_mtls_certs(self, mock_get_workload_cert_and_key_paths): + credentials = self.make_credentials( + credential_source=self.CREDENTIAL_SOURCE_CERTIFICATE.copy() + ) + + cert, key = credentials._get_mtls_cert_and_key_paths() + assert cert == "cert" + assert key == "key" + + def test_get_mtls_certs_invalid(self): + credentials = self.make_credentials( + credential_source=self.CREDENTIAL_SOURCE_TEXT.copy() + ) + + with pytest.raises(exceptions.RefreshError) as excinfo: + credentials._get_mtls_cert_and_key_paths() + + assert excinfo.match( + 'The credential is not configured to use mtls requests. The credential should include a "certificate" section in the credential source.' + ) From 69ad1a4875e904b56099a7d655380246d5ed8a68 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Mon, 8 Jul 2024 13:57:12 -0700 Subject: [PATCH 844/966] chore: update sys test creds (#1555) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 94db780f16017777e75274af736892801f45a760..325974fe37c7d45ba4c4fedf56ff9d0cfa8c1b1b 100644 GIT binary patch literal 10324 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�f8}!^IXZiYf#A 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 mWT)m%3QNzGD%b~gpM0F;6s7rmmb$05;(=-H literal 10324 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

        kOw>TkL@ zd6w!l8dfI<+>HCnf`#P@D<;0(WvpoF@)~giRcv-*qU=V0UEl1R>D!gzwj_5xtix-1 zEQ34j1z$G)ld384K6Sg;rv#pWgK!|ea(!q>KO_!yT-WM1*^{xjCID+{HEH@w;o%sDxBRkT31^^EBf#@raYdGh^Z$7iZ2#Zix>>svbdq>L`Wk*Bl zypJ4&bAxsr>WIUPUkfD;gx~*WX?M_`eAKi?P=xgk{O+_E9ih1N6=qHD?#(xuDxHc` zYvxFrHVeK6^wUZF%ek3JVDFXgOWVxij_hf-3o4~=3c@N$WWY>(3JZV2ca?275ryxt zJ^ICHz9_eVW;@?07V4(Z@_6Sw#{B%JEM*5e3%=twE4`zrC6U4@J@X_z`(=g^`Xct5 z1{ph@&g*w%3a4#Ihpq&T@!#|A-oe3G9y_Mtd?|j_I~}q2`^~OcRrOobLV(w}<#0q9+wnUZ&;O8R(16;# mHER;J*$nz4)^k_vfsVh54N0^(;te%woqwrNW@$8QR1l&kg+E3B From 735cbd9307d50ad934dc7da3de2f54e32da9bbc9 Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Mon, 8 Jul 2024 14:26:26 -0700 Subject: [PATCH 845/966] chore(python): use python 3.10 for docs build (#1554) Source-Link: https://github.com/googleapis/synthtool/commit/9ae07858520bf035a3d5be569b5a65d960ee4392 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:52210e0e0559f5ea8c52be148b33504022e1faef4e95fbe4b32d68022af2fa7e Co-authored-by: Owl Bot Co-authored-by: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> --- .../google-auth/.github/.OwlBot.lock.yaml | 4 +- .../.kokoro/docker/docs/Dockerfile | 23 +- .../.kokoro/docker/docs/requirements.txt | 40 +- .../google-auth/.kokoro/populate-secrets.sh | 2 +- packages/google-auth/.kokoro/publish-docs.sh | 2 +- packages/google-auth/.kokoro/release.sh | 2 +- packages/google-auth/.kokoro/requirements.txt | 509 +++++++++--------- .../.kokoro/test-samples-against-head.sh | 2 +- .../google-auth/.kokoro/test-samples-impl.sh | 2 +- packages/google-auth/.kokoro/test-samples.sh | 2 +- packages/google-auth/.kokoro/trampoline.sh | 2 +- packages/google-auth/.kokoro/trampoline_v2.sh | 2 +- packages/google-auth/.trampolinerc | 2 +- 13 files changed, 312 insertions(+), 282 deletions(-) diff --git a/packages/google-auth/.github/.OwlBot.lock.yaml b/packages/google-auth/.github/.OwlBot.lock.yaml index 81f87c56917d..f30cb3775afc 100644 --- a/packages/google-auth/.github/.OwlBot.lock.yaml +++ b/packages/google-auth/.github/.OwlBot.lock.yaml @@ -13,5 +13,5 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:5a4c19d17e597b92d786e569be101e636c9c2817731f80a5adec56b2aa8fe070 -# created: 2024-04-12T11:35:58.922854369Z + digest: sha256:52210e0e0559f5ea8c52be148b33504022e1faef4e95fbe4b32d68022af2fa7e +# created: 2024-07-08T19:25:35.862283192Z diff --git a/packages/google-auth/.kokoro/docker/docs/Dockerfile b/packages/google-auth/.kokoro/docker/docs/Dockerfile index bdaf39fe22d0..5205308b334d 100644 --- a/packages/google-auth/.kokoro/docker/docs/Dockerfile +++ b/packages/google-auth/.kokoro/docker/docs/Dockerfile @@ -1,4 +1,4 @@ -# Copyright 2023 Google LLC +# Copyright 2024 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from ubuntu:22.04 +from ubuntu:24.04 ENV DEBIAN_FRONTEND noninteractive @@ -40,7 +40,6 @@ RUN apt-get update \ libssl-dev \ libsqlite3-dev \ portaudio19-dev \ - python3-distutils \ redis-server \ software-properties-common \ ssh \ @@ -60,18 +59,22 @@ RUN apt-get update \ && rm -rf /var/lib/apt/lists/* \ && rm -f /var/cache/apt/archives/*.deb -###################### Install python 3.9.13 -# Download python 3.9.13 -RUN wget https://www.python.org/ftp/python/3.9.13/Python-3.9.13.tgz +###################### Install python 3.10.14 for docs/docfx session + +# Download python 3.10.14 +RUN wget https://www.python.org/ftp/python/3.10.14/Python-3.10.14.tgz # Extract files -RUN tar -xvf Python-3.9.13.tgz +RUN tar -xvf Python-3.10.14.tgz -# Install python 3.9.13 -RUN ./Python-3.9.13/configure --enable-optimizations +# Install python 3.10.14 +RUN ./Python-3.10.14/configure --enable-optimizations RUN make altinstall +RUN python3.10 -m venv /venv +ENV PATH /venv/bin:$PATH + ###################### Install pip RUN wget -O /tmp/get-pip.py 'https://bootstrap.pypa.io/get-pip.py' \ && python3 /tmp/get-pip.py \ @@ -84,4 +87,4 @@ RUN python3 -m pip COPY requirements.txt /requirements.txt RUN python3 -m pip install --require-hashes -r requirements.txt -CMD ["python3.8"] +CMD ["python3.10"] diff --git a/packages/google-auth/.kokoro/docker/docs/requirements.txt b/packages/google-auth/.kokoro/docker/docs/requirements.txt index 0e5d70f20f83..7129c7715594 100644 --- a/packages/google-auth/.kokoro/docker/docs/requirements.txt +++ b/packages/google-auth/.kokoro/docker/docs/requirements.txt @@ -4,9 +4,9 @@ # # pip-compile --allow-unsafe --generate-hashes requirements.in # -argcomplete==3.2.3 \ - --hash=sha256:bf7900329262e481be5a15f56f19736b376df6f82ed27576fa893652c5de6c23 \ - --hash=sha256:c12355e0494c76a2a7b73e3a59b09024ca0ba1e279fb9ed6c1b82d5b74b6a70c +argcomplete==3.4.0 \ + --hash=sha256:69a79e083a716173e5532e0fa3bef45f793f4e61096cf52b5a42c0211c8b8aa5 \ + --hash=sha256:c2abcdfe1be8ace47ba777d4fce319eb13bf8ad9dace8d085dcad6eded88057f # via nox colorlog==6.8.2 \ --hash=sha256:3e3e079a41feb5a1b64f978b5ea4f46040a94f11f0e8bbb8261e3dbbeca64d44 \ @@ -16,23 +16,27 @@ distlib==0.3.8 \ --hash=sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784 \ --hash=sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64 # via virtualenv -filelock==3.13.1 \ - --hash=sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e \ - --hash=sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c +filelock==3.15.4 \ + --hash=sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb \ + --hash=sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7 # via virtualenv -nox==2024.3.2 \ - --hash=sha256:e53514173ac0b98dd47585096a55572fe504fecede58ced708979184d05440be \ - --hash=sha256:f521ae08a15adbf5e11f16cb34e8d0e6ea521e0b92868f684e91677deb974553 +nox==2024.4.15 \ + --hash=sha256:6492236efa15a460ecb98e7b67562a28b70da006ab0be164e8821177577c0565 \ + --hash=sha256:ecf6700199cdfa9e5ea0a41ff5e6ef4641d09508eda6edb89d9987864115817f # via -r requirements.in -packaging==24.0 \ - --hash=sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5 \ - --hash=sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9 +packaging==24.1 \ + --hash=sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002 \ + --hash=sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124 # via nox -platformdirs==4.2.0 \ - --hash=sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068 \ - --hash=sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768 +platformdirs==4.2.2 \ + --hash=sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee \ + --hash=sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3 # via virtualenv -virtualenv==20.25.1 \ - --hash=sha256:961c026ac520bac5f69acb8ea063e8a4f071bcc9457b9c1f28f6b085c511583a \ - --hash=sha256:e08e13ecdca7a0bd53798f356d5831434afa5b07b93f0abdf0797b7a06ffe197 +tomli==2.0.1 \ + --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \ + --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f + # via nox +virtualenv==20.26.3 \ + --hash=sha256:4c43a2a236279d9ea36a0d76f98d84bd6ca94ac4e0f4a3b9d46d05e10fea542a \ + --hash=sha256:8cc4a31139e796e9a7de2cd5cf2489de1217193116a8fd42328f1bd65f434589 # via nox diff --git a/packages/google-auth/.kokoro/populate-secrets.sh b/packages/google-auth/.kokoro/populate-secrets.sh index 6f3972140e80..c435402f473e 100755 --- a/packages/google-auth/.kokoro/populate-secrets.sh +++ b/packages/google-auth/.kokoro/populate-secrets.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright 2023 Google LLC. +# Copyright 2024 Google LLC. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/.kokoro/publish-docs.sh b/packages/google-auth/.kokoro/publish-docs.sh index 9eafe0be3bba..38f083f05aa0 100755 --- a/packages/google-auth/.kokoro/publish-docs.sh +++ b/packages/google-auth/.kokoro/publish-docs.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright 2023 Google LLC +# Copyright 2024 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/.kokoro/release.sh b/packages/google-auth/.kokoro/release.sh index 020ec9baf6b6..c22751b9844d 100755 --- a/packages/google-auth/.kokoro/release.sh +++ b/packages/google-auth/.kokoro/release.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright 2023 Google LLC +# Copyright 2024 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/.kokoro/requirements.txt b/packages/google-auth/.kokoro/requirements.txt index 51f92b8e12f1..9622baf0ba38 100644 --- a/packages/google-auth/.kokoro/requirements.txt +++ b/packages/google-auth/.kokoro/requirements.txt @@ -4,21 +4,25 @@ # # pip-compile --allow-unsafe --generate-hashes requirements.in # -argcomplete==3.1.4 \ - --hash=sha256:72558ba729e4c468572609817226fb0a6e7e9a0a7d477b882be168c0b4a62b94 \ - --hash=sha256:fbe56f8cda08aa9a04b307d8482ea703e96a6a801611acb4be9bf3942017989f +argcomplete==3.4.0 \ + --hash=sha256:69a79e083a716173e5532e0fa3bef45f793f4e61096cf52b5a42c0211c8b8aa5 \ + --hash=sha256:c2abcdfe1be8ace47ba777d4fce319eb13bf8ad9dace8d085dcad6eded88057f # via nox -attrs==23.1.0 \ - --hash=sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04 \ - --hash=sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015 +attrs==23.2.0 \ + --hash=sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30 \ + --hash=sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1 # via gcp-releasetool -cachetools==5.3.2 \ - --hash=sha256:086ee420196f7b2ab9ca2db2520aca326318b68fe5ba8bc4d49cca91add450f2 \ - --hash=sha256:861f35a13a451f94e301ce2bec7cac63e881232ccce7ed67fab9b5df4d3beaa1 +backports-tarfile==1.2.0 \ + --hash=sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34 \ + --hash=sha256:d75e02c268746e1b8144c278978b6e98e85de6ad16f8e4b0844a154557eca991 + # via jaraco-context +cachetools==5.3.3 \ + --hash=sha256:0abad1021d3f8325b2fc1d2e9c8b9c9d57b04c3932657a72465447332c24d945 \ + --hash=sha256:ba29e2dfa0b8b556606f097407ed1aa62080ee108ab0dc5ec9d6a723a007d105 # via google-auth -certifi==2023.7.22 \ - --hash=sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082 \ - --hash=sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9 +certifi==2024.7.4 \ + --hash=sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b \ + --hash=sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90 # via requests cffi==1.16.0 \ --hash=sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc \ @@ -87,90 +91,90 @@ click==8.0.4 \ # -r requirements.in # gcp-docuploader # gcp-releasetool -colorlog==6.7.0 \ - --hash=sha256:0d33ca236784a1ba3ff9c532d4964126d8a2c44f1f0cb1d2b0728196f512f662 \ - --hash=sha256:bd94bd21c1e13fac7bd3153f4bc3a7dc0eb0974b8bc2fdf1a989e474f6e582e5 +colorlog==6.8.2 \ + --hash=sha256:3e3e079a41feb5a1b64f978b5ea4f46040a94f11f0e8bbb8261e3dbbeca64d44 \ + --hash=sha256:4dcbb62368e2800cb3c5abd348da7e53f6c362dda502ec27c560b2e58a66bd33 # via # gcp-docuploader # nox -cryptography==42.0.5 \ - --hash=sha256:0270572b8bd2c833c3981724b8ee9747b3ec96f699a9665470018594301439ee \ - --hash=sha256:111a0d8553afcf8eb02a4fea6ca4f59d48ddb34497aa8706a6cf536f1a5ec576 \ - --hash=sha256:16a48c23a62a2f4a285699dba2e4ff2d1cff3115b9df052cdd976a18856d8e3d \ - --hash=sha256:1b95b98b0d2af784078fa69f637135e3c317091b615cd0905f8b8a087e86fa30 \ - --hash=sha256:1f71c10d1e88467126f0efd484bd44bca5e14c664ec2ede64c32f20875c0d413 \ - --hash=sha256:2424ff4c4ac7f6b8177b53c17ed5d8fa74ae5955656867f5a8affaca36a27abb \ - --hash=sha256:2bce03af1ce5a5567ab89bd90d11e7bbdff56b8af3acbbec1faded8f44cb06da \ - --hash=sha256:329906dcc7b20ff3cad13c069a78124ed8247adcac44b10bea1130e36caae0b4 \ - --hash=sha256:37dd623507659e08be98eec89323469e8c7b4c1407c85112634ae3dbdb926fdd \ - --hash=sha256:3eaafe47ec0d0ffcc9349e1708be2aaea4c6dd4978d76bf6eb0cb2c13636c6fc \ - --hash=sha256:5e6275c09d2badf57aea3afa80d975444f4be8d3bc58f7f80d2a484c6f9485c8 \ - --hash=sha256:6fe07eec95dfd477eb9530aef5bead34fec819b3aaf6c5bd6d20565da607bfe1 \ - --hash=sha256:7367d7b2eca6513681127ebad53b2582911d1736dc2ffc19f2c3ae49997496bc \ - --hash=sha256:7cde5f38e614f55e28d831754e8a3bacf9ace5d1566235e39d91b35502d6936e \ - --hash=sha256:9481ffe3cf013b71b2428b905c4f7a9a4f76ec03065b05ff499bb5682a8d9ad8 \ - --hash=sha256:98d8dc6d012b82287f2c3d26ce1d2dd130ec200c8679b6213b3c73c08b2b7940 \ - --hash=sha256:a011a644f6d7d03736214d38832e030d8268bcff4a41f728e6030325fea3e400 \ - --hash=sha256:a2913c5375154b6ef2e91c10b5720ea6e21007412f6437504ffea2109b5a33d7 \ - --hash=sha256:a30596bae9403a342c978fb47d9b0ee277699fa53bbafad14706af51fe543d16 \ - --hash=sha256:b03c2ae5d2f0fc05f9a2c0c997e1bc18c8229f392234e8a0194f202169ccd278 \ - --hash=sha256:b6cd2203306b63e41acdf39aa93b86fb566049aeb6dc489b70e34bcd07adca74 \ - --hash=sha256:b7ffe927ee6531c78f81aa17e684e2ff617daeba7f189f911065b2ea2d526dec \ - --hash=sha256:b8cac287fafc4ad485b8a9b67d0ee80c66bf3574f655d3b97ef2e1082360faf1 \ - --hash=sha256:ba334e6e4b1d92442b75ddacc615c5476d4ad55cc29b15d590cc6b86efa487e2 \ - --hash=sha256:ba3e4a42397c25b7ff88cdec6e2a16c2be18720f317506ee25210f6d31925f9c \ - --hash=sha256:c41fb5e6a5fe9ebcd58ca3abfeb51dffb5d83d6775405305bfa8715b76521922 \ - --hash=sha256:cd2030f6650c089aeb304cf093f3244d34745ce0cfcc39f20c6fbfe030102e2a \ - --hash=sha256:cd65d75953847815962c84a4654a84850b2bb4aed3f26fadcc1c13892e1e29f6 \ - --hash=sha256:e4985a790f921508f36f81831817cbc03b102d643b5fcb81cd33df3fa291a1a1 \ - --hash=sha256:e807b3188f9eb0eaa7bbb579b462c5ace579f1cedb28107ce8b48a9f7ad3679e \ - --hash=sha256:f12764b8fffc7a123f641d7d049d382b73f96a34117e0b637b80643169cec8ac \ - --hash=sha256:f8837fe1d6ac4a8052a9a8ddab256bc006242696f03368a4009be7ee3075cdb7 +cryptography==42.0.8 \ + --hash=sha256:013629ae70b40af70c9a7a5db40abe5d9054e6f4380e50ce769947b73bf3caad \ + --hash=sha256:2346b911eb349ab547076f47f2e035fc8ff2c02380a7cbbf8d87114fa0f1c583 \ + --hash=sha256:2f66d9cd9147ee495a8374a45ca445819f8929a3efcd2e3df6428e46c3cbb10b \ + --hash=sha256:2f88d197e66c65be5e42cd72e5c18afbfae3f741742070e3019ac8f4ac57262c \ + --hash=sha256:31f721658a29331f895a5a54e7e82075554ccfb8b163a18719d342f5ffe5ecb1 \ + --hash=sha256:343728aac38decfdeecf55ecab3264b015be68fc2816ca800db649607aeee648 \ + --hash=sha256:5226d5d21ab681f432a9c1cf8b658c0cb02533eece706b155e5fbd8a0cdd3949 \ + --hash=sha256:57080dee41209e556a9a4ce60d229244f7a66ef52750f813bfbe18959770cfba \ + --hash=sha256:5a94eccb2a81a309806027e1670a358b99b8fe8bfe9f8d329f27d72c094dde8c \ + --hash=sha256:6b7c4f03ce01afd3b76cf69a5455caa9cfa3de8c8f493e0d3ab7d20611c8dae9 \ + --hash=sha256:7016f837e15b0a1c119d27ecd89b3515f01f90a8615ed5e9427e30d9cdbfed3d \ + --hash=sha256:81884c4d096c272f00aeb1f11cf62ccd39763581645b0812e99a91505fa48e0c \ + --hash=sha256:81d8a521705787afe7a18d5bfb47ea9d9cc068206270aad0b96a725022e18d2e \ + --hash=sha256:8d09d05439ce7baa8e9e95b07ec5b6c886f548deb7e0f69ef25f64b3bce842f2 \ + --hash=sha256:961e61cefdcb06e0c6d7e3a1b22ebe8b996eb2bf50614e89384be54c48c6b63d \ + --hash=sha256:9c0c1716c8447ee7dbf08d6db2e5c41c688544c61074b54fc4564196f55c25a7 \ + --hash=sha256:a0608251135d0e03111152e41f0cc2392d1e74e35703960d4190b2e0f4ca9c70 \ + --hash=sha256:a0c5b2b0585b6af82d7e385f55a8bc568abff8923af147ee3c07bd8b42cda8b2 \ + --hash=sha256:ad803773e9df0b92e0a817d22fd8a3675493f690b96130a5e24f1b8fabbea9c7 \ + --hash=sha256:b297f90c5723d04bcc8265fc2a0f86d4ea2e0f7ab4b6994459548d3a6b992a14 \ + --hash=sha256:ba4f0a211697362e89ad822e667d8d340b4d8d55fae72cdd619389fb5912eefe \ + --hash=sha256:c4783183f7cb757b73b2ae9aed6599b96338eb957233c58ca8f49a49cc32fd5e \ + --hash=sha256:c9bb2ae11bfbab395bdd072985abde58ea9860ed84e59dbc0463a5d0159f5b71 \ + --hash=sha256:cafb92b2bc622cd1aa6a1dce4b93307792633f4c5fe1f46c6b97cf67073ec961 \ + --hash=sha256:d45b940883a03e19e944456a558b67a41160e367a719833c53de6911cabba2b7 \ + --hash=sha256:dc0fdf6787f37b1c6b08e6dfc892d9d068b5bdb671198c72072828b80bd5fe4c \ + --hash=sha256:dea567d1b0e8bc5764b9443858b673b734100c2871dc93163f58c46a97a83d28 \ + --hash=sha256:dec9b018df185f08483f294cae6ccac29e7a6e0678996587363dc352dc65c842 \ + --hash=sha256:e3ec3672626e1b9e55afd0df6d774ff0e953452886e06e0f1eb7eb0c832e8902 \ + --hash=sha256:e599b53fd95357d92304510fb7bda8523ed1f79ca98dce2f43c115950aa78801 \ + --hash=sha256:fa76fbb7596cc5839320000cdd5d0955313696d9511debab7ee7278fc8b5c84a \ + --hash=sha256:fff12c88a672ab9c9c1cf7b0c80e3ad9e2ebd9d828d955c126be4fd3e5578c9e # via # -r requirements.in # gcp-releasetool # secretstorage -distlib==0.3.7 \ - --hash=sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057 \ - --hash=sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8 +distlib==0.3.8 \ + --hash=sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784 \ + --hash=sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64 # via virtualenv -docutils==0.20.1 \ - --hash=sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6 \ - --hash=sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b +docutils==0.21.2 \ + --hash=sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f \ + --hash=sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2 # via readme-renderer -filelock==3.13.1 \ - --hash=sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e \ - --hash=sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c +filelock==3.15.4 \ + --hash=sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb \ + --hash=sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7 # via virtualenv gcp-docuploader==0.6.5 \ --hash=sha256:30221d4ac3e5a2b9c69aa52fdbef68cc3f27d0e6d0d90e220fc024584b8d2318 \ --hash=sha256:b7458ef93f605b9d46a4bf3a8dc1755dad1f31d030c8679edf304e343b347eea # via -r requirements.in -gcp-releasetool==2.0.0 \ - --hash=sha256:3d73480b50ba243f22d7c7ec08b115a30e1c7817c4899781840c26f9c55b8277 \ - --hash=sha256:7aa9fd935ec61e581eb8458ad00823786d91756c25e492f372b2b30962f3c28f +gcp-releasetool==2.0.1 \ + --hash=sha256:34314a910c08e8911d9c965bd44f8f2185c4f556e737d719c33a41f6a610de96 \ + --hash=sha256:b0d5863c6a070702b10883d37c4bdfd74bf930fe417f36c0c965d3b7c779ae62 # via -r requirements.in -google-api-core==2.12.0 \ - --hash=sha256:c22e01b1e3c4dcd90998494879612c38d0a3411d1f7b679eb89e2abe3ce1f553 \ - --hash=sha256:ec6054f7d64ad13b41e43d96f735acbd763b0f3b695dabaa2d579673f6a6e160 +google-api-core==2.19.1 \ + --hash=sha256:f12a9b8309b5e21d92483bbd47ce2c445861ec7d269ef6784ecc0ea8c1fa6125 \ + --hash=sha256:f4695f1e3650b316a795108a76a1c416e6afb036199d1c1f1f110916df479ffd # via # google-cloud-core # google-cloud-storage -google-auth==2.23.4 \ - --hash=sha256:79905d6b1652187def79d491d6e23d0cbb3a21d3c7ba0dbaa9c8a01906b13ff3 \ - --hash=sha256:d4bbc92fe4b8bfd2f3e8d88e5ba7085935da208ee38a134fc280e7ce682a05f2 +google-auth==2.31.0 \ + --hash=sha256:042c4702efa9f7d3c48d3a69341c209381b125faa6dbf3ebe56bc7e40ae05c23 \ + --hash=sha256:87805c36970047247c8afe614d4e3af8eceafc1ebba0c679fe75ddd1d575e871 # via # gcp-releasetool # google-api-core # google-cloud-core # google-cloud-storage -google-cloud-core==2.3.3 \ - --hash=sha256:37b80273c8d7eee1ae816b3a20ae43585ea50506cb0e60f3cf5be5f87f1373cb \ - --hash=sha256:fbd11cad3e98a7e5b0343dc07cb1039a5ffd7a5bb96e1f1e27cee4bda4a90863 +google-cloud-core==2.4.1 \ + --hash=sha256:9b7749272a812bde58fff28868d0c5e2f585b82f37e09a1f6ed2d4d10f134073 \ + --hash=sha256:a9e6a4422b9ac5c29f79a0ede9485473338e2ce78d91f2370c01e730eab22e61 # via google-cloud-storage -google-cloud-storage==2.13.0 \ - --hash=sha256:ab0bf2e1780a1b74cf17fccb13788070b729f50c252f0c94ada2aae0ca95437d \ - --hash=sha256:f62dc4c7b6cd4360d072e3deb28035fbdad491ac3d9b0b1815a12daea10f37c7 +google-cloud-storage==2.17.0 \ + --hash=sha256:49378abff54ef656b52dca5ef0f2eba9aa83dc2b2c72c78714b03a1a95fe9388 \ + --hash=sha256:5b393bc766b7a3bc6f5407b9e665b2450d36282614b7945e570b3480a456d1e1 # via gcp-docuploader google-crc32c==1.5.0 \ --hash=sha256:024894d9d3cfbc5943f8f230e23950cd4906b2fe004c72e29b209420a1e6b05a \ @@ -244,28 +248,36 @@ google-crc32c==1.5.0 \ # via # google-cloud-storage # google-resumable-media -google-resumable-media==2.6.0 \ - --hash=sha256:972852f6c65f933e15a4a210c2b96930763b47197cdf4aa5f5bea435efb626e7 \ - --hash=sha256:fc03d344381970f79eebb632a3c18bb1828593a2dc5572b5f90115ef7d11e81b +google-resumable-media==2.7.1 \ + --hash=sha256:103ebc4ba331ab1bfdac0250f8033627a2cd7cde09e7ccff9181e31ba4315b2c \ + --hash=sha256:eae451a7b2e2cdbaaa0fd2eb00cc8a1ee5e95e16b55597359cbc3d27d7d90e33 # via google-cloud-storage -googleapis-common-protos==1.61.0 \ - --hash=sha256:22f1915393bb3245343f6efe87f6fe868532efc12aa26b391b15132e1279f1c0 \ - --hash=sha256:8a64866a97f6304a7179873a465d6eee97b7a24ec6cfd78e0f575e96b821240b +googleapis-common-protos==1.63.2 \ + --hash=sha256:27a2499c7e8aff199665b22741997e485eccc8645aa9176c7c988e6fae507945 \ + --hash=sha256:27c5abdffc4911f28101e635de1533fb4cfd2c37fbaa9174587c799fac90aa87 # via google-api-core idna==3.7 \ --hash=sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc \ --hash=sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0 # via requests -importlib-metadata==6.8.0 \ - --hash=sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb \ - --hash=sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743 +importlib-metadata==8.0.0 \ + --hash=sha256:15584cf2b1bf449d98ff8a6ff1abef57bf20f3ac6454f431736cd3e660921b2f \ + --hash=sha256:188bd24e4c346d3f0a933f275c2fec67050326a856b9a359881d7c2a697e8812 # via # -r requirements.in # keyring # twine -jaraco-classes==3.3.0 \ - --hash=sha256:10afa92b6743f25c0cf5f37c6bb6e18e2c5bb84a16527ccfc0040ea377e7aaeb \ - --hash=sha256:c063dd08e89217cee02c8d5e5ec560f2c8ce6cdc2fcdc2e68f7b2e5547ed3621 +jaraco-classes==3.4.0 \ + --hash=sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd \ + --hash=sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790 + # via keyring +jaraco-context==5.3.0 \ + --hash=sha256:3e16388f7da43d384a1a7cd3452e72e14732ac9fe459678773a3608a812bf266 \ + --hash=sha256:c2f67165ce1f9be20f32f650f25d8edfc1646a8aeee48ae06fb35f90763576d2 + # via keyring +jaraco-functools==4.0.1 \ + --hash=sha256:3b24ccb921d6b593bdceb56ce14799204f473976e2a9d4b15b04d0f2c2326664 \ + --hash=sha256:d33fa765374c0611b52f8b3a795f8900869aa88c84769d4d1746cd68fb28c3e8 # via keyring jeepney==0.8.0 \ --hash=sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806 \ @@ -273,13 +285,13 @@ jeepney==0.8.0 \ # via # keyring # secretstorage -jinja2==3.1.3 \ - --hash=sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa \ - --hash=sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90 +jinja2==3.1.4 \ + --hash=sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369 \ + --hash=sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d # via gcp-releasetool -keyring==24.2.0 \ - --hash=sha256:4901caaf597bfd3bbd78c9a0c7c4c29fcd8310dab2cffefe749e916b6527acd6 \ - --hash=sha256:ca0746a19ec421219f4d713f848fa297a661a8a8c1504867e55bfb5e09091509 +keyring==25.2.1 \ + --hash=sha256:2458681cdefc0dbc0b7eb6cf75d0b98e59f9ad9b2d4edd319d18f68bdca95e50 \ + --hash=sha256:daaffd42dbda25ddafb1ad5fec4024e5bbcfe424597ca1ca452b299861e49f1b # via # gcp-releasetool # twine @@ -287,146 +299,153 @@ markdown-it-py==3.0.0 \ --hash=sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1 \ --hash=sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb # via rich -markupsafe==2.1.3 \ - --hash=sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e \ - --hash=sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e \ - --hash=sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431 \ - --hash=sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686 \ - --hash=sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c \ - --hash=sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559 \ - --hash=sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc \ - --hash=sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb \ - --hash=sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939 \ - --hash=sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c \ - --hash=sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0 \ - --hash=sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4 \ - --hash=sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9 \ - --hash=sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575 \ - --hash=sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba \ - --hash=sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d \ - --hash=sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd \ - --hash=sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3 \ - --hash=sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00 \ - --hash=sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155 \ - --hash=sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac \ - --hash=sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52 \ - --hash=sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f \ - --hash=sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8 \ - --hash=sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b \ - --hash=sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007 \ - --hash=sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24 \ - --hash=sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea \ - --hash=sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198 \ - --hash=sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0 \ - --hash=sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee \ - --hash=sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be \ - --hash=sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2 \ - --hash=sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1 \ - --hash=sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707 \ - --hash=sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6 \ - --hash=sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c \ - --hash=sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58 \ - --hash=sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823 \ - --hash=sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779 \ - --hash=sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636 \ - --hash=sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c \ - --hash=sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad \ - --hash=sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee \ - --hash=sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc \ - --hash=sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2 \ - --hash=sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48 \ - --hash=sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7 \ - --hash=sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e \ - --hash=sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b \ - --hash=sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa \ - --hash=sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5 \ - --hash=sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e \ - --hash=sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb \ - --hash=sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9 \ - --hash=sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57 \ - --hash=sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc \ - --hash=sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc \ - --hash=sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2 \ - --hash=sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11 +markupsafe==2.1.5 \ + --hash=sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf \ + --hash=sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff \ + --hash=sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f \ + --hash=sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3 \ + --hash=sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532 \ + --hash=sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f \ + --hash=sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617 \ + --hash=sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df \ + --hash=sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4 \ + --hash=sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906 \ + --hash=sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f \ + --hash=sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4 \ + --hash=sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8 \ + --hash=sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371 \ + --hash=sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2 \ + --hash=sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465 \ + --hash=sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52 \ + --hash=sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6 \ + --hash=sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169 \ + --hash=sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad \ + --hash=sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2 \ + --hash=sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0 \ + --hash=sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029 \ + --hash=sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f \ + --hash=sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a \ + --hash=sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced \ + --hash=sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5 \ + --hash=sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c \ + --hash=sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf \ + --hash=sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9 \ + --hash=sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb \ + --hash=sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad \ + --hash=sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3 \ + --hash=sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1 \ + --hash=sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46 \ + --hash=sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc \ + --hash=sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a \ + --hash=sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee \ + --hash=sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900 \ + --hash=sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5 \ + --hash=sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea \ + --hash=sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f \ + --hash=sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5 \ + --hash=sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e \ + --hash=sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a \ + --hash=sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f \ + --hash=sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50 \ + --hash=sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a \ + --hash=sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b \ + --hash=sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4 \ + --hash=sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff \ + --hash=sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2 \ + --hash=sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46 \ + --hash=sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b \ + --hash=sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf \ + --hash=sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5 \ + --hash=sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5 \ + --hash=sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab \ + --hash=sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd \ + --hash=sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68 # via jinja2 mdurl==0.1.2 \ --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \ --hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba # via markdown-it-py -more-itertools==10.1.0 \ - --hash=sha256:626c369fa0eb37bac0291bce8259b332fd59ac792fa5497b59837309cd5b114a \ - --hash=sha256:64e0735fcfdc6f3464ea133afe8ea4483b1c5fe3a3d69852e6503b43a0b222e6 - # via jaraco-classes -nh3==0.2.14 \ - --hash=sha256:116c9515937f94f0057ef50ebcbcc10600860065953ba56f14473ff706371873 \ - --hash=sha256:18415df36db9b001f71a42a3a5395db79cf23d556996090d293764436e98e8ad \ - --hash=sha256:203cac86e313cf6486704d0ec620a992c8bc164c86d3a4fd3d761dd552d839b5 \ - --hash=sha256:2b0be5c792bd43d0abef8ca39dd8acb3c0611052ce466d0401d51ea0d9aa7525 \ - --hash=sha256:377aaf6a9e7c63962f367158d808c6a1344e2b4f83d071c43fbd631b75c4f0b2 \ - --hash=sha256:525846c56c2bcd376f5eaee76063ebf33cf1e620c1498b2a40107f60cfc6054e \ - --hash=sha256:5529a3bf99402c34056576d80ae5547123f1078da76aa99e8ed79e44fa67282d \ - --hash=sha256:7771d43222b639a4cd9e341f870cee336b9d886de1ad9bec8dddab22fe1de450 \ - --hash=sha256:88c753efbcdfc2644a5012938c6b9753f1c64a5723a67f0301ca43e7b85dcf0e \ - --hash=sha256:93a943cfd3e33bd03f77b97baa11990148687877b74193bf777956b67054dcc6 \ - --hash=sha256:9be2f68fb9a40d8440cbf34cbf40758aa7f6093160bfc7fb018cce8e424f0c3a \ - --hash=sha256:a0c509894fd4dccdff557068e5074999ae3b75f4c5a2d6fb5415e782e25679c4 \ - --hash=sha256:ac8056e937f264995a82bf0053ca898a1cb1c9efc7cd68fa07fe0060734df7e4 \ - --hash=sha256:aed56a86daa43966dd790ba86d4b810b219f75b4bb737461b6886ce2bde38fd6 \ - --hash=sha256:e8986f1dd3221d1e741fda0a12eaa4a273f1d80a35e31a1ffe579e7c621d069e \ - --hash=sha256:f99212a81c62b5f22f9e7c3e347aa00491114a5647e1f13bbebd79c3e5f08d75 +more-itertools==10.3.0 \ + --hash=sha256:e5d93ef411224fbcef366a6e8ddc4c5781bc6359d43412a65dd5964e46111463 \ + --hash=sha256:ea6a02e24a9161e51faad17a8782b92a0df82c12c1c8886fec7f0c3fa1a1b320 + # via + # jaraco-classes + # jaraco-functools +nh3==0.2.18 \ + --hash=sha256:0411beb0589eacb6734f28d5497ca2ed379eafab8ad8c84b31bb5c34072b7164 \ + --hash=sha256:14c5a72e9fe82aea5fe3072116ad4661af5cf8e8ff8fc5ad3450f123e4925e86 \ + --hash=sha256:19aaba96e0f795bd0a6c56291495ff59364f4300d4a39b29a0abc9cb3774a84b \ + --hash=sha256:34c03fa78e328c691f982b7c03d4423bdfd7da69cd707fe572f544cf74ac23ad \ + --hash=sha256:36c95d4b70530b320b365659bb5034341316e6a9b30f0b25fa9c9eff4c27a204 \ + --hash=sha256:3a157ab149e591bb638a55c8c6bcb8cdb559c8b12c13a8affaba6cedfe51713a \ + --hash=sha256:42c64511469005058cd17cc1537578eac40ae9f7200bedcfd1fc1a05f4f8c200 \ + --hash=sha256:5f36b271dae35c465ef5e9090e1fdaba4a60a56f0bb0ba03e0932a66f28b9189 \ + --hash=sha256:6955369e4d9f48f41e3f238a9e60f9410645db7e07435e62c6a9ea6135a4907f \ + --hash=sha256:7b7c2a3c9eb1a827d42539aa64091640bd275b81e097cd1d8d82ef91ffa2e811 \ + --hash=sha256:8ce0f819d2f1933953fca255db2471ad58184a60508f03e6285e5114b6254844 \ + --hash=sha256:94a166927e53972a9698af9542ace4e38b9de50c34352b962f4d9a7d4c927af4 \ + --hash=sha256:a7f1b5b2c15866f2db413a3649a8fe4fd7b428ae58be2c0f6bca5eefd53ca2be \ + --hash=sha256:c8b3a1cebcba9b3669ed1a84cc65bf005728d2f0bc1ed2a6594a992e817f3a50 \ + --hash=sha256:de3ceed6e661954871d6cd78b410213bdcb136f79aafe22aa7182e028b8c7307 \ + --hash=sha256:f0eca9ca8628dbb4e916ae2491d72957fdd35f7a5d326b7032a345f111ac07fe # via readme-renderer -nox==2023.4.22 \ - --hash=sha256:0b1adc619c58ab4fa57d6ab2e7823fe47a32e70202f287d78474adcc7bda1891 \ - --hash=sha256:46c0560b0dc609d7d967dc99e22cb463d3c4caf54a5fda735d6c11b5177e3a9f +nox==2024.4.15 \ + --hash=sha256:6492236efa15a460ecb98e7b67562a28b70da006ab0be164e8821177577c0565 \ + --hash=sha256:ecf6700199cdfa9e5ea0a41ff5e6ef4641d09508eda6edb89d9987864115817f # via -r requirements.in -packaging==23.2 \ - --hash=sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5 \ - --hash=sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7 +packaging==24.1 \ + --hash=sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002 \ + --hash=sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124 # via # gcp-releasetool # nox -pkginfo==1.9.6 \ - --hash=sha256:4b7a555a6d5a22169fcc9cf7bfd78d296b0361adad412a346c1226849af5e546 \ - --hash=sha256:8fd5896e8718a4372f0ea9cc9d96f6417c9b986e23a4d116dda26b62cc29d046 +pkginfo==1.10.0 \ + --hash=sha256:5df73835398d10db79f8eecd5cd86b1f6d29317589ea70796994d49399af6297 \ + --hash=sha256:889a6da2ed7ffc58ab5b900d888ddce90bce912f2d2de1dc1c26f4cb9fe65097 # via twine -platformdirs==3.11.0 \ - --hash=sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3 \ - --hash=sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e +platformdirs==4.2.2 \ + --hash=sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee \ + --hash=sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3 # via virtualenv -protobuf==4.25.3 \ - --hash=sha256:19b270aeaa0099f16d3ca02628546b8baefe2955bbe23224aaf856134eccf1e4 \ - --hash=sha256:209ba4cc916bab46f64e56b85b090607a676f66b473e6b762e6f1d9d591eb2e8 \ - --hash=sha256:25b5d0b42fd000320bd7830b349e3b696435f3b329810427a6bcce6a5492cc5c \ - --hash=sha256:7c8daa26095f82482307bc717364e7c13f4f1c99659be82890dcfc215194554d \ - --hash=sha256:c053062984e61144385022e53678fbded7aea14ebb3e0305ae3592fb219ccfa4 \ - --hash=sha256:d4198877797a83cbfe9bffa3803602bbe1625dc30d8a097365dbc762e5790faa \ - --hash=sha256:e3c97a1555fd6388f857770ff8b9703083de6bf1f9274a002a332d65fbb56c8c \ - --hash=sha256:e7cb0ae90dd83727f0c0718634ed56837bfeeee29a5f82a7514c03ee1364c019 \ - --hash=sha256:f0700d54bcf45424477e46a9f0944155b46fb0639d69728739c0e47bab83f2b9 \ - --hash=sha256:f1279ab38ecbfae7e456a108c5c0681e4956d5b1090027c1de0f934dfdb4b35c \ - --hash=sha256:f4f118245c4a087776e0a8408be33cf09f6c547442c00395fbfb116fac2f8ac2 +proto-plus==1.24.0 \ + --hash=sha256:30b72a5ecafe4406b0d339db35b56c4059064e69227b8c3bda7462397f966445 \ + --hash=sha256:402576830425e5f6ce4c2a6702400ac79897dab0b4343821aa5188b0fab81a12 + # via google-api-core +protobuf==5.27.2 \ + --hash=sha256:0e341109c609749d501986b835f667c6e1e24531096cff9d34ae411595e26505 \ + --hash=sha256:176c12b1f1c880bf7a76d9f7c75822b6a2bc3db2d28baa4d300e8ce4cde7409b \ + --hash=sha256:354d84fac2b0d76062e9b3221f4abbbacdfd2a4d8af36bab0474f3a0bb30ab38 \ + --hash=sha256:4fadd8d83e1992eed0248bc50a4a6361dc31bcccc84388c54c86e530b7f58863 \ + --hash=sha256:54330f07e4949d09614707c48b06d1a22f8ffb5763c159efd5c0928326a91470 \ + --hash=sha256:610e700f02469c4a997e58e328cac6f305f649826853813177e6290416e846c6 \ + --hash=sha256:7fc3add9e6003e026da5fc9e59b131b8f22b428b991ccd53e2af8071687b4fce \ + --hash=sha256:9e8f199bf7f97bd7ecebffcae45ebf9527603549b2b562df0fbc6d4d688f14ca \ + --hash=sha256:a109916aaac42bff84702fb5187f3edadbc7c97fc2c99c5ff81dd15dcce0d1e5 \ + --hash=sha256:b848dbe1d57ed7c191dfc4ea64b8b004a3f9ece4bf4d0d80a367b76df20bf36e \ + --hash=sha256:f3ecdef226b9af856075f28227ff2c90ce3a594d092c39bee5513573f25e2714 # via # gcp-docuploader # gcp-releasetool # google-api-core # googleapis-common-protos -pyasn1==0.5.0 \ - --hash=sha256:87a2121042a1ac9358cabcaf1d07680ff97ee6404333bacca15f76aa8ad01a57 \ - --hash=sha256:97b7290ca68e62a832558ec3976f15cbf911bf5d7c7039d8b861c2a0ece69fde + # proto-plus +pyasn1==0.6.0 \ + --hash=sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c \ + --hash=sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473 # via # pyasn1-modules # rsa -pyasn1-modules==0.3.0 \ - --hash=sha256:5bd01446b736eb9d31512a30d46c1ac3395d676c6f3cafa4c03eb54b9925631c \ - --hash=sha256:d3ccd6ed470d9ffbc716be08bd90efbd44d0734bc9303818f7336070984a162d +pyasn1-modules==0.4.0 \ + --hash=sha256:831dbcea1b177b28c9baddf4c6d1013c24c3accd14a1873fffaa6a2e905f17b6 \ + --hash=sha256:be04f15b66c206eed667e0bb5ab27e2b1855ea54a842e5037738099e8ca4ae0b # via google-auth -pycparser==2.21 \ - --hash=sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 \ - --hash=sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206 +pycparser==2.22 \ + --hash=sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6 \ + --hash=sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc # via cffi -pygments==2.16.1 \ - --hash=sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692 \ - --hash=sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29 +pygments==2.18.0 \ + --hash=sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199 \ + --hash=sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a # via # readme-renderer # rich @@ -434,20 +453,20 @@ pyjwt==2.8.0 \ --hash=sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de \ --hash=sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320 # via gcp-releasetool -pyperclip==1.8.2 \ - --hash=sha256:105254a8b04934f0bc84e9c24eb360a591aaf6535c9def5f29d92af107a9bf57 +pyperclip==1.9.0 \ + --hash=sha256:b7de0142ddc81bfc5c7507eea19da920b92252b548b96186caf94a5e2527d310 # via gcp-releasetool -python-dateutil==2.8.2 \ - --hash=sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 \ - --hash=sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9 +python-dateutil==2.9.0.post0 \ + --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \ + --hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427 # via gcp-releasetool -readme-renderer==42.0 \ - --hash=sha256:13d039515c1f24de668e2c93f2e877b9dbe6c6c32328b90a40a49d8b2b85f36d \ - --hash=sha256:2d55489f83be4992fe4454939d1a051c33edbab778e82761d060c9fc6b308cd1 +readme-renderer==44.0 \ + --hash=sha256:2fbca89b81a08526aadf1357a8c2ae889ec05fb03f5da67f9769c9a592166151 \ + --hash=sha256:8712034eabbfa6805cacf1402b4eeb2a73028f72d1166d6f5cb7f9c047c5d1e1 # via twine -requests==2.31.0 \ - --hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \ - --hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1 +requests==2.32.3 \ + --hash=sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760 \ + --hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6 # via # gcp-releasetool # google-api-core @@ -462,9 +481,9 @@ rfc3986==2.0.0 \ --hash=sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd \ --hash=sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c # via twine -rich==13.6.0 \ - --hash=sha256:2b38e2fe9ca72c9a00170a1a2d20c63c790d0e10ef1fe35eba76e1e7b1d7d245 \ - --hash=sha256:5c14d22737e6d5084ef4771b62d5d4363165b403455a30a1c8ca39dc7b644bef +rich==13.7.1 \ + --hash=sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222 \ + --hash=sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432 # via twine rsa==4.9 \ --hash=sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7 \ @@ -480,35 +499,39 @@ six==1.16.0 \ # via # gcp-docuploader # python-dateutil -twine==4.0.2 \ - --hash=sha256:929bc3c280033347a00f847236564d1c52a3e61b1ac2516c97c48f3ceab756d8 \ - --hash=sha256:9e102ef5fdd5a20661eb88fad46338806c3bd32cf1db729603fe3697b1bc83c8 +tomli==2.0.1 \ + --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \ + --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f + # via nox +twine==5.1.1 \ + --hash=sha256:215dbe7b4b94c2c50a7315c0275d2258399280fbb7d04182c7e55e24b5f93997 \ + --hash=sha256:9aa0825139c02b3434d913545c7b847a21c835e11597f5255842d457da2322db # via -r requirements.in -typing-extensions==4.8.0 \ - --hash=sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0 \ - --hash=sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef +typing-extensions==4.12.2 \ + --hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d \ + --hash=sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8 # via -r requirements.in -urllib3==2.0.7 \ - --hash=sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84 \ - --hash=sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e +urllib3==2.2.2 \ + --hash=sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472 \ + --hash=sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168 # via # requests # twine -virtualenv==20.24.6 \ - --hash=sha256:02ece4f56fbf939dbbc33c0715159951d6bf14aaf5457b092e4548e1382455af \ - --hash=sha256:520d056652454c5098a00c0f073611ccbea4c79089331f60bf9d7ba247bb7381 +virtualenv==20.26.3 \ + --hash=sha256:4c43a2a236279d9ea36a0d76f98d84bd6ca94ac4e0f4a3b9d46d05e10fea542a \ + --hash=sha256:8cc4a31139e796e9a7de2cd5cf2489de1217193116a8fd42328f1bd65f434589 # via nox -wheel==0.41.3 \ - --hash=sha256:488609bc63a29322326e05560731bf7bfea8e48ad646e1f5e40d366607de0942 \ - --hash=sha256:4d4987ce51a49370ea65c0bfd2234e8ce80a12780820d9dc462597a6e60d0841 +wheel==0.43.0 \ + --hash=sha256:465ef92c69fa5c5da2d1cf8ac40559a8c940886afcef87dcf14b9470862f1d85 \ + --hash=sha256:55c570405f142630c6b9f72fe09d9b67cf1477fcf543ae5b8dcb1f5b7377da81 # via -r requirements.in -zipp==3.17.0 \ - --hash=sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31 \ - --hash=sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0 +zipp==3.19.2 \ + --hash=sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19 \ + --hash=sha256:f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: -setuptools==69.2.0 \ - --hash=sha256:0ff4183f8f42cd8fa3acea16c45205521a4ef28f73c6391d8a25e92893134f2e \ - --hash=sha256:c21c49fb1042386df081cb5d86759792ab89efca84cf114889191cd09aacc80c +setuptools==70.2.0 \ + --hash=sha256:b8b8060bb426838fbe942479c90296ce976249451118ef566a5a0b7d8b78fb05 \ + --hash=sha256:bd63e505105011b25c3c11f753f7e3b8465ea739efddaccef8f0efac2137bac1 # via -r requirements.in diff --git a/packages/google-auth/.kokoro/test-samples-against-head.sh b/packages/google-auth/.kokoro/test-samples-against-head.sh index 63ac41dfae1d..e9d8bd79a644 100755 --- a/packages/google-auth/.kokoro/test-samples-against-head.sh +++ b/packages/google-auth/.kokoro/test-samples-against-head.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright 2023 Google LLC +# Copyright 2024 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/.kokoro/test-samples-impl.sh b/packages/google-auth/.kokoro/test-samples-impl.sh index 5a0f5fab6a89..55910c8ba178 100755 --- a/packages/google-auth/.kokoro/test-samples-impl.sh +++ b/packages/google-auth/.kokoro/test-samples-impl.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright 2023 Google LLC +# Copyright 2024 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/.kokoro/test-samples.sh b/packages/google-auth/.kokoro/test-samples.sh index 50b35a48c190..7933d820149a 100755 --- a/packages/google-auth/.kokoro/test-samples.sh +++ b/packages/google-auth/.kokoro/test-samples.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright 2023 Google LLC +# Copyright 2024 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/.kokoro/trampoline.sh b/packages/google-auth/.kokoro/trampoline.sh index d85b1f267693..48f79699706e 100755 --- a/packages/google-auth/.kokoro/trampoline.sh +++ b/packages/google-auth/.kokoro/trampoline.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright 2023 Google LLC +# Copyright 2024 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/.kokoro/trampoline_v2.sh b/packages/google-auth/.kokoro/trampoline_v2.sh index 59a7cf3a9373..35fa529231dc 100755 --- a/packages/google-auth/.kokoro/trampoline_v2.sh +++ b/packages/google-auth/.kokoro/trampoline_v2.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Copyright 2023 Google LLC +# Copyright 2024 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/packages/google-auth/.trampolinerc b/packages/google-auth/.trampolinerc index a7dfeb42c6d0..0080152373d5 100644 --- a/packages/google-auth/.trampolinerc +++ b/packages/google-auth/.trampolinerc @@ -1,4 +1,4 @@ -# Copyright 2023 Google LLC +# Copyright 2024 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. From c44cb78835e43bfc6e665d331dfee8e58e0c7e46 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 8 Jul 2024 21:54:18 +0000 Subject: [PATCH 846/966] chore(main): release 2.32.0 (#1546) :robot: I have created a release *beep* *boop* --- ## [2.32.0](https://togithub.com/googleapis/google-auth-library-python/compare/v2.31.0...v2.32.0) (2024-07-08) ### Features * Adds support for X509 workload credential type ([#1541](https://togithub.com/googleapis/google-auth-library-python/issues/1541)) ([1270217](https://togithub.com/googleapis/google-auth-library-python/commit/1270217556a0249b6be8824e2b6209371f24e400)) --- This PR was generated with [Release Please](https://togithub.com/googleapis/release-please). See [documentation](https://togithub.com/googleapis/release-please#release-please). --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index ffc55ac9c66e..e6bbd7781fd6 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.32.0](https://github.com/googleapis/google-auth-library-python/compare/v2.31.0...v2.32.0) (2024-07-08) + + +### Features + +* Adds support for X509 workload credential type ([#1541](https://github.com/googleapis/google-auth-library-python/issues/1541)) ([1270217](https://github.com/googleapis/google-auth-library-python/commit/1270217556a0249b6be8824e2b6209371f24e400)) + ## [2.31.0](https://github.com/googleapis/google-auth-library-python/compare/v2.30.0...v2.31.0) (2024-06-27) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index b9313c667da0..51f7f62acd7b 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.31.0" +__version__ = "2.32.0" From a333e2ae626a4bbf4a0d14611f83b8462bd951dd Mon Sep 17 00:00:00 2001 From: Carl Lundin <108372512+clundin25@users.noreply.github.com> Date: Thu, 11 Jul 2024 15:27:15 -0700 Subject: [PATCH 847/966] chore: Modify exponential backoff implementation to have no initial sleep (#1547) chore: No sleep on initial attempt in exponential backoff implementation It is unintuitive that the initial attempt in the exponential backoff loop sleeps. This can lead to subtle bugs in future call sites. This patch refactors the exponential backoff to begin sleeping on the 2nd iteration so requests can be done in a single for loop. --- .../google/auth/_exponential_backoff.py | 10 +++++ packages/google-auth/google/oauth2/_client.py | 22 ++++------- .../google/oauth2/_client_async.py | 20 ++++------ .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../google-auth/tests/oauth2/test__client.py | 10 ++--- .../tests/test__exponential_backoff.py | 35 +++++++++++++----- .../tests_async/oauth2/test__client_async.py | 2 +- 7 files changed, 56 insertions(+), 43 deletions(-) diff --git a/packages/google-auth/google/auth/_exponential_backoff.py b/packages/google-auth/google/auth/_exponential_backoff.py index 0dd621a9492a..04f9f9764120 100644 --- a/packages/google-auth/google/auth/_exponential_backoff.py +++ b/packages/google-auth/google/auth/_exponential_backoff.py @@ -15,6 +15,8 @@ import random import time +from google.auth import exceptions + # The default amount of retry attempts _DEFAULT_RETRY_TOTAL_ATTEMPTS = 3 @@ -68,6 +70,11 @@ def __init__( randomization_factor=_DEFAULT_RANDOMIZATION_FACTOR, multiplier=_DEFAULT_MULTIPLIER, ): + if total_attempts < 1: + raise exceptions.InvalidValue( + f"total_attempts must be greater than or equal to 1 but was {total_attempts}" + ) + self._total_attempts = total_attempts self._initial_wait_seconds = initial_wait_seconds @@ -87,6 +94,9 @@ def __next__(self): raise StopIteration self._backoff_count += 1 + if self._backoff_count <= 1: + return self._backoff_count + jitter_variance = self._current_wait_in_seconds * self._randomization_factor jitter = random.uniform( self._current_wait_in_seconds - jitter_variance, diff --git a/packages/google-auth/google/oauth2/_client.py b/packages/google-auth/google/oauth2/_client.py index bce797b88bb0..68e13ddc7344 100644 --- a/packages/google-auth/google/oauth2/_client.py +++ b/packages/google-auth/google/oauth2/_client.py @@ -183,7 +183,11 @@ def _token_endpoint_request_no_throw( if headers: headers_to_use.update(headers) - def _perform_request(): + response_data = {} + retryable_error = False + + retries = _exponential_backoff.ExponentialBackoff() + for _ in retries: response = request( method="POST", url=token_uri, headers=headers_to_use, body=body, **kwargs ) @@ -192,7 +196,7 @@ def _perform_request(): if hasattr(response.data, "decode") else response.data ) - response_data = "" + try: # response_body should be a JSON response_data = json.loads(response_body) @@ -206,18 +210,8 @@ def _perform_request(): status_code=response.status, response_data=response_data ) - return False, response_data, retryable_error - - request_succeeded, response_data, retryable_error = _perform_request() - - if request_succeeded or not retryable_error or not can_retry: - return request_succeeded, response_data, retryable_error - - retries = _exponential_backoff.ExponentialBackoff() - for _ in retries: - request_succeeded, response_data, retryable_error = _perform_request() - if request_succeeded or not retryable_error: - return request_succeeded, response_data, retryable_error + if not can_retry or not retryable_error: + return False, response_data, retryable_error return False, response_data, retryable_error diff --git a/packages/google-auth/google/oauth2/_client_async.py b/packages/google-auth/google/oauth2/_client_async.py index 2858d862b0b1..8867f0a52740 100644 --- a/packages/google-auth/google/oauth2/_client_async.py +++ b/packages/google-auth/google/oauth2/_client_async.py @@ -67,7 +67,11 @@ async def _token_endpoint_request_no_throw( if access_token: headers["Authorization"] = "Bearer {}".format(access_token) - async def _perform_request(): + response_data = {} + retryable_error = False + + retries = _exponential_backoff.ExponentialBackoff() + for _ in retries: response = await request( method="POST", url=token_uri, headers=headers, body=body ) @@ -93,18 +97,8 @@ async def _perform_request(): status_code=response.status, response_data=response_data ) - return False, response_data, retryable_error - - request_succeeded, response_data, retryable_error = await _perform_request() - - if request_succeeded or not retryable_error or not can_retry: - return request_succeeded, response_data, retryable_error - - retries = _exponential_backoff.ExponentialBackoff() - for _ in retries: - request_succeeded, response_data, retryable_error = await _perform_request() - if request_succeeded or not retryable_error: - return request_succeeded, response_data, retryable_error + if not can_retry or not retryable_error: + return False, response_data, retryable_error return False, response_data, retryable_error diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 325974fe37c7d45ba4c4fedf56ff9d0cfa8c1b1b..3d9bd3327546ea90e45e2a84311b752d1744c692 100644 GIT binary patch literal 10324 zcmV-aD67{BB>?tKRTDvxhv+ac|FOqQ=Vu-py_^L#6k-ae0V0+lz#c%7=JC)8m>u?U*O*1pWWyLag9oF%bI-˓eC8e(xEgXU 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 literal 10324 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�f8}!^IXZiYf#A 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 mWT)m%3QNzGD%b~gpM0F;6s7rmmb$05;(=-H diff --git a/packages/google-auth/tests/oauth2/test__client.py b/packages/google-auth/tests/oauth2/test__client.py index ab079ac5bc78..9da63cbdec08 100644 --- a/packages/google-auth/tests/oauth2/test__client.py +++ b/packages/google-auth/tests/oauth2/test__client.py @@ -194,8 +194,8 @@ def test__token_endpoint_request_internal_failure_error(): _client._token_endpoint_request( request, "http://example.com", {"error_description": "internal_failure"} ) - # request should be called once and then with 3 retries - assert request.call_count == 4 + # request with 2 retries + assert request.call_count == 3 request = make_request( {"error": "internal_failure"}, status=http_client.BAD_REQUEST @@ -205,8 +205,8 @@ def test__token_endpoint_request_internal_failure_error(): _client._token_endpoint_request( request, "http://example.com", {"error": "internal_failure"} ) - # request should be called once and then with 3 retries - assert request.call_count == 4 + # request with 2 retries + assert request.call_count == 3 def test__token_endpoint_request_internal_failure_and_retry_failure_error(): @@ -625,6 +625,6 @@ def test__token_endpoint_request_no_throw_with_retry(can_retry): ) if can_retry: - assert mock_request.call_count == 4 + assert mock_request.call_count == 3 else: assert mock_request.call_count == 1 diff --git a/packages/google-auth/tests/test__exponential_backoff.py b/packages/google-auth/tests/test__exponential_backoff.py index 06a54527e6bb..95422502b0d6 100644 --- a/packages/google-auth/tests/test__exponential_backoff.py +++ b/packages/google-auth/tests/test__exponential_backoff.py @@ -13,8 +13,10 @@ # limitations under the License. import mock +import pytest # type: ignore from google.auth import _exponential_backoff +from google.auth import exceptions @mock.patch("time.sleep", return_value=None) @@ -24,18 +26,31 @@ def test_exponential_backoff(mock_time): iteration_count = 0 for attempt in eb: - backoff_interval = mock_time.call_args[0][0] - jitter = curr_wait * eb._randomization_factor - - assert (curr_wait - jitter) <= backoff_interval <= (curr_wait + jitter) - assert attempt == iteration_count + 1 - assert eb.backoff_count == iteration_count + 1 - assert eb._current_wait_in_seconds == eb._multiplier ** (iteration_count + 1) - - curr_wait = eb._current_wait_in_seconds + if attempt == 1: + assert mock_time.call_count == 0 + else: + backoff_interval = mock_time.call_args[0][0] + jitter = curr_wait * eb._randomization_factor + + assert (curr_wait - jitter) <= backoff_interval <= (curr_wait + jitter) + assert attempt == iteration_count + 1 + assert eb.backoff_count == iteration_count + 1 + assert eb._current_wait_in_seconds == eb._multiplier ** iteration_count + + curr_wait = eb._current_wait_in_seconds iteration_count += 1 assert eb.total_attempts == _exponential_backoff._DEFAULT_RETRY_TOTAL_ATTEMPTS assert eb.backoff_count == _exponential_backoff._DEFAULT_RETRY_TOTAL_ATTEMPTS assert iteration_count == _exponential_backoff._DEFAULT_RETRY_TOTAL_ATTEMPTS - assert mock_time.call_count == _exponential_backoff._DEFAULT_RETRY_TOTAL_ATTEMPTS + assert ( + mock_time.call_count == _exponential_backoff._DEFAULT_RETRY_TOTAL_ATTEMPTS - 1 + ) + + +def test_minimum_total_attempts(): + with pytest.raises(exceptions.InvalidValue): + _exponential_backoff.ExponentialBackoff(total_attempts=0) + with pytest.raises(exceptions.InvalidValue): + _exponential_backoff.ExponentialBackoff(total_attempts=-1) + _exponential_backoff.ExponentialBackoff(total_attempts=1) diff --git a/packages/google-auth/tests_async/oauth2/test__client_async.py b/packages/google-auth/tests_async/oauth2/test__client_async.py index add1b4e60bc7..7ffbc7ae136c 100644 --- a/packages/google-auth/tests_async/oauth2/test__client_async.py +++ b/packages/google-auth/tests_async/oauth2/test__client_async.py @@ -492,6 +492,6 @@ async def test__token_endpoint_request_no_throw_with_retry(can_retry): ) if can_retry: - assert mock_request.call_count == 4 + assert mock_request.call_count == 3 else: assert mock_request.call_count == 1 From 2677b117a07f4d92f220662d79a06fb4084ed2b0 Mon Sep 17 00:00:00 2001 From: Mohammad Varmazyar Date: Fri, 12 Jul 2024 23:08:00 +0200 Subject: [PATCH 848/966] fix(metadata): enhance retry logic for metadata server access in _metadata.py (#1545) * fix(metadata): Use exponential backoff retry logic for GCE metadata server access --- packages/google-auth/CONTRIBUTING.rst | 2 +- .../google/auth/compute_engine/_metadata.py | 21 ++++++++++--------- .../tests/compute_engine/test__metadata.py | 15 ++++++++----- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/packages/google-auth/CONTRIBUTING.rst b/packages/google-auth/CONTRIBUTING.rst index 34a91ec7addf..68d598c93f01 100644 --- a/packages/google-auth/CONTRIBUTING.rst +++ b/packages/google-auth/CONTRIBUTING.rst @@ -16,7 +16,7 @@ A few notes on making changes to ``google-auth-library-python``. - If you've added a new feature or modified an existing feature, be sure to add or update any applicable documentation in docstrings and in the documentation (in ``docs/``). You can re-generate the reference documentation - using ``nox -s docgen``. + using ``nox -s docs``. - The change must work fully on the following CPython versions: 3.7, 3.8, 3.9, 3.10, 3.11 and 3.12 across macOS, Linux, and Windows. diff --git a/packages/google-auth/google/auth/compute_engine/_metadata.py b/packages/google-auth/google/auth/compute_engine/_metadata.py index e597365851c9..69b7b5245893 100644 --- a/packages/google-auth/google/auth/compute_engine/_metadata.py +++ b/packages/google-auth/google/auth/compute_engine/_metadata.py @@ -28,11 +28,12 @@ from google.auth import environment_vars from google.auth import exceptions from google.auth import metrics +from google.auth._exponential_backoff import ExponentialBackoff _LOGGER = logging.getLogger(__name__) # Environment variable GCE_METADATA_HOST is originally named -# GCE_METADATA_ROOT. For compatiblity reasons, here it checks +# GCE_METADATA_ROOT. For compatibility reasons, here it checks # the new variable first; if not set, the system falls back # to the old variable. _GCE_METADATA_HOST = os.getenv(environment_vars.GCE_METADATA_HOST, None) @@ -119,11 +120,12 @@ def ping(request, timeout=_METADATA_DEFAULT_TIMEOUT, retry_count=3): # could lead to false negatives in the event that we are on GCE, but # the metadata resolution was particularly slow. The latter case is # "unlikely". - retries = 0 headers = _METADATA_HEADERS.copy() headers[metrics.API_CLIENT_HEADER] = metrics.mds_ping() - while retries < retry_count: + backoff = ExponentialBackoff(total_attempts=retry_count) + + for attempt in backoff: try: response = request( url=_METADATA_IP_ROOT, method="GET", headers=headers, timeout=timeout @@ -139,11 +141,10 @@ def ping(request, timeout=_METADATA_DEFAULT_TIMEOUT, retry_count=3): _LOGGER.warning( "Compute Engine Metadata server unavailable on " "attempt %s of %s. Reason: %s", - retries + 1, + attempt, retry_count, e, ) - retries += 1 return False @@ -179,7 +180,7 @@ def get( Returns: Union[Mapping, str]: If the metadata server returns JSON, a mapping of - the decoded JSON is return. Otherwise, the response content is + the decoded JSON is returned. Otherwise, the response content is returned as a string. Raises: @@ -198,8 +199,9 @@ def get( url = _helpers.update_query(base_url, query_params) - retries = 0 - while retries < retry_count: + backoff = ExponentialBackoff(total_attempts=retry_count) + + for attempt in backoff: try: response = request(url=url, method="GET", headers=headers_to_use) break @@ -208,11 +210,10 @@ def get( _LOGGER.warning( "Compute Engine Metadata server unavailable on " "attempt %s of %s. Reason: %s", - retries + 1, + attempt, retry_count, e, ) - retries += 1 else: raise exceptions.TransportError( "Failed to retrieve {} from the Google Compute Engine " diff --git a/packages/google-auth/tests/compute_engine/test__metadata.py b/packages/google-auth/tests/compute_engine/test__metadata.py index 60ae355ac764..391422b040ce 100644 --- a/packages/google-auth/tests/compute_engine/test__metadata.py +++ b/packages/google-auth/tests/compute_engine/test__metadata.py @@ -125,13 +125,15 @@ def test_ping_success_retry(mock_metrics_header_value): assert request.call_count == 2 -def test_ping_failure_bad_flavor(): +@mock.patch("time.sleep", return_value=None) +def test_ping_failure_bad_flavor(mock_sleep): request = make_request("", headers={_metadata._METADATA_FLAVOR_HEADER: "meep"}) assert not _metadata.ping(request) -def test_ping_failure_connection_failed(): +@mock.patch("time.sleep", return_value=None) +def test_ping_failure_connection_failed(mock_sleep): request = make_request("") request.side_effect = exceptions.TransportError() @@ -194,7 +196,8 @@ def test_get_success_json_content_type_charset(): assert result[key] == value -def test_get_success_retry(): +@mock.patch("time.sleep", return_value=None) +def test_get_success_retry(mock_sleep): key, value = "foo", "bar" data = json.dumps({key: value}) @@ -310,7 +313,8 @@ def test_get_success_custom_root_old_variable(): ) -def test_get_failure(): +@mock.patch("time.sleep", return_value=None) +def test_get_failure(mock_sleep): request = make_request("Metadata error", status=http_client.NOT_FOUND) with pytest.raises(exceptions.TransportError) as excinfo: @@ -337,7 +341,8 @@ def test_get_return_none_for_not_found_error(): ) -def test_get_failure_connection_failed(): +@mock.patch("time.sleep", return_value=None) +def test_get_failure_connection_failed(mock_sleep): request = make_request("") request.side_effect = exceptions.TransportError() From f11e136a07a4136b78ca1a091e27d9647c68c684 Mon Sep 17 00:00:00 2001 From: ohmayr Date: Tue, 16 Jul 2024 11:22:14 -0400 Subject: [PATCH 849/966] feat: implement base classes for credentials and request sessions (#1551) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * implement base classes for credentials and request sessions * remove public methods apply and before_request from base credentials * move MTLS logic back to requests * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * minor fix * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * address PR comments * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * address PR comments * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * chore: Refresh system test creds. * revert copyright date * fix import statements order --------- Co-authored-by: Owl Bot Co-authored-by: Carl Lundin <108372512+clundin25@users.noreply.github.com> Co-authored-by: Carl Lundin --- .../google/auth/_credentials_base.py | 73 ++++++++++++++++++ .../google-auth/google/auth/credentials.py | 12 ++- .../google/auth/transport/_requests_base.py | 52 +++++++++++++ .../google/auth/transport/requests.py | 5 +- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 5 files changed, 133 insertions(+), 9 deletions(-) create mode 100644 packages/google-auth/google/auth/_credentials_base.py create mode 100644 packages/google-auth/google/auth/transport/_requests_base.py diff --git a/packages/google-auth/google/auth/_credentials_base.py b/packages/google-auth/google/auth/_credentials_base.py new file mode 100644 index 000000000000..29462dc0ceb2 --- /dev/null +++ b/packages/google-auth/google/auth/_credentials_base.py @@ -0,0 +1,73 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +"""Interface for base credentials.""" + +import abc + +from google.auth import _helpers + + +class _BaseCredentials(metaclass=abc.ABCMeta): + """Base class for all credentials. + + All credentials have a :attr:`token` that is used for authentication and + may also optionally set an :attr:`expiry` to indicate when the token will + no longer be valid. + + Most credentials will be :attr:`invalid` until :meth:`refresh` is called. + Credentials can do this automatically before the first HTTP request in + :meth:`before_request`. + + Although the token and expiration will change as the credentials are + :meth:`refreshed ` and used, credentials should be considered + immutable. Various credentials will accept configuration such as private + keys, scopes, and other options. These options are not changeable after + construction. Some classes will provide mechanisms to copy the credentials + with modifications such as :meth:`ScopedCredentials.with_scopes`. + """ + + def __init__(self): + self.token = None + """str: The bearer token that can be used in HTTP headers to make + authenticated requests.""" + + @abc.abstractmethod + def refresh(self, request): + """Refreshes the access token. + + Args: + request (google.auth.transport.Request): The object used to make + HTTP requests. + + Raises: + google.auth.exceptions.RefreshError: If the credentials could + not be refreshed. + """ + # pylint: disable=missing-raises-doc + # (pylint doesn't recognize that this is abstract) + raise NotImplementedError("Refresh must be implemented") + + def _apply(self, headers, token=None): + """Apply the token to the authentication header. + + Args: + headers (Mapping): The HTTP request headers. + token (Optional[str]): If specified, overrides the current access + token. + """ + headers["authorization"] = "Bearer {}".format( + _helpers.from_bytes(token or self.token) + ) diff --git a/packages/google-auth/google/auth/credentials.py b/packages/google-auth/google/auth/credentials.py index 27abd443dc09..e31930311be4 100644 --- a/packages/google-auth/google/auth/credentials.py +++ b/packages/google-auth/google/auth/credentials.py @@ -22,12 +22,13 @@ from google.auth import _helpers, environment_vars from google.auth import exceptions from google.auth import metrics +from google.auth._credentials_base import _BaseCredentials from google.auth._refresh_worker import RefreshThreadManager DEFAULT_UNIVERSE_DOMAIN = "googleapis.com" -class Credentials(metaclass=abc.ABCMeta): +class Credentials(_BaseCredentials): """Base class for all credentials. All credentials have a :attr:`token` that is used for authentication and @@ -47,9 +48,8 @@ class Credentials(metaclass=abc.ABCMeta): """ def __init__(self): - self.token = None - """str: The bearer token that can be used in HTTP headers to make - authenticated requests.""" + super(Credentials, self).__init__() + self.expiry = None """Optional[datetime]: When the token expires and is no longer valid. If this is None, the token is assumed to never expire.""" @@ -167,9 +167,7 @@ def apply(self, headers, token=None): token (Optional[str]): If specified, overrides the current access token. """ - headers["authorization"] = "Bearer {}".format( - _helpers.from_bytes(token or self.token) - ) + self._apply(headers, token=token) """Trust boundary value will be a cached value from global lookup. The response of trust boundary will be a list of regions and a hex diff --git a/packages/google-auth/google/auth/transport/_requests_base.py b/packages/google-auth/google/auth/transport/_requests_base.py new file mode 100644 index 000000000000..ec718d909a36 --- /dev/null +++ b/packages/google-auth/google/auth/transport/_requests_base.py @@ -0,0 +1,52 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Transport adapter for Base Requests.""" + + +import abc + + +_DEFAULT_TIMEOUT = 120 # in second + + +class _BaseAuthorizedSession(metaclass=abc.ABCMeta): + """Base class for a Request Session with credentials. This class is intended to capture + the common logic between synchronous and asynchronous request sessions and is not intended to + be instantiated directly. + + Args: + credentials (google.auth._credentials_base.BaseCredentials): The credentials to + add to the request. + """ + + def __init__(self, credentials): + self.credentials = credentials + + @abc.abstractmethod + def request( + self, + method, + url, + data=None, + headers=None, + max_allowed_time=None, + timeout=_DEFAULT_TIMEOUT, + **kwargs + ): + raise NotImplementedError("Request must be implemented") + + @abc.abstractmethod + def close(self): + raise NotImplementedError("Close must be implemented") diff --git a/packages/google-auth/google/auth/transport/requests.py b/packages/google-auth/google/auth/transport/requests.py index 23a69783dc33..68f67c59bdfe 100644 --- a/packages/google-auth/google/auth/transport/requests.py +++ b/packages/google-auth/google/auth/transport/requests.py @@ -38,6 +38,7 @@ from google.auth import exceptions from google.auth import transport import google.auth.transport._mtls_helper +from google.auth.transport._requests_base import _BaseAuthorizedSession from google.oauth2 import service_account _LOGGER = logging.getLogger(__name__) @@ -292,7 +293,7 @@ def proxy_manager_for(self, *args, **kwargs): return super(_MutualTlsOffloadAdapter, self).proxy_manager_for(*args, **kwargs) -class AuthorizedSession(requests.Session): +class AuthorizedSession(requests.Session, _BaseAuthorizedSession): """A Requests Session class with credentials. This class is used to perform requests to API endpoints that require @@ -389,7 +390,7 @@ def __init__( default_host=None, ): super(AuthorizedSession, self).__init__() - self.credentials = credentials + _BaseAuthorizedSession.__init__(self, credentials) self._refresh_status_codes = refresh_status_codes self._max_refresh_attempts = max_refresh_attempts self._refresh_timeout = refresh_timeout diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 3d9bd3327546ea90e45e2a84311b752d1744c692..77ab87e0f1bf6d7d084d21c1c2fa8691e6c59709 100644 GIT binary patch literal 10324 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 literal 10324 zcmV-aD67{BB>?tKRTDvxhv+ac|FOqQ=Vu-py_^L#6k-ae0V0+lz#c%7=JC)8m>u?U*O*1pWWyLag9oF%bI-˓eC8e(xEgXU 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 From 27519d74d102c5f8e02d6d7d8ed35c4bf2bf9aa3 Mon Sep 17 00:00:00 2001 From: ohmayr Date: Wed, 17 Jul 2024 16:43:32 -0400 Subject: [PATCH 850/966] feat: implement async `StaticCredentials` using access tokens (#1559) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: implement async oauth2 credentials * minor cleanup * inherit base credentials class in async credentials * fix whitespace * implement builder class for oauth 2.0 credentials * feat: implement async static credentials * revert implementing oauth2 credentials * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * update values used in tests * update the exception raised for static refresh * add async anonymous credentials * update docstrings * address PR comments * chore: Refresh system test creds. * fix lint issues * add test coverage * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot Co-authored-by: Carl Lundin --- .../google/auth/_credentials_base.py | 6 +- .../google-auth/google/auth/aio/__init__.py | 25 +++ .../google/auth/aio/credentials.py | 143 ++++++++++++++++++ .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../tests/test_credentials_async.py | 136 +++++++++++++++++ 5 files changed, 308 insertions(+), 2 deletions(-) create mode 100644 packages/google-auth/google/auth/aio/__init__.py create mode 100644 packages/google-auth/google/auth/aio/credentials.py create mode 100644 packages/google-auth/tests/test_credentials_async.py diff --git a/packages/google-auth/google/auth/_credentials_base.py b/packages/google-auth/google/auth/_credentials_base.py index 29462dc0ceb2..64d5ce34b9a3 100644 --- a/packages/google-auth/google/auth/_credentials_base.py +++ b/packages/google-auth/google/auth/_credentials_base.py @@ -37,12 +37,14 @@ class _BaseCredentials(metaclass=abc.ABCMeta): keys, scopes, and other options. These options are not changeable after construction. Some classes will provide mechanisms to copy the credentials with modifications such as :meth:`ScopedCredentials.with_scopes`. + + Attributes: + token (Optional[str]): The bearer token that can be used in HTTP headers to make + authenticated requests. """ def __init__(self): self.token = None - """str: The bearer token that can be used in HTTP headers to make - authenticated requests.""" @abc.abstractmethod def refresh(self, request): diff --git a/packages/google-auth/google/auth/aio/__init__.py b/packages/google-auth/google/auth/aio/__init__.py new file mode 100644 index 000000000000..331708cba62c --- /dev/null +++ b/packages/google-auth/google/auth/aio/__init__.py @@ -0,0 +1,25 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google Auth AIO Library for Python.""" + +import logging + +from google.auth import version as google_auth_version + + +__version__ = google_auth_version.__version__ + +# Set default logging handler to avoid "No handler found" warnings. +logging.getLogger(__name__).addHandler(logging.NullHandler()) diff --git a/packages/google-auth/google/auth/aio/credentials.py b/packages/google-auth/google/auth/aio/credentials.py new file mode 100644 index 000000000000..3bc6a5a6762a --- /dev/null +++ b/packages/google-auth/google/auth/aio/credentials.py @@ -0,0 +1,143 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +"""Interfaces for asynchronous credentials.""" + + +from google.auth import _helpers +from google.auth import exceptions +from google.auth._credentials_base import _BaseCredentials + + +class Credentials(_BaseCredentials): + """Base class for all asynchronous credentials. + + All credentials have a :attr:`token` that is used for authentication and + may also optionally set an :attr:`expiry` to indicate when the token will + no longer be valid. + + Most credentials will be :attr:`invalid` until :meth:`refresh` is called. + Credentials can do this automatically before the first HTTP request in + :meth:`before_request`. + + Although the token and expiration will change as the credentials are + :meth:`refreshed ` and used, credentials should be considered + immutable. Various credentials will accept configuration such as private + keys, scopes, and other options. These options are not changeable after + construction. Some classes will provide mechanisms to copy the credentials + with modifications such as :meth:`ScopedCredentials.with_scopes`. + """ + + def __init__(self): + super(Credentials, self).__init__() + + async def apply(self, headers, token=None): + """Apply the token to the authentication header. + + Args: + headers (Mapping): The HTTP request headers. + token (Optional[str]): If specified, overrides the current access + token. + """ + self._apply(headers, token=token) + + async def refresh(self, request): + """Refreshes the access token. + + Args: + request (google.auth.aio.transport.Request): The object used to make + HTTP requests. + + Raises: + google.auth.exceptions.RefreshError: If the credentials could + not be refreshed. + """ + raise NotImplementedError("Refresh must be implemented") + + async def before_request(self, request, method, url, headers): + """Performs credential-specific before request logic. + + Refreshes the credentials if necessary, then calls :meth:`apply` to + apply the token to the authentication header. + + Args: + request (google.auth.aio.transport.Request): The object used to make + HTTP requests. + method (str): The request's HTTP method or the RPC method being + invoked. + url (str): The request's URI or the RPC service's URI. + headers (Mapping): The request's headers. + """ + await self.apply(headers) + + +class StaticCredentials(Credentials): + """Asynchronous Credentials representing an immutable access token. + + The credentials are considered immutable except the tokens which can be + configured in the constructor :: + + credentials = StaticCredentials(token="token123") + + StaticCredentials does not support :meth `refresh` and assumes that the configured + token is valid and not expired. StaticCredentials will never attempt to + refresh the token. + """ + + def __init__(self, token): + """ + Args: + token (str): The access token. + """ + super(StaticCredentials, self).__init__() + self.token = token + + @_helpers.copy_docstring(Credentials) + async def refresh(self, request): + raise exceptions.InvalidOperation("Static credentials cannot be refreshed.") + + # Note: before_request should never try to refresh access tokens. + # StaticCredentials intentionally does not support it. + @_helpers.copy_docstring(Credentials) + async def before_request(self, request, method, url, headers): + await self.apply(headers) + + +class AnonymousCredentials(Credentials): + """Asynchronous Credentials that do not provide any authentication information. + + These are useful in the case of services that support anonymous access or + local service emulators that do not use credentials. + """ + + async def refresh(self, request): + """Raises :class:``InvalidOperation``, anonymous credentials cannot be + refreshed.""" + raise exceptions.InvalidOperation("Anonymous credentials cannot be refreshed.") + + async def apply(self, headers, token=None): + """Anonymous credentials do nothing to the request. + + The optional ``token`` argument is not supported. + + Raises: + google.auth.exceptions.InvalidValue: If a token was specified. + """ + if token is not None: + raise exceptions.InvalidValue("Anonymous credentials don't support tokens.") + + async def before_request(self, request, method, url, headers): + """Anonymous credentials do nothing to the request.""" + pass diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 77ab87e0f1bf6d7d084d21c1c2fa8691e6c59709..ccb040256c0cf0e7e68c58b58536bc49bf81293d 100644 GIT binary patch literal 10324 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 literal 10324 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 diff --git a/packages/google-auth/tests/test_credentials_async.py b/packages/google-auth/tests/test_credentials_async.py new file mode 100644 index 000000000000..51e4f0611c88 --- /dev/null +++ b/packages/google-auth/tests/test_credentials_async.py @@ -0,0 +1,136 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest # type: ignore + +from google.auth import exceptions +from google.auth.aio import credentials + + +class CredentialsImpl(credentials.Credentials): + pass + + +def test_credentials_constructor(): + credentials = CredentialsImpl() + assert not credentials.token + + +@pytest.mark.asyncio +async def test_before_request(): + credentials = CredentialsImpl() + request = "water" + headers = {} + credentials.token = "orchid" + + # before_request should not affect the value of the token. + await credentials.before_request(request, "http://example.com", "GET", headers) + assert credentials.token == "orchid" + assert headers["authorization"] == "Bearer orchid" + assert "x-allowed-locations" not in headers + + request = "earth" + headers = {} + + # Second call shouldn't affect token or headers. + await credentials.before_request(request, "http://example.com", "GET", headers) + assert credentials.token == "orchid" + assert headers["authorization"] == "Bearer orchid" + assert "x-allowed-locations" not in headers + + +@pytest.mark.asyncio +async def test_static_credentials_ctor(): + static_creds = credentials.StaticCredentials(token="orchid") + assert static_creds.token == "orchid" + + +@pytest.mark.asyncio +async def test_static_credentials_apply_default(): + static_creds = credentials.StaticCredentials(token="earth") + headers = {} + + await static_creds.apply(headers) + assert headers["authorization"] == "Bearer earth" + + await static_creds.apply(headers, token="orchid") + assert headers["authorization"] == "Bearer orchid" + + +@pytest.mark.asyncio +async def test_static_credentials_before_request(): + static_creds = credentials.StaticCredentials(token="orchid") + request = "water" + headers = {} + + # before_request should not affect the value of the token. + await static_creds.before_request(request, "http://example.com", "GET", headers) + assert static_creds.token == "orchid" + assert headers["authorization"] == "Bearer orchid" + assert "x-allowed-locations" not in headers + + request = "earth" + headers = {} + + # Second call shouldn't affect token or headers. + await static_creds.before_request(request, "http://example.com", "GET", headers) + assert static_creds.token == "orchid" + assert headers["authorization"] == "Bearer orchid" + assert "x-allowed-locations" not in headers + + +@pytest.mark.asyncio +async def test_static_credentials_refresh(): + static_creds = credentials.StaticCredentials(token="orchid") + request = "earth" + + with pytest.raises(exceptions.InvalidOperation) as exc: + await static_creds.refresh(request) + assert exc.match("Static credentials cannot be refreshed.") + + +@pytest.mark.asyncio +async def test_anonymous_credentials_ctor(): + anon = credentials.AnonymousCredentials() + assert anon.token is None + + +@pytest.mark.asyncio +async def test_anonymous_credentials_refresh(): + anon = credentials.AnonymousCredentials() + request = object() + with pytest.raises(exceptions.InvalidOperation) as exc: + await anon.refresh(request) + assert exc.match("Anonymous credentials cannot be refreshed.") + + +@pytest.mark.asyncio +async def test_anonymous_credentials_apply_default(): + anon = credentials.AnonymousCredentials() + headers = {} + await anon.apply(headers) + assert headers == {} + with pytest.raises(ValueError): + await anon.apply(headers, token="orchid") + + +@pytest.mark.asyncio +async def test_anonymous_credentials_before_request(): + anon = credentials.AnonymousCredentials() + request = object() + method = "GET" + url = "https://example.com/api/endpoint" + headers = {} + await anon.before_request(request, method, url, headers) + assert headers == {} From 2b663dd1553a9cacf08ff941609e4e240f3855d9 Mon Sep 17 00:00:00 2001 From: Ulises Reyes Date: Thu, 18 Jul 2024 00:30:18 +0200 Subject: [PATCH 851/966] docs: Update argument for Credentials initialization (#1557) Credentials expects `aws_security_credentials_supplier`, not `aws_security_token_supplier` --- packages/google-auth/docs/user-guide.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/docs/user-guide.rst b/packages/google-auth/docs/user-guide.rst index 05516393eb04..e9ad000e5793 100644 --- a/packages/google-auth/docs/user-guide.rst +++ b/packages/google-auth/docs/user-guide.rst @@ -578,7 +578,7 @@ can be used to make HTTP requests.:: credentials = aws.Credentials( AUDIENCE, # Set GCP Audience. "urn:ietf:params:aws:token-type:aws4_request", # Set AWS subject token type. - aws_security_token_supplier=supplier, # Set supplier. + aws_security_credentials_supplier=supplier, # Set supplier. scopes=SCOPES # Set desired scopes. ) From fd35fd5988f1530388e70fcc7043b3f5b879fc20 Mon Sep 17 00:00:00 2001 From: Carl Lundin <108372512+clundin25@users.noreply.github.com> Date: Tue, 6 Aug 2024 09:38:21 -0700 Subject: [PATCH 852/966] chore: Add aiohttp requirements test constraint. (#1566) See https://github.com/googleapis/google-auth-library-python/issues/1565 for more information. --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes packages/google-auth/testing/requirements.txt | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index ccb040256c0cf0e7e68c58b58536bc49bf81293d..ba7444e1df8e8d3fc1f51912ad4ef49d4ec5b07d 100644 GIT binary patch literal 10324 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 literal 10324 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 diff --git a/packages/google-auth/testing/requirements.txt b/packages/google-auth/testing/requirements.txt index 27a0b3cb798b..b1a29542f457 100644 --- a/packages/google-auth/testing/requirements.txt +++ b/packages/google-auth/testing/requirements.txt @@ -17,4 +17,4 @@ grpcio pytest-asyncio; python_version > '3.0' aioresponses; python_version > '3.0' asynctest; python_version > '3.0' -aiohttp; python_version > '3.0' +aiohttp < 3.10.0; python_version > '3.0' From e81d97c7550430e84eeecde34a386a1728ae991d Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 6 Aug 2024 11:29:36 -0700 Subject: [PATCH 853/966] chore(main): release 2.33.0 (#1560) * chore(main): release 2.33.0 --- packages/google-auth/CHANGELOG.md | 18 ++++++++++++++++++ packages/google-auth/google/auth/version.py | 2 +- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index e6bbd7781fd6..c6978ef00e37 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,24 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.33.0](https://github.com/googleapis/google-auth-library-python/compare/v2.32.0...v2.33.0) (2024-08-06) + + +### Features + +* Implement async `StaticCredentials` using access tokens ([#1559](https://github.com/googleapis/google-auth-library-python/issues/1559)) ([dc17dfc](https://github.com/googleapis/google-auth-library-python/commit/dc17dfc3fb65c87f2912300f0d11f79781240e78)) +* Implement base classes for credentials and request sessions ([#1551](https://github.com/googleapis/google-auth-library-python/issues/1551)) ([036dac4](https://github.com/googleapis/google-auth-library-python/commit/036dac43018b8cc26b5608e1bb21d6e3ee62a282)) + + +### Bug Fixes + +* **metadata:** Enhance retry logic for metadata server access in _metadata.py ([#1545](https://github.com/googleapis/google-auth-library-python/issues/1545)) ([61c2432](https://github.com/googleapis/google-auth-library-python/commit/61c24321e52f6e017eecee211e11260d621c909b)) + + +### Documentation + +* Update argument for Credentials initialization ([#1557](https://github.com/googleapis/google-auth-library-python/issues/1557)) ([40b9ed9](https://github.com/googleapis/google-auth-library-python/commit/40b9ed91a6b01948561cfc71edaaabdd7f362f17)) + ## [2.32.0](https://github.com/googleapis/google-auth-library-python/compare/v2.31.0...v2.32.0) (2024-07-08) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 51f7f62acd7b..c41f8776589f 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.32.0" +__version__ = "2.33.0" diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index ba7444e1df8e8d3fc1f51912ad4ef49d4ec5b07d..aab772bbb8d79f8762222990bf9fc488027e473e 100644 GIT binary patch literal 10324 zcmV-aD67{BB>?tKRTG?HW>*5_7uVq!%qZY4&_9dx6(~fQq=A?fQexTwbK?@KPylv+ ziVm%O?&5eud^MoM_BQOuS(S#HAL0ruV`T+OFTmSQcIe6XCL8K%WtI-zm|Sv`-IVab zH88~c$dDgs^Jj3;pqpbh{3K;44h-EY+@Wb{Cn?N0s#e^iM~@vH?}I%N*mLpi_QPu% z*(We1F^nuIP|MF^;282?LZmHc>UH>d|J;mhVV|fg9lByen>^En8Q8&c&0PScy+w`W zd+}=l@MMHye2-n5E*fb%V#jX(?Z#47K@6hBSwws=(b#mB5>wXq!xC5NZ;`qcr=oEc zgNq$II7XTq)E#>tBViAp$bgJ0`l@ds*m(kH74$GbHqahdki?fUNb@jQDsT9$K0R5) zTp}nM#QOWiNYilKd&z9W^1U$w#)ze*&k_j<-_-^}KG*Htqogl#$BGr=0drW?d$zP} zlNoxt&3fk~PcZ;NZfuSfY1cBa4pYjmV+``8(NINNN?1r60m|^%|BFC1YtaIfPFNDi z3-JAS`Yf0l4Q^l%z)hZTsL&HCIc}^c^iY7MHjjDmg6bD$A3XTCf`4UGG+(#_Ja=!* zXmTcS#^yLYoysSy99M{Ao7De-tOEQK^>Kx_rneMZ3e1$7&W-T1#9ewbZ9#-n2L=)GxdTtVUKk#a6SX zzKoKgw@9_Py2f|ybQ~&CQ6MXc6nE@79}`R=A^woos7f(5=M~&HeVu|tK=Ts6!#z@L zpnb-7@MJ}y6akAns9DP*v$QOO%GbG7vFUgP2bP~r?A&5b<6qSL& z*#%xnzQ19o!rod&5FqW~cf6=ZqNA3+ov@Akk&7uQ;}1_DM=OsBp^!B}ss{2yQnt4f zSB~6{{^#Z4T(v4d0^FXek+LDaV`CD1CT#-6$!J)ee}K!DoamI^Y`TgD&}*JzAh?@> zVE~MpJG$5e7N$29$fcxmN5b@(@0dC0p85oZT8WatiC+FBD$*<7LV|OlhSc|9=6J;~SxYgs z#Vi;ZGV1qS46VpFI9Vzf+Oc7$OTp8$^%g}<=ulc6rFCl|e$y8p4Zd| z9eFFrC>Rr z;_`+Ym1Hagwrp&XU@yC^x*LkNaEx$BEZV$F!6Avv`wd&e>CQ8Sq0WMw*sn?YUpz4X zs0reyjF0sbOhodetu}>OR$!S-jFs~=&sF!uQ6Qx)#ST3%wtPi%B{{c`bYcS+H!PFC zkPp=O|Ez9qwUkM~@N}r^R%xOW>uN3dj2BpIB0;*0bhvy!aCxxSr|EU~+pNw9N}7z` za8~U|DSjJIGj4IFx)xo%+q$PDiim*OY`*FO6G&pzfiac*U@pp@*06I+O8 z^pTs#oY#4z7arh0w}CV8d>xOa-*cO>h9#J(wfc5Jms^|b+*v)(h%V3iYZYyy0&-Bq zo?k?3cH8+b13B>1q0v>KLGwZp1!DPvxLujynK?~n?pI<7QEK%<<^$GXYKkMj;6Cb} ze>mUK;^XT3-n^hIF^1d71x4KTasvPfdM+tGU2q+TBgY=mtI*;?EuA)takp)44ldj)s1SGjSTtLUJp0coVxGP7i^j1GU<{^hJm zD6M*hk7OIjftSZYc|2H#WgMnfaf2z_TaFqg&5^$=X)LSP2(Wm3-izfkhi*_pIQ|xb zH%wulMDtkNa^a-FeXX!cI-9Hh18S{1YEORsl!x4pVa?9GA8^8yXB9{Z* ze~=%%uoP6giB@e-IlY{}+=9E{l}GZU5Al_TJJDsh{AD?s~nWX&*6}SG%>LZ6gT1KLJ7w_40G3KO9q?a zb>g-ymiluQUji6rF=gk$IWuyT0xz1xoSLTdN@)tkT<7XCf~O8Kt9Qt5PaDz#Q|EI(Y;+?Xq~_!7?h0qe;GyA%TUf~Gg!2Bt3PRu zDUJt#|4h*X8{C!6wjr#4#xKKhoON!o808kr zTwbcjIHM)C*z_p@%`qi-!ia%zZB|vnelQ7JkQuJ9jfm;f;ag))zt=S?3qcNjpkN7E zjcYeEw#?w}(Qws)xN$|EzF~CG%-x`VmC6GRQRrR>lTDrT?lRqjEL4I$k{T;sQO3;3Ie7(4=k}7!T zZnJLn=^LAoptAO^eO^Wa z=@|Vcu_L28(Us{qQ854%?}<)YDZHXhr9~q@^zeq+&#rxQp{SOmk2x-I)_E0mc;v)u;M+F2YDl{R+ zFl@Ar(n09>6XYDWbJw+a7p~HZvWAZn3a3;|NPc60iyqTW9^C>p{{7)VzU!khds-l# z0&s8*s>0P%eJ3~TGB`9Lt00=adKngY1(3c7k-hbJrUZ|i!-o2Ij}##?ePGVQc7 z$${S8u-1h);8(>Zp}VH7)U94=MZ~++=$2%zO&&^cFc(LIOCHAd+<(J!DjE6n-FfNu z>VjivC2z+qUwecN;y|GgEw|#J9o;xihHM3UH&HIR-s8NrEOp*t^*C0BMpD$}b%iC|| znQ)zN9Lilyb!SuBGYgx&CS17kl_kiv&L@|XpX8q0P4jyJ=LN2JO~{wHrEf_~RK?wQ zi>htt>|I+71gyb?-F1L5jQ*I9`+(rVX^Adq9uOWV#x)#jimeubK^F74(CRizSKV5- z@ql&Q!lHSHZCGYDp;n%}gPU?<2oRlo>>3efwr^dAhQ(K)`JClRAGWi7*G@RzwUto5 zI&#z#Dfg)Bu=>M{qH?Qhk8q(w4+Uz7Nwi>4cE~Z zR< z3J3UJYD%6jDpJj1_eh;dG)&_Cwte1o2@Z%%K|x+OqN%sQv(B9qlB2fgA@^TBP$Y7) zM`i5y&Y9Ju96Og6`&Wu+_-Q@goxLX_1#}roNuX{`DkT_G5gh7M9WfN2DKZvu%3C}u zrMi-}SLUzJ(L$aQ1|tdM{s*xjo>DUT!+gyq38(S4 zb%VLT$FXe3{@6LU!U=n)Jo7pX3m|E2;?u_jJv?$24x~P4j8S+GCP6qw3X`y)<NCp@aexxMR^R6y z#rZ+aL^VPc=h`>8VDUXVk9@V3@UQ1u_g}#WYt&jhBcMGHuw`wX5Ip2yyloEaWDvCv zvc{+hAvK$knd!=x3s-Xnx|n-GZ{?f~`ng`do~Z*%F_RaX$kH^!U~B`hUr{6e63W&^ zlo~rKNt)zTo37=ZK98n}I-iB-@lSV_z$f;uhTc)v!L#lpm}wB)}Ge(hm<`>U?oR@$boK zIv!&q9p5TY7O|PWX1#6NZ4UmJkLpzTZ1kK8KJ3+o-S~uLPITr+s`5wA!CvM0xGRdb z7Xocprt{|H{RCS!GVprms(4IL%5{o<9Jf+FuTlsII#Rd1=nLyDF*_Hg^xRdr@o}BY zr7JsT7zDk@S#D5XFLj5TN0^4{=}b+Ub|=jiJ<3)OSp3su`MiBOYE0*VG)1D+KUDch zGui{nNKFyARsIOuN+0p-w4@`Zi~C4B8@i3w73{)m8t(^q=Oq!Y8ugmeUZ*$?D66i0 z>IA8_-f4&7=>$OxaWM`*(A<>S%Wjb|O|yLf)k8Hu(WE+x)kLFE8oLh&`(HKDwb{V{ z*Up*fd8_q@l`~sYur|tTcx(Ol%hIwInn{qqxcgDqRXZAwc$vdIhHs||b1l^3|F=Gf z3B2T$dZCaDkcTH*D+ub}QJq&2s^~~}8apY%-{D(l(Ul4k$S@*%?|(KsrFGYhWUc0p z#$3dg3X~WxLPM9dvckxlCLDuwT2Xr-m{e2TVV?Nq4tso`5gRW?BrS`CXJQ2-jis=@ zXI5+aKew0lZ>;*vutkWM1GzFBH{7%-6?^wU^PLgRF%v!*&4+Q?*wu=EnTgZDSgA{~ z?kzm#XiCc#8b|O`qN)&xY_dm+%RBOgJR(+0-o^$J=)8_`RA3bO??@jr>$o@cP?unQ zFWTGLiD?Q6VS``EW6T9on%}CHrW7gA=4sMvW$K zVT(_;_4^BGRGH{~Fy zvUHZoa+zmXT{13K6;*#pl2me7V6_S57Ugz%g1m^;eFeXC#B;q%a^j%v*`En5IAkC9 zYmoy$7ZS|TT3wcaRswSo0o0>AMDu$P*x0?MP$xw1Yy$gE;%=m|LGZ!$<%+K4zYs8u z-8@>6o>cR>C0D&FDCjE1OOgLNw?rPv?k-JByVPNeb?VopVfX5~|H3AuaW|dfxSYB*~Cw1Ro?>N)~R?R<0OqDd41Urw(?K z;2;wTls2zdh)CrxkEREE>3r1Xvpf=4lMejvN71J=2aYYxA3OloVW&^^jyuM2Y@yyV zuzAi;{V=^`97at7k4kJ7{~`3kJ2R6Nng`1xAQ8R(`L{daZ4bCCZbVB3iGSRcl)$WI zK+<9~uUvlip1NRiz=6;pKg=|TPZPW%!Dm_cGwq+db0KN1-=W%8YWBNyq6j3(7BXrX zKpkbb@Kwjc)pk5FvrE?ErGTQ?j0kSkTu*CsKYf2H1|Let&C>fKfQ?no z+j(jC{QEgF5>t39XQ<+8L|2$ZdOHa?SnkyH(#0b@o2Rg2^Zq!48DAgl7(odv$s@gc zes(q?ofE+L&eL40Qo@CQ$`-|88bq17Z*A0==y2t7jnTB$K4vq6M@wI~tAwM!l)1oCeBvAqY(65c!{kl6Ia zW0iP3MN)-jh?dt@NzTPdx8|iTuCl*)i&{y!u-$*f)DA5(R!%}E^&7&473`B`%826& ziirW-mf(MKFl+cE1PTi_rHTQ&Yj>9yN=Lz1K{X;!5UaF^l$u>at;J#ZEGfx#iX*iT zH6z`enU53Z|3#8WWX|Rc&h(M4rUX28rv|c_B~AIfL#xVHyX(I#5QogA8olVh$~Ah{A+?Wo{JYt_+zCyRVQ58SdkwW^NdMi0z)` zRLcOH$uQ|N*HeS!s04C+y?y%K)`}|W5B?gvf1#wi_M6R}(OrJZbmIi$IZyN?KVL9f z?mGG;!utsurguLFtBBsQYpv#8+zn@{LTvq<~f6rp~opw zN{B6LnBn`bTO7GKd1sPE?Jvg@B{gglG9qqP5l&N&ez;5kJe4Vh3^tZIj8v>j1+l&w zwKdWwVX#OPzDW8i&Xg~fL5O65YZrTZM5Ak4wYYchw_VgT@z~5~nbnnU!1}c;+ev+E z2un0){EqNl$6pEHKD`|=5{Rk;WNhIkF$$gwemQIXnxIx{LKBJzTV-#E54~4Xu%shi zfUG4EO!VIy10SC(e8&Jp&|1}NfWu_zkv|}&%Hf1xUyZNiK41gSPo$1Qj z#a~v^Pi{JO>hCQu1}EybUjHI)FX@1_k(KFof)~PxkZP{nn5mgwZLnr{1@^f`qXdN| z;J7RxN|KFeY-DR`4=>U~CpdoY0@)IRH`wwzr*=xXxPDxKBpGz)O1Jm7DKFZAJ#J#? zlKyp0z$XO2!Ou%3kC!zB2-4Au9Ggu#)qFaMi2*7S_2YCqPv6K9o-RN4xzT;v4xFe;RY0*CPiyBmFu}mYOb1gx^6DccR>M2ER%80(NHDx zGx+q)MQE1zHahj3?b@#qwC(7pnoc=mvLtqem+dYag*L6D>Y=f$inf|*+*IxnNet31 z*9o7zV~thS&*vUEb-sj9c|)ohtM>EUQC?Ha^q6j6Y9LJ-;KV^+(P$#dXB>Xo{yTUw zP^xv`r;G49D7tQ44>2!jq{5(|eeT+Ky*k;q`U@EVHQ=dV-LA%sahA+Pg;v zSWAT7mL`^pssRB_CTPx=M3U>Rl`H>|XSW#TJbAQRU!rFPOIM`@=+0W=`KS(qLIc8k zSz(%lN(hfa$IT{NOYD>NwIIg6^#IH~Bi%*0t zzY&v7W8Z1k6Rj@gQ@d{($um(C7)k|atH&I4q-EYt{uJ^haJeA_I6qh(S3*%%nk=Bg zE~*a%|0@Z}z#|FZUWmLzD*F z3!01sK2#U|zDH-TPHu>P%Qz^O2N(#4bLD^`1!U&}sri*f49Gpek zEHj9B{JWt-J2Q5Qm-n_Oe!1sCq~9U97W|dRv`S=VLb8#0{)erx+jSfHNEASz9s|QN zUt|89N5xLVQs^@zGZ`qOflG)89%PVhZuD(a0Ads3-u^&wQ>gO*&skNRZ}0C)Y%xP$SOpF2Z!*SkUw^R0SExpqOpTKEXlSKG+h$IR zOG7sbt1$Go%Ex3z!W|8UDgEwRGlxE(W{L>m>k8-v!`v=`ZG_X*=y)S2o$UVK-BUG1 z&@WgjM?ZV~g^_0X#B{R@8cJL|K^uuM4o52*HkRTtbwaba9u%g$JniHEme;BR)=}W! z6I(VhjYskwNLz3R%Qu+*DLa>~rd_O0ON!va;%17?u9^9iNlp|`hBk zRsKG%AsvkT8bDQ6*%FVO@}fCW#JM9c_uq>JM^qkfZ5>xkzE~l@-jp4x=#e&JN%yX6 ziCu!>^quei8zl{M!cV_ByVSgq;MUq|iv4v2xJ~El5QkMbr%1!)#_gTB9yM zM(s}EiJ(2WbZ1w>wJ=>PBL1%~`=MOg+!RiMbUm=sjdQ%9V^0#@GIUBxXS)0{RFuj! zq-OtYYJNMz7frG!>Fm^SYzboRbrK3FU^)QH+=p@zInm0$KA zS3TKQ=NWtx*e6N#xE{-oYb(xI|e;oOAmnZZe1mmE}4zBFULTB?aQitPz_C=MplEB7ofNq#TH-` zDnKfn{hw_*wSkk_L_Tp!Sf1MpFs#Y`Ho_DbE+*=?W$iMwl@QH$@YYoxYcZu+Rlnur zrAhcvpppj;@?)2@avwXHCzMTAYe}n^MDT6<$Syv%+g@zQ66k7tG~K zs_M!CKAcTiY7e1gG-3IrgnyLjv+)8f_7Le$8@2HK5INzr3qQ$Hn-t1zj{JyTVVmxPGv}zDa z9@G%=@Q!Kl$vdtze|EB@&+O+SIeEgFLGod z>P}E@Q|DQNRO9tfy&h=dY7WBDUDYKH9y<(kP=a8rA?^By)oKcqT^3NEAKW>13^Y)Q*CT$t7bU!ar{A z?$f}+dj3+J_2<~=g=KAgT#`%r+XMgLXDW{b=70z{K#9MXZ2Jzr zPeGG2LH@X_2nL&yqo@>5xFkDbg&MaNQwmXey5Es!ELytCI;lwdt7#Y^K8!~@LR(lg z>+jc|7tT+`mEv#iZ-|~!s;fsRPHIYINSle-ied5%ye*~es9t*6mDQ?*JV1Y^ zJsCJw8sl*B(qwbZ$=327EQnvB$=}5>p;|SU#HUcercLvw=jbI*fSTf{lqzID584|I zk4M5ITFz+EQUIMoV&+Bdb~5bDf7TC!r&q4VI-GvZ#bBlWWzb?azvz?IhPo1WMHO!I zsV@htb|>$F3}S^0XL%ZaUa?)tZ|{zpG;+)$H#TTb)58;xr{N?!+?kjQC>|@e9S?FI(B*7IUS^>NyqL%umMu_f#qbT zo)B9{Z+XQgeZ5uRj0Du}%+4&`#kBr6isM(vxa>?6S~SH33hprtj}j$eW;C%{xdqcCB!ec4nE>MUp*M z3tPlw$2JaZWEG1z*bRCvU^f1)7-aFNZm@b*-$Cta+V7p%XXJf%3V;{;XyUyebls=U zvk)kzjLEGWgy*KkppX<6LbR*d)e7uFILGK=GXEqT zyIg8Y6>e7gu|N{}joHQZuo4|j4iMY#hJoc<1WaQSt0O;-xoZ6e#hC|f9Bsw-C(So5 zVH*TyaY)p7%5n+nB#bFL(KVvZ)G1JWg4GFDW)z~+LsWd_S4rC6i%zCn8?~+;uOkDk zV@69h#PN(>MeX<$w%)7fH~jr2+El0Bo!L(zmI?dEFa{~@8qOk~6(!YFSmmZGXpW1A zij)}>{B_P42s_>6JhMuE{wv?}b`}0RWH-9!Aj(RTyNz`AZ5;32Fh?VsNG_sxrfi0u z=A$7guwl)a5mO5;ZDC^U4j2&p1(gTe@W|C$(JZ2p5Jch}H16mfs8eFH<^^gG-NSAz zBGZ;5bM;Ifq;MPW9Vr|F1RQDVi%U%gh-x21L_q(qb z6eErm7t!?RGI5r(yxJdn=-}&q%PDHtN?hkVIgqjyHe~pa_l=JglJvL)Mno4C$*J$z`Ajw%ygwaDZ@A z?a~aFGTq3slt4PS+k1Us{H+PYDe_cZVu!Jb&eSa~XX_8ytYGv>OuV(@@GFlem;^JE zB4x`Nx&~fq!A{!Xm}<9oq>kS6dfl&4rS;$41inSaoYW=w55;xCJF1-+SOi)E)@N#% z%Cq;dN)zlm?uT-$1%DF3d)1af0<7P+v>yRYtaj~~fD}%N2m zU=(Z}UBU@ZUlKVHgGL(kSNYxS^ed%m9(L!A-;*lzCu7|rw21G}Xb_{mKxc4e*-E_` zXucQ&?^tD45bs71cdxI`Ilz;dfP6C1=ws!x@j6oX4dfyYzYfHLb(LpU>F))BCa=*sxo zVXuNXOdjdJ-_i3dtuLr+4yRo%!v2I7Uapyi5ORm%gmG0D{>Iprr1Fg_M>|?}teY6v z6Vc))B!YkgMHBh%%awk!3}Uu|?&sBnLS{UPHPwZZ_42;#8(+Ag{uhE83~x+8xg< zGW+V8)`}#mzL&MFv!^o~>PKQS_?5*}(Bg}(;3e{O(_BF&k78%gR&MXy_pLj!hWf=_ zb&9h(h#mv#-}ZLBW|xo{a4y=*ln*!TZe?`#eQMP2zx3}CO@bB mz^tIib~$|7Y9b3^8G@PJ4RpK_yGam;2BG5kQ>balOX+R)?B$OD literal 10324 zcmV-aD67{BB>?tKRTEdEYGJTZ<>FTl(mkbszCMI9ylE@VNasIML}e%qA3zeSPylv+ ziVkJc%0}s*Ebc^*w;Ob}Qogu}1fYImvwUW1HY0ssfFm1(eWNvy?;s56pS2Q?o-*!T z8-soi@Q|V8D!Ub@sAw0}Wa1IB3Vhm0Z4X6CUp1G>RRH*IB!I$UWq{%^8C$3930c>0qK*EwF26j-0)9!ux9F`kH zoTJ3Gd)uo*&m)fF8{t{yF;2Vn!`H}((lp(Lbj^et-$13#gn8jpLZpPcHNwj)q=7Sw zG9OVWv~Q?i9VD|m$-D9qwyi5#U0n-e7@!B?8|#ZYrtyA!@RjQ&@N5Pi0aVKLa$QgX zkV`3zN(w-7!(fcdZQzjOdcqK{>f#m~!iS$xs63d4{{%QStO&re6yISbWm#6z-CJsw zbRw~geAoZFga^}oXX%;PD_3}jXmeXNcwhDY606Zr6DU}1n)7`)tH=qd_3!W-oRS7RAU8?r~)5Ek1N$@4wt-ha8#jnuLAx6 zSes9yuY@$Hn4fBBX8bQg>94LsPqoQMj+jjfud@(?aBhQALDHKpOqgd5HMP5)k+Kr>e| z&3izPYpXp9OkkNKliNbR+~tiLg+_@_l$1pjA9DwZIc>2UjiDW~JWtso{0;3gPE7l>KQH(sGi7tOBTxFw02SEuZ`D$l^_WaKU@3Wo&Sr1 zry_~;b)ZGgI`>)6l^-+|s<=&e2?ek%wD2i77i~_4)Mub;j`~OaW%XUan%nDLe)Owd zz{6wkhol-wI1e1r#s0EPyoupep;hv-{@!3++fBq6W+=T@+q!M+>T3zGgb| z{_z_L&QF3mVpcO2Gmi`UqnzZ3pp_>c#hzrw)fBJ>#$yQ)sX~EHvHjf)*k%sd5wEG* z=6~WLEInrDXLhJNa0*)Rhw`+6I6h~?qYtw2F7Z2j&{osBvImS*EM^yzb#^|mZ=!C# zc>E)8dx$~D7BkZGNy|QCzW+T6zOZJb&W~{bHalmG6d_D{O-_*t4%Q~+n)9mmt=ehH z(|Z~3WS7i4A>xL~`j*toX4cEtD*q|!i0-g#334X89qO?Ij^4^Z0W^1#i}Yb%dEB40 zg5+Gj$FFloxIiMgFC>q}#=~qmE%G7s2;DnWo;cH7?m|0P;mWHh$Ks_+w-Gu<6gDK# z-Why8XJ{50Gxl|HA`Wk}`jolI*DRXH-NSnTX4l^ikO&_Ob=}i3;mY9V#JlV+`E0p| zcf<*lgw)Xh9s)!FYH8JrC5^iyl8S2^JMmA~s+7P!(n5qgJ(sSPwT){@2SMK=&Ff;7 zGKH!8$hOS7&cSU!CWNpP#cbn=klN24%+X@H4vT^Pf$1i36gE3AJYrMG!}GmLe^6bu zm-;mLu+CWl!0Ci{p_9E8(Gb(2tR){@M0tq<7WIq`+NjFN(7+>ARLQu~B{!iq3 zzqQ)d&@~2HpY2ek_^`Xu*wUmuwz#)XXGbY_Zb6Q)Jj{fGm*;0pz}5lXT8g)7b}utW zE~tC4auT~I6l}JLHX7^$Cjh>dS~ypsdQ!Ouw@5yN-H5mw&@HWxpV6R_?u#o#!&E|s zK+QA<{I+A?b(yM7Rurox)FDNx?r|C2%B&VRr2L;OHy=qe{qF(yOHLlq$x+Dg_1-Ee;LYol&sg0H`oASh(S;~+e6pOJr?_CpU4hj+9RA!J z8^iabuR#xkJ=sMRcuxqvU>yK%qNtjV>zH~*3x34fQZB|Gko=%sMmAC91OrakMb@OE zL5#q9pZ4l$bBbUC^>=$^wM0oEbBBJD1G|c;=K1{>phVa#C6A<7wt1#xS{DO}plWDM ze>0OI&%dafi>AhsDGT7=t{!|j_MvU2{HJrGfom!B10c@Jw|;-U!+qP{CGduL#9u(L zHUcE5Ivatjk(4p%T)z(H2?-9veZ0!89Kvm;J%V$L86UP?2SOc-0Ke-yJwH+(_ z4@M{^%xrM^HW0j{+sCMko}1nHbK_D6FY^`5-P1#u!c?~a$f#<8G=Lb6eK~rCB3WGWml0L#U<;*(Yx!4* ztS*FdgWH4toXvt@W|%UM;Pasvz7Q~of|=Bfl|{DvOc;Dn+XPkC*EBqle$haouU5MVN2;nlw~ z+ui=K*(9>bFlKDcFAk?3eyWe$al?{RnO~lHL|7mV<1RnFG`}$iVY0J z`XqlJp20h&Y;ZyOlHCzQtGPbsS+Ozw^8a*H<4f^h+ZX+-;fI9|Z+T;eOI_zO{Y&sB zrYzFARgvg=2`oU*e=s$g!CVb-xwqJ!+5KLy+42S>@zC^lO6?@E5AIB{LmHp#C5gVn zH0!JMq6)PvAR<^7PbvZ6Ryyg0%4TE*=}M_nl@-%LNW;uhvc4NTF|XgSjYEFkq)rXv z2^QDr-Vx!< z;TDXIJT#PB0Y3aNO06|pAGf0YGGJxO)}&gw!HoNzrCk3g+Y#~Y(C!n;^RS3wkwud>thzY*I*<9gTTODqqiJ$*AjP2TWW0f^mQm`uhauT@UJ1>ovo)9-y7wE*lh$B1lG9 zf3-YB7PoBxu&P{8l~zub7#u%;MMCgwD*!PaC6Rt>8vO}a@aSd#%3 zntvl!1197kljKKGf5#%#`n);=@L(=AZ?ZCUTjK00Tw7I`I3HLDa0ZEt6}L{{t(z7T z(R0DT-fOptOp^!8jy0E+uwW~U5!$(I;Kn(Z}@jQ?sNv^z-Z!%z{rRlWshua7>gZWdVTm0tFMLE}EL4UR4ef7^jt% z0L+A;&{$FnTj{~{sks~Nrjc>ovZ&m%P@8*tbFnB+7dJps6%-m{oGK*iPzE(zD*Sg- zON#*C-bbk$O<*(G=rt>!se|<$VP^#6dHbftY+R6M6vj`!zpo4=Aj{H7Zqa}DV6Pb-g6*fchKZrGZcc1nDgc+^w+G&-7=LzEZ^%G z^p-oxv}=pD5~z#Hiz=86vXIssL9#}Vv_dxK0lm|O(Bj{!SBMs~)!RveKJoBQLJ4_z+8|<^cW%l^dwyVhK(zztG+eZZ&_U3~+vIy+5nbSDhzQ&B zORUw;t(??3FA#a3PAIbL@wUkNd9eA;yN^LqazYYEXX|?W20jW>{CY8aH8LrgW(*n& z7~BO4gAM}A)@n~E&__ZLy0BV}fMK)W>4vx;CSZeS^r&nIwWrCq82G@8iPAt{mGOAr zm|~i({fzrs-Mw-$J_4UWWLLHTs68k88vUD6@_;}_`=dU3D^f;qThM3*8FkGa0~PF% z3IVvbw0D>0hrry_Q>+<&x|M58tUC#?U~ZN=Du$3{z=?EfhwCG6NJ|Hc{IuMTswJ(9 z02C`?Fgo-MWjG+l=@rD(v)3z)i6ZI2+ zHqlnNEl%PnCdx!c2ywcZzY0?G`+~=Pyau1zJW>psCq_O>RhG=@t2ms$*Z|w8+og7a z14`kaEfKkGOV$%=(wdN+>LH&{)pkTm^#=>)RE@bn^V$7hx#Zgl_&w--Nva|p_gnjr zJ2Z=<`k0Cevks zqUnOaTlddydf;g6pEgq?{p2fURm#YO;nr^(&3Y<#ZTI(fTd*$T5u1_}xKjiDUua1I zcQdIu2Za4%H+h39-m;AHc@d@?ab!&gO`yrj{C&13+)~d%(MHN!(mtNPF7f%zH?fDL zQqFi0jvZ)Yk;UyEP;4*u4kuAL$vSqEvr6-5HYeDizZn@Lrf{{G3KAfm&rQ@0dy`K6 zA-hY#NHIg$k4bY6Jz0rroLQ|3CVaAmyOK?~UvK^>jbd$6|IRHzjoLY>7Tp~>t7^;1 z^%Or%GBYMJD-H?->RSgNFjC$zXZdG>M=l;aJtq`(z`UzI+gnY4nHsRkqn+40(eeyY zH=bJP#I2E;A8@VGv zyvRH15Gnoy?+-0$PqHR}-m0))YpySg6!<6y+lkiqgKN5kab!Z~+q?B&&hWrBTa*E5yM2>uC}`MqgW%EIBI@KmMBR%dH9?lh-h zz5sq)w=w+e9cCYwBzi2OpFvc1;MMLr`LjUEMZ3XKflY^~{CK!~&Wo@P!!z%8eS-+7 zIPjcK7IrB>R<`SjBlR|{PJ4(~1>N#b7OS9TP97_y$qYya@3kc_gV55VL^)iBm)E~> z<=+^z1%i20C_;8T&!+$YU_L`E`CtCCitW+eh6HQ6TwM^ILoVL)Y>)OZhGhysis_r( z3$duV5c4-V}fAV>(W-Ax$3fzO?9~qEWPvkMPfVnZb+xlE|C( z2{HPGfHMv(R54x{oX>`-xoQfgwS_Fka29lR$PS)IcYcOI6nB3muX%?qDz+kWG~v0# zbEHc4#O*rm$SRd%|8#LUD^Ec>WR_AFdi;`nlQqvuu#ZhrE^3(KN*(mshfkuT__&|o z5F@w7#_gs;fhtb*n2CR0Rl@0C07jNUza$TO$9G2*+!CJiiEMD`yr&8k`Tp4> z7m^heHf~QJ^D(6KBYwyJz9FudVwwGH>)tUzywSsJx2l)A4S;<~z+{cQ`hL_lMy)VORA#I&QoU6J(^`2MzTAw%;D{(*p=_q5!Wf;z3AfhI=do8A^nVza%o8r+#gjh`g z8)(3O$jZnyN}kBRIcvw~JgSJ)TUcTnILg(@!oQ3Xtb86?+oFJszc7|kpqw{+I1qKW zH_wMg)mfM>(wd*{HUYqM3ivJ9m1=pR#r6822QQu4dmoXWBzaa6dVw&Y>?RPa`g+XB z2l-AP4~YWAG%&|-&oPM7KHEr#<)^$MCiLOmE7Zkj&ZdZ4Y9u(hLBs1=cx-Bgn{v9; z0HI?XC=MQlSE(Ut>UtNG)i)aF=SF3km(ThF#oIg~Tk{$OOrYw^hH``_%Rt5tG=YQp zc>eWx(*TsN>oo7Ci5N7k&kq-*B?u*%_%e%kNS&%@SL_^~z4v zt=+sjMCnO&0j!HBr)OeC)nS+75@6T&IK>7<{!uP+Ix?)Z)Jg`Fxc&1JeJQbX-4aqA zzbt>VYq74;q)nSVT6?Q&owyx7$A>+Mh!R+a!JGRbJrlVum#T?FP1lIP+qyQ?C_cl- zvKf}1Pgq{7LC^)Kwu8928|{R$RQ)0efi1HaVfK)^irr+}{Ny7p2yOa&*{TAq4UoK- zZ(hm&>yp3};Emqkp_Pi4Jdcz4@=mO7PJD)c5up-eq)y{3yJ1hvTG(vi$q;w&2%&=H z6u9Mqrbv#gBetq1;bY+LGP(GOf6(DCm_ZbhOq<(tHDhOS6v+8|6@f@4Is?2_Qp(qI zvhxtZnQ2YvbM-iY^ZCQa<_}W^#&FgKj2Wd=1{G^0_<4% zf+x2ggPpxoACPOV3%<#|&v(%;yn~>whRcAZt;IxS7nS6l5Rm+G^9V!XMcpX7&DGo2T|k8f z466FUd_`m45^ifo3)-(}9apkOzxY~zX}LI*GpZBObV5&_rbK4ICS%rN?IK9LqT)Q$ zv-(+uxTLi%MoS7VX3mnahr7L#3UEu~0W913Fr|ZCvYgqSLV5jhgopP#Q|0n-zY%1x! zs_9m$uIUU9BBtUlNF~h*<9OKWeENfk@gwIJA@(CsN|9eAUq=J9} z#1XqwYZke?IIn!d11yTD*t-*Ovx;o5%+TE5>(vvyN&fvJ;G?foayO(eTMwf*VqC## zN+)H0_Ple-lG?ceu-Z%eL91fko#^R(coL20YS|hGM>46^(##39n-^a}U=EVW%i8@F zU6P)+wpS((6C#EEJG%pfK_!6o0A>w&T^qui*P(Q+3PTc%`p}xZ`UU2~fzW5CCK_H= zZlb=C_ao;lV@doPavW^MKyQcx=%x>_GO$d3^$X^Py%Mk$?mB7~nN9Yln*eMr%QQN{ zB)HFthN;b;uu%GdU`#9Oh-0F8R{-__MvP&$hzhRQ0Qu9GWgj|a@iGjkXB6Mm;=Lv! z7_90ytMo2o05A*F=%x;_T!h+c@gO+3f1yu6Oi3!(?GI_nxS?n={@*8jAfPRnJ&|%y z>J6rtuFhXj1#QUlMRU!st6Ql&y-+iR?HwbZQrlDbtAjsQw0|ha1*P%feLtBNGUOrQ zpwnc(1adl}>2f%}gmyfWmiW(S-F!;T4$=B6^m(p-Z5b;vVVqxvd_Vd8CAGY~;ZxWU z2qfc!j~ahNb@nv?bGD2qu!XU|IlY&0YBk}jwIz;4 z^H1zZyytjj5c4qb5$AO*AW~f#q18$fwdDLKq34F?uNZ#Ahta0Dlt50HUhBN{T3LSy z)mYYSJ^Qa1R9uK{CC@n|*K@iav;VQM*a_s^n?+i|`VuTDogUX=7i15$(_P;{>_1$y zInSnROJ$~uWE5b%w;-A(H{BS>Xewfq+W3#sqmseyBFMdM6>oB3R1(HZ0{R@oc{+87;HpHN5W?k6cD_!Errk4v9kp``;)8d54xVtB7V&;n*2>l zok0x}_I|$88VA!S7#8A#zcwY&0;RyLCrHCk{4FddKeH`H^oZQa4+*DV#i5r%_c{(H9Z0J;!7S>u&B~i1ER>NF2w^6UT+iR(O7a{(51L98;HTB% zyPyEfiGjfuv#-`UB@YvKi&ehWcAh3uQBGUqFel3(3_0Qr!b-A7xJok5Yo;GM{A?AG ze^eq@Aut0H3;O!|6YOItPS3D?PrQR@mTF0>-u4w`%E`o&_Dj?9@-EzGy>Xu+=p4>~ z(6pQW*TDUkXO+l1>I*-z8dym{8uY~=xIYU_b3>W%v9(mu8D4PUY^;GGLCCQ$E4`3b z^c-w%G?ilG36Kz!hans7AQI%mzu*9qfy&zgkN|C{eGLJY`~_tM9a&L8%A!nWkqB$V4BKG^!4#?E|2Y>irMeiTq*|8r>N7>s z22S_lEt2?yC6GdP*%Q7WX~*lc<~p?#0K z&PI8^HefC(Z-YbhJA*B+uHgTv;}TEJaHb=#+3PDZqR!P=EII{9BH9d0%g|<2apb30 z#cTzelAUbUC1n%9Ow|#yS2hj4TK{2cg^B1XY$8t_y1=v51G@qXU^5_>;*`5{XvPnW zK>D|mBzYohzyex2KY~2P!LfT}qp!(Y^<3RKP%e~S7fx3u_=n+30Af0;qBZ5}m3i5! zVnySL_3US{?C~M9+?qN^*grRp9$~^S{%xDa25rjIJR>&>@mjk#qjb0cL{;Fw>nA$d zHQpJaDhVlUE>v{T8|17W`L0k8V{JlxH~!AUsco$>jjwCQ)kVCM{zX>WswL9E*Mrj;n(QJBkAvSt&o7SCTEK2fhIy%HTZ=gt_d@^W=43% zA>2jb|<>-eVznc;CvTFj-nG@a4ekwOo>CBEOHX7A;XLG%>3!* zi5gEILOGvq?DiHiwx!w~WgbHVp7>cGPX7SV>A8(7ZPf*e#xe91#WpVAo1PjVWg(}K z9pw)S^B%N$bo~XOLapnsUFKm+_wEvx8tmN(nJN=U3JLPZ{7_{_5e>yFC!$p2x=TN8ywj!GhDISTbb1BdRaA1gDM;cH%r4f z{)RvxZ@Wejl(h|#fuuD5wMv81mjWBir~!zYd2t2W;t>rP6QzT0XgIOV4f7k#>NTI;K(Zys+X$C+VPZsk;o9^P7bN+ z$3>sJPkS8@HEN95py&N|Zf<97Fq}Nja$PoFstEqM-U++(I`!%8BviQ0?hB|1SX>bH z^wWUoeAiKGf@@omaa&=qDJN(^Hb`nafuP=KhJd+;P#aJgwtZ5;$)^2Rnb(I^2mT+v z?;Nb9(cHm{Ar?7~t z>)=@>7riSSA=+_6Z?uI>Jb;Vn_6mT`myQ<=3MLTNTjm_m&3(Z=SFB%FiVFVa2|u*2 zancNl;_xiO3>~oPo53GfQ}9cI7xC_d+AfwiM9L57G>AI@f4dY1??4-nKp3_;`SH}H z9~n0uxZaZOyLZ;V{sjsv19%9NzArsO(56e-x4CU*9n;0%t}eTL59Q-cQ=d_q|; zb>Bf3Z>eF`;PjS!Z6|fG@Hqvy8Kb{V*}wE= z9}c&83sY=}doF41o45@1Z}Fw!$PFSZz(0pToFbH%qQcjyr>%Jt4b#eI3gPDrVPyLQ zSXO7o3|`nH0R$xJMBCRgyZHp_C=(c<)0|{H+xU_^;xcr8#_90dNcTc2h)9nU5}xj# z?aA~P?5YU04&tSqzdndcKVK27?M9Ja8ciEv1XyCCMu4<*8Fx|+{L6IxCjM&g&I3l6 zJY#avP~rKib;3qCQ>aaUfavbth#m*A6?;Ewb#f@z=S+N+7}oZrE6^}L-$j$bG3daN z@D`tCurg)d*(AcVsKJ+l?m`EX&<4a8ArOlPb^ zWA9=FdnH^IqQ7339`8|-^mj4K>d6>)vck(-5X2@DNgHq~anpG8!MVP6y6HVY4_JK{ zQ}ibHUrhch#W(W=_0(G{#;rMtGx;ihP5|{J576SMwX!kpTPUR-n)o75ZcttLM_HhRPlsJ($fPZ0j2?psak{V)X@gS#z!os z=j=!bt;+S!ymC}8N<|C)CE|0Z(99S&@z8FNv6!Cb7HXi74H#DK0w)75F*l6UZ$pUq zn-sCJ0Uc8XmXMh9l&B%@EOTl}T0xL;kR6OG`OQm3O&Rw}VKUd-=cFz6t4%Fm^$`qM z6gHZYqdQYpqIY!rhzJDQ(9!jX6e00EQeq`t6elBLHPI^ihYE>@9if$B~QZY(GYJSrjbQ{ zxMB5*L~wZT%-u$H_*@Qb*^En3@S?nX6g_@xJ+vtb|3xCciRUH5^*B}HN0H+ZO;h@I z&6m#fW;KQD&4db`W5hrq+~7=4ybdblAWQ^A{JMSv3;uGae7&Wj5FY@mCST$?(6`E) z-+0TS>ae?pEJ5OaWmt3ONyK-++@J0@vUE0Lq{Y8uI+M*kbb5zTdePh2n+&XEzlwEe zzfgz%RKt%*kZf(2Y)gS59a;n-to=-`X$F0l3n7R<1_!Uf`25z5t{|kctw3y?J$V&p z>`Fj5^ntz5IaPjnZT{iPpe)gOiOPkiQP5`6x|1KL+>+POlT@!d{{%Hhhu}^9L)pkt zz=XOg+bK|@fYTm9gc!Cck0+GD! z^ak+sSij_I!2LdvaVh**@fI0=|9t=p|5Kegg|f(0uzaC*ggRjTOTh?>G9<>O)#rW= mgp)CVH)^5O(40R1<24kMS$zD%5J=ukpgAlzok-+9-GP`ylnqY+ From c05713d93a2f6a7124c7b6b21570ba85b1a83d80 Mon Sep 17 00:00:00 2001 From: Tom Milligan Date: Wed, 7 Aug 2024 17:47:37 +0100 Subject: [PATCH 854/966] fix: retry token request on retryable status code (#1563) * fix: retry token request on retryable status code --- .../google/auth/compute_engine/_metadata.py | 13 +++- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../tests/compute_engine/test__metadata.py | 68 ++++++++++++++++++ 3 files changed, 80 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/compute_engine/_metadata.py b/packages/google-auth/google/auth/compute_engine/_metadata.py index 69b7b5245893..b66d9f9b37f1 100644 --- a/packages/google-auth/google/auth/compute_engine/_metadata.py +++ b/packages/google-auth/google/auth/compute_engine/_metadata.py @@ -28,6 +28,7 @@ from google.auth import environment_vars from google.auth import exceptions from google.auth import metrics +from google.auth import transport from google.auth._exponential_backoff import ExponentialBackoff _LOGGER = logging.getLogger(__name__) @@ -204,7 +205,17 @@ def get( for attempt in backoff: try: response = request(url=url, method="GET", headers=headers_to_use) - break + if response.status in transport.DEFAULT_RETRYABLE_STATUS_CODES: + _LOGGER.warning( + "Compute Engine Metadata server unavailable on " + "attempt %s of %s. Response status: %s", + attempt, + retry_count, + response.status, + ) + continue + else: + break except exceptions.TransportError as e: _LOGGER.warning( diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index aab772bbb8d79f8762222990bf9fc488027e473e..a38b5d5bc5a969faf0b2997ff388ee289dadb693 100644 GIT binary patch literal 10324 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 literal 10324 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 diff --git a/packages/google-auth/tests/compute_engine/test__metadata.py b/packages/google-auth/tests/compute_engine/test__metadata.py index 391422b040ce..f49886d714ca 100644 --- a/packages/google-auth/tests/compute_engine/test__metadata.py +++ b/packages/google-auth/tests/compute_engine/test__metadata.py @@ -431,6 +431,74 @@ def test_get_universe_domain_not_found(): assert universe_domain == "googleapis.com" +def test_get_universe_domain_retryable_error_failure(): + # Test that if the universe domain endpoint returns a retryable error + # we should retry. + # + # In this case, the error persists, and we still fail after retrying. + request = make_request("too many requests", status=http_client.TOO_MANY_REQUESTS) + + with pytest.raises(exceptions.TransportError) as excinfo: + _metadata.get_universe_domain(request) + + assert excinfo.match(r"Compute Engine Metadata server unavailable") + + request.assert_called_with( + method="GET", + url=_metadata._METADATA_ROOT + "universe/universe_domain", + headers=_metadata._METADATA_HEADERS, + ) + assert request.call_count == 5 + + +def test_get_universe_domain_retryable_error_success(): + # Test that if the universe domain endpoint returns a retryable error + # we should retry. + # + # In this case, the error is temporary, and we succeed after retrying. + request_error = make_request( + "too many requests", status=http_client.TOO_MANY_REQUESTS + ) + request_ok = make_request( + "fake_universe_domain", headers={"content-type": "text/plain"} + ) + + class _RequestErrorOnce: + """This class forwards the request parameters to `request_error` once. + + All subsequent calls are forwarded to `request_ok`. + """ + + def __init__(self, request_error, request_ok): + self._request_error = request_error + self._request_ok = request_ok + self._call_index = 0 + + def request(self, *args, **kwargs): + if self._call_index == 0: + self._call_index += 1 + return self._request_error(*args, **kwargs) + + return self._request_ok(*args, **kwargs) + + request = _RequestErrorOnce(request_error, request_ok).request + + universe_domain = _metadata.get_universe_domain(request) + + request_error.assert_called_once_with( + method="GET", + url=_metadata._METADATA_ROOT + "universe/universe_domain", + headers=_metadata._METADATA_HEADERS, + ) + request_ok.assert_called_once_with( + method="GET", + url=_metadata._METADATA_ROOT + "universe/universe_domain", + headers=_metadata._METADATA_HEADERS, + ) + + assert universe_domain == "fake_universe_domain" + + def test_get_universe_domain_other_error(): # Test that if the universe domain endpoint returns an error other than 404 # we should throw the error From 6deb8bd8534286720b1c6b062b52e912f09698fb Mon Sep 17 00:00:00 2001 From: Andy Zhao Date: Wed, 7 Aug 2024 16:48:14 -0700 Subject: [PATCH 855/966] feat(auth): Update get_client_ssl_credentials to support X.509 workload certs (#1558) * feat(auth): Update get_client_ssl_credentials to support X.509 workload certs * feat(auth): Update has_default_client_cert_source * feat(auth): Fix formatting * feat(auth): Fix test__mtls_helper.py * feat(auth): Fix function name in tests * chore: Refresh system test creds. * feat(auth): Fix style * feat(auth): Fix casing * feat(auth): Fix linter issue * feat(auth): Fix coverage issue --------- Co-authored-by: Carl Lundin Co-authored-by: Carl Lundin <108372512+clundin25@users.noreply.github.com> --- .../google/auth/transport/_mtls_helper.py | 40 +++++--- .../google-auth/google/auth/transport/grpc.py | 2 +- .../google-auth/google/auth/transport/mtls.py | 17 +++- .../tests/transport/test__mtls_helper.py | 97 +++++++++++++------ .../google-auth/tests/transport/test_grpc.py | 34 +++---- .../google-auth/tests/transport/test_mtls.py | 25 +++-- 6 files changed, 145 insertions(+), 70 deletions(-) diff --git a/packages/google-auth/google/auth/transport/_mtls_helper.py b/packages/google-auth/google/auth/transport/_mtls_helper.py index 6299e2bdeaaa..68568dd60395 100644 --- a/packages/google-auth/google/auth/transport/_mtls_helper.py +++ b/packages/google-auth/google/auth/transport/_mtls_helper.py @@ -23,7 +23,7 @@ from google.auth import exceptions CONTEXT_AWARE_METADATA_PATH = "~/.secureConnect/context_aware_metadata.json" -_CERTIFICATE_CONFIGURATION_DEFAULT_PATH = "~/.config/gcloud/certificate_config.json" +CERTIFICATE_CONFIGURATION_DEFAULT_PATH = "~/.config/gcloud/certificate_config.json" _CERTIFICATE_CONFIGURATION_ENV = "GOOGLE_API_CERTIFICATE_CONFIG" _CERT_PROVIDER_COMMAND = "cert_provider_command" _CERT_REGEX = re.compile( @@ -48,21 +48,21 @@ ) -def _check_dca_metadata_path(metadata_path): - """Checks for context aware metadata. If it exists, returns the absolute path; +def _check_config_path(config_path): + """Checks for config file path. If it exists, returns the absolute path with user expansion; otherwise returns None. Args: - metadata_path (str): context aware metadata path. + config_path (str): The config file path for either context_aware_metadata.json or certificate_config.json for example Returns: str: absolute path if exists and None otherwise. """ - metadata_path = path.expanduser(metadata_path) - if not path.exists(metadata_path): - _LOGGER.debug("%s is not found, skip client SSL authentication.", metadata_path) + config_path = path.expanduser(config_path) + if not path.exists(config_path): + _LOGGER.debug("%s is not found.", config_path) return None - return metadata_path + return config_path def _load_json_file(path): @@ -136,7 +136,7 @@ def _get_cert_config_path(certificate_config_path=None): if env_path is not None and env_path != "": certificate_config_path = env_path else: - certificate_config_path = _CERTIFICATE_CONFIGURATION_DEFAULT_PATH + certificate_config_path = CERTIFICATE_CONFIGURATION_DEFAULT_PATH certificate_config_path = path.expanduser(certificate_config_path) if not path.exists(certificate_config_path): @@ -279,14 +279,22 @@ def _run_cert_provider_command(command, expect_encrypted_key=False): def get_client_ssl_credentials( generate_encrypted_key=False, context_aware_metadata_path=CONTEXT_AWARE_METADATA_PATH, + certificate_config_path=CERTIFICATE_CONFIGURATION_DEFAULT_PATH, ): """Returns the client side certificate, private key and passphrase. + We look for certificates and keys with the following order of priority: + 1. Certificate and key specified by certificate_config.json. + Currently, only X.509 workload certificates are supported. + 2. Certificate and key specified by context aware metadata (i.e. SecureConnect). + Args: generate_encrypted_key (bool): If set to True, encrypted private key and passphrase will be generated; otherwise, unencrypted private key - will be generated and passphrase will be None. + will be generated and passphrase will be None. This option only + affects keys obtained via context_aware_metadata.json. context_aware_metadata_path (str): The context_aware_metadata.json file path. + certificate_config_path (str): The certificate_config.json file path. Returns: Tuple[bool, bytes, bytes, bytes]: @@ -297,7 +305,17 @@ def get_client_ssl_credentials( google.auth.exceptions.ClientCertError: if problems occurs when getting the cert, key and passphrase. """ - metadata_path = _check_dca_metadata_path(context_aware_metadata_path) + + # 1. Check for certificate config json. + cert_config_path = _check_config_path(certificate_config_path) + if cert_config_path: + # Attempt to retrieve X.509 Workload cert and key. + cert, key = _get_workload_cert_and_key(cert_config_path) + if cert and key: + return True, cert, key, None + + # 2. Check for context aware metadata json + metadata_path = _check_config_path(context_aware_metadata_path) if metadata_path: metadata_json = _load_json_file(metadata_path) diff --git a/packages/google-auth/google/auth/transport/grpc.py b/packages/google-auth/google/auth/transport/grpc.py index 9a817976d776..1ebe1379579e 100644 --- a/packages/google-auth/google/auth/transport/grpc.py +++ b/packages/google-auth/google/auth/transport/grpc.py @@ -302,7 +302,7 @@ def __init__(self): self._is_mtls = False else: # Load client SSL credentials. - metadata_path = _mtls_helper._check_dca_metadata_path( + metadata_path = _mtls_helper._check_config_path( _mtls_helper.CONTEXT_AWARE_METADATA_PATH ) self._is_mtls = metadata_path is not None diff --git a/packages/google-auth/google/auth/transport/mtls.py b/packages/google-auth/google/auth/transport/mtls.py index c5707617ff80..e7a7304f60ee 100644 --- a/packages/google-auth/google/auth/transport/mtls.py +++ b/packages/google-auth/google/auth/transport/mtls.py @@ -24,10 +24,19 @@ def has_default_client_cert_source(): Returns: bool: indicating if the default client cert source exists. """ - metadata_path = _mtls_helper._check_dca_metadata_path( - _mtls_helper.CONTEXT_AWARE_METADATA_PATH - ) - return metadata_path is not None + if ( + _mtls_helper._check_config_path(_mtls_helper.CONTEXT_AWARE_METADATA_PATH) + is not None + ): + return True + if ( + _mtls_helper._check_config_path( + _mtls_helper.CERTIFICATE_CONFIGURATION_DEFAULT_PATH + ) + is not None + ): + return True + return False def default_client_cert_source(): diff --git a/packages/google-auth/tests/transport/test__mtls_helper.py b/packages/google-auth/tests/transport/test__mtls_helper.py index b195616dd577..f6e20b726a86 100644 --- a/packages/google-auth/tests/transport/test__mtls_helper.py +++ b/packages/google-auth/tests/transport/test__mtls_helper.py @@ -111,15 +111,15 @@ def test_key(self): ) -class TestCheckaMetadataPath(object): +class TestCheckConfigPath(object): def test_success(self): metadata_path = os.path.join(pytest.data_dir, "context_aware_metadata.json") - returned_path = _mtls_helper._check_dca_metadata_path(metadata_path) + returned_path = _mtls_helper._check_config_path(metadata_path) assert returned_path is not None def test_failure(self): metadata_path = os.path.join(pytest.data_dir, "not_exists.json") - returned_path = _mtls_helper._check_dca_metadata_path(metadata_path) + returned_path = _mtls_helper._check_config_path(metadata_path) assert returned_path is None @@ -275,21 +275,24 @@ def test_popen_raise_exception(self, mock_popen): class TestGetClientSslCredentials(object): @mock.patch( - "google.auth.transport._mtls_helper._run_cert_provider_command", autospec=True + "google.auth.transport._mtls_helper._get_workload_cert_and_key", autospec=True ) - @mock.patch("google.auth.transport._mtls_helper._load_json_file", autospec=True) @mock.patch( - "google.auth.transport._mtls_helper._check_dca_metadata_path", autospec=True + "google.auth.transport._mtls_helper._run_cert_provider_command", autospec=True ) - def test_success( + @mock.patch("google.auth.transport._mtls_helper._load_json_file", autospec=True) + @mock.patch("google.auth.transport._mtls_helper._check_config_path", autospec=True) + def test_success_with_context_aware_metadata( self, - mock_check_dca_metadata_path, + mock_check_config_path, mock_load_json_file, mock_run_cert_provider_command, + mock_get_workload_cert_and_key, ): - mock_check_dca_metadata_path.return_value = True + mock_check_config_path.return_value = "/path/to/config" mock_load_json_file.return_value = {"cert_provider_command": ["command"]} mock_run_cert_provider_command.return_value = (b"cert", b"key", None) + mock_get_workload_cert_and_key.return_value = (None, None) has_cert, cert, key, passphrase = _mtls_helper.get_client_ssl_credentials() assert has_cert assert cert == b"cert" @@ -297,10 +300,42 @@ def test_success( assert passphrase is None @mock.patch( - "google.auth.transport._mtls_helper._check_dca_metadata_path", autospec=True + "google.auth.transport._mtls_helper._read_cert_and_key_files", autospec=True ) - def test_success_without_metadata(self, mock_check_dca_metadata_path): - mock_check_dca_metadata_path.return_value = False + @mock.patch( + "google.auth.transport._mtls_helper._get_cert_config_path", autospec=True + ) + @mock.patch("google.auth.transport._mtls_helper._load_json_file", autospec=True) + @mock.patch("google.auth.transport._mtls_helper._check_config_path", autospec=True) + def test_success_with_certificate_config( + self, + mock_check_config_path, + mock_load_json_file, + mock_get_cert_config_path, + mock_read_cert_and_key_files, + ): + cert_config_path = "/path/to/config" + mock_check_config_path.return_value = cert_config_path + mock_load_json_file.return_value = { + "cert_configs": { + "workload": {"cert_path": "cert/path", "key_path": "key/path"} + } + } + mock_get_cert_config_path.return_value = cert_config_path + mock_read_cert_and_key_files.return_value = ( + pytest.public_cert_bytes, + pytest.private_key_bytes, + ) + + has_cert, cert, key, passphrase = _mtls_helper.get_client_ssl_credentials() + assert has_cert + assert cert == pytest.public_cert_bytes + assert key == pytest.private_key_bytes + assert passphrase is None + + @mock.patch("google.auth.transport._mtls_helper._check_config_path", autospec=True) + def test_success_without_metadata(self, mock_check_config_path): + mock_check_config_path.return_value = False has_cert, cert, key, passphrase = _mtls_helper.get_client_ssl_credentials() assert not has_cert assert cert is None @@ -308,21 +343,24 @@ def test_success_without_metadata(self, mock_check_dca_metadata_path): assert passphrase is None @mock.patch( - "google.auth.transport._mtls_helper._run_cert_provider_command", autospec=True + "google.auth.transport._mtls_helper._get_workload_cert_and_key", autospec=True ) - @mock.patch("google.auth.transport._mtls_helper._load_json_file", autospec=True) @mock.patch( - "google.auth.transport._mtls_helper._check_dca_metadata_path", autospec=True + "google.auth.transport._mtls_helper._run_cert_provider_command", autospec=True ) + @mock.patch("google.auth.transport._mtls_helper._load_json_file", autospec=True) + @mock.patch("google.auth.transport._mtls_helper._check_config_path", autospec=True) def test_success_with_encrypted_key( self, - mock_check_dca_metadata_path, + mock_check_config_path, mock_load_json_file, mock_run_cert_provider_command, + mock_get_workload_cert_and_key, ): - mock_check_dca_metadata_path.return_value = True + mock_check_config_path.return_value = "/path/to/config" mock_load_json_file.return_value = {"cert_provider_command": ["command"]} mock_run_cert_provider_command.return_value = (b"cert", b"key", b"passphrase") + mock_get_workload_cert_and_key.return_value = (None, None) has_cert, cert, key, passphrase = _mtls_helper.get_client_ssl_credentials( generate_encrypted_key=True ) @@ -334,15 +372,20 @@ def test_success_with_encrypted_key( ["command", "--with_passphrase"], expect_encrypted_key=True ) - @mock.patch("google.auth.transport._mtls_helper._load_json_file", autospec=True) @mock.patch( - "google.auth.transport._mtls_helper._check_dca_metadata_path", autospec=True + "google.auth.transport._mtls_helper._get_workload_cert_and_key", autospec=True ) + @mock.patch("google.auth.transport._mtls_helper._load_json_file", autospec=True) + @mock.patch("google.auth.transport._mtls_helper._check_config_path", autospec=True) def test_missing_cert_command( - self, mock_check_dca_metadata_path, mock_load_json_file + self, + mock_check_config_path, + mock_load_json_file, + mock_get_workload_cert_and_key, ): - mock_check_dca_metadata_path.return_value = True + mock_check_config_path.return_value = "/path/to/config" mock_load_json_file.return_value = {} + mock_get_workload_cert_and_key.return_value = (None, None) with pytest.raises(exceptions.ClientCertError): _mtls_helper.get_client_ssl_credentials() @@ -350,17 +393,15 @@ def test_missing_cert_command( "google.auth.transport._mtls_helper._run_cert_provider_command", autospec=True ) @mock.patch("google.auth.transport._mtls_helper._load_json_file", autospec=True) - @mock.patch( - "google.auth.transport._mtls_helper._check_dca_metadata_path", autospec=True - ) + @mock.patch("google.auth.transport._mtls_helper._check_config_path", autospec=True) def test_customize_context_aware_metadata_path( self, - mock_check_dca_metadata_path, + mock_check_config_path, mock_load_json_file, mock_run_cert_provider_command, ): context_aware_metadata_path = "/path/to/metata/data" - mock_check_dca_metadata_path.return_value = context_aware_metadata_path + mock_check_config_path.return_value = context_aware_metadata_path mock_load_json_file.return_value = {"cert_provider_command": ["command"]} mock_run_cert_provider_command.return_value = (b"cert", b"key", None) @@ -372,7 +413,7 @@ def test_customize_context_aware_metadata_path( assert cert == b"cert" assert key == b"key" assert passphrase is None - mock_check_dca_metadata_path.assert_called_with(context_aware_metadata_path) + mock_check_config_path.assert_called_with(context_aware_metadata_path) mock_load_json_file.assert_called_with(context_aware_metadata_path) @@ -520,7 +561,7 @@ def test_default(self, mock_path_exists): mock_path_exists.return_value = True returned_path = _mtls_helper._get_cert_config_path() expected_path = os.path.expanduser( - _mtls_helper._CERTIFICATE_CONFIGURATION_DEFAULT_PATH + _mtls_helper.CERTIFICATE_CONFIGURATION_DEFAULT_PATH ) assert returned_path == expected_path diff --git a/packages/google-auth/tests/transport/test_grpc.py b/packages/google-auth/tests/transport/test_grpc.py index 433cc6855226..ed3f3ee83911 100644 --- a/packages/google-auth/tests/transport/test_grpc.py +++ b/packages/google-auth/tests/transport/test_grpc.py @@ -142,12 +142,10 @@ def test__get_authorization_headers_with_service_account_and_default_host(self): @mock.patch("grpc.secure_channel", autospec=True) class TestSecureAuthorizedChannel(object): @mock.patch("google.auth.transport._mtls_helper._load_json_file", autospec=True) - @mock.patch( - "google.auth.transport._mtls_helper._check_dca_metadata_path", autospec=True - ) + @mock.patch("google.auth.transport._mtls_helper._check_config_path", autospec=True) def test_secure_authorized_channel_adc( self, - check_dca_metadata_path, + check_config_path, load_json_file, secure_channel, ssl_channel_credentials, @@ -161,7 +159,7 @@ def test_secure_authorized_channel_adc( # Mock the context aware metadata and client cert/key so mTLS SSL channel # will be used. - check_dca_metadata_path.return_value = METADATA_PATH + check_config_path.return_value = METADATA_PATH load_json_file.return_value = {"cert_provider_command": ["some command"]} get_client_ssl_credentials.return_value = ( True, @@ -331,12 +329,10 @@ def test_secure_authorized_channel_with_client_cert_callback_success( ) @mock.patch("google.auth.transport._mtls_helper._load_json_file", autospec=True) - @mock.patch( - "google.auth.transport._mtls_helper._check_dca_metadata_path", autospec=True - ) + @mock.patch("google.auth.transport._mtls_helper._check_config_path", autospec=True) def test_secure_authorized_channel_with_client_cert_callback_failure( self, - check_dca_metadata_path, + check_config_path, load_json_file, secure_channel, ssl_channel_credentials, @@ -400,19 +396,17 @@ def test_secure_authorized_channel_cert_callback_without_client_cert_env( "google.auth.transport._mtls_helper.get_client_ssl_credentials", autospec=True ) @mock.patch("google.auth.transport._mtls_helper._load_json_file", autospec=True) -@mock.patch( - "google.auth.transport._mtls_helper._check_dca_metadata_path", autospec=True -) +@mock.patch("google.auth.transport._mtls_helper._check_config_path", autospec=True) class TestSslCredentials(object): def test_no_context_aware_metadata( self, - mock_check_dca_metadata_path, + mock_check_config_path, mock_load_json_file, mock_get_client_ssl_credentials, mock_ssl_channel_credentials, ): # Mock that the metadata file doesn't exist. - mock_check_dca_metadata_path.return_value = None + mock_check_config_path.return_value = None with mock.patch.dict( os.environ, {environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE: "true"} @@ -429,12 +423,12 @@ def test_no_context_aware_metadata( def test_get_client_ssl_credentials_failure( self, - mock_check_dca_metadata_path, + mock_check_config_path, mock_load_json_file, mock_get_client_ssl_credentials, mock_ssl_channel_credentials, ): - mock_check_dca_metadata_path.return_value = METADATA_PATH + mock_check_config_path.return_value = METADATA_PATH mock_load_json_file.return_value = {"cert_provider_command": ["some command"]} # Mock that client cert and key are not loaded and exception is raised. @@ -448,12 +442,12 @@ def test_get_client_ssl_credentials_failure( def test_get_client_ssl_credentials_success( self, - mock_check_dca_metadata_path, + mock_check_config_path, mock_load_json_file, mock_get_client_ssl_credentials, mock_ssl_channel_credentials, ): - mock_check_dca_metadata_path.return_value = METADATA_PATH + mock_check_config_path.return_value = METADATA_PATH mock_load_json_file.return_value = {"cert_provider_command": ["some command"]} mock_get_client_ssl_credentials.return_value = ( True, @@ -476,7 +470,7 @@ def test_get_client_ssl_credentials_success( def test_get_client_ssl_credentials_without_client_cert_env( self, - mock_check_dca_metadata_path, + mock_check_config_path, mock_load_json_file, mock_get_client_ssl_credentials, mock_ssl_channel_credentials, @@ -486,7 +480,7 @@ def test_get_client_ssl_credentials_without_client_cert_env( assert ssl_credentials.ssl_credentials is not None assert not ssl_credentials.is_mtls - mock_check_dca_metadata_path.assert_not_called() + mock_check_config_path.assert_not_called() mock_load_json_file.assert_not_called() mock_get_client_ssl_credentials.assert_not_called() mock_ssl_channel_credentials.assert_called_once() diff --git a/packages/google-auth/tests/transport/test_mtls.py b/packages/google-auth/tests/transport/test_mtls.py index b62063e47975..ea549ae142b2 100644 --- a/packages/google-auth/tests/transport/test_mtls.py +++ b/packages/google-auth/tests/transport/test_mtls.py @@ -16,17 +16,30 @@ import pytest # type: ignore from google.auth import exceptions +from google.auth.transport import _mtls_helper from google.auth.transport import mtls -@mock.patch( - "google.auth.transport._mtls_helper._check_dca_metadata_path", autospec=True -) -def test_has_default_client_cert_source(check_dca_metadata_path): - check_dca_metadata_path.return_value = mock.Mock() +@mock.patch("google.auth.transport._mtls_helper._check_config_path", autospec=True) +def test_has_default_client_cert_source(check_config_path): + def return_path_for_metadata(path): + return mock.Mock() if path == _mtls_helper.CONTEXT_AWARE_METADATA_PATH else None + + check_config_path.side_effect = return_path_for_metadata + assert mtls.has_default_client_cert_source() + + def return_path_for_cert_config(path): + return ( + mock.Mock() + if path == _mtls_helper.CERTIFICATE_CONFIGURATION_DEFAULT_PATH + else None + ) + + check_config_path.side_effect = return_path_for_cert_config assert mtls.has_default_client_cert_source() - check_dca_metadata_path.return_value = None + check_config_path.side_effect = None + check_config_path.return_value = None assert not mtls.has_default_client_cert_source() From 891464afefd769cf0c5f5b922c6ed1d8eaed9098 Mon Sep 17 00:00:00 2001 From: Carl Lundin <108372512+clundin25@users.noreply.github.com> Date: Tue, 13 Aug 2024 11:34:20 -0700 Subject: [PATCH 856/966] chore: Update ECP deps. (#1583) --- packages/google-auth/setup.py | 4 +--- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 4f697a704d8a..4e4c0d47b63d 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -32,9 +32,7 @@ "pyopenssl": ["pyopenssl>=20.0.0", "cryptography>=38.0.3"], "requests": "requests >= 2.20.0, < 3.0.0.dev0", "reauth": "pyu2f>=0.1.5", - # Enterprise cert only works for OpenSSL 1.1.1. Newer versions of these - # dependencies are built with OpenSSL 3.0 so we need to fix the version. - "enterprise_cert": ["cryptography==36.0.2", "pyopenssl==22.0.0"], + "enterprise_cert": ["cryptography", "pyopenssl"], } with io.open("README.rst", "r") as fh: diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index a38b5d5bc5a969faf0b2997ff388ee289dadb693..7a7c5248764a6c1a25b12dcb7cbbadad194b2a6e 100644 GIT binary patch literal 10324 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 literal 10324 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 From 6570a05bc6b97e1cdb7e617ac6358a52ba9a0a11 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Wed, 14 Aug 2024 14:36:36 -0700 Subject: [PATCH 857/966] chore(main): release 2.34.0 (#1574) --- packages/google-auth/CHANGELOG.md | 12 ++++++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index c6978ef00e37..c0495420e1c1 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,18 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.34.0](https://github.com/googleapis/google-auth-library-python/compare/v2.33.0...v2.34.0) (2024-08-13) + + +### Features + +* **auth:** Update get_client_ssl_credentials to support X.509 workload certs ([#1558](https://github.com/googleapis/google-auth-library-python/issues/1558)) ([18c2ec1](https://github.com/googleapis/google-auth-library-python/commit/18c2ec1b571d506c0dbcffc483aa5e7b95e1b246)) + + +### Bug Fixes + +* Retry token request on retryable status code ([#1563](https://github.com/googleapis/google-auth-library-python/issues/1563)) ([f858a15](https://github.com/googleapis/google-auth-library-python/commit/f858a151cb7e29d34578e03c9e3fd4110c6bc258)) + ## [2.33.0](https://github.com/googleapis/google-auth-library-python/compare/v2.32.0...v2.33.0) (2024-08-06) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index c41f8776589f..297e18a45f1b 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.33.0" +__version__ = "2.34.0" From c4644bcedcabc8f8f26699e626876f2595d498d5 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Wed, 11 Sep 2024 18:24:40 -0700 Subject: [PATCH 858/966] feat: add cred info to ADC creds (#1587) * feat: add cred info to ADC credentials * address comment * chore: add token info support * chore: update sys test cred --- packages/google-auth/google/auth/_default.py | 2 + .../google/auth/compute_engine/credentials.py | 8 ++ .../google-auth/google/auth/credentials.py | 11 ++ .../google/auth/external_account.py | 43 ++++-- .../auth/external_account_authorized_user.py | 34 +++-- .../google/auth/impersonated_credentials.py | 38 +++-- .../google-auth/google/oauth2/credentials.py | 126 +++++++++-------- .../google/oauth2/service_account.py | 14 +- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../tests/compute_engine/test_credentials.py | 7 + .../tests/oauth2/test_credentials.py | 130 ++++++++++++++++-- .../tests/oauth2/test_service_account.py | 17 +++ packages/google-auth/tests/test__default.py | 32 +++++ .../google-auth/tests/test_credentials.py | 5 + .../tests/test_external_account.py | 65 ++++++--- .../test_external_account_authorized_user.py | 16 +++ .../tests/test_impersonated_credentials.py | 17 +++ 17 files changed, 442 insertions(+), 123 deletions(-) diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index 63009dfb8626..7bbcf8591457 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -237,6 +237,7 @@ def _get_gcloud_sdk_credentials(quota_project_id=None): credentials, project_id = load_credentials_from_file( credentials_filename, quota_project_id=quota_project_id ) + credentials._cred_file_path = credentials_filename if not project_id: project_id = _cloud_sdk.get_project_id() @@ -270,6 +271,7 @@ def _get_explicit_environ_credentials(quota_project_id=None): credentials, project_id = load_credentials_from_file( os.environ[environment_vars.CREDENTIALS], quota_project_id=quota_project_id ) + credentials._cred_file_path = f"{explicit_file} file via the GOOGLE_APPLICATION_CREDENTIALS environment variable" return credentials, project_id diff --git a/packages/google-auth/google/auth/compute_engine/credentials.py b/packages/google-auth/google/auth/compute_engine/credentials.py index 008b991bb957..f0126c0a80ef 100644 --- a/packages/google-auth/google/auth/compute_engine/credentials.py +++ b/packages/google-auth/google/auth/compute_engine/credentials.py @@ -157,6 +157,14 @@ def universe_domain(self): self._universe_domain_cached = True return self._universe_domain + @_helpers.copy_docstring(credentials.Credentials) + def get_cred_info(self): + return { + "credential_source": "metadata server", + "credential_type": "VM credentials", + "principal": self.service_account_email, + } + @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) def with_quota_project(self, quota_project_id): creds = self.__class__( diff --git a/packages/google-auth/google/auth/credentials.py b/packages/google-auth/google/auth/credentials.py index e31930311be4..2c67e04432a4 100644 --- a/packages/google-auth/google/auth/credentials.py +++ b/packages/google-auth/google/auth/credentials.py @@ -128,6 +128,17 @@ def universe_domain(self): """The universe domain value.""" return self._universe_domain + def get_cred_info(self): + """The credential information JSON. + + The credential information will be added to auth related error messages + by client library. + + Returns: + Mapping[str, str]: The credential information JSON. + """ + return None + @abc.abstractmethod def refresh(self, request): """Refreshes the access token. diff --git a/packages/google-auth/google/auth/external_account.py b/packages/google-auth/google/auth/external_account.py index df0511f255e1..161e6c50ce32 100644 --- a/packages/google-auth/google/auth/external_account.py +++ b/packages/google-auth/google/auth/external_account.py @@ -186,6 +186,7 @@ def __init__( self._supplier_context = SupplierContext( self._subject_token_type, self._audience ) + self._cred_file_path = None if not self.is_workforce_pool and self._workforce_pool_user_project: # Workload identity pools do not support workforce pool user projects. @@ -321,11 +322,24 @@ def token_info_url(self): return self._token_info_url + @_helpers.copy_docstring(credentials.Credentials) + def get_cred_info(self): + if self._cred_file_path: + cred_info_json = { + "credential_source": self._cred_file_path, + "credential_type": "external account credentials", + } + if self.service_account_email: + cred_info_json["principal"] = self.service_account_email + return cred_info_json + return None + @_helpers.copy_docstring(credentials.Scoped) def with_scopes(self, scopes, default_scopes=None): kwargs = self._constructor_args() kwargs.update(scopes=scopes, default_scopes=default_scopes) scoped = self.__class__(**kwargs) + scoped._cred_file_path = self._cred_file_path scoped._metrics_options = self._metrics_options return scoped @@ -442,30 +456,31 @@ def refresh(self, request): self.expiry = now + lifetime - @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) - def with_quota_project(self, quota_project_id): - # Return copy of instance with the provided quota project ID. + def _make_copy(self): kwargs = self._constructor_args() - kwargs.update(quota_project_id=quota_project_id) new_cred = self.__class__(**kwargs) + new_cred._cred_file_path = self._cred_file_path new_cred._metrics_options = self._metrics_options return new_cred + @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) + def with_quota_project(self, quota_project_id): + # Return copy of instance with the provided quota project ID. + cred = self._make_copy() + cred._quota_project_id = quota_project_id + return cred + @_helpers.copy_docstring(credentials.CredentialsWithTokenUri) def with_token_uri(self, token_uri): - kwargs = self._constructor_args() - kwargs.update(token_url=token_uri) - new_cred = self.__class__(**kwargs) - new_cred._metrics_options = self._metrics_options - return new_cred + cred = self._make_copy() + cred._token_url = token_uri + return cred @_helpers.copy_docstring(credentials.CredentialsWithUniverseDomain) def with_universe_domain(self, universe_domain): - kwargs = self._constructor_args() - kwargs.update(universe_domain=universe_domain) - new_cred = self.__class__(**kwargs) - new_cred._metrics_options = self._metrics_options - return new_cred + cred = self._make_copy() + cred._universe_domain = universe_domain + return cred def _should_initialize_impersonated_credentials(self): return ( diff --git a/packages/google-auth/google/auth/external_account_authorized_user.py b/packages/google-auth/google/auth/external_account_authorized_user.py index f73387172c26..4d0c3c680697 100644 --- a/packages/google-auth/google/auth/external_account_authorized_user.py +++ b/packages/google-auth/google/auth/external_account_authorized_user.py @@ -120,6 +120,7 @@ def __init__( self._quota_project_id = quota_project_id self._scopes = scopes self._universe_domain = universe_domain or credentials.DEFAULT_UNIVERSE_DOMAIN + self._cred_file_path = None if not self.valid and not self.can_refresh: raise exceptions.InvalidOperation( @@ -290,23 +291,38 @@ def refresh(self, request): def _make_sts_request(self, request): return self._sts_client.refresh_token(request, self._refresh_token) + @_helpers.copy_docstring(credentials.Credentials) + def get_cred_info(self): + if self._cred_file_path: + return { + "credential_source": self._cred_file_path, + "credential_type": "external account authorized user credentials", + } + return None + + def _make_copy(self): + kwargs = self.constructor_args() + cred = self.__class__(**kwargs) + cred._cred_file_path = self._cred_file_path + return cred + @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) def with_quota_project(self, quota_project_id): - kwargs = self.constructor_args() - kwargs.update(quota_project_id=quota_project_id) - return self.__class__(**kwargs) + cred = self._make_copy() + cred._quota_project_id = quota_project_id + return cred @_helpers.copy_docstring(credentials.CredentialsWithTokenUri) def with_token_uri(self, token_uri): - kwargs = self.constructor_args() - kwargs.update(token_url=token_uri) - return self.__class__(**kwargs) + cred = self._make_copy() + cred._token_url = token_uri + return cred @_helpers.copy_docstring(credentials.CredentialsWithUniverseDomain) def with_universe_domain(self, universe_domain): - kwargs = self.constructor_args() - kwargs.update(universe_domain=universe_domain) - return self.__class__(**kwargs) + cred = self._make_copy() + cred._universe_domain = universe_domain + return cred @classmethod def from_info(cls, info, **kwargs): diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index 3c6f8712a9b8..c42a9364333a 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -226,6 +226,7 @@ def __init__( self.expiry = _helpers.utcnow() self._quota_project_id = quota_project_id self._iam_endpoint_override = iam_endpoint_override + self._cred_file_path = None def _metric_header_for_usage(self): return metrics.CRED_TYPE_SA_IMPERSONATE @@ -316,29 +317,40 @@ def signer(self): def requires_scopes(self): return not self._target_scopes - @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) - def with_quota_project(self, quota_project_id): - return self.__class__( + @_helpers.copy_docstring(credentials.Credentials) + def get_cred_info(self): + if self._cred_file_path: + return { + "credential_source": self._cred_file_path, + "credential_type": "impersonated credentials", + "principal": self._target_principal, + } + return None + + def _make_copy(self): + cred = self.__class__( self._source_credentials, target_principal=self._target_principal, target_scopes=self._target_scopes, delegates=self._delegates, lifetime=self._lifetime, - quota_project_id=quota_project_id, + quota_project_id=self._quota_project_id, iam_endpoint_override=self._iam_endpoint_override, ) + cred._cred_file_path = self._cred_file_path + return cred + + @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) + def with_quota_project(self, quota_project_id): + cred = self._make_copy() + cred._quota_project_id = quota_project_id + return cred @_helpers.copy_docstring(credentials.Scoped) def with_scopes(self, scopes, default_scopes=None): - return self.__class__( - self._source_credentials, - target_principal=self._target_principal, - target_scopes=scopes or default_scopes, - delegates=self._delegates, - lifetime=self._lifetime, - quota_project_id=self._quota_project_id, - iam_endpoint_override=self._iam_endpoint_override, - ) + cred = self._make_copy() + cred._target_scopes = scopes or default_scopes + return cred class IDTokenCredentials(credentials.CredentialsWithQuotaProject): diff --git a/packages/google-auth/google/oauth2/credentials.py b/packages/google-auth/google/oauth2/credentials.py index 5ca00d4c5a58..a478669cfc72 100644 --- a/packages/google-auth/google/oauth2/credentials.py +++ b/packages/google-auth/google/oauth2/credentials.py @@ -32,6 +32,7 @@ """ from datetime import datetime +import http.client as http_client import io import json import logging @@ -50,6 +51,9 @@ # The Google OAuth 2.0 token endpoint. Used for authorized user credentials. _GOOGLE_OAUTH2_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token" +# The Google OAuth 2.0 token info endpoint. Used for getting token info JSON from access tokens. +_GOOGLE_OAUTH2_TOKEN_INFO_ENDPOINT = "https://oauth2.googleapis.com/tokeninfo" + class Credentials(credentials.ReadOnlyScoped, credentials.CredentialsWithQuotaProject): """Credentials using OAuth 2.0 access and refresh tokens. @@ -151,6 +155,7 @@ def __init__( self._trust_boundary = trust_boundary self._universe_domain = universe_domain or credentials.DEFAULT_UNIVERSE_DOMAIN self._account = account or "" + self._cred_file_path = None def __getstate__(self): """A __getstate__ method must exist for the __setstate__ to be called @@ -189,6 +194,7 @@ def __setstate__(self, d): self._universe_domain = ( d.get("_universe_domain") or credentials.DEFAULT_UNIVERSE_DOMAIN ) + self._cred_file_path = d.get("_cred_file_path") # The refresh_handler setter should be used to repopulate this. self._refresh_handler = None self._refresh_worker = None @@ -278,10 +284,8 @@ def account(self): """str: The user account associated with the credential. If the account is unknown an empty string is returned.""" return self._account - @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) - def with_quota_project(self, quota_project_id): - - return self.__class__( + def _make_copy(self): + cred = self.__class__( self.token, refresh_token=self.refresh_token, id_token=self.id_token, @@ -291,34 +295,39 @@ def with_quota_project(self, quota_project_id): scopes=self.scopes, default_scopes=self.default_scopes, granted_scopes=self.granted_scopes, - quota_project_id=quota_project_id, + quota_project_id=self.quota_project_id, rapt_token=self.rapt_token, enable_reauth_refresh=self._enable_reauth_refresh, trust_boundary=self._trust_boundary, universe_domain=self._universe_domain, account=self._account, ) + cred._cred_file_path = self._cred_file_path + return cred + + @_helpers.copy_docstring(credentials.Credentials) + def get_cred_info(self): + if self._cred_file_path: + cred_info = { + "credential_source": self._cred_file_path, + "credential_type": "user credentials", + } + if self.account: + cred_info["principal"] = self.account + return cred_info + return None + + @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) + def with_quota_project(self, quota_project_id): + cred = self._make_copy() + cred._quota_project_id = quota_project_id + return cred @_helpers.copy_docstring(credentials.CredentialsWithTokenUri) def with_token_uri(self, token_uri): - - return self.__class__( - self.token, - refresh_token=self.refresh_token, - id_token=self.id_token, - token_uri=token_uri, - client_id=self.client_id, - client_secret=self.client_secret, - scopes=self.scopes, - default_scopes=self.default_scopes, - granted_scopes=self.granted_scopes, - quota_project_id=self.quota_project_id, - rapt_token=self.rapt_token, - enable_reauth_refresh=self._enable_reauth_refresh, - trust_boundary=self._trust_boundary, - universe_domain=self._universe_domain, - account=self._account, - ) + cred = self._make_copy() + cred._token_uri = token_uri + return cred def with_account(self, account): """Returns a copy of these credentials with a modified account. @@ -329,49 +338,46 @@ def with_account(self, account): Returns: google.oauth2.credentials.Credentials: A new credentials instance. """ - - return self.__class__( - self.token, - refresh_token=self.refresh_token, - id_token=self.id_token, - token_uri=self._token_uri, - client_id=self.client_id, - client_secret=self.client_secret, - scopes=self.scopes, - default_scopes=self.default_scopes, - granted_scopes=self.granted_scopes, - quota_project_id=self.quota_project_id, - rapt_token=self.rapt_token, - enable_reauth_refresh=self._enable_reauth_refresh, - trust_boundary=self._trust_boundary, - universe_domain=self._universe_domain, - account=account, - ) + cred = self._make_copy() + cred._account = account + return cred @_helpers.copy_docstring(credentials.CredentialsWithUniverseDomain) def with_universe_domain(self, universe_domain): - - return self.__class__( - self.token, - refresh_token=self.refresh_token, - id_token=self.id_token, - token_uri=self._token_uri, - client_id=self.client_id, - client_secret=self.client_secret, - scopes=self.scopes, - default_scopes=self.default_scopes, - granted_scopes=self.granted_scopes, - quota_project_id=self.quota_project_id, - rapt_token=self.rapt_token, - enable_reauth_refresh=self._enable_reauth_refresh, - trust_boundary=self._trust_boundary, - universe_domain=universe_domain, - account=self._account, - ) + cred = self._make_copy() + cred._universe_domain = universe_domain + return cred def _metric_header_for_usage(self): return metrics.CRED_TYPE_USER + def _set_account_from_access_token(self, request): + """Obtain the account from token info endpoint and set the account field. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + """ + # We only set the account if it's not yet set. + if self._account: + return + + if not self.token: + return + + # Make request to token info endpoint with the access token. + # If the token is invalid, it returns 400 error code. + # If the token is valid, it returns 200 status with a JSON. The account + # is the "email" field of the JSON. + token_info_url = "{}?access_token={}".format( + _GOOGLE_OAUTH2_TOKEN_INFO_ENDPOINT, self.token + ) + response = request(method="GET", url=token_info_url) + + if response.status == http_client.OK: + response_data = json.loads(response.data.decode("utf-8")) + self._account = response_data.get("email") + @_helpers.copy_docstring(credentials.Credentials) def refresh(self, request): if self._universe_domain != credentials.DEFAULT_UNIVERSE_DOMAIN: @@ -408,6 +414,7 @@ def refresh(self, request): ) self.token = token self.expiry = expiry + self._set_account_from_access_token(request) return if ( @@ -444,6 +451,7 @@ def refresh(self, request): self._refresh_token = refresh_token self._id_token = grant_response.get("id_token") self._rapt_token = rapt_token + self._set_account_from_access_token(request) if scopes and "scope" in grant_response: requested_scopes = frozenset(scopes) diff --git a/packages/google-auth/google/oauth2/service_account.py b/packages/google-auth/google/oauth2/service_account.py index 0e12868f14b5..98dafa3e3800 100644 --- a/packages/google-auth/google/oauth2/service_account.py +++ b/packages/google-auth/google/oauth2/service_account.py @@ -173,6 +173,7 @@ def __init__( """ super(Credentials, self).__init__() + self._cred_file_path = None self._scopes = scopes self._default_scopes = default_scopes self._signer = signer @@ -220,7 +221,7 @@ def _from_signer_and_info(cls, signer, info, **kwargs): "universe_domain", credentials.DEFAULT_UNIVERSE_DOMAIN ), trust_boundary=info.get("trust_boundary"), - **kwargs + **kwargs, ) @classmethod @@ -294,6 +295,7 @@ def _make_copy(self): always_use_jwt_access=self._always_use_jwt_access, universe_domain=self._universe_domain, ) + cred._cred_file_path = self._cred_file_path return cred @_helpers.copy_docstring(credentials.Scoped) @@ -503,6 +505,16 @@ def signer(self): def signer_email(self): return self._service_account_email + @_helpers.copy_docstring(credentials.Credentials) + def get_cred_info(self): + if self._cred_file_path: + return { + "credential_source": self._cred_file_path, + "credential_type": "service account credentials", + "principal": self.service_account_email, + } + return None + class IDTokenCredentials( credentials.Signing, diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 7a7c5248764a6c1a25b12dcb7cbbadad194b2a6e..c22d40f6adfb4c567bb21756a3be06c4191ec2e1 100644 GIT binary patch literal 10324 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 diff --git a/packages/google-auth/tests/compute_engine/test_credentials.py b/packages/google-auth/tests/compute_engine/test_credentials.py index bb29f8c6e2b6..662210fa412c 100644 --- a/packages/google-auth/tests/compute_engine/test_credentials.py +++ b/packages/google-auth/tests/compute_engine/test_credentials.py @@ -72,6 +72,13 @@ def credentials_fixture(self): universe_domain=FAKE_UNIVERSE_DOMAIN, ) + def test_get_cred_info(self): + assert self.credentials.get_cred_info() == { + "credential_source": "metadata server", + "credential_type": "VM credentials", + "principal": "default", + } + def test_default_state(self): assert not self.credentials.valid # Expiration hasn't been set yet diff --git a/packages/google-auth/tests/oauth2/test_credentials.py b/packages/google-auth/tests/oauth2/test_credentials.py index 2166419465d0..c63597f79641 100644 --- a/packages/google-auth/tests/oauth2/test_credentials.py +++ b/packages/google-auth/tests/oauth2/test_credentials.py @@ -71,6 +71,76 @@ def test_default_state(self): assert credentials.rapt_token == self.RAPT_TOKEN assert credentials.refresh_handler is None + def test__set_account_from_access_token_no_token(self): + credentials = self.make_credentials() + assert not credentials.token + assert not credentials.account + + credentials._set_account_from_access_token(mock.Mock()) + assert not credentials.account + + def test__set_account_from_access_token_account_already_set(self): + credentials = self.make_credentials() + credentials.token = "fake-token" + credentials._account = "fake-account" + + credentials._set_account_from_access_token(mock.Mock()) + assert credentials.account == "fake-account" + + def test__set_account_from_access_token_error_response(self): + credentials = self.make_credentials() + credentials.token = "fake-token" + assert not credentials.account + + mock_response = mock.Mock() + mock_response.status = 400 + mock_request = mock.Mock(return_value=mock_response) + credentials._set_account_from_access_token(mock_request) + assert not credentials.account + + def test__set_account_from_access_token_success(self): + credentials = self.make_credentials() + credentials.token = "fake-token" + assert not credentials.account + + mock_response = mock.Mock() + mock_response.status = 200 + mock_response.data = ( + b'{"aud": "aud", "sub": "sub", "scope": "scope", "email": "fake-account"}' + ) + + mock_request = mock.Mock(return_value=mock_response) + credentials._set_account_from_access_token(mock_request) + assert credentials.account == "fake-account" + + def test_get_cred_info(self): + credentials = self.make_credentials() + credentials._account = "fake-account" + assert not credentials.get_cred_info() + + credentials._cred_file_path = "/path/to/file" + assert credentials.get_cred_info() == { + "credential_source": "/path/to/file", + "credential_type": "user credentials", + "principal": "fake-account", + } + + def test_get_cred_info_no_account(self): + credentials = self.make_credentials() + assert not credentials.get_cred_info() + + credentials._cred_file_path = "/path/to/file" + assert credentials.get_cred_info() == { + "credential_source": "/path/to/file", + "credential_type": "user credentials", + } + + def test__make_copy_get_cred_info(self): + credentials = self.make_credentials() + credentials._cred_file_path = "/path/to/file" + cred_copy = credentials._make_copy() + assert cred_copy._cred_file_path == "/path/to/file" + def test_token_usage_metrics(self): credentials = self.make_credentials() credentials.token = "token" @@ -135,12 +205,15 @@ def test_refresh_with_non_default_universe_domain(self): "refresh is only supported in the default googleapis.com universe domain" ) + @mock.patch.object( + credentials.Credentials, "_set_account_from_access_token", autospec=True + ) @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD, ) - def test_refresh_success(self, unused_utcnow, refresh_grant): + def test_refresh_success(self, unused_utcnow, refresh_grant, set_account): token = "token" new_rapt_token = "new_rapt_token" expiry = _helpers.utcnow() + datetime.timedelta(seconds=500) @@ -186,6 +259,8 @@ def test_refresh_success(self, unused_utcnow, refresh_grant): # expired) assert credentials.valid + set_account.assert_called_once() + def test_refresh_no_refresh_token(self): request = mock.create_autospec(transport.Request) credentials_ = credentials.Credentials(token=None, refresh_token=None) @@ -195,13 +270,16 @@ def test_refresh_no_refresh_token(self): request.assert_not_called() + @mock.patch.object( + credentials.Credentials, "_set_account_from_access_token", autospec=True + ) @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD, ) def test_refresh_with_refresh_token_and_refresh_handler( - self, unused_utcnow, refresh_grant + self, unused_utcnow, refresh_grant, set_account ): token = "token" new_rapt_token = "new_rapt_token" @@ -261,8 +339,15 @@ def test_refresh_with_refresh_token_and_refresh_handler( # higher priority. refresh_handler.assert_not_called() + set_account.assert_called_once() + + @mock.patch.object( + credentials.Credentials, "_set_account_from_access_token", autospec=True + ) @mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) - def test_refresh_with_refresh_handler_success_scopes(self, unused_utcnow): + def test_refresh_with_refresh_handler_success_scopes( + self, unused_utcnow, set_account + ): expected_expiry = datetime.datetime.min + datetime.timedelta(seconds=2800) refresh_handler = mock.Mock(return_value=("ACCESS_TOKEN", expected_expiry)) scopes = ["email", "profile"] @@ -286,11 +371,17 @@ def test_refresh_with_refresh_handler_success_scopes(self, unused_utcnow): assert creds.expiry == expected_expiry assert creds.valid assert not creds.expired + set_account.assert_called_once() # Confirm refresh handler called with the expected arguments. refresh_handler.assert_called_with(request, scopes=scopes) + @mock.patch.object( + credentials.Credentials, "_set_account_from_access_token", autospec=True + ) @mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) - def test_refresh_with_refresh_handler_success_default_scopes(self, unused_utcnow): + def test_refresh_with_refresh_handler_success_default_scopes( + self, unused_utcnow, set_account + ): expected_expiry = datetime.datetime.min + datetime.timedelta(seconds=2800) original_refresh_handler = mock.Mock( return_value=("UNUSED_TOKEN", expected_expiry) @@ -318,6 +409,7 @@ def test_refresh_with_refresh_handler_success_default_scopes(self, unused_utcnow assert creds.expiry == expected_expiry assert creds.valid assert not creds.expired + set_account.assert_called_once() # default_scopes should be used since no developer provided scopes # are provided. refresh_handler.assert_called_with(request, scopes=default_scopes) @@ -411,13 +503,16 @@ def test_refresh_with_refresh_handler_expired_token(self, unused_utcnow): # Confirm refresh handler called with the expected arguments. refresh_handler.assert_called_with(request, scopes=scopes) + @mock.patch.object( + credentials.Credentials, "_set_account_from_access_token", autospec=True + ) @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD, ) def test_credentials_with_scopes_requested_refresh_success( - self, unused_utcnow, refresh_grant + self, unused_utcnow, refresh_grant, set_account ): scopes = ["email", "profile"] default_scopes = ["https://www.googleapis.com/auth/cloud-platform"] @@ -473,18 +568,22 @@ def test_credentials_with_scopes_requested_refresh_success( assert creds.has_scopes(scopes) assert creds.rapt_token == new_rapt_token assert creds.granted_scopes == scopes + set_account.assert_called_once() # Check that the credentials are valid (have a token and are not # expired.) assert creds.valid + @mock.patch.object( + credentials.Credentials, "_set_account_from_access_token", autospec=True + ) @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD, ) def test_credentials_with_only_default_scopes_requested( - self, unused_utcnow, refresh_grant + self, unused_utcnow, refresh_grant, set_account ): default_scopes = ["email", "profile"] token = "token" @@ -538,18 +637,22 @@ def test_credentials_with_only_default_scopes_requested( assert creds.has_scopes(default_scopes) assert creds.rapt_token == new_rapt_token assert creds.granted_scopes == default_scopes + set_account.assert_called_once() # Check that the credentials are valid (have a token and are not # expired.) assert creds.valid + @mock.patch.object( + credentials.Credentials, "_set_account_from_access_token", autospec=True + ) @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD, ) def test_credentials_with_scopes_returned_refresh_success( - self, unused_utcnow, refresh_grant + self, unused_utcnow, refresh_grant, set_account ): scopes = ["email", "profile"] token = "token" @@ -603,18 +706,22 @@ def test_credentials_with_scopes_returned_refresh_success( assert creds.has_scopes(scopes) assert creds.rapt_token == new_rapt_token assert creds.granted_scopes == scopes + set_account.assert_called_once() # Check that the credentials are valid (have a token and are not # expired.) assert creds.valid + @mock.patch.object( + credentials.Credentials, "_set_account_from_access_token", autospec=True + ) @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD, ) def test_credentials_with_only_default_scopes_requested_different_granted_scopes( - self, unused_utcnow, refresh_grant + self, unused_utcnow, refresh_grant, set_account ): default_scopes = ["email", "profile"] token = "token" @@ -668,18 +775,22 @@ def test_credentials_with_only_default_scopes_requested_different_granted_scopes assert creds.has_scopes(default_scopes) assert creds.rapt_token == new_rapt_token assert creds.granted_scopes == ["email"] + set_account.assert_called_once() # Check that the credentials are valid (have a token and are not # expired.) assert creds.valid + @mock.patch.object( + credentials.Credentials, "_set_account_from_access_token", autospec=True + ) @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD, ) def test_credentials_with_scopes_refresh_different_granted_scopes( - self, unused_utcnow, refresh_grant + self, unused_utcnow, refresh_grant, set_account ): scopes = ["email", "profile"] scopes_returned = ["email"] @@ -737,6 +848,7 @@ def test_credentials_with_scopes_refresh_different_granted_scopes( assert creds.has_scopes(scopes) assert creds.rapt_token == new_rapt_token assert creds.granted_scopes == scopes_returned + set_account.assert_called_once() # Check that the credentials are valid (have a token and are not # expired.) diff --git a/packages/google-auth/tests/oauth2/test_service_account.py b/packages/google-auth/tests/oauth2/test_service_account.py index f16a43fb9ee0..2c3fea5b20d8 100644 --- a/packages/google-auth/tests/oauth2/test_service_account.py +++ b/packages/google-auth/tests/oauth2/test_service_account.py @@ -68,6 +68,23 @@ def make_credentials(cls, universe_domain=DEFAULT_UNIVERSE_DOMAIN): universe_domain=universe_domain, ) + def test_get_cred_info(self): + credentials = self.make_credentials() + assert not credentials.get_cred_info() + + credentials._cred_file_path = "/path/to/file" + assert credentials.get_cred_info() == { + "credential_source": "/path/to/file", + "credential_type": "service account credentials", + "principal": "service-account@example.com", + } + + def test__make_copy_get_cred_info(self): + credentials = self.make_credentials() + credentials._cred_file_path = "/path/to/file" + cred_copy = credentials._make_copy() + assert cred_copy._cred_file_path == "/path/to/file" + def test_constructor_no_universe_domain(self): credentials = service_account.Credentials( SIGNER, self.SERVICE_ACCOUNT_EMAIL, self.TOKEN_URI, universe_domain=None diff --git a/packages/google-auth/tests/test__default.py b/packages/google-auth/tests/test__default.py index cb9a7c1304fa..d17c747af171 100644 --- a/packages/google-auth/tests/test__default.py +++ b/packages/google-auth/tests/test__default.py @@ -882,6 +882,38 @@ def test_default_early_out(unused_get): assert _default.default() == (MOCK_CREDENTIALS, mock.sentinel.project_id) +@mock.patch( + "google.auth._default.load_credentials_from_file", + return_value=(MOCK_CREDENTIALS, mock.sentinel.project_id), + autospec=True, +) +def test_default_cred_file_path_env_var(unused_load_cred, monkeypatch): + monkeypatch.setenv(environment_vars.CREDENTIALS, "/path/to/file") + cred, _ = _default.default() + assert ( + cred._cred_file_path + == "/path/to/file file via the GOOGLE_APPLICATION_CREDENTIALS environment variable" + ) + + +@mock.patch("os.path.isfile", return_value=True, autospec=True) +@mock.patch( + "google.auth._cloud_sdk.get_application_default_credentials_path", + return_value="/path/to/adc/file", + autospec=True, +) +@mock.patch( + "google.auth._default.load_credentials_from_file", + return_value=(MOCK_CREDENTIALS, mock.sentinel.project_id), + autospec=True, +) +def test_default_cred_file_path_gcloud( + unused_load_cred, unused_get_adc_file, unused_isfile +): + cred, _ = _default.default() + assert cred._cred_file_path == "/path/to/adc/file" + + @mock.patch( "google.auth._default._get_explicit_environ_credentials", return_value=(MOCK_CREDENTIALS, mock.sentinel.project_id), diff --git a/packages/google-auth/tests/test_credentials.py b/packages/google-auth/tests/test_credentials.py index 8e6bbc96330c..e11bcb4e551f 100644 --- a/packages/google-auth/tests/test_credentials.py +++ b/packages/google-auth/tests/test_credentials.py @@ -52,6 +52,11 @@ def test_credentials_constructor(): assert not credentials._use_non_blocking_refresh +def test_credentials_get_cred_info(): + credentials = CredentialsImpl() + assert not credentials.get_cred_info() + + def test_with_non_blocking_refresh(): c = CredentialsImpl() c.with_non_blocking_refresh() diff --git a/packages/google-auth/tests/test_external_account.py b/packages/google-auth/tests/test_external_account.py index 3c372e6291c8..bddcb4afa1af 100644 --- a/packages/google-auth/tests/test_external_account.py +++ b/packages/google-auth/tests/test_external_account.py @@ -275,6 +275,31 @@ def assert_resource_manager_request_kwargs( assert request_kwargs["headers"] == headers assert "body" not in request_kwargs + def test_get_cred_info(self): + credentials = self.make_credentials() + assert not credentials.get_cred_info() + + credentials._cred_file_path = "/path/to/file" + assert credentials.get_cred_info() == { + "credential_source": "/path/to/file", + "credential_type": "external account credentials", + } + + credentials._service_account_impersonation_url = ( + self.SERVICE_ACCOUNT_IMPERSONATION_URL + ) + assert credentials.get_cred_info() == { + "credential_source": "/path/to/file", + "credential_type": "external account credentials", + "principal": SERVICE_ACCOUNT_EMAIL, + } + + def test__make_copy_get_cred_info(self): + credentials = self.make_credentials() + credentials._cred_file_path = "/path/to/file" + cred_copy = credentials._make_copy() + assert cred_copy._cred_file_path == "/path/to/file" + def test_default_state(self): credentials = self.make_credentials( service_account_impersonation_url=self.SERVICE_ACCOUNT_IMPERSONATION_URL @@ -469,25 +494,29 @@ def test_with_quota_project_full_options_propagated(self): with mock.patch.object( external_account.Credentials, "__init__", return_value=None ) as mock_init: - credentials.with_quota_project("project-foo") + new_cred = credentials.with_quota_project("project-foo") - # Confirm with_quota_project initialized the credential with the - # expected parameters and quota project ID. - mock_init.assert_called_once_with( - audience=self.AUDIENCE, - subject_token_type=self.SUBJECT_TOKEN_TYPE, - token_url=self.TOKEN_URL, - token_info_url=self.TOKEN_INFO_URL, - credential_source=self.CREDENTIAL_SOURCE, - service_account_impersonation_url=self.SERVICE_ACCOUNT_IMPERSONATION_URL, - service_account_impersonation_options={"token_lifetime_seconds": 2800}, - client_id=CLIENT_ID, - client_secret=CLIENT_SECRET, - quota_project_id="project-foo", - scopes=self.SCOPES, - default_scopes=["default1"], - universe_domain=DEFAULT_UNIVERSE_DOMAIN, - ) + # Confirm with_quota_project initialized the credential with the + # expected parameters. + mock_init.assert_called_once_with( + audience=self.AUDIENCE, + subject_token_type=self.SUBJECT_TOKEN_TYPE, + token_url=self.TOKEN_URL, + token_info_url=self.TOKEN_INFO_URL, + credential_source=self.CREDENTIAL_SOURCE, + service_account_impersonation_url=self.SERVICE_ACCOUNT_IMPERSONATION_URL, + service_account_impersonation_options={"token_lifetime_seconds": 2800}, + client_id=CLIENT_ID, + client_secret=CLIENT_SECRET, + quota_project_id=self.QUOTA_PROJECT_ID, + scopes=self.SCOPES, + default_scopes=["default1"], + universe_domain=DEFAULT_UNIVERSE_DOMAIN, + ) + + # Confirm with_quota_project sets the correct quota project after + # initialization. + assert new_cred.quota_project_id == "project-foo" def test_info(self): credentials = self.make_credentials(universe_domain="dummy_universe.com") diff --git a/packages/google-auth/tests/test_external_account_authorized_user.py b/packages/google-auth/tests/test_external_account_authorized_user.py index 743ee9c848d7..93926a1314c6 100644 --- a/packages/google-auth/tests/test_external_account_authorized_user.py +++ b/packages/google-auth/tests/test_external_account_authorized_user.py @@ -83,6 +83,22 @@ def make_mock_request(cls, status=http_client.OK, data=None): return request + def test_get_cred_info(self): + credentials = self.make_credentials() + assert not credentials.get_cred_info() + + credentials._cred_file_path = "/path/to/file" + assert credentials.get_cred_info() == { + "credential_source": "/path/to/file", + "credential_type": "external account authorized user credentials", + } + + def test__make_copy_get_cred_info(self): + credentials = self.make_credentials() + credentials._cred_file_path = "/path/to/file" + cred_copy = credentials._make_copy() + assert cred_copy._cred_file_path == "/path/to/file" + def test_default_state(self): creds = self.make_credentials() diff --git a/packages/google-auth/tests/test_impersonated_credentials.py b/packages/google-auth/tests/test_impersonated_credentials.py index a2bf31bf858e..83e2606384f2 100644 --- a/packages/google-auth/tests/test_impersonated_credentials.py +++ b/packages/google-auth/tests/test_impersonated_credentials.py @@ -135,6 +135,23 @@ def make_credentials( iam_endpoint_override=iam_endpoint_override, ) + def test_get_cred_info(self): + credentials = self.make_credentials() + assert not credentials.get_cred_info() + + credentials._cred_file_path = "/path/to/file" + assert credentials.get_cred_info() == { + "credential_source": "/path/to/file", + "credential_type": "impersonated credentials", + "principal": "impersonated@project.iam.gserviceaccount.com", + } + + def test__make_copy_get_cred_info(self): + credentials = self.make_credentials() + credentials._cred_file_path = "/path/to/file" + cred_copy = credentials._make_copy() + assert cred_copy._cred_file_path == "/path/to/file" + def test_make_from_user_credentials(self): credentials = self.make_credentials( source_credentials=self.USER_SOURCE_CREDENTIALS From 0d38ef83b59a6a1bd089f3209487d5fadeddc726 Mon Sep 17 00:00:00 2001 From: ohmayr Date: Mon, 16 Sep 2024 17:44:27 -0400 Subject: [PATCH 859/966] feat: Add support for asynchronous `AuthorizedSession` api (#1577) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: initial setup for async auth sessions api (#1571) * chore: initial setup for async auth sessions api * fix whitespace * add init file * update file names to aiohttp * update import statement * feat: Implement asynchronous timeout context manager (#1569) * feat: implement async timeout guard * add docstring * clean whitespace * update import file name * add missing return statement * update test cases * update test cases * include underlying timeout exception in trace * avoid the cost of actual time * feat: Implement asynchronous `AuthorizedSession` api response class (#1575) * feat: implement asynchronous response class for AuthorizedSessions API * check if aiohttp is installed and avoid tests dependency * update content to be async * update docstring to be specific to aiohttp * add type checking and avoid leaking underlying API responses * add test case for iterating chunks * add read method to response interface * address PR comments * fix lint issues * feat: Implement asynchronous `AuthorizedSession` api request class (#1579) * feat: implement request class for asynchoronous AuthorizedSession API * add type checking and address TODOs * remove default values from interface methods * aiohttp reponse close method must not be awaited * cleanup * update Request class docstring * feat: Implement asynchronous `AuthorizedSession` class (#1580) * feat: Implement Asynchronous AuthorizedSession class * add comment for implementing locks within refresh * move timeout guard to sessions * add unit tests and code cleanup * implement async exponential backoff iterator * cleanup * add testing for http methods and cleanup * update number of retries to 3 * refactor test cases * fix linter and mypy issues * fix pytest code coverage * fix: avoid leaking api error for closed session * add error handling for response * cleanup default values and add test coverage * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * cleanup: minor code cleanup (#1589) * chore: Add aiohttp requirements test constraint. (#1566) See https://github.com/googleapis/google-auth-library-python/issues/1565 for more information. * chore(main): release 2.33.0 (#1560) * chore(main): release 2.33.0 * fix: retry token request on retryable status code (#1563) * fix: retry token request on retryable status code * feat(auth): Update get_client_ssl_credentials to support X.509 workload certs (#1558) * feat(auth): Update get_client_ssl_credentials to support X.509 workload certs * feat(auth): Update has_default_client_cert_source * feat(auth): Fix formatting * feat(auth): Fix test__mtls_helper.py * feat(auth): Fix function name in tests * chore: Refresh system test creds. * feat(auth): Fix style * feat(auth): Fix casing * feat(auth): Fix linter issue * feat(auth): Fix coverage issue --------- Co-authored-by: Carl Lundin Co-authored-by: Carl Lundin <108372512+clundin25@users.noreply.github.com> * chore: Update ECP deps. (#1583) * chore(main): release 2.34.0 (#1574) * cleanup: minor code cleanup * fix lint issues --------- Co-authored-by: Carl Lundin <108372512+clundin25@users.noreply.github.com> Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> Co-authored-by: Andy Zhao Co-authored-by: Carl Lundin * update secrets from forked repo --------- Co-authored-by: Owl Bot Co-authored-by: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Co-authored-by: Carl Lundin <108372512+clundin25@users.noreply.github.com> Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> Co-authored-by: Andy Zhao Co-authored-by: Carl Lundin --- .../google/auth/_exponential_backoff.py | 77 ++++- .../google/auth/aio/transport/__init__.py | 144 ++++++++ .../google/auth/aio/transport/aiohttp.py | 184 +++++++++++ .../google/auth/aio/transport/sessions.py | 268 +++++++++++++++ .../google-auth/google/auth/exceptions.py | 8 + .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../tests/test__exponential_backoff.py | 41 +++ .../tests/transport/aio/test_aiohttp.py | 170 ++++++++++ .../tests/transport/aio/test_sessions.py | 311 ++++++++++++++++++ 9 files changed, 1187 insertions(+), 16 deletions(-) create mode 100644 packages/google-auth/google/auth/aio/transport/__init__.py create mode 100644 packages/google-auth/google/auth/aio/transport/aiohttp.py create mode 100644 packages/google-auth/google/auth/aio/transport/sessions.py create mode 100644 packages/google-auth/tests/transport/aio/test_aiohttp.py create mode 100644 packages/google-auth/tests/transport/aio/test_sessions.py diff --git a/packages/google-auth/google/auth/_exponential_backoff.py b/packages/google-auth/google/auth/_exponential_backoff.py index 04f9f9764120..89853448f9fc 100644 --- a/packages/google-auth/google/auth/_exponential_backoff.py +++ b/packages/google-auth/google/auth/_exponential_backoff.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import asyncio import random import time @@ -38,9 +39,8 @@ """ -class ExponentialBackoff: - """An exponential backoff iterator. This can be used in a for loop to - perform requests with exponential backoff. +class _BaseExponentialBackoff: + """An exponential backoff iterator base class. Args: total_attempts Optional[int]: @@ -84,9 +84,40 @@ def __init__( self._multiplier = multiplier self._backoff_count = 0 - def __iter__(self): + @property + def total_attempts(self): + """The total amount of backoff attempts that will be made.""" + return self._total_attempts + + @property + def backoff_count(self): + """The current amount of backoff attempts that have been made.""" + return self._backoff_count + + def _reset(self): self._backoff_count = 0 self._current_wait_in_seconds = self._initial_wait_seconds + + def _calculate_jitter(self): + jitter_variance = self._current_wait_in_seconds * self._randomization_factor + jitter = random.uniform( + self._current_wait_in_seconds - jitter_variance, + self._current_wait_in_seconds + jitter_variance, + ) + + return jitter + + +class ExponentialBackoff(_BaseExponentialBackoff): + """An exponential backoff iterator. This can be used in a for loop to + perform requests with exponential backoff. + """ + + def __init__(self, *args, **kwargs): + super(ExponentialBackoff, self).__init__(*args, **kwargs) + + def __iter__(self): + self._reset() return self def __next__(self): @@ -97,23 +128,37 @@ def __next__(self): if self._backoff_count <= 1: return self._backoff_count - jitter_variance = self._current_wait_in_seconds * self._randomization_factor - jitter = random.uniform( - self._current_wait_in_seconds - jitter_variance, - self._current_wait_in_seconds + jitter_variance, - ) + jitter = self._calculate_jitter() time.sleep(jitter) self._current_wait_in_seconds *= self._multiplier return self._backoff_count - @property - def total_attempts(self): - """The total amount of backoff attempts that will be made.""" - return self._total_attempts - @property - def backoff_count(self): - """The current amount of backoff attempts that have been made.""" +class AsyncExponentialBackoff(_BaseExponentialBackoff): + """An async exponential backoff iterator. This can be used in a for loop to + perform async requests with exponential backoff. + """ + + def __init__(self, *args, **kwargs): + super(AsyncExponentialBackoff, self).__init__(*args, **kwargs) + + def __aiter__(self): + self._reset() + return self + + async def __anext__(self): + if self._backoff_count >= self._total_attempts: + raise StopAsyncIteration + self._backoff_count += 1 + + if self._backoff_count <= 1: + return self._backoff_count + + jitter = self._calculate_jitter() + + await asyncio.sleep(jitter) + + self._current_wait_in_seconds *= self._multiplier return self._backoff_count diff --git a/packages/google-auth/google/auth/aio/transport/__init__.py b/packages/google-auth/google/auth/aio/transport/__init__.py new file mode 100644 index 000000000000..166a3be50914 --- /dev/null +++ b/packages/google-auth/google/auth/aio/transport/__init__.py @@ -0,0 +1,144 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Transport - Asynchronous HTTP client library support. + +:mod:`google.auth.aio` is designed to work with various asynchronous client libraries such +as aiohttp. In order to work across these libraries with different +interfaces some abstraction is needed. + +This module provides two interfaces that are implemented by transport adapters +to support HTTP libraries. :class:`Request` defines the interface expected by +:mod:`google.auth` to make asynchronous requests. :class:`Response` defines the interface +for the return value of :class:`Request`. +""" + +import abc +from typing import AsyncGenerator, Mapping, Optional + +import google.auth.transport + + +_DEFAULT_TIMEOUT_SECONDS = 180 + +DEFAULT_RETRYABLE_STATUS_CODES = google.auth.transport.DEFAULT_RETRYABLE_STATUS_CODES +"""Sequence[int]: HTTP status codes indicating a request can be retried. +""" + + +DEFAULT_MAX_RETRY_ATTEMPTS = 3 +"""int: How many times to retry a request.""" + + +class Response(metaclass=abc.ABCMeta): + """Asynchronous HTTP Response Interface.""" + + @property + @abc.abstractmethod + def status_code(self) -> int: + """ + The HTTP response status code. + + Returns: + int: The HTTP response status code. + + """ + raise NotImplementedError("status_code must be implemented.") + + @property + @abc.abstractmethod + def headers(self) -> Mapping[str, str]: + """The HTTP response headers. + + Returns: + Mapping[str, str]: The HTTP response headers. + """ + raise NotImplementedError("headers must be implemented.") + + @abc.abstractmethod + async def content(self, chunk_size: int) -> AsyncGenerator[bytes, None]: + """The raw response content. + + Args: + chunk_size (int): The size of each chunk. + + Yields: + AsyncGenerator[bytes, None]: An asynchronous generator yielding + response chunks as bytes. + """ + raise NotImplementedError("content must be implemented.") + + @abc.abstractmethod + async def read(self) -> bytes: + """Read the entire response content as bytes. + + Returns: + bytes: The entire response content. + """ + raise NotImplementedError("read must be implemented.") + + @abc.abstractmethod + async def close(self): + """Close the response after it is fully consumed to resource.""" + raise NotImplementedError("close must be implemented.") + + +class Request(metaclass=abc.ABCMeta): + """Interface for a callable that makes HTTP requests. + + Specific transport implementations should provide an implementation of + this that adapts their specific request / response API. + + .. automethod:: __call__ + """ + + @abc.abstractmethod + async def __call__( + self, + url: str, + method: str, + body: Optional[bytes], + headers: Optional[Mapping[str, str]], + timeout: float, + **kwargs + ) -> Response: + """Make an HTTP request. + + Args: + url (str): The URI to be requested. + method (str): The HTTP method to use for the request. Defaults + to 'GET'. + body (Optional[bytes]): The payload / body in HTTP request. + headers (Mapping[str, str]): Request headers. + timeout (float): The number of seconds to wait for a + response from the server. If not specified or if None, the + transport-specific default timeout will be used. + kwargs: Additional arguments passed on to the transport's + request method. + + Returns: + google.auth.aio.transport.Response: The HTTP response. + + Raises: + google.auth.exceptions.TransportError: If any exception occurred. + """ + # pylint: disable=redundant-returns-doc, missing-raises-doc + # (pylint doesn't play well with abstract docstrings.) + raise NotImplementedError("__call__ must be implemented.") + + async def close(self) -> None: + """ + Close the underlying session. + """ + raise NotImplementedError("close must be implemented.") diff --git a/packages/google-auth/google/auth/aio/transport/aiohttp.py b/packages/google-auth/google/auth/aio/transport/aiohttp.py new file mode 100644 index 000000000000..074d1491c700 --- /dev/null +++ b/packages/google-auth/google/auth/aio/transport/aiohttp.py @@ -0,0 +1,184 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Transport adapter for Asynchronous HTTP Requests based on aiohttp. +""" + +import asyncio +from typing import AsyncGenerator, Mapping, Optional + +try: + import aiohttp # type: ignore +except ImportError as caught_exc: # pragma: NO COVER + raise ImportError( + "The aiohttp library is not installed from please install the aiohttp package to use the aiohttp transport." + ) from caught_exc + +from google.auth import _helpers +from google.auth import exceptions +from google.auth.aio import transport + + +class Response(transport.Response): + """ + Represents an HTTP response and its data. It is returned by ``google.auth.aio.transport.sessions.AsyncAuthorizedSession``. + + Args: + response (aiohttp.ClientResponse): An instance of aiohttp.ClientResponse. + + Attributes: + status_code (int): The HTTP status code of the response. + headers (Mapping[str, str]): The HTTP headers of the response. + """ + + def __init__(self, response: aiohttp.ClientResponse): + self._response = response + + @property + @_helpers.copy_docstring(transport.Response) + def status_code(self) -> int: + return self._response.status + + @property + @_helpers.copy_docstring(transport.Response) + def headers(self) -> Mapping[str, str]: + return {key: value for key, value in self._response.headers.items()} + + @_helpers.copy_docstring(transport.Response) + async def content(self, chunk_size: int = 1024) -> AsyncGenerator[bytes, None]: + try: + async for chunk in self._response.content.iter_chunked( + chunk_size + ): # pragma: no branch + yield chunk + except aiohttp.ClientPayloadError as exc: + raise exceptions.ResponseError( + "Failed to read from the payload stream." + ) from exc + + @_helpers.copy_docstring(transport.Response) + async def read(self) -> bytes: + try: + return await self._response.read() + except aiohttp.ClientResponseError as exc: + raise exceptions.ResponseError("Failed to read the response body.") from exc + + @_helpers.copy_docstring(transport.Response) + async def close(self): + self._response.close() + + +class Request(transport.Request): + """Asynchronous Requests request adapter. + + This class is used internally for making requests using aiohttp + in a consistent way. If you use :class:`google.auth.aio.transport.sessions.AsyncAuthorizedSession` + you do not need to construct or use this class directly. + + This class can be useful if you want to configure a Request callable + with a custom ``aiohttp.ClientSession`` in :class:`AuthorizedSession` or if + you want to manually refresh a :class:`~google.auth.aio.credentials.Credentials` instance:: + + import aiohttp + import google.auth.aio.transport.aiohttp + + # Default example: + request = google.auth.aio.transport.aiohttp.Request() + await credentials.refresh(request) + + # Custom aiohttp Session Example: + session = session=aiohttp.ClientSession(auto_decompress=False) + request = google.auth.aio.transport.aiohttp.Request(session=session) + auth_sesion = google.auth.aio.transport.sessions.AsyncAuthorizedSession(auth_request=request) + + Args: + session (aiohttp.ClientSession): An instance :class:`aiohttp.ClientSession` used + to make HTTP requests. If not specified, a session will be created. + + .. automethod:: __call__ + """ + + def __init__(self, session: aiohttp.ClientSession = None): + self._session = session + self._closed = False + + async def __call__( + self, + url: str, + method: str = "GET", + body: Optional[bytes] = None, + headers: Optional[Mapping[str, str]] = None, + timeout: float = transport._DEFAULT_TIMEOUT_SECONDS, + **kwargs, + ) -> transport.Response: + """ + Make an HTTP request using aiohttp. + + Args: + url (str): The URL to be requested. + method (Optional[str]): + The HTTP method to use for the request. Defaults to 'GET'. + body (Optional[bytes]): + The payload or body in HTTP request. + headers (Optional[Mapping[str, str]]): + Request headers. + timeout (float): The number of seconds to wait for a + response from the server. If not specified or if None, the + requests default timeout will be used. + kwargs: Additional arguments passed through to the underlying + aiohttp :meth:`aiohttp.Session.request` method. + + Returns: + google.auth.aio.transport.Response: The HTTP response. + + Raises: + - google.auth.exceptions.TransportError: If the request fails or if the session is closed. + - google.auth.exceptions.TimeoutError: If the request times out. + """ + + try: + if self._closed: + raise exceptions.TransportError("session is closed.") + + if not self._session: + self._session = aiohttp.ClientSession() + + client_timeout = aiohttp.ClientTimeout(total=timeout) + response = await self._session.request( + method, + url, + data=body, + headers=headers, + timeout=client_timeout, + **kwargs, + ) + return Response(response) + + except aiohttp.ClientError as caught_exc: + client_exc = exceptions.TransportError(f"Failed to send request to {url}.") + raise client_exc from caught_exc + + except asyncio.TimeoutError as caught_exc: + timeout_exc = exceptions.TimeoutError( + f"Request timed out after {timeout} seconds." + ) + raise timeout_exc from caught_exc + + async def close(self) -> None: + """ + Close the underlying aiohttp session to release the acquired resources. + """ + if not self._closed and self._session: + await self._session.close() + self._closed = True diff --git a/packages/google-auth/google/auth/aio/transport/sessions.py b/packages/google-auth/google/auth/aio/transport/sessions.py new file mode 100644 index 000000000000..fea7cbbb2c3d --- /dev/null +++ b/packages/google-auth/google/auth/aio/transport/sessions.py @@ -0,0 +1,268 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import asyncio +from contextlib import asynccontextmanager +import functools +import time +from typing import Mapping, Optional + +from google.auth import _exponential_backoff, exceptions +from google.auth.aio import transport +from google.auth.aio.credentials import Credentials +from google.auth.exceptions import TimeoutError + +try: + from google.auth.aio.transport.aiohttp import Request as AiohttpRequest + + AIOHTTP_INSTALLED = True +except ImportError: # pragma: NO COVER + AIOHTTP_INSTALLED = False + + +@asynccontextmanager +async def timeout_guard(timeout): + """ + timeout_guard is an asynchronous context manager to apply a timeout to an asynchronous block of code. + + Args: + timeout (float): The time in seconds before the context manager times out. + + Raises: + google.auth.exceptions.TimeoutError: If the code within the context exceeds the provided timeout. + + Usage: + async with timeout_guard(10) as with_timeout: + await with_timeout(async_function()) + """ + start = time.monotonic() + total_timeout = timeout + + def _remaining_time(): + elapsed = time.monotonic() - start + remaining = total_timeout - elapsed + if remaining <= 0: + raise TimeoutError( + f"Context manager exceeded the configured timeout of {total_timeout}s." + ) + return remaining + + async def with_timeout(coro): + try: + remaining = _remaining_time() + response = await asyncio.wait_for(coro, remaining) + return response + except (asyncio.TimeoutError, TimeoutError) as e: + raise TimeoutError( + f"The operation {coro} exceeded the configured timeout of {total_timeout}s." + ) from e + + try: + yield with_timeout + + finally: + _remaining_time() + + +class AsyncAuthorizedSession: + """This is an asynchronous implementation of :class:`google.auth.requests.AuthorizedSession` class. + We utilize an instance of a class that implements :class:`google.auth.aio.transport.Request` configured + by the caller or otherwise default to `google.auth.aio.transport.aiohttp.Request` if the external aiohttp + package is installed. + + A Requests Session class with credentials. + + This class is used to perform asynchronous requests to API endpoints that require + authorization:: + + import aiohttp + from google.auth.aio.transport import sessions + + async with sessions.AsyncAuthorizedSession(credentials) as authed_session: + response = await authed_session.request( + 'GET', 'https://www.googleapis.com/storage/v1/b') + + The underlying :meth:`request` implementation handles adding the + credentials' headers to the request and refreshing credentials as needed. + + Args: + credentials (google.auth.aio.credentials.Credentials): + The credentials to add to the request. + auth_request (Optional[google.auth.aio.transport.Request]): + An instance of a class that implements + :class:`~google.auth.aio.transport.Request` used to make requests + and refresh credentials. If not passed, + an instance of :class:`~google.auth.aio.transport.aiohttp.Request` + is created. + + Raises: + - google.auth.exceptions.TransportError: If `auth_request` is `None` + and the external package `aiohttp` is not installed. + - google.auth.exceptions.InvalidType: If the provided credentials are + not of type `google.auth.aio.credentials.Credentials`. + """ + + def __init__( + self, credentials: Credentials, auth_request: Optional[transport.Request] = None + ): + if not isinstance(credentials, Credentials): + raise exceptions.InvalidType( + f"The configured credentials of type {type(credentials)} are invalid and must be of type `google.auth.aio.credentials.Credentials`" + ) + self._credentials = credentials + _auth_request = auth_request + if not _auth_request and AIOHTTP_INSTALLED: + _auth_request = AiohttpRequest() + if _auth_request is None: + raise exceptions.TransportError( + "`auth_request` must either be configured or the external package `aiohttp` must be installed to use the default value." + ) + self._auth_request = _auth_request + + async def request( + self, + method: str, + url: str, + data: Optional[bytes] = None, + headers: Optional[Mapping[str, str]] = None, + max_allowed_time: float = transport._DEFAULT_TIMEOUT_SECONDS, + timeout: float = transport._DEFAULT_TIMEOUT_SECONDS, + **kwargs, + ) -> transport.Response: + """ + Args: + method (str): The http method used to make the request. + url (str): The URI to be requested. + data (Optional[bytes]): The payload or body in HTTP request. + headers (Optional[Mapping[str, str]]): Request headers. + timeout (float): + The amount of time in seconds to wait for the server response + with each individual request. + max_allowed_time (float): + If the method runs longer than this, a ``Timeout`` exception is + automatically raised. Unlike the ``timeout`` parameter, this + value applies to the total method execution time, even if + multiple requests are made under the hood. + + Mind that it is not guaranteed that the timeout error is raised + at ``max_allowed_time``. It might take longer, for example, if + an underlying request takes a lot of time, but the request + itself does not timeout, e.g. if a large file is being + transmitted. The timout error will be raised after such + request completes. + + Returns: + google.auth.aio.transport.Response: The HTTP response. + + Raises: + google.auth.exceptions.TimeoutError: If the method does not complete within + the configured `max_allowed_time` or the request exceeds the configured + `timeout`. + """ + + retries = _exponential_backoff.AsyncExponentialBackoff( + total_attempts=transport.DEFAULT_MAX_RETRY_ATTEMPTS + ) + async with timeout_guard(max_allowed_time) as with_timeout: + await with_timeout( + # Note: before_request will attempt to refresh credentials if expired. + self._credentials.before_request( + self._auth_request, method, url, headers + ) + ) + # Workaround issue in python 3.9 related to code coverage by adding `# pragma: no branch` + # See https://github.com/googleapis/gapic-generator-python/pull/1174#issuecomment-1025132372 + async for _ in retries: # pragma: no branch + response = await with_timeout( + self._auth_request(url, method, data, headers, timeout, **kwargs) + ) + if response.status_code not in transport.DEFAULT_RETRYABLE_STATUS_CODES: + break + return response + + @functools.wraps(request) + async def get( + self, + url: str, + data: Optional[bytes] = None, + headers: Optional[Mapping[str, str]] = None, + max_allowed_time: float = transport._DEFAULT_TIMEOUT_SECONDS, + timeout: float = transport._DEFAULT_TIMEOUT_SECONDS, + **kwargs, + ) -> transport.Response: + return await self.request( + "GET", url, data, headers, max_allowed_time, timeout, **kwargs + ) + + @functools.wraps(request) + async def post( + self, + url: str, + data: Optional[bytes] = None, + headers: Optional[Mapping[str, str]] = None, + max_allowed_time: float = transport._DEFAULT_TIMEOUT_SECONDS, + timeout: float = transport._DEFAULT_TIMEOUT_SECONDS, + **kwargs, + ) -> transport.Response: + return await self.request( + "POST", url, data, headers, max_allowed_time, timeout, **kwargs + ) + + @functools.wraps(request) + async def put( + self, + url: str, + data: Optional[bytes] = None, + headers: Optional[Mapping[str, str]] = None, + max_allowed_time: float = transport._DEFAULT_TIMEOUT_SECONDS, + timeout: float = transport._DEFAULT_TIMEOUT_SECONDS, + **kwargs, + ) -> transport.Response: + return await self.request( + "PUT", url, data, headers, max_allowed_time, timeout, **kwargs + ) + + @functools.wraps(request) + async def patch( + self, + url: str, + data: Optional[bytes] = None, + headers: Optional[Mapping[str, str]] = None, + max_allowed_time: float = transport._DEFAULT_TIMEOUT_SECONDS, + timeout: float = transport._DEFAULT_TIMEOUT_SECONDS, + **kwargs, + ) -> transport.Response: + return await self.request( + "PATCH", url, data, headers, max_allowed_time, timeout, **kwargs + ) + + @functools.wraps(request) + async def delete( + self, + url: str, + data: Optional[bytes] = None, + headers: Optional[Mapping[str, str]] = None, + max_allowed_time: float = transport._DEFAULT_TIMEOUT_SECONDS, + timeout: float = transport._DEFAULT_TIMEOUT_SECONDS, + **kwargs, + ) -> transport.Response: + return await self.request( + "DELETE", url, data, headers, max_allowed_time, timeout, **kwargs + ) + + async def close(self) -> None: + """ + Close the underlying auth request session. + """ + await self._auth_request.close() diff --git a/packages/google-auth/google/auth/exceptions.py b/packages/google-auth/google/auth/exceptions.py index fcbe61b74623..feb9f7411e04 100644 --- a/packages/google-auth/google/auth/exceptions.py +++ b/packages/google-auth/google/auth/exceptions.py @@ -98,3 +98,11 @@ class InvalidType(DefaultCredentialsError, TypeError): class OSError(DefaultCredentialsError, EnvironmentError): """Used to wrap EnvironmentError(OSError after python3.3).""" + + +class TimeoutError(GoogleAuthError): + """Used to indicate a timeout error occurred during an HTTP request.""" + + +class ResponseError(GoogleAuthError): + """Used to indicate an error occurred when reading an HTTP response.""" diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index c22d40f6adfb4c567bb21756a3be06c4191ec2e1..7f0fd8d86ea09a1a7c44877f3f2bf39ff3509c39 100644 GIT binary patch literal 10324 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 bytes: + content = await self.content(1024) + return b"".join([chunk async for chunk in content]) + + async def content(self, chunk_size=None) -> AsyncGenerator: + return self._content + + async def close(self) -> None: + self._close = True + + +class TestTimeoutGuard(object): + default_timeout = 1 + + def make_timeout_guard(self, timeout): + return sessions.timeout_guard(timeout) + + @pytest.mark.asyncio + async def test_timeout_with_simple_async_task_within_bounds( + self, simple_async_task + ): + task = False + with patch("time.monotonic", side_effect=[0, 0.25, 0.75]): + with patch("asyncio.wait_for", lambda coro, _: coro): + async with self.make_timeout_guard( + timeout=self.default_timeout + ) as with_timeout: + task = await with_timeout(simple_async_task) + + # Task succeeds. + assert task is True + + @pytest.mark.asyncio + async def test_timeout_with_simple_async_task_out_of_bounds( + self, simple_async_task + ): + task = False + with patch("time.monotonic", side_effect=[0, 1, 1]): + with pytest.raises(TimeoutError) as exc: + async with self.make_timeout_guard( + timeout=self.default_timeout + ) as with_timeout: + task = await with_timeout(simple_async_task) + + # Task does not succeed and the context manager times out i.e. no remaining time left. + assert task is False + assert exc.match( + f"Context manager exceeded the configured timeout of {self.default_timeout}s." + ) + + @pytest.mark.asyncio + async def test_timeout_with_async_task_timing_out_before_context( + self, simple_async_task + ): + task = False + with pytest.raises(TimeoutError) as exc: + async with self.make_timeout_guard( + timeout=self.default_timeout + ) as with_timeout: + with patch("asyncio.wait_for", side_effect=asyncio.TimeoutError): + task = await with_timeout(simple_async_task) + + # Task does not complete i.e. the operation times out. + assert task is False + assert exc.match( + f"The operation {simple_async_task} exceeded the configured timeout of {self.default_timeout}s." + ) + + +class TestAsyncAuthorizedSession(object): + TEST_URL = "http://example.com/" + credentials = AnonymousCredentials() + + @pytest.fixture + async def mocked_content(self): + content = [b"Cavefish ", b"have ", b"no ", b"sight."] + for chunk in content: + yield chunk + + @pytest.mark.asyncio + async def test_constructor_with_default_auth_request(self): + with patch("google.auth.aio.transport.sessions.AIOHTTP_INSTALLED", True): + authed_session = sessions.AsyncAuthorizedSession(self.credentials) + assert authed_session._credentials == self.credentials + await authed_session.close() + + @pytest.mark.asyncio + async def test_constructor_with_provided_auth_request(self): + auth_request = MockRequest() + authed_session = sessions.AsyncAuthorizedSession( + self.credentials, auth_request=auth_request + ) + + assert authed_session._auth_request is auth_request + await authed_session.close() + + @pytest.mark.asyncio + async def test_constructor_raises_no_auth_request_error(self): + with patch("google.auth.aio.transport.sessions.AIOHTTP_INSTALLED", False): + with pytest.raises(TransportError) as exc: + sessions.AsyncAuthorizedSession(self.credentials) + + exc.match( + "`auth_request` must either be configured or the external package `aiohttp` must be installed to use the default value." + ) + + @pytest.mark.asyncio + async def test_constructor_raises_incorrect_credentials_error(self): + credentials = Mock() + with pytest.raises(InvalidType) as exc: + sessions.AsyncAuthorizedSession(credentials) + + exc.match( + f"The configured credentials of type {type(credentials)} are invalid and must be of type `google.auth.aio.credentials.Credentials`" + ) + + @pytest.mark.asyncio + async def test_request_default_auth_request_success(self): + with aioresponses() as m: + mocked_chunks = [b"Cavefish ", b"have ", b"no ", b"sight."] + mocked_response = b"".join(mocked_chunks) + m.get(self.TEST_URL, status=200, body=mocked_response) + authed_session = sessions.AsyncAuthorizedSession(self.credentials) + response = await authed_session.request("GET", self.TEST_URL) + assert response.status_code == 200 + assert response.headers == {"Content-Type": "application/json"} + assert await response.read() == b"Cavefish have no sight." + await response.close() + + await authed_session.close() + + @pytest.mark.asyncio + async def test_request_provided_auth_request_success(self, mocked_content): + mocked_response = MockResponse( + status_code=200, + headers={"Content-Type": "application/json"}, + content=mocked_content, + ) + auth_request = MockRequest(mocked_response) + authed_session = sessions.AsyncAuthorizedSession(self.credentials, auth_request) + response = await authed_session.request("GET", self.TEST_URL) + assert response.status_code == 200 + assert response.headers == {"Content-Type": "application/json"} + assert await response.read() == b"Cavefish have no sight." + await response.close() + assert response._close + + await authed_session.close() + + @pytest.mark.asyncio + async def test_request_raises_timeout_error(self): + auth_request = MockRequest(side_effect=asyncio.TimeoutError) + authed_session = sessions.AsyncAuthorizedSession(self.credentials, auth_request) + with pytest.raises(TimeoutError): + await authed_session.request("GET", self.TEST_URL) + + @pytest.mark.asyncio + async def test_request_raises_transport_error(self): + auth_request = MockRequest(side_effect=TransportError) + authed_session = sessions.AsyncAuthorizedSession(self.credentials, auth_request) + with pytest.raises(TransportError): + await authed_session.request("GET", self.TEST_URL) + + @pytest.mark.asyncio + async def test_request_max_allowed_time_exceeded_error(self): + auth_request = MockRequest(side_effect=TransportError) + authed_session = sessions.AsyncAuthorizedSession(self.credentials, auth_request) + with patch("time.monotonic", side_effect=[0, 1, 1]): + with pytest.raises(TimeoutError): + await authed_session.request("GET", self.TEST_URL, max_allowed_time=1) + + @pytest.mark.parametrize("retry_status", DEFAULT_RETRYABLE_STATUS_CODES) + @pytest.mark.asyncio + async def test_request_max_retries(self, retry_status): + mocked_response = MockResponse(status_code=retry_status) + auth_request = MockRequest(mocked_response) + with patch("asyncio.sleep", return_value=None): + authed_session = sessions.AsyncAuthorizedSession( + self.credentials, auth_request + ) + await authed_session.request("GET", self.TEST_URL) + assert auth_request.call_count == DEFAULT_MAX_RETRY_ATTEMPTS + + @pytest.mark.asyncio + async def test_http_get_method_success(self): + expected_payload = b"content is retrieved." + authed_session = sessions.AsyncAuthorizedSession(self.credentials) + with aioresponses() as m: + m.get(self.TEST_URL, status=200, body=expected_payload) + response = await authed_session.get(self.TEST_URL) + assert await response.read() == expected_payload + response = await authed_session.close() + + @pytest.mark.asyncio + async def test_http_post_method_success(self): + expected_payload = b"content is posted." + authed_session = sessions.AsyncAuthorizedSession(self.credentials) + with aioresponses() as m: + m.post(self.TEST_URL, status=200, body=expected_payload) + response = await authed_session.post(self.TEST_URL) + assert await response.read() == expected_payload + response = await authed_session.close() + + @pytest.mark.asyncio + async def test_http_put_method_success(self): + expected_payload = b"content is retrieved." + authed_session = sessions.AsyncAuthorizedSession(self.credentials) + with aioresponses() as m: + m.put(self.TEST_URL, status=200, body=expected_payload) + response = await authed_session.put(self.TEST_URL) + assert await response.read() == expected_payload + response = await authed_session.close() + + @pytest.mark.asyncio + async def test_http_patch_method_success(self): + expected_payload = b"content is retrieved." + authed_session = sessions.AsyncAuthorizedSession(self.credentials) + with aioresponses() as m: + m.patch(self.TEST_URL, status=200, body=expected_payload) + response = await authed_session.patch(self.TEST_URL) + assert await response.read() == expected_payload + response = await authed_session.close() + + @pytest.mark.asyncio + async def test_http_delete_method_success(self): + expected_payload = b"content is deleted." + authed_session = sessions.AsyncAuthorizedSession(self.credentials) + with aioresponses() as m: + m.delete(self.TEST_URL, status=200, body=expected_payload) + response = await authed_session.delete(self.TEST_URL) + assert await response.read() == expected_payload + response = await authed_session.close() From 8ade8677663f98c5b7b3db350ffb06f164b94f52 Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Mon, 16 Sep 2024 15:35:08 -0700 Subject: [PATCH 860/966] fix: remove token_info call from token refresh path (#1595) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: remove token_info call from token refresh path * update secret * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot --- .../google-auth/google/oauth2/credentials.py | 30 ------ .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../tests/oauth2/test_credentials.py | 102 ++---------------- 3 files changed, 9 insertions(+), 123 deletions(-) diff --git a/packages/google-auth/google/oauth2/credentials.py b/packages/google-auth/google/oauth2/credentials.py index a478669cfc72..6e158089f31d 100644 --- a/packages/google-auth/google/oauth2/credentials.py +++ b/packages/google-auth/google/oauth2/credentials.py @@ -32,7 +32,6 @@ """ from datetime import datetime -import http.client as http_client import io import json import logging @@ -351,33 +350,6 @@ def with_universe_domain(self, universe_domain): def _metric_header_for_usage(self): return metrics.CRED_TYPE_USER - def _set_account_from_access_token(self, request): - """Obtain the account from token info endpoint and set the account field. - - Args: - request (google.auth.transport.Request): A callable used to make - HTTP requests. - """ - # We only set the account if it's not yet set. - if self._account: - return - - if not self.token: - return - - # Make request to token info endpoint with the access token. - # If the token is invalid, it returns 400 error code. - # If the token is valid, it returns 200 status with a JSON. The account - # is the "email" field of the JSON. - token_info_url = "{}?access_token={}".format( - _GOOGLE_OAUTH2_TOKEN_INFO_ENDPOINT, self.token - ) - response = request(method="GET", url=token_info_url) - - if response.status == http_client.OK: - response_data = json.loads(response.data.decode("utf-8")) - self._account = response_data.get("email") - @_helpers.copy_docstring(credentials.Credentials) def refresh(self, request): if self._universe_domain != credentials.DEFAULT_UNIVERSE_DOMAIN: @@ -414,7 +386,6 @@ def refresh(self, request): ) self.token = token self.expiry = expiry - self._set_account_from_access_token(request) return if ( @@ -451,7 +422,6 @@ def refresh(self, request): self._refresh_token = refresh_token self._id_token = grant_response.get("id_token") self._rapt_token = rapt_token - self._set_account_from_access_token(request) if scopes and "scope" in grant_response: requested_scopes = frozenset(scopes) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 7f0fd8d86ea09a1a7c44877f3f2bf39ff3509c39..ebddbfe057d8bd05308ce2e6c3eb38013851901e 100644 GIT binary patch literal 10324 zcmV-aD67{BB>?tKRTK7Bd=3*_WNnd)uM+`bb(Uc?N&-t?`KFI!QEj&&F4HIyx`bv6oeQc8mmor_eA={&K!Ivcmu;|}p zf3D%pnrj&O#_6+s<32A(yG@Wj6Jkx=8e&?~V#M4YA9lCo5g8e92tr`1P!oCNE7*LS zd@;V_5`oljZ}Ah0Q`f-tkXPu+c#|qHa6!Y;r3*gDi7yn{Y+p2B*A-?5x%k2-6B^{` zoezT>cc%v;3)DxYaZu|(pzXxYR4trR>+9&6P~Wd^J%Gwb4J-n5*b*WKehF@4&u!U~ zrrI|#Y|h42FO4gZv3xTD_6``oYMoP#LE4?R0xKA#(5-zx#7mbV+~`r1KZ;%&w7xU6 zaq(-jVuL=$7sRt@D9YFv$Kr(oy!SD^6XGl-kMc`i1Dgq2E$zuZ60P!_!=+TNn z+yK2}gqDYlX6?U+;mY%H1_0+Y3Fh{KC5$%>+Drur$Em5TZ@H8XjxyI>!LBlhjFT3KRx;?R)Qe7Yc6VkFtK7HNmUD zQ(yX|l?8W;I@aWb=5kL6I2`YQk}ImPEBu|lPM0~I6uGGCK}y)Y=xQeR6BzcjLEEa> z+T4jw*>~t1Tcq$;AIfFH^@mv$>EW5>1o{YLcn1=h!ulP2%>;BA@m2n8h;ex1a7kW+^Et_Su5!C z*@!A1Ktqe#4BcEcg!%$W5AAT}V380pF=!f)t|LP*rUDB8CyOtEsW^bXIj@~ve5zwy zE;K)w*|3K9=DsKZ+A1OCy$qAdLafv--qF}f?&o8)31319-C-`1a+l&cx4-bef@3(` z6qs&lQdH_wQR{_4cpPcJRZ+GHVy!hn1Bt4`cIu3+T2x{WARJ{(b7E&+P&fwzgkDgM zPwyN%UvO%g8|<0wOBJQ0bszHl`Zl_;mU3qVOHr4`Mtqo3K6%bQOgo zuUkxSxCXBO0h)*v>Z#cM{<2q9SDX-c-2UJ)WN;apWt4JtX*+!6z%zRgG8xn80M(cF zE`SdK9>+NL5Ms1{39*{U82}vsK*PttB7!zr%nc#%a1M1KEaLrVW*|a>~Aq2&T*D2E8z# zI;8Y&863Sv`vmI6BUry8p{nCFQ=z%sL=KjP+8RZ)5ofY|q5>^3q8;NnSI1=k5}T@-n;W2d^WL!i#KUK-U!gzK7>; zbm}rEIt7|e1p20T%e)K`;S2k`fS?$yB#=F=-cW~wQ5PYV)VAJXJ<82^?N0+8+u2>C z(VpoHg02c@ECYyBZ^ZOgG@l{2@kvjAJgM7;K$U7cqUZbw-SHVvim!*1s-Y6s%fvYaf%ig=AxNwqL~Mnx=U$|=%Lnb__fBil4Vxc&@A z`JeJ815bk@wP+Yb$=^aZWY!9hI^F33Fs5GEzd4)V_LkSvwlD8b=V68TC*J6Pd$k%p zk-j`_7tn25!M(A`%Yzff3&eZ{Q_3NryeEe)V+Vdw1F`B+)B^67Bi32 zfeP+YJ(&uMcxgAu#J(72V?6it6Znm7zzB-Js)bn)JTs*! zy2m({>2Ncs@^qiH95-n`d6?&E4tG_To2?xBu|$ZYg01LF8iAo>* zs9M<5)l)%%ETHGM^diDPWh z_oU8S8V4C$sPgtskPSnW4H7<#=dUF7X?O>ri17Tn2Yac+YWnoy>r22O$d6#Fdw=W% zW7#nY$eNP7P+u66KBBoA26Sti^u)V;N;wY|=q~gsRb)lvLpBB&ckAwC2n;@$g;MCJ z+?PoExOaU!TD2Z?9dq|L&jGOEDwIRJUMFiZfN1r=W7Y&9(^fB^ixlV%fsTuAGY6?v z76`5I-Mbv|fOpr^`CLjuJ_DEg4q|OFt|zwN8CFd=^$y&<`H{Q+s*h>1s~@pF1y;i7 z=)l^=1j~kWs1)!kZW4LNaOV#$8hd!}HY<`?cwkUTBm13K(vM?@yGCt(3L7e3&cZ$U&vDJ7=phc7nl(UL} ztX3HT&yVb?4Ztv#t<-1cgc?_)gNMw9+LnKy5JvvBP@(!hAH*~Xn7dT7%+EKGddvIF z@Yo~sUhSZddfcshg7W`*?S z4cVWZ?RhIEduCy$~`n0o>=4|Tr^9B#$@C*!F??P+St3di5wdk}=TDxJg$LR=< z6hXut8h&$lm-NbeIqG$$G6$6BUqT8u9^(tCVqqSA%_e7ose1#|#yjrsfrWW*zTF%c zKA7Z3i$Au3%iUiEqOcmS2$qOE8rTZF?A5DR&x05G2ES#Y+>?hT<9kj35o(p z!A+x?m;42X;hJ%p*D!kcFio@zIzraLq78~F%>xaU8#8zg45&*%YF^IcrqTjF@TmRh z7SyS##x}+y7sO^#vS2X0oleGot=+_vbG6_`n1J-Pnr_mFw@Zo~sGqDI#E#b58VC6@ zjshe+AR1hF?x@q%*HM#kWYQAJp4v+Cpyr0v&+fW%cad53cjQwnm)5D1VnzOuN-1xc z*YhT8BLP%r?}dKT2=TPsi_G0&`o+h8gl*B5X1rtUb6HHe%?o5!Gc69J7L#3AGEd_n z!gTK`l3W#aqlI|Bu023QKX;rXNKE6XdmBtF&FGA4yfM#KYE#Ymj-p!W#`EqV< zjO%ZO0km%m3lklj*%S-CA+!M=ZD%*6ZMyCbi1EJnQMh-7OQu)KG?{^O0bbT==?+%8 z2X9NWPod*F#5-mjrP8^^Y+G+K(YGyqz>>8p#0d&-8=dHb%tXM;aR0hMg8Dc(t&o%@ z5vPK|bN$*!)MqyW;(cJy&Vs2B%>0q&H!Ive(i4vSwI8isi9+KRpnP&#>vy1|I6t!R z7we$C*l6Zj*HR(m5I_IWUrc6{_-gA_ECCc@LpFP7%gXu6f1`er!@&!@ZQlnf>OoE0 z$V`o*awhM_>Dd>&?H1#}7<{BlS}a3H`+E%b@b6b3!ar!B5uZv?gA=RXlr4QhF=}5 z-12AKsfVh#I-ECQSw{@M4?m!fW5@cbs`BSSWGY8yYHu4KF!&nw{{db5YGcQIdL~++ zcOH^D>6EgaYH=~YD$nCN-ry~p0oA(&&>AxxMNTcvXHsEg^!T_+jkPEVA}={XeO>zY z*erK*hoAoTmuQ+eQcbNHx^e#5x^SeTVeA;p*!(20V6#wdv+4`5_s8@9;JPe6jNMPY z`#ZkpiPXrLEU%5wBorkq)4VM-k=kDLGCMbP5!;Cx$wzUeBd{Hp!Ta{8!5Q~Px?~k_ zfSx1igq4Ik7pqY3sq8?vwC_vYzM>#SN1=(?+UsBYY|Ui*~m}@lsFi1!S9~SNq%c|>_UFX)}lkOeJG+^Y4HW;}j#Y*LS z-@Dk3JV2UC&0_ug)_B_8>b4SgnCuV0?z03=ur=yL>Tcn;`gcuY!G@+0R^tJB%94ZsFHe!{)t5ek& z@7eZbhKHNe$7eSX%a|=F2bb(T#JmMCNO`|9$3yVrdzs0<9Z5j9CeP8YssZ0UI*{cg zArsqKCk_0-_r)mWj(&%>36*o6MUST*<-}&PHnL%X#DS8*o4-nU1UNU*-_|KV8 zpGbatiTP6tV+J6*;=`JcSr$q=?d|y~*h%FYz5;T>fQh+9a5IS3`!*#-mH-PAFh~l~ z!5sPBL;-$rM;ziD+TxF|;>!BSnDAI((H=t1g=R*X+WT6U#k|VGoVlAuRDY;nzj}_e zM_7AQlLek`V<%NvDnPV`blC@~d+eTD+_4=-cklR3)>w^M#M(y_9bNzaVUqnQZ>2nmM2;Rl56%&$^x6*+uw8uIyV(G>86siK8$h-Eg_ ze+!N?!PCYO63^$w5Ch8YEStsLauZ6wU9UAGb4U83bZ~{vuxte>NK?6ft`*9l`|^4L zJJ!+*x=vX~flepmkj;GryoGgy-zqM5*aZy5aSIxXpy>tC{VCA-gED)=0HO4Y(Pa0) z^mCb0l0dHrCg-y=SG64YjC-!zf}2itgLxTzrlsZzvtC9K2&w}c@_%ghmda8$3j%zR zC4cB~=B-KIU!b+6yXMu`S^nmXL(CUJk{^=0b!jkO82c!_7lFU#!MeCZcbzn4wC@i= zGv1OoRJ-zCX9{)p$1O+wiwJ-$5astTtEB3gzCm_35PUbU)|F5$Fl<;3PwkNgI;_Ng zp$tGDV6j$+O@f2GksC2HbV1TkUOvQtwxCzflUy9xjF1-DY)fwg4wZ6X?pz2|1l=cV;J0Z%qlnhNn_oE?b@+EZ#VY+;UNH47)e5d#rCt@ z4j+(s`XmWIUoB|EG5a)wd|4i9!)2R%MG6N=TX^P^)2BG`9WkJQ>+=@`-3RIuInH}9 zigXV+LV9EOgR*pgvVQ+H!{MITJ|^cd&isnz>^RNc~RH2j0BNdO8l? z46g-QjqT>0*%xK+Jn0+aMG>)kkLH*7yiiYWnQCI4!A~q}hDj3^eL@RI>iFRy`~vy2 z%o!cJ>fqS6C}x~v>&1}iAPJs>pkL`1Jb`xtY*6C%Q&+ZQ?TkG1Evbyyp58)N^wM*$ z5dL5K5nC&|_}qJIKr%P>^BFi*?v%kzyB=ySTOT^{QsPi1E7=D*k}3+C2=c>(OT4Nn z@a7>J%)9Jt1Z5=Ur@oRBuwfaHj+}1GZbq&w)*+_;Udx(*M{1J;Qtn03U}A-^KGDK* zf_@9oi#v=0Gsjo~UrxNCgakk=A69w6(A%?l6 zrq+Gp8;G8S!pclTxeJmTEy(WX?*=y*wNTm(k{T1&hXTQKK*XGAYea$|>*%>EAPSO8 z|PyGqvh=dG!7$7Veu$;GDs277Y z7OdFq8Z`>ebII)d_6a2|%Lstcx^Sv8Q2zH1pAQJcLOV@8pQz{VabQywUByPr9M4m; zM#S>y&nzYTN$kBURh7*J_FEZGW^*m<#hZ!332MCyK0@M#Umav{=Mi=kTvz<6BJ%EP zsCrwb5g-XJPjAtw?LU@@EsMf%D6E<@6$22UHN6}x2uhG11M1y^y6n`ooE6|De(ddDw*mvU>Op?yqmiKn>#>Pcz7$H-AA}~b6p9{^%>aZ~Uf>~m}67vod zWo@OAGR?gSP(auiK!co$t88=6U>x}sN8dvn(y>!3<72PTQu3srWTz0HuF5pYso(Wr ztmbY$6uso_s-qjr(N>~9%naVrC&SL{gUzg;Q89qVj?D`3RMYH4nbvO zw$kj^Ae8bq$be1j`TFj%^PoV6RB7Q_h)Hr`hh9-h8WAFc09=5`kyaO2*Rw1i(gOb10G0-8M8~P>+qt!?R)=_KD60V=L2+e`@s6>Z5434B%660lb z!tnHWe-dzJE=644WItabbNN)8Qg)HiSF~>3;ouqBLapP-k z9#yzDD@kriPn3US531WTHyC{Xl!Mut1>^DHQBy~UhH9q z!?%9UJs0>I=9{bOZlw`n%R9ETD2ySAhJ)wO)t5Aen`;r%F)R4zo-0J67fj~P6YRH( z)o6qm;XhZDjXLPr0y zVu)<>2F%O3W1M65++zJq%*KhbIjl@{aj=$k&Cm0wvwbdIoHP8%)Rbu-?c1h zKi`wd2vGtWefTab8$4$)F$N2~ec1yYW&5`kuVG~CYAJx>O6ZfAZb0QJ4fE804XIl+ zkPc>-T&`R7#YbHNM|RkqSlfEGO~U!qp1GUdFG1>e}3k8 zU8~;@n2ZXHcq3Yk=b$0;62nmylvH^FbgtE};!o3mk>ywzy1C5wAmYT!8xr#WAuX>H zj?_Fj3!XAn6+wy+QbTfqwz7_8*jRNJo)((I(VQ(k{3tl(8~!YqrFw=Hf(!5PTftmvjQE_Q3g#FExAmjsj5 zx#1qft9;$QTuM8Bb0i!8u+Q4+zg*XU|d0h{66Hu1sK0t^{@^P1098v2`(I?b=0{O@D zc>}2=&8S&(vqoycZN7m8A2n3O`*o5^AVXGT%|6{sIm!nE$=C!?pF#v=o-^Ki9z6|$ znXJJ?x!zApJ`bNkdr*E4I(ql|T{F4VHaT<7rN~PeTXN8)QJ z#@OQ3qEPHDiTay)M9)$guf!G2#6|)2e{!`BS55~Zw`_a3i+S<-S+I){k>6uL&}Omq zyeO))Z`!JE?%WrLgf2sWPWSJMjOjN8zy{w|svg6KLca>-Stn$^mED4lZ8Lb!Q&2cX z%KpUZH3?Lh2Wsn=B=_}kPfj7Kn5a0C!Uk}jrt5E2aHy(Gl0bdHHW5Y}Qv+&74GRC~ z(IG{P0<{CIWK;zC`9JUNb@c%BS<5msgj z&XHU}G9`)F`FJ$@1q2d{sot$3L{AqK;PFkXT2UTmib=VhFt2yi8y&=|)G52VH0gl3 zDf0@9m34;3RMNStLzl}3{8ii*992|U{6o%;!J32nur`txEJmce;l~>wv5uNx(^4s# z+svzH?iiq|ezMq{fpJL<^YQ0&mf7-+1w)T(Q!KdDaS=&XtdO{PUUB|fGuN-bQb25G zDK_VJece#6$^K#RQY+!gG=18O)DqUmyI&(UlYYh2(K;f)iS^FKgK|GNHJF7~uGAU1 z_+=I6???2NRdt`#?^z_OFB(z(lZuA-W*gZCEiKvRPh}@w-}<2Gjjt_2G)Gt*^8YUD(@>!a^gwn`%nPFeUEM`oU_3JHh6&3YK5!4yRAVE zk&Pvv?{|}&_E9+bn4Dy!im`_RF{b(`4g{6Jv+Oow#+hX&&Qya?9jTenlp^S)O<3#7 z_v-2Tp|CCMfFrwJR@Vp06OJXRG1+)R|PUb})8PNr^ zk__n9kW+jqvHQ1QdzDU*0-8~gB5<=zZFFhdTl~O3*UW=ZqC(+g>snI0?Q6FA*^0&V zQx(*Wp)v%u^Zu}Fsm^>l-U(t72j%5CH;3f*v7WxI6#!XC%<^0?xSA9OvewDu^d-Q)@qCdBR_5d3aM zENbbE3bI-s7awoy)cv;?MeoC}>>gjv=JAQ587SqZIC29k?Ca5)B9+zRWsXKTZbQ@G zL$M6h^k4LyK`2He1XCM8qY~^{+)u6atwxdiMZ%(B9pNiX1=~V&EhF$H!q*QYBI5ifb zHkkiCp4>WD*X$QY@)?B3JYIFAg_@QPnk6qRDok=OK&zvSo*C6v(fhvPM8*TNI4grG zcc*t`^!U*pjMe4%X$d5{;uL|66+zi6$G#HraGZ&x5T94P|6*z0Y0}kM?g+?&Y3yB> zkvR`azu6PoeD^H-7w|3HkAy%pYtq??#1rl~^j>Nc`P z=QSS*y?Ejvy@7pnIu3uChQv(>s!TKyb(mFVcEyQ2f)|N;+ZgeP@xlmwO^yH%*{bJuv#)}Y%Ys|kSLjm)JL3bX8fg7Rdq-Hrsx zwaExd0zkk@j?Z7cbosTZ2HFzhZ7T4N_H{1%(l)(_r9xY#RX zo8OI^%2cQw4$e3A5z@LTQ1%M91d~ByD$3f+n!DS_?K%{jTJE_OZh1=;lsoIs0m7iWDVTe5ER+W%zHFb` zacdAPm-y6@y+_>Y}a8%_ssHxRWa;|v+5Ho>P<>e3-kz} zi3B@WcIP*~+3Zw=E8*!*Gh-fGLNSVqp04ZusgcvHZ3-^KxH2Skue;>zeE`3;I`6?6 zhm^cX99$D{$`(`gNqTrN-k1atSLU?~@?P-?N91VI_jmgD>71@|Ty#f%=R%wh+OnzR zG}FSnbr|%&49kk&Bx2;G6uSRHqO}$yC-UYf)^_^*B)x~3zkj=FDy%zwhHU0Z(Nj=T z^a@UbX&=JDf6h80xwN1MG&*|KFcd4yGbflldnc)Uo19pc#i=~N-r@s3`?Lny1fqmv z|EQK<3W17@d5A&7m1e?R4S#@C8=c2tR#^aE0tNuAWR-THftmH z`j}9>V`*e0Xl?pqd=Zff)A^1biEJmDWJsc)V)L&Bwu&QPRS^@3X`Fmw34k2 zrpkG8pqv-!1w$btiKMk6cM9cByF>V{-+3|&t*xF}64vUCzy2p-(1|A})ce$z6YS5N zADh!5uLWoYyQ&VDmWsj5wLnUs>?9-jX7b)|u_eKfvjb7rSp;_jcOrR&>lUC{^0ncI z>3ZB4;ddG!wx#=I-6B4%EnU&3;8#*|&A_u5E{`x6{<$Z?B!<6En!l@x$SwY$tgQj| z!Iol*LBA`;9vCfKqUty-7n!N(XcrPU8`}gnI|5Xc32HuadJB+%E>=7%+5w})*p5Dz zzg@dRW&BMmrLjYilpDxh=a6Xv97OJ*JCz%rcYA*;NEo*K+i>6z>8&Dkrp(S$LDRo`pa5OTSe3JKF2}KGh>6MIr*Uo{sD>TjI;#B zBBQbCnZ#(LKq|DhEXpxG6as}F<_)l>Y*W6?q6z2vsapSw{A4(sXI+;Hf`O>9iqM*H zR1t&^0zewe9O*{j?6 zdq-=*jDA$nAmu_`_gPBEs2Np?tKRTG&}PIQO^>a3$o%0xH+zb*FNnLP?=-=iB(h2XRO2dNUOPyh@) z@m=?li1DuoOdaEYRJc31feC?COe9z^K7UfAGP*@ONRI} zl@b8Y1eQ}V8Y^?zWR~kN`Jj~bcg01MZQu9|3{(aMb&aw(ki`V0IaPUz`o!IFs(MqL z`Yc+$k8DZ7&evBfLJ-Yw3SqFU)9j0j$!})*XQmC^;{52VEwk+FtW_d_(PI`*cUi6w zRI^gXLtAEIq!x9RZ=AbP+p0tTo??$A<5s|@JG`qZ>(n$JZoOwi!4KYn_N|jeLQMFM z65Xj#T7zu|T~eS=D;tfp9DR>NOCE^>BmC$-{(q!4HbLm_X_ZPCier%f;cV8v{T9}Ija_HjZ6h*zL1A!0(@u|Itltsl2c3Zs@0brd7DY4;nO0Ym`7M zJo}2&Dh&#-Y9fUykI1CISaM!iUJhl0otOU&_h3Rz1&d+sK%G(gWfkFGihtB$$A66k z$g08K#)YU5VK^Hq&*F7JXspTsx{(wi=+>+hxSqbx;{@0kCbb@CVbE7NnKjnkMR|;j z2mlbLT=6l@KaC7$CZf02bp5)dii zzLbllAR3;?5CU=ajcth4NR+@iv185g& z*8nEseC^vhF!o%#ki-@gFi^RArRY$_prRR#pHz5>V~@>VvOIP|uaAp(VHrLJ>ZuKt zrm;0SAm9C#(`%0VtR+y+A{wf1IotDGou`!g5i~b|uqJWPVg>)TOizi=ZiP4yZs&YPGC6}|XG;@(BZEskcTbjAE3tJbE3+yG`R7Xw z_1B{SoND@5b8>j)T0cXnXrZrY$LUg{EpE3RkCb8vu}votK+K2cNdjUq)|E!^%`+)zf$&n4hh=mW z>i~@;93fVKO*_d1r|!GM(@I8BZPgXXP#;IkKf;$>NTdruGP;kAKT1PG9)K%GHW=U& z#}G!nZ;eSk`Sen$d7Tb3eP?o6?>yq_>R7kfv+ z87?oO(Eu5EI_tI`Qfxd3@s>IAYQdLQE541fKgfA4TRbiNVmJTM`BIQ^Tr+~gc4}uU zm*PiEzc91%l+MJ8(lo9R+Ub7brGVP(dcJB&7t5U2 zR5AUoOda=*X)KOTT_gJzn!6D#scQ;RF5z&*PycI+P(c_Qw4@xMT#jzhYEz`h z@iI-AbjbG`yzj2aWLxs6X;RtvI-5UMHKv#|%4PV~KI>g=27PZ%u(t@O^Su2YEq{Yg zO6ZRn-f;&|Cz}9t5QzF#tW6}?7AE{L|5hp0Omhj*#-s-<7!NlkjrM%gSjfE&NeTjp z34ln?$!CYw^+wjtBxrM>+?;C%SE0X3;R}t!t)TaRFzMaMS>30e z@|8J7enLEYX$+SAP?B;P?mjKM_7T$o=bzL7sJ*`X@V;dgtgaYX1GJxU{w0tbBvL}o z;?3gMBy5*L)r__U_lv~&cqpFs?Q>OT!Qrkm0{Uc0up}q^2!06o2!J65&L}SS+#61` zeT=^1&x^)aY{5qTKhX=0tD%l9%|eoj-_L1^r#g(^hF!lcEhyQUc-!a4w%fZTBREc! z2VGAf*{6@dCBxX?E+Ji4e$ZJr5$YBcM!M=@3XSl>7QwJ=N=AqUtkqhdVsush=)08_ z$sW2UTT^gP62y%S3BKLP? zEoZwgs|Tbr$GL95-zUJZJmK9=@t|%x>q^RHZ-yXU1qB4B-`r!!9BZfkbqr_2nDu5u z{2^Ws+rK@hNhl~R&L%;mA54$p%?Y?$1>56^6ZW=&qz|=x91|j*7FBf738kIDwc(lW z-h1-U(W%bhWppx;hX3gi)q&TYyA+3G#=pV;U27`(__4vqPhQ4nr@+GjUI^PHZOxLm z)gvgSxp-pw_4nT1h$4)zCE-h^Lb0?kGJlhs;&u#%SVy@=@<(tV$XJd)g?m;+jBxPA zpjjpf!gF+rFCPL62!&k1DJ$6UfWVV1?U#T_%UZ6wRxJ17y$Ho59 z7as8(&6L1KdP0qJ5x0HJNKG~k8USn@y)!s;K9}PYlkuImII$H53(J9z8P8qLGwTE`+sZ0FK#Z4(=vhPJ*ze6rGWOQQAT8s!VF~V+*C~5&LW|S7IpnT`nDaorT z@O^95(mC>r*bC!bu#Z@zVS_N9Y6oz?fMd7fr?Vg(HkiRA^Wr2^BCXL5UVFHOqQ#4* z@?@@}c(HK$X1Pc5f8W6FS4n__qR`hHYPMWF8Eq_qBL3~6EHxn;l0@v;C?n<8N}T#oj)U>jqyILo&IMh6?6(<`5%9GNKoOX9#OG0J$8Lw3?I% z$x^d>FioF|e2{}XMFPQ!Ulh?S&l}q0$Dv?H>OjQd zobE}rPb$4n z-kr-$w+=yWEs@B7cKG=GW6+?h87UXcMB*`Jl0NA9?IY}{wlub9nbn&FfGTUHC!-h+RWoLxi5TBm>n!52 zs=Rfzq9!Q6RRoMQbMfgkiDw~0ztsD++2p%iW%&J zYjU_vah`y;OnNPMaaCYREk#|sC=RQrAz!cn(sS@V-buv#EfU2$ova`0-1z?9kdZYH zfq!rzzOH=ONPo#zvR_3-Sp|C*n3EN=+dqqy^F+Z*Vt9TI<0dc)BB#yWDKE_qKEeD+W$z?(E zzJi3_x@E`}71ezGXmYu0{TW&|q82W?1}Rzf-J*2n`&*fnB7XOAM1cdYJ7}wb!zZ95 zKW}j7E_(Gv!)ae-lMV~;8vO$GA#2X7HA5O|kXBrHGnE)}RtO|%G5bgBMb=Yp#<`5X z`8wz)uYoxh^)VjqwMuPk3B7a}tOtBfk8yY?(06|$w!K)HO9oA!2P{Y_ zIoD2};TScc#C(>U08YLXcXt=XrOrf4Old9Z{FZ6P-O$&vxcoIuF_Xj?N$-Pft*JMg z1T&wQ!1VskoVekMIaV`S6eVq~;xP(Z7w;P@pquw_C#Dw1sGGxECcBA*hkv$=1ZwQH z;zI-E!zN;u9mvc<#e&W@D30E7CMX9(PQ0PfahtpAf7(VFTI`loxi7>7)+J`+A^KYV zfpva$W?r4yZM^$!>#66Cocr|R$&MFUH~;cDc8nN>ORsw;cH$g0dJMlSXyE|ij1X4@ z&rob-=VWj7c6;7%`Vd-j10%+fu%gLJfV~?V=v3sO(~8(1Or>V6_>~&lR~vSsyXTV$ z&4xa~6D!dLtNTf4s4#y1y38>P{}qYzZ4?Aa;uqE3fbCObL&dUxeWff<6rtYL%W7&B zS6;mhZQ^+%J_#R!6T;xH^GyB8@Pw4$7O>5;9UlafX}<*O%>h^ef57US zl5qzbzqK^;dnDaRg}%*u$D(Yy#Y5-3u2rETksBNHYk~B7Wn^xoqvqqdG6t(b;8mG7 zL{2+deRQ5{CNPUfyMc+!;xWo2IiK4TQwlbRQvvB-oX0``q{%>#m5_evSc4*Rmi6&2 zABof#FI$vi357*skW==4hhRo*k>uiNmYuZ!;j>`qkdGUIJ6X5p3>rysRN6UF^^ z2rYcgLFKoINWBS!%AKL&2#$?u5Z(UDfg`mV);>gGgC~fP6cyM5=+1dn%TuUfePPti zTWGVBBly@M+Ma-=7V7jT!R?GN$*X<tWk{yV$75=ZkM~0_xLw zsTEI_K)tIrpgK9!!#%x7kMCz$(Ac|7A={E1dir zFN-FD{_=Jt+j$UfADY+u6$i=_@@Aw8TaU%lx-Yjur*s%~lc8Yg>c+QygDkO-xQ32V z;EZ*C6~_&>B09vuo!9xG&7MQV3oy5o{Cn}?`8VKI#D&OFNPw)<>E_bEv}?emc<+H3 zMQ*Kk%o?Q){r<%W(9xX&$&XOO6hsUQDt($RA@TCmKZ#Ab_Y#(O;%Lfzl45FDhzJ-% zTqh+#`bHYbHk`GB|4HRn3@jgkb3%%nY;~r@`32axTOSbDEc5rJl{dwQEpAkPwL*2s z1t&qAZ9%8RDF($98AO`3mkakRdO?IvD5N6!s-PUHT^kHH7)QM}n zC-#yG9Kl9KYXC32W}5slzy+?D9 z$xYpg0fMrD1{>`+1T74x1D7FGBWjDLp0Xwv0K25VlI9H!>48+ji*o^12TCx;L(G?S z71QT_Jldy?@okw~7?p?eKoDG!Pe(~ZWE!Q~*dcnXGT+sgBLKFu33^<(sxD)PCT~V^ zDuzH>nxs&td^*1$dFTcl8Dh(Jp`ry&UA@SvWiJDNyW?dV>Y$9=MWZD__~J?n0nE24UEb8{ggAy=U)H!4yp;hX zJh>z>t4pp@bT%GfS|E%|P9|Q@8s##&MR1>#s>c9IG=B(;649va{oF{9LfC`N06$A=Ug+h{39mVG;SuD3hKQ=k$=3aOt z*G|(8i=iJ6APzqc{5*`OIc?ukolva4f zF5^>f&ZQ;HUt+S{`gI^3=lfvm=e?{dXg!w65ChOSSD$^K+Z*QRP63bz`Z(l76cuGP zzlUoFnG1?pwGq=N?Y?i}=p5l}(e4oPlw?ImmiV107f{&#yy;cbEn{71scdoEPH#WJ zy;l-~8}SSm)XPg^m>`+LBDtkk+9^UX=~>=pZ`Bnc1P_|t*I^J7+Foyn3$AV;e{OS9 zkG}%EL7)h=DjYT?vOjwL0i`0hv@v?n(rY`VI*l2?bPXh#rJyZR^sJh@8(TW=eK!%D zjgw8GbMJKz=mRxGPop{kC|=9;;FjEUH5pa2IZDBBm06u|rOQh|xayKJ=l5gH*MW;v zN;@jI!zV@q?{r0PLI&7F@L z#ZMPVc|4#!%6&N>n3Sgy2cC2HzACje2-oLw@*_La*}0WSK3#w{98c(o?j=YVTBolu zktxq(mD~c#TK%@f6wm->+w8}QANk!lw?ZtqlZ*jV4<8l=R|WH zQ6s*&e?xV$jkc7!lfc4#ubO#xvD0ON5p;NgMXw$4zj4}MMcxwcS0rNg6(2v#cvWNG z+8D+99k`=~Dj#-R$5q?>$Fh%exJaS4Eg+79y$z~0uH|ZtjRvz?mN{j=Ktvv>yRb6M zZo8KD+6erswzHEr=O|ai(uHeIf5S@h{={osAOgdplNP^_3Np4;>GGN*8_e`Q{$3h6cL|FRF%rRO9DjOr(rSX#BLz!rJvGwnAW6D2balJ94f$CkhFZ*KBv3+@}HwZc|EFt?l#0+>4rhM8IWCPT4 z*>`%$go`D4@*q&qF##6XwqEueu3iYVyQKW<>jnSOoZgy0jNmC@5Vk0Q*2X?rcXvkq zxy}amdWBe`GM*c9=w&1{;(0JN2!lQV-qAN0iD^#Nd&}0=#ij+hHYRSnvFH=WIjx)D zWD1TN5Tc>~cIevWNS%OaYJsPp#BUXzHHlLiPXKCM?|qv!(qX~JNh?@72m4mfPbsPA zIz}CqeMPU$tPrvDb{BZ&+)3?}1L z3!Id2>l()-{>wCQK(I~9kW)r#;d8Mo*69>a0a6jN%z~rbn*ZofJ}M z(&adLI|*u_4HKNq9}U6ExDXl`6cz zzHEcapvIA>AckAcMCs2Qn203-1`B1mr0K!q zKhbeUtY-g*u-l#1q5R;z-|Xb-$!lX`_2bpk7-Bv`fJ*uw!fa?9<_q1+?6ZP0Dp4=e za>}~!(iOl_fIE&Q+gid2-b+p8;k^!A=6KYrFXH=Tcolee!$W~>cFbtabhG+U57%6Z z^2lvged%=R>xvBVLbKJe;X=Jhti=?wO_-+5Z)AB4kEb5dGe*u`w64Yb{$&@Hh=>s< z{(mP^x&UKfjM!L%yHK1thc)7TUz`@qyX9rLyx7JO4+@&h;i|WadF| z!HJj~Gi5feeFj(HMgx!$YFmCVQEwQ$k20`(aQzIB?x#cUac$m?iZj^cnVCm)7NV4) zmB5aLXRA?EzR$QM(OB&0dN;h3E~zB)#{CyI>Yy%1Pm6SP48!+x7goa=@U$&m7R3rRZzgAimw`INL~QY95tpZm%Be%?z4LF z0%AMO825vJ_)7s!Ajhk9#+l5_{5-7wTcw>=!qSe7Ju7F$e}u|2NE*5OghdoJr%{Q@ z(RETA>h1yWXyK3_fAH1-Mq=RB5}kh0nAhB%r#DrMaPyA@p=ov=L)=yomJW#g0lw+K zMasTNSS_=j&^D1VSGWyCa-coYEVa&kgAi0XX!>b*6E;|i|0zcs=ig!Hl$3-($2m^R z^9S?jqJYt!1E4OE>0^20S$S)NZPqj`l`q2=Qd^k#(gTyUdNbJ>iClOX|Qt#6Kw)7|70j`Jof>N__^&P95mKz7tw6-SC2z27eFn%i=y)UoJ3@5|I+9Dh`$OI%%RP7LW)sh}5a* zNPSjFgfXYSgWY2%vnV4?h{~zkV{53iw%m1NjO&Go4Fox|vf_JnB}8cggpvl)$%IMt zXeN&5rS0d=>o%`n0YE?LgPL;A z*D9~<+i))tu)RpEjjOWJ`#VHyHytO<+XgU&^m0nex%}~HIzbVo zwDfKX+Q;-autVLHg8}NtCHfnPJ+6fIvsGM!R*p?o3aQo2r37IRdyjkVtIz|zKB+s( zc4|@nfs{ENT@6Xc>W~bzj8k&HKNwC~l))$JbPkG(^o(*$cr`d2ep#9O3L7+2*0AMDSlI@L^kTRD5G|_g%Br+f1+-Lrc_I8-xJXNGsHb z4AOYyz#%>WbQ-X?-*zDkD$_n29-M=J2-iV_6WBMWE|}-i17K{DK1Tnh(AL|YqcQM> za4NhVdF#XLLyNlSGT@CM+?0dHPP&wdggp3pb^^CzAASTGdRJ7AN@V*HmfqhH2Zjbk z1u>qh*p@ifA@Mxx+$FYumMPgxmpSJfN7zbM6JLJdfGrax5W4V=`yG?G-lVO2ei!!0 zl2-Vy%dK%kJQxX?dR%6xv@hG49;>ql9)_SGzwPhb{c$5UgKR7jT7CD^5Q3{wTxJdw ziD^fd*|4JMMNya17RMnaO`z%ORCN|BiPhd5vA^zu{eSl_0{0kL>d zmkM6ApS55zDex3mVB#=#+SO+Af`JjEMM%C&rm-TxUx*W7?MkfqQWKPxj zGnWu=DjkfIq*`ADWw-9fm=O7fF3+rkc4LFv`r}6%+68naw;===`e1w}T#7|YbmJWAygw1*H{VLg=TA3 zrZgrs8d}6VBcCnZ^jKO&B7;*>!RDw5EsEMHO)=?cv%}@4MnxbS(n{pAc$+?L*EU>Y zt$)JR$jk&4{Oul|-*AY?D}00wTQng+xCYhl&E=h()Im8AsX?8sC2|Cu6jUIVAch%E z26Dq5uCDbQwCna|zaMT|h~>1qJduj?5-cvHI@${L-M8ldHjr0iSF$B1fm=Ct^ew-m zOxpl@u1|Zr(&j3T811lrH`MJ~d#7_BQB##${*an#wX~Mh*~Vg1^+FOK`I;_$%ph`j z2IPG=Qn!nds?5EwYxZ9PbOOH?_Eq3H~D0NZ2mi* z4~I$?*s^o&p|p$;RPO!v&48%sJtZ0nz_LO}Y%F1%C2_mnwGPy`*xBH4ukgT=txyhTEhw;pxCmNy( z3u~xUl<7pcKl5>jP=hL>gokr@rstBD=a! zyIAx_BclOOJRaGA65yU`oqc4!vVdD}G|1D>B_Z%>q`S$F9`AmydG$0FYd}2eiatF7 z)HYU7F_O2OnYSIPH3(DP_7V4C0hEsfVXnh#+d#Ft#U#D+CZ)8R9=IFamI2mQnz(e- z_zRqJnz1@Yv=g2A<>C9XYhwaC-?3i Date: Tue, 17 Sep 2024 12:17:10 -0400 Subject: [PATCH 861/966] chore: update templated files (#1597) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: update templated files * remove replacement in owlbot.py * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot --- .../google-auth/.github/.OwlBot.lock.yaml | 4 ++-- .../.kokoro/docker/docs/Dockerfile | 9 ++++----- packages/google-auth/.kokoro/publish-docs.sh | 20 +++++++++---------- packages/google-auth/.kokoro/release.sh | 2 +- .../google-auth/.kokoro/release/common.cfg | 2 +- packages/google-auth/owlbot.py | 7 ------- 6 files changed, 18 insertions(+), 26 deletions(-) diff --git a/packages/google-auth/.github/.OwlBot.lock.yaml b/packages/google-auth/.github/.OwlBot.lock.yaml index f30cb3775afc..597e0c3261ca 100644 --- a/packages/google-auth/.github/.OwlBot.lock.yaml +++ b/packages/google-auth/.github/.OwlBot.lock.yaml @@ -13,5 +13,5 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:52210e0e0559f5ea8c52be148b33504022e1faef4e95fbe4b32d68022af2fa7e -# created: 2024-07-08T19:25:35.862283192Z + digest: sha256:e8dcfd7cbfd8beac3a3ff8d3f3185287ea0625d859168cc80faccfc9a7a00455 +# created: 2024-09-16T21:04:09.091105552Z diff --git a/packages/google-auth/.kokoro/docker/docs/Dockerfile b/packages/google-auth/.kokoro/docker/docs/Dockerfile index 5205308b334d..e5410e296bd8 100644 --- a/packages/google-auth/.kokoro/docker/docs/Dockerfile +++ b/packages/google-auth/.kokoro/docker/docs/Dockerfile @@ -72,19 +72,18 @@ RUN tar -xvf Python-3.10.14.tgz RUN ./Python-3.10.14/configure --enable-optimizations RUN make altinstall -RUN python3.10 -m venv /venv -ENV PATH /venv/bin:$PATH +ENV PATH /usr/local/bin/python3.10:$PATH ###################### Install pip RUN wget -O /tmp/get-pip.py 'https://bootstrap.pypa.io/get-pip.py' \ - && python3 /tmp/get-pip.py \ + && python3.10 /tmp/get-pip.py \ && rm /tmp/get-pip.py # Test pip -RUN python3 -m pip +RUN python3.10 -m pip # Install build requirements COPY requirements.txt /requirements.txt -RUN python3 -m pip install --require-hashes -r requirements.txt +RUN python3.10 -m pip install --require-hashes -r requirements.txt CMD ["python3.10"] diff --git a/packages/google-auth/.kokoro/publish-docs.sh b/packages/google-auth/.kokoro/publish-docs.sh index 38f083f05aa0..233205d580e9 100755 --- a/packages/google-auth/.kokoro/publish-docs.sh +++ b/packages/google-auth/.kokoro/publish-docs.sh @@ -21,18 +21,18 @@ export PYTHONUNBUFFERED=1 export PATH="${HOME}/.local/bin:${PATH}" # Install nox -python3 -m pip install --require-hashes -r .kokoro/requirements.txt -python3 -m nox --version +python3.10 -m pip install --require-hashes -r .kokoro/requirements.txt +python3.10 -m nox --version # build docs nox -s docs # create metadata -python3 -m docuploader create-metadata \ +python3.10 -m docuploader create-metadata \ --name=$(jq --raw-output '.name // empty' .repo-metadata.json) \ - --version=$(python3 setup.py --version) \ + --version=$(python3.10 setup.py --version) \ --language=$(jq --raw-output '.language // empty' .repo-metadata.json) \ - --distribution-name=$(python3 setup.py --name) \ + --distribution-name=$(python3.10 setup.py --name) \ --product-page=$(jq --raw-output '.product_documentation // empty' .repo-metadata.json) \ --github-repository=$(jq --raw-output '.repo // empty' .repo-metadata.json) \ --issue-tracker=$(jq --raw-output '.issue_tracker // empty' .repo-metadata.json) @@ -40,18 +40,18 @@ python3 -m docuploader create-metadata \ cat docs.metadata # upload docs -python3 -m docuploader upload docs/_build/html --metadata-file docs.metadata --staging-bucket "${STAGING_BUCKET}" +python3.10 -m docuploader upload docs/_build/html --metadata-file docs.metadata --staging-bucket "${STAGING_BUCKET}" # docfx yaml files nox -s docfx # create metadata. -python3 -m docuploader create-metadata \ +python3.10 -m docuploader create-metadata \ --name=$(jq --raw-output '.name // empty' .repo-metadata.json) \ - --version=$(python3 setup.py --version) \ + --version=$(python3.10 setup.py --version) \ --language=$(jq --raw-output '.language // empty' .repo-metadata.json) \ - --distribution-name=$(python3 setup.py --name) \ + --distribution-name=$(python3.10 setup.py --name) \ --product-page=$(jq --raw-output '.product_documentation // empty' .repo-metadata.json) \ --github-repository=$(jq --raw-output '.repo // empty' .repo-metadata.json) \ --issue-tracker=$(jq --raw-output '.issue_tracker // empty' .repo-metadata.json) @@ -59,4 +59,4 @@ python3 -m docuploader create-metadata \ cat docs.metadata # upload docs -python3 -m docuploader upload docs/_build/html/docfx_yaml --metadata-file docs.metadata --destination-prefix docfx --staging-bucket "${V2_STAGING_BUCKET}" +python3.10 -m docuploader upload docs/_build/html/docfx_yaml --metadata-file docs.metadata --destination-prefix docfx --staging-bucket "${V2_STAGING_BUCKET}" diff --git a/packages/google-auth/.kokoro/release.sh b/packages/google-auth/.kokoro/release.sh index c22751b9844d..5b3d28e51434 100755 --- a/packages/google-auth/.kokoro/release.sh +++ b/packages/google-auth/.kokoro/release.sh @@ -23,7 +23,7 @@ python3 -m releasetool publish-reporter-script > /tmp/publisher-script; source / export PYTHONUNBUFFERED=1 # Move into the package, build the distribution and upload. -TWINE_PASSWORD=$(cat "${KOKORO_KEYSTORE_DIR}/73713_google-cloud-pypi-token-keystore-1") +TWINE_PASSWORD=$(cat "${KOKORO_KEYSTORE_DIR}/73713_google-cloud-pypi-token-keystore-2") cd github/google-auth-library-python python3 setup.py sdist bdist_wheel twine upload --username __token__ --password "${TWINE_PASSWORD}" dist/* diff --git a/packages/google-auth/.kokoro/release/common.cfg b/packages/google-auth/.kokoro/release/common.cfg index f1d7175f620a..062ad3aa909e 100644 --- a/packages/google-auth/.kokoro/release/common.cfg +++ b/packages/google-auth/.kokoro/release/common.cfg @@ -28,7 +28,7 @@ before_action { fetch_keystore { keystore_resource { keystore_config_id: 73713 - keyname: "google-cloud-pypi-token-keystore-1" + keyname: "google-cloud-pypi-token-keystore-2" } } } diff --git a/packages/google-auth/owlbot.py b/packages/google-auth/owlbot.py index 99fcc3898967..c2cd0fc20a90 100644 --- a/packages/google-auth/owlbot.py +++ b/packages/google-auth/owlbot.py @@ -26,11 +26,4 @@ templated_files / "renovate.json", ) - -assert 1 == s.replace( - ".kokoro/docs/docs-presubmit.cfg", - 'value: "docs docfx"', - 'value: "docs"', -) - s.shell.run(["nox", "-s", "blacken"], hide_output=False) From 7aa815ff2629cb1c2cde9a7f30d663cd6c060fdf Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 19 Sep 2024 14:00:10 -0400 Subject: [PATCH 862/966] chore(main): release 2.35.0 (#1592) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 13 +++++++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index c0495420e1c1..730533a4d6f3 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,19 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.35.0](https://github.com/googleapis/google-auth-library-python/compare/v2.34.0...v2.35.0) (2024-09-17) + + +### Features + +* Add cred info to ADC creds ([#1587](https://github.com/googleapis/google-auth-library-python/issues/1587)) ([6f75dd5](https://github.com/googleapis/google-auth-library-python/commit/6f75dd5de9ee1da4509306ff2e6420b3d88f9d00)) +* Add support for asynchronous `AuthorizedSession` api ([#1577](https://github.com/googleapis/google-auth-library-python/issues/1577)) ([2910b6b](https://github.com/googleapis/google-auth-library-python/commit/2910b6b56f8b82ad6b2e78befb7d0b3fbe96042d)) + + +### Bug Fixes + +* Remove token_info call from token refresh path ([#1595](https://github.com/googleapis/google-auth-library-python/issues/1595)) ([afb9e5a](https://github.com/googleapis/google-auth-library-python/commit/afb9e5ac889ec7599976cf3cf8516d17f6b59633)) + ## [2.34.0](https://github.com/googleapis/google-auth-library-python/compare/v2.33.0...v2.34.0) (2024-08-13) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 297e18a45f1b..6610120c693a 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.34.0" +__version__ = "2.35.0" From 143eeea7449493f2a2869464330b1bf531bbe6ec Mon Sep 17 00:00:00 2001 From: Carl Lundin <108372512+clundin25@users.noreply.github.com> Date: Mon, 30 Sep 2024 13:50:37 -0700 Subject: [PATCH 863/966] feat: IAM signblob retries (#1600) Add exponential backoff w/ jitter retries to IAM signBlob calls. --- packages/google-auth/google/auth/iam.py | 29 +++++++++++++----- .../google/auth/impersonated_credentials.py | 25 +++++++++------ .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes packages/google-auth/tests/test_iam.py | 13 ++++++++ .../tests/test_impersonated_credentials.py | 18 ++++++++++- 5 files changed, 67 insertions(+), 18 deletions(-) diff --git a/packages/google-auth/google/auth/iam.py b/packages/google-auth/google/auth/iam.py index bba1624c1645..1c2eb913b8fa 100644 --- a/packages/google-auth/google/auth/iam.py +++ b/packages/google-auth/google/auth/iam.py @@ -23,10 +23,18 @@ import http.client as http_client import json +from google.auth import _exponential_backoff from google.auth import _helpers from google.auth import crypt from google.auth import exceptions +IAM_RETRY_CODES = { + http_client.INTERNAL_SERVER_ERROR, + http_client.BAD_GATEWAY, + http_client.SERVICE_UNAVAILABLE, + http_client.GATEWAY_TIMEOUT, +} + _IAM_SCOPE = ["https://www.googleapis.com/auth/iam"] @@ -88,15 +96,22 @@ def _make_signing_request(self, message): {"payload": base64.b64encode(message).decode("utf-8")} ).encode("utf-8") - self._credentials.before_request(self._request, method, url, headers) - response = self._request(url=url, method=method, body=body, headers=headers) + retries = _exponential_backoff.ExponentialBackoff() + for _ in retries: + self._credentials.before_request(self._request, method, url, headers) + + response = self._request(url=url, method=method, body=body, headers=headers) + + if response.status in IAM_RETRY_CODES: + continue - if response.status != http_client.OK: - raise exceptions.TransportError( - "Error calling the IAM signBlob API: {}".format(response.data) - ) + if response.status != http_client.OK: + raise exceptions.TransportError( + "Error calling the IAM signBlob API: {}".format(response.data) + ) - return json.loads(response.data.decode("utf-8")) + return json.loads(response.data.decode("utf-8")) + raise exceptions.TransportError("exhausted signBlob endpoint retries") @property def key_id(self): diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index c42a9364333a..afac4120b347 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -31,6 +31,7 @@ import http.client as http_client import json +from google.auth import _exponential_backoff from google.auth import _helpers from google.auth import credentials from google.auth import exceptions @@ -288,18 +289,22 @@ def sign_bytes(self, message): authed_session = AuthorizedSession(self._source_credentials) try: - response = authed_session.post( - url=iam_sign_endpoint, headers=headers, json=body - ) + retries = _exponential_backoff.ExponentialBackoff() + for _ in retries: + response = authed_session.post( + url=iam_sign_endpoint, headers=headers, json=body + ) + if response.status_code in iam.IAM_RETRY_CODES: + continue + if response.status_code != http_client.OK: + raise exceptions.TransportError( + "Error calling sign_bytes: {}".format(response.json()) + ) + + return base64.b64decode(response.json()["signedBlob"]) finally: authed_session.close() - - if response.status_code != http_client.OK: - raise exceptions.TransportError( - "Error calling sign_bytes: {}".format(response.json()) - ) - - return base64.b64decode(response.json()["signedBlob"]) + raise exceptions.TransportError("exhausted signBlob endpoint retries") @property def signer_email(self): diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index ebddbfe057d8bd05308ce2e6c3eb38013851901e..4a6a25883c48ae15a2b07b3dc239fc812835aa75 100644 GIT binary patch literal 10324 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 literal 10324 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 Date: Thu, 17 Oct 2024 17:34:10 +0000 Subject: [PATCH 864/966] feat: Support External Account Authorized User as a Source Credential for impersonated credentials in ADC (#1608) * feat: Support External Account Authorized User as a Source Credential for impersonated credentials in ADC * formatting --- packages/google-auth/google/auth/_default.py | 4 ++++ .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes ...ternal_account_authorized_user_source.json | 16 ++++++++++++++++ packages/google-auth/tests/test__default.py | 16 ++++++++++++++++ 4 files changed, 36 insertions(+) create mode 100644 packages/google-auth/tests/data/impersonated_service_account_external_account_authorized_user_source.json diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index 7bbcf8591457..cdc8b7a64609 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -472,6 +472,10 @@ def _get_impersonated_service_account_credentials(filename, info, scopes): source_credentials, _ = _get_service_account_credentials( filename, source_credentials_info ) + elif source_credentials_type == _EXTERNAL_ACCOUNT_AUTHORIZED_USER_TYPE: + source_credentials, _ = _get_external_account_authorized_user_credentials( + filename, source_credentials_info + ) else: raise exceptions.InvalidType( "source credential of type {} is not supported.".format( diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 4a6a25883c48ae15a2b07b3dc239fc812835aa75..b2fb69b4adb68560ddbc8cc0f1bc33aaa51068c7 100644 GIT binary patch literal 10324 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 literal 10324 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 diff --git a/packages/google-auth/tests/data/impersonated_service_account_external_account_authorized_user_source.json b/packages/google-auth/tests/data/impersonated_service_account_external_account_authorized_user_source.json new file mode 100644 index 000000000000..0bc44c13a28b --- /dev/null +++ b/packages/google-auth/tests/data/impersonated_service_account_external_account_authorized_user_source.json @@ -0,0 +1,16 @@ +{ + "delegates": [ + "service-account-delegate@example.com" + ], + "service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/service-account-target@example.com:generateAccessToken", + "source_credentials": { + "type": "external_account_authorized_user", + "audience": "//iam.googleapis.com/locations/global/workforcePools/$WORKFORCE_POOL_ID/providers/$PROVIDER_ID", + "refresh_token": "refreshToken", + "token_url": "https://sts.googleapis.com/v1/oauth/token", + "token_info_url": "https://sts.googleapis.com/v1/instrospect", + "client_id": "clientId", + "client_secret": "clientSecret" + }, + "type": "impersonated_service_account" +} \ No newline at end of file diff --git a/packages/google-auth/tests/test__default.py b/packages/google-auth/tests/test__default.py index d17c747af171..e42b4dd9437d 100644 --- a/packages/google-auth/tests/test__default.py +++ b/packages/google-auth/tests/test__default.py @@ -153,6 +153,11 @@ DATA_DIR, "impersonated_service_account_service_account_source.json" ) +IMPERSONATED_SERVICE_ACCOUNT_EXTERNAL_ACCOUNT_AUTHORIZED_USER_SOURCE_FILE = os.path.join( + DATA_DIR, + "impersonated_service_account_external_account_authorized_user_source.json", +) + EXTERNAL_ACCOUNT_AUTHORIZED_USER_FILE = os.path.join( DATA_DIR, "external_account_authorized_user.json" ) @@ -365,6 +370,17 @@ def test_load_credentials_from_file_impersonated_with_service_account_source(): assert not credentials._quota_project_id +def test_load_credentials_from_file_impersonated_with_external_account_authorized_user_source(): + credentials, _ = _default.load_credentials_from_file( + IMPERSONATED_SERVICE_ACCOUNT_EXTERNAL_ACCOUNT_AUTHORIZED_USER_SOURCE_FILE + ) + assert isinstance(credentials, impersonated_credentials.Credentials) + assert isinstance( + credentials._source_credentials, external_account_authorized_user.Credentials + ) + assert not credentials._quota_project_id + + def test_load_credentials_from_file_impersonated_passing_quota_project(): credentials, _ = _default.load_credentials_from_file( IMPERSONATED_SERVICE_ACCOUNT_SERVICE_ACCOUNT_SOURCE_FILE, From 60925e1acb2a52ae62c17f79e1540185d3df03da Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Fri, 18 Oct 2024 20:40:56 +0000 Subject: [PATCH 865/966] fix: update secret (#1611) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index b2fb69b4adb68560ddbc8cc0f1bc33aaa51068c7..a13879adc306e55886c219a6e4ffe55218b1dc06 100644 GIT binary patch literal 10324 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 literal 10324 zcmV-aD67{BB>?tKRTJPElJAyHJza-T_FG@G%0 z0`_oQGq_ap9-L>vPBE(@yy~@}d#iwBnaCQceGr0bF4@$zxcPrtd1LJyf$mss5Wzgf0 zZ2Pqh5uxdIAF#CTV?@B9-5!UX2Yk8$h2e_?2f1--7GbmFxYJ$H`?Rdcrz76Xctg4&QjBZ zVIOAt+Tz)?hdoB5Vi)+*cdWgA&Mv^N>m1j`5Moo#)t%g5 zN2~;7fBl-qikq{p8DorFB%7;4`3dpy0kyI*>jDG zdx5IMFxRkgVO3`h9iTQahfT*6Y2t|+ZLP+b9VyOm8{D)flOhBru?^lw=7r?QUe<%V|z2$yi#J)XDf9z@099{-Jgw+sF4ov*X14_OXET!~!R z%l}AYuIg`-?EqILbo-`tGX24HkENGZOFX6G%aft0K$~5I7TE8(ZUxZeAK1A9&BiDva#s|iZxLW@-PlA^B z7qb>K_-~pl8v6Hqb?ju{9NDZnh~oi75Fprr4!(T~(d5;b#sw449Bd=!HIdFAsIxet z`ZT>6L{12FId)`fX%nO)a%`b2gQ8|gM&od4I0?-zU%7togm_URWFdwD!q3xwD-CYG zL4LKNnFUzAvb%k*;L|C>TazyczvvsXKN)dnbNG~9WP<3{ugXT@siTynio~fF$5BKp zmsvxO3ILza6vmr~OIyItBL;Mj;B(5|r?ok8yEO|b)okcmLV&A_Uhw+sEx}rJPV)w> zZS>{r>Ud7Srr#P82q^7htt(S@*{g|T>w!>mGf}C6b~>8lQpcj@KiuE`2kr7S@_~#; zi6Yg6HuN9eZo~N?s!k){NS`6yjI?nFGb~J8zJ4bFK>XU8uA@6I$2+|@;z?_6t+nyw zu0Mu%Bau6!#BdMA+K@AvU6!SMY~A)CB;<-0+gzZL$S5FJRkNJ}V(71tefZ)V~mp3oYa<(T%m0_^TfK&tqIG54uag`h=+W1h)??)rf@{Kww5~s}f5Kxh+TdPNm za37U{@qIB1j-JFEgPSkV4N#m#D#v-X)-^CHX-#pO@~lu8^av0tkO*de6W49rJneb8I*YCJT$)UgYx%4W#_P13=C%^3alsD4+WPDt_?9{ zOR$e9qoZYl&kW!4sK#&fRn61kiB$dkvex;ub2nayTtZA+J>ts;G>?K0khu{5LCNjB z4uB&SA*?*BF(hM}wJq;%LBpqCsumB=?-bF%*gZ~tG@d|_6MzPo>Wi!sz75$U)8x2N zWZ3BU6*Yl<+}#}xRHthebcqteF|ph{&dqznF{#MmB#TQi09f9r&lEq0&D&Ixc85RzMcl=02L`3R|r<%#Yy{YVyh z`PnnvHHn?mZrvxAhA|E15mHPhOY8|~fnAA$H!p5|mYPV(m*u|(+;F|md2pkLX>KTiQT=TnnNC!b z66L6<{6s5XY??nQ1CN{%XoOTFIWKT}-Ng#*j^GNA5)!~(P;llVLb&!f9a6`C&UcRp zHrRAdX_qob1rN)oysitbmqB+vd(XFE#8i0#0L3lRA@}RHZHOHbJ8^fS;onNOh|64;?jl5h$;?q14b;!`WJu4whb?7E)tHuT*-;4(VvpXAs5sNC^XB?Lt2rN) z(iqS7Wpeq;)ZEsJ?UdF-p{Z4VY;UYT!t<9i(?0?;sRE7#(!X)cB_rtfb6?@eI*IJk z6wDwYCfS2LH8V$G7`gRhgU;}T=BA|Iw}j@imPzDybdxC=(JFWo+e~@d;0+wa`NJ{% z5g$9z1g?IMbhmIy*bOV%tbOvLqAufy(e20{VePnw41yPHYqPpYU^<2qn-B&DXIu1! zJGu|pS-VRC-Gza_ONt;9IXuAPd{M#~pB&5oq1e!)v~bzL_U5;hD7b{R#QuL`jdin9 zwR~lxPH`_Go5HVch4gD`Im9MZ>8tws{%M)s$W(uy{#xsO@c<#}y6iojtJF9wSNlB! z&Gr-sQ20^CpejG_&Q%#plQF#zU65+|LMm}!Sb91_hikA|$tr}w2in_dq2O@n zZeJnjsc<2IU=Sr^fAp$Wc@Txz7H@Yw6pr(%fv5b%lIMe1!}J>EQMTqr*?`=SgLl4B z3a&0!zWCF6wj$cmAX3No%btmeK!I(46aJqrSjoZ*AG3Rp=*rJV-P}=jZXtNDIx^eO zSsC8ydIigwtS5JI{aC5Yx^EM8M3In*r%QIlI*fKSg8YsN8KW7Tv^@vJ>^NVlrJ`Yw z>RXVMtw?pMm!mA%XblpS{sACNj>W$Y%st(f zay3h7t}5o<1U`0Rhsz#o9OQg%nVr0JuSbVix*62n&R6gwI~=FL7WcU;ojdq5P~=1C zdc*V#H<#)#SIEDc9jdjCKfC@w3VYb@kv%kuXt2NI#VUi z-u;OjgW}HL^wPQungVZnTZTC-i9RfQ^u!8zPHayz3xSJqxv?Z6x;V8L`f5qi8G+p9 z2$mknK>YHD$V(>1cCgGFHSB>>gEg%y@g!O+EH4Wco*Lp)MQcS;MN=`g1qu0|7j*0) zA(}Rx{Cx?G002)bM(1_u%|FWVKf+l_r?aEtV+_ycJh}mR|G2_2>tqDqmZL|$Cv=`= z=XzSYKfJ>8*q_drra` z#*azs5*Dw`cnRI_3eVlbnq6rxj*ulrs!A}inW8rdvZBbN)Nh6e;pUm}k)sYZz|)C= zTg#qG?WNS}&fnVsRtOF1$NCk;fvaH z+TnkHh!Nch{!jkt$D_&<;iVE0nl22}SdqE_5@`A3|Bs4@QQpvsHNrNqk;CtwaOF2p zm~Ii2XbPJg41S=hk-Bk4Tgcb$2Y$mUX3;}jEA=?#S?}mZq(Z-E)OMAUSmLBe4=h=6 zNgjFoP?qBpo?vRT;MDJQ`*|BjhTmQ1HL3A~zA28jM=9BW_8XErY%yWkuJ&LM)_BnP zBI1W$4z{Ja;Mf3X1f)=RQ(tWQqwc9r8JdiBG_!02#8||-j1{y^BXNN}F5Dl~oY7i6l{j=O73N9*$m>H^U1_Z9E8MI&If*7>WG|dkX3k6B zO>5%VITYKvm=B{0^XXkMT_QzI5h4e!M5282D$_Z#NVceL0o~t`De7oK4ahy>Kl5$zkv*zqt-NB+QoZ{dRxV~3uEhAJ3MeS}hlVw{Om zhI->wp`(O%%7b0!1Fe=%Q>!*ssv>pYUb2V1hMf^U?2h}?AX{+Aq8AQ;oiwY^D_nQs zO6uK9A2UVmC@MItol{uGglTd++mt3RmCL~R{=8g)_qT|-pC7CL;7&TU;a*iOr}V;r z)a)yw7sn5Tl#Vz~U!-Y1f-Ia9Q!<5LyQ#NyYiqeu&2oqVmXMW&HYKf{zkMOmDB*jF zCT}33{ni=5ywRbN-}_a0WgYMBmg>?c?J!5f<{N;nU7tnqR=PF=@tUl?i;J&nO<(>8 zU@TNUB*J(R=MNxTSiB)mn8p(Rp)3pOHUr&|At1(L-sB)TL5)15Kf}`$YBVIjvz`jO z&4F^U#diCAt_)EN1X}~wLBz?aAyPi8MkgCN5xTWnfJ@Ki6vxQ6l!plU+41>nS$R@XrK7Hy zuWLyYKm$Hr)-&H!bR`Z!c9l*6>&_m!tVuEw%$b5j_$rcVGm3{kmiMvE2(#&nHce!^ zK0ea>3@^3Ux@Yztr_oBXC{RhmE7m})&zC3h%|TK=0zzy-^ZO^;ESdu|rnT$(L} zQw{+g(=83laZVj1P;63R@<6^aNm(eLX-GKmRO7HipsBpr>6(Eru$3^g6Ha8ko}nI` z!PWkceZEctx=K{xJR~)dX`sl8-p0l7XAOZtR-_qXUqh`UrWgllDX^i6C*-C$on8zb ze<1eyj?{$(B|pmki@qt=e1tn>@SFDvYeq#PZ~Am@4ELQqLN`zZ^ZEG1pn;E@aHf2{ zzg&fuqXu}rUJq3M8Y?Z6=ewJCy8e~e;KtRG))8J&C;|uI9NdOr9zic!7LeJEx!)g6 zUQnWBe{gUVWMEIwa}C)hj5R>9ZFn-Od|^?iRI861r2jL$57}x$z+^ru2HVs~LJ+yw z^*mojIG=(+YN+Q#J7Lt+c0vLx^|{%=fw}s8B>%j7TZO&+i@?0$dj7=#NQdEf;6S{Y zPNmzZPDp0fcDmtTobEPShbt_W_l5A$V+XyRc3(78L_O?9#hSU5jS}Q zLDhhjs=04zGUh)WBPl#>mMjv)!23ge~bT;)^=d+s*Hu$|F?L*2hd&it76q@M}w&o zw_a-?zYIul!D3oQd27e<6to3Omi%!TsM0mCAZ{ zC!t{=|IGMaaC%}W-EEMuRXKR?v2rxP)4k4f&0OmS;TRYM{x9oW%s|D3A1z#5VS4`0 zqLy3>tEdsdh~eTw4S$=(v zy00F%1jcZ5eBtWw1Y&cX8vmTC{2EFEYeKfe%?M{ z)8O--^l$GI$p*h;@k@r&MPN)3@zY|ArEU=^Uo+&CjrMRU?4r>)%cdjKl(k7Xi#rbH z%0RUHyhXyqgouax(uAl(%mL8mWIG>K9JFW+Afg)k6VIb}B-&wl9QVVdWEDa3+v$87 z5YXT)L%NGX53K?vgARQpWIPJ_r^TMqy?byS&1Y@ytU4BxZe*3uU8Nb(_=i?p6{`YX z6Wak)_QA@kYpQnuS(>j5k)ld5%9QPHfvL_$A!5%|0Lrb^LU%3Ld4-F2|h4D(xoYNSOd;IYic~t?#yrUyQ2e-U8k4~#M0jH+;NvyPyUc)uu?7-z?-}n%|xc zl`Ldg1wuZ_{2R66AL{e31S3%H_anRn3|IaZkhWW}doZ(!MHzj$Y^iNcTR`juuZq5? zpiRYM)JXF6kwiyu&B6azp#`t4dt)x9C)Di(of@PHjIoay?Eyay&?jGc)ge(|NJKXB zis%C*9r%JfoN~WigqIb&b;`Bz?b>CG%9|`4WM_(*7m!)D5Z>kO)XOVa^p53BIehjJ zp9-K)pPXSh_GHd^CwB=(0!QNOdy6{E&en|ZObcC(DA+pE%Y8d|EWCQ&e~cu(-nGzJ zzp}Ec=R@l`J+I=J1bOSUywH08b_eU8!trd(mLJj!jjYd;p=c*bUhHVM@i?{M_IjY?5=I71s5%y?=)? zE0AvP#iGDChu%1vB(ns8NRGW${{x=8J}4BD-a%S&;U*97vUtVS7(7RVW#7E~W*cP> z^minJ37PQ+8*!+@uF$Iw_Q#Rs`{!+TAU4TK1}={QFTYPTB;&8 za+w-7SQJxP;j1+%Cx;mcq}43Rub`=rfTHS$C`Rpy%zf+K8=bE++&D|N;MCtO4KnxA zdJbxH^e&ienMkjo?QQqzH;*vSXR@ZlIobq_bDFyL+qAZ`vFhcZ1YyQFpf4vZl+FcCztfcKhIqB|n~BuSYHhghPcJhIa1F#qZC{ z42#Ur(C{Z{&?^^`JFw9QI z3}duL{7EaKQgJ7THF7tS4w%nhHb%|KD!u7=x3AVA%^179GY^;Uf4m8 zn2|~Vy!`O*o%2q9uQ zRQjQ$y}I{B`b6O7zB=a8g<4pHB2fo}Pk&Kf_6D;Mf-eRB@OXjfCCJPbYIpbeftSfF zk7>tm!8C>Qb;II1en3+K1|pa4vG8fQKqnVwICNsc1I|~}li1Z=aYwP5(RDNVa^NlF zmxVE6@P=GT7Md8qMYgO+$>QxI=TRZr!rqjxrjaNG z15$Lnq=`2|Uh1GEjf3t%LXNXDc|vIpGeZN6>~GR~@U2r0+#$4RJVEq_kbjQo>yX-D zX|CGuDYJsnmkW^wbZETzYvjR3xvI9?b993EDz_~YWJvHx$Wg9c*`XZ zQt1#{rRHK02sVB#ohB%tia#6{0gLG??jA!H?zS>i+HHlc%@8igp2-hOtO1yNRT8$F zr*fF4&y|H09(jHBM;zDrVV*}DTo#COEf=&iQePnH9TRx&vI~9Rz__TH0H<^&BpT@F z{3ozuRcl;>K#Ah=Z_VOBX02fSFqZqW%y2|2qEJTxW`Gz~v!yzO^oBp^kE-ol;LdKk zk`t1%7>O7lXQcnJ-4axT!qBTFS?QRGcRqq^T+#%!&bSGTVbzr(X~LxkZx z3h(uLj2RC>R-l^gM+e1cJB0!X+f011btfk%Ub|y{UsZ7A-_CRNJ~S(XQ2XlSp2(#i znSS8wK-qy8snY!+=>!%pFyMBC?$=f<=;*C1Xh)l0^ctJBwicin%bS#Gh}zgs-v3Fi z0VE#Y-8~<$762Tb=2Zor8#AJ>;1Nc8=Iv|8tt2k_GA1!z8!U&ijppH$GSpBFfCa%j9-X&Jx*5JlIG41Gdj9pz~)GgC!| zMP|d>)}9H91B|5@M#ZhJ!UI` zHP2uyOnC!HDk&9IO|C7lgIJFQc`aKg10QonK8Xbc<^8i37JqTKVqP+*Asr*^r7p=K z`)fizQhsZ{NGYFzrV3%YGGKy5!z9VoDyfX$St996}#YrKW11Mc_r* z_y1FvrfU<3UXwE|R=BXmq!i;!`Fg!ma#SGv=nn8qcLTj$;MQ=i;zt7R(E5Ddqa5|B z8E{KAc^!HSa6Swa>=yA`F2OH5wO9GVqaqD+qzy#pk2lX%1t`X5P3-AO`dL8;Qjv1N zFLKHigNRqSZwTcggh{=CP7|qtRK36qx7U5=(RaF?kK~eJ+Y-lh zhJrpqGh&Wdq#2+4m_zTb2g7&vFiIP%^&C?QPBDXQmByaaHJy%whdF(GMMX(5?5U6( zl)Fm>2nh;2(+fK@7)%Xk=4D0-4op!$#Tac0!1Ej=Ti#Bhn1VoqP$}qh8>U}12~_fp zKX}>TS*Z2JjSNv-CY%=IbAb_RP)d|@inF#IDr>fPqka*wIf%lsh*3=yK#WAIZh-dy zrr&~A=Eiz4m8`xzl(XQDFVX?kdksyo3c)x)p&1ja7Zi98P! zAe=<~38D}!AyQEZO5p9@t+f1u{I~!Z-Gy4FbRFv~vU~QQRgT?93F{CXoZ$eFk;>c_ zoqa&`2148A{37P;ivfhbG;f#Myl?l>b)#Rx)hhabqvT(%mUVp;226IE0NQwx%Of+Y zk1-;fV@z4&dNB|bGSV~MFy>y7O#t&AjnFee@KixgPbEl;-ud)0n0!o>s8SZOM|S^~ zTnc^12h9$XPL_5s(r_+2{iRc^0cl~&nky%6MEUPxGT;lIF|?hQzETkk3n8H+GXk>& z_)D%+olrop0gpZD0k8cEUM-1G(Wn{@v3Jb2)_q_xqJ%t>7L~|c#1z!_Rb@F zK1H7H8FRETd=kHlJ#K*_|57=7>lMKgF`W2v4*4j)874^8h#am#gE zTZz37ne%#x&JC|vfd-}7#CJbN*{y4hkK4_3g$3(J`Vl0fhu{SN){7($VXi^ zb8?#dKEass0Qp@Z^r4rTq%%2+a@(gwgJ(}#_N9q?SxsgKjO|8g66y?tR!|*O*>}BR zJcF>e+@ebJB7&O4r?4<&ez7T=rxtxvz`A|JDkTcuAj0Gz%~gETKa5*^QD^41&WbwU%b|1b-`0UF>FwcI-{Mu!}&+RE?UHt`zPb)KYVy71Ot-vfz@x}MV^g>Nem;7ufzYmLtC2g} z?B0~z3!{jYfdC76bQb1WGXhvuM44;fOx>+B5Vb*@mTwF2c@vd0*Q{|bxF5`w6JanurJG-=$I4X1whx%4Wc-q)jL?AFig-s`$x<3H%_{! zAgt?>C5gJ1V!uJo4y+L+vptNgG6L)p-uqds=O?_&S=tJ3GW-!RlcSiJ^C0Y0AkmM$ zyeIaDDog@qWd@l`F+6EIEQ5ej7k_1(uGu7s+Q5MXM-zEGkE3U&zt(d2+l}9i@iJdA zU#%O>hcBDb^nJiLYOSC!ykGheOeYHR$}ql~>Cc7-b?*8XLSMfbq?|zwr)O@zzB;+c z%cfXe{A%0$*mF}9okWFzxo)d$;A&{O=-APuwy>rLqGnOyU8La;ulr%C^uY<>HIDiI zZVl?J zyfo^adS$MwUc|Y?!*vsu%Jk#K419?>fwJPmOsx@b8@ltEC(i=BWLyO+_gUV(A%sm# z?H~4ee`LVwn-V^oNk9n|RIt%U&+)5jFS!o8&d9wY#5-~8ks#hBJ=J@3O#xlR>Li4D zqu*KZX6F*NXy>n1Hz%;~6A1{ZDvkbQbTUm#v?FyTTd|xW;g|LFp2}lx>#$TMpxip>w$514v;c5=KwdT9;J*;F zaIp&ot)`NS<Okh@5k(PMEhE~(jc9H|4Ey%yux?toR~KbhQ2XO m-BFUKY>30dzXd1@fKP{I_Pon+TXSR>TN!&#iH+rI0CjZ=_cEdY From 4b363a2d9638879d19d64967a98557f4847ec59b Mon Sep 17 00:00:00 2001 From: Timur Sadykov Date: Sat, 19 Oct 2024 00:28:47 -0700 Subject: [PATCH 866/966] feat: making iam endpoint universe-aware (#1604) * feat: making iam endpoint universe-aware * feat: make sign and idtoken endpooints universe aware * add universe_domain parameter for the iam request * fix: test updates --------- Co-authored-by: Owl Bot --- packages/google-auth/google/auth/iam.py | 13 +- .../google/auth/impersonated_credentials.py | 16 ++- packages/google-auth/google/oauth2/_client.py | 9 +- .../google/oauth2/service_account.py | 1 + .../tests/compute_engine/test_credentials.py | 20 +++ .../google-auth/tests/oauth2/test__client.py | 2 + .../tests/oauth2/test_service_account.py | 8 +- .../tests/test_impersonated_credentials.py | 130 +++++++++++++++++- 8 files changed, 178 insertions(+), 21 deletions(-) diff --git a/packages/google-auth/google/auth/iam.py b/packages/google-auth/google/auth/iam.py index 1c2eb913b8fa..bed1930f5b14 100644 --- a/packages/google-auth/google/auth/iam.py +++ b/packages/google-auth/google/auth/iam.py @@ -35,22 +35,19 @@ http_client.GATEWAY_TIMEOUT, } - _IAM_SCOPE = ["https://www.googleapis.com/auth/iam"] _IAM_ENDPOINT = ( - "https://iamcredentials.googleapis.com/v1/projects/-" + "https://iamcredentials.{}/v1/projects/-" + "/serviceAccounts/{}:generateAccessToken" ) _IAM_SIGN_ENDPOINT = ( - "https://iamcredentials.googleapis.com/v1/projects/-" - + "/serviceAccounts/{}:signBlob" + "https://iamcredentials.{}/v1/projects/-" + "/serviceAccounts/{}:signBlob" ) _IAM_IDTOKEN_ENDPOINT = ( - "https://iamcredentials.googleapis.com/v1/" - + "projects/-/serviceAccounts/{}:generateIdToken" + "https://iamcredentials.{}/v1/" + "projects/-/serviceAccounts/{}:generateIdToken" ) @@ -90,7 +87,9 @@ def _make_signing_request(self, message): message = _helpers.to_bytes(message) method = "POST" - url = _IAM_SIGN_ENDPOINT.format(self._service_account_email) + url = _IAM_SIGN_ENDPOINT.format( + self._credentials.universe_domain, self._service_account_email + ) headers = {"Content-Type": "application/json"} body = json.dumps( {"payload": base64.b64encode(message).decode("utf-8")} diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index afac4120b347..3173a141f7df 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -46,7 +46,7 @@ def _make_iam_token_request( - request, principal, headers, body, iam_endpoint_override=None + request, principal, headers, body, universe_domain, iam_endpoint_override=None ): """Makes a request to the Google Cloud IAM service for an access token. Args: @@ -67,7 +67,9 @@ def _make_iam_token_request( `iamcredentials.googleapis.com` is not enabled or the `Service Account Token Creator` is not assigned """ - iam_endpoint = iam_endpoint_override or iam._IAM_ENDPOINT.format(principal) + iam_endpoint = iam_endpoint_override or iam._IAM_ENDPOINT.format( + universe_domain, principal + ) body = json.dumps(body).encode("utf-8") @@ -219,6 +221,8 @@ def __init__( and self._source_credentials._always_use_jwt_access ): self._source_credentials._create_self_signed_jwt(None) + + self._universe_domain = source_credentials.universe_domain self._target_principal = target_principal self._target_scopes = target_scopes self._delegates = delegates @@ -271,13 +275,16 @@ def _update_token(self, request): principal=self._target_principal, headers=headers, body=body, + universe_domain=self.universe_domain, iam_endpoint_override=self._iam_endpoint_override, ) def sign_bytes(self, message): from google.auth.transport.requests import AuthorizedSession - iam_sign_endpoint = iam._IAM_SIGN_ENDPOINT.format(self._target_principal) + iam_sign_endpoint = iam._IAM_SIGN_ENDPOINT.format( + self.universe_domain, self._target_principal + ) body = { "payload": base64.b64encode(message).decode("utf-8"), @@ -428,7 +435,8 @@ def refresh(self, request): from google.auth.transport.requests import AuthorizedSession iam_sign_endpoint = iam._IAM_IDTOKEN_ENDPOINT.format( - self._target_credentials.signer_email + self._target_credentials.universe_domain, + self._target_credentials.signer_email, ) body = { diff --git a/packages/google-auth/google/oauth2/_client.py b/packages/google-auth/google/oauth2/_client.py index 68e13ddc7344..ee56891201ce 100644 --- a/packages/google-auth/google/oauth2/_client.py +++ b/packages/google-auth/google/oauth2/_client.py @@ -319,7 +319,12 @@ def jwt_grant(request, token_uri, assertion, can_retry=True): def call_iam_generate_id_token_endpoint( - request, iam_id_token_endpoint, signer_email, audience, access_token + request, + iam_id_token_endpoint, + signer_email, + audience, + access_token, + universe_domain, ): """Call iam.generateIdToken endpoint to get ID token. @@ -339,7 +344,7 @@ def call_iam_generate_id_token_endpoint( response_data = _token_endpoint_request( request, - iam_id_token_endpoint.format(signer_email), + iam_id_token_endpoint.format(universe_domain, signer_email), body, access_token=access_token, use_json=True, diff --git a/packages/google-auth/google/oauth2/service_account.py b/packages/google-auth/google/oauth2/service_account.py index 98dafa3e3800..3e84194ac7d1 100644 --- a/packages/google-auth/google/oauth2/service_account.py +++ b/packages/google-auth/google/oauth2/service_account.py @@ -812,6 +812,7 @@ def _refresh_with_iam_endpoint(self, request): self.signer_email, self._target_audience, jwt_credentials.token.decode(), + self._universe_domain, ) @_helpers.copy_docstring(credentials.Credentials) diff --git a/packages/google-auth/tests/compute_engine/test_credentials.py b/packages/google-auth/tests/compute_engine/test_credentials.py index 662210fa412c..13983467fdb5 100644 --- a/packages/google-auth/tests/compute_engine/test_credentials.py +++ b/packages/google-auth/tests/compute_engine/test_credentials.py @@ -487,6 +487,16 @@ def test_with_target_audience_integration(self): }, ) + # mock information about universe_domain + responses.add( + responses.GET, + "http://metadata.google.internal/computeMetadata/v1/universe/" + "universe_domain", + status=200, + content_type="application/json", + json={}, + ) + # mock token for credentials responses.add( responses.GET, @@ -659,6 +669,16 @@ def test_with_quota_project_integration(self): }, ) + # stubby response about universe_domain + responses.add( + responses.GET, + "http://metadata.google.internal/computeMetadata/v1/universe/" + "universe_domain", + status=200, + content_type="application/json", + json={}, + ) + # mock sign blob endpoint signature = base64.b64encode(b"some-signature").decode("utf-8") responses.add( diff --git a/packages/google-auth/tests/oauth2/test__client.py b/packages/google-auth/tests/oauth2/test__client.py index 9da63cbdec08..6a085729f4c2 100644 --- a/packages/google-auth/tests/oauth2/test__client.py +++ b/packages/google-auth/tests/oauth2/test__client.py @@ -324,6 +324,7 @@ def test_call_iam_generate_id_token_endpoint(): "fake_email", "fake_audience", "fake_access_token", + "googleapis.com", ) assert ( @@ -361,6 +362,7 @@ def test_call_iam_generate_id_token_endpoint_no_id_token(): "fake_email", "fake_audience", "fake_access_token", + "googleapis.com", ) assert excinfo.match("No ID token in response") diff --git a/packages/google-auth/tests/oauth2/test_service_account.py b/packages/google-auth/tests/oauth2/test_service_account.py index 2c3fea5b20d8..45e0d6c91ba7 100644 --- a/packages/google-auth/tests/oauth2/test_service_account.py +++ b/packages/google-auth/tests/oauth2/test_service_account.py @@ -789,7 +789,7 @@ def test_refresh_iam_flow(self, call_iam_generate_id_token_endpoint): ) request = mock.Mock() credentials.refresh(request) - req, iam_endpoint, signer_email, target_audience, access_token = call_iam_generate_id_token_endpoint.call_args[ + req, iam_endpoint, signer_email, target_audience, access_token, universe_domain = call_iam_generate_id_token_endpoint.call_args[ 0 ] assert req == request @@ -798,6 +798,7 @@ def test_refresh_iam_flow(self, call_iam_generate_id_token_endpoint): assert target_audience == "https://example.com" decoded_access_token = jwt.decode(access_token, verify=False) assert decoded_access_token["scope"] == "https://www.googleapis.com/auth/iam" + assert universe_domain == "googleapis.com" @mock.patch( "google.oauth2._client.call_iam_generate_id_token_endpoint", autospec=True @@ -811,18 +812,19 @@ def test_refresh_iam_flow_non_gdu(self, call_iam_generate_id_token_endpoint): ) request = mock.Mock() credentials.refresh(request) - req, iam_endpoint, signer_email, target_audience, access_token = call_iam_generate_id_token_endpoint.call_args[ + req, iam_endpoint, signer_email, target_audience, access_token, universe_domain = call_iam_generate_id_token_endpoint.call_args[ 0 ] assert req == request assert ( iam_endpoint - == "https://iamcredentials.fake-universe/v1/projects/-/serviceAccounts/{}:generateIdToken" + == "https://iamcredentials.{}/v1/projects/-/serviceAccounts/{}:generateIdToken" ) assert signer_email == "service-account@example.com" assert target_audience == "https://example.com" decoded_access_token = jwt.decode(access_token, verify=False) assert decoded_access_token["scope"] == "https://www.googleapis.com/auth/iam" + assert universe_domain == "fake-universe" @mock.patch("google.oauth2._client.id_token_jwt_grant", autospec=True) def test_before_request_refreshes(self, id_token_jwt_grant): diff --git a/packages/google-auth/tests/test_impersonated_credentials.py b/packages/google-auth/tests/test_impersonated_credentials.py index f467269e2928..0fe6e2329e25 100644 --- a/packages/google-auth/tests/test_impersonated_credentials.py +++ b/packages/google-auth/tests/test_impersonated_credentials.py @@ -146,6 +146,13 @@ def test_get_cred_info(self): "principal": "impersonated@project.iam.gserviceaccount.com", } + def test_universe_domain_matching_source(self): + source_credentials = service_account.Credentials( + SIGNER, "some@email.com", TOKEN_URI, universe_domain="foo.bar" + ) + credentials = self.make_credentials(source_credentials=source_credentials) + assert credentials.universe_domain == "foo.bar" + def test__make_copy_get_cred_info(self): credentials = self.make_credentials() credentials._cred_file_path = "/path/to/file" @@ -231,6 +238,38 @@ def test_refresh_success(self, use_data_bytes, mock_donor_credentials): == ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE ) + @pytest.mark.parametrize("use_data_bytes", [True, False]) + def test_refresh_success_nonGdu(self, use_data_bytes, mock_donor_credentials): + source_credentials = service_account.Credentials( + SIGNER, "some@email.com", TOKEN_URI, universe_domain="foo.bar" + ) + credentials = self.make_credentials( + lifetime=None, source_credentials=source_credentials + ) + token = "token" + + expire_time = ( + _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=500) + ).isoformat("T") + "Z" + response_body = {"accessToken": token, "expireTime": expire_time} + + request = self.make_request( + data=json.dumps(response_body), + status=http_client.OK, + use_data_bytes=use_data_bytes, + ) + + credentials.refresh(request) + + assert credentials.valid + assert not credentials.expired + # Confirm override endpoint used. + request_kwargs = request.call_args[1] + assert ( + request_kwargs["url"] + == "https://iamcredentials.foo.bar/v1/projects/-/serviceAccounts/impersonated@project.iam.gserviceaccount.com:generateAccessToken" + ) + @pytest.mark.parametrize("use_data_bytes", [True, False]) def test_refresh_success_iam_endpoint_override( self, use_data_bytes, mock_donor_credentials @@ -397,6 +436,38 @@ def test_service_account_email(self): def test_sign_bytes(self, mock_donor_credentials, mock_authorizedsession_sign): credentials = self.make_credentials(lifetime=None) + expected_url = "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/impersonated@project.iam.gserviceaccount.com:signBlob" + self._sign_bytes_helper( + credentials, + mock_donor_credentials, + mock_authorizedsession_sign, + expected_url, + ) + + def test_sign_bytes_nonGdu( + self, mock_donor_credentials, mock_authorizedsession_sign + ): + source_credentials = service_account.Credentials( + SIGNER, "some@email.com", TOKEN_URI, universe_domain="foo.bar" + ) + credentials = self.make_credentials( + lifetime=None, source_credentials=source_credentials + ) + expected_url = "https://iamcredentials.foo.bar/v1/projects/-/serviceAccounts/impersonated@project.iam.gserviceaccount.com:signBlob" + self._sign_bytes_helper( + credentials, + mock_donor_credentials, + mock_authorizedsession_sign, + expected_url, + ) + + def _sign_bytes_helper( + self, + credentials, + mock_donor_credentials, + mock_authorizedsession_sign, + expected_url, + ): token = "token" expire_time = ( @@ -412,11 +483,19 @@ def test_sign_bytes(self, mock_donor_credentials, mock_authorizedsession_sign): request.return_value = response credentials.refresh(request) - assert credentials.valid assert not credentials.expired signature = credentials.sign_bytes(b"signed bytes") + mock_authorizedsession_sign.assert_called_with( + mock.ANY, + "POST", + expected_url, + None, + json={"payload": "c2lnbmVkIGJ5dGVz", "delegates": []}, + headers={"Content-Type": "application/json"}, + ) + assert signature == b"signature" def test_sign_bytes_failure(self): @@ -563,6 +642,45 @@ def test_id_token_from_credential( self, mock_donor_credentials, mock_authorizedsession_idtoken ): credentials = self.make_credentials(lifetime=None) + target_credentials = self.make_credentials(lifetime=None) + expected_url = "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/impersonated@project.iam.gserviceaccount.com:generateIdToken" + self._test_id_token_helper( + credentials, + target_credentials, + mock_donor_credentials, + mock_authorizedsession_idtoken, + expected_url, + ) + + def test_id_token_from_credential_nonGdu( + self, mock_donor_credentials, mock_authorizedsession_idtoken + ): + source_credentials = service_account.Credentials( + SIGNER, "some@email.com", TOKEN_URI, universe_domain="foo.bar" + ) + credentials = self.make_credentials( + lifetime=None, source_credentials=source_credentials + ) + target_credentials = self.make_credentials( + lifetime=None, source_credentials=source_credentials + ) + expected_url = "https://iamcredentials.foo.bar/v1/projects/-/serviceAccounts/impersonated@project.iam.gserviceaccount.com:generateIdToken" + self._test_id_token_helper( + credentials, + target_credentials, + mock_donor_credentials, + mock_authorizedsession_idtoken, + expected_url, + ) + + def _test_id_token_helper( + self, + credentials, + target_credentials, + mock_donor_credentials, + mock_authorizedsession_idtoken, + expected_url, + ): token = "token" target_audience = "https://foo.bar" @@ -580,17 +698,19 @@ def test_id_token_from_credential( assert credentials.valid assert not credentials.expired - new_credentials = self.make_credentials(lifetime=None) - id_creds = impersonated_credentials.IDTokenCredentials( credentials, target_audience=target_audience, include_email=True ) - id_creds = id_creds.from_credentials(target_credentials=new_credentials) + id_creds = id_creds.from_credentials(target_credentials=target_credentials) id_creds.refresh(request) + args = mock_authorizedsession_idtoken.call_args.args + + assert args[2] == expected_url + assert id_creds.token == ID_TOKEN_DATA assert id_creds._include_email is True - assert id_creds._target_credentials is new_credentials + assert id_creds._target_credentials is target_credentials def test_id_token_with_target_audience( self, mock_donor_credentials, mock_authorizedsession_idtoken From 0cb82bf32add76d7e261842c4b6f161d14ec368f Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Thu, 24 Oct 2024 21:19:07 +0000 Subject: [PATCH 867/966] fix: update secret (#1617) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index a13879adc306e55886c219a6e4ffe55218b1dc06..2d3f631e9751498b0e0f83c67065b6affcd2872f 100644 GIT binary patch literal 10324 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 literal 10324 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 From cdc28b22dbed972d02eac9c989ed240b70b09958 Mon Sep 17 00:00:00 2001 From: Timur Sadykov Date: Thu, 24 Oct 2024 15:03:37 -0700 Subject: [PATCH 868/966] fix: change universe_domain to universe-domain (#1613) --- .../google/auth/compute_engine/_metadata.py | 2 +- .../tests/compute_engine/test__metadata.py | 14 +++++++------- .../tests/compute_engine/test_credentials.py | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/google-auth/google/auth/compute_engine/_metadata.py b/packages/google-auth/google/auth/compute_engine/_metadata.py index b66d9f9b37f1..8d692972fd67 100644 --- a/packages/google-auth/google/auth/compute_engine/_metadata.py +++ b/packages/google-auth/google/auth/compute_engine/_metadata.py @@ -294,7 +294,7 @@ def get_universe_domain(request): 404 occurs while retrieving metadata. """ universe_domain = get( - request, "universe/universe_domain", return_none_for_not_found_error=True + request, "universe/universe-domain", return_none_for_not_found_error=True ) if not universe_domain: return "googleapis.com" diff --git a/packages/google-auth/tests/compute_engine/test__metadata.py b/packages/google-auth/tests/compute_engine/test__metadata.py index f49886d714ca..c5f80d8979dc 100644 --- a/packages/google-auth/tests/compute_engine/test__metadata.py +++ b/packages/google-auth/tests/compute_engine/test__metadata.py @@ -397,7 +397,7 @@ def test_get_universe_domain_success(): request.assert_called_once_with( method="GET", - url=_metadata._METADATA_ROOT + "universe/universe_domain", + url=_metadata._METADATA_ROOT + "universe/universe-domain", headers=_metadata._METADATA_HEADERS, ) assert universe_domain == "fake_universe_domain" @@ -410,7 +410,7 @@ def test_get_universe_domain_success_empty_response(): request.assert_called_once_with( method="GET", - url=_metadata._METADATA_ROOT + "universe/universe_domain", + url=_metadata._METADATA_ROOT + "universe/universe-domain", headers=_metadata._METADATA_HEADERS, ) assert universe_domain == "googleapis.com" @@ -425,7 +425,7 @@ def test_get_universe_domain_not_found(): request.assert_called_once_with( method="GET", - url=_metadata._METADATA_ROOT + "universe/universe_domain", + url=_metadata._METADATA_ROOT + "universe/universe-domain", headers=_metadata._METADATA_HEADERS, ) assert universe_domain == "googleapis.com" @@ -445,7 +445,7 @@ def test_get_universe_domain_retryable_error_failure(): request.assert_called_with( method="GET", - url=_metadata._METADATA_ROOT + "universe/universe_domain", + url=_metadata._METADATA_ROOT + "universe/universe-domain", headers=_metadata._METADATA_HEADERS, ) assert request.call_count == 5 @@ -487,12 +487,12 @@ def request(self, *args, **kwargs): request_error.assert_called_once_with( method="GET", - url=_metadata._METADATA_ROOT + "universe/universe_domain", + url=_metadata._METADATA_ROOT + "universe/universe-domain", headers=_metadata._METADATA_HEADERS, ) request_ok.assert_called_once_with( method="GET", - url=_metadata._METADATA_ROOT + "universe/universe_domain", + url=_metadata._METADATA_ROOT + "universe/universe-domain", headers=_metadata._METADATA_HEADERS, ) @@ -511,7 +511,7 @@ def test_get_universe_domain_other_error(): request.assert_called_once_with( method="GET", - url=_metadata._METADATA_ROOT + "universe/universe_domain", + url=_metadata._METADATA_ROOT + "universe/universe-domain", headers=_metadata._METADATA_HEADERS, ) diff --git a/packages/google-auth/tests/compute_engine/test_credentials.py b/packages/google-auth/tests/compute_engine/test_credentials.py index 13983467fdb5..fddfb7f64d34 100644 --- a/packages/google-auth/tests/compute_engine/test_credentials.py +++ b/packages/google-auth/tests/compute_engine/test_credentials.py @@ -491,7 +491,7 @@ def test_with_target_audience_integration(self): responses.add( responses.GET, "http://metadata.google.internal/computeMetadata/v1/universe/" - "universe_domain", + "universe-domain", status=200, content_type="application/json", json={}, @@ -673,7 +673,7 @@ def test_with_quota_project_integration(self): responses.add( responses.GET, "http://metadata.google.internal/computeMetadata/v1/universe/" - "universe_domain", + "universe-domain", status=200, content_type="application/json", json={}, From b15ef0255a7b6c8e2e3219898cafe9545dadd138 Mon Sep 17 00:00:00 2001 From: Timur Sadykov Date: Thu, 24 Oct 2024 16:20:44 -0700 Subject: [PATCH 869/966] fix: revert templates for iam endpoints (#1614) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: revert templates for iam endpoints * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * tests update --------- Co-authored-by: Owl Bot --- packages/google-auth/google/auth/iam.py | 15 +++++++++------ .../google/auth/impersonated_credentials.py | 18 +++++++++--------- packages/google-auth/google/oauth2/_client.py | 5 ++++- .../tests/oauth2/test_service_account.py | 4 +--- 4 files changed, 23 insertions(+), 19 deletions(-) diff --git a/packages/google-auth/google/auth/iam.py b/packages/google-auth/google/auth/iam.py index bed1930f5b14..dcf0dbf9d583 100644 --- a/packages/google-auth/google/auth/iam.py +++ b/packages/google-auth/google/auth/iam.py @@ -25,6 +25,7 @@ from google.auth import _exponential_backoff from google.auth import _helpers +from google.auth import credentials from google.auth import crypt from google.auth import exceptions @@ -38,16 +39,18 @@ _IAM_SCOPE = ["https://www.googleapis.com/auth/iam"] _IAM_ENDPOINT = ( - "https://iamcredentials.{}/v1/projects/-" + "https://iamcredentials.googleapis.com/v1/projects/-" + "/serviceAccounts/{}:generateAccessToken" ) _IAM_SIGN_ENDPOINT = ( - "https://iamcredentials.{}/v1/projects/-" + "/serviceAccounts/{}:signBlob" + "https://iamcredentials.googleapis.com/v1/projects/-" + + "/serviceAccounts/{}:signBlob" ) _IAM_IDTOKEN_ENDPOINT = ( - "https://iamcredentials.{}/v1/" + "projects/-/serviceAccounts/{}:generateIdToken" + "https://iamcredentials.googleapis.com/v1/" + + "projects/-/serviceAccounts/{}:generateIdToken" ) @@ -87,9 +90,9 @@ def _make_signing_request(self, message): message = _helpers.to_bytes(message) method = "POST" - url = _IAM_SIGN_ENDPOINT.format( - self._credentials.universe_domain, self._service_account_email - ) + url = _IAM_SIGN_ENDPOINT.replace( + credentials.DEFAULT_UNIVERSE_DOMAIN, self._credentials.universe_domain + ).format(self._service_account_email) headers = {"Content-Type": "application/json"} body = json.dumps( {"payload": base64.b64encode(message).decode("utf-8")} diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index 3173a141f7df..22583da349c6 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -67,9 +67,9 @@ def _make_iam_token_request( `iamcredentials.googleapis.com` is not enabled or the `Service Account Token Creator` is not assigned """ - iam_endpoint = iam_endpoint_override or iam._IAM_ENDPOINT.format( - universe_domain, principal - ) + iam_endpoint = iam_endpoint_override or iam._IAM_ENDPOINT.replace( + credentials.DEFAULT_UNIVERSE_DOMAIN, universe_domain + ).format(principal) body = json.dumps(body).encode("utf-8") @@ -282,9 +282,9 @@ def _update_token(self, request): def sign_bytes(self, message): from google.auth.transport.requests import AuthorizedSession - iam_sign_endpoint = iam._IAM_SIGN_ENDPOINT.format( - self.universe_domain, self._target_principal - ) + iam_sign_endpoint = iam._IAM_SIGN_ENDPOINT.replace( + credentials.DEFAULT_UNIVERSE_DOMAIN, self.universe_domain + ).format(self._target_principal) body = { "payload": base64.b64encode(message).decode("utf-8"), @@ -434,10 +434,10 @@ def with_quota_project(self, quota_project_id): def refresh(self, request): from google.auth.transport.requests import AuthorizedSession - iam_sign_endpoint = iam._IAM_IDTOKEN_ENDPOINT.format( + iam_sign_endpoint = iam._IAM_IDTOKEN_ENDPOINT.replace( + credentials.DEFAULT_UNIVERSE_DOMAIN, self._target_credentials.universe_domain, - self._target_credentials.signer_email, - ) + ).format(self._target_credentials.signer_email) body = { "audience": self._target_audience, diff --git a/packages/google-auth/google/oauth2/_client.py b/packages/google-auth/google/oauth2/_client.py index ee56891201ce..98d9599cf3ea 100644 --- a/packages/google-auth/google/oauth2/_client.py +++ b/packages/google-auth/google/oauth2/_client.py @@ -30,6 +30,7 @@ from google.auth import _exponential_backoff from google.auth import _helpers +from google.auth import credentials from google.auth import exceptions from google.auth import jwt from google.auth import metrics @@ -344,7 +345,9 @@ def call_iam_generate_id_token_endpoint( response_data = _token_endpoint_request( request, - iam_id_token_endpoint.format(universe_domain, signer_email), + iam_id_token_endpoint.replace( + credentials.DEFAULT_UNIVERSE_DOMAIN, universe_domain + ).format(signer_email), body, access_token=access_token, use_json=True, diff --git a/packages/google-auth/tests/oauth2/test_service_account.py b/packages/google-auth/tests/oauth2/test_service_account.py index 45e0d6c91ba7..91a7d93e0a5f 100644 --- a/packages/google-auth/tests/oauth2/test_service_account.py +++ b/packages/google-auth/tests/oauth2/test_service_account.py @@ -798,7 +798,6 @@ def test_refresh_iam_flow(self, call_iam_generate_id_token_endpoint): assert target_audience == "https://example.com" decoded_access_token = jwt.decode(access_token, verify=False) assert decoded_access_token["scope"] == "https://www.googleapis.com/auth/iam" - assert universe_domain == "googleapis.com" @mock.patch( "google.oauth2._client.call_iam_generate_id_token_endpoint", autospec=True @@ -818,13 +817,12 @@ def test_refresh_iam_flow_non_gdu(self, call_iam_generate_id_token_endpoint): assert req == request assert ( iam_endpoint - == "https://iamcredentials.{}/v1/projects/-/serviceAccounts/{}:generateIdToken" + == "https://iamcredentials.fake-universe/v1/projects/-/serviceAccounts/{}:generateIdToken" ) assert signer_email == "service-account@example.com" assert target_audience == "https://example.com" decoded_access_token = jwt.decode(access_token, verify=False) assert decoded_access_token["scope"] == "https://www.googleapis.com/auth/iam" - assert universe_domain == "fake-universe" @mock.patch("google.oauth2._client.id_token_jwt_grant", autospec=True) def test_before_request_refreshes(self, id_token_jwt_grant): From d3476ed88bae6dc85462a00c8ba880296af3fbef Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Tue, 29 Oct 2024 16:37:26 -0400 Subject: [PATCH 870/966] chore: update templated files (#1618) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: update templated files * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot --- .../google-auth/.github/.OwlBot.lock.yaml | 4 +- .../.kokoro/docker/docs/requirements.txt | 42 +- packages/google-auth/.kokoro/docs/common.cfg | 6 +- packages/google-auth/.kokoro/release.sh | 2 +- .../google-auth/.kokoro/release/common.cfg | 8 +- packages/google-auth/.kokoro/requirements.txt | 610 +++++++++--------- 6 files changed, 319 insertions(+), 353 deletions(-) diff --git a/packages/google-auth/.github/.OwlBot.lock.yaml b/packages/google-auth/.github/.OwlBot.lock.yaml index 597e0c3261ca..13fc69ce9fc9 100644 --- a/packages/google-auth/.github/.OwlBot.lock.yaml +++ b/packages/google-auth/.github/.OwlBot.lock.yaml @@ -13,5 +13,5 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:e8dcfd7cbfd8beac3a3ff8d3f3185287ea0625d859168cc80faccfc9a7a00455 -# created: 2024-09-16T21:04:09.091105552Z + digest: sha256:5efdf8d38e5a22c1ec9e5541cbdfde56399bdffcb6f531183f84ac66052a8024 +# created: 2024-10-25 diff --git a/packages/google-auth/.kokoro/docker/docs/requirements.txt b/packages/google-auth/.kokoro/docker/docs/requirements.txt index 7129c7715594..66eacc82f041 100644 --- a/packages/google-auth/.kokoro/docker/docs/requirements.txt +++ b/packages/google-auth/.kokoro/docker/docs/requirements.txt @@ -4,39 +4,39 @@ # # pip-compile --allow-unsafe --generate-hashes requirements.in # -argcomplete==3.4.0 \ - --hash=sha256:69a79e083a716173e5532e0fa3bef45f793f4e61096cf52b5a42c0211c8b8aa5 \ - --hash=sha256:c2abcdfe1be8ace47ba777d4fce319eb13bf8ad9dace8d085dcad6eded88057f +argcomplete==3.5.1 \ + --hash=sha256:1a1d148bdaa3e3b93454900163403df41448a248af01b6e849edc5ac08e6c363 \ + --hash=sha256:eb1ee355aa2557bd3d0145de7b06b2a45b0ce461e1e7813f5d066039ab4177b4 # via nox colorlog==6.8.2 \ --hash=sha256:3e3e079a41feb5a1b64f978b5ea4f46040a94f11f0e8bbb8261e3dbbeca64d44 \ --hash=sha256:4dcbb62368e2800cb3c5abd348da7e53f6c362dda502ec27c560b2e58a66bd33 # via nox -distlib==0.3.8 \ - --hash=sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784 \ - --hash=sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64 +distlib==0.3.9 \ + --hash=sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87 \ + --hash=sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403 # via virtualenv -filelock==3.15.4 \ - --hash=sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb \ - --hash=sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7 +filelock==3.16.1 \ + --hash=sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0 \ + --hash=sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435 # via virtualenv -nox==2024.4.15 \ - --hash=sha256:6492236efa15a460ecb98e7b67562a28b70da006ab0be164e8821177577c0565 \ - --hash=sha256:ecf6700199cdfa9e5ea0a41ff5e6ef4641d09508eda6edb89d9987864115817f +nox==2024.10.9 \ + --hash=sha256:1d36f309a0a2a853e9bccb76bbef6bb118ba92fa92674d15604ca99adeb29eab \ + --hash=sha256:7aa9dc8d1c27e9f45ab046ffd1c3b2c4f7c91755304769df231308849ebded95 # via -r requirements.in packaging==24.1 \ --hash=sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002 \ --hash=sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124 # via nox -platformdirs==4.2.2 \ - --hash=sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee \ - --hash=sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3 +platformdirs==4.3.6 \ + --hash=sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907 \ + --hash=sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb # via virtualenv -tomli==2.0.1 \ - --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \ - --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f +tomli==2.0.2 \ + --hash=sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38 \ + --hash=sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed # via nox -virtualenv==20.26.3 \ - --hash=sha256:4c43a2a236279d9ea36a0d76f98d84bd6ca94ac4e0f4a3b9d46d05e10fea542a \ - --hash=sha256:8cc4a31139e796e9a7de2cd5cf2489de1217193116a8fd42328f1bd65f434589 +virtualenv==20.26.6 \ + --hash=sha256:280aede09a2a5c317e409a00102e7077c6432c5a38f0ef938e643805a7ad2c48 \ + --hash=sha256:7345cc5b25405607a624d8418154577459c3e0277f5466dd79c49d5e492995f2 # via nox diff --git a/packages/google-auth/.kokoro/docs/common.cfg b/packages/google-auth/.kokoro/docs/common.cfg index 980bff5db259..a1aa466c8890 100644 --- a/packages/google-auth/.kokoro/docs/common.cfg +++ b/packages/google-auth/.kokoro/docs/common.cfg @@ -30,9 +30,9 @@ env_vars: { env_vars: { key: "V2_STAGING_BUCKET" - # Push non-cloud library docs to `docs-staging-v2-staging` instead of the + # Push non-cloud library docs to `docs-staging-v2-dev` instead of the # Cloud RAD bucket `docs-staging-v2` - value: "docs-staging-v2-staging" + value: "docs-staging-v2-dev" } # It will upload the docker image after successful builds. @@ -64,4 +64,4 @@ before_action { keyname: "docuploader_service_account" } } -} \ No newline at end of file +} diff --git a/packages/google-auth/.kokoro/release.sh b/packages/google-auth/.kokoro/release.sh index 5b3d28e51434..dcc6d1fefd63 100755 --- a/packages/google-auth/.kokoro/release.sh +++ b/packages/google-auth/.kokoro/release.sh @@ -23,7 +23,7 @@ python3 -m releasetool publish-reporter-script > /tmp/publisher-script; source / export PYTHONUNBUFFERED=1 # Move into the package, build the distribution and upload. -TWINE_PASSWORD=$(cat "${KOKORO_KEYSTORE_DIR}/73713_google-cloud-pypi-token-keystore-2") +TWINE_PASSWORD=$(cat "${KOKORO_KEYSTORE_DIR}/73713_google-cloud-pypi-token-keystore-3") cd github/google-auth-library-python python3 setup.py sdist bdist_wheel twine upload --username __token__ --password "${TWINE_PASSWORD}" dist/* diff --git a/packages/google-auth/.kokoro/release/common.cfg b/packages/google-auth/.kokoro/release/common.cfg index 062ad3aa909e..43bec962c932 100644 --- a/packages/google-auth/.kokoro/release/common.cfg +++ b/packages/google-auth/.kokoro/release/common.cfg @@ -28,17 +28,11 @@ before_action { fetch_keystore { keystore_resource { keystore_config_id: 73713 - keyname: "google-cloud-pypi-token-keystore-2" + keyname: "google-cloud-pypi-token-keystore-3" } } } -# Tokens needed to report release status back to GitHub -env_vars: { - key: "SECRET_MANAGER_KEYS" - value: "releasetool-publish-reporter-app,releasetool-publish-reporter-googleapis-installation,releasetool-publish-reporter-pem" -} - # Store the packages we uploaded to PyPI. That way, we have a record of exactly # what we published, which we can use to generate SBOMs and attestations. action { diff --git a/packages/google-auth/.kokoro/requirements.txt b/packages/google-auth/.kokoro/requirements.txt index 9622baf0ba38..006d8ef931bf 100644 --- a/packages/google-auth/.kokoro/requirements.txt +++ b/packages/google-auth/.kokoro/requirements.txt @@ -4,79 +4,94 @@ # # pip-compile --allow-unsafe --generate-hashes requirements.in # -argcomplete==3.4.0 \ - --hash=sha256:69a79e083a716173e5532e0fa3bef45f793f4e61096cf52b5a42c0211c8b8aa5 \ - --hash=sha256:c2abcdfe1be8ace47ba777d4fce319eb13bf8ad9dace8d085dcad6eded88057f +argcomplete==3.5.1 \ + --hash=sha256:1a1d148bdaa3e3b93454900163403df41448a248af01b6e849edc5ac08e6c363 \ + --hash=sha256:eb1ee355aa2557bd3d0145de7b06b2a45b0ce461e1e7813f5d066039ab4177b4 # via nox -attrs==23.2.0 \ - --hash=sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30 \ - --hash=sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1 +attrs==24.2.0 \ + --hash=sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346 \ + --hash=sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2 # via gcp-releasetool backports-tarfile==1.2.0 \ --hash=sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34 \ --hash=sha256:d75e02c268746e1b8144c278978b6e98e85de6ad16f8e4b0844a154557eca991 # via jaraco-context -cachetools==5.3.3 \ - --hash=sha256:0abad1021d3f8325b2fc1d2e9c8b9c9d57b04c3932657a72465447332c24d945 \ - --hash=sha256:ba29e2dfa0b8b556606f097407ed1aa62080ee108ab0dc5ec9d6a723a007d105 +cachetools==5.5.0 \ + --hash=sha256:02134e8439cdc2ffb62023ce1debca2944c3f289d66bb17ead3ab3dede74b292 \ + --hash=sha256:2cc24fb4cbe39633fb7badd9db9ca6295d766d9c2995f245725a46715d050f2a # via google-auth -certifi==2024.7.4 \ - --hash=sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b \ - --hash=sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90 +certifi==2024.8.30 \ + --hash=sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8 \ + --hash=sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9 # via requests -cffi==1.16.0 \ - --hash=sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc \ - --hash=sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a \ - --hash=sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417 \ - --hash=sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab \ - --hash=sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520 \ - --hash=sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36 \ - --hash=sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743 \ - --hash=sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8 \ - --hash=sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed \ - --hash=sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684 \ - --hash=sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56 \ - --hash=sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324 \ - --hash=sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d \ - --hash=sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235 \ - --hash=sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e \ - --hash=sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088 \ - --hash=sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000 \ - --hash=sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7 \ - --hash=sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e \ - --hash=sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673 \ - --hash=sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c \ - --hash=sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe \ - --hash=sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2 \ - --hash=sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098 \ - --hash=sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8 \ - --hash=sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a \ - --hash=sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0 \ - --hash=sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b \ - --hash=sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896 \ - --hash=sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e \ - --hash=sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9 \ - --hash=sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2 \ - --hash=sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b \ - --hash=sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6 \ - --hash=sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404 \ - --hash=sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f \ - --hash=sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0 \ - --hash=sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4 \ - --hash=sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc \ - --hash=sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936 \ - --hash=sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba \ - --hash=sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872 \ - --hash=sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb \ - --hash=sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614 \ - --hash=sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1 \ - --hash=sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d \ - --hash=sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969 \ - --hash=sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b \ - --hash=sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4 \ - --hash=sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627 \ - --hash=sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956 \ - --hash=sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357 +cffi==1.17.1 \ + --hash=sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8 \ + --hash=sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2 \ + --hash=sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1 \ + --hash=sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15 \ + --hash=sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36 \ + --hash=sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824 \ + --hash=sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8 \ + --hash=sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36 \ + --hash=sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17 \ + --hash=sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf \ + --hash=sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc \ + --hash=sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3 \ + --hash=sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed \ + --hash=sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702 \ + --hash=sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1 \ + --hash=sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8 \ + --hash=sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903 \ + --hash=sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6 \ + --hash=sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d \ + --hash=sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b \ + --hash=sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e \ + --hash=sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be \ + --hash=sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c \ + --hash=sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683 \ + --hash=sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9 \ + --hash=sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c \ + --hash=sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8 \ + --hash=sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1 \ + --hash=sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4 \ + --hash=sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655 \ + --hash=sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67 \ + --hash=sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595 \ + --hash=sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0 \ + --hash=sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65 \ + --hash=sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41 \ + --hash=sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6 \ + --hash=sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401 \ + --hash=sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6 \ + --hash=sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3 \ + --hash=sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16 \ + --hash=sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93 \ + --hash=sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e \ + --hash=sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4 \ + --hash=sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964 \ + --hash=sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c \ + --hash=sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576 \ + --hash=sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0 \ + --hash=sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3 \ + --hash=sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662 \ + --hash=sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3 \ + --hash=sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff \ + --hash=sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5 \ + --hash=sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd \ + --hash=sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f \ + --hash=sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5 \ + --hash=sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14 \ + --hash=sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d \ + --hash=sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9 \ + --hash=sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7 \ + --hash=sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382 \ + --hash=sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a \ + --hash=sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e \ + --hash=sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a \ + --hash=sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4 \ + --hash=sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99 \ + --hash=sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87 \ + --hash=sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b # via cryptography charset-normalizer==2.1.1 \ --hash=sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845 \ @@ -97,72 +112,67 @@ colorlog==6.8.2 \ # via # gcp-docuploader # nox -cryptography==42.0.8 \ - --hash=sha256:013629ae70b40af70c9a7a5db40abe5d9054e6f4380e50ce769947b73bf3caad \ - --hash=sha256:2346b911eb349ab547076f47f2e035fc8ff2c02380a7cbbf8d87114fa0f1c583 \ - --hash=sha256:2f66d9cd9147ee495a8374a45ca445819f8929a3efcd2e3df6428e46c3cbb10b \ - --hash=sha256:2f88d197e66c65be5e42cd72e5c18afbfae3f741742070e3019ac8f4ac57262c \ - --hash=sha256:31f721658a29331f895a5a54e7e82075554ccfb8b163a18719d342f5ffe5ecb1 \ - --hash=sha256:343728aac38decfdeecf55ecab3264b015be68fc2816ca800db649607aeee648 \ - --hash=sha256:5226d5d21ab681f432a9c1cf8b658c0cb02533eece706b155e5fbd8a0cdd3949 \ - --hash=sha256:57080dee41209e556a9a4ce60d229244f7a66ef52750f813bfbe18959770cfba \ - --hash=sha256:5a94eccb2a81a309806027e1670a358b99b8fe8bfe9f8d329f27d72c094dde8c \ - --hash=sha256:6b7c4f03ce01afd3b76cf69a5455caa9cfa3de8c8f493e0d3ab7d20611c8dae9 \ - --hash=sha256:7016f837e15b0a1c119d27ecd89b3515f01f90a8615ed5e9427e30d9cdbfed3d \ - --hash=sha256:81884c4d096c272f00aeb1f11cf62ccd39763581645b0812e99a91505fa48e0c \ - --hash=sha256:81d8a521705787afe7a18d5bfb47ea9d9cc068206270aad0b96a725022e18d2e \ - --hash=sha256:8d09d05439ce7baa8e9e95b07ec5b6c886f548deb7e0f69ef25f64b3bce842f2 \ - --hash=sha256:961e61cefdcb06e0c6d7e3a1b22ebe8b996eb2bf50614e89384be54c48c6b63d \ - --hash=sha256:9c0c1716c8447ee7dbf08d6db2e5c41c688544c61074b54fc4564196f55c25a7 \ - --hash=sha256:a0608251135d0e03111152e41f0cc2392d1e74e35703960d4190b2e0f4ca9c70 \ - --hash=sha256:a0c5b2b0585b6af82d7e385f55a8bc568abff8923af147ee3c07bd8b42cda8b2 \ - --hash=sha256:ad803773e9df0b92e0a817d22fd8a3675493f690b96130a5e24f1b8fabbea9c7 \ - --hash=sha256:b297f90c5723d04bcc8265fc2a0f86d4ea2e0f7ab4b6994459548d3a6b992a14 \ - --hash=sha256:ba4f0a211697362e89ad822e667d8d340b4d8d55fae72cdd619389fb5912eefe \ - --hash=sha256:c4783183f7cb757b73b2ae9aed6599b96338eb957233c58ca8f49a49cc32fd5e \ - --hash=sha256:c9bb2ae11bfbab395bdd072985abde58ea9860ed84e59dbc0463a5d0159f5b71 \ - --hash=sha256:cafb92b2bc622cd1aa6a1dce4b93307792633f4c5fe1f46c6b97cf67073ec961 \ - --hash=sha256:d45b940883a03e19e944456a558b67a41160e367a719833c53de6911cabba2b7 \ - --hash=sha256:dc0fdf6787f37b1c6b08e6dfc892d9d068b5bdb671198c72072828b80bd5fe4c \ - --hash=sha256:dea567d1b0e8bc5764b9443858b673b734100c2871dc93163f58c46a97a83d28 \ - --hash=sha256:dec9b018df185f08483f294cae6ccac29e7a6e0678996587363dc352dc65c842 \ - --hash=sha256:e3ec3672626e1b9e55afd0df6d774ff0e953452886e06e0f1eb7eb0c832e8902 \ - --hash=sha256:e599b53fd95357d92304510fb7bda8523ed1f79ca98dce2f43c115950aa78801 \ - --hash=sha256:fa76fbb7596cc5839320000cdd5d0955313696d9511debab7ee7278fc8b5c84a \ - --hash=sha256:fff12c88a672ab9c9c1cf7b0c80e3ad9e2ebd9d828d955c126be4fd3e5578c9e +cryptography==43.0.1 \ + --hash=sha256:014f58110f53237ace6a408b5beb6c427b64e084eb451ef25a28308270086494 \ + --hash=sha256:1bbcce1a551e262dfbafb6e6252f1ae36a248e615ca44ba302df077a846a8806 \ + --hash=sha256:203e92a75716d8cfb491dc47c79e17d0d9207ccffcbcb35f598fbe463ae3444d \ + --hash=sha256:27e613d7077ac613e399270253259d9d53872aaf657471473ebfc9a52935c062 \ + --hash=sha256:2bd51274dcd59f09dd952afb696bf9c61a7a49dfc764c04dd33ef7a6b502a1e2 \ + --hash=sha256:38926c50cff6f533f8a2dae3d7f19541432610d114a70808f0926d5aaa7121e4 \ + --hash=sha256:511f4273808ab590912a93ddb4e3914dfd8a388fed883361b02dea3791f292e1 \ + --hash=sha256:58d4e9129985185a06d849aa6df265bdd5a74ca6e1b736a77959b498e0505b85 \ + --hash=sha256:5b43d1ea6b378b54a1dc99dd8a2b5be47658fe9a7ce0a58ff0b55f4b43ef2b84 \ + --hash=sha256:61ec41068b7b74268fa86e3e9e12b9f0c21fcf65434571dbb13d954bceb08042 \ + --hash=sha256:666ae11966643886c2987b3b721899d250855718d6d9ce41b521252a17985f4d \ + --hash=sha256:68aaecc4178e90719e95298515979814bda0cbada1256a4485414860bd7ab962 \ + --hash=sha256:7c05650fe8023c5ed0d46793d4b7d7e6cd9c04e68eabe5b0aeea836e37bdcec2 \ + --hash=sha256:80eda8b3e173f0f247f711eef62be51b599b5d425c429b5d4ca6a05e9e856baa \ + --hash=sha256:8385d98f6a3bf8bb2d65a73e17ed87a3ba84f6991c155691c51112075f9ffc5d \ + --hash=sha256:88cce104c36870d70c49c7c8fd22885875d950d9ee6ab54df2745f83ba0dc365 \ + --hash=sha256:9d3cdb25fa98afdd3d0892d132b8d7139e2c087da1712041f6b762e4f807cc96 \ + --hash=sha256:a575913fb06e05e6b4b814d7f7468c2c660e8bb16d8d5a1faf9b33ccc569dd47 \ + --hash=sha256:ac119bb76b9faa00f48128b7f5679e1d8d437365c5d26f1c2c3f0da4ce1b553d \ + --hash=sha256:c1332724be35d23a854994ff0b66530119500b6053d0bd3363265f7e5e77288d \ + --hash=sha256:d03a475165f3134f773d1388aeb19c2d25ba88b6a9733c5c590b9ff7bbfa2e0c \ + --hash=sha256:d75601ad10b059ec832e78823b348bfa1a59f6b8d545db3a24fd44362a1564cb \ + --hash=sha256:de41fd81a41e53267cb020bb3a7212861da53a7d39f863585d13ea11049cf277 \ + --hash=sha256:e710bf40870f4db63c3d7d929aa9e09e4e7ee219e703f949ec4073b4294f6172 \ + --hash=sha256:ea25acb556320250756e53f9e20a4177515f012c9eaea17eb7587a8c4d8ae034 \ + --hash=sha256:f98bf604c82c416bc829e490c700ca1553eafdf2912a91e23a79d97d9801372a \ + --hash=sha256:fba1007b3ef89946dbbb515aeeb41e30203b004f0b4b00e5e16078b518563289 # via # -r requirements.in # gcp-releasetool # secretstorage -distlib==0.3.8 \ - --hash=sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784 \ - --hash=sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64 +distlib==0.3.9 \ + --hash=sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87 \ + --hash=sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403 # via virtualenv docutils==0.21.2 \ --hash=sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f \ --hash=sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2 # via readme-renderer -filelock==3.15.4 \ - --hash=sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb \ - --hash=sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7 +filelock==3.16.1 \ + --hash=sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0 \ + --hash=sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435 # via virtualenv gcp-docuploader==0.6.5 \ --hash=sha256:30221d4ac3e5a2b9c69aa52fdbef68cc3f27d0e6d0d90e220fc024584b8d2318 \ --hash=sha256:b7458ef93f605b9d46a4bf3a8dc1755dad1f31d030c8679edf304e343b347eea # via -r requirements.in -gcp-releasetool==2.0.1 \ - --hash=sha256:34314a910c08e8911d9c965bd44f8f2185c4f556e737d719c33a41f6a610de96 \ - --hash=sha256:b0d5863c6a070702b10883d37c4bdfd74bf930fe417f36c0c965d3b7c779ae62 +gcp-releasetool==2.1.1 \ + --hash=sha256:25639269f4eae510094f9dbed9894977e1966933211eb155a451deebc3fc0b30 \ + --hash=sha256:845f4ded3d9bfe8cc7fdaad789e83f4ea014affa77785259a7ddac4b243e099e # via -r requirements.in -google-api-core==2.19.1 \ - --hash=sha256:f12a9b8309b5e21d92483bbd47ce2c445861ec7d269ef6784ecc0ea8c1fa6125 \ - --hash=sha256:f4695f1e3650b316a795108a76a1c416e6afb036199d1c1f1f110916df479ffd +google-api-core==2.21.0 \ + --hash=sha256:4a152fd11a9f774ea606388d423b68aa7e6d6a0ffe4c8266f74979613ec09f81 \ + --hash=sha256:6869eacb2a37720380ba5898312af79a4d30b8bca1548fb4093e0697dc4bdf5d # via # google-cloud-core # google-cloud-storage -google-auth==2.31.0 \ - --hash=sha256:042c4702efa9f7d3c48d3a69341c209381b125faa6dbf3ebe56bc7e40ae05c23 \ - --hash=sha256:87805c36970047247c8afe614d4e3af8eceafc1ebba0c679fe75ddd1d575e871 +google-auth==2.35.0 \ + --hash=sha256:25df55f327ef021de8be50bad0dfd4a916ad0de96da86cd05661c9297723ad3f \ + --hash=sha256:f4c64ed4e01e8e8b646ef34c018f8bf3338df0c8e37d8b3bba40e7f574a3278a # via # gcp-releasetool # google-api-core @@ -172,97 +182,56 @@ google-cloud-core==2.4.1 \ --hash=sha256:9b7749272a812bde58fff28868d0c5e2f585b82f37e09a1f6ed2d4d10f134073 \ --hash=sha256:a9e6a4422b9ac5c29f79a0ede9485473338e2ce78d91f2370c01e730eab22e61 # via google-cloud-storage -google-cloud-storage==2.17.0 \ - --hash=sha256:49378abff54ef656b52dca5ef0f2eba9aa83dc2b2c72c78714b03a1a95fe9388 \ - --hash=sha256:5b393bc766b7a3bc6f5407b9e665b2450d36282614b7945e570b3480a456d1e1 +google-cloud-storage==2.18.2 \ + --hash=sha256:97a4d45c368b7d401ed48c4fdfe86e1e1cb96401c9e199e419d289e2c0370166 \ + --hash=sha256:aaf7acd70cdad9f274d29332673fcab98708d0e1f4dceb5a5356aaef06af4d99 # via gcp-docuploader -google-crc32c==1.5.0 \ - --hash=sha256:024894d9d3cfbc5943f8f230e23950cd4906b2fe004c72e29b209420a1e6b05a \ - --hash=sha256:02c65b9817512edc6a4ae7c7e987fea799d2e0ee40c53ec573a692bee24de876 \ - --hash=sha256:02ebb8bf46c13e36998aeaad1de9b48f4caf545e91d14041270d9dca767b780c \ - --hash=sha256:07eb3c611ce363c51a933bf6bd7f8e3878a51d124acfc89452a75120bc436289 \ - --hash=sha256:1034d91442ead5a95b5aaef90dbfaca8633b0247d1e41621d1e9f9db88c36298 \ - --hash=sha256:116a7c3c616dd14a3de8c64a965828b197e5f2d121fedd2f8c5585c547e87b02 \ - --hash=sha256:19e0a019d2c4dcc5e598cd4a4bc7b008546b0358bd322537c74ad47a5386884f \ - --hash=sha256:1c7abdac90433b09bad6c43a43af253e688c9cfc1c86d332aed13f9a7c7f65e2 \ - --hash=sha256:1e986b206dae4476f41bcec1faa057851f3889503a70e1bdb2378d406223994a \ - --hash=sha256:272d3892a1e1a2dbc39cc5cde96834c236d5327e2122d3aaa19f6614531bb6eb \ - --hash=sha256:278d2ed7c16cfc075c91378c4f47924c0625f5fc84b2d50d921b18b7975bd210 \ - --hash=sha256:2ad40e31093a4af319dadf503b2467ccdc8f67c72e4bcba97f8c10cb078207b5 \ - --hash=sha256:2e920d506ec85eb4ba50cd4228c2bec05642894d4c73c59b3a2fe20346bd00ee \ - --hash=sha256:3359fc442a743e870f4588fcf5dcbc1bf929df1fad8fb9905cd94e5edb02e84c \ - --hash=sha256:37933ec6e693e51a5b07505bd05de57eee12f3e8c32b07da7e73669398e6630a \ - --hash=sha256:398af5e3ba9cf768787eef45c803ff9614cc3e22a5b2f7d7ae116df8b11e3314 \ - --hash=sha256:3b747a674c20a67343cb61d43fdd9207ce5da6a99f629c6e2541aa0e89215bcd \ - --hash=sha256:461665ff58895f508e2866824a47bdee72497b091c730071f2b7575d5762ab65 \ - --hash=sha256:4c6fdd4fccbec90cc8a01fc00773fcd5fa28db683c116ee3cb35cd5da9ef6c37 \ - --hash=sha256:5829b792bf5822fd0a6f6eb34c5f81dd074f01d570ed7f36aa101d6fc7a0a6e4 \ - --hash=sha256:596d1f98fc70232fcb6590c439f43b350cb762fb5d61ce7b0e9db4539654cc13 \ - --hash=sha256:5ae44e10a8e3407dbe138984f21e536583f2bba1be9491239f942c2464ac0894 \ - --hash=sha256:635f5d4dd18758a1fbd1049a8e8d2fee4ffed124462d837d1a02a0e009c3ab31 \ - --hash=sha256:64e52e2b3970bd891309c113b54cf0e4384762c934d5ae56e283f9a0afcd953e \ - --hash=sha256:66741ef4ee08ea0b2cc3c86916ab66b6aef03768525627fd6a1b34968b4e3709 \ - --hash=sha256:67b741654b851abafb7bc625b6d1cdd520a379074e64b6a128e3b688c3c04740 \ - --hash=sha256:6ac08d24c1f16bd2bf5eca8eaf8304812f44af5cfe5062006ec676e7e1d50afc \ - --hash=sha256:6f998db4e71b645350b9ac28a2167e6632c239963ca9da411523bb439c5c514d \ - --hash=sha256:72218785ce41b9cfd2fc1d6a017dc1ff7acfc4c17d01053265c41a2c0cc39b8c \ - --hash=sha256:74dea7751d98034887dbd821b7aae3e1d36eda111d6ca36c206c44478035709c \ - --hash=sha256:759ce4851a4bb15ecabae28f4d2e18983c244eddd767f560165563bf9aefbc8d \ - --hash=sha256:77e2fd3057c9d78e225fa0a2160f96b64a824de17840351b26825b0848022906 \ - --hash=sha256:7c074fece789b5034b9b1404a1f8208fc2d4c6ce9decdd16e8220c5a793e6f61 \ - --hash=sha256:7c42c70cd1d362284289c6273adda4c6af8039a8ae12dc451dcd61cdabb8ab57 \ - --hash=sha256:7f57f14606cd1dd0f0de396e1e53824c371e9544a822648cd76c034d209b559c \ - --hash=sha256:83c681c526a3439b5cf94f7420471705bbf96262f49a6fe546a6db5f687a3d4a \ - --hash=sha256:8485b340a6a9e76c62a7dce3c98e5f102c9219f4cfbf896a00cf48caf078d438 \ - --hash=sha256:84e6e8cd997930fc66d5bb4fde61e2b62ba19d62b7abd7a69920406f9ecca946 \ - --hash=sha256:89284716bc6a5a415d4eaa11b1726d2d60a0cd12aadf5439828353662ede9dd7 \ - --hash=sha256:8b87e1a59c38f275c0e3676fc2ab6d59eccecfd460be267ac360cc31f7bcde96 \ - --hash=sha256:8f24ed114432de109aa9fd317278518a5af2d31ac2ea6b952b2f7782b43da091 \ - --hash=sha256:98cb4d057f285bd80d8778ebc4fde6b4d509ac3f331758fb1528b733215443ae \ - --hash=sha256:998679bf62b7fb599d2878aa3ed06b9ce688b8974893e7223c60db155f26bd8d \ - --hash=sha256:9ba053c5f50430a3fcfd36f75aff9caeba0440b2d076afdb79a318d6ca245f88 \ - --hash=sha256:9c99616c853bb585301df6de07ca2cadad344fd1ada6d62bb30aec05219c45d2 \ - --hash=sha256:a1fd716e7a01f8e717490fbe2e431d2905ab8aa598b9b12f8d10abebb36b04dd \ - --hash=sha256:a2355cba1f4ad8b6988a4ca3feed5bff33f6af2d7f134852cf279c2aebfde541 \ - --hash=sha256:b1f8133c9a275df5613a451e73f36c2aea4fe13c5c8997e22cf355ebd7bd0728 \ - --hash=sha256:b8667b48e7a7ef66afba2c81e1094ef526388d35b873966d8a9a447974ed9178 \ - --hash=sha256:ba1eb1843304b1e5537e1fca632fa894d6f6deca8d6389636ee5b4797affb968 \ - --hash=sha256:be82c3c8cfb15b30f36768797a640e800513793d6ae1724aaaafe5bf86f8f346 \ - --hash=sha256:c02ec1c5856179f171e032a31d6f8bf84e5a75c45c33b2e20a3de353b266ebd8 \ - --hash=sha256:c672d99a345849301784604bfeaeba4db0c7aae50b95be04dd651fd2a7310b93 \ - --hash=sha256:c6c777a480337ac14f38564ac88ae82d4cd238bf293f0a22295b66eb89ffced7 \ - --hash=sha256:cae0274952c079886567f3f4f685bcaf5708f0a23a5f5216fdab71f81a6c0273 \ - --hash=sha256:cd67cf24a553339d5062eff51013780a00d6f97a39ca062781d06b3a73b15462 \ - --hash=sha256:d3515f198eaa2f0ed49f8819d5732d70698c3fa37384146079b3799b97667a94 \ - --hash=sha256:d5280312b9af0976231f9e317c20e4a61cd2f9629b7bfea6a693d1878a264ebd \ - --hash=sha256:de06adc872bcd8c2a4e0dc51250e9e65ef2ca91be023b9d13ebd67c2ba552e1e \ - --hash=sha256:e1674e4307fa3024fc897ca774e9c7562c957af85df55efe2988ed9056dc4e57 \ - --hash=sha256:e2096eddb4e7c7bdae4bd69ad364e55e07b8316653234a56552d9c988bd2d61b \ - --hash=sha256:e560628513ed34759456a416bf86b54b2476c59144a9138165c9a1575801d0d9 \ - --hash=sha256:edfedb64740750e1a3b16152620220f51d58ff1b4abceb339ca92e934775c27a \ - --hash=sha256:f13cae8cc389a440def0c8c52057f37359014ccbc9dc1f0827936bcd367c6100 \ - --hash=sha256:f314013e7dcd5cf45ab1945d92e713eec788166262ae8deb2cfacd53def27325 \ - --hash=sha256:f583edb943cf2e09c60441b910d6a20b4d9d626c75a36c8fcac01a6c96c01183 \ - --hash=sha256:fd8536e902db7e365f49e7d9029283403974ccf29b13fc7028b97e2295b33556 \ - --hash=sha256:fe70e325aa68fa4b5edf7d1a4b6f691eb04bbccac0ace68e34820d283b5f80d4 +google-crc32c==1.6.0 \ + --hash=sha256:05e2d8c9a2f853ff116db9706b4a27350587f341eda835f46db3c0a8c8ce2f24 \ + --hash=sha256:18e311c64008f1f1379158158bb3f0c8d72635b9eb4f9545f8cf990c5668e59d \ + --hash=sha256:236c87a46cdf06384f614e9092b82c05f81bd34b80248021f729396a78e55d7e \ + --hash=sha256:35834855408429cecf495cac67ccbab802de269e948e27478b1e47dfb6465e57 \ + --hash=sha256:386122eeaaa76951a8196310432c5b0ef3b53590ef4c317ec7588ec554fec5d2 \ + --hash=sha256:40b05ab32a5067525670880eb5d169529089a26fe35dce8891127aeddc1950e8 \ + --hash=sha256:48abd62ca76a2cbe034542ed1b6aee851b6f28aaca4e6551b5599b6f3ef175cc \ + --hash=sha256:50cf2a96da226dcbff8671233ecf37bf6e95de98b2a2ebadbfdf455e6d05df42 \ + --hash=sha256:51c4f54dd8c6dfeb58d1df5e4f7f97df8abf17a36626a217f169893d1d7f3e9f \ + --hash=sha256:5bcc90b34df28a4b38653c36bb5ada35671ad105c99cfe915fb5bed7ad6924aa \ + --hash=sha256:62f6d4a29fea082ac4a3c9be5e415218255cf11684ac6ef5488eea0c9132689b \ + --hash=sha256:6eceb6ad197656a1ff49ebfbbfa870678c75be4344feb35ac1edf694309413dc \ + --hash=sha256:7aec8e88a3583515f9e0957fe4f5f6d8d4997e36d0f61624e70469771584c760 \ + --hash=sha256:91ca8145b060679ec9176e6de4f89b07363d6805bd4760631ef254905503598d \ + --hash=sha256:a184243544811e4a50d345838a883733461e67578959ac59964e43cca2c791e7 \ + --hash=sha256:a9e4b426c3702f3cd23b933436487eb34e01e00327fac20c9aebb68ccf34117d \ + --hash=sha256:bb0966e1c50d0ef5bc743312cc730b533491d60585a9a08f897274e57c3f70e0 \ + --hash=sha256:bb8b3c75bd157010459b15222c3fd30577042a7060e29d42dabce449c087f2b3 \ + --hash=sha256:bd5e7d2445d1a958c266bfa5d04c39932dc54093fa391736dbfdb0f1929c1fb3 \ + --hash=sha256:c87d98c7c4a69066fd31701c4e10d178a648c2cac3452e62c6b24dc51f9fcc00 \ + --hash=sha256:d2952396dc604544ea7476b33fe87faedc24d666fb0c2d5ac971a2b9576ab871 \ + --hash=sha256:d8797406499f28b5ef791f339594b0b5fdedf54e203b5066675c406ba69d705c \ + --hash=sha256:d9e9913f7bd69e093b81da4535ce27af842e7bf371cde42d1ae9e9bd382dc0e9 \ + --hash=sha256:e2806553238cd076f0a55bddab37a532b53580e699ed8e5606d0de1f856b5205 \ + --hash=sha256:ebab974b1687509e5c973b5c4b8b146683e101e102e17a86bd196ecaa4d099fc \ + --hash=sha256:ed767bf4ba90104c1216b68111613f0d5926fb3780660ea1198fc469af410e9d \ + --hash=sha256:f7a1fc29803712f80879b0806cb83ab24ce62fc8daf0569f2204a0cfd7f68ed4 # via # google-cloud-storage # google-resumable-media -google-resumable-media==2.7.1 \ - --hash=sha256:103ebc4ba331ab1bfdac0250f8033627a2cd7cde09e7ccff9181e31ba4315b2c \ - --hash=sha256:eae451a7b2e2cdbaaa0fd2eb00cc8a1ee5e95e16b55597359cbc3d27d7d90e33 +google-resumable-media==2.7.2 \ + --hash=sha256:3ce7551e9fe6d99e9a126101d2536612bb73486721951e9562fee0f90c6ababa \ + --hash=sha256:5280aed4629f2b60b847b0d42f9857fd4935c11af266744df33d8074cae92fe0 # via google-cloud-storage -googleapis-common-protos==1.63.2 \ - --hash=sha256:27a2499c7e8aff199665b22741997e485eccc8645aa9176c7c988e6fae507945 \ - --hash=sha256:27c5abdffc4911f28101e635de1533fb4cfd2c37fbaa9174587c799fac90aa87 +googleapis-common-protos==1.65.0 \ + --hash=sha256:2972e6c496f435b92590fd54045060867f3fe9be2c82ab148fc8885035479a63 \ + --hash=sha256:334a29d07cddc3aa01dee4988f9afd9b2916ee2ff49d6b757155dc0d197852c0 # via google-api-core -idna==3.7 \ - --hash=sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc \ - --hash=sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0 +idna==3.10 \ + --hash=sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9 \ + --hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3 # via requests -importlib-metadata==8.0.0 \ - --hash=sha256:15584cf2b1bf449d98ff8a6ff1abef57bf20f3ac6454f431736cd3e660921b2f \ - --hash=sha256:188bd24e4c346d3f0a933f275c2fec67050326a856b9a359881d7c2a697e8812 +importlib-metadata==8.5.0 \ + --hash=sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b \ + --hash=sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7 # via # -r requirements.in # keyring @@ -271,13 +240,13 @@ jaraco-classes==3.4.0 \ --hash=sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd \ --hash=sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790 # via keyring -jaraco-context==5.3.0 \ - --hash=sha256:3e16388f7da43d384a1a7cd3452e72e14732ac9fe459678773a3608a812bf266 \ - --hash=sha256:c2f67165ce1f9be20f32f650f25d8edfc1646a8aeee48ae06fb35f90763576d2 +jaraco-context==6.0.1 \ + --hash=sha256:9bae4ea555cf0b14938dc0aee7c9f32ed303aa20a3b73e7dc80111628792d1b3 \ + --hash=sha256:f797fc481b490edb305122c9181830a3a5b76d84ef6d1aef2fb9b47ab956f9e4 # via keyring -jaraco-functools==4.0.1 \ - --hash=sha256:3b24ccb921d6b593bdceb56ce14799204f473976e2a9d4b15b04d0f2c2326664 \ - --hash=sha256:d33fa765374c0611b52f8b3a795f8900869aa88c84769d4d1746cd68fb28c3e8 +jaraco-functools==4.1.0 \ + --hash=sha256:70f7e0e2ae076498e212562325e805204fc092d7b4c17e0e86c959e249701a9d \ + --hash=sha256:ad159f13428bc4acbf5541ad6dec511f91573b90fba04df61dafa2a1231cf649 # via keyring jeepney==0.8.0 \ --hash=sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806 \ @@ -289,9 +258,9 @@ jinja2==3.1.4 \ --hash=sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369 \ --hash=sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d # via gcp-releasetool -keyring==25.2.1 \ - --hash=sha256:2458681cdefc0dbc0b7eb6cf75d0b98e59f9ad9b2d4edd319d18f68bdca95e50 \ - --hash=sha256:daaffd42dbda25ddafb1ad5fec4024e5bbcfe424597ca1ca452b299861e49f1b +keyring==25.4.1 \ + --hash=sha256:5426f817cf7f6f007ba5ec722b1bcad95a75b27d780343772ad76b17cb47b0bf \ + --hash=sha256:b07ebc55f3e8ed86ac81dd31ef14e81ace9dd9c3d4b5d77a6e9a2016d0d71a1b # via # gcp-releasetool # twine @@ -299,75 +268,76 @@ markdown-it-py==3.0.0 \ --hash=sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1 \ --hash=sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb # via rich -markupsafe==2.1.5 \ - --hash=sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf \ - --hash=sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff \ - --hash=sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f \ - --hash=sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3 \ - --hash=sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532 \ - --hash=sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f \ - --hash=sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617 \ - --hash=sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df \ - --hash=sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4 \ - --hash=sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906 \ - --hash=sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f \ - --hash=sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4 \ - --hash=sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8 \ - --hash=sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371 \ - --hash=sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2 \ - --hash=sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465 \ - --hash=sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52 \ - --hash=sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6 \ - --hash=sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169 \ - --hash=sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad \ - --hash=sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2 \ - --hash=sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0 \ - --hash=sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029 \ - --hash=sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f \ - --hash=sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a \ - --hash=sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced \ - --hash=sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5 \ - --hash=sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c \ - --hash=sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf \ - --hash=sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9 \ - --hash=sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb \ - --hash=sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad \ - --hash=sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3 \ - --hash=sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1 \ - --hash=sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46 \ - --hash=sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc \ - --hash=sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a \ - --hash=sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee \ - --hash=sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900 \ - --hash=sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5 \ - --hash=sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea \ - --hash=sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f \ - --hash=sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5 \ - --hash=sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e \ - --hash=sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a \ - --hash=sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f \ - --hash=sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50 \ - --hash=sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a \ - --hash=sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b \ - --hash=sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4 \ - --hash=sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff \ - --hash=sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2 \ - --hash=sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46 \ - --hash=sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b \ - --hash=sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf \ - --hash=sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5 \ - --hash=sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5 \ - --hash=sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab \ - --hash=sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd \ - --hash=sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68 +markupsafe==3.0.1 \ + --hash=sha256:0778de17cff1acaeccc3ff30cd99a3fd5c50fc58ad3d6c0e0c4c58092b859396 \ + --hash=sha256:0f84af7e813784feb4d5e4ff7db633aba6c8ca64a833f61d8e4eade234ef0c38 \ + --hash=sha256:17b2aea42a7280db02ac644db1d634ad47dcc96faf38ab304fe26ba2680d359a \ + --hash=sha256:242d6860f1fd9191aef5fae22b51c5c19767f93fb9ead4d21924e0bcb17619d8 \ + --hash=sha256:244dbe463d5fb6d7ce161301a03a6fe744dac9072328ba9fc82289238582697b \ + --hash=sha256:26627785a54a947f6d7336ce5963569b5d75614619e75193bdb4e06e21d447ad \ + --hash=sha256:2a4b34a8d14649315c4bc26bbfa352663eb51d146e35eef231dd739d54a5430a \ + --hash=sha256:2ae99f31f47d849758a687102afdd05bd3d3ff7dbab0a8f1587981b58a76152a \ + --hash=sha256:312387403cd40699ab91d50735ea7a507b788091c416dd007eac54434aee51da \ + --hash=sha256:3341c043c37d78cc5ae6e3e305e988532b072329639007fd408a476642a89fd6 \ + --hash=sha256:33d1c36b90e570ba7785dacd1faaf091203d9942bc036118fab8110a401eb1a8 \ + --hash=sha256:3e683ee4f5d0fa2dde4db77ed8dd8a876686e3fc417655c2ece9a90576905344 \ + --hash=sha256:3ffb4a8e7d46ed96ae48805746755fadd0909fea2306f93d5d8233ba23dda12a \ + --hash=sha256:40621d60d0e58aa573b68ac5e2d6b20d44392878e0bfc159012a5787c4e35bc8 \ + --hash=sha256:40f1e10d51c92859765522cbd79c5c8989f40f0419614bcdc5015e7b6bf97fc5 \ + --hash=sha256:45d42d132cff577c92bfba536aefcfea7e26efb975bd455db4e6602f5c9f45e7 \ + --hash=sha256:48488d999ed50ba8d38c581d67e496f955821dc183883550a6fbc7f1aefdc170 \ + --hash=sha256:4935dd7883f1d50e2ffecca0aa33dc1946a94c8f3fdafb8df5c330e48f71b132 \ + --hash=sha256:4c2d64fdba74ad16138300815cfdc6ab2f4647e23ced81f59e940d7d4a1469d9 \ + --hash=sha256:4c8817557d0de9349109acb38b9dd570b03cc5014e8aabf1cbddc6e81005becd \ + --hash=sha256:4ffaaac913c3f7345579db4f33b0020db693f302ca5137f106060316761beea9 \ + --hash=sha256:5a4cb365cb49b750bdb60b846b0c0bc49ed62e59a76635095a179d440540c346 \ + --hash=sha256:62fada2c942702ef8952754abfc1a9f7658a4d5460fabe95ac7ec2cbe0d02abc \ + --hash=sha256:67c519635a4f64e495c50e3107d9b4075aec33634272b5db1cde839e07367589 \ + --hash=sha256:6a54c43d3ec4cf2a39f4387ad044221c66a376e58c0d0e971d47c475ba79c6b5 \ + --hash=sha256:7044312a928a66a4c2a22644147bc61a199c1709712069a344a3fb5cfcf16915 \ + --hash=sha256:730d86af59e0e43ce277bb83970530dd223bf7f2a838e086b50affa6ec5f9295 \ + --hash=sha256:800100d45176652ded796134277ecb13640c1a537cad3b8b53da45aa96330453 \ + --hash=sha256:80fcbf3add8790caddfab6764bde258b5d09aefbe9169c183f88a7410f0f6dea \ + --hash=sha256:82b5dba6eb1bcc29cc305a18a3c5365d2af06ee71b123216416f7e20d2a84e5b \ + --hash=sha256:852dc840f6d7c985603e60b5deaae1d89c56cb038b577f6b5b8c808c97580f1d \ + --hash=sha256:8ad4ad1429cd4f315f32ef263c1342166695fad76c100c5d979c45d5570ed58b \ + --hash=sha256:8ae369e84466aa70f3154ee23c1451fda10a8ee1b63923ce76667e3077f2b0c4 \ + --hash=sha256:93e8248d650e7e9d49e8251f883eed60ecbc0e8ffd6349e18550925e31bd029b \ + --hash=sha256:973a371a55ce9ed333a3a0f8e0bcfae9e0d637711534bcb11e130af2ab9334e7 \ + --hash=sha256:9ba25a71ebf05b9bb0e2ae99f8bc08a07ee8e98c612175087112656ca0f5c8bf \ + --hash=sha256:a10860e00ded1dd0a65b83e717af28845bb7bd16d8ace40fe5531491de76b79f \ + --hash=sha256:a4792d3b3a6dfafefdf8e937f14906a51bd27025a36f4b188728a73382231d91 \ + --hash=sha256:a7420ceda262dbb4b8d839a4ec63d61c261e4e77677ed7c66c99f4e7cb5030dd \ + --hash=sha256:ad91738f14eb8da0ff82f2acd0098b6257621410dcbd4df20aaa5b4233d75a50 \ + --hash=sha256:b6a387d61fe41cdf7ea95b38e9af11cfb1a63499af2759444b99185c4ab33f5b \ + --hash=sha256:b954093679d5750495725ea6f88409946d69cfb25ea7b4c846eef5044194f583 \ + --hash=sha256:bbde71a705f8e9e4c3e9e33db69341d040c827c7afa6789b14c6e16776074f5a \ + --hash=sha256:beeebf760a9c1f4c07ef6a53465e8cfa776ea6a2021eda0d0417ec41043fe984 \ + --hash=sha256:c91b394f7601438ff79a4b93d16be92f216adb57d813a78be4446fe0f6bc2d8c \ + --hash=sha256:c97ff7fedf56d86bae92fa0a646ce1a0ec7509a7578e1ed238731ba13aabcd1c \ + --hash=sha256:cb53e2a99df28eee3b5f4fea166020d3ef9116fdc5764bc5117486e6d1211b25 \ + --hash=sha256:cbf445eb5628981a80f54087f9acdbf84f9b7d862756110d172993b9a5ae81aa \ + --hash=sha256:d06b24c686a34c86c8c1fba923181eae6b10565e4d80bdd7bc1c8e2f11247aa4 \ + --hash=sha256:d98e66a24497637dd31ccab090b34392dddb1f2f811c4b4cd80c230205c074a3 \ + --hash=sha256:db15ce28e1e127a0013dfb8ac243a8e392db8c61eae113337536edb28bdc1f97 \ + --hash=sha256:db842712984e91707437461930e6011e60b39136c7331e971952bb30465bc1a1 \ + --hash=sha256:e24bfe89c6ac4c31792793ad9f861b8f6dc4546ac6dc8f1c9083c7c4f2b335cd \ + --hash=sha256:e81c52638315ff4ac1b533d427f50bc0afc746deb949210bc85f05d4f15fd772 \ + --hash=sha256:e9393357f19954248b00bed7c56f29a25c930593a77630c719653d51e7669c2a \ + --hash=sha256:ee3941769bd2522fe39222206f6dd97ae83c442a94c90f2b7a25d847d40f4729 \ + --hash=sha256:f31ae06f1328595d762c9a2bf29dafd8621c7d3adc130cbb46278079758779ca \ + --hash=sha256:f94190df587738280d544971500b9cafc9b950d32efcb1fba9ac10d84e6aa4e6 \ + --hash=sha256:fa7d686ed9883f3d664d39d5a8e74d3c5f63e603c2e3ff0abcba23eac6542635 \ + --hash=sha256:fb532dd9900381d2e8f48172ddc5a59db4c445a11b9fab40b3b786da40d3b56b \ + --hash=sha256:fe32482b37b4b00c7a52a07211b479653b7fe4f22b2e481b9a9b099d8a430f2f # via jinja2 mdurl==0.1.2 \ --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \ --hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba # via markdown-it-py -more-itertools==10.3.0 \ - --hash=sha256:e5d93ef411224fbcef366a6e8ddc4c5781bc6359d43412a65dd5964e46111463 \ - --hash=sha256:ea6a02e24a9161e51faad17a8782b92a0df82c12c1c8886fec7f0c3fa1a1b320 +more-itertools==10.5.0 \ + --hash=sha256:037b0d3203ce90cca8ab1defbbdac29d5f993fc20131f3664dc8d6acfa872aef \ + --hash=sha256:5482bfef7849c25dc3c6dd53a6173ae4795da2a41a80faea6700d9f5846c5da6 # via # jaraco-classes # jaraco-functools @@ -389,9 +359,9 @@ nh3==0.2.18 \ --hash=sha256:de3ceed6e661954871d6cd78b410213bdcb136f79aafe22aa7182e028b8c7307 \ --hash=sha256:f0eca9ca8628dbb4e916ae2491d72957fdd35f7a5d326b7032a345f111ac07fe # via readme-renderer -nox==2024.4.15 \ - --hash=sha256:6492236efa15a460ecb98e7b67562a28b70da006ab0be164e8821177577c0565 \ - --hash=sha256:ecf6700199cdfa9e5ea0a41ff5e6ef4641d09508eda6edb89d9987864115817f +nox==2024.10.9 \ + --hash=sha256:1d36f309a0a2a853e9bccb76bbef6bb118ba92fa92674d15604ca99adeb29eab \ + --hash=sha256:7aa9dc8d1c27e9f45ab046ffd1c3b2c4f7c91755304769df231308849ebded95 # via -r requirements.in packaging==24.1 \ --hash=sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002 \ @@ -403,41 +373,41 @@ pkginfo==1.10.0 \ --hash=sha256:5df73835398d10db79f8eecd5cd86b1f6d29317589ea70796994d49399af6297 \ --hash=sha256:889a6da2ed7ffc58ab5b900d888ddce90bce912f2d2de1dc1c26f4cb9fe65097 # via twine -platformdirs==4.2.2 \ - --hash=sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee \ - --hash=sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3 +platformdirs==4.3.6 \ + --hash=sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907 \ + --hash=sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb # via virtualenv proto-plus==1.24.0 \ --hash=sha256:30b72a5ecafe4406b0d339db35b56c4059064e69227b8c3bda7462397f966445 \ --hash=sha256:402576830425e5f6ce4c2a6702400ac79897dab0b4343821aa5188b0fab81a12 # via google-api-core -protobuf==5.27.2 \ - --hash=sha256:0e341109c609749d501986b835f667c6e1e24531096cff9d34ae411595e26505 \ - --hash=sha256:176c12b1f1c880bf7a76d9f7c75822b6a2bc3db2d28baa4d300e8ce4cde7409b \ - --hash=sha256:354d84fac2b0d76062e9b3221f4abbbacdfd2a4d8af36bab0474f3a0bb30ab38 \ - --hash=sha256:4fadd8d83e1992eed0248bc50a4a6361dc31bcccc84388c54c86e530b7f58863 \ - --hash=sha256:54330f07e4949d09614707c48b06d1a22f8ffb5763c159efd5c0928326a91470 \ - --hash=sha256:610e700f02469c4a997e58e328cac6f305f649826853813177e6290416e846c6 \ - --hash=sha256:7fc3add9e6003e026da5fc9e59b131b8f22b428b991ccd53e2af8071687b4fce \ - --hash=sha256:9e8f199bf7f97bd7ecebffcae45ebf9527603549b2b562df0fbc6d4d688f14ca \ - --hash=sha256:a109916aaac42bff84702fb5187f3edadbc7c97fc2c99c5ff81dd15dcce0d1e5 \ - --hash=sha256:b848dbe1d57ed7c191dfc4ea64b8b004a3f9ece4bf4d0d80a367b76df20bf36e \ - --hash=sha256:f3ecdef226b9af856075f28227ff2c90ce3a594d092c39bee5513573f25e2714 +protobuf==5.28.2 \ + --hash=sha256:2c69461a7fcc8e24be697624c09a839976d82ae75062b11a0972e41fd2cd9132 \ + --hash=sha256:35cfcb15f213449af7ff6198d6eb5f739c37d7e4f1c09b5d0641babf2cc0c68f \ + --hash=sha256:52235802093bd8a2811abbe8bf0ab9c5f54cca0a751fdd3f6ac2a21438bffece \ + --hash=sha256:59379674ff119717404f7454647913787034f03fe7049cbef1d74a97bb4593f0 \ + --hash=sha256:5e8a95246d581eef20471b5d5ba010d55f66740942b95ba9b872d918c459452f \ + --hash=sha256:87317e9bcda04a32f2ee82089a204d3a2f0d3c8aeed16568c7daf4756e4f1fe0 \ + --hash=sha256:8ddc60bf374785fb7cb12510b267f59067fa10087325b8e1855b898a0d81d276 \ + --hash=sha256:a8b9403fc70764b08d2f593ce44f1d2920c5077bf7d311fefec999f8c40f78b7 \ + --hash=sha256:c0ea0123dac3399a2eeb1a1443d82b7afc9ff40241433296769f7da42d142ec3 \ + --hash=sha256:ca53faf29896c526863366a52a8f4d88e69cd04ec9571ed6082fa117fac3ab36 \ + --hash=sha256:eeea10f3dc0ac7e6b4933d32db20662902b4ab81bf28df12218aa389e9c2102d # via # gcp-docuploader # gcp-releasetool # google-api-core # googleapis-common-protos # proto-plus -pyasn1==0.6.0 \ - --hash=sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c \ - --hash=sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473 +pyasn1==0.6.1 \ + --hash=sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629 \ + --hash=sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034 # via # pyasn1-modules # rsa -pyasn1-modules==0.4.0 \ - --hash=sha256:831dbcea1b177b28c9baddf4c6d1013c24c3accd14a1873fffaa6a2e905f17b6 \ - --hash=sha256:be04f15b66c206eed667e0bb5ab27e2b1855ea54a842e5037738099e8ca4ae0b +pyasn1-modules==0.4.1 \ + --hash=sha256:49bfa96b45a292b711e986f222502c1c9a5e1f4e568fc30e2574a6c7d07838fd \ + --hash=sha256:c28e2dbf9c06ad61c71a075c7e0f9fd0f1b0bb2d2ad4377f240d33ac2ab60a7c # via google-auth pycparser==2.22 \ --hash=sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6 \ @@ -449,9 +419,9 @@ pygments==2.18.0 \ # via # readme-renderer # rich -pyjwt==2.8.0 \ - --hash=sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de \ - --hash=sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320 +pyjwt==2.9.0 \ + --hash=sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850 \ + --hash=sha256:7e1e5b56cc735432a7369cbfa0efe50fa113ebecdc04ae6922deba8b84582d0c # via gcp-releasetool pyperclip==1.9.0 \ --hash=sha256:b7de0142ddc81bfc5c7507eea19da920b92252b548b96186caf94a5e2527d310 @@ -481,9 +451,9 @@ rfc3986==2.0.0 \ --hash=sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd \ --hash=sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c # via twine -rich==13.7.1 \ - --hash=sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222 \ - --hash=sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432 +rich==13.9.2 \ + --hash=sha256:51a2c62057461aaf7152b4d611168f93a9fc73068f8ded2790f29fe2b5366d0c \ + --hash=sha256:8c82a3d3f8dcfe9e734771313e606b39d8247bb6b826e196f4914b333b743cf1 # via twine rsa==4.9 \ --hash=sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7 \ @@ -499,9 +469,9 @@ six==1.16.0 \ # via # gcp-docuploader # python-dateutil -tomli==2.0.1 \ - --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \ - --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f +tomli==2.0.2 \ + --hash=sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38 \ + --hash=sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed # via nox twine==5.1.1 \ --hash=sha256:215dbe7b4b94c2c50a7315c0275d2258399280fbb7d04182c7e55e24b5f93997 \ @@ -510,28 +480,30 @@ twine==5.1.1 \ typing-extensions==4.12.2 \ --hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d \ --hash=sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8 - # via -r requirements.in -urllib3==2.2.2 \ - --hash=sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472 \ - --hash=sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168 + # via + # -r requirements.in + # rich +urllib3==2.2.3 \ + --hash=sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac \ + --hash=sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9 # via # requests # twine -virtualenv==20.26.3 \ - --hash=sha256:4c43a2a236279d9ea36a0d76f98d84bd6ca94ac4e0f4a3b9d46d05e10fea542a \ - --hash=sha256:8cc4a31139e796e9a7de2cd5cf2489de1217193116a8fd42328f1bd65f434589 +virtualenv==20.26.6 \ + --hash=sha256:280aede09a2a5c317e409a00102e7077c6432c5a38f0ef938e643805a7ad2c48 \ + --hash=sha256:7345cc5b25405607a624d8418154577459c3e0277f5466dd79c49d5e492995f2 # via nox -wheel==0.43.0 \ - --hash=sha256:465ef92c69fa5c5da2d1cf8ac40559a8c940886afcef87dcf14b9470862f1d85 \ - --hash=sha256:55c570405f142630c6b9f72fe09d9b67cf1477fcf543ae5b8dcb1f5b7377da81 +wheel==0.44.0 \ + --hash=sha256:2376a90c98cc337d18623527a97c31797bd02bad0033d41547043a1cbfbe448f \ + --hash=sha256:a29c3f2817e95ab89aa4660681ad547c0e9547f20e75b0562fe7723c9a2a9d49 # via -r requirements.in -zipp==3.19.2 \ - --hash=sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19 \ - --hash=sha256:f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c +zipp==3.20.2 \ + --hash=sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350 \ + --hash=sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29 # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: -setuptools==70.2.0 \ - --hash=sha256:b8b8060bb426838fbe942479c90296ce976249451118ef566a5a0b7d8b78fb05 \ - --hash=sha256:bd63e505105011b25c3c11f753f7e3b8465ea739efddaccef8f0efac2137bac1 +setuptools==75.1.0 \ + --hash=sha256:35ab7fd3bcd95e6b7fd704e4a1539513edad446c097797f2985e0e4b960772f2 \ + --hash=sha256:d59a21b17a275fb872a9c3dae73963160ae079f1049ed956880cd7c09b120538 # via -r requirements.in From bc222a3f76e74e6061b955f54bc404121a19ccd5 Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Wed, 30 Oct 2024 16:38:43 +0000 Subject: [PATCH 871/966] fix: update secret (#1621) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 2d3f631e9751498b0e0f83c67065b6affcd2872f..d0d4ce70215274b2e2ad05b9a5ce70940a328d5c 100644 GIT binary patch literal 10324 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 literal 10324 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 From c03b5f8c4fe22e6fdeffe25a261c9815c428956a Mon Sep 17 00:00:00 2001 From: ohmayr Date: Wed, 30 Oct 2024 13:49:10 -0400 Subject: [PATCH 872/966] fix: remove base class to avoid type conflict (#1619) * fix: remove base class to avoid type conflict * remove unused import * disable coverage --- packages/google-auth/.coveragerc | 2 ++ packages/google-auth/google/auth/transport/_requests_base.py | 3 ++- packages/google-auth/google/auth/transport/requests.py | 5 ++--- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/google-auth/.coveragerc b/packages/google-auth/.coveragerc index 9ba3d3fe66a0..4192b8cf8d3c 100644 --- a/packages/google-auth/.coveragerc +++ b/packages/google-auth/.coveragerc @@ -6,6 +6,8 @@ omit = */samples/* */conftest.py */google-cloud-sdk/lib/* + # NOTE: Temporarily disabling coverage for `_requests_base.py`. + */_requests_base.py exclude_lines = # Re-enable the standard pragma pragma: NO COVER diff --git a/packages/google-auth/google/auth/transport/_requests_base.py b/packages/google-auth/google/auth/transport/_requests_base.py index ec718d909a36..0608223d8c20 100644 --- a/packages/google-auth/google/auth/transport/_requests_base.py +++ b/packages/google-auth/google/auth/transport/_requests_base.py @@ -13,7 +13,8 @@ # limitations under the License. """Transport adapter for Base Requests.""" - +# NOTE: The coverage for this file is temporarily disabled in `.coveragerc` +# since it is currently unused. import abc diff --git a/packages/google-auth/google/auth/transport/requests.py b/packages/google-auth/google/auth/transport/requests.py index 68f67c59bdfe..23a69783dc33 100644 --- a/packages/google-auth/google/auth/transport/requests.py +++ b/packages/google-auth/google/auth/transport/requests.py @@ -38,7 +38,6 @@ from google.auth import exceptions from google.auth import transport import google.auth.transport._mtls_helper -from google.auth.transport._requests_base import _BaseAuthorizedSession from google.oauth2 import service_account _LOGGER = logging.getLogger(__name__) @@ -293,7 +292,7 @@ def proxy_manager_for(self, *args, **kwargs): return super(_MutualTlsOffloadAdapter, self).proxy_manager_for(*args, **kwargs) -class AuthorizedSession(requests.Session, _BaseAuthorizedSession): +class AuthorizedSession(requests.Session): """A Requests Session class with credentials. This class is used to perform requests to API endpoints that require @@ -390,7 +389,7 @@ def __init__( default_host=None, ): super(AuthorizedSession, self).__init__() - _BaseAuthorizedSession.__init__(self, credentials) + self.credentials = credentials self._refresh_status_codes = refresh_status_codes self._max_refresh_attempts = max_refresh_attempts self._refresh_timeout = refresh_timeout From e96920f18121f1397496694bb02b9a9205272392 Mon Sep 17 00:00:00 2001 From: Timur Sadykov Date: Wed, 30 Oct 2024 11:25:21 -0700 Subject: [PATCH 873/966] fix: adding default parameters to updated interfaces (#1622) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: adding default parameters to updated interfaces * fix: adding default UD parameter to client * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot --- .../google-auth/google/auth/impersonated_credentials.py | 7 ++++++- packages/google-auth/google/oauth2/_client.py | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index 22583da349c6..d51c8ef1e857 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -46,7 +46,12 @@ def _make_iam_token_request( - request, principal, headers, body, universe_domain, iam_endpoint_override=None + request, + principal, + headers, + body, + universe_domain=credentials.DEFAULT_UNIVERSE_DOMAIN, + iam_endpoint_override=None, ): """Makes a request to the Google Cloud IAM service for an access token. Args: diff --git a/packages/google-auth/google/oauth2/_client.py b/packages/google-auth/google/oauth2/_client.py index 98d9599cf3ea..5a9fc3503c53 100644 --- a/packages/google-auth/google/oauth2/_client.py +++ b/packages/google-auth/google/oauth2/_client.py @@ -325,7 +325,7 @@ def call_iam_generate_id_token_endpoint( signer_email, audience, access_token, - universe_domain, + universe_domain=credentials.DEFAULT_UNIVERSE_DOMAIN, ): """Call iam.generateIdToken endpoint to get ID token. From 22ab5325034d2be57fb7ee6a1971433e17f4c8c8 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 31 Oct 2024 09:22:02 -0700 Subject: [PATCH 874/966] chore(main): release 2.36.0 (#1601) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 20 ++++++++++++++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 730533a4d6f3..2404b282e018 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,26 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.36.0](https://github.com/googleapis/google-auth-library-python/compare/v2.35.0...v2.36.0) (2024-10-30) + + +### Features + +* IAM signblob retries ([#1600](https://github.com/googleapis/google-auth-library-python/issues/1600)) ([484c8db](https://github.com/googleapis/google-auth-library-python/commit/484c8db151690a4ae7b6b0ae38db0a8ede88df69)) +* Making iam endpoint universe-aware ([#1604](https://github.com/googleapis/google-auth-library-python/issues/1604)) ([16c728d](https://github.com/googleapis/google-auth-library-python/commit/16c728d30adb36e19aabe34f0ed5a95152d9a135)) +* Support External Account Authorized User as a Source Credential for impersonated credentials in ADC ([#1608](https://github.com/googleapis/google-auth-library-python/issues/1608)) ([875796c](https://github.com/googleapis/google-auth-library-python/commit/875796cbace847797898bc506eac188edbaa3192)) + + +### Bug Fixes + +* Adding default parameters to updated interfaces ([#1622](https://github.com/googleapis/google-auth-library-python/issues/1622)) ([8cf1cb1](https://github.com/googleapis/google-auth-library-python/commit/8cf1cb1663fccd03ea17a1bf055d862bddf61406)) +* Change universe_domain to universe-domain ([#1613](https://github.com/googleapis/google-auth-library-python/issues/1613)) ([168fcc6](https://github.com/googleapis/google-auth-library-python/commit/168fcc63593cb1da018b86070e20a576d5b93b7b)) +* Remove base class to avoid type conflict ([#1619](https://github.com/googleapis/google-auth-library-python/issues/1619)) ([9e2789a](https://github.com/googleapis/google-auth-library-python/commit/9e2789a208d58221292873b0095ae4e8a924a703)) +* Revert templates for iam endpoints ([#1614](https://github.com/googleapis/google-auth-library-python/issues/1614)) ([0a4363a](https://github.com/googleapis/google-auth-library-python/commit/0a4363a556455a29ececc1339385826e26459c65)) +* Update secret ([#1611](https://github.com/googleapis/google-auth-library-python/issues/1611)) ([f070de0](https://github.com/googleapis/google-auth-library-python/commit/f070de06492a5eed3fab54313272acb5f603193a)) +* Update secret ([#1617](https://github.com/googleapis/google-auth-library-python/issues/1617)) ([10f42a7](https://github.com/googleapis/google-auth-library-python/commit/10f42a70e6089f1eecd060893632dd0ef40423f7)) +* Update secret ([#1621](https://github.com/googleapis/google-auth-library-python/issues/1621)) ([6be19fb](https://github.com/googleapis/google-auth-library-python/commit/6be19fb6ad571f3a8275db8ccdd7a69cf97e1ceb)) + ## [2.35.0](https://github.com/googleapis/google-auth-library-python/compare/v2.34.0...v2.35.0) (2024-09-17) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 6610120c693a..15dc37470748 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.35.0" +__version__ = "2.36.0" From 06a541e354ab31b8ed445f48ca444efe4255dbd7 Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Fri, 8 Nov 2024 17:05:53 +0000 Subject: [PATCH 875/966] fix: improve user guide for Impersonation and SA (#1627) * fix: improve user guide for Impersonation and SA * moved sa to end --- packages/google-auth/docs/user-guide.rst | 112 ++++++++++-------- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 2 files changed, 60 insertions(+), 52 deletions(-) diff --git a/packages/google-auth/docs/user-guide.rst b/packages/google-auth/docs/user-guide.rst index e9ad000e5793..3545a8a3190a 100644 --- a/packages/google-auth/docs/user-guide.rst +++ b/packages/google-auth/docs/user-guide.rst @@ -62,57 +62,6 @@ store service account private keys locally. .. _Google Cloud SDK: https://cloud.google.com/sdk -Service account private key files -+++++++++++++++++++++++++++++++++ - -A service account private key file can be used to obtain credentials for a -service account. You can create a private key using the `Credentials page of the -Google Cloud Console`_. Once you have a private key you can either obtain -credentials one of three ways: - -1. Set the ``GOOGLE_APPLICATION_CREDENTIALS`` environment variable to the full - path to your service account private key file - - .. code-block:: bash - - $ export GOOGLE_APPLICATION_CREDENTIALS=/path/to/key.json - - Then, use :ref:`application default credentials `. - :func:`default` checks for the ``GOOGLE_APPLICATION_CREDENTIALS`` - environment variable before all other checks, so this will always use the - credentials you explicitly specify. - -2. Use :meth:`service_account.Credentials.from_service_account_file - `:: - - from google.oauth2 import service_account - - credentials = service_account.Credentials.from_service_account_file( - '/path/to/key.json') - - scoped_credentials = credentials.with_scopes( - ['https://www.googleapis.com/auth/cloud-platform']) - -3. Use :meth:`service_account.Credentials.from_service_account_info - `:: - - import json - - from google.oauth2 import service_account - - json_acct_info = json.loads(function_to_get_json_creds()) - credentials = service_account.Credentials.from_service_account_info( - json_acct_info) - - scoped_credentials = credentials.with_scopes( - ['https://www.googleapis.com/auth/cloud-platform']) - -.. warning:: Private keys must be kept secret. If you expose your private key it - is recommended to revoke it immediately from the Google Cloud Console. - -.. _Credentials page of the Google Cloud Console: - https://console.cloud.google.com/apis/credentials - Compute Engine, Container Engine, and the App Engine flexible environment +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ @@ -231,6 +180,7 @@ You can also use :class:`google_auth_oauthlib.flow.Flow` to perform the OAuth .. _requests-oauthlib: https://requests-oauthlib.readthedocs.io/en/latest/ + External credentials (Workload identity federation) +++++++++++++++++++++++++++++++++++++++++++++++++++ @@ -981,7 +931,8 @@ Impersonated credentials ++++++++++++++++++++++++ Impersonated Credentials allows one set of credentials issued to a user or service account -to impersonate another. The source credentials must be granted +to impersonate a service account. Impersonation is the preferred way of using service account for +local development over downloading the service account key. The source credentials must be granted the "Service Account Token Creator" IAM role. :: from google.auth import impersonated_credentials @@ -1006,6 +957,63 @@ In the example above `source_credentials` does not have direct access to list bu in the target project. Using `ImpersonatedCredentials` will allow the source_credentials to assume the identity of a target_principal that does have access. +It is possible to provide a delegation chain through `delegates` paramter while +initializing the impersonated credential. Refer `create short lived credentials delegated`_ for more details on delegation chain. + +.. _create short lived credentials delegated: https://cloud.google.com/iam/docs/create-short-lived-credentials-delegated + + +Service account private key files ++++++++++++++++++++++++++++++++++ + +A service account private key file can be used to obtain credentials for a service account. If you are not +able to use any of the authentication methods listed above, you can create a private key using `Credentials page of the +Google Cloud Console`_. Once you have a private key you can obtain +credentials one of three ways: + +1. Set the ``GOOGLE_APPLICATION_CREDENTIALS`` environment variable to the full + path to your service account private key file + + .. code-block:: bash + + $ export GOOGLE_APPLICATION_CREDENTIALS=/path/to/key.json + + Then, use :ref:`application default credentials `. + :func:`default` checks for the ``GOOGLE_APPLICATION_CREDENTIALS`` + environment variable before all other checks, so this will always use the + credentials you explicitly specify. + +2. Use :meth:`service_account.Credentials.from_service_account_file + `:: + + from google.oauth2 import service_account + + credentials = service_account.Credentials.from_service_account_file( + '/path/to/key.json') + + scoped_credentials = credentials.with_scopes( + ['https://www.googleapis.com/auth/cloud-platform']) + +3. Use :meth:`service_account.Credentials.from_service_account_info + `:: + + import json + + from google.oauth2 import service_account + + json_acct_info = json.loads(function_to_get_json_creds()) + credentials = service_account.Credentials.from_service_account_info( + json_acct_info) + + scoped_credentials = credentials.with_scopes( + ['https://www.googleapis.com/auth/cloud-platform']) + +.. warning:: Private keys must be kept secret. If you expose your private key it + is recommended to revoke it immediately from the Google Cloud Console. + +.. _Credentials page of the Google Cloud Console: + https://console.cloud.google.com/apis/credentials + Downscoped credentials ++++++++++++++++++++++ diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index d0d4ce70215274b2e2ad05b9a5ce70940a328d5c..590ca713ab5ed999f5545f238a7e6c86ca2f9fc4 100644 GIT binary patch literal 10324 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 literal 10324 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 From 409b172a40dcfcd3713d7e135f8004d3f3d7bd2b Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Fri, 8 Nov 2024 10:00:35 -0800 Subject: [PATCH 876/966] chore(main): release 2.36.1 (#1630) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 2404b282e018..53359b566994 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.36.1](https://github.com/googleapis/google-auth-library-python/compare/v2.36.0...v2.36.1) (2024-11-08) + + +### Bug Fixes + +* Improve user guide for Impersonation and SA ([#1627](https://github.com/googleapis/google-auth-library-python/issues/1627)) ([656307d](https://github.com/googleapis/google-auth-library-python/commit/656307d40941d2b72bb41e15238ebabba5ab6f52)) + ## [2.36.0](https://github.com/googleapis/google-auth-library-python/compare/v2.35.0...v2.36.0) (2024-10-30) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 15dc37470748..e5bf67c06d3c 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.36.0" +__version__ = "2.36.1" From 1f61670eec3ef978644ff75b1fb831d190fc908f Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Tue, 3 Dec 2024 18:57:07 +0000 Subject: [PATCH 877/966] chore: update rt (#1640) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 590ca713ab5ed999f5545f238a7e6c86ca2f9fc4..c9c0bf7e7da656e20d94cb65facb257eb95110c4 100644 GIT binary patch literal 10324 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 literal 10324 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 From 2e2912fe387f57ba90ce655d3011f12773598444 Mon Sep 17 00:00:00 2001 From: Harkamal Jot Singh Kumar Date: Thu, 5 Dec 2024 14:50:24 -0800 Subject: [PATCH 878/966] chore: update secret (#1642) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index c9c0bf7e7da656e20d94cb65facb257eb95110c4..0ffb3cd07db9a39874bab93726de2c523a9d9c8f 100644 GIT binary patch literal 10324 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 literal 10324 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 From d34602f268f9cccdde1beba3b8fed5a083d18b2f Mon Sep 17 00:00:00 2001 From: Harkamal Jot Singh Kumar Date: Mon, 9 Dec 2024 09:31:51 -0800 Subject: [PATCH 879/966] feat: allow users to use jwk keys for verifying ID token (#1641) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add support for jwk format * update formatting * formatting changes * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * remove pyjwt --------- Co-authored-by: Owl Bot --- .../google-auth/docs/requirements-docs.txt | 2 +- .../google-auth/google/oauth2/id_token.py | 38 ++++++++++++++----- packages/google-auth/setup.py | 1 + packages/google-auth/system_tests/noxfile.py | 2 +- .../google-auth/testing/constraints-3.7.txt | 1 + packages/google-auth/testing/requirements.txt | 1 + .../google-auth/tests/oauth2/test_id_token.py | 23 +++++++++++ 7 files changed, 56 insertions(+), 12 deletions(-) diff --git a/packages/google-auth/docs/requirements-docs.txt b/packages/google-auth/docs/requirements-docs.txt index 89ad689a719a..c08477f7d3d6 100644 --- a/packages/google-auth/docs/requirements-docs.txt +++ b/packages/google-auth/docs/requirements-docs.txt @@ -2,4 +2,4 @@ cryptography sphinx-docstring-typing urllib3 requests -requests-oauthlib +requests-oauthlib \ No newline at end of file diff --git a/packages/google-auth/google/oauth2/id_token.py b/packages/google-auth/google/oauth2/id_token.py index e5dda508da2e..b68ab6b303a6 100644 --- a/packages/google-auth/google/oauth2/id_token.py +++ b/packages/google-auth/google/oauth2/id_token.py @@ -82,7 +82,8 @@ def _fetch_certs(request, certs_url): """Fetches certificates. Google-style cerificate endpoints return JSON in the format of - ``{'key id': 'x509 certificate'}``. + ``{'key id': 'x509 certificate'}`` or a certificate array according + to the JWK spec (see https://tools.ietf.org/html/rfc7517). Args: request (google.auth.transport.Request): The object used to make @@ -90,8 +91,8 @@ def _fetch_certs(request, certs_url): certs_url (str): The certificate endpoint URL. Returns: - Mapping[str, str]: A mapping of public key ID to x.509 certificate - data. + Mapping[str, str] | Mapping[str, list]: A mapping of public keys + in x.509 or JWK spec. """ response = request(certs_url, method="GET") @@ -120,7 +121,8 @@ def verify_token( intended for. If None then the audience is not verified. certs_url (str): The URL that specifies the certificates to use to verify the token. This URL should return JSON in the format of - ``{'key id': 'x509 certificate'}``. + ``{'key id': 'x509 certificate'}`` or a certificate array according to + the JWK spec (see https://tools.ietf.org/html/rfc7517). clock_skew_in_seconds (int): The clock skew used for `iat` and `exp` validation. @@ -129,12 +131,28 @@ def verify_token( """ certs = _fetch_certs(request, certs_url) - return jwt.decode( - id_token, - certs=certs, - audience=audience, - clock_skew_in_seconds=clock_skew_in_seconds, - ) + if "keys" in certs: + try: + import jwt as jwt_lib # type: ignore + except ImportError as caught_exc: # pragma: NO COVER + raise ImportError( + "The pyjwt library is not installed, please install the pyjwt package to use the jwk certs format." + ) from caught_exc + jwks_client = jwt_lib.PyJWKClient(certs_url) + signing_key = jwks_client.get_signing_key_from_jwt(id_token) + return jwt_lib.decode( + id_token, + signing_key.key, + algorithms=[signing_key.algorithm_name], + audience=audience, + ) + else: + return jwt.decode( + id_token, + certs=certs, + audience=audience, + clock_skew_in_seconds=clock_skew_in_seconds, + ) def verify_oauth2_token(id_token, request, audience=None, clock_skew_in_seconds=0): diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 4e4c0d47b63d..b5c7e627cf85 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -33,6 +33,7 @@ "requests": "requests >= 2.20.0, < 3.0.0.dev0", "reauth": "pyu2f>=0.1.5", "enterprise_cert": ["cryptography", "pyopenssl"], + "pyjwt": ["pyjwt>=2.0", "cryptography>=38.0.3"], } with io.open("README.rst", "r") as fh: diff --git a/packages/google-auth/system_tests/noxfile.py b/packages/google-auth/system_tests/noxfile.py index eb22108bb1cd..56d39224d60c 100644 --- a/packages/google-auth/system_tests/noxfile.py +++ b/packages/google-auth/system_tests/noxfile.py @@ -162,7 +162,7 @@ def configure_cloud_sdk(session, application_default_credentials, project=False) # Test sesssions TEST_DEPENDENCIES_ASYNC = ["aiohttp", "pytest-asyncio", "nest-asyncio", "mock"] -TEST_DEPENDENCIES_SYNC = ["pytest", "requests", "mock"] +TEST_DEPENDENCIES_SYNC = ["pytest", "requests", "mock", "pyjwt"] PYTHON_VERSIONS_ASYNC = ["3.7"] PYTHON_VERSIONS_SYNC = ["3.7"] diff --git a/packages/google-auth/testing/constraints-3.7.txt b/packages/google-auth/testing/constraints-3.7.txt index 6c4dd2e8cbc7..f3fd641a9206 100644 --- a/packages/google-auth/testing/constraints-3.7.txt +++ b/packages/google-auth/testing/constraints-3.7.txt @@ -11,3 +11,4 @@ setuptools==40.3.0 rsa==3.1.4 aiohttp==3.6.2 requests==2.20.0 +pyjwt==2.0 \ No newline at end of file diff --git a/packages/google-auth/testing/requirements.txt b/packages/google-auth/testing/requirements.txt index b1a29542f457..742c4a46d695 100644 --- a/packages/google-auth/testing/requirements.txt +++ b/packages/google-auth/testing/requirements.txt @@ -8,6 +8,7 @@ pytest pytest-cov pytest-localserver pyu2f +pyjwt requests urllib3 cryptography < 39.0.0 diff --git a/packages/google-auth/tests/oauth2/test_id_token.py b/packages/google-auth/tests/oauth2/test_id_token.py index 40204f9d4eb8..7d6a2248157a 100644 --- a/packages/google-auth/tests/oauth2/test_id_token.py +++ b/packages/google-auth/tests/oauth2/test_id_token.py @@ -78,6 +78,29 @@ def test_verify_token(_fetch_certs, decode): ) +@mock.patch("google.oauth2.id_token._fetch_certs", autospec=True) +@mock.patch("jwt.PyJWKClient", autospec=True) +@mock.patch("jwt.decode", autospec=True) +def test_verify_token_jwk(decode, py_jwk, _fetch_certs): + certs_url = "abc123" + data = {"keys": [{"alg": "RS256"}]} + _fetch_certs.return_value = data + result = id_token.verify_token( + mock.sentinel.token, mock.sentinel.request, certs_url=certs_url + ) + assert result == decode.return_value + py_jwk.assert_called_once_with(certs_url) + signing_key = py_jwk.return_value.get_signing_key_from_jwt + _fetch_certs.assert_called_once_with(mock.sentinel.request, certs_url) + signing_key.assert_called_once_with(mock.sentinel.token) + decode.assert_called_once_with( + mock.sentinel.token, + signing_key.return_value.key, + algorithms=[signing_key.return_value.algorithm_name], + audience=None, + ) + + @mock.patch("google.auth.jwt.decode", autospec=True) @mock.patch("google.oauth2.id_token._fetch_certs", autospec=True) def test_verify_token_args(_fetch_certs, decode): From d842f56d96bd7c1ad8bc9ec3eb92de02da3a1479 Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Wed, 11 Dec 2024 01:53:37 +0000 Subject: [PATCH 880/966] chore: update secret (#1644) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 0ffb3cd07db9a39874bab93726de2c523a9d9c8f..8c0501b3ced76a3134c0282b7b6ce1f50781ed31 100644 GIT binary patch literal 10324 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 literal 10324 zcmV-aD67{BB>?tKRTJic#KPeM7_V|N%*o7a(X>{jmm6M?-rodd`W zzlO4L?7o=U$O@RrIp1ljf1cC$J8>Y2*hVg?GY-vZ z8m;$*xpzDdy&urFRc*GG2x5{5v5GMDA$NJVPp~Vts2~wc5j#K4hf9|BJ-p{)^kIFU z&RXuVm%lWmUXm7;A2+sP)CBmx&%&7g*tx-ri=E3mDK_TdFZ zUITG%hrhZ}u4*3^JY@;)`h6*d!(1SxOe=fVQ)16-sXX`~*^;F2$1Hy}S2DaRtJU3z zgTAdX_TN+T!P+5Lz#|eQ32CN)+TJfFp&Xj(wgxJV25Rnt*4(~twzkGHZQ=~%`laAf zWz9a)Xo72G$maDvJ_zeX+9TsMSSgVv*6utqjhY7oTH`#ax;j6z<*pj(sewvhNxZ9FhQdhqonr-+;#mq&PONr*V{uZ5hc?VhVzyqg9u zItp`dLnQ=UqCxXt1jxYyoA^DMjnf;XroPWt*-}p=mXNg;2!(5W3<=<2l%7dI6yBL- zuBnqES|9*%m*A(1a~3Z$Ijlm>GI-8uNz}s{U0|moiM*izR4|?0&a=@&tAJ*bcM=BX z19{DiCBU~kX0y{kr(;5K6lWTQ8k6Vc-@QT9tSxN$eaXVnh5cjUQPH=+HC}PC3lFoF zR?tdx8i+^&+2q3IQs6u7d84Wra_g_54p~r*XM!p*v5T{i)R?ee5B(k3erFLB#@G{; zZC&&Fzex7=tOmUn5BjN**(s__tolLKlxF(?YIWwBq=6N=vW{sBmJ?qL4y_Be(LfB} zElR(MDO#4iutz*ImC_3&JfpC2IfO7*KmHY*;y5M;M}>yMea$u9f#(%(&|GW&mgl35 zw;`GB_1e-E2TjIeiXTZ335rswjgs4ogKK~%BkU2GYofB**$vo;Q+5!9Vn0R7i*F82 zsY4Ljc8mgkLIIoe%E(>kKJ)wqL9ammrg5+>H~pd9C)7Mu;DFhCMS{!6$X;M5wZjKS zb6(kZ$Q~=?z!zwQZ_RN`q?3r%=~q{3W%VbbuWFY?8*MmE`L2f@15ComYK-}eJ0c23 z^^bkInNePQg2%n}jiQp|CeH@AzmKp=_g^Xtzz}9rORXPuDdt<{PhY5a-ZGFp`5(Dy z3sfO97__Y5{X`mc2!gP{N+xd+SvG0e2h?2OXOra^s~U|zzviptbX9_SM{}PU%m+MK z4uLC8c!DZm2u~7(%1JO3ek2Y+fz6D+=<$GytU5}^8>iY9>C$$Za`?BSXe{cCnYWnL zFtiUI@`{k_pA&3xr*tc=MW@BNx;Xl{r@i*``y(5cQ*j%e!b0i6i7UE-2k#ANNQ?T$ zy5h%eWr(XQn`pmt_JeSZKvy5;#IFjHV6F43Mf5Xtl2;1ueMd6+59Ly(LZ31{a0NwKy2)xT%jhxBLWj$JLo@UR@&=%wf*<+QYEFKko z?DWCBw3p%d#amH(c_@{sYGUN~ABA0~Y1TbP5(Vy$rq&Ppm#+Zdprk?4K6wkLiEQ+Y zlLJE!e?BA!)JxS~SzFx0_IsXq^bN3xdsNy^IUv19H4QN3&P_@ts14na0g;6}UAAPZ zl(XFUMo+~H>~jqki+yq#BB)Kp5!qOvX8kA1%!f76+&7D{FK2beyrn)GtkN0^&QxTs z-jC%#NNG{Wmp_Epzap=@9V9}){K_u;$2 zI{jD{DCh2ZIaGqHe~sMK>^edevmGdtd%SrLvipxnHz%Qhv1+zgCke!Wc^H;CYz#=;M zSm+U+e>?OBK{3&8F7X*6lg7Ht^`c!@$Mal3I8&hG+KBA*RN!SHBH(z#6D}7}Lnfr~ z*S)n-ERO1AVGXvaO&^im8pMZVGNoB&sCQQY(;sTJ zV~@##MIHkN2_Uk<2y%T8!Eknd$xzstr+H1t&=om|2!pVqrOetAd2`0J1aKop+rvAN zKxg`xrW&6qvF!4`pdiaz*)a%TWn3_Pt(mrBd`%2PN%N2Z?GQ!)NsAh1j`E3tffPPCO{W@ zaCN1N2MaeChjRuokr(N%8pEgX!zq7wkkmF?(|53UPZPa6`K_&hjtIwJ50-;8Y~)Yq zo5V*x6Ep^o=2iMaUsU$Mkh@eAZ2AHf95~Z`zRw~iJhI4_@xak2ZN*OF#sVaPf0X$l zFGVDS!@gA+f-M2|zVf2Mdia0R!oR}-neV$7{meIZ7RA8{iw?w^%yj)op6;>IN!jgd zO>T~Q8NWiWDbHA*`*nMGs3#**1Z7)$JmTf*>R;5$yFf`s8Cr%)uGW-{lCqeiQ`^S@ zI_#2ddPK<7#lNGkT-sh-1)LrC1~heQPLJ_QlM}^2ef&=PZ0P6(X;20zr+)x{jqhkX zdK)8NOahswBnhsOoW3cHH>ISIreCMM@}GbXa$8E-+VeFh^Ey@4yZ8V1et1nW*1>K4 zdObkV9>Dzayn&ct*5TB?ZI)Ee?S_=*Le^_+aHHfWs!d4HFGZ44QGU}~p&=Q7G_Pmu zJ;&r8bsV_Y^Oe~jZy??N0l)EQpn78Fu4WZv3QN!igtQM)ZO51u*H8?XlQH0NdyyvH zqVbx}EenhZ!AOb)T+QZ@g;Oa1tgY?df3ENxP&T3cbq)A(-S*mMxQ~7>KG#x$?PLTJ>VnGP+^t(6paZuMi#VO9sXCxv%_M!!=A4{lcJN74u z2{t!e8Dtm65;YIrt>S6uJIjo^eQToY5**-N)Y1eZatzTox(nNpXF)gLjno@@s`44r z+7v$b6SsRpN~6%Id3)kL=@&C()C?GzhARJv%`jgq(on;sfGE`(3_xqGz-2vJmoKBc zn6-hhYp}@{yGCEN3Ytwb|9DB=_0!FD`S{FF{aF@@W~a{F`klsN_Zg?ic&a(b_guE& zVAf7=<3>|ljRS^5ZT5~cK4=ZcAblM>hivo{e<-d+dXD9R^&5;}C!in@ORQ`G@m?m_ z0>A7wK2#;)aL6-czaewqF0pvcDvH7~MxP_wf3Qz2b1Y@dFbU?iS(twS{=9X~x%5@Q z9+QYri_|^W25Jmt`o~Z{LAiyX4#^icc&Z6(gQ&IYx46oUkI!!9fl;od{6JjtGm4z> zl9PNVHe0R!Mc|R|IRHBbbd={_oHSMss1~?!7r>X9lOOK>Qz~GJQ-WOq$+V@Cb69Ac({41EK z36tjg50v(6MOK_X+iQl;+H_%bMa8R_=xwb1^|2mE)Zm2RV=Cvy94JG{mFJ*$L_${y z8o=(ZiOrqciA&F8(|8{?0$B{8IX#o23nph~%mrd(ir1{{1kN-60Mm zeHIU~&ThqHG<~{jAB-koiIs}!vY5tG5^PJ0xLYDYVJ5|JlVRf}FE28Wa9_`X-~0tj zM@#1lOCwy&Fg{cv`OqltE$L?hy1Ma@5N4IZ@eq~&mSz(aGgNJZ<8WkIv?}45Xz@n5 zWM=R)cO+%^@O6CZh6z`@2H;-* z&POzz;CoVzqH8mjCYkS%-t9*Ey+;NrEB~oGY-ZUopbn)m)0kAhwCq&=ON9i0RYMBV zx@w2yW#LAH0&--#LYI>`3t!hA<@j_0epUJ-Fb%d|nB`2bN1u~V>IhZ4SPutDFdsoX z0q|4@wuUwX3&}jES2iK-$wTa_Oa-I%UCv&Xfl}?9rD4*6Vz=?T*`dr(L>S0B%G;2O zb+JOoj9)A+WXd4!FPizqj0pnngmUjj#sJr0{TjvlMp+_kuRKaQ``Nq!(($%oLZ25j8rV{VvKl)ko>84OIR4-*j?dgq>W-R> z-BbCCTv4!9i6-|suAO&o5$H#rFibWBAX&4YWUTTRk_^_{2X1&gd( zhGA^bi;pE;Be_%(z|&Z4NRRrR6Fqr_7aVYPh;T1209@C8a$qb17rJeG%T#f{&uk~( z{5Y3Ww(78NRns^J==^%9S*9KRTL^J4``oQ_lpE=jiiSrAd=$v%o-&es!C8ebKP$Fq zHn!No@w3+W%~W66#LkZCd>p2K>{3a0ZYTcFnDowy-MsTP;*V+@SX8`-73f1)Wh!m) zEn@w%`KOe{2}lDhFr}#5!%uNVdrl^ugYk~5+1D}hV`SlWijI@|yK536ue3sx3xsj5 z|EhRUdePD@I`w)s69EDVliJ4_m;(qg_$S&R_-mEFX5Iu93am=wb(yigy0*a$IJI_U zsM4Wt$dB!uhfD#`@Tav)I&ArMZ~+#0%HeQe_zB(il+04~>b0cX5aj;~{Ja44OAZD2 zngweN`(TUixH{8OY61tInC6a1f@LA+UXa4QDEU)SY4z<;M6ItR@pMXX)U~Jur$rQupYtRad*qU`%P^6Ve?!XsjeSB0kDa6lZLG%DOv`kss0Q1Uu zHj5FW=fFkSnI7JM2M0y~HJfkc?h|nZ3r)YbcD1);76{-}AlxjkL?%=T!zz*8{G4OL zJo4dB9%eCymsnQ+X6!m*@S_4G!S#KaE@MP9!IK{V*m(j_8!JErtqWHHK@JOdBUa-E zRPbWQdv&zJXGQ@MP^%>3;AZ?ejWM4Pt0a&Ph~{o|t`S9LRM&RMYK}ayX7HF?RjmNN z6jcfRUdoMklAXA7aHCYfKn4k#?g6*~`66o05{j6swn>DwO1tEYX13L+65|XYHTORN z-?;k!%oN%E@gfQ_G1TeqV!ACW%?%O=vB~?4{yIQ{)O-k^5-2Oq5V+dhU5*`K4JWDz z1~H{bXXpbPl9zaTk0!ab#i6f*ENiv!qZf{EoOc%*oS|y4*@cX`L=TfiYhjhPANkp$ ziQV8c4!l3b(+V#h*F#(pVuEpKJb23R!mER$8ssq!^G2pPd$(lnF)H5I7?4FaXyEU8 zz1vuZXqg;cjQ1Ka1_0W@bQ zF2DMcY%?&>@a2ad-B@j}p+DUIumc*F*y)9Wy{6K|;O$3WrJ4>1#F#tmtKp=U%QF_n z^HozVlE?M=AP<~K1mq`W>v4ZPDzMzkis;Y^s^Bo!cujaX^53U2<&_0UVuBAP$b~y4 zEnqPTwCZqnVR^>ITq~YCO9wlyGID=G;V&Bw|#oppp$emPdgoWX!{{B4zs88|ONl3?k zp&3^;-uzztld3R%wQ??gTutKE=D^*O=Sr+b<%h1>vb*5g%O)4=zPor=uGsrmzvB*Z z7s5l6(R?43ZP_C$3dP9PO z`^(VjKmjADb>VK+UGd5P7AWX#sK3U6l$4h0xUAyY>eiEli|40@>$LGG3slucNM49`!^I*U~71&e{1{0iCPse|y{=w}M+; znbg#-e?%7WKI1CD$1JKay6tq|hDw;F z2L?3K@KD9~5J)?_1dzy9j$|JB7snTcD5VlGXO6V}VR6&g%qJtkTO!1oR>pX2uI{P7 zGNJVbl#oCWw{9*L;CP0eLJL%(%~jX@K7#^+AN}>G{Ct)}{}Ut50prVMdlUCHd93GU z6JY9R<+UDzva$7LcyKUEE01| z%xvxd$K-;Nd_-GEk<})cKj0(}&VvKzs^!vfDspbV!Ro)QftC`M3~8rF-G6VdJ zhC(`Hq~}=oW!beO7TR0nbaMe~qo$J|LO`Q?%w>#ae!`IvKuqS|oQ7HUa@~DTA7uv3 zlGOJiX``F7*=4xzF}yt_c7WCWr=YhDFf_P^Cn)3_kP0!w|##u$Sp2yc0#%OkCeI z5xWUU_;OJ5>I1;svE}?Y##z2$)Q@)v(GyVLGgHn_L^tD(TX&qftGsC|R7OFf>HxL_N3MHZ+e}V%9fY&v)e?$#5v$moF#^EqmPrQ$`dz#gigZdTj zeU*^=MQInM+*eCkI99J&X6d+=Z}OE;aNJlA>VE7-vFajx7!3es79zM1nO%fzIPgDy z{WsAqSPFkzEX&c1&w*O&$^5&*kFB|_e0}|%khzIRsUpu#2wQytR3;;(90A$QfG2{~ z`KyLgq>j7V{`&*3{`tZlAc{Pbm!u0>67(B+n>4We7v_dW=$3m+FKF%~^5MHiwaYUM zy9(@%nd*nrL~DrKS#xLCLZF3-?#Dsf4?66dt`L{>#U0nj2hYF7j!Z5TDtI6O!NOxc z=_U!>)uI#kVuC}x4;i$k-0Rzefug0!b-P@M*0ea556W(08ex(7p>0htD^_ZlrrT`s zr*kbvMv)J>=Rzqt>$SC6jr4WUr$8(qa2d_5jpw+y{Oi{#0HbV*S-A4N&$?gp&-UJ+ z3lVvYfjO8o4 zzg_4b9iXYhHcg2L-P3@wM)<0_b7^SFsSDU&>_#3(DU_=0x8T8ze0+NKHo%5-daAaa zzI&Ybkzzxxl{9EWds$Em(YxX2c-M@pF`(}rjRlnWv(7@6K$3U4YfBF_r+@e;vmILd z^PMz=(JYk)At;<0;DS$@XkyzY-X_?erp0cXanDZXb8x0`p@RpcfBJsjF-m3QZg2VB7$?$~52rwd zcJU0H8x#HE%$F<_+kyBjO6WIuDL5UW z%oKR#RqoOZQX28CisrYT?ar%wr{@0i#%LRnvkb?@U$uo5ASNEfg+tN;s~vCO%$n)# z%<%U()T+*+c=MI26I0$rD+sUI$k2+Nd`v!An-*+!eCFs+)yZb_JkQis0Wp4JMp2VH&FCUpn>5@N&-9rHTPPhb zoLapyFppMpUx9jlbc)J)y!G5lriJS$lEra*vj+)s4;oM+`iYNr22-3`|COSm2)Up4 zK#C<@^8V=@aLvwrif7Rv+5fxc;XQ&$ia%kJvTd7PP2Id30WaLf7hOCTQC-ZMYn@K= zOeH`tfZh({Nl>Yq1WRRk24)td%h`kI^@OjG(V_&IJRV0AEyIct??M_P^o19rpHum# zO=s($EhFsvSU;J&=hoHELsHqdD)RfE`dO|x>z2{^o$L7Tb;uTo8v&92zn-*8K=8Wo0 zejkdLj(}kiKgY>L9=X^Q>Fz@%ozeZBUcPcrP^6MwPfk8 zD^Kt7uCn~u+IU*_Uaxa*F?~Jiw`QsR*kH$yFly{T;{T+-3?q5>P4Uz}>(ZBcZi~BO zOwxdSPp^bZocIg{6rlsc(`*m(pN3$xf7286uO?b227TaW?6sEQ57^>y5GvwzYED=w zxSL~=8(Z{+)M*icS^oM4a;$8dt9i|)Jyg_SrUp_ZymR^RWS}1X!&V9h$Z2Iqy;$h%pcTFyqoX@ePVJlwRJI!4ftlkM1 zcdJnn`wNLkx6?<@w9QB{39`(?=x+0#K0MuloFz@;8{-A%6}&~TVtmCC2br%7_U=X- z&l?~?-E;Pe=P}<+YdF66zYi2NgNN`r)#68mmZLkGqbf|zW24L;3Cb{i7Frt<1jn~e z_w%@?ssmt;JQgW~>=M?-K{Y(tj%qJ@8VMrD)5&63&b!q~mA+H)c5L{Xz~^Uwwo6#a z^Zl6yfV#ERq((K@3v1&S5)`Rk&NU1p(rj#9=S+Om`kohRVx&C-?{tuyas6P%0uf?t zD($bwl6&znK#93`n*IwoJ*t@z^nSAWf>{iRj;ddTv#mvLGgxXghqJE*%a17RT=@7H zxO*h*>4d1b9)bOlGJlx`iZ6cu>`{w_RxCaz8VNj~2+?GR{zC+Qn$gB00U8Kk`2msy z={LLV(&hccz$Ygfe}_*d1Gl%XWeajRfQTD@9C;}llw99r4hWy?BTy|bewD)>lS&ny ze^-A@%B|)%C`TE046}Q2=Gz7#l4lR$)bsLWCEH$~rw1rO2GkyuG;#TjwL%iOskm0P z>=8`4f^@M9e=yiQa`doVA<=*_cJkzV;DYuNCAWlJnCx;)`*1pD*z$uQ#-BEZ-Ewk7BgmNS-zRvCRmAmn^{m}%ho zox(TGbB8n>Q4N`?{w^)E7rI1=cNO{#UGrZgC{_h`qZL%UmJCU99&YzJ@Hzis|7y~_ zTQ?*Y?eEMY`Z9JRMc;ZoOeqp;0JEM>>MZF{D)_R-dpRkxd~$LPt6r!cE9!{Zrj7bF z&nQ9v+On4rd6^Gq zeq$W0CR^N*;=~MKjPfw` zbn8P=2C86-bnk~24uVX$rAb5v0@4MnaXb*gb%sNtak44G<*=M|WjwLQeTUTx>>6cC zZu-CjKLGFHH97iKRDLp+5|gZ3q_Yl}PLz%&K1K;rmAyT%Jj1Mp^|mUrhIaUgXD^{@m})4gOX`E*;V0rl7A1d1la4BIYzXbte<~ zXn1R+%<8Yvjr$V&_^+=Osu?Z#P%^JY5|XwvlM;mQyS~P4qKOG~rPBZPrjrXmj2*vi z(w<`EB*Wa#ksjvbJM@OnB+fS)sx9hCged5l)3ko87Y)bCzenErTmColqfm?am}Ve{ z4tm(!Tqoozm$OmLQaf1LC0H1o)j7yfP4-M6im`Q}bC&!}p|mk}TzqgvU&v_&s2y;# zU`Xfw!V=qQMT?=R4za&6?!DFwnd!PUlBA!-(b}p@?C{BB*$ojz;IVWtAaS{Ni%Gay zL{E8Md3?S?ltYHAa9-hHpUv5p_VXNETxB5T{=^wS`r_^3#w@t3n=29u4|?*WwZ3 z;i@>MAI*pA+z=w?A|LR|l+XlP3ulEUdc)5{r5&)vlVao>V^i>_HNs~R7jt7&MGx0$17-sgp>Xl0=jA?)bi;-5b`zYjsn-m8{t-PsL?S|R;`#oSWr7tn_8+Ar&)!DOSp@aMYDyz5-VV)I&e zyVD8*t&+cZU8!3&(!S>bPt)Q#{8E%#r#v4C3iwQSw7gyS@s!VAR0OD|&~QjTjkm{s zZVGqi0mL0h**vUU6y;s#R>g9l86yae9g|RN5KaTn6hym=ZchX20f1gu0JV From 3532f3e033a80eb711aa228ad22f4676debbec6a Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Wed, 11 Dec 2024 12:05:11 -0800 Subject: [PATCH 881/966] chore(main): release 2.37.0 (#1643) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 53359b566994..c7f8e51ce2fa 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.37.0](https://github.com/googleapis/google-auth-library-python/compare/v2.36.1...v2.37.0) (2024-12-11) + + +### Features + +* Allow users to use jwk keys for verifying ID token ([#1641](https://github.com/googleapis/google-auth-library-python/issues/1641)) ([98c3ed9](https://github.com/googleapis/google-auth-library-python/commit/98c3ed94a25bd99e89f87f9500408e8e65d79723)) + ## [2.36.1](https://github.com/googleapis/google-auth-library-python/compare/v2.36.0...v2.36.1) (2024-11-08) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index e5bf67c06d3c..06ec7e7fb79d 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.36.1" +__version__ = "2.37.0" From d62d652e63827f0641aede0be1f099be7a885218 Mon Sep 17 00:00:00 2001 From: Harkamal Jot Singh Kumar Date: Fri, 17 Jan 2025 11:45:56 +0530 Subject: [PATCH 882/966] chore: improve error message in _metadata.py (#1652) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * improve error message * log last error response * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * update secret * update test case to make sure failure reason is included * update test * update secret --------- Co-authored-by: Owl Bot --- .../google/auth/compute_engine/_metadata.py | 12 +++++++-- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../tests/compute_engine/test__metadata.py | 24 ++++++++++++++++-- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/packages/google-auth/google/auth/compute_engine/_metadata.py b/packages/google-auth/google/auth/compute_engine/_metadata.py index 8d692972fd67..06f99de0e2ca 100644 --- a/packages/google-auth/google/auth/compute_engine/_metadata.py +++ b/packages/google-auth/google/auth/compute_engine/_metadata.py @@ -201,7 +201,7 @@ def get( url = _helpers.update_query(base_url, query_params) backoff = ExponentialBackoff(total_attempts=retry_count) - + failure_reason = None for attempt in backoff: try: response = request(url=url, method="GET", headers=headers_to_use) @@ -213,6 +213,11 @@ def get( retry_count, response.status, ) + failure_reason = ( + response.data.decode("utf-8") + if hasattr(response.data, "decode") + else response.data + ) continue else: break @@ -225,10 +230,13 @@ def get( retry_count, e, ) + failure_reason = e else: raise exceptions.TransportError( "Failed to retrieve {} from the Google Compute Engine " - "metadata service. Compute Engine Metadata server unavailable".format(url) + "metadata service. Compute Engine Metadata server unavailable due to {}".format( + url, failure_reason + ) ) content = _helpers.from_bytes(response.data) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 8c0501b3ced76a3134c0282b7b6ce1f50781ed31..feb9c5fc55768eea62d5e0c7eba1f450d9e985e6 100644 GIT binary patch literal 10324 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

        yz;`mge$0jUT;J2`9CKeyo}$_OghWB- zJK@`h-d{}3an`kFdMg@eB(56e;kP2goW{1Gb~?5ucle`bNHI3d%F2I{ff{l3Qj88E zUZw}iCB|>BibDOFvjUAkXE)Qm8x07v3P-4y5m09&;5%2oB4E~Xy?SWYnq}~S3oZ8F zoX0>W&{CPl4B*tZIr~yh*Zg4z6^|niB}=Y2m6urb-Kyd+70!JiwfDldY9EpG^}=+Z zbSagG<~Ke!QBK~Cf<2ccgi!_pd-B!w$#-B)87G_0H#u8HC@*D!i)*LFX$XPze;1~l z`QL2pyyT(j3_XqwG$lEFHY1>dQ@a z08pCKUo4-q`|YlsNUQ8M^F6mW0ORue-lAQJgozanS??Mc0xYYbht2IQZ|pl%j;?h{ z2Xwpz2_4*~V^s_`7l-=wNhFR9RIte>`DvmWAoK8!viJ6;|^@>_{IVY z_r;!HHsVB8RU#bO+(-PWBN#LQs)KnDgtr*RH`RHirRmV zu@7lz@%0M5rB^@5npsSQ_K;1H0Dp_5I4H|AJB`lhS}seuUlfxShNf1UC^&d0EHzC> zuZe>Q`sP1{p%niQ2te5i$-J@As+noG6Mxg`4T@QQ=!uNe^zQ=6MeWTOy8>=*{}bsd!Glj9!20UE>m9Bs(rOLd&Lr{suU0d*x5hva)NCLSIm_?nq=vYI?9Kb7%ejBOSGZg1xo+}Li+X$R zAK1C$cq2I9m4iQW82|r`g59I!gkV|DPur#?twvDl9)|s+mqnwFoQdz+Nwyw8%Mgum zF}-8F!?}PxRebp`8wuhk!@Lc;rzRf#MjZTAO)!o?FV(Hu-E_WFE)$ ztdaUXJ}Ijg^`Maknwg$4io9}U`r{lWpZzuvG^8(43lG&PozK|xwxez0OC?-<-Hyqe zsGoE+cQ265k)SsHw~@60DQX;GZ-tw)LHv4-u)yBoWrtj0=>qG&8{W+w#g mbOeBrQZgGJ=!2A!B9BDjX6?mKRV}lJDKKl@k^082gV}R&-z(z) literal 10324 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 diff --git a/packages/google-auth/tests/compute_engine/test__metadata.py b/packages/google-auth/tests/compute_engine/test__metadata.py index c5f80d8979dc..7c028eb6232f 100644 --- a/packages/google-auth/tests/compute_engine/test__metadata.py +++ b/packages/google-auth/tests/compute_engine/test__metadata.py @@ -344,12 +344,32 @@ def test_get_return_none_for_not_found_error(): @mock.patch("time.sleep", return_value=None) def test_get_failure_connection_failed(mock_sleep): request = make_request("") - request.side_effect = exceptions.TransportError() + request.side_effect = exceptions.TransportError("failure message") with pytest.raises(exceptions.TransportError) as excinfo: _metadata.get(request, PATH) - assert excinfo.match(r"Compute Engine Metadata server unavailable") + assert excinfo.match( + r"Compute Engine Metadata server unavailable due to failure message" + ) + + request.assert_called_with( + method="GET", + url=_metadata._METADATA_ROOT + PATH, + headers=_metadata._METADATA_HEADERS, + ) + assert request.call_count == 5 + + +def test_get_too_many_requests_retryable_error_failure(): + request = make_request("too many requests", status=http_client.TOO_MANY_REQUESTS) + + with pytest.raises(exceptions.TransportError) as excinfo: + _metadata.get(request, PATH) + + assert excinfo.match( + r"Compute Engine Metadata server unavailable due to too many requests" + ) request.assert_called_with( method="GET", From fcb9c1649276283545c97fddfe715a7436f121bb Mon Sep 17 00:00:00 2001 From: Brian Jung <65934595+brianhmj@users.noreply.github.com> Date: Fri, 17 Jan 2025 03:34:30 -0500 Subject: [PATCH 883/966] feat: adding domain-wide delegation flow in impersonated credential (#1624) * Adding a flow in impersonated credentials to check if a subject is specificed for domain-wide delegation auth. * Adding a flow in impersonated credentials to check if a subject is specificed for domain-wide delegation auth. * Minor fixes to dwd flow in impersonation * Adding a flow in impersonated credentials to check if a subject is specificed for domain-wide delegation auth. * deleted repeated * delete repeated code * Fixing where source credentials authentication header info is, and target scopes. * Formatted code to uniform standard * Fixing lint and coverage failures from kokoro tests --------- Co-authored-by: Brian Jung Co-authored-by: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> --- packages/google-auth/google/auth/iam.py | 5 + .../google/auth/impersonated_credentials.py | 101 ++++++++++++++- .../tests/test_impersonated_credentials.py | 120 ++++++++++++++++++ 3 files changed, 225 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/iam.py b/packages/google-auth/google/auth/iam.py index dcf0dbf9d583..1e4cdffec188 100644 --- a/packages/google-auth/google/auth/iam.py +++ b/packages/google-auth/google/auth/iam.py @@ -48,6 +48,11 @@ + "/serviceAccounts/{}:signBlob" ) +_IAM_SIGNJWT_ENDPOINT = ( + "https://iamcredentials.googleapis.com/v1/projects/-" + + "/serviceAccounts/{}:signJwt" +) + _IAM_IDTOKEN_ENDPOINT = ( "https://iamcredentials.googleapis.com/v1/" + "projects/-/serviceAccounts/{}:generateIdToken" diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index d51c8ef1e857..ed7e3f00b1c0 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -38,12 +38,15 @@ from google.auth import iam from google.auth import jwt from google.auth import metrics +from google.oauth2 import _client _REFRESH_ERROR = "Unable to acquire impersonated credentials" _DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds +_GOOGLE_OAUTH2_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token" + def _make_iam_token_request( request, @@ -177,6 +180,7 @@ def __init__( target_principal, target_scopes, delegates=None, + subject=None, lifetime=_DEFAULT_TOKEN_LIFETIME_SECS, quota_project_id=None, iam_endpoint_override=None, @@ -204,9 +208,12 @@ def __init__( quota_project_id (Optional[str]): The project ID used for quota and billing. This project may be different from the project used to create the credentials. - iam_endpoint_override (Optiona[str]): The full IAM endpoint override + iam_endpoint_override (Optional[str]): The full IAM endpoint override with the target_principal embedded. This is useful when supporting impersonation with regional endpoints. + subject (Optional[str]): sub field of a JWT. This field should only be set + if you wish to impersonate as a user. This feature is useful when + using domain wide delegation. """ super(Credentials, self).__init__() @@ -231,6 +238,7 @@ def __init__( self._target_principal = target_principal self._target_scopes = target_scopes self._delegates = delegates + self._subject = subject self._lifetime = lifetime or _DEFAULT_TOKEN_LIFETIME_SECS self.token = None self.expiry = _helpers.utcnow() @@ -275,6 +283,39 @@ def _update_token(self, request): # Apply the source credentials authentication info. self._source_credentials.apply(headers) + # If a subject is specified a domain-wide delegation auth-flow is initiated + # to impersonate as the provided subject (user). + if self._subject: + if self.universe_domain != credentials.DEFAULT_UNIVERSE_DOMAIN: + raise exceptions.GoogleAuthError( + "Domain-wide delegation is not supported in universes other " + + "than googleapis.com" + ) + + now = _helpers.utcnow() + payload = { + "iss": self._target_principal, + "scope": _helpers.scopes_to_string(self._target_scopes or ()), + "sub": self._subject, + "aud": _GOOGLE_OAUTH2_TOKEN_ENDPOINT, + "iat": _helpers.datetime_to_secs(now), + "exp": _helpers.datetime_to_secs(now) + _DEFAULT_TOKEN_LIFETIME_SECS, + } + + assertion = _sign_jwt_request( + request=request, + principal=self._target_principal, + headers=headers, + payload=payload, + delegates=self._delegates, + ) + + self.token, self.expiry, _ = _client.jwt_grant( + request, _GOOGLE_OAUTH2_TOKEN_ENDPOINT, assertion + ) + + return + self.token, self.expiry = _make_iam_token_request( request=request, principal=self._target_principal, @@ -478,3 +519,61 @@ def refresh(self, request): self.expiry = datetime.utcfromtimestamp( jwt.decode(id_token, verify=False)["exp"] ) + + +def _sign_jwt_request(request, principal, headers, payload, delegates=[]): + """Makes a request to the Google Cloud IAM service to sign a JWT using a + service account's system-managed private key. + Args: + request (Request): The Request object to use. + principal (str): The principal to request an access token for. + headers (Mapping[str, str]): Map of headers to transmit. + payload (Mapping[str, str]): The JWT payload to sign. Must be a + serialized JSON object that contains a JWT Claims Set. + delegates (Sequence[str]): The chained list of delegates required + to grant the final access_token. If set, the sequence of + identities must have "Service Account Token Creator" capability + granted to the prceeding identity. For example, if set to + [serviceAccountB, serviceAccountC], the source_credential + must have the Token Creator role on serviceAccountB. + serviceAccountB must have the Token Creator on + serviceAccountC. + Finally, C must have Token Creator on target_principal. + If left unset, source_credential must have that role on + target_principal. + + Raises: + google.auth.exceptions.TransportError: Raised if there is an underlying + HTTP connection error + google.auth.exceptions.RefreshError: Raised if the impersonated + credentials are not available. Common reasons are + `iamcredentials.googleapis.com` is not enabled or the + `Service Account Token Creator` is not assigned + """ + iam_endpoint = iam._IAM_SIGNJWT_ENDPOINT.format(principal) + + body = {"delegates": delegates, "payload": json.dumps(payload)} + body = json.dumps(body).encode("utf-8") + + response = request(url=iam_endpoint, method="POST", headers=headers, body=body) + + # support both string and bytes type response.data + response_body = ( + response.data.decode("utf-8") + if hasattr(response.data, "decode") + else response.data + ) + + if response.status != http_client.OK: + raise exceptions.RefreshError(_REFRESH_ERROR, response_body) + + try: + jwt_response = json.loads(response_body) + signed_jwt = jwt_response["signedJwt"] + return signed_jwt + + except (KeyError, ValueError) as caught_exc: + new_exc = exceptions.RefreshError( + "{}: No signed JWT in response.".format(_REFRESH_ERROR), response_body + ) + raise new_exc from caught_exc diff --git a/packages/google-auth/tests/test_impersonated_credentials.py b/packages/google-auth/tests/test_impersonated_credentials.py index 0fe6e2329e25..8f6b22670395 100644 --- a/packages/google-auth/tests/test_impersonated_credentials.py +++ b/packages/google-auth/tests/test_impersonated_credentials.py @@ -71,6 +71,17 @@ def mock_donor_credentials(): yield grant +@pytest.fixture +def mock_dwd_credentials(): + with mock.patch("google.oauth2._client.jwt_grant", autospec=True) as grant: + grant.return_value = ( + "1/fFAGRNJasdfz70BzhT3Zg", + _helpers.utcnow() + datetime.timedelta(seconds=500), + {}, + ) + yield grant + + class MockResponse: def __init__(self, json_data, status_code): self.json_data = json_data @@ -123,6 +134,7 @@ def make_credentials( source_credentials=SOURCE_CREDENTIALS, lifetime=LIFETIME, target_principal=TARGET_PRINCIPAL, + subject=None, iam_endpoint_override=None, ): @@ -132,6 +144,7 @@ def make_credentials( target_scopes=self.TARGET_SCOPES, delegates=self.DELEGATES, lifetime=lifetime, + subject=subject, iam_endpoint_override=iam_endpoint_override, ) @@ -238,6 +251,28 @@ def test_refresh_success(self, use_data_bytes, mock_donor_credentials): == ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE ) + @pytest.mark.parametrize("use_data_bytes", [True, False]) + def test_refresh_with_subject_success(self, use_data_bytes, mock_dwd_credentials): + credentials = self.make_credentials(subject="test@email.com", lifetime=None) + + response_body = {"signedJwt": "example_signed_jwt"} + + request = self.make_request( + data=json.dumps(response_body), + status=http_client.OK, + use_data_bytes=use_data_bytes, + ) + + with mock.patch( + "google.auth.metrics.token_request_access_token_impersonate", + return_value=ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, + ): + credentials.refresh(request) + + assert credentials.valid + assert not credentials.expired + assert credentials.token == "1/fFAGRNJasdfz70BzhT3Zg" + @pytest.mark.parametrize("use_data_bytes", [True, False]) def test_refresh_success_nonGdu(self, use_data_bytes, mock_donor_credentials): source_credentials = service_account.Credentials( @@ -418,6 +453,33 @@ def test_refresh_failure_http_error(self, mock_donor_credentials): assert not credentials.valid assert credentials.expired + def test_refresh_failure_subject_with_nondefault_domain( + self, mock_donor_credentials + ): + source_credentials = service_account.Credentials( + SIGNER, "some@email.com", TOKEN_URI, universe_domain="foo.bar" + ) + credentials = self.make_credentials( + source_credentials=source_credentials, subject="test@email.com" + ) + + expire_time = (_helpers.utcnow().replace(microsecond=0)).isoformat("T") + "Z" + response_body = {"accessToken": "token", "expireTime": expire_time} + request = self.make_request( + data=json.dumps(response_body), status=http_client.OK + ) + + with pytest.raises(exceptions.GoogleAuthError) as excinfo: + credentials.refresh(request) + + assert excinfo.match( + "Domain-wide delegation is not supported in universes other " + + "than googleapis.com" + ) + + assert not credentials.valid + assert credentials.expired + def test_expired(self): credentials = self.make_credentials(lifetime=None) assert credentials.expired @@ -810,3 +872,61 @@ def test_id_token_with_quota_project( id_creds.refresh(request) assert id_creds.quota_project_id == "project-foo" + + def test_sign_jwt_request_success(self): + principal = "foo@example.com" + expected_signed_jwt = "correct_signed_jwt" + + response_body = {"keyId": "1", "signedJwt": expected_signed_jwt} + request = self.make_request( + data=json.dumps(response_body), status=http_client.OK + ) + + signed_jwt = impersonated_credentials._sign_jwt_request( + request=request, principal=principal, headers={}, payload={} + ) + + assert signed_jwt == expected_signed_jwt + request.assert_called_once_with( + url="https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/foo@example.com:signJwt", + method="POST", + headers={}, + body=json.dumps({"delegates": [], "payload": json.dumps({})}).encode( + "utf-8" + ), + ) + + def test_sign_jwt_request_http_error(self): + principal = "foo@example.com" + + request = self.make_request( + data="error_message", status=http_client.BAD_REQUEST + ) + + with pytest.raises(exceptions.RefreshError) as excinfo: + _ = impersonated_credentials._sign_jwt_request( + request=request, principal=principal, headers={}, payload={} + ) + + assert excinfo.match(impersonated_credentials._REFRESH_ERROR) + + assert excinfo.value.args[0] == "Unable to acquire impersonated credentials" + assert excinfo.value.args[1] == "error_message" + + def test_sign_jwt_request_invalid_response_error(self): + principal = "foo@example.com" + + request = self.make_request(data="invalid_data", status=http_client.OK) + + with pytest.raises(exceptions.RefreshError) as excinfo: + _ = impersonated_credentials._sign_jwt_request( + request=request, principal=principal, headers={}, payload={} + ) + + assert excinfo.match(impersonated_credentials._REFRESH_ERROR) + + assert ( + excinfo.value.args[0] + == "Unable to acquire impersonated credentials: No signed JWT in response." + ) + assert excinfo.value.args[1] == "invalid_data" From a035836f6a456d464d3e0c9867cf3a99f3ed6df2 Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Wed, 22 Jan 2025 23:15:50 +0000 Subject: [PATCH 884/966] chore: Add warnings regarding consuming externally sourced credentials (#1655) * chore: Add warnings regarding consuming externally sourced credential configurations * update syntax * remove in ADC * period * make it warning * update warning syntax * update secret after rebase --- packages/google-auth/docs/user-guide.rst | 11 +++++++++ packages/google-auth/google/auth/_default.py | 22 ++++++++++++++++++ .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 3 files changed, 33 insertions(+) diff --git a/packages/google-auth/docs/user-guide.rst b/packages/google-auth/docs/user-guide.rst index 3545a8a3190a..04dffaf89862 100644 --- a/packages/google-auth/docs/user-guide.rst +++ b/packages/google-auth/docs/user-guide.rst @@ -29,6 +29,17 @@ that supports OpenID Connect (OIDC). Obtaining credentials --------------------- +.. warning:: + Important: If you accept a credential configuration (credential JSON/File/Stream) + from an external source for authentication to Google Cloud Platform, you must + validate it before providing it to any Google API or client library. Providing an + unvalidated credential configuration to Google APIs or libraries can compromise + the security of your systems and data. For more information, refer to + `Validate credential configurations from external sources`_. + +.. _Validate credential configurations from external sources: + https://cloud.google.com/docs/authentication/external/externally-sourced-credentials + .. _application-default: Application default credentials diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index cdc8b7a64609..1234fb25d787 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -85,6 +85,17 @@ def load_credentials_from_file( user credentials, external account credentials, or impersonated service account credentials. + .. warning:: + Important: If you accept a credential configuration (credential JSON/File/Stream) + from an external source for authentication to Google Cloud Platform, you must + validate it before providing it to any Google API or client library. Providing an + unvalidated credential configuration to Google APIs or libraries can compromise + the security of your systems and data. For more information, refer to + `Validate credential configurations from external sources`_. + + .. _Validate credential configurations from external sources: + https://cloud.google.com/docs/authentication/external/externally-sourced-credentials + Args: filename (str): The full path to the credentials file. scopes (Optional[Sequence[str]]): The list of scopes for the credentials. If @@ -137,6 +148,17 @@ def load_credentials_from_dict( user credentials, external account credentials, or impersonated service account credentials. + .. warning:: + Important: If you accept a credential configuration (credential JSON/File/Stream) + from an external source for authentication to Google Cloud Platform, you must + validate it before providing it to any Google API or client library. Providing an + unvalidated credential configuration to Google APIs or libraries can compromise + the security of your systems and data. For more information, refer to + `Validate credential configurations from external sources`_. + + .. _Validate credential configurations from external sources: + https://cloud.google.com/docs/authentication/external/externally-sourced-credentials + Args: info (Dict[str, Any]): A dict object containing the credentials scopes (Optional[Sequence[str]]): The list of scopes for the credentials. If diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index feb9c5fc55768eea62d5e0c7eba1f450d9e985e6..3f239d76ba8e0d9d20e3782b10fda01fa851f9f8 100644 GIT binary patch literal 10324 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 literal 10324 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

        yz;`mge$0jUT;J2`9CKeyo}$_OghWB- zJK@`h-d{}3an`kFdMg@eB(56e;kP2goW{1Gb~?5ucle`bNHI3d%F2I{ff{l3Qj88E zUZw}iCB|>BibDOFvjUAkXE)Qm8x07v3P-4y5m09&;5%2oB4E~Xy?SWYnq}~S3oZ8F zoX0>W&{CPl4B*tZIr~yh*Zg4z6^|niB}=Y2m6urb-Kyd+70!JiwfDldY9EpG^}=+Z zbSagG<~Ke!QBK~Cf<2ccgi!_pd-B!w$#-B)87G_0H#u8HC@*D!i)*LFX$XPze;1~l z`QL2pyyT(j3_XqwG$lEFHY1>dQ@a z08pCKUo4-q`|YlsNUQ8M^F6mW0ORue-lAQJgozanS??Mc0xYYbht2IQZ|pl%j;?h{ z2Xwpz2_4*~V^s_`7l-=wNhFR9RIte>`DvmWAoK8!viJ6;|^@>_{IVY z_r;!HHsVB8RU#bO+(-PWBN#LQs)KnDgtr*RH`RHirRmV zu@7lz@%0M5rB^@5npsSQ_K;1H0Dp_5I4H|AJB`lhS}seuUlfxShNf1UC^&d0EHzC> zuZe>Q`sP1{p%niQ2te5i$-J@As+noG6Mxg`4T@QQ=!uNe^zQ=6MeWTOy8>=*{}bsd!Glj9!20UE>m9Bs(rOLd&Lr{suU0d*x5hva)NCLSIm_?nq=vYI?9Kb7%ejBOSGZg1xo+}Li+X$R zAK1C$cq2I9m4iQW82|r`g59I!gkV|DPur#?twvDl9)|s+mqnwFoQdz+Nwyw8%Mgum zF}-8F!?}PxRebp`8wuhk!@Lc;rzRf#MjZTAO)!o?FV(Hu-E_WFE)$ ztdaUXJ}Ijg^`Maknwg$4io9}U`r{lWpZzuvG^8(43lG&PozK|xwxez0OC?-<-Hyqe zsGoE+cQ265k)SsHw~@60DQX;GZ-tw)LHv4-u)yBoWrtj0=>qG&8{W+w#g mbOeBrQZgGJ=!2A!B9BDjX6?mKRV}lJDKKl@k^082gV}R&-z(z) From 2106f9bd4bbe76b980b9fc889ccf8a2b75e5df48 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 23 Jan 2025 00:58:09 +0000 Subject: [PATCH 885/966] chore(main): release 2.38.0 (#1657) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 12 ++++++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index c7f8e51ce2fa..cb6a413584ef 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,18 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.38.0](https://github.com/googleapis/google-auth-library-python/compare/v2.37.0...v2.38.0) (2025-01-23) + + +### Features + +* Adding domain-wide delegation flow in impersonated credential ([#1624](https://github.com/googleapis/google-auth-library-python/issues/1624)) ([34ee3fe](https://github.com/googleapis/google-auth-library-python/commit/34ee3fef8cba6a1bbaa46fa16b43af0d89b60b0f)) + + +### Documentation + +* Add warnings regarding consuming externally sourced credentials ([d049370](https://github.com/googleapis/google-auth-library-python/commit/d049370d266b50db0e09d7b292dbf33052b27853)) + ## [2.37.0](https://github.com/googleapis/google-auth-library-python/compare/v2.36.1...v2.37.0) (2024-12-11) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 06ec7e7fb79d..41a80e6c6767 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.37.0" +__version__ = "2.38.0" From a71b7227258f0d660f7482575271563f52375629 Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Thu, 30 Jan 2025 15:45:35 +0000 Subject: [PATCH 886/966] chore: update secret for tests (#1664) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 3f239d76ba8e0d9d20e3782b10fda01fa851f9f8..bcc3f042d767c6059d9535c264a5aa2c67262f5e 100644 GIT binary patch literal 10324 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 literal 10324 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 From 7768184372a57f2d4dd7127b8306006dba85ff9b Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Thu, 30 Jan 2025 11:12:32 -0500 Subject: [PATCH 887/966] build: configure readthedocs to refresh stale documentation (#1662) * build: configure readthedocs to refresh stale documentation * add new line * Use python 3.10 for docs --- packages/google-auth/.readthedocs.yaml | 22 +++++++++++++++++++ .../google-auth/docs/requirements-docs.txt | 14 +++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 packages/google-auth/.readthedocs.yaml diff --git a/packages/google-auth/.readthedocs.yaml b/packages/google-auth/.readthedocs.yaml new file mode 100644 index 000000000000..2fb28446e3ab --- /dev/null +++ b/packages/google-auth/.readthedocs.yaml @@ -0,0 +1,22 @@ +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the OS, Python version, and other tools you might need +build: + os: ubuntu-24.04 + tools: + python: "3.10" + +# Build documentation in the "docs/" directory with Sphinx +sphinx: + configuration: docs/conf.py + +python: + install: + - requirements: docs/requirements-docs.txt + # Install our python package before building the docs + - method: pip + path: . diff --git a/packages/google-auth/docs/requirements-docs.txt b/packages/google-auth/docs/requirements-docs.txt index c08477f7d3d6..8b2c192dd08d 100644 --- a/packages/google-auth/docs/requirements-docs.txt +++ b/packages/google-auth/docs/requirements-docs.txt @@ -2,4 +2,16 @@ cryptography sphinx-docstring-typing urllib3 requests -requests-oauthlib \ No newline at end of file +requests-oauthlib +# We need to pin to specific versions of the `sphinxcontrib-*` packages +# which still support sphinx 4.x. +# See https://github.com/googleapis/sphinx-docfx-yaml/issues/344 +# and https://github.com/googleapis/sphinx-docfx-yaml/issues/345. +sphinxcontrib-applehelp==1.0.4 +sphinxcontrib-devhelp==1.0.2 +sphinxcontrib-htmlhelp==2.0.1 +sphinxcontrib-qthelp==1.0.3 +sphinxcontrib-serializinghtml==1.1.5 +sphinx==4.5.0 +alabaster +recommonmark From c55887f68edf1ba3d2c73354d347b33cb1432264 Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Tue, 25 Mar 2025 21:14:51 +0000 Subject: [PATCH 888/966] fix: fix systest failure (#1703) --- .../google-auth/.kokoro/build-systests.sh | 2 +- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/.kokoro/build-systests.sh b/packages/google-auth/.kokoro/build-systests.sh index a2947c25a3fd..3c8dcf85b418 100755 --- a/packages/google-auth/.kokoro/build-systests.sh +++ b/packages/google-auth/.kokoro/build-systests.sh @@ -28,7 +28,7 @@ export PYTHONUNBUFFERED=1 python3 -m pip uninstall --yes --quiet nox-automation # Install nox -python3 -m pip install --upgrade --quiet nox +python3 -m pip install nox==2024.10.9 python3 -m nox --version # Setup service account credentials. diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index bcc3f042d767c6059d9535c264a5aa2c67262f5e..b5b063fca331994a2dbb2ee4321861e456297a23 100644 GIT binary patch literal 10324 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 literal 10324 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 From cbcfd3499ede2bf017d92363c892307a6ce3574d Mon Sep 17 00:00:00 2001 From: aeitzman <12433791+aeitzman@users.noreply.github.com> Date: Tue, 25 Mar 2025 14:57:17 -0700 Subject: [PATCH 889/966] feat: adds GA support for X.509 workload identity federation (#1695) * feat: adds GA support for X.509 workload identity federation * Adding more tests * remove comment block * Adding pyopessl imports * responding to PR comments --- .../google-auth/google/auth/identity_pool.py | 93 ++++++++++- .../tests/data/trust_chain_with_leaf.pem | 52 ++++++ .../tests/data/trust_chain_without_leaf.pem | 33 ++++ .../tests/data/trust_chain_wrong_order.pem | 52 ++++++ .../google-auth/tests/test_identity_pool.py | 157 +++++++++++++++++- 5 files changed, 383 insertions(+), 4 deletions(-) create mode 100644 packages/google-auth/tests/data/trust_chain_with_leaf.pem create mode 100644 packages/google-auth/tests/data/trust_chain_without_leaf.pem create mode 100644 packages/google-auth/tests/data/trust_chain_wrong_order.pem diff --git a/packages/google-auth/google/auth/identity_pool.py b/packages/google-auth/google/auth/identity_pool.py index 47f9a55715ce..c06f88428702 100644 --- a/packages/google-auth/google/auth/identity_pool.py +++ b/packages/google-auth/google/auth/identity_pool.py @@ -41,6 +41,7 @@ except ImportError: # pragma: NO COVER from collections import Mapping # type: ignore import abc +import base64 import json import os from typing import NamedTuple @@ -145,9 +146,88 @@ def get_subject_token(self, context, request): class _X509Supplier(SubjectTokenSupplier): """Internal supplier for X509 workload credentials. This class is used internally and always returns an empty string as the subject token.""" + def __init__(self, trust_chain_path, leaf_cert_callback): + self._trust_chain_path = trust_chain_path + self._leaf_cert_callback = leaf_cert_callback + @_helpers.copy_docstring(SubjectTokenSupplier) def get_subject_token(self, context, request): - return "" + # Import OpennSSL inline because it is an extra import only required by customers + # using mTLS. + from OpenSSL import crypto + + leaf_cert = crypto.load_certificate( + crypto.FILETYPE_PEM, self._leaf_cert_callback() + ) + trust_chain = self._read_trust_chain() + cert_chain = [] + + cert_chain.append(_X509Supplier._encode_cert(leaf_cert)) + + if trust_chain is None or len(trust_chain) == 0: + return json.dumps(cert_chain) + + # Append the first cert if it is not the leaf cert. + first_cert = _X509Supplier._encode_cert(trust_chain[0]) + if first_cert != cert_chain[0]: + cert_chain.append(first_cert) + + for i in range(1, len(trust_chain)): + encoded = _X509Supplier._encode_cert(trust_chain[i]) + # Check if the current cert is the leaf cert and raise an exception if it is. + if encoded == cert_chain[0]: + raise exceptions.RefreshError( + "The leaf certificate must be at the top of the trust chain file" + ) + else: + cert_chain.append(encoded) + return json.dumps(cert_chain) + + def _read_trust_chain(self): + # Import OpennSSL inline because it is an extra import only required by customers + # using mTLS. + from OpenSSL import crypto + + certificate_trust_chain = [] + # If no trust chain path was provided, return an empty list. + if self._trust_chain_path is None or self._trust_chain_path == "": + return certificate_trust_chain + try: + # Open the trust chain file. + with open(self._trust_chain_path, "rb") as f: + trust_chain_data = f.read() + # Split PEM data into individual certificates. + cert_blocks = trust_chain_data.split(b"-----BEGIN CERTIFICATE-----") + for cert_block in cert_blocks: + # Skip empty blocks. + if cert_block.strip(): + cert_data = b"-----BEGIN CERTIFICATE-----" + cert_block + try: + # Load each certificate and add it to the trust chain. + cert = crypto.load_certificate( + crypto.FILETYPE_PEM, cert_data + ) + certificate_trust_chain.append(cert) + except Exception as e: + raise exceptions.RefreshError( + "Error loading PEM certificates from the trust chain file '{}'".format( + self._trust_chain_path + ) + ) from e + return certificate_trust_chain + except FileNotFoundError: + raise exceptions.RefreshError( + "Trust chain file '{}' was not found.".format(self._trust_chain_path) + ) + + def _encode_cert(cert): + # Import OpennSSL inline because it is an extra import only required by customers + # using mTLS. + from OpenSSL import crypto + + return base64.b64encode( + crypto.dump_certificate(crypto.FILETYPE_ASN1, cert) + ).decode("utf-8") def _parse_token_data(token_content, format_type="text", subject_token_field_name=None): @@ -296,7 +376,9 @@ def __init__( self._credential_source_headers, ) else: # self._credential_source_certificate - self._subject_token_supplier = _X509Supplier() + self._subject_token_supplier = _X509Supplier( + self._trust_chain_path, self._get_cert_bytes + ) @_helpers.copy_docstring(external_account.Credentials) def retrieve_subject_token(self, request): @@ -314,6 +396,10 @@ def _get_mtls_cert_and_key_paths(self): self._certificate_config_location ) + def _get_cert_bytes(self): + cert_path, _ = self._get_mtls_cert_and_key_paths() + return _mtls_helper._read_cert_file(cert_path) + def _mtls_required(self): return self._credential_source_certificate is not None @@ -350,6 +436,9 @@ def _validate_certificate_config(self): use_default = self._credential_source_certificate.get( "use_default_certificate_config" ) + self._trust_chain_path = self._credential_source_certificate.get( + "trust_chain_path" + ) if self._certificate_config_location and use_default: raise exceptions.MalformedError( "Invalid certificate configuration, certificate_config_location cannot be specified when use_default_certificate_config = true." diff --git a/packages/google-auth/tests/data/trust_chain_with_leaf.pem b/packages/google-auth/tests/data/trust_chain_with_leaf.pem new file mode 100644 index 000000000000..250387d9d59c --- /dev/null +++ b/packages/google-auth/tests/data/trust_chain_with_leaf.pem @@ -0,0 +1,52 @@ +-----BEGIN CERTIFICATE----- +MIIDIzCCAgugAwIBAgIJAMfISuBQ5m+5MA0GCSqGSIb3DQEBBQUAMBUxEzARBgNV +BAMTCnVuaXQtdGVzdHMwHhcNMTExMjA2MTYyNjAyWhcNMjExMjAzMTYyNjAyWjAV +MRMwEQYDVQQDEwp1bml0LXRlc3RzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEA4ej0p7bQ7L/r4rVGUz9RN4VQWoej1Bg1mYWIDYslvKrk1gpj7wZgkdmM +7oVK2OfgrSj/FCTkInKPqaCR0gD7K80q+mLBrN3PUkDrJQZpvRZIff3/xmVU1Wer +uQLFJjnFb2dqu0s/FY/2kWiJtBCakXvXEOb7zfbINuayL+MSsCGSdVYsSliS5qQp +gyDap+8b5fpXZVJkq92hrcNtbkg7hCYUJczt8n9hcCTJCfUpApvaFQ18pe+zpyl4 ++WzkP66I28hniMQyUlA1hBiskT7qiouq0m8IOodhv2fagSZKjOTTU2xkSBc//fy3 +ZpsL7WqgsZS7Q+0VRK8gKfqkxg5OYQIDAQABo3YwdDAdBgNVHQ4EFgQU2RQ8yO+O +gN8oVW2SW7RLrfYd9jEwRQYDVR0jBD4wPIAU2RQ8yO+OgN8oVW2SW7RLrfYd9jGh +GaQXMBUxEzARBgNVBAMTCnVuaXQtdGVzdHOCCQDHyErgUOZvuTAMBgNVHRMEBTAD +AQH/MA0GCSqGSIb3DQEBBQUAA4IBAQBRv+M/6+FiVu7KXNjFI5pSN17OcW5QUtPr +odJMlWrJBtynn/TA1oJlYu3yV5clc/71Vr/AxuX5xGP+IXL32YDF9lTUJXG/uUGk ++JETpKmQviPbRsvzYhz4pf6ZIOZMc3/GIcNq92ECbseGO+yAgyWUVKMmZM0HqXC9 +ovNslqe0M8C1sLm1zAR5z/h/litE7/8O2ietija3Q/qtl2TOXJdCA6sgjJX2WUql +ybrC55ct18NKf3qhpcEkGQvFU40rVYApJpi98DiZPYFdx1oBDp/f4uZ3ojpxRVFT +cDwcJLfNRCPUhormsY7fDS9xSyThiHsW9mjJYdcaKQkwYZ0F11yB +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFtTCCA52gAwIBAgIJAPBsLZmNGfKtMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQwHhcNMTYwOTIxMDI0NTEyWhcNMTYxMDIxMDI0NTEyWjBF +MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50 +ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC +CgKCAgEAsiMC7mTsmUXwZoYlT4aHY1FLw8bxIXC+z3IqA+TY1WqfbeiZRo8MA5Zx +lTTxYMKPCZUE1XBc7jvD8GJhWIj6pToPYHn73B01IBkLBxq4kF1yV2Z7DVmkvc6H +EcxXXq8zkCx0j6XOfiI4+qkXnuQn8cvrk8xfhtnMMZM7iVm6VSN93iRP/8ey6xuL +XTHrDX7ukoRce1hpT8O+15GXNrY0irhhYQz5xKibNCJF3EjV28WMry8y7I8uYUFU +RWDiQawwK9ec1zhZ94v92+GZDlPevmcFmSERKYQ0NsKcT0Y3lGuGnaExs8GyOpnC +oksu4YJGXQjg7lkv4MxzsNbRqmCkUwxw1Mg6FP0tsCNsw9qTrkvWCRA9zp/aU+sZ +IBGh1t4UGCub8joeQFvHxvr/3F7mH/dyvCjA34u0Lo1VPx+jYUIi9i0odltMspDW +xOpjqdGARZYmlJP5Au9q5cQjPMcwS/EBIb8cwNl32mUE6WnFlep+38mNR/FghIjO +ViAkXuKQmcHe6xppZAoHFsO/t3l4Tjek5vNW7erI1rgrFku/fvkIW/G8V1yIm/+Q +F+CE4maQzCJfhftpkhM/sPC/FuLNBmNE8BHVX8y58xG4is/cQxL4Z9TsFIw0C5+3 +uTrFW9D0agysahMVzPGtCqhDQqJdIJrBQqlS6bztpzBA8zEI0skCAwEAAaOBpzCB +pDAdBgNVHQ4EFgQUz/8FmW6TfqXyNJZr7rhc+Tn5sKQwdQYDVR0jBG4wbIAUz/8F +mW6TfqXyNJZr7rhc+Tn5sKShSaRHMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpT +b21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGSCCQDw +bC2ZjRnyrTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4ICAQCQmrcfhurX +riR3Q0Y+nq040/3dJIAJXjyI9CEtxaU0nzCNTng7PwgZ0CKmCelQfInuwWFwBSHS +6kBfC1rgJeFnjnTt8a3RCgRlIgUr9NCdPSEccB7TurobwPJ2h6cJjjR8urcb0CXh +CEMvPneyPj0xUFY8vVKXMGWahz/kyfwIiVqcX/OtMZ29fUu1onbWl71g2gVLtUZl +sECdZ+AC/6HDCVpYIVETMl1T7N/XyqXZQiDLDNRDeZhnapz8w9fsW1KVujAZLNQR +pVnw2qa2UK1dSf2FHX+lQU5mFSYM4vtwaMlX/LgfdLZ9I796hFh619WwTVz+LO2N +vHnwBMabld3XSPuZRqlbBulDQ07Vbqdjv8DYSLA2aKI4ZkMMKuFLG/oS28V2ZYmv +/KpGEs5UgKY+P9NulYpTDwCU/6SomuQpP795wbG6sm7Hzq82r2RmB61GupNRGeqi +pXKsy69T388zBxYu6zQrosXiDl5YzaViH7tm0J7opye8dCWjjpnahki0vq2znti7 +6cWla2j8Xz1glvLz+JI/NCOMfxUInb82T7ijo80N0VJ2hzf7p2GxRZXAxAV9knLI +nM4F5TLjSd7ZhOOZ7ni/eZFueTMisWfypt2nc41whGjHMX/Zp1kPfhB4H2bLKIX/ +lSrwNr3qbGTEJX8JqpDBNVAd96XkMvDNyA== +-----END CERTIFICATE----- \ No newline at end of file diff --git a/packages/google-auth/tests/data/trust_chain_without_leaf.pem b/packages/google-auth/tests/data/trust_chain_without_leaf.pem new file mode 100644 index 000000000000..9da0f37fedf9 --- /dev/null +++ b/packages/google-auth/tests/data/trust_chain_without_leaf.pem @@ -0,0 +1,33 @@ +-----BEGIN CERTIFICATE----- +MIIFtTCCA52gAwIBAgIJAPBsLZmNGfKtMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQwHhcNMTYwOTIxMDI0NTEyWhcNMTYxMDIxMDI0NTEyWjBF +MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50 +ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC +CgKCAgEAsiMC7mTsmUXwZoYlT4aHY1FLw8bxIXC+z3IqA+TY1WqfbeiZRo8MA5Zx +lTTxYMKPCZUE1XBc7jvD8GJhWIj6pToPYHn73B01IBkLBxq4kF1yV2Z7DVmkvc6H +EcxXXq8zkCx0j6XOfiI4+qkXnuQn8cvrk8xfhtnMMZM7iVm6VSN93iRP/8ey6xuL +XTHrDX7ukoRce1hpT8O+15GXNrY0irhhYQz5xKibNCJF3EjV28WMry8y7I8uYUFU +RWDiQawwK9ec1zhZ94v92+GZDlPevmcFmSERKYQ0NsKcT0Y3lGuGnaExs8GyOpnC +oksu4YJGXQjg7lkv4MxzsNbRqmCkUwxw1Mg6FP0tsCNsw9qTrkvWCRA9zp/aU+sZ +IBGh1t4UGCub8joeQFvHxvr/3F7mH/dyvCjA34u0Lo1VPx+jYUIi9i0odltMspDW +xOpjqdGARZYmlJP5Au9q5cQjPMcwS/EBIb8cwNl32mUE6WnFlep+38mNR/FghIjO +ViAkXuKQmcHe6xppZAoHFsO/t3l4Tjek5vNW7erI1rgrFku/fvkIW/G8V1yIm/+Q +F+CE4maQzCJfhftpkhM/sPC/FuLNBmNE8BHVX8y58xG4is/cQxL4Z9TsFIw0C5+3 +uTrFW9D0agysahMVzPGtCqhDQqJdIJrBQqlS6bztpzBA8zEI0skCAwEAAaOBpzCB +pDAdBgNVHQ4EFgQUz/8FmW6TfqXyNJZr7rhc+Tn5sKQwdQYDVR0jBG4wbIAUz/8F +mW6TfqXyNJZr7rhc+Tn5sKShSaRHMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpT +b21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGSCCQDw +bC2ZjRnyrTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4ICAQCQmrcfhurX +riR3Q0Y+nq040/3dJIAJXjyI9CEtxaU0nzCNTng7PwgZ0CKmCelQfInuwWFwBSHS +6kBfC1rgJeFnjnTt8a3RCgRlIgUr9NCdPSEccB7TurobwPJ2h6cJjjR8urcb0CXh +CEMvPneyPj0xUFY8vVKXMGWahz/kyfwIiVqcX/OtMZ29fUu1onbWl71g2gVLtUZl +sECdZ+AC/6HDCVpYIVETMl1T7N/XyqXZQiDLDNRDeZhnapz8w9fsW1KVujAZLNQR +pVnw2qa2UK1dSf2FHX+lQU5mFSYM4vtwaMlX/LgfdLZ9I796hFh619WwTVz+LO2N +vHnwBMabld3XSPuZRqlbBulDQ07Vbqdjv8DYSLA2aKI4ZkMMKuFLG/oS28V2ZYmv +/KpGEs5UgKY+P9NulYpTDwCU/6SomuQpP795wbG6sm7Hzq82r2RmB61GupNRGeqi +pXKsy69T388zBxYu6zQrosXiDl5YzaViH7tm0J7opye8dCWjjpnahki0vq2znti7 +6cWla2j8Xz1glvLz+JI/NCOMfxUInb82T7ijo80N0VJ2hzf7p2GxRZXAxAV9knLI +nM4F5TLjSd7ZhOOZ7ni/eZFueTMisWfypt2nc41whGjHMX/Zp1kPfhB4H2bLKIX/ +lSrwNr3qbGTEJX8JqpDBNVAd96XkMvDNyA== +-----END CERTIFICATE----- \ No newline at end of file diff --git a/packages/google-auth/tests/data/trust_chain_wrong_order.pem b/packages/google-auth/tests/data/trust_chain_wrong_order.pem new file mode 100644 index 000000000000..e8dc5d359319 --- /dev/null +++ b/packages/google-auth/tests/data/trust_chain_wrong_order.pem @@ -0,0 +1,52 @@ +-----BEGIN CERTIFICATE----- +MIIFtTCCA52gAwIBAgIJAPBsLZmNGfKtMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQwHhcNMTYwOTIxMDI0NTEyWhcNMTYxMDIxMDI0NTEyWjBF +MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50 +ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC +CgKCAgEAsiMC7mTsmUXwZoYlT4aHY1FLw8bxIXC+z3IqA+TY1WqfbeiZRo8MA5Zx +lTTxYMKPCZUE1XBc7jvD8GJhWIj6pToPYHn73B01IBkLBxq4kF1yV2Z7DVmkvc6H +EcxXXq8zkCx0j6XOfiI4+qkXnuQn8cvrk8xfhtnMMZM7iVm6VSN93iRP/8ey6xuL +XTHrDX7ukoRce1hpT8O+15GXNrY0irhhYQz5xKibNCJF3EjV28WMry8y7I8uYUFU +RWDiQawwK9ec1zhZ94v92+GZDlPevmcFmSERKYQ0NsKcT0Y3lGuGnaExs8GyOpnC +oksu4YJGXQjg7lkv4MxzsNbRqmCkUwxw1Mg6FP0tsCNsw9qTrkvWCRA9zp/aU+sZ +IBGh1t4UGCub8joeQFvHxvr/3F7mH/dyvCjA34u0Lo1VPx+jYUIi9i0odltMspDW +xOpjqdGARZYmlJP5Au9q5cQjPMcwS/EBIb8cwNl32mUE6WnFlep+38mNR/FghIjO +ViAkXuKQmcHe6xppZAoHFsO/t3l4Tjek5vNW7erI1rgrFku/fvkIW/G8V1yIm/+Q +F+CE4maQzCJfhftpkhM/sPC/FuLNBmNE8BHVX8y58xG4is/cQxL4Z9TsFIw0C5+3 +uTrFW9D0agysahMVzPGtCqhDQqJdIJrBQqlS6bztpzBA8zEI0skCAwEAAaOBpzCB +pDAdBgNVHQ4EFgQUz/8FmW6TfqXyNJZr7rhc+Tn5sKQwdQYDVR0jBG4wbIAUz/8F +mW6TfqXyNJZr7rhc+Tn5sKShSaRHMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpT +b21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGSCCQDw +bC2ZjRnyrTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4ICAQCQmrcfhurX +riR3Q0Y+nq040/3dJIAJXjyI9CEtxaU0nzCNTng7PwgZ0CKmCelQfInuwWFwBSHS +6kBfC1rgJeFnjnTt8a3RCgRlIgUr9NCdPSEccB7TurobwPJ2h6cJjjR8urcb0CXh +CEMvPneyPj0xUFY8vVKXMGWahz/kyfwIiVqcX/OtMZ29fUu1onbWl71g2gVLtUZl +sECdZ+AC/6HDCVpYIVETMl1T7N/XyqXZQiDLDNRDeZhnapz8w9fsW1KVujAZLNQR +pVnw2qa2UK1dSf2FHX+lQU5mFSYM4vtwaMlX/LgfdLZ9I796hFh619WwTVz+LO2N +vHnwBMabld3XSPuZRqlbBulDQ07Vbqdjv8DYSLA2aKI4ZkMMKuFLG/oS28V2ZYmv +/KpGEs5UgKY+P9NulYpTDwCU/6SomuQpP795wbG6sm7Hzq82r2RmB61GupNRGeqi +pXKsy69T388zBxYu6zQrosXiDl5YzaViH7tm0J7opye8dCWjjpnahki0vq2znti7 +6cWla2j8Xz1glvLz+JI/NCOMfxUInb82T7ijo80N0VJ2hzf7p2GxRZXAxAV9knLI +nM4F5TLjSd7ZhOOZ7ni/eZFueTMisWfypt2nc41whGjHMX/Zp1kPfhB4H2bLKIX/ +lSrwNr3qbGTEJX8JqpDBNVAd96XkMvDNyA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDIzCCAgugAwIBAgIJAMfISuBQ5m+5MA0GCSqGSIb3DQEBBQUAMBUxEzARBgNV +BAMTCnVuaXQtdGVzdHMwHhcNMTExMjA2MTYyNjAyWhcNMjExMjAzMTYyNjAyWjAV +MRMwEQYDVQQDEwp1bml0LXRlc3RzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEA4ej0p7bQ7L/r4rVGUz9RN4VQWoej1Bg1mYWIDYslvKrk1gpj7wZgkdmM +7oVK2OfgrSj/FCTkInKPqaCR0gD7K80q+mLBrN3PUkDrJQZpvRZIff3/xmVU1Wer +uQLFJjnFb2dqu0s/FY/2kWiJtBCakXvXEOb7zfbINuayL+MSsCGSdVYsSliS5qQp +gyDap+8b5fpXZVJkq92hrcNtbkg7hCYUJczt8n9hcCTJCfUpApvaFQ18pe+zpyl4 ++WzkP66I28hniMQyUlA1hBiskT7qiouq0m8IOodhv2fagSZKjOTTU2xkSBc//fy3 +ZpsL7WqgsZS7Q+0VRK8gKfqkxg5OYQIDAQABo3YwdDAdBgNVHQ4EFgQU2RQ8yO+O +gN8oVW2SW7RLrfYd9jEwRQYDVR0jBD4wPIAU2RQ8yO+OgN8oVW2SW7RLrfYd9jGh +GaQXMBUxEzARBgNVBAMTCnVuaXQtdGVzdHOCCQDHyErgUOZvuTAMBgNVHRMEBTAD +AQH/MA0GCSqGSIb3DQEBBQUAA4IBAQBRv+M/6+FiVu7KXNjFI5pSN17OcW5QUtPr +odJMlWrJBtynn/TA1oJlYu3yV5clc/71Vr/AxuX5xGP+IXL32YDF9lTUJXG/uUGk ++JETpKmQviPbRsvzYhz4pf6ZIOZMc3/GIcNq92ECbseGO+yAgyWUVKMmZM0HqXC9 +ovNslqe0M8C1sLm1zAR5z/h/litE7/8O2ietija3Q/qtl2TOXJdCA6sgjJX2WUql +ybrC55ct18NKf3qhpcEkGQvFU40rVYApJpi98DiZPYFdx1oBDp/f4uZ3ojpxRVFT +cDwcJLfNRCPUhormsY7fDS9xSyThiHsW9mjJYdcaKQkwYZ0F11yB +-----END CERTIFICATE----- \ No newline at end of file diff --git a/packages/google-auth/tests/test_identity_pool.py b/packages/google-auth/tests/test_identity_pool.py index b2b0063ec547..41fd18892a32 100644 --- a/packages/google-auth/tests/test_identity_pool.py +++ b/packages/google-auth/tests/test_identity_pool.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import base64 import datetime import http.client as http_client import json @@ -19,6 +20,7 @@ import urllib import mock +from OpenSSL import crypto import pytest # type: ignore from google.auth import _helpers, external_account @@ -48,6 +50,13 @@ DATA_DIR = os.path.join(os.path.dirname(__file__), "data") SUBJECT_TOKEN_TEXT_FILE = os.path.join(DATA_DIR, "external_subject_token.txt") SUBJECT_TOKEN_JSON_FILE = os.path.join(DATA_DIR, "external_subject_token.json") +TRUST_CHAIN_WITH_LEAF_FILE = os.path.join(DATA_DIR, "trust_chain_with_leaf.pem") +TRUST_CHAIN_WITHOUT_LEAF_FILE = os.path.join(DATA_DIR, "trust_chain_without_leaf.pem") +TRUST_CHAIN_WRONG_ORDER_FILE = os.path.join(DATA_DIR, "trust_chain_wrong_order.pem") +CERT_FILE = os.path.join(DATA_DIR, "public_cert.pem") +KEY_FILE = os.path.join(DATA_DIR, "privatekey.pem") +OTHER_CERT_FILE = os.path.join(DATA_DIR, "other_cert.pem") + SUBJECT_TOKEN_FIELD_NAME = "access_token" with open(SUBJECT_TOKEN_TEXT_FILE) as fh: @@ -57,6 +66,20 @@ JSON_FILE_CONTENT = json.load(fh) JSON_FILE_SUBJECT_TOKEN = JSON_FILE_CONTENT.get(SUBJECT_TOKEN_FIELD_NAME) +with open(CERT_FILE, "rb") as f: + CERT_FILE_CONTENT = base64.b64encode( + crypto.dump_certificate( + crypto.FILETYPE_ASN1, crypto.load_certificate(crypto.FILETYPE_PEM, f.read()) + ) + ).decode("utf-8") + +with open(OTHER_CERT_FILE, "rb") as f: + OTHER_CERT_FILE_CONTENT = base64.b64encode( + crypto.dump_certificate( + crypto.FILETYPE_ASN1, crypto.load_certificate(crypto.FILETYPE_PEM, f.read()) + ) + ).decode("utf-8") + TOKEN_URL = "https://sts.googleapis.com/v1/token" TOKEN_INFO_URL = "https://sts.googleapis.com/v1/introspect" SUBJECT_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:jwt" @@ -185,6 +208,24 @@ class TestCredentials(object): CREDENTIAL_SOURCE_CERTIFICATE_NOT_DEFAULT = { "certificate": {"certificate_config_location": "path/to/config"} } + CREDENTIAL_SOURCE_CERTIFICATE_TRUST_CHAIN_WITH_LEAF = { + "certificate": { + "use_default_certificate_config": "true", + "trust_chain_path": TRUST_CHAIN_WITH_LEAF_FILE, + } + } + CREDENTIAL_SOURCE_CERTIFICATE_TRUST_CHAIN_WITHOUT_LEAF = { + "certificate": { + "use_default_certificate_config": "true", + "trust_chain_path": TRUST_CHAIN_WITHOUT_LEAF_FILE, + } + } + CREDENTIAL_SOURCE_CERTIFICATE_TRUST_CHAIN_WRONG_ORDER = { + "certificate": { + "use_default_certificate_config": "true", + "trust_chain_path": TRUST_CHAIN_WRONG_ORDER_FILE, + } + } SUCCESS_RESPONSE = { "access_token": "ACCESS_TOKEN", "issued_token_type": "urn:ietf:params:oauth:token-type:access_token", @@ -936,14 +977,126 @@ def test_retrieve_subject_token_json_file(self): assert subject_token == JSON_FILE_SUBJECT_TOKEN - def test_retrieve_subject_token_certificate(self): + @mock.patch( + "google.auth.transport._mtls_helper._get_workload_cert_and_key_paths", + return_value=(CERT_FILE, KEY_FILE), + ) + def test_retrieve_subject_token_certificate_default( + self, mock_get_workload_cert_and_key_paths + ): credentials = self.make_credentials( credential_source=self.CREDENTIAL_SOURCE_CERTIFICATE ) subject_token = credentials.retrieve_subject_token(None) - assert subject_token == "" + assert subject_token == json.dumps([CERT_FILE_CONTENT]) + + @mock.patch( + "google.auth.transport._mtls_helper._get_workload_cert_and_key_paths", + return_value=(CERT_FILE, KEY_FILE), + ) + def test_retrieve_subject_token_certificate_non_default_path( + self, mock_get_workload_cert_and_key_paths + ): + credentials = self.make_credentials( + credential_source=self.CREDENTIAL_SOURCE_CERTIFICATE_NOT_DEFAULT + ) + + subject_token = credentials.retrieve_subject_token(None) + + assert subject_token == json.dumps([CERT_FILE_CONTENT]) + + @mock.patch( + "google.auth.transport._mtls_helper._get_workload_cert_and_key_paths", + return_value=(CERT_FILE, KEY_FILE), + ) + def test_retrieve_subject_token_certificate_trust_chain_with_leaf( + self, mock_get_workload_cert_and_key_paths + ): + credentials = self.make_credentials( + credential_source=self.CREDENTIAL_SOURCE_CERTIFICATE_TRUST_CHAIN_WITH_LEAF + ) + + subject_token = credentials.retrieve_subject_token(None) + assert subject_token == json.dumps([CERT_FILE_CONTENT, OTHER_CERT_FILE_CONTENT]) + + @mock.patch( + "google.auth.transport._mtls_helper._get_workload_cert_and_key_paths", + return_value=(CERT_FILE, KEY_FILE), + ) + def test_retrieve_subject_token_certificate_trust_chain_without_leaf( + self, mock_get_workload_cert_and_key_paths + ): + credentials = self.make_credentials( + credential_source=self.CREDENTIAL_SOURCE_CERTIFICATE_TRUST_CHAIN_WITHOUT_LEAF + ) + + subject_token = credentials.retrieve_subject_token(None) + assert subject_token == json.dumps([CERT_FILE_CONTENT, OTHER_CERT_FILE_CONTENT]) + + @mock.patch( + "google.auth.transport._mtls_helper._get_workload_cert_and_key_paths", + return_value=(CERT_FILE, KEY_FILE), + ) + def test_retrieve_subject_token_certificate_trust_chain_invalid_order( + self, mock_get_workload_cert_and_key_paths + ): + + credentials = self.make_credentials( + credential_source=self.CREDENTIAL_SOURCE_CERTIFICATE_TRUST_CHAIN_WRONG_ORDER + ) + + with pytest.raises(exceptions.RefreshError) as excinfo: + credentials.retrieve_subject_token(None) + + assert excinfo.match( + "The leaf certificate must be at the top of the trust chain file" + ) + + @mock.patch( + "google.auth.transport._mtls_helper._get_workload_cert_and_key_paths", + return_value=(CERT_FILE, KEY_FILE), + ) + def test_retrieve_subject_token_certificate_trust_chain_file_does_not_exist( + self, mock_get_workload_cert_and_key_paths + ): + + credentials = self.make_credentials( + credential_source={ + "certificate": { + "use_default_certificate_config": "true", + "trust_chain_path": "fake.pem", + } + } + ) + + with pytest.raises(exceptions.RefreshError) as excinfo: + credentials.retrieve_subject_token(None) + + assert excinfo.match("Trust chain file 'fake.pem' was not found.") + + @mock.patch( + "google.auth.transport._mtls_helper._get_workload_cert_and_key_paths", + return_value=(CERT_FILE, KEY_FILE), + ) + def test_retrieve_subject_token_certificate_invalid_trust_chain_file( + self, mock_get_workload_cert_and_key_paths + ): + + credentials = self.make_credentials( + credential_source={ + "certificate": { + "use_default_certificate_config": "true", + "trust_chain_path": SUBJECT_TOKEN_TEXT_FILE, + } + } + ) + + with pytest.raises(exceptions.RefreshError) as excinfo: + credentials.retrieve_subject_token(None) + + assert excinfo.match("Error loading PEM certificates from the trust chain file") def test_retrieve_subject_token_json_file_invalid_field_name(self): credential_source = { From 99fe856112748c6cb007acc5aa7dd87249e0a77e Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Fri, 28 Mar 2025 18:35:30 +0000 Subject: [PATCH 890/966] fix: add request timeout for MDS requests (#1699) * fix: add request timeout for MDS requests * add seconds --- .../google/auth/compute_engine/_metadata.py | 6 +++- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../tests/compute_engine/test__metadata.py | 26 ++++++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/compute_engine/_metadata.py b/packages/google-auth/google/auth/compute_engine/_metadata.py index 06f99de0e2ca..ddbe8ac2f708 100644 --- a/packages/google-auth/google/auth/compute_engine/_metadata.py +++ b/packages/google-auth/google/auth/compute_engine/_metadata.py @@ -159,6 +159,7 @@ def get( retry_count=5, headers=None, return_none_for_not_found_error=False, + timeout=_METADATA_DEFAULT_TIMEOUT, ): """Fetch a resource from the metadata server. @@ -178,6 +179,7 @@ def get( headers (Optional[Mapping[str, str]]): Headers for the request. return_none_for_not_found_error (Optional[bool]): If True, returns None for 404 error instead of throwing an exception. + timeout (int): How long to wait, in seconds for the metadata server to respond. Returns: Union[Mapping, str]: If the metadata server returns JSON, a mapping of @@ -204,7 +206,9 @@ def get( failure_reason = None for attempt in backoff: try: - response = request(url=url, method="GET", headers=headers_to_use) + response = request( + url=url, method="GET", headers=headers_to_use, timeout=timeout + ) if response.status in transport.DEFAULT_RETRYABLE_STATUS_CODES: _LOGGER.warning( "Compute Engine Metadata server unavailable on " diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index b5b063fca331994a2dbb2ee4321861e456297a23..307fcb5778623610841b4d6bbbc3f59294aab089 100644 GIT binary patch literal 10324 zcmV-aD67{BB>?tKRTHn$p36~kWG7lo3pF+wGG(1cJIbXQi!HbVhpHG#8fFrzPyk0I z;}-nYYk)ASq$PGZ%aw|;wxSVJ&P}%435v`dY(%x7n-1mZeQuc01H$h|u6fHdQ26m9 zW9c!r&h{(f<3J0pEs_CdulJ_KM>w1WD=dEGD^epcWpn3u9h$c>1x~^%S(-6X%2AfB z>nzOq*uAQJ+Ki|kxfeHejl9L)3%oOHe7iE|Wc|-tvvewD+O7jHuGfz0oVnku@d#P+VO>1n`Z7DNF(vcm7|l?LnYdSyQiEr=QgU=NtZ3 zf^s!1SCn2|u6Vc?6^DBmjo94#5D-V-tM;~gP$cVnAO+^df*`d|r2$JKZ;_i|&^#d!BxG2rl=kiuw>Xi$wzk)I$PJjKGIR&j793p12emCU ze;DN~oAJivWNQf(vJ%=jQ5cFg7EyU6cMD4^ifYr^oQ_T`_gyz?xmpWdYpdNa}!)+CA(lRpl-DE+(j0ch) z&=mtKz4VNe;j}u@`#(~^2rd1b5f01s_;{f1BE7&gU-g)opd)B8pCF2YKoYQZ)|}`P zWQpLulv#!1<-_4H#(`hc@+E%=-fYGPv>%;L+a_VI8Aak|1}S$+t9_h73UHNr6Xd%H*KK3;aB*%v=lm=GBwNYrgAyIRIzIg$S?OB+ifyz2gOp?VgCMXvboE;%B%71-}9~kW6cVB%;8)Wnv zrQM9GOcX?nMo-O?M4=>S?pMoZr11z*k0BOCeS!7Vn{q#<+cnD&b1)_;S5ayT zJmyFZlZUgG?Ss)eVss$dvOL`(G5#HCed4y}%D)28FtU=7VmD9o>TV7jwo_JyO9Z53 z9V0TMLC^>TPG-x!AG3QK3!RE}n&bLJCJzozPNyo`#gv+KiLB7NXEZ(An8EshfD2z) z2ME~wh!$n68SWVi1-!X7x_0t93>s3y{PK6W3|M-=b-G+}kH@!kB-`{gd@7*2CWVwd z&{}$J4mn{Y?52Ed@yOs~^BfeysNz)Q+;8I;SJ~XM65d z3*?-Nqo5)D67z6ag62Tq5GtBy+ypNeA%NTH8dgCa(@W@ceOE_1oyjZdof{kg_NgAw zEU2b--zTUro!MC?H3yx(~Amn-0=Ll`puebD%;nhTjd~pdt z^syY{Q7tSEADu)RX`2nG8gAhb(p(>07Dd3A_JpPmr){c%Cf9f8NmcdZGO?R&PA+f0 zocD}P{h<4br8#P|Tmj>TF_O>&5#Rj^3fC1OgV1=2^x7r&;4>X0b9JhIS(SUOfxfB} zgQK)94E+p!P1uQC0OcDLq!HbTo@ILM-jE^+r3F_-cle7xfa{XJP6Gryb}oXO&QT0| z99$(%8~y>M?(hz)yrP6!VOEGS(N9B)7MHhcPxvM`zgIfBuL?)#NMtmjJCf@TiTm!4d4a`a2gSB4DSyBm>g zZNF)$Y{(#lLT9$b9N+x^)ZXpR%kTKZ?#nUv2N5%}yot*Nv)?78_3kq;aRVEUXp8=X z8kwn(lqrgaYE9j<0h)c+GrGB9MB?Fv%g-s2_DhlHn3U%TMU~1X)JJ156r4#)9mJ1` zEBU>e6o-CNrd9?zePf>3orSETLrA0sJ z=o6@eR5cyI)4ckSI#({$d`tWmWz`WReKLra70Q<=$A>-cU_au6(f#`v9nAWA0hc(! zRxvry6^_Kar49DzK6WGm&(srfMiIKN#>V>1@HWh2^w|GIbxrJ+n2^rwn6Y`U;{)f2 zKmgTjr@(0%Yqs2}QzF&|a8h`aeWPm&dTr|}$x;-x zS_yd6qM1Gqn|A#V1+3Q`EmXSt(runk2jHiu!lR?;7#mZ`^${=ToYpZWLs%~>jfir^ zh%5LE3Pk}r8mr%Sh5no+AGbhbW&&S?L*_z94_&k*IjgPn(mCe9*OniKl1SmODeU%8_fVrA~xsZ6t(enyCy27})&yB}vL;(U(-t{M8le*?!%TZWKO5r@J zBq$odzu-*Z(IOtOQ9K{S^Gui;m-RIS9S<)llOZ2Ml&9;T@O;Q>b%J1=ceQHn#uVil zdmah1(er>0Bn>p53a@H8>!P%aY(u(^L1Tx5+mO5vC~g@LmR~LtEhtdK7~qR|e|K$Z zF?kO!nS&-#L13AR)4rp8m7)q%H;3$|P|##ZY_-2-6k51IxaYu$=rB@TPTov01?JZn z@5Mr$);snt&HuM7_>8XIY?t(|7&Zr-q z`Tbtva3k5@CnvwyPRKetkt&_~zGrzDGMCRd<)pK-gt-^-@PF}oLVE(pV*W8R6~(TD zJT+dAndXn;9b0<5;gk~`w;EIA*Rf&9e+*D+8W|2EN1bkSe_|(Hszf#H6}35qU1HS!xbS$U?i0YE3Cbbr4L z2C-BtPtFnBkCWW8LBlhB7-kjX8mw7Jo6qIg?3hi>pi4*W2K-{h&exCU*twrqr&SC> zxDoRmX23sg2w?8=rX}pY_#waIyYXFSJ~}Z-Mlr5~nZU;rIz=tDm^6ssJ4W~ADRw4i zI?>eV`%?*l@A%=<`kcmDU89VL0`!Dy1M>%?DXcT1y`M#X^%r=buv8!+a5aE(z@6Zz zjwQR01JbhYm_$v|Z(Q;}x+DR=M5TCV3dYY2BD^Ixbav% zT7_Hln3Fb{{<2#n?*FlFx%p=!@P|u!keHldi5Q|w+C&JAgvXpIr9mYU?8uXDh@8dP zL#_N__%nZspwm(|YfMQ#sBu!ZfN##kr^6TP7e%3j+v8>s>f4ikIVbC}*+5N4Q1d)I zKm~_NyVQ;c+p!|@$@^D$B<&kkkAe2x8-NeE9h__qbrHXgF8HU;UABvKZb0k+ge3mcXM`!mpu&l_jsbW64zqlf&^_?it-*1DLa$4_Pb zpZ@-HK?(aY^S|-or+*EFpK%RoGP>l1EBv;8&8;vzJ| z|0p{641-@eyr`Ik3ojVLsbq4E^{A0qF}!`V6~D;Sj7> ze?6eC%4ckf+5yiE5>oo^2%V=K=!Rc3EVKA*EDS60ldKi4&h#vY88&OCi2Q9GeXwfk zgv}E>uRj(Rz^LAdrhm5taDb3Yn-dh+-W16bLU9BHn0XeBTQY;R#2vE?Ss{-mWg@8Bvwg7E zdNpS`-2ia^aUe2Yi9(T{9hd zRmVdbP@dv!1&69eyeV=Hd+HT0pze2(7No)MvSL%3aqi1FX8iW6H8z z%oHJDgAX~NNvs4Wr4h2Fy<_fYi(Lp9Myw11Ol}+$6@bNLx8!bVKh6bS_D}cyOn7uB ztMrz}8$Z$AHt?>@ryn$ZloYRslZ%}@qX8!lFUl5!V>|fbiom{|EP>W_)C;9>KL~bL z(irLKr_URAfp6YHHbUrFk2c* z;69_DIX<6HUdb)b5k{tTt3uu^ZH2=<;gJ$^?2D?Bd-J)HPtgGNYM;Q@?)G7=^CV)A zvT+G^7P0g%(R{+=B&yJ>9V}M~2weQVf}7f_-hBZ8dsBr(>mK(;fOQ{XnA=vq5UmF{ zGqLC%AW1vp5x8R|&PYn{ec5*8S^w4oVhVl9a9BgfnW~;0BE%KTmq%Of%qVFn=rQyF z_Z*6snz`zAxR|4AJPX!lVbuU92f_>`+3NzXeAD`b3?DU7@&|i?WFx8HpuN;IJGgaB z=dyb7w?Twy23X>Ax%k^)jzVRR@W|J;730#>8`6UEPg1IeG|^F9BzK}+vY?m$E$arR zrOd4UWQW%EpgU5=H)v#qBW*-R6CwSh1$*Uxs~7?;)hb6^6AkI}&FqdAdffS7Ad>Ui zj7iyZ&{72H9`^AnuIJ9kNn_2^FQ`WZgOG5tt&#L#X?XQ|<|Z8nZqOc2zR7rUAnRvI z>lo94kTQ&>8h5yFh`y@+n){ap?ELQOG7;lO<{E_FD|mT77=4x;k8+&eA`i)Nedw_Q zX=f4R3x|6Z-32jEp~4^E|p7K<&#C%MmcebfXsdhWPca+(?m{Qwjs-X|rkJWM93 zZl29sDpZGTkX_)^QRzF2JPmv~jlrx^bOjd6e*KCI(J86@A{xoyWgT%t7fax_l~-|X zzl!I*X^VDXCDmu&o^eRTmZxdT+d~+|Ad@z8=9Q4Iy^<5QwJTC#G8X;#D_t|(#@l?= zk286(uE>?L1cyOT1|o@5?KSYiL=LAVq)bC)nbtRaWFQw`O;`3R*kiUgjK@yL@a-6J_G~DPg;WwiQGI+$mF6JYvI)}FDxTo z-<5yAJJy5FM4JrFRRnKuH_%$iC6w6Umq$KechM{%U9Ln^$Dn~B(?A83m!gG)|86}W zPNhH@a4PJ9E`(jgb?t%daRkv65dArP2QD04?b<63t}FeVR3BFVghOi&4~kZ&Iy9YG zz*ZUjKr7W8Eoyl!^@fdHI8x~teO1)d`X8(JODIbAuuCd+EeL}X3Y%!{YqJEi)i#5; zenArPU}~0qKqH$E{Pwy0Tj{~*kw_R(ntOBJLBt@!4ZjW5uAWP{aNkO^`|P zmDn^dZ41K{P_!HoJ9@8tvE!ot?41-)A0j74m^|h|%G6WH-q+DShDxc>0QoxuE6eir z7|){77r8v55b4{ZiIk)#pcD^$QkaBR(SM%eOgANJQ23>H<9l8=MNR;a;?dcxhSN_F zesseoYN!p|q0`=LgS7-=_FT`_?J>hr$UtAr=&EYUE*20c*}dmDUQd35KL#j#7*9F_+t8 zH#PV#9SAo`Eq|Yp%IrK_;o5>C4U61ykClp;yWWdhUo0z!{tI3KePzIjq6f^w3=^r? zdo}DZ7yWLcOfSH3OK_Rzc!Ii$Kxu2*e+YZ}5F#v>Y%%)&-A7OFmiZDf;!~OYkj&w% ztM0BD0Ym|A@KR>?=GM#L;k=+8Ii;SFwcie8CBP^2%-oi5oO$uW3DJwQ8Xp6tn;ZcQ zHwaE$F~H*mW(yG0aHMcUY&yh&kvnsXVoi;E(X$vx%)8_eultN#}ed{EE^>}u{Uwph!n$}xj}|vwD*oze%Qlm zuA8_b@Ti(0TZJBMBM`@p?bz^Z3iCxu1xh28!8>fHA`3hi&pmHvzy$=Xh^}QZn zrz`6d+E>j_9(f}Ny12Qpb!$L`6!dX-NzVozb+_&IfhvUUhJ%5e`7O(SP?IA~(NqP9c}K$5YRz@u8xJC@&#oWKQ(PS&bnSSUpTrBK zdKDUv@;DmRUE+bmPdieV_PKSAnR_or*9mKrBJBj4j(-~ z>X*=mG_B4l+C5fLBLNAOsBC)>AvCm?(d?Z_ zS2tMe$33uURsyWj8j@}#Ti9$(lFe1UQ~wikA`UDAlaT%>r5S>m`WCc@bMA9Q4dzRE{Qa7i{OJELPlQ<9?#|d%AVl1IU+csZAy= zSW}zX%I6+gCyxFQPx&DVBPk?|otUFJ?GL{6={U};onT85XXI_+Kzojizj&eVZkViM z@kKapJji5^8Pj5NGFiaq1`3sj+FHm^bwh1Js?%0d&mq@g z5-ZW!w6GMq5GLJ+R=bv88we$R=ncV*nS0>qyV12OofEC81wJF23=-O4L21oFkD>AS z{5tV5i6KRv22_O0GdkqJL#a)3Sl~p_qjk{#sFWD(>SNZF=NsUMW6C>M)9Vxce<{mXJo^ z0+@onl$0>32R2`l!Pc?Mw4zn-}an zHU7iXdTrFlnA)+Gj)j^LT1&ESgO`l8E}_N8s1)R^3SLWb?6T=MK>S4T)MbH2e_1O9 zs39@_TZyA7@Q4&b=lSQ~K=S}Xv6M`Q&askCU)1cq17h3c_3 zXX-lqU1jhYG&Bi6J|{A6y>wtu#1LHM4ea@BM4Mhz#9g-WKYn) zH-UM^jR2Z38hE!*>*@Fk0ki9YP@^vX`84akqjxD0bGav4c2F7o*LA@ctN%*c@a6a9q=bS^ylo+YC?%p^(!Rxr%e@xYf9C*EYVWxstsO^Slq;G9uh>602sd?lNiZOd$c;SZeeL{}+}4lk z;&pmIEx?J4K1sAK zGOOKXF88rB15S=`!mAnGRRsJ_XdamM zt@oiJBDFxI^u_&fX^}!9T8GGn9LSrJ6G)@IuAO^I-&=>5rzh&2RBeryxH$**K*l{l zafyfi$J^Z8G=HRe2bYl$!MM}9XF|n8{nunS`(qim-GvGPTfQ4Jj@=+GgXS08>C(`2+p+tgvo2niigEal^9$s9WoYV_`VCRSo zqw`(@qh89e{}CYKd#glJH0f?pleAIuvR?>2nSMC!kD$srP2y+V30K-A8N6<@JFsP= zXJfvCLU?cpb-~5_AYKr|YM$>=#w$4inb4-SUyfCYE$IVKZpCtQ@9q*rw#OKnvc4-R zOg+rQBVR>XC)n?u7K#LiD1M+ZTXkJ}l3!PG&EeY&QRmDHe{-U~^3lnhT%kW)ck))M zI&+?k7~je(;eorYsRPRx>*TGM%G!vrB2X#E8Xl8cd=H%`-H=r>LKr&&EV+@F>N?_2 zfGzfih8Z9Ao<#?NycC$N1;5H(|GYLb1I|hT9mZs5p(-IHPNzH#LrTLgb|vHmB8cm! z76c$0Exf@I`C;O3sl;2y54RU(@iS!FlzDS9Q&d^1E^RJLpYhFQqUqIsb#I}MMQH^W zMNvAVAZZ4=ixCGKas(bYXrxQMc`JwBs1o6jn91x!P3Zixg-K6y!vyfk3E^#Ry-W*8 zECqmyfaVJ64MChbLq=w6UE8oh4=G3#3WLvf$b>7zee|c3P-*>QHpgR8@`NGT7c%EvTiZacxD~aP3x~XvtR2${ z2Np$kp-12bsG+WcXOZzcNr@L&Jx&eZOw%d$|DUi`5W(eYY`rqS@^M-V6k7TkpbmU$ zjHu!JdsZ}Ghi6f|3G%bvu`SN_=2_h_eZt`5#6MA`M`zqMiDd(5#pl%V0o;Bkhg#`2 z=L&zYPx&BvK}Zi`eH}vcrn#i-mA?sLq;|UqMD>;Bp45MiNg`{m8Eo^XGlb#CVsB6X zaYEy~S=?604B)))(gSo!6({0(r(SP3o?0QY?jfU21tU-VGRhs?tHEmT07p59o$T%_ zwqPY5Em{JQ9g0k8eysFT-WJLImGv68buARp-$gXh^W#J!Cn3DpMq}thbN6zjsZfR9 zG3MbL%q*>6eO}j91inCXH-$|C+RI51oMg99Db^G2>dts^TxALBAWG`IgCea#a&AI2 z)7Zu0Or{Vd@2dhiMrXmxnzqK)x~!*J1aovy!#S}!q_p0x$13j=>sSN=d}VVlQG1GJ zB|B{~uIc~SLK6v0wD#trx+T0iLOzr_0*n;)Iw*AV2NF(9b@M&q>3Y1i-v1K1Jnqgq z(_!rRQK(v; zXPbwqSdF8@%xf!(1XK<}9s}^%D$~46mIGom1rUY}UMPhez}Z9qMad1`lGIpLP{KcJ z)i=f|zBjT(#~1Gf#Qd$(MXfdsc=*s&`ttgze5r+Tdf*dqB zgU>R{G_GoscaZ5N^GR;~!*L^)02UhX$2r}A2`uD4la@87W_P8LowAPyV|C&$j6*e7 zYX_%!`gw#31D)|Y3sDnO3=i{~WDcaC)M7I3gN$=p)!#=q()NN-C~kkg#x7^&!qj%j zDZyX?=rGiX-D*wr=_ZzI8CFE(kzk-a-GeiIZ2QPcs*E|RG-p-Dt4H_Vg7)p?%cqlm zz?D*Aipd1j-ufiHzH8BSIp--!BV3OBlEG=Vxdm!Cul8!8jD_jVzj m?sDnmd19DQzrX~5CvN=dKdZ1bo)a~rGy`zgWkNympEX6uSjr#( literal 10324 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 diff --git a/packages/google-auth/tests/compute_engine/test__metadata.py b/packages/google-auth/tests/compute_engine/test__metadata.py index 7c028eb6232f..c90bc603a801 100644 --- a/packages/google-auth/tests/compute_engine/test__metadata.py +++ b/packages/google-auth/tests/compute_engine/test__metadata.py @@ -174,6 +174,7 @@ def test_get_success_json(): method="GET", url=_metadata._METADATA_ROOT + PATH, headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) assert result[key] == value @@ -192,6 +193,7 @@ def test_get_success_json_content_type_charset(): method="GET", url=_metadata._METADATA_ROOT + PATH, headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) assert result[key] == value @@ -211,6 +213,7 @@ def test_get_success_retry(mock_sleep): method="GET", url=_metadata._METADATA_ROOT + PATH, headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) assert request.call_count == 2 assert result[key] == value @@ -226,6 +229,7 @@ def test_get_success_text(): method="GET", url=_metadata._METADATA_ROOT + PATH, headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) assert result == data @@ -241,6 +245,7 @@ def test_get_success_params(): method="GET", url=_metadata._METADATA_ROOT + PATH + "?recursive=true", headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) assert result == data @@ -255,6 +260,7 @@ def test_get_success_recursive_and_params(): method="GET", url=_metadata._METADATA_ROOT + PATH + "?recursive=true", headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) assert result == data @@ -269,6 +275,7 @@ def test_get_success_recursive(): method="GET", url=_metadata._METADATA_ROOT + PATH + "?recursive=true", headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) assert result == data @@ -290,6 +297,7 @@ def test_get_success_custom_root_new_variable(): method="GET", url="http://{}/computeMetadata/v1/{}".format(fake_root, PATH), headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) @@ -310,6 +318,7 @@ def test_get_success_custom_root_old_variable(): method="GET", url="http://{}/computeMetadata/v1/{}".format(fake_root, PATH), headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) @@ -326,6 +335,7 @@ def test_get_failure(mock_sleep): method="GET", url=_metadata._METADATA_ROOT + PATH, headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) @@ -338,6 +348,7 @@ def test_get_return_none_for_not_found_error(): method="GET", url=_metadata._METADATA_ROOT + PATH, headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) @@ -357,6 +368,7 @@ def test_get_failure_connection_failed(mock_sleep): method="GET", url=_metadata._METADATA_ROOT + PATH, headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) assert request.call_count == 5 @@ -375,6 +387,7 @@ def test_get_too_many_requests_retryable_error_failure(): method="GET", url=_metadata._METADATA_ROOT + PATH, headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) assert request.call_count == 5 @@ -391,6 +404,7 @@ def test_get_failure_bad_json(): method="GET", url=_metadata._METADATA_ROOT + PATH, headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) @@ -404,6 +418,7 @@ def test_get_project_id(): method="GET", url=_metadata._METADATA_ROOT + "project/project-id", headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) assert project_id == project @@ -419,6 +434,7 @@ def test_get_universe_domain_success(): method="GET", url=_metadata._METADATA_ROOT + "universe/universe-domain", headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) assert universe_domain == "fake_universe_domain" @@ -432,6 +448,7 @@ def test_get_universe_domain_success_empty_response(): method="GET", url=_metadata._METADATA_ROOT + "universe/universe-domain", headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) assert universe_domain == "googleapis.com" @@ -447,6 +464,7 @@ def test_get_universe_domain_not_found(): method="GET", url=_metadata._METADATA_ROOT + "universe/universe-domain", headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) assert universe_domain == "googleapis.com" @@ -467,6 +485,7 @@ def test_get_universe_domain_retryable_error_failure(): method="GET", url=_metadata._METADATA_ROOT + "universe/universe-domain", headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) assert request.call_count == 5 @@ -509,11 +528,13 @@ def request(self, *args, **kwargs): method="GET", url=_metadata._METADATA_ROOT + "universe/universe-domain", headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) request_ok.assert_called_once_with( method="GET", url=_metadata._METADATA_ROOT + "universe/universe-domain", headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) assert universe_domain == "fake_universe_domain" @@ -533,6 +554,7 @@ def test_get_universe_domain_other_error(): method="GET", url=_metadata._METADATA_ROOT + "universe/universe-domain", headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) @@ -557,6 +579,7 @@ def test_get_service_account_token(utcnow, mock_metrics_header_value): "metadata-flavor": "Google", "x-goog-api-client": ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, }, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) assert token == "token" assert expiry == utcnow() + datetime.timedelta(seconds=ttl) @@ -583,6 +606,7 @@ def test_get_service_account_token_with_scopes_list(utcnow, mock_metrics_header_ "metadata-flavor": "Google", "x-goog-api-client": ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, }, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) assert token == "token" assert expiry == utcnow() + datetime.timedelta(seconds=ttl) @@ -611,6 +635,7 @@ def test_get_service_account_token_with_scopes_string( "metadata-flavor": "Google", "x-goog-api-client": ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, }, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) assert token == "token" assert expiry == utcnow() + datetime.timedelta(seconds=ttl) @@ -628,6 +653,7 @@ def test_get_service_account_info(): method="GET", url=_metadata._METADATA_ROOT + PATH + "/?recursive=true", headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) assert info[key] == value From 7749adf803ce6637fb8b3bf80131708d37863485 Mon Sep 17 00:00:00 2001 From: Harkamal Jot Singh Kumar Date: Mon, 7 Apr 2025 14:50:21 -0700 Subject: [PATCH 891/966] fix: add missing packaging dependency for feature requiring urllib3 (#1732) --- .../google/auth/transport/urllib3.py | 16 ++++++++++++---- packages/google-auth/setup.py | 1 + .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/google-auth/google/auth/transport/urllib3.py b/packages/google-auth/google/auth/transport/urllib3.py index 63144f5fffa4..db4fa93ff11b 100644 --- a/packages/google-auth/google/auth/transport/urllib3.py +++ b/packages/google-auth/google/auth/transport/urllib3.py @@ -34,13 +34,21 @@ try: import urllib3 # type: ignore import urllib3.exceptions # type: ignore + from packaging import version # type: ignore except ImportError as caught_exc: # pragma: NO COVER raise ImportError( - "The urllib3 library is not installed from please install the " - "urllib3 package to use the urllib3 transport." + "" + f"Error: {caught_exc}." + " The 'google-auth' library requires the extras installed " + "for urllib3 network transport." + "\n" + "Please install the necessary dependencies using pip:\n" + " pip install google-auth[urllib3]\n" + "\n" + "(Note: Using '[urllib3]' ensures the specific dependencies needed for this feature are installed. " + "We recommend running this command in your virtual environment.)" ) from caught_exc -from packaging import version # type: ignore from google.auth import environment_vars from google.auth import exceptions @@ -414,7 +422,7 @@ def urlopen(self, method, url, body=None, headers=None, **kwargs): body=body, headers=headers, _credential_refresh_attempt=_credential_refresh_attempt + 1, - **kwargs + **kwargs, ) return response diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index b5c7e627cf85..ace714b1fbd7 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -34,6 +34,7 @@ "reauth": "pyu2f>=0.1.5", "enterprise_cert": ["cryptography", "pyopenssl"], "pyjwt": ["pyjwt>=2.0", "cryptography>=38.0.3"], + "urllib3": ["urllib3", "packaging"], } with io.open("README.rst", "r") as fh: diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 307fcb5778623610841b4d6bbbc3f59294aab089..862bae58c74d7720282ac7c89beb94c09e192421 100644 GIT binary patch literal 10324 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 From c743f43c68b95a5f211bb511bbcb6881f8d1d2a8 Mon Sep 17 00:00:00 2001 From: Harkamal Jot Singh Kumar Date: Wed, 9 Apr 2025 15:07:10 -0700 Subject: [PATCH 892/966] chore: update secret for tests (#1742) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 862bae58c74d7720282ac7c89beb94c09e192421..1c8487b87e3ddec611e9515fdafdfee2dfdb2681 100644 GIT binary patch literal 10324 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 literal 10324 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 Date: Thu, 10 Apr 2025 19:20:51 +0500 Subject: [PATCH 893/966] chore: add testing requirements extra (#1707) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: add testing requirements extra * address PR feedback * update extra for build * add missing test deps * add pyjwt extra * add reauth extra * refactor extras in setup file * address unit test failures * link issues * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * Update setup.py Co-authored-by: Anthonios Partheniou * Update setup.py Co-authored-by: Anthonios Partheniou * address PR feedback * Update setup.py Co-authored-by: Anthonios Partheniou * Update setup.py Co-authored-by: Anthonios Partheniou * Update setup.py Co-authored-by: Anthonios Partheniou * address PR comments --------- Co-authored-by: Owl Bot Co-authored-by: Anthonios Partheniou --- packages/google-auth/noxfile.py | 9 +-- packages/google-auth/setup.py | 70 +++++++++++++++++-- packages/google-auth/testing/requirements.txt | 21 ------ 3 files changed, 66 insertions(+), 34 deletions(-) delete mode 100644 packages/google-auth/testing/requirements.txt diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index 07cef9bcce3d..ef48441a7c49 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -89,8 +89,7 @@ def unit(session): constraints_path = str( CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt" ) - session.install("-r", "testing/requirements.txt", "-c", constraints_path) - session.install("-e", ".", "-c", constraints_path) + session.install("-e", ".[testing]", "-c", constraints_path) session.run( "pytest", f"--junitxml=unit_{session.python}_sponge_log.xml", @@ -105,8 +104,7 @@ def unit(session): @nox.session(python="3.8") def cover(session): - session.install("-r", "testing/requirements.txt") - session.install("-e", ".") + session.install("-e", ".[testing]") session.run( "pytest", "--cov=google.auth", @@ -144,8 +142,7 @@ def docs(session): @nox.session(python="pypy") def pypy(session): - session.install("-r", "testing/requirements.txt") - session.install("-e", ".") + session.install("-e", ".[testing]") session.run( "pytest", f"--junitxml=unit_{session.python}_sponge_log.xml", diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index ace714b1fbd7..fcdcb21e00cb 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -27,14 +27,70 @@ "rsa>=3.1.4,<5", ) +# TODO(https://github.com/googleapis/google-auth-library-python/issues/1737): Unit test fails with +# `No module named 'cryptography.hazmat.backends.openssl.x509' for Python 3.7``. +cryptography_base_require = [ + "cryptography >= 38.0.3", + "cryptography < 39.0.0; python_version < '3.8'", +] + +requests_extra_require = ["requests >= 2.20.0, < 3.0.0"] + +aiohttp_extra_require = ["aiohttp >= 3.6.2, < 4.0.0", *requests_extra_require] + +pyjwt_extra_require = ["pyjwt>=2.0", *cryptography_base_require] + +reauth_extra_require = ["pyu2f>=0.1.5"] + +# TODO(https://github.com/googleapis/google-auth-library-python/issues/1738): Add bounds for cryptography and pyopenssl dependencies. +enterprise_cert_extra_require = ["cryptography", "pyopenssl"] + +pyopenssl_extra_require = ["pyopenssl>=20.0.0", cryptography_base_require] + +# TODO(https://github.com/googleapis/google-auth-library-python/issues/1739): Add bounds for urllib3 and packaging dependencies. +urllib3_extra_require = ["urllib3", "packaging"] + +# Unit test requirements. +testing_extra_require = [ + # TODO(https://github.com/googleapis/google-auth-library-python/issues/1735): Remove `grpcio` from testing requirements once an extra is added for `grpcio` dependency. + "grpcio", + "flask", + "freezegun", + "mock", + # TODO(https://github.com/googleapis/google-auth-library-python/issues/1736): Remove `oauth2client` from testing requirements once an extra is added for `oauth2client` dependency. + "oauth2client", + *pyjwt_extra_require, + "pytest", + "pytest-cov", + "pytest-localserver", + *pyopenssl_extra_require, + *reauth_extra_require, + "responses", + *urllib3_extra_require, + # Async Dependencies + *aiohttp_extra_require, + "aioresponses", + "pytest-asyncio", + # TODO(https://github.com/googleapis/google-auth-library-python/issues/1665): Remove the pinned version of pyopenssl + # once `TestDecryptPrivateKey::test_success` is updated to remove the deprecated `OpenSSL.crypto.sign` and + # `OpenSSL.crypto.verify` methods. See: https://www.pyopenssl.org/en/latest/changelog.html#id3. + "pyopenssl < 24.3.0", + # TODO(https://github.com/googleapis/google-auth-library-python/issues/1722): `test_aiohttp_requests` depend on + # aiohttp < 3.10.0 which is a bug. Investigate and remove the pinned aiohttp version. + "aiohttp < 3.10.0", +] + extras = { - "aiohttp": ["aiohttp >= 3.6.2, < 4.0.0.dev0", "requests >= 2.20.0, < 3.0.0.dev0"], - "pyopenssl": ["pyopenssl>=20.0.0", "cryptography>=38.0.3"], - "requests": "requests >= 2.20.0, < 3.0.0.dev0", - "reauth": "pyu2f>=0.1.5", - "enterprise_cert": ["cryptography", "pyopenssl"], - "pyjwt": ["pyjwt>=2.0", "cryptography>=38.0.3"], - "urllib3": ["urllib3", "packaging"], + "aiohttp": aiohttp_extra_require, + "enterprise_cert": enterprise_cert_extra_require, + "pyopenssl": pyopenssl_extra_require, + "pyjwt": pyjwt_extra_require, + "reauth": reauth_extra_require, + "requests": requests_extra_require, + "testing": testing_extra_require, + "urllib3": urllib3_extra_require, + # TODO(https://github.com/googleapis/google-auth-library-python/issues/1735): Add an extra for `grpcio` dependency. + # TODO(https://github.com/googleapis/google-auth-library-python/issues/1736): Add an extra for `oauth2client` dependency. } with io.open("README.rst", "r") as fh: diff --git a/packages/google-auth/testing/requirements.txt b/packages/google-auth/testing/requirements.txt deleted file mode 100644 index 742c4a46d695..000000000000 --- a/packages/google-auth/testing/requirements.txt +++ /dev/null @@ -1,21 +0,0 @@ -# Unit test requirements -flask -freezegun -mock -oauth2client -pyopenssl -pytest -pytest-cov -pytest-localserver -pyu2f -pyjwt -requests -urllib3 -cryptography < 39.0.0 -responses -grpcio -# Async Dependencies -pytest-asyncio; python_version > '3.0' -aioresponses; python_version > '3.0' -asynctest; python_version > '3.0' -aiohttp < 3.10.0; python_version > '3.0' From 3bfa661266a165d8e2d39ee21ebb3c8845a247fa Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Thu, 10 Apr 2025 09:39:16 -0700 Subject: [PATCH 894/966] chore(python): update templated files (#1709) Source-Link: https://github.com/googleapis/synthtool/commit/87677404f85cee860588ebe2c352d0609f683d5d Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:023a21377a2a00008057f99f0118edadc30a19d1636a3fee47189ebec2f3921c Co-authored-by: Owl Bot Co-authored-by: Anthonios Partheniou --- packages/google-auth/.github/.OwlBot.lock.yaml | 6 +++--- packages/google-auth/.kokoro/test-samples-impl.sh | 3 ++- packages/google-auth/renovate.json | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/google-auth/.github/.OwlBot.lock.yaml b/packages/google-auth/.github/.OwlBot.lock.yaml index 13fc69ce9fc9..c4e82889dc81 100644 --- a/packages/google-auth/.github/.OwlBot.lock.yaml +++ b/packages/google-auth/.github/.OwlBot.lock.yaml @@ -1,4 +1,4 @@ -# Copyright 2024 Google LLC +# Copyright 2025 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,5 +13,5 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:5efdf8d38e5a22c1ec9e5541cbdfde56399bdffcb6f531183f84ac66052a8024 -# created: 2024-10-25 + digest: sha256:023a21377a2a00008057f99f0118edadc30a19d1636a3fee47189ebec2f3921c +# created: 2025-03-31T16:51:40.130756953Z diff --git a/packages/google-auth/.kokoro/test-samples-impl.sh b/packages/google-auth/.kokoro/test-samples-impl.sh index 55910c8ba178..53e365bc4e79 100755 --- a/packages/google-auth/.kokoro/test-samples-impl.sh +++ b/packages/google-auth/.kokoro/test-samples-impl.sh @@ -33,7 +33,8 @@ export PYTHONUNBUFFERED=1 env | grep KOKORO # Install nox -python3.9 -m pip install --upgrade --quiet nox +# `virtualenv==20.26.6` is added for Python 3.7 compatibility +python3.9 -m pip install --upgrade --quiet nox virtualenv==20.26.6 # Use secrets acessor service account to get secrets if [[ -f "${KOKORO_GFILE_DIR}/secrets_viewer_service_account.json" ]]; then diff --git a/packages/google-auth/renovate.json b/packages/google-auth/renovate.json index 39b2a0ec9296..c7875c469bd5 100644 --- a/packages/google-auth/renovate.json +++ b/packages/google-auth/renovate.json @@ -5,7 +5,7 @@ ":preserveSemverRanges", ":disableDependencyDashboard" ], - "ignorePaths": [".pre-commit-config.yaml", ".kokoro/requirements.txt", "setup.py"], + "ignorePaths": [".pre-commit-config.yaml", ".kokoro/requirements.txt", "setup.py", ".github/workflows/unittest.yml"], "pip_requirements": { "fileMatch": ["requirements-test.txt", "samples/[\\S/]*constraints.txt", "samples/[\\S/]*constraints-test.txt"] } From ea48ac75d1b5f76c5d018121a9ff387cdf9bb7b5 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Thu, 10 Apr 2025 15:59:06 -0400 Subject: [PATCH 895/966] chore: Allow `googleapis-auth` and `aion-sdk` to approve samples/ (#1746) --- packages/google-auth/.github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/.github/CODEOWNERS b/packages/google-auth/.github/CODEOWNERS index 4ddae6fb2afe..a6a81621d430 100644 --- a/packages/google-auth/.github/CODEOWNERS +++ b/packages/google-auth/.github/CODEOWNERS @@ -26,5 +26,5 @@ tests/test_identity_pool.py @googleapis/googleap tests/test_pluggable.py @googleapis/googleapis-auth @googleapis/aion-sdk tests/test_sts.py @googleapis/googleapis-auth @googleapis/aion-sdk tests/test_impersonated_credentials.py @googleapis/googleapis-auth @googleapis/aion-sdk -/samples/ @googleapis/python-samples-owners +/samples/ @googleapis/googleapis-auth @googleapis/aion-sdk @googleapis/python-samples-owners system_tests/secrets.tar.enc # Remove noise from test creds. From 18e948cefd4209543070ce08c5db1b9cb6831677 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Thu, 10 Apr 2025 15:59:45 -0400 Subject: [PATCH 896/966] fix: explicitly declare support for Python 3.13 (#1741) * fix: explicitly declare support for Python 3.13 * Add config for samples * skip 2 tests due to oauth2client incompatibility * bump google-cloud-storage in samples test * bump google-auth in samples test * coverage --------- Co-authored-by: ohmayr --- .../.kokoro/samples/python3.13/common.cfg | 37 +++++++++++++++++++ .../.kokoro/samples/python3.13/continuous.cfg | 6 +++ .../samples/python3.13/periodic-head.cfg | 11 ++++++ .../.kokoro/samples/python3.13/periodic.cfg | 6 +++ .../.kokoro/samples/python3.13/presubmit.cfg | 6 +++ packages/google-auth/CONTRIBUTING.rst | 2 +- packages/google-auth/noxfile.py | 2 +- .../samples/cloud-client/snippets/noxfile.py | 2 +- .../cloud-client/snippets/requirements.txt | 6 +-- packages/google-auth/setup.py | 1 + .../google-auth/testing/constraints-3.13.txt | 0 .../google-auth/tests/test__oauth2client.py | 16 ++++++++ 12 files changed, 89 insertions(+), 6 deletions(-) create mode 100644 packages/google-auth/.kokoro/samples/python3.13/common.cfg create mode 100644 packages/google-auth/.kokoro/samples/python3.13/continuous.cfg create mode 100644 packages/google-auth/.kokoro/samples/python3.13/periodic-head.cfg create mode 100644 packages/google-auth/.kokoro/samples/python3.13/periodic.cfg create mode 100644 packages/google-auth/.kokoro/samples/python3.13/presubmit.cfg create mode 100644 packages/google-auth/testing/constraints-3.13.txt diff --git a/packages/google-auth/.kokoro/samples/python3.13/common.cfg b/packages/google-auth/.kokoro/samples/python3.13/common.cfg new file mode 100644 index 000000000000..c1dd4316bc30 --- /dev/null +++ b/packages/google-auth/.kokoro/samples/python3.13/common.cfg @@ -0,0 +1,37 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Build logs will be here +action { + define_artifacts { + regex: "**/*sponge_log.xml" + } +} + +# Specify which tests to run +env_vars: { + key: "RUN_TESTS_SESSION" + value: "unit-3.13" +} + +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" + +# Download resources for system tests (service account key, etc.) +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-auth-library-python" + +# Use the trampoline script to run in docker. +build_file: "google-auth-library-python/.kokoro/trampoline.sh" + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/python-multi" +} +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/google-auth-library-python/.kokoro/build.sh" +} +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/google-auth-library-python/.kokoro/samples-test-setup.sh" +} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.13/continuous.cfg b/packages/google-auth/.kokoro/samples/python3.13/continuous.cfg new file mode 100644 index 000000000000..a1c8d9759c88 --- /dev/null +++ b/packages/google-auth/.kokoro/samples/python3.13/continuous.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.13/periodic-head.cfg b/packages/google-auth/.kokoro/samples/python3.13/periodic-head.cfg new file mode 100644 index 000000000000..83eace873ccb --- /dev/null +++ b/packages/google-auth/.kokoro/samples/python3.13/periodic-head.cfg @@ -0,0 +1,11 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/google-auth-library-python/.kokoro/test-samples-against-head.sh" +} diff --git a/packages/google-auth/.kokoro/samples/python3.13/periodic.cfg b/packages/google-auth/.kokoro/samples/python3.13/periodic.cfg new file mode 100644 index 000000000000..71cd1e597e38 --- /dev/null +++ b/packages/google-auth/.kokoro/samples/python3.13/periodic.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "False" +} diff --git a/packages/google-auth/.kokoro/samples/python3.13/presubmit.cfg b/packages/google-auth/.kokoro/samples/python3.13/presubmit.cfg new file mode 100644 index 000000000000..a1c8d9759c88 --- /dev/null +++ b/packages/google-auth/.kokoro/samples/python3.13/presubmit.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} \ No newline at end of file diff --git a/packages/google-auth/CONTRIBUTING.rst b/packages/google-auth/CONTRIBUTING.rst index 68d598c93f01..3975a3d2a85b 100644 --- a/packages/google-auth/CONTRIBUTING.rst +++ b/packages/google-auth/CONTRIBUTING.rst @@ -19,7 +19,7 @@ A few notes on making changes to ``google-auth-library-python``. using ``nox -s docs``. - The change must work fully on the following CPython versions: - 3.7, 3.8, 3.9, 3.10, 3.11 and 3.12 across macOS, Linux, and Windows. + 3.7, 3.8, 3.9, 3.10, 3.11, 3.12 and 3.13 across macOS, Linux, and Windows. - The codebase *must* have 100% test statement coverage after each commit. You can test coverage via ``nox -e cover``. diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index ef48441a7c49..1071be0ad557 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -84,7 +84,7 @@ def mypy(session): session.run("mypy", "-p", "google", "-p", "tests", "-p", "tests_async") -@nox.session(python=["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"]) +@nox.session(python=["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]) def unit(session): constraints_path = str( CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt" diff --git a/packages/google-auth/samples/cloud-client/snippets/noxfile.py b/packages/google-auth/samples/cloud-client/snippets/noxfile.py index f5d827941a8f..c21466d4f024 100644 --- a/packages/google-auth/samples/cloud-client/snippets/noxfile.py +++ b/packages/google-auth/samples/cloud-client/snippets/noxfile.py @@ -60,7 +60,7 @@ ] -@nox.session(python=["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"]) +@nox.session(python=["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]) def unit(session): # constraints_path = str( # CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt" diff --git a/packages/google-auth/samples/cloud-client/snippets/requirements.txt b/packages/google-auth/samples/cloud-client/snippets/requirements.txt index d661b7fc9823..416c56b94671 100644 --- a/packages/google-auth/samples/cloud-client/snippets/requirements.txt +++ b/packages/google-auth/samples/cloud-client/snippets/requirements.txt @@ -1,4 +1,4 @@ google-cloud-compute==1.5.1 -google-cloud-storage==2.5.0 -google-auth==2.11.0 -pytest==7.1.2 \ No newline at end of file +google-cloud-storage==3.1.0 +google-auth==2.38.0 +pytest==7.1.2 diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index fcdcb21e00cb..3874354fded0 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -128,6 +128,7 @@ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", diff --git a/packages/google-auth/testing/constraints-3.13.txt b/packages/google-auth/testing/constraints-3.13.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/google-auth/tests/test__oauth2client.py b/packages/google-auth/tests/test__oauth2client.py index 9f0c192aee7f..2d4a809bb0d0 100644 --- a/packages/google-auth/tests/test__oauth2client.py +++ b/packages/google-auth/tests/test__oauth2client.py @@ -116,6 +116,14 @@ def test__convert_appengine_app_assertion_credentials( app_identity, mock_oauth2client_gae_imports ): + # `oauth2client` requires `cgi` which was removed in Python 3.13 + # See https://github.com/googleapis/oauth2client/blob/50d20532a748f18e53f7d24ccbe6647132c979a9/oauth2client/contrib/appengine.py#L20 + # oauth2client is no longer being updated so this test must be skipped on newer Python Runtimes + if sys.version_info >= (3, 13): # pragma: NO COVER + pytest.skip( + "Skipping test for Python 3.13+ due to oauth2client incompatibility." + ) + import oauth2client.contrib.appengine # type: ignore service_account_id = "service_account_id" @@ -165,6 +173,14 @@ def reset__oauth2client_module(): def test_import_has_app_engine( mock_oauth2client_gae_imports, reset__oauth2client_module ): + # `oauth2client` requires `cgi` which was removed in Python 3.13 + # See https://github.com/googleapis/oauth2client/blob/50d20532a748f18e53f7d24ccbe6647132c979a9/oauth2client/contrib/appengine.py#L20 + # oauth2client is no longer being updated so this test must be skipped on newer Python Runtimes + if sys.version_info >= (3, 13): # pragma: NO COVER + pytest.skip( + "Skipping test for Python 3.13+ due to oauth2client incompatibility." + ) + importlib.reload(_oauth2client) assert _oauth2client._HAS_APPENGINE From 8476443d86d4c87c2dfe8950d7a9e2d7946b14b5 Mon Sep 17 00:00:00 2001 From: Nolan Eastin <80856764+nolanleastin@users.noreply.github.com> Date: Thu, 10 Apr 2025 13:44:42 -0700 Subject: [PATCH 897/966] fix: add impersonated SA via local ADC support for fetch_id_token (#1740) * Add `from_impersonated_account_info` method to ImperstonatedCredentials. * id_token.py uses impersonated_credentials.from_impersonated_account_info. * Add token_id unit test for impersonated service account credentials. * _default uses impersonated_credentials method. --- packages/google-auth/google/auth/_default.py | 38 +--------- .../google/auth/impersonated_credentials.py | 75 +++++++++++++++++++ .../google-auth/google/oauth2/id_token.py | 12 +++ .../google-auth/tests/oauth2/test_id_token.py | 15 ++++ .../tests/test_impersonated_credentials.py | 39 ++++++++++ 5 files changed, 143 insertions(+), 36 deletions(-) diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index 1234fb25d787..cf0cdd772988 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -484,42 +484,8 @@ def _get_impersonated_service_account_credentials(filename, info, scopes): from google.auth import impersonated_credentials try: - source_credentials_info = info.get("source_credentials") - source_credentials_type = source_credentials_info.get("type") - if source_credentials_type == _AUTHORIZED_USER_TYPE: - source_credentials, _ = _get_authorized_user_credentials( - filename, source_credentials_info - ) - elif source_credentials_type == _SERVICE_ACCOUNT_TYPE: - source_credentials, _ = _get_service_account_credentials( - filename, source_credentials_info - ) - elif source_credentials_type == _EXTERNAL_ACCOUNT_AUTHORIZED_USER_TYPE: - source_credentials, _ = _get_external_account_authorized_user_credentials( - filename, source_credentials_info - ) - else: - raise exceptions.InvalidType( - "source credential of type {} is not supported.".format( - source_credentials_type - ) - ) - impersonation_url = info.get("service_account_impersonation_url") - start_index = impersonation_url.rfind("/") - end_index = impersonation_url.find(":generateAccessToken") - if start_index == -1 or end_index == -1 or start_index > end_index: - raise exceptions.InvalidValue( - "Cannot extract target principal from {}".format(impersonation_url) - ) - target_principal = impersonation_url[start_index + 1 : end_index] - delegates = info.get("delegates") - quota_project_id = info.get("quota_project_id") - credentials = impersonated_credentials.Credentials( - source_credentials, - target_principal, - scopes, - delegates, - quota_project_id=quota_project_id, + credentials = impersonated_credentials.Credentials.from_impersonated_service_account_info( + info, scopes=scopes ) except ValueError as caught_exc: msg = "Failed to load impersonated service account credentials from {}".format( diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index ed7e3f00b1c0..d49998cfbdc6 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -47,6 +47,12 @@ _GOOGLE_OAUTH2_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token" +_SOURCE_CREDENTIAL_AUTHORIZED_USER_TYPE = "authorized_user" +_SOURCE_CREDENTIAL_SERVICE_ACCOUNT_TYPE = "service_account" +_SOURCE_CREDENTIAL_EXTERNAL_ACCOUNT_AUTHORIZED_USER_TYPE = ( + "external_account_authorized_user" +) + def _make_iam_token_request( request, @@ -410,6 +416,75 @@ def with_scopes(self, scopes, default_scopes=None): cred._target_scopes = scopes or default_scopes return cred + @classmethod + def from_impersonated_service_account_info(cls, info, scopes=None): + """Creates a Credentials instance from parsed impersonated service account credentials info. + + Args: + info (Mapping[str, str]): The impersonated service account credentials info in Google + format. + scopes (Sequence[str]): Optional list of scopes to include in the + credentials. + + Returns: + google.oauth2.credentials.Credentials: The constructed + credentials. + + Raises: + InvalidType: If the info["source_credentials"] are not a supported impersonation type + InvalidValue: If the info["service_account_impersonation_url"] is not in the expected format. + ValueError: If the info is not in the expected format. + """ + + source_credentials_info = info.get("source_credentials") + source_credentials_type = source_credentials_info.get("type") + if source_credentials_type == _SOURCE_CREDENTIAL_AUTHORIZED_USER_TYPE: + from google.oauth2 import credentials + + source_credentials = credentials.Credentials.from_authorized_user_info( + source_credentials_info + ) + elif source_credentials_type == _SOURCE_CREDENTIAL_SERVICE_ACCOUNT_TYPE: + from google.oauth2 import service_account + + source_credentials = service_account.Credentials.from_service_account_info( + source_credentials_info + ) + elif ( + source_credentials_type + == _SOURCE_CREDENTIAL_EXTERNAL_ACCOUNT_AUTHORIZED_USER_TYPE + ): + from google.auth import external_account_authorized_user + + source_credentials = external_account_authorized_user.Credentials.from_info( + source_credentials_info + ) + else: + raise exceptions.InvalidType( + "source credential of type {} is not supported.".format( + source_credentials_type + ) + ) + + impersonation_url = info.get("service_account_impersonation_url") + start_index = impersonation_url.rfind("/") + end_index = impersonation_url.find(":generateAccessToken") + if start_index == -1 or end_index == -1 or start_index > end_index: + raise exceptions.InvalidValue( + "Cannot extract target principal from {}".format(impersonation_url) + ) + target_principal = impersonation_url[start_index + 1 : end_index] + delegates = info.get("delegates") + quota_project_id = info.get("quota_project_id") + + return cls( + source_credentials, + target_principal, + scopes, + delegates, + quota_project_id=quota_project_id, + ) + class IDTokenCredentials(credentials.CredentialsWithQuotaProject): """Open ID Connect ID Token-based service account credentials. diff --git a/packages/google-auth/google/oauth2/id_token.py b/packages/google-auth/google/oauth2/id_token.py index b68ab6b303a6..a6c51ce63814 100644 --- a/packages/google-auth/google/oauth2/id_token.py +++ b/packages/google-auth/google/oauth2/id_token.py @@ -284,6 +284,18 @@ def fetch_id_token_credentials(audience, request=None): return service_account.IDTokenCredentials.from_service_account_info( info, target_audience=audience ) + elif info.get("type") == "impersonated_service_account": + from google.auth import impersonated_credentials + + target_credentials = impersonated_credentials.Credentials.from_impersonated_service_account_info( + info + ) + + return impersonated_credentials.IDTokenCredentials( + target_credentials=target_credentials, + target_audience=audience, + include_email=True, + ) except ValueError as caught_exc: new_exc = exceptions.DefaultCredentialsError( "GOOGLE_APPLICATION_CREDENTIALS is not valid service account credentials.", diff --git a/packages/google-auth/tests/oauth2/test_id_token.py b/packages/google-auth/tests/oauth2/test_id_token.py index 7d6a2248157a..ff3d4b6d8315 100644 --- a/packages/google-auth/tests/oauth2/test_id_token.py +++ b/packages/google-auth/tests/oauth2/test_id_token.py @@ -20,6 +20,7 @@ from google.auth import environment_vars from google.auth import exceptions +from google.auth import impersonated_credentials from google.auth import transport from google.oauth2 import id_token from google.oauth2 import service_account @@ -27,6 +28,12 @@ SERVICE_ACCOUNT_FILE = os.path.join( os.path.dirname(__file__), "../data/service_account.json" ) + +IMPERSONATED_SERVICE_ACCOUNT_FILE = os.path.join( + os.path.dirname(__file__), + "../data/impersonated_service_account_authorized_user_source.json", +) + ID_TOKEN_AUDIENCE = "https://pubsub.googleapis.com" @@ -262,6 +269,14 @@ def test_fetch_id_token_credentials_from_explicit_cred_json_file(monkeypatch): assert cred._target_audience == ID_TOKEN_AUDIENCE +def test_fetch_id_token_credentials_from_impersonated_cred_json_file(monkeypatch): + monkeypatch.setenv(environment_vars.CREDENTIALS, IMPERSONATED_SERVICE_ACCOUNT_FILE) + + cred = id_token.fetch_id_token_credentials(ID_TOKEN_AUDIENCE) + assert isinstance(cred, impersonated_credentials.IDTokenCredentials) + assert cred._target_audience == ID_TOKEN_AUDIENCE + + def test_fetch_id_token_credentials_no_cred_exists(monkeypatch): monkeypatch.delenv(environment_vars.CREDENTIALS, raising=False) diff --git a/packages/google-auth/tests/test_impersonated_credentials.py b/packages/google-auth/tests/test_impersonated_credentials.py index 8f6b22670395..4aa357e3e07e 100644 --- a/packages/google-auth/tests/test_impersonated_credentials.py +++ b/packages/google-auth/tests/test_impersonated_credentials.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import copy import datetime import http.client as http_client import json @@ -35,6 +36,9 @@ PRIVATE_KEY_BYTES = fh.read() SERVICE_ACCOUNT_JSON_FILE = os.path.join(DATA_DIR, "service_account.json") +IMPERSONATED_SERVICE_ACCOUNT_AUTHORIZED_USER_SOURCE_FILE = os.path.join( + DATA_DIR, "impersonated_service_account_authorized_user_source.json" +) ID_TOKEN_DATA = ( "eyJhbGciOiJSUzI1NiIsImtpZCI6ImRmMzc1ODkwOGI3OTIyOTNhZDk3N2Ew" @@ -49,6 +53,9 @@ with open(SERVICE_ACCOUNT_JSON_FILE, "rb") as fh: SERVICE_ACCOUNT_INFO = json.load(fh) +with open(IMPERSONATED_SERVICE_ACCOUNT_AUTHORIZED_USER_SOURCE_FILE, "rb") as fh: + IMPERSONATED_SERVICE_ACCOUNT_AUTHORIZED_USER_SOURCE_INFO = json.load(fh) + SIGNER = crypt.RSASigner.from_string(PRIVATE_KEY_BYTES, "1") TOKEN_URI = "https://example.com/oauth2/token" @@ -148,6 +155,38 @@ def make_credentials( iam_endpoint_override=iam_endpoint_override, ) + def test_from_impersonated_service_account_info(self): + credentials = impersonated_credentials.Credentials.from_impersonated_service_account_info( + IMPERSONATED_SERVICE_ACCOUNT_AUTHORIZED_USER_SOURCE_INFO + ) + assert isinstance(credentials, impersonated_credentials.Credentials) + + def test_from_impersonated_service_account_info_with_invalid_source_credentials_type( + self + ): + info = copy.deepcopy(IMPERSONATED_SERVICE_ACCOUNT_AUTHORIZED_USER_SOURCE_INFO) + assert "source_credentials" in info + # Set the source_credentials to an invalid type + info["source_credentials"]["type"] = "invalid_type" + with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: + impersonated_credentials.Credentials.from_impersonated_service_account_info( + info + ) + assert excinfo.match( + "source credential of type {} is not supported".format("invalid_type") + ) + + def test_from_impersonated_service_account_info_with_invalid_impersonation_url( + self + ): + info = copy.deepcopy(IMPERSONATED_SERVICE_ACCOUNT_AUTHORIZED_USER_SOURCE_INFO) + info["service_account_impersonation_url"] = "invalid_url" + with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: + impersonated_credentials.Credentials.from_impersonated_service_account_info( + info + ) + assert excinfo.match(r"Cannot extract target principal from") + def test_get_cred_info(self): credentials = self.make_credentials() assert not credentials.get_cred_info() From ccbaec720b39a29219b8553ed44145c70328d067 Mon Sep 17 00:00:00 2001 From: Harkamal Jot Singh Kumar Date: Mon, 14 Apr 2025 09:32:44 -0700 Subject: [PATCH 898/966] chore: update secret for tests (#1749) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 1c8487b87e3ddec611e9515fdafdfee2dfdb2681..c8792a28994cac62c75f6025a79c07cf3e1d9282 100644 GIT binary patch literal 10324 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 literal 10324 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 From 041248a577ede3c289b416440e0e0b5881f8a3e2 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 14 Apr 2025 10:36:31 -0700 Subject: [PATCH 899/966] chore(main): release 2.39.0 (#1705) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 15 +++++++++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index cb6a413584ef..01e21e7cc7af 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,21 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.39.0](https://github.com/googleapis/google-auth-library-python/compare/v2.38.0...v2.39.0) (2025-04-14) + + +### Features + +* Adds GA support for X.509 workload identity federation ([#1695](https://github.com/googleapis/google-auth-library-python/issues/1695)) ([7495960](https://github.com/googleapis/google-auth-library-python/commit/74959605400f9a1976bbdc52c029943b634eb553)) + + +### Bug Fixes + +* Add impersonated SA via local ADC support for fetch_id_token ([#1740](https://github.com/googleapis/google-auth-library-python/issues/1740)) ([f249764](https://github.com/googleapis/google-auth-library-python/commit/f24976452d741de6a49d9b7a85cdab47812f5312)) +* Add missing packaging dependency for feature requiring urllib3 ([#1732](https://github.com/googleapis/google-auth-library-python/issues/1732)) ([221f4a8](https://github.com/googleapis/google-auth-library-python/commit/221f4a82fa25c1ad453b85bc8b7f2fc304724879)) +* Add request timeout for MDS requests ([#1699](https://github.com/googleapis/google-auth-library-python/issues/1699)) ([9f7d3fa](https://github.com/googleapis/google-auth-library-python/commit/9f7d3fa92c0e656a1c970182833abe2d0d3ad3ee)) +* Explicitly declare support for Python 3.13 ([#1741](https://github.com/googleapis/google-auth-library-python/issues/1741)) ([6fd04d5](https://github.com/googleapis/google-auth-library-python/commit/6fd04d57df90866f24b554c489f8f2653467d70e)) + ## [2.38.0](https://github.com/googleapis/google-auth-library-python/compare/v2.37.0...v2.38.0) (2025-01-23) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 41a80e6c6767..393caa8ad44d 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.38.0" +__version__ = "2.39.0" From 7dfedc412a6bb564aea07e67f28a0a70a4a75ac9 Mon Sep 17 00:00:00 2001 From: Yujian Zhao Date: Mon, 14 Apr 2025 12:38:51 -0700 Subject: [PATCH 900/966] fix: Correct webauthn JSON parsing to be compliant with standard. (#1658) 'rpid' -> 'rpId': https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialrequestoptionsjson-rpid Co-authored-by: Harkamal Jot Singh Kumar --- packages/google-auth/google/oauth2/webauthn_types.py | 2 +- packages/google-auth/tests/oauth2/test_webauthn_handler.py | 2 +- packages/google-auth/tests/oauth2/test_webauthn_types.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/google-auth/google/oauth2/webauthn_types.py b/packages/google-auth/google/oauth2/webauthn_types.py index 7784e83d0b98..24e984f3d336 100644 --- a/packages/google-auth/google/oauth2/webauthn_types.py +++ b/packages/google-auth/google/oauth2/webauthn_types.py @@ -67,7 +67,7 @@ class GetRequest: extensions: Optional[AuthenticationExtensionsClientInputs] = None def to_json(self) -> str: - req_options: Dict[str, Any] = {"rpid": self.rpid, "challenge": self.challenge} + req_options: Dict[str, Any] = {"rpId": self.rpid, "challenge": self.challenge} if self.timeout_ms: req_options["timeout"] = self.timeout_ms if self.allow_credentials: diff --git a/packages/google-auth/tests/oauth2/test_webauthn_handler.py b/packages/google-auth/tests/oauth2/test_webauthn_handler.py index 454e97cb61d8..9fba266da9fc 100644 --- a/packages/google-auth/tests/oauth2/test_webauthn_handler.py +++ b/packages/google-auth/tests/oauth2/test_webauthn_handler.py @@ -118,7 +118,7 @@ def test_success_get_assertion(os_get_stub, subprocess_run_stub): "type": "get", "origin": "fake_origin", "requestData": { - "rpid": "fake_rpid", + "rpId": "fake_rpid", "challenge": "fake_challenge", "allowCredentials": [{"type": "public-key", "id": "fake_id_1"}], }, diff --git a/packages/google-auth/tests/oauth2/test_webauthn_types.py b/packages/google-auth/tests/oauth2/test_webauthn_types.py index 5231d21896a9..bafe5b050605 100644 --- a/packages/google-auth/tests/oauth2/test_webauthn_types.py +++ b/packages/google-auth/tests/oauth2/test_webauthn_types.py @@ -82,7 +82,7 @@ def test_GetRequest(has_allow_credentials): "type": "get", "origin": "fake_origin", "requestData": { - "rpid": "fake_rpid", + "rpId": "fake_rpid", "timeout": 123, "challenge": "fake_challenge", "userVerification": "preferred", From e07b8472b8e12660e9f37b109d627d7016cbd39d Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Mon, 28 Apr 2025 23:35:57 +0000 Subject: [PATCH 901/966] chore: update secrets (#1752) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index c8792a28994cac62c75f6025a79c07cf3e1d9282..fe6fb3aa0cbbc05a77da905c261b30e091e46c6f 100644 GIT binary patch literal 10324 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 From 7fa3e0668c5d37be0f5160dd0cb2f9b3d1b30217 Mon Sep 17 00:00:00 2001 From: ohmayr Date: Wed, 30 Apr 2025 02:21:09 +0500 Subject: [PATCH 902/966] feat: add request response logging to auth (#1678) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add functionality to hash data (#1677) * feat: add functionality to hash data * change sensitive fields to private * update to sha512 * update docstring * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * chore: add request-response log helpers (#1685) * chore: add request-response log helpers * fix presubmit * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * feat: opt-in logging support for request / response (#1686) * feat: opt-in logging support for request/response * add pragma no cover * add test coverage for request/response * add code coverage * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * chore: remove logging for async requests (#1698) * chore: remove logging for async requests * change Dict to Mapping * fix mypy and lint issues * address PR feedback * link issue * feat: parse request/response for logging (#1696) * feat: parse request/response for logging * add test case for list * address PR comments * address PR feedback * fix typo * add test coverage * add code coverage * feat: hash sensitive info in logs (#1700) * feat: hash sensitive info in logs * make helper private * add code coverage * address PR feedback * fix mypy type issue * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * feat: add support for async response log (#1733) * feat: add support for async response log * fix whitespace * add await * add code coverage * fix lint * fix lint issues * address PR feedback * address PR feedback * link issue * feat: add request response logs for sync api calls (#1747) * fix: remove dependency on api-core for logging (#1748) * fix: remove dep on api-core for logging * disable propagation to the root logger * update async helpers tests * fix lint issue --------- Co-authored-by: Owl Bot --- packages/google-auth/google/auth/_helpers.py | 240 ++++++++++ .../google-auth/google/auth/aio/_helpers.py | 57 +++ .../google/auth/aio/transport/aiohttp.py | 6 + .../auth/transport/_aiohttp_requests.py | 9 +- .../google/auth/transport/_http_client.py | 4 +- .../google/auth/transport/requests.py | 6 +- .../google/auth/transport/urllib3.py | 4 +- .../google-auth/tests/aio/test__helpers.py | 110 +++++ packages/google-auth/tests/test__helpers.py | 434 +++++++++++++++++- 9 files changed, 863 insertions(+), 7 deletions(-) create mode 100644 packages/google-auth/google/auth/aio/_helpers.py create mode 100644 packages/google-auth/tests/aio/test__helpers.py diff --git a/packages/google-auth/google/auth/_helpers.py b/packages/google-auth/google/auth/_helpers.py index a6c07f7d8296..78fe22f72644 100644 --- a/packages/google-auth/google/auth/_helpers.py +++ b/packages/google-auth/google/auth/_helpers.py @@ -18,15 +18,38 @@ import calendar import datetime from email.message import Message +import hashlib +import json +import logging import sys +from typing import Any, Dict, Mapping, Optional, Union import urllib from google.auth import exceptions + +# _BASE_LOGGER_NAME is the base logger for all google-based loggers. +_BASE_LOGGER_NAME = "google" + +# _LOGGING_INITIALIZED ensures that base logger is only configured once +# (unless already configured by the end-user). +_LOGGING_INITIALIZED = False + + # The smallest MDS cache used by this library stores tokens until 4 minutes from # expiry. REFRESH_THRESHOLD = datetime.timedelta(minutes=3, seconds=45) +# TODO(https://github.com/googleapis/google-auth-library-python/issues/1684): Audit and update the list below. +_SENSITIVE_FIELDS = { + "accessToken", + "access_token", + "id_token", + "client_id", + "refresh_token", + "client_secret", +} + def copy_docstring(source_class): """Decorator that copies a method's docstring from another class. @@ -271,3 +294,220 @@ def is_python_3(): bool: True if the Python interpreter is Python 3 and False otherwise. """ return sys.version_info > (3, 0) + + +def _hash_sensitive_info(data: Union[dict, list]) -> Union[dict, list, str]: + """ + Hashes sensitive information within a dictionary. + + Args: + data: The dictionary containing data to be processed. + + Returns: + A new dictionary with sensitive values replaced by their SHA512 hashes. + If the input is a list, returns a list with each element recursively processed. + If the input is neither a dict nor a list, returns the type of the input as a string. + + """ + if isinstance(data, dict): + hashed_data: Dict[Any, Union[Optional[str], dict, list]] = {} + for key, value in data.items(): + if key in _SENSITIVE_FIELDS and not isinstance(value, (dict, list)): + hashed_data[key] = _hash_value(value, key) + elif isinstance(value, (dict, list)): + hashed_data[key] = _hash_sensitive_info(value) + else: + hashed_data[key] = value + return hashed_data + elif isinstance(data, list): + hashed_list = [] + for val in data: + hashed_list.append(_hash_sensitive_info(val)) + return hashed_list + else: + # TODO(https://github.com/googleapis/google-auth-library-python/issues/1701): + # Investigate and hash sensitive info before logging when the data type is + # not a dict or a list. + return str(type(data)) + + +def _hash_value(value, field_name: str) -> Optional[str]: + """Hashes a value and returns a formatted hash string.""" + if value is None: + return None + encoded_value = str(value).encode("utf-8") + hash_object = hashlib.sha512() + hash_object.update(encoded_value) + hex_digest = hash_object.hexdigest() + return f"hashed_{field_name}-{hex_digest}" + + +def _logger_configured(logger: logging.Logger) -> bool: + """Determines whether `logger` has non-default configuration + + Args: + logger: The logger to check. + + Returns: + bool: Whether the logger has any non-default configuration. + """ + return ( + logger.handlers != [] or logger.level != logging.NOTSET or not logger.propagate + ) + + +def is_logging_enabled(logger: logging.Logger) -> bool: + """ + Checks if debug logging is enabled for the given logger. + + Args: + logger: The logging.Logger instance to check. + + Returns: + True if debug logging is enabled, False otherwise. + """ + # NOTE: Log propagation to the root logger is disabled unless + # the base logger i.e. logging.getLogger("google") is + # explicitly configured by the end user. Ideally this + # needs to happen in the client layer (already does for GAPICs). + # However, this is implemented here to avoid logging + # (if a root logger is configured) when a version of google-auth + # which supports logging is used with: + # - an older version of a GAPIC which does not support logging. + # - Apiary client which does not support logging. + global _LOGGING_INITIALIZED + if not _LOGGING_INITIALIZED: + base_logger = logging.getLogger(_BASE_LOGGER_NAME) + if not _logger_configured(base_logger): + base_logger.propagate = False + _LOGGING_INITIALIZED = True + + return logger.isEnabledFor(logging.DEBUG) + + +def request_log( + logger: logging.Logger, + method: str, + url: str, + body: Optional[bytes], + headers: Optional[Mapping[str, str]], +) -> None: + """ + Logs an HTTP request at the DEBUG level if logging is enabled. + + Args: + logger: The logging.Logger instance to use. + method: The HTTP method (e.g., "GET", "POST"). + url: The URL of the request. + body: The request body (can be None). + headers: The request headers (can be None). + """ + if is_logging_enabled(logger): + content_type = ( + headers["Content-Type"] if headers and "Content-Type" in headers else "" + ) + json_body = _parse_request_body(body, content_type=content_type) + logged_body = _hash_sensitive_info(json_body) + logger.debug( + "Making request...", + extra={ + "httpRequest": { + "method": method, + "url": url, + "body": logged_body, + "headers": headers, + } + }, + ) + + +def _parse_request_body(body: Optional[bytes], content_type: str = "") -> Any: + """ + Parses a request body, handling bytes and string types, and different content types. + + Args: + body (Optional[bytes]): The request body. + content_type (str): The content type of the request body, e.g., "application/json", + "application/x-www-form-urlencoded", or "text/plain". If empty, attempts + to parse as JSON. + + Returns: + Parsed body (dict, str, or None). + - JSON: Decodes if content_type is "application/json" or None (fallback). + - URL-encoded: Parses if content_type is "application/x-www-form-urlencoded". + - Plain text: Returns string if content_type is "text/plain". + - None: Returns if body is None, UTF-8 decode fails, or content_type is unknown. + """ + if body is None: + return None + try: + body_str = body.decode("utf-8") + except (UnicodeDecodeError, AttributeError): + return None + content_type = content_type.lower() + if not content_type or "application/json" in content_type: + try: + return json.loads(body_str) + except (json.JSONDecodeError, TypeError): + return body_str + if "application/x-www-form-urlencoded" in content_type: + parsed_query = urllib.parse.parse_qs(body_str) + result = {k: v[0] for k, v in parsed_query.items()} + return result + if "text/plain" in content_type: + return body_str + return None + + +def _parse_response(response: Any) -> Any: + """ + Parses a response, attempting to decode JSON. + + Args: + response: The response object to parse. This can be any type, but + it is expected to have a `json()` method if it contains JSON. + + Returns: + The parsed response. If the response contains valid JSON, the + decoded JSON object (e.g., a dictionary or list) is returned. + If the response does not have a `json()` method or if the JSON + decoding fails, None is returned. + """ + try: + json_response = response.json() + return json_response + except Exception: + # TODO(https://github.com/googleapis/google-auth-library-python/issues/1744): + # Parse and return response payload as json based on different content types. + return None + + +def _response_log_base(logger: logging.Logger, parsed_response: Any) -> None: + """ + Logs a parsed HTTP response at the DEBUG level. + + This internal helper function takes a parsed response and logs it + using the provided logger. It also applies a hashing function to + potentially sensitive information before logging. + + Args: + logger: The logging.Logger instance to use for logging. + parsed_response: The parsed HTTP response object (e.g., a dictionary, + list, or the original response if parsing failed). + """ + + logged_response = _hash_sensitive_info(parsed_response) + logger.debug("Response received...", extra={"httpResponse": logged_response}) + + +def response_log(logger: logging.Logger, response: Any) -> None: + """ + Logs an HTTP response at the DEBUG level if logging is enabled. + + Args: + logger: The logging.Logger instance to use. + response: The HTTP response object to log. + """ + if is_logging_enabled(logger): + json_response = _parse_response(response) + _response_log_base(logger, json_response) diff --git a/packages/google-auth/google/auth/aio/_helpers.py b/packages/google-auth/google/auth/aio/_helpers.py new file mode 100644 index 000000000000..fd7d37a2f7b0 --- /dev/null +++ b/packages/google-auth/google/auth/aio/_helpers.py @@ -0,0 +1,57 @@ +# Copyright 2025 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Helper functions for commonly used utilities.""" + + +import logging +from typing import Any + +from google.auth import _helpers + + +async def _parse_response_async(response: Any) -> Any: + """ + Parses an async response, attempting to decode JSON. + + Args: + response: The response object to parse. This can be any type, but + it is expected to have a `json()` method if it contains JSON. + + Returns: + The parsed response. If the response contains valid JSON, the + decoded JSON object (e.g., a dictionary) is returned. + If the response does not have a `json()` method or if the JSON + decoding fails, None is returned. + """ + try: + json_response = await response.json() + return json_response + except Exception: + # TODO(https://github.com/googleapis/google-auth-library-python/issues/1745): + # Parse and return response payload as json based on different content types. + return None + + +async def response_log_async(logger: logging.Logger, response: Any) -> None: + """ + Logs an Async HTTP response at the DEBUG level if logging is enabled. + + Args: + logger: The logging.Logger instance to use. + response: The HTTP response object to log. + """ + if _helpers.is_logging_enabled(logger): + json_response = await _parse_response_async(response) + _helpers._response_log_base(logger, json_response) diff --git a/packages/google-auth/google/auth/aio/transport/aiohttp.py b/packages/google-auth/google/auth/aio/transport/aiohttp.py index 074d1491c700..67a19f952d24 100644 --- a/packages/google-auth/google/auth/aio/transport/aiohttp.py +++ b/packages/google-auth/google/auth/aio/transport/aiohttp.py @@ -16,6 +16,7 @@ """ import asyncio +import logging from typing import AsyncGenerator, Mapping, Optional try: @@ -27,8 +28,11 @@ from google.auth import _helpers from google.auth import exceptions +from google.auth.aio import _helpers as _helpers_async from google.auth.aio import transport +_LOGGER = logging.getLogger(__name__) + class Response(transport.Response): """ @@ -155,6 +159,7 @@ async def __call__( self._session = aiohttp.ClientSession() client_timeout = aiohttp.ClientTimeout(total=timeout) + _helpers.request_log(_LOGGER, method, url, body, headers) response = await self._session.request( method, url, @@ -163,6 +168,7 @@ async def __call__( timeout=client_timeout, **kwargs, ) + await _helpers_async.response_log_async(_LOGGER, response) return Response(response) except aiohttp.ClientError as caught_exc: diff --git a/packages/google-auth/google/auth/transport/_aiohttp_requests.py b/packages/google-auth/google/auth/transport/_aiohttp_requests.py index bc4d9dc69afe..36366be51086 100644 --- a/packages/google-auth/google/auth/transport/_aiohttp_requests.py +++ b/packages/google-auth/google/auth/transport/_aiohttp_requests.py @@ -22,14 +22,20 @@ import asyncio import functools +import logging import aiohttp # type: ignore import urllib3 # type: ignore +from google.auth import _helpers from google.auth import exceptions from google.auth import transport +from google.auth.aio import _helpers as _helpers_async from google.auth.transport import requests + +_LOGGER = logging.getLogger(__name__) + # Timeout can be re-defined depending on async requirement. Currently made 60s more than # sync timeout. _DEFAULT_TIMEOUT = 180 # in seconds @@ -182,10 +188,11 @@ async def __call__( self.session = aiohttp.ClientSession( auto_decompress=False ) # pragma: NO COVER - requests._LOGGER.debug("Making request: %s %s", method, url) + _helpers.request_log(_LOGGER, method, url, body, headers) response = await self.session.request( method, url, data=body, headers=headers, timeout=timeout, **kwargs ) + await _helpers_async.response_log_async(_LOGGER, response) return _CombinedResponse(response) except aiohttp.ClientError as caught_exc: diff --git a/packages/google-auth/google/auth/transport/_http_client.py b/packages/google-auth/google/auth/transport/_http_client.py index cec0ab73fb31..898a86519b7c 100644 --- a/packages/google-auth/google/auth/transport/_http_client.py +++ b/packages/google-auth/google/auth/transport/_http_client.py @@ -19,6 +19,7 @@ import socket import urllib +from google.auth import _helpers from google.auth import exceptions from google.auth import transport @@ -99,10 +100,11 @@ def __call__( connection = http_client.HTTPConnection(parts.netloc, timeout=timeout) try: - _LOGGER.debug("Making request: %s %s", method, url) + _helpers.request_log(_LOGGER, method, url, body, headers) connection.request(method, path, body=body, headers=headers, **kwargs) response = connection.getresponse() + _helpers.response_log(_LOGGER, response) return Response(response) except (http_client.HTTPException, socket.error) as caught_exc: diff --git a/packages/google-auth/google/auth/transport/requests.py b/packages/google-auth/google/auth/transport/requests.py index 23a69783dc33..0540746f894a 100644 --- a/packages/google-auth/google/auth/transport/requests.py +++ b/packages/google-auth/google/auth/transport/requests.py @@ -34,6 +34,7 @@ create_urllib3_context, ) # pylint: disable=ungrouped-imports +from google.auth import _helpers from google.auth import environment_vars from google.auth import exceptions from google.auth import transport @@ -182,10 +183,11 @@ def __call__( google.auth.exceptions.TransportError: If any exception occurred. """ try: - _LOGGER.debug("Making request: %s %s", method, url) + _helpers.request_log(_LOGGER, method, url, body, headers) response = self.session.request( method, url, data=body, headers=headers, timeout=timeout, **kwargs ) + _helpers.response_log(_LOGGER, response) return _Response(response) except requests.exceptions.RequestException as caught_exc: new_exc = exceptions.TransportError(caught_exc) @@ -534,6 +536,7 @@ def request( remaining_time = guard.remaining_timeout with TimeoutGuard(remaining_time) as guard: + _helpers.request_log(_LOGGER, method, url, data, headers) response = super(AuthorizedSession, self).request( method, url, @@ -542,6 +545,7 @@ def request( timeout=timeout, **kwargs ) + _helpers.response_log(_LOGGER, response) remaining_time = guard.remaining_timeout # If the response indicated that the credentials needed to be diff --git a/packages/google-auth/google/auth/transport/urllib3.py b/packages/google-auth/google/auth/transport/urllib3.py index db4fa93ff11b..03ed75aa2f52 100644 --- a/packages/google-auth/google/auth/transport/urllib3.py +++ b/packages/google-auth/google/auth/transport/urllib3.py @@ -50,6 +50,7 @@ ) from caught_exc +from google.auth import _helpers from google.auth import environment_vars from google.auth import exceptions from google.auth import transport @@ -144,10 +145,11 @@ def __call__( kwargs["timeout"] = timeout try: - _LOGGER.debug("Making request: %s %s", method, url) + _helpers.request_log(_LOGGER, method, url, body, headers) response = self.http.request( method, url, body=body, headers=headers, **kwargs ) + _helpers.response_log(_LOGGER, response) return _Response(response) except urllib3.exceptions.HTTPError as caught_exc: new_exc = exceptions.TransportError(caught_exc) diff --git a/packages/google-auth/tests/aio/test__helpers.py b/packages/google-auth/tests/aio/test__helpers.py new file mode 100644 index 000000000000..7642431caec6 --- /dev/null +++ b/packages/google-auth/tests/aio/test__helpers.py @@ -0,0 +1,110 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import logging + +import pytest # type: ignore + +from google.auth.aio import _helpers + +# _MOCK_BASE_LOGGER_NAME is the base logger namespace used for testing. +_MOCK_BASE_LOGGER_NAME = "foogle" + +# _MOCK_CHILD_LOGGER_NAME is the child logger namespace used for testing. +_MOCK_CHILD_LOGGER_NAME = "foogle.bar" + + +@pytest.fixture +def logger(): + """Returns a child logger for testing.""" + logger = logging.getLogger(_MOCK_CHILD_LOGGER_NAME) + logger.level = logging.NOTSET + logger.handlers = [] + logger.propagate = True + return logger + + +@pytest.fixture +def base_logger(): + """Returns a child logger for testing.""" + logger = logging.getLogger(_MOCK_BASE_LOGGER_NAME) + logger.level = logging.NOTSET + logger.handlers = [] + logger.propagate = True + return logger + + +@pytest.mark.asyncio +async def test_response_log_debug_enabled(logger, caplog, base_logger): + caplog.set_level(logging.DEBUG, logger=_MOCK_CHILD_LOGGER_NAME) + await _helpers.response_log_async(logger, {"payload": None}) + assert len(caplog.records) == 1 + record = caplog.records[0] + assert record.message == "Response received..." + assert record.httpResponse == "" + + +@pytest.mark.asyncio +async def test_response_log_debug_disabled(logger, caplog, base_logger): + caplog.set_level(logging.INFO, logger=_MOCK_CHILD_LOGGER_NAME) + await _helpers.response_log_async(logger, "another_response") + assert "Response received..." not in caplog.text + + +@pytest.mark.asyncio +async def test_response_log_debug_enabled_response_json(logger, caplog, base_logger): + class MockResponse: + async def json(self): + return {"key1": "value1", "key2": "value2", "key3": "value3"} + + response = MockResponse() + caplog.set_level(logging.DEBUG, logger=_MOCK_CHILD_LOGGER_NAME) + await _helpers.response_log_async(logger, response) + assert len(caplog.records) == 1 + record = caplog.records[0] + assert record.message == "Response received..." + assert record.httpResponse == {"key1": "value1", "key2": "value2", "key3": "value3"} + + +@pytest.mark.asyncio +async def test_parse_response_async_json_valid(): + class MockResponse: + async def json(self): + return {"data": "test"} + + response = MockResponse() + expected = {"data": "test"} + assert await _helpers._parse_response_async(response) == expected + + +@pytest.mark.asyncio +async def test_parse_response_async_json_invalid(): + class MockResponse: + def json(self): + raise json.JSONDecodeError("msg", "doc", 0) + + response = MockResponse() + assert await _helpers._parse_response_async(response) is None + + +@pytest.mark.asyncio +async def test_parse_response_async_no_json_method(): + response = "plain text" + assert await _helpers._parse_response_async(response) is None + + +@pytest.mark.asyncio +async def test_parse_response_async_none(): + assert await _helpers._parse_response_async(None) is None diff --git a/packages/google-auth/tests/test__helpers.py b/packages/google-auth/tests/test__helpers.py index c9a3847ac48c..a4337c01608c 100644 --- a/packages/google-auth/tests/test__helpers.py +++ b/packages/google-auth/tests/test__helpers.py @@ -13,12 +13,50 @@ # limitations under the License. import datetime +import json +import logging +from unittest import mock import urllib import pytest # type: ignore from google.auth import _helpers +# _MOCK_BASE_LOGGER_NAME is the base logger namespace used for testing. +_MOCK_BASE_LOGGER_NAME = "foogle" + +# _MOCK_CHILD_LOGGER_NAME is the child logger namespace used for testing. +_MOCK_CHILD_LOGGER_NAME = "foogle.bar" + + +@pytest.fixture +def logger(): + """Returns a child logger for testing.""" + logger = logging.getLogger(_MOCK_CHILD_LOGGER_NAME) + logger.level = logging.NOTSET + logger.handlers = [] + logger.propagate = True + return logger + + +@pytest.fixture +def base_logger(): + """Returns a child logger for testing.""" + logger = logging.getLogger(_MOCK_BASE_LOGGER_NAME) + logger.level = logging.NOTSET + logger.handlers = [] + logger.propagate = True + return logger + + +@pytest.fixture(autouse=True) +def reset_logging_initialized(): + """Resets the global _LOGGING_INITIALIZED variable before each test.""" + original_state = _helpers._LOGGING_INITIALIZED + _helpers._LOGGING_INITIALIZED = False + yield + _helpers._LOGGING_INITIALIZED = original_state + class SourceClass(object): def func(self): # pragma: NO COVER @@ -92,7 +130,7 @@ def test_to_bytes_with_bytes(): def test_to_bytes_with_unicode(): - value = u"string-val" + value = "string-val" encoded_value = b"string-val" assert _helpers.to_bytes(value) == encoded_value @@ -103,13 +141,13 @@ def test_to_bytes_with_nonstring_type(): def test_from_bytes_with_unicode(): - value = u"bytes-val" + value = "bytes-val" assert _helpers.from_bytes(value) == value def test_from_bytes_with_bytes(): value = b"string-val" - decoded_value = u"string-val" + decoded_value = "string-val" assert _helpers.from_bytes(value) == decoded_value @@ -194,3 +232,393 @@ def test_unpadded_urlsafe_b64encode(): for case, expected in cases: assert _helpers.unpadded_urlsafe_b64encode(case) == expected + + +def test_hash_sensitive_info_basic(): + test_data = { + "expires_in": 3599, + "access_token": "access-123", + "scope": "https://www.googleapis.com/auth/test-api", + "token_type": "Bearer", + } + hashed_data = _helpers._hash_sensitive_info(test_data) + assert hashed_data["expires_in"] == 3599 + assert hashed_data["scope"] == "https://www.googleapis.com/auth/test-api" + assert hashed_data["access_token"].startswith("hashed_access_token-") + assert hashed_data["token_type"] == "Bearer" + + +def test_hash_sensitive_info_multiple_sensitive(): + test_data = { + "access_token": "some_long_token", + "id_token": "1234-5678-9012-3456", + "expires_in": 3599, + "token_type": "Bearer", + } + hashed_data = _helpers._hash_sensitive_info(test_data) + assert hashed_data["expires_in"] == 3599 + assert hashed_data["token_type"] == "Bearer" + assert hashed_data["access_token"].startswith("hashed_access_token-") + assert hashed_data["id_token"].startswith("hashed_id_token-") + + +def test_hash_sensitive_info_none_value(): + test_data = {"username": "user3", "secret": None, "normal_data": "abc"} + hashed_data = _helpers._hash_sensitive_info(test_data) + assert hashed_data["secret"] is None + assert hashed_data["normal_data"] == "abc" + + +def test_hash_sensitive_info_non_string_value(): + test_data = {"username": "user4", "access_token": 12345, "normal_data": "def"} + hashed_data = _helpers._hash_sensitive_info(test_data) + assert hashed_data["access_token"].startswith("hashed_access_token-") + assert hashed_data["normal_data"] == "def" + + +def test_hash_sensitive_info_list_value(): + test_data = [ + {"name": "Alice", "access_token": "12345"}, + {"name": "Bob", "client_id": "1141"}, + ] + hashed_data = _helpers._hash_sensitive_info(test_data) + assert hashed_data[0]["access_token"].startswith("hashed_access_token-") + assert hashed_data[1]["client_id"].startswith("hashed_client_id-") + + +def test_hash_sensitive_info_nested_list_value(): + test_data = [{"names": ["Alice", "Bob"], "tokens": [{"access_token": "1234"}]}] + hashed_data = _helpers._hash_sensitive_info(test_data) + assert hashed_data[0]["tokens"][0]["access_token"].startswith( + "hashed_access_token-" + ) + + +def test_hash_sensitive_info_int_value(): + test_data = 123 + hashed_data = _helpers._hash_sensitive_info(test_data) + assert hashed_data == "" + + +def test_hash_sensitive_info_bool_value(): + test_data = True + hashed_data = _helpers._hash_sensitive_info(test_data) + assert hashed_data == "" + + +def test_hash_sensitive_info_byte_value(): + test_data = b"1243" + hashed_data = _helpers._hash_sensitive_info(test_data) + assert hashed_data == "" + + +def test_hash_sensitive_info_empty_dict(): + test_data = {} + hashed_data = _helpers._hash_sensitive_info(test_data) + assert hashed_data == {} + + +def test_hash_value_consistent_hashing(): + value = "test_value" + field_name = "test_field" + hash1 = _helpers._hash_value(value, field_name) + hash2 = _helpers._hash_value(value, field_name) + assert hash1 == hash2 + + +def test_hash_value_different_hashing(): + value1 = "test_value1" + value2 = "test_value2" + field_name = "test_field" + hash1 = _helpers._hash_value(value1, field_name) + hash2 = _helpers._hash_value(value2, field_name) + assert hash1 != hash2 + + +def test_hash_value_none(): + assert _helpers._hash_value(None, "test") is None + + +def test_logger_configured_default(logger): + assert not _helpers._logger_configured(logger) + + +def test_logger_configured_with_handler(logger): + mock_handler = logging.NullHandler() + logger.addHandler(mock_handler) + assert _helpers._logger_configured(logger) + + # Cleanup + logger.removeHandler(mock_handler) + + +def test_logger_configured_with_custom_level(logger): + original_level = logger.level + logger.level = logging.INFO + assert _helpers._logger_configured(logger) + + # Cleanup + logging.level = original_level + + +def test_logger_configured_with_propagate(logger): + original_propagate = logger.propagate + logger.propagate = False + assert _helpers._logger_configured(logger) + + # Cleanup + logger.propagate = original_propagate + + +def test_is_logging_enabled_with_no_level_set(logger, base_logger): + with mock.patch("google.auth._helpers._BASE_LOGGER_NAME", "foogle"): + assert _helpers.is_logging_enabled(logger) is False + + +def test_is_logging_enabled_with_debug_disabled(caplog, logger, base_logger): + with mock.patch("google.auth._helpers._BASE_LOGGER_NAME", _MOCK_BASE_LOGGER_NAME): + caplog.set_level(logging.INFO, logger=_MOCK_CHILD_LOGGER_NAME) + assert _helpers.is_logging_enabled(logger) is False + + +def test_is_logging_enabled_with_debug_enabled(caplog, logger, base_logger): + with mock.patch("google.auth._helpers._BASE_LOGGER_NAME", _MOCK_BASE_LOGGER_NAME): + caplog.set_level(logging.DEBUG, logger=_MOCK_CHILD_LOGGER_NAME) + assert _helpers.is_logging_enabled(logger) + + +def test_is_logging_enabled_with_base_logger_configured_with_info( + caplog, logger, base_logger +): + with mock.patch("google.auth._helpers._BASE_LOGGER_NAME", _MOCK_BASE_LOGGER_NAME): + caplog.set_level(logging.INFO, logger=_MOCK_BASE_LOGGER_NAME) + + base_logger = logging.getLogger(_MOCK_BASE_LOGGER_NAME) + assert not _helpers.is_logging_enabled(base_logger) + assert not _helpers.is_logging_enabled(logger) + + +def test_is_logging_enabled_with_base_logger_configured_with_debug( + caplog, logger, base_logger +): + with mock.patch("google.auth._helpers._BASE_LOGGER_NAME", _MOCK_BASE_LOGGER_NAME): + caplog.set_level(logging.DEBUG, logger=_MOCK_BASE_LOGGER_NAME) + + assert _helpers.is_logging_enabled(base_logger) + assert _helpers.is_logging_enabled(logger) + + +def test_is_logging_enabled_with_base_logger_info_child_logger_debug( + caplog, logger, base_logger +): + with mock.patch("google.auth._helpers._BASE_LOGGER_NAME", _MOCK_BASE_LOGGER_NAME): + caplog.set_level(logging.INFO, logger=_MOCK_BASE_LOGGER_NAME) + caplog.set_level(logging.DEBUG, logger=_MOCK_CHILD_LOGGER_NAME) + + assert not _helpers.is_logging_enabled(base_logger) + assert _helpers.is_logging_enabled(logger) + + +def test_is_logging_enabled_with_base_logger_debug_child_logger_info( + caplog, logger, base_logger +): + with mock.patch("google.auth._helpers._BASE_LOGGER_NAME", _MOCK_BASE_LOGGER_NAME): + caplog.set_level(logging.DEBUG, logger=_MOCK_BASE_LOGGER_NAME) + caplog.set_level(logging.INFO, logger=_MOCK_CHILD_LOGGER_NAME) + + assert _helpers.is_logging_enabled(base_logger) + assert not _helpers.is_logging_enabled(logger) + + +def test_request_log_debug_enabled(logger, caplog, base_logger): + caplog.set_level(logging.DEBUG, logger=_MOCK_CHILD_LOGGER_NAME) + _helpers.request_log( + logger, + "GET", + "http://example.com", + b'{"key": "value"}', + {"Authorization": "Bearer token"}, + ) + assert len(caplog.records) == 1 + record = caplog.records[0] + assert record.message == "Making request..." + assert record.httpRequest == { + "method": "GET", + "url": "http://example.com", + "body": {"key": "value"}, + "headers": {"Authorization": "Bearer token"}, + } + + +def test_request_log_plain_text_debug_enabled(logger, caplog, base_logger): + caplog.set_level(logging.DEBUG, logger=_MOCK_CHILD_LOGGER_NAME) + _helpers.request_log( + logger, + "GET", + "http://example.com", + b"This is plain text.", + {"Authorization": "Bearer token", "Content-Type": "text/plain"}, + ) + assert len(caplog.records) == 1 + record = caplog.records[0] + assert record.message == "Making request..." + assert record.httpRequest == { + "method": "GET", + "url": "http://example.com", + "body": "", + "headers": {"Authorization": "Bearer token", "Content-Type": "text/plain"}, + } + + +def test_request_log_debug_disabled(logger, caplog, base_logger): + caplog.set_level(logging.INFO, logger=_MOCK_CHILD_LOGGER_NAME) + _helpers.request_log( + logger, + "POST", + "https://api.example.com", + "data", + {"Content-Type": "application/json"}, + ) + assert "Making request: POST https://api.example.com" not in caplog.text + + +def test_response_log_debug_enabled(logger, caplog, base_logger): + caplog.set_level(logging.DEBUG, logger=_MOCK_CHILD_LOGGER_NAME) + _helpers.response_log(logger, {"payload": None}) + assert len(caplog.records) == 1 + record = caplog.records[0] + assert record.message == "Response received..." + assert record.httpResponse == "" + + +def test_response_log_debug_disabled(logger, caplog): + caplog.set_level(logging.INFO, logger=_MOCK_CHILD_LOGGER_NAME) + _helpers.response_log(logger, "another_response") + assert "Response received..." not in caplog.text + + +def test_response_log_base_logger_configured(logger, caplog, base_logger): + caplog.set_level(logging.DEBUG, logger=_MOCK_BASE_LOGGER_NAME) + _helpers.response_log(logger, "another_response") + assert "Response received..." in caplog.text + + +def test_response_log_debug_enabled_response_list(logger, caplog, base_logger): + # NOTE: test the response log when response.json() returns a list as per + # https://requests.readthedocs.io/en/latest/api/#requests.Response.json. + class MockResponse: + def json(self): + return ["item1", "item2", "item3"] + + response = MockResponse() + caplog.set_level(logging.DEBUG, logger=_MOCK_CHILD_LOGGER_NAME) + _helpers.response_log(logger, response) + assert len(caplog.records) == 1 + record = caplog.records[0] + assert record.message == "Response received..." + assert record.httpResponse == ["", "", ""] + + +def test_parse_request_body_bytes_valid(): + body = b"key1=value1&key2=value2" + expected = {"key1": "value1", "key2": "value2"} + assert ( + _helpers._parse_request_body( + body, content_type="application/x-www-form-urlencoded" + ) + == expected + ) + + +def test_parse_request_body_bytes_empty(): + body = b"" + assert _helpers._parse_request_body(body) == "" + + +def test_parse_request_body_bytes_invalid_encoding(): + body = b"\xff\xfe\xfd" # Invalid UTF-8 sequence + assert _helpers._parse_request_body(body) is None + + +def test_parse_request_body_bytes_malformed_query(): + body = b"key1=value1&key2=value2" # missing equals + expected = {"key1": "value1", "key2": "value2"} + assert ( + _helpers._parse_request_body( + body, content_type="application/x-www-form-urlencoded" + ) + == expected + ) + + +def test_parse_request_body_none(): + assert _helpers._parse_request_body(None) is None + + +def test_parse_request_body_bytes_no_content_type(): + body = b'{"key": "value"}' + expected = {"key": "value"} + assert _helpers._parse_request_body(body) == expected + + +def test_parse_request_body_bytes_content_type_json(): + body = b'{"key": "value"}' + expected = {"key": "value"} + assert ( + _helpers._parse_request_body(body, content_type="application/json") == expected + ) + + +def test_parse_request_body_content_type_urlencoded(): + body = b"key=value" + expected = {"key": "value"} + assert ( + _helpers._parse_request_body( + body, content_type="application/x-www-form-urlencoded" + ) + == expected + ) + + +def test_parse_request_body_bytes_content_type_text(): + body = b"This is plain text." + expected = "This is plain text." + assert _helpers._parse_request_body(body, content_type="text/plain") == expected + + +def test_parse_request_body_content_type_invalid(): + body = b'{"key": "value"}' + assert _helpers._parse_request_body(body, content_type="invalid") is None + + +def test_parse_request_body_other_type(): + assert _helpers._parse_request_body(123) is None + assert _helpers._parse_request_body("string") is None + + +def test_parse_response_json_valid(): + class MockResponse: + def json(self): + return {"data": "test"} + + response = MockResponse() + expected = {"data": "test"} + assert _helpers._parse_response(response) == expected + + +def test_parse_response_json_invalid(): + class MockResponse: + def json(self): + raise json.JSONDecodeError("msg", "doc", 0) + + response = MockResponse() + assert _helpers._parse_response(response) is None + + +def test_parse_response_no_json_method(): + response = "plain text" + assert _helpers._parse_response(response) is None + + +def test_parse_response_none(): + assert _helpers._parse_response(None) is None From b637035e0e4317223258b08afb15d8d19cff4630 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 5 May 2025 11:15:54 -0700 Subject: [PATCH 903/966] chore(main): release 2.40.0 (#1750) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 12 ++++++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 01e21e7cc7af..eff4202b8947 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,18 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.40.0](https://github.com/googleapis/google-auth-library-python/compare/v2.39.0...v2.40.0) (2025-04-29) + + +### Features + +* Add request response logging to auth ([#1678](https://github.com/googleapis/google-auth-library-python/issues/1678)) ([77ad53e](https://github.com/googleapis/google-auth-library-python/commit/77ad53eb00c74b3badc486c8207a16dbc49f37e5)) + + +### Bug Fixes + +* Correct webauthn JSON parsing to be compliant with standard. ([#1658](https://github.com/googleapis/google-auth-library-python/issues/1658)) ([0c5ef36](https://github.com/googleapis/google-auth-library-python/commit/0c5ef364fb13ca9d7d17100166de87732d752de8)) + ## [2.39.0](https://github.com/googleapis/google-auth-library-python/compare/v2.38.0...v2.39.0) (2025-04-14) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 393caa8ad44d..d1363c1ef0ea 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.39.0" +__version__ = "2.40.0" From 3ca867de0683901e45449739d35e32983a19b3b5 Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Tue, 6 May 2025 21:48:42 +0000 Subject: [PATCH 904/966] chore: update secret (#1757) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index fe6fb3aa0cbbc05a77da905c261b30e091e46c6f..8fa36e70b68c81f3fa76dbddb9048e7fa0290f2f 100644 GIT binary patch literal 10324 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 Date: Wed, 7 May 2025 03:27:32 +0500 Subject: [PATCH 905/966] fix: disable logging response body for async logs (#1756) * fix: disable logging response body for async logs * remove unused test code --------- Co-authored-by: Anthonios Partheniou --- packages/google-auth/google/auth/aio/_helpers.py | 7 ++++++- packages/google-auth/tests/aio/test__helpers.py | 8 ++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/google-auth/google/auth/aio/_helpers.py b/packages/google-auth/google/auth/aio/_helpers.py index fd7d37a2f7b0..1d5a4618cb61 100644 --- a/packages/google-auth/google/auth/aio/_helpers.py +++ b/packages/google-auth/google/auth/aio/_helpers.py @@ -53,5 +53,10 @@ async def response_log_async(logger: logging.Logger, response: Any) -> None: response: The HTTP response object to log. """ if _helpers.is_logging_enabled(logger): - json_response = await _parse_response_async(response) + # TODO(https://github.com/googleapis/google-auth-library-python/issues/1755): + # Parsing the response for async streaming logging results in + # the stream to be empty downstream. For now, we will not be logging + # the response for async responses until we investigate further. + # json_response = await _parse_response_async(response) + json_response = None _helpers._response_log_base(logger, json_response) diff --git a/packages/google-auth/tests/aio/test__helpers.py b/packages/google-auth/tests/aio/test__helpers.py index 7642431caec6..829dc97ff33c 100644 --- a/packages/google-auth/tests/aio/test__helpers.py +++ b/packages/google-auth/tests/aio/test__helpers.py @@ -65,17 +65,13 @@ async def test_response_log_debug_disabled(logger, caplog, base_logger): @pytest.mark.asyncio async def test_response_log_debug_enabled_response_json(logger, caplog, base_logger): - class MockResponse: - async def json(self): - return {"key1": "value1", "key2": "value2", "key3": "value3"} - - response = MockResponse() + response = None caplog.set_level(logging.DEBUG, logger=_MOCK_CHILD_LOGGER_NAME) await _helpers.response_log_async(logger, response) assert len(caplog.records) == 1 record = caplog.records[0] assert record.message == "Response received..." - assert record.httpResponse == {"key1": "value1", "key2": "value2", "key3": "value3"} + assert record.httpResponse == "" @pytest.mark.asyncio From f0081a5fa637336fcdef8e4616d616e679d2b30f Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 6 May 2025 17:55:35 -0700 Subject: [PATCH 906/966] chore(main): release 2.40.1 (#1758) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index eff4202b8947..c43ab2b1c7e7 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.40.1](https://github.com/googleapis/google-auth-library-python/compare/v2.40.0...v2.40.1) (2025-05-06) + + +### Bug Fixes + +* Disable logging response body for async logs ([#1756](https://github.com/googleapis/google-auth-library-python/issues/1756)) ([2f0ddfe](https://github.com/googleapis/google-auth-library-python/commit/2f0ddfeb9f6c726c68beebd7eefd32c86f7f0963)) + ## [2.40.0](https://github.com/googleapis/google-auth-library-python/compare/v2.39.0...v2.40.0) (2025-04-29) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index d1363c1ef0ea..ee0bb12609d1 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.40.0" +__version__ = "2.40.1" From 42116e7e27482e00c56aeb578c0f3eea33892b84 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Tue, 6 May 2025 21:04:04 -0400 Subject: [PATCH 907/966] chore: remove unused files (#1691) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: remove unused files * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot --- .../.kokoro/docker/docs/Dockerfile | 89 --- .../.kokoro/docker/docs/requirements.in | 1 - .../.kokoro/docker/docs/requirements.txt | 42 -- packages/google-auth/.kokoro/docs/common.cfg | 67 --- .../.kokoro/docs/docs-presubmit.cfg | 28 - packages/google-auth/.kokoro/docs/docs.cfg | 1 - packages/google-auth/.kokoro/publish-docs.sh | 62 --- packages/google-auth/.kokoro/release.sh | 29 - .../google-auth/.kokoro/release/common.cfg | 43 -- .../google-auth/.kokoro/release/release.cfg | 1 - packages/google-auth/.kokoro/requirements.in | 11 - packages/google-auth/.kokoro/requirements.txt | 509 ------------------ 12 files changed, 883 deletions(-) delete mode 100644 packages/google-auth/.kokoro/docker/docs/Dockerfile delete mode 100644 packages/google-auth/.kokoro/docker/docs/requirements.in delete mode 100644 packages/google-auth/.kokoro/docker/docs/requirements.txt delete mode 100644 packages/google-auth/.kokoro/docs/common.cfg delete mode 100644 packages/google-auth/.kokoro/docs/docs-presubmit.cfg delete mode 100644 packages/google-auth/.kokoro/docs/docs.cfg delete mode 100755 packages/google-auth/.kokoro/publish-docs.sh delete mode 100755 packages/google-auth/.kokoro/release.sh delete mode 100644 packages/google-auth/.kokoro/release/common.cfg delete mode 100644 packages/google-auth/.kokoro/release/release.cfg delete mode 100644 packages/google-auth/.kokoro/requirements.in delete mode 100644 packages/google-auth/.kokoro/requirements.txt diff --git a/packages/google-auth/.kokoro/docker/docs/Dockerfile b/packages/google-auth/.kokoro/docker/docs/Dockerfile deleted file mode 100644 index e5410e296bd8..000000000000 --- a/packages/google-auth/.kokoro/docker/docs/Dockerfile +++ /dev/null @@ -1,89 +0,0 @@ -# Copyright 2024 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from ubuntu:24.04 - -ENV DEBIAN_FRONTEND noninteractive - -# Ensure local Python is preferred over distribution Python. -ENV PATH /usr/local/bin:$PATH - -# Install dependencies. -RUN apt-get update \ - && apt-get install -y --no-install-recommends \ - apt-transport-https \ - build-essential \ - ca-certificates \ - curl \ - dirmngr \ - git \ - gpg-agent \ - graphviz \ - libbz2-dev \ - libdb5.3-dev \ - libexpat1-dev \ - libffi-dev \ - liblzma-dev \ - libreadline-dev \ - libsnappy-dev \ - libssl-dev \ - libsqlite3-dev \ - portaudio19-dev \ - redis-server \ - software-properties-common \ - ssh \ - sudo \ - tcl \ - tcl-dev \ - tk \ - tk-dev \ - uuid-dev \ - wget \ - zlib1g-dev \ - && add-apt-repository universe \ - && apt-get update \ - && apt-get -y install jq \ - && apt-get clean autoclean \ - && apt-get autoremove -y \ - && rm -rf /var/lib/apt/lists/* \ - && rm -f /var/cache/apt/archives/*.deb - - -###################### Install python 3.10.14 for docs/docfx session - -# Download python 3.10.14 -RUN wget https://www.python.org/ftp/python/3.10.14/Python-3.10.14.tgz - -# Extract files -RUN tar -xvf Python-3.10.14.tgz - -# Install python 3.10.14 -RUN ./Python-3.10.14/configure --enable-optimizations -RUN make altinstall - -ENV PATH /usr/local/bin/python3.10:$PATH - -###################### Install pip -RUN wget -O /tmp/get-pip.py 'https://bootstrap.pypa.io/get-pip.py' \ - && python3.10 /tmp/get-pip.py \ - && rm /tmp/get-pip.py - -# Test pip -RUN python3.10 -m pip - -# Install build requirements -COPY requirements.txt /requirements.txt -RUN python3.10 -m pip install --require-hashes -r requirements.txt - -CMD ["python3.10"] diff --git a/packages/google-auth/.kokoro/docker/docs/requirements.in b/packages/google-auth/.kokoro/docker/docs/requirements.in deleted file mode 100644 index 816817c672a1..000000000000 --- a/packages/google-auth/.kokoro/docker/docs/requirements.in +++ /dev/null @@ -1 +0,0 @@ -nox diff --git a/packages/google-auth/.kokoro/docker/docs/requirements.txt b/packages/google-auth/.kokoro/docker/docs/requirements.txt deleted file mode 100644 index 66eacc82f041..000000000000 --- a/packages/google-auth/.kokoro/docker/docs/requirements.txt +++ /dev/null @@ -1,42 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.9 -# by the following command: -# -# pip-compile --allow-unsafe --generate-hashes requirements.in -# -argcomplete==3.5.1 \ - --hash=sha256:1a1d148bdaa3e3b93454900163403df41448a248af01b6e849edc5ac08e6c363 \ - --hash=sha256:eb1ee355aa2557bd3d0145de7b06b2a45b0ce461e1e7813f5d066039ab4177b4 - # via nox -colorlog==6.8.2 \ - --hash=sha256:3e3e079a41feb5a1b64f978b5ea4f46040a94f11f0e8bbb8261e3dbbeca64d44 \ - --hash=sha256:4dcbb62368e2800cb3c5abd348da7e53f6c362dda502ec27c560b2e58a66bd33 - # via nox -distlib==0.3.9 \ - --hash=sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87 \ - --hash=sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403 - # via virtualenv -filelock==3.16.1 \ - --hash=sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0 \ - --hash=sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435 - # via virtualenv -nox==2024.10.9 \ - --hash=sha256:1d36f309a0a2a853e9bccb76bbef6bb118ba92fa92674d15604ca99adeb29eab \ - --hash=sha256:7aa9dc8d1c27e9f45ab046ffd1c3b2c4f7c91755304769df231308849ebded95 - # via -r requirements.in -packaging==24.1 \ - --hash=sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002 \ - --hash=sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124 - # via nox -platformdirs==4.3.6 \ - --hash=sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907 \ - --hash=sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb - # via virtualenv -tomli==2.0.2 \ - --hash=sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38 \ - --hash=sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed - # via nox -virtualenv==20.26.6 \ - --hash=sha256:280aede09a2a5c317e409a00102e7077c6432c5a38f0ef938e643805a7ad2c48 \ - --hash=sha256:7345cc5b25405607a624d8418154577459c3e0277f5466dd79c49d5e492995f2 - # via nox diff --git a/packages/google-auth/.kokoro/docs/common.cfg b/packages/google-auth/.kokoro/docs/common.cfg deleted file mode 100644 index a1aa466c8890..000000000000 --- a/packages/google-auth/.kokoro/docs/common.cfg +++ /dev/null @@ -1,67 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -# Build logs will be here -action { - define_artifacts { - regex: "**/*sponge_log.xml" - } -} - -# Download trampoline resources. -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" - -# Use the trampoline script to run in docker. -build_file: "google-auth-library-python/.kokoro/trampoline_v2.sh" - -# Configure the docker image for kokoro-trampoline. -env_vars: { - key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-kokoro-resources/python-lib-docs" -} -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/google-auth-library-python/.kokoro/publish-docs.sh" -} - -env_vars: { - key: "STAGING_BUCKET" - value: "docs-staging" -} - -env_vars: { - key: "V2_STAGING_BUCKET" - # Push non-cloud library docs to `docs-staging-v2-dev` instead of the - # Cloud RAD bucket `docs-staging-v2` - value: "docs-staging-v2-dev" -} - -# It will upload the docker image after successful builds. -env_vars: { - key: "TRAMPOLINE_IMAGE_UPLOAD" - value: "true" -} - -# It will always build the docker image. -env_vars: { - key: "TRAMPOLINE_DOCKERFILE" - value: ".kokoro/docker/docs/Dockerfile" -} - -# Fetch the token needed for reporting release status to GitHub -before_action { - fetch_keystore { - keystore_resource { - keystore_config_id: 73713 - keyname: "yoshi-automation-github-key" - } - } -} - -before_action { - fetch_keystore { - keystore_resource { - keystore_config_id: 73713 - keyname: "docuploader_service_account" - } - } -} diff --git a/packages/google-auth/.kokoro/docs/docs-presubmit.cfg b/packages/google-auth/.kokoro/docs/docs-presubmit.cfg deleted file mode 100644 index d3f0deae3c20..000000000000 --- a/packages/google-auth/.kokoro/docs/docs-presubmit.cfg +++ /dev/null @@ -1,28 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -env_vars: { - key: "STAGING_BUCKET" - value: "gcloud-python-test" -} - -env_vars: { - key: "V2_STAGING_BUCKET" - value: "gcloud-python-test" -} - -# We only upload the image in the main `docs` build. -env_vars: { - key: "TRAMPOLINE_IMAGE_UPLOAD" - value: "false" -} - -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/google-auth-library-python/.kokoro/build.sh" -} - -# Only run this nox session. -env_vars: { - key: "NOX_SESSION" - value: "docs" -} diff --git a/packages/google-auth/.kokoro/docs/docs.cfg b/packages/google-auth/.kokoro/docs/docs.cfg deleted file mode 100644 index 8f43917d92fe..000000000000 --- a/packages/google-auth/.kokoro/docs/docs.cfg +++ /dev/null @@ -1 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto \ No newline at end of file diff --git a/packages/google-auth/.kokoro/publish-docs.sh b/packages/google-auth/.kokoro/publish-docs.sh deleted file mode 100755 index 233205d580e9..000000000000 --- a/packages/google-auth/.kokoro/publish-docs.sh +++ /dev/null @@ -1,62 +0,0 @@ -#!/bin/bash -# Copyright 2024 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -set -eo pipefail - -# Disable buffering, so that the logs stream through. -export PYTHONUNBUFFERED=1 - -export PATH="${HOME}/.local/bin:${PATH}" - -# Install nox -python3.10 -m pip install --require-hashes -r .kokoro/requirements.txt -python3.10 -m nox --version - -# build docs -nox -s docs - -# create metadata -python3.10 -m docuploader create-metadata \ - --name=$(jq --raw-output '.name // empty' .repo-metadata.json) \ - --version=$(python3.10 setup.py --version) \ - --language=$(jq --raw-output '.language // empty' .repo-metadata.json) \ - --distribution-name=$(python3.10 setup.py --name) \ - --product-page=$(jq --raw-output '.product_documentation // empty' .repo-metadata.json) \ - --github-repository=$(jq --raw-output '.repo // empty' .repo-metadata.json) \ - --issue-tracker=$(jq --raw-output '.issue_tracker // empty' .repo-metadata.json) - -cat docs.metadata - -# upload docs -python3.10 -m docuploader upload docs/_build/html --metadata-file docs.metadata --staging-bucket "${STAGING_BUCKET}" - - -# docfx yaml files -nox -s docfx - -# create metadata. -python3.10 -m docuploader create-metadata \ - --name=$(jq --raw-output '.name // empty' .repo-metadata.json) \ - --version=$(python3.10 setup.py --version) \ - --language=$(jq --raw-output '.language // empty' .repo-metadata.json) \ - --distribution-name=$(python3.10 setup.py --name) \ - --product-page=$(jq --raw-output '.product_documentation // empty' .repo-metadata.json) \ - --github-repository=$(jq --raw-output '.repo // empty' .repo-metadata.json) \ - --issue-tracker=$(jq --raw-output '.issue_tracker // empty' .repo-metadata.json) - -cat docs.metadata - -# upload docs -python3.10 -m docuploader upload docs/_build/html/docfx_yaml --metadata-file docs.metadata --destination-prefix docfx --staging-bucket "${V2_STAGING_BUCKET}" diff --git a/packages/google-auth/.kokoro/release.sh b/packages/google-auth/.kokoro/release.sh deleted file mode 100755 index dcc6d1fefd63..000000000000 --- a/packages/google-auth/.kokoro/release.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/bash -# Copyright 2024 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -set -eo pipefail - -# Start the releasetool reporter -python3 -m pip install --require-hashes -r github/google-auth-library-python/.kokoro/requirements.txt -python3 -m releasetool publish-reporter-script > /tmp/publisher-script; source /tmp/publisher-script - -# Disable buffering, so that the logs stream through. -export PYTHONUNBUFFERED=1 - -# Move into the package, build the distribution and upload. -TWINE_PASSWORD=$(cat "${KOKORO_KEYSTORE_DIR}/73713_google-cloud-pypi-token-keystore-3") -cd github/google-auth-library-python -python3 setup.py sdist bdist_wheel -twine upload --username __token__ --password "${TWINE_PASSWORD}" dist/* diff --git a/packages/google-auth/.kokoro/release/common.cfg b/packages/google-auth/.kokoro/release/common.cfg deleted file mode 100644 index 43bec962c932..000000000000 --- a/packages/google-auth/.kokoro/release/common.cfg +++ /dev/null @@ -1,43 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -# Build logs will be here -action { - define_artifacts { - regex: "**/*sponge_log.xml" - } -} - -# Download trampoline resources. -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" - -# Use the trampoline script to run in docker. -build_file: "google-auth-library-python/.kokoro/trampoline.sh" - -# Configure the docker image for kokoro-trampoline. -env_vars: { - key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-kokoro-resources/python-multi" -} -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/google-auth-library-python/.kokoro/release.sh" -} - -# Fetch PyPI password -before_action { - fetch_keystore { - keystore_resource { - keystore_config_id: 73713 - keyname: "google-cloud-pypi-token-keystore-3" - } - } -} - -# Store the packages we uploaded to PyPI. That way, we have a record of exactly -# what we published, which we can use to generate SBOMs and attestations. -action { - define_artifacts { - regex: "github/google-auth-library-python/**/*.tar.gz" - strip_prefix: "github/google-auth-library-python" - } -} diff --git a/packages/google-auth/.kokoro/release/release.cfg b/packages/google-auth/.kokoro/release/release.cfg deleted file mode 100644 index 8f43917d92fe..000000000000 --- a/packages/google-auth/.kokoro/release/release.cfg +++ /dev/null @@ -1 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto \ No newline at end of file diff --git a/packages/google-auth/.kokoro/requirements.in b/packages/google-auth/.kokoro/requirements.in deleted file mode 100644 index fff4d9ce0d0a..000000000000 --- a/packages/google-auth/.kokoro/requirements.in +++ /dev/null @@ -1,11 +0,0 @@ -gcp-docuploader -gcp-releasetool>=2 # required for compatibility with cryptography>=42.x -importlib-metadata -typing-extensions -twine -wheel -setuptools -nox>=2022.11.21 # required to remove dependency on py -charset-normalizer<3 -click<8.1.0 -cryptography>=42.0.5 diff --git a/packages/google-auth/.kokoro/requirements.txt b/packages/google-auth/.kokoro/requirements.txt deleted file mode 100644 index 006d8ef931bf..000000000000 --- a/packages/google-auth/.kokoro/requirements.txt +++ /dev/null @@ -1,509 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.9 -# by the following command: -# -# pip-compile --allow-unsafe --generate-hashes requirements.in -# -argcomplete==3.5.1 \ - --hash=sha256:1a1d148bdaa3e3b93454900163403df41448a248af01b6e849edc5ac08e6c363 \ - --hash=sha256:eb1ee355aa2557bd3d0145de7b06b2a45b0ce461e1e7813f5d066039ab4177b4 - # via nox -attrs==24.2.0 \ - --hash=sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346 \ - --hash=sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2 - # via gcp-releasetool -backports-tarfile==1.2.0 \ - --hash=sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34 \ - --hash=sha256:d75e02c268746e1b8144c278978b6e98e85de6ad16f8e4b0844a154557eca991 - # via jaraco-context -cachetools==5.5.0 \ - --hash=sha256:02134e8439cdc2ffb62023ce1debca2944c3f289d66bb17ead3ab3dede74b292 \ - --hash=sha256:2cc24fb4cbe39633fb7badd9db9ca6295d766d9c2995f245725a46715d050f2a - # via google-auth -certifi==2024.8.30 \ - --hash=sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8 \ - --hash=sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9 - # via requests -cffi==1.17.1 \ - --hash=sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8 \ - --hash=sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2 \ - --hash=sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1 \ - --hash=sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15 \ - --hash=sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36 \ - --hash=sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824 \ - --hash=sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8 \ - --hash=sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36 \ - --hash=sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17 \ - --hash=sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf \ - --hash=sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc \ - --hash=sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3 \ - --hash=sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed \ - --hash=sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702 \ - --hash=sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1 \ - --hash=sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8 \ - --hash=sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903 \ - --hash=sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6 \ - --hash=sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d \ - --hash=sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b \ - --hash=sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e \ - --hash=sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be \ - --hash=sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c \ - --hash=sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683 \ - --hash=sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9 \ - --hash=sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c \ - --hash=sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8 \ - --hash=sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1 \ - --hash=sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4 \ - --hash=sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655 \ - --hash=sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67 \ - --hash=sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595 \ - --hash=sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0 \ - --hash=sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65 \ - --hash=sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41 \ - --hash=sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6 \ - --hash=sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401 \ - --hash=sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6 \ - --hash=sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3 \ - --hash=sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16 \ - --hash=sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93 \ - --hash=sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e \ - --hash=sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4 \ - --hash=sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964 \ - --hash=sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c \ - --hash=sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576 \ - --hash=sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0 \ - --hash=sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3 \ - --hash=sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662 \ - --hash=sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3 \ - --hash=sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff \ - --hash=sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5 \ - --hash=sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd \ - --hash=sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f \ - --hash=sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5 \ - --hash=sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14 \ - --hash=sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d \ - --hash=sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9 \ - --hash=sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7 \ - --hash=sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382 \ - --hash=sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a \ - --hash=sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e \ - --hash=sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a \ - --hash=sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4 \ - --hash=sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99 \ - --hash=sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87 \ - --hash=sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b - # via cryptography -charset-normalizer==2.1.1 \ - --hash=sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845 \ - --hash=sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f - # via - # -r requirements.in - # requests -click==8.0.4 \ - --hash=sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1 \ - --hash=sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb - # via - # -r requirements.in - # gcp-docuploader - # gcp-releasetool -colorlog==6.8.2 \ - --hash=sha256:3e3e079a41feb5a1b64f978b5ea4f46040a94f11f0e8bbb8261e3dbbeca64d44 \ - --hash=sha256:4dcbb62368e2800cb3c5abd348da7e53f6c362dda502ec27c560b2e58a66bd33 - # via - # gcp-docuploader - # nox -cryptography==43.0.1 \ - --hash=sha256:014f58110f53237ace6a408b5beb6c427b64e084eb451ef25a28308270086494 \ - --hash=sha256:1bbcce1a551e262dfbafb6e6252f1ae36a248e615ca44ba302df077a846a8806 \ - --hash=sha256:203e92a75716d8cfb491dc47c79e17d0d9207ccffcbcb35f598fbe463ae3444d \ - --hash=sha256:27e613d7077ac613e399270253259d9d53872aaf657471473ebfc9a52935c062 \ - --hash=sha256:2bd51274dcd59f09dd952afb696bf9c61a7a49dfc764c04dd33ef7a6b502a1e2 \ - --hash=sha256:38926c50cff6f533f8a2dae3d7f19541432610d114a70808f0926d5aaa7121e4 \ - --hash=sha256:511f4273808ab590912a93ddb4e3914dfd8a388fed883361b02dea3791f292e1 \ - --hash=sha256:58d4e9129985185a06d849aa6df265bdd5a74ca6e1b736a77959b498e0505b85 \ - --hash=sha256:5b43d1ea6b378b54a1dc99dd8a2b5be47658fe9a7ce0a58ff0b55f4b43ef2b84 \ - --hash=sha256:61ec41068b7b74268fa86e3e9e12b9f0c21fcf65434571dbb13d954bceb08042 \ - --hash=sha256:666ae11966643886c2987b3b721899d250855718d6d9ce41b521252a17985f4d \ - --hash=sha256:68aaecc4178e90719e95298515979814bda0cbada1256a4485414860bd7ab962 \ - --hash=sha256:7c05650fe8023c5ed0d46793d4b7d7e6cd9c04e68eabe5b0aeea836e37bdcec2 \ - --hash=sha256:80eda8b3e173f0f247f711eef62be51b599b5d425c429b5d4ca6a05e9e856baa \ - --hash=sha256:8385d98f6a3bf8bb2d65a73e17ed87a3ba84f6991c155691c51112075f9ffc5d \ - --hash=sha256:88cce104c36870d70c49c7c8fd22885875d950d9ee6ab54df2745f83ba0dc365 \ - --hash=sha256:9d3cdb25fa98afdd3d0892d132b8d7139e2c087da1712041f6b762e4f807cc96 \ - --hash=sha256:a575913fb06e05e6b4b814d7f7468c2c660e8bb16d8d5a1faf9b33ccc569dd47 \ - --hash=sha256:ac119bb76b9faa00f48128b7f5679e1d8d437365c5d26f1c2c3f0da4ce1b553d \ - --hash=sha256:c1332724be35d23a854994ff0b66530119500b6053d0bd3363265f7e5e77288d \ - --hash=sha256:d03a475165f3134f773d1388aeb19c2d25ba88b6a9733c5c590b9ff7bbfa2e0c \ - --hash=sha256:d75601ad10b059ec832e78823b348bfa1a59f6b8d545db3a24fd44362a1564cb \ - --hash=sha256:de41fd81a41e53267cb020bb3a7212861da53a7d39f863585d13ea11049cf277 \ - --hash=sha256:e710bf40870f4db63c3d7d929aa9e09e4e7ee219e703f949ec4073b4294f6172 \ - --hash=sha256:ea25acb556320250756e53f9e20a4177515f012c9eaea17eb7587a8c4d8ae034 \ - --hash=sha256:f98bf604c82c416bc829e490c700ca1553eafdf2912a91e23a79d97d9801372a \ - --hash=sha256:fba1007b3ef89946dbbb515aeeb41e30203b004f0b4b00e5e16078b518563289 - # via - # -r requirements.in - # gcp-releasetool - # secretstorage -distlib==0.3.9 \ - --hash=sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87 \ - --hash=sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403 - # via virtualenv -docutils==0.21.2 \ - --hash=sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f \ - --hash=sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2 - # via readme-renderer -filelock==3.16.1 \ - --hash=sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0 \ - --hash=sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435 - # via virtualenv -gcp-docuploader==0.6.5 \ - --hash=sha256:30221d4ac3e5a2b9c69aa52fdbef68cc3f27d0e6d0d90e220fc024584b8d2318 \ - --hash=sha256:b7458ef93f605b9d46a4bf3a8dc1755dad1f31d030c8679edf304e343b347eea - # via -r requirements.in -gcp-releasetool==2.1.1 \ - --hash=sha256:25639269f4eae510094f9dbed9894977e1966933211eb155a451deebc3fc0b30 \ - --hash=sha256:845f4ded3d9bfe8cc7fdaad789e83f4ea014affa77785259a7ddac4b243e099e - # via -r requirements.in -google-api-core==2.21.0 \ - --hash=sha256:4a152fd11a9f774ea606388d423b68aa7e6d6a0ffe4c8266f74979613ec09f81 \ - --hash=sha256:6869eacb2a37720380ba5898312af79a4d30b8bca1548fb4093e0697dc4bdf5d - # via - # google-cloud-core - # google-cloud-storage -google-auth==2.35.0 \ - --hash=sha256:25df55f327ef021de8be50bad0dfd4a916ad0de96da86cd05661c9297723ad3f \ - --hash=sha256:f4c64ed4e01e8e8b646ef34c018f8bf3338df0c8e37d8b3bba40e7f574a3278a - # via - # gcp-releasetool - # google-api-core - # google-cloud-core - # google-cloud-storage -google-cloud-core==2.4.1 \ - --hash=sha256:9b7749272a812bde58fff28868d0c5e2f585b82f37e09a1f6ed2d4d10f134073 \ - --hash=sha256:a9e6a4422b9ac5c29f79a0ede9485473338e2ce78d91f2370c01e730eab22e61 - # via google-cloud-storage -google-cloud-storage==2.18.2 \ - --hash=sha256:97a4d45c368b7d401ed48c4fdfe86e1e1cb96401c9e199e419d289e2c0370166 \ - --hash=sha256:aaf7acd70cdad9f274d29332673fcab98708d0e1f4dceb5a5356aaef06af4d99 - # via gcp-docuploader -google-crc32c==1.6.0 \ - --hash=sha256:05e2d8c9a2f853ff116db9706b4a27350587f341eda835f46db3c0a8c8ce2f24 \ - --hash=sha256:18e311c64008f1f1379158158bb3f0c8d72635b9eb4f9545f8cf990c5668e59d \ - --hash=sha256:236c87a46cdf06384f614e9092b82c05f81bd34b80248021f729396a78e55d7e \ - --hash=sha256:35834855408429cecf495cac67ccbab802de269e948e27478b1e47dfb6465e57 \ - --hash=sha256:386122eeaaa76951a8196310432c5b0ef3b53590ef4c317ec7588ec554fec5d2 \ - --hash=sha256:40b05ab32a5067525670880eb5d169529089a26fe35dce8891127aeddc1950e8 \ - --hash=sha256:48abd62ca76a2cbe034542ed1b6aee851b6f28aaca4e6551b5599b6f3ef175cc \ - --hash=sha256:50cf2a96da226dcbff8671233ecf37bf6e95de98b2a2ebadbfdf455e6d05df42 \ - --hash=sha256:51c4f54dd8c6dfeb58d1df5e4f7f97df8abf17a36626a217f169893d1d7f3e9f \ - --hash=sha256:5bcc90b34df28a4b38653c36bb5ada35671ad105c99cfe915fb5bed7ad6924aa \ - --hash=sha256:62f6d4a29fea082ac4a3c9be5e415218255cf11684ac6ef5488eea0c9132689b \ - --hash=sha256:6eceb6ad197656a1ff49ebfbbfa870678c75be4344feb35ac1edf694309413dc \ - --hash=sha256:7aec8e88a3583515f9e0957fe4f5f6d8d4997e36d0f61624e70469771584c760 \ - --hash=sha256:91ca8145b060679ec9176e6de4f89b07363d6805bd4760631ef254905503598d \ - --hash=sha256:a184243544811e4a50d345838a883733461e67578959ac59964e43cca2c791e7 \ - --hash=sha256:a9e4b426c3702f3cd23b933436487eb34e01e00327fac20c9aebb68ccf34117d \ - --hash=sha256:bb0966e1c50d0ef5bc743312cc730b533491d60585a9a08f897274e57c3f70e0 \ - --hash=sha256:bb8b3c75bd157010459b15222c3fd30577042a7060e29d42dabce449c087f2b3 \ - --hash=sha256:bd5e7d2445d1a958c266bfa5d04c39932dc54093fa391736dbfdb0f1929c1fb3 \ - --hash=sha256:c87d98c7c4a69066fd31701c4e10d178a648c2cac3452e62c6b24dc51f9fcc00 \ - --hash=sha256:d2952396dc604544ea7476b33fe87faedc24d666fb0c2d5ac971a2b9576ab871 \ - --hash=sha256:d8797406499f28b5ef791f339594b0b5fdedf54e203b5066675c406ba69d705c \ - --hash=sha256:d9e9913f7bd69e093b81da4535ce27af842e7bf371cde42d1ae9e9bd382dc0e9 \ - --hash=sha256:e2806553238cd076f0a55bddab37a532b53580e699ed8e5606d0de1f856b5205 \ - --hash=sha256:ebab974b1687509e5c973b5c4b8b146683e101e102e17a86bd196ecaa4d099fc \ - --hash=sha256:ed767bf4ba90104c1216b68111613f0d5926fb3780660ea1198fc469af410e9d \ - --hash=sha256:f7a1fc29803712f80879b0806cb83ab24ce62fc8daf0569f2204a0cfd7f68ed4 - # via - # google-cloud-storage - # google-resumable-media -google-resumable-media==2.7.2 \ - --hash=sha256:3ce7551e9fe6d99e9a126101d2536612bb73486721951e9562fee0f90c6ababa \ - --hash=sha256:5280aed4629f2b60b847b0d42f9857fd4935c11af266744df33d8074cae92fe0 - # via google-cloud-storage -googleapis-common-protos==1.65.0 \ - --hash=sha256:2972e6c496f435b92590fd54045060867f3fe9be2c82ab148fc8885035479a63 \ - --hash=sha256:334a29d07cddc3aa01dee4988f9afd9b2916ee2ff49d6b757155dc0d197852c0 - # via google-api-core -idna==3.10 \ - --hash=sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9 \ - --hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3 - # via requests -importlib-metadata==8.5.0 \ - --hash=sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b \ - --hash=sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7 - # via - # -r requirements.in - # keyring - # twine -jaraco-classes==3.4.0 \ - --hash=sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd \ - --hash=sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790 - # via keyring -jaraco-context==6.0.1 \ - --hash=sha256:9bae4ea555cf0b14938dc0aee7c9f32ed303aa20a3b73e7dc80111628792d1b3 \ - --hash=sha256:f797fc481b490edb305122c9181830a3a5b76d84ef6d1aef2fb9b47ab956f9e4 - # via keyring -jaraco-functools==4.1.0 \ - --hash=sha256:70f7e0e2ae076498e212562325e805204fc092d7b4c17e0e86c959e249701a9d \ - --hash=sha256:ad159f13428bc4acbf5541ad6dec511f91573b90fba04df61dafa2a1231cf649 - # via keyring -jeepney==0.8.0 \ - --hash=sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806 \ - --hash=sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755 - # via - # keyring - # secretstorage -jinja2==3.1.4 \ - --hash=sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369 \ - --hash=sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d - # via gcp-releasetool -keyring==25.4.1 \ - --hash=sha256:5426f817cf7f6f007ba5ec722b1bcad95a75b27d780343772ad76b17cb47b0bf \ - --hash=sha256:b07ebc55f3e8ed86ac81dd31ef14e81ace9dd9c3d4b5d77a6e9a2016d0d71a1b - # via - # gcp-releasetool - # twine -markdown-it-py==3.0.0 \ - --hash=sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1 \ - --hash=sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb - # via rich -markupsafe==3.0.1 \ - --hash=sha256:0778de17cff1acaeccc3ff30cd99a3fd5c50fc58ad3d6c0e0c4c58092b859396 \ - --hash=sha256:0f84af7e813784feb4d5e4ff7db633aba6c8ca64a833f61d8e4eade234ef0c38 \ - --hash=sha256:17b2aea42a7280db02ac644db1d634ad47dcc96faf38ab304fe26ba2680d359a \ - --hash=sha256:242d6860f1fd9191aef5fae22b51c5c19767f93fb9ead4d21924e0bcb17619d8 \ - --hash=sha256:244dbe463d5fb6d7ce161301a03a6fe744dac9072328ba9fc82289238582697b \ - --hash=sha256:26627785a54a947f6d7336ce5963569b5d75614619e75193bdb4e06e21d447ad \ - --hash=sha256:2a4b34a8d14649315c4bc26bbfa352663eb51d146e35eef231dd739d54a5430a \ - --hash=sha256:2ae99f31f47d849758a687102afdd05bd3d3ff7dbab0a8f1587981b58a76152a \ - --hash=sha256:312387403cd40699ab91d50735ea7a507b788091c416dd007eac54434aee51da \ - --hash=sha256:3341c043c37d78cc5ae6e3e305e988532b072329639007fd408a476642a89fd6 \ - --hash=sha256:33d1c36b90e570ba7785dacd1faaf091203d9942bc036118fab8110a401eb1a8 \ - --hash=sha256:3e683ee4f5d0fa2dde4db77ed8dd8a876686e3fc417655c2ece9a90576905344 \ - --hash=sha256:3ffb4a8e7d46ed96ae48805746755fadd0909fea2306f93d5d8233ba23dda12a \ - --hash=sha256:40621d60d0e58aa573b68ac5e2d6b20d44392878e0bfc159012a5787c4e35bc8 \ - --hash=sha256:40f1e10d51c92859765522cbd79c5c8989f40f0419614bcdc5015e7b6bf97fc5 \ - --hash=sha256:45d42d132cff577c92bfba536aefcfea7e26efb975bd455db4e6602f5c9f45e7 \ - --hash=sha256:48488d999ed50ba8d38c581d67e496f955821dc183883550a6fbc7f1aefdc170 \ - --hash=sha256:4935dd7883f1d50e2ffecca0aa33dc1946a94c8f3fdafb8df5c330e48f71b132 \ - --hash=sha256:4c2d64fdba74ad16138300815cfdc6ab2f4647e23ced81f59e940d7d4a1469d9 \ - --hash=sha256:4c8817557d0de9349109acb38b9dd570b03cc5014e8aabf1cbddc6e81005becd \ - --hash=sha256:4ffaaac913c3f7345579db4f33b0020db693f302ca5137f106060316761beea9 \ - --hash=sha256:5a4cb365cb49b750bdb60b846b0c0bc49ed62e59a76635095a179d440540c346 \ - --hash=sha256:62fada2c942702ef8952754abfc1a9f7658a4d5460fabe95ac7ec2cbe0d02abc \ - --hash=sha256:67c519635a4f64e495c50e3107d9b4075aec33634272b5db1cde839e07367589 \ - --hash=sha256:6a54c43d3ec4cf2a39f4387ad044221c66a376e58c0d0e971d47c475ba79c6b5 \ - --hash=sha256:7044312a928a66a4c2a22644147bc61a199c1709712069a344a3fb5cfcf16915 \ - --hash=sha256:730d86af59e0e43ce277bb83970530dd223bf7f2a838e086b50affa6ec5f9295 \ - --hash=sha256:800100d45176652ded796134277ecb13640c1a537cad3b8b53da45aa96330453 \ - --hash=sha256:80fcbf3add8790caddfab6764bde258b5d09aefbe9169c183f88a7410f0f6dea \ - --hash=sha256:82b5dba6eb1bcc29cc305a18a3c5365d2af06ee71b123216416f7e20d2a84e5b \ - --hash=sha256:852dc840f6d7c985603e60b5deaae1d89c56cb038b577f6b5b8c808c97580f1d \ - --hash=sha256:8ad4ad1429cd4f315f32ef263c1342166695fad76c100c5d979c45d5570ed58b \ - --hash=sha256:8ae369e84466aa70f3154ee23c1451fda10a8ee1b63923ce76667e3077f2b0c4 \ - --hash=sha256:93e8248d650e7e9d49e8251f883eed60ecbc0e8ffd6349e18550925e31bd029b \ - --hash=sha256:973a371a55ce9ed333a3a0f8e0bcfae9e0d637711534bcb11e130af2ab9334e7 \ - --hash=sha256:9ba25a71ebf05b9bb0e2ae99f8bc08a07ee8e98c612175087112656ca0f5c8bf \ - --hash=sha256:a10860e00ded1dd0a65b83e717af28845bb7bd16d8ace40fe5531491de76b79f \ - --hash=sha256:a4792d3b3a6dfafefdf8e937f14906a51bd27025a36f4b188728a73382231d91 \ - --hash=sha256:a7420ceda262dbb4b8d839a4ec63d61c261e4e77677ed7c66c99f4e7cb5030dd \ - --hash=sha256:ad91738f14eb8da0ff82f2acd0098b6257621410dcbd4df20aaa5b4233d75a50 \ - --hash=sha256:b6a387d61fe41cdf7ea95b38e9af11cfb1a63499af2759444b99185c4ab33f5b \ - --hash=sha256:b954093679d5750495725ea6f88409946d69cfb25ea7b4c846eef5044194f583 \ - --hash=sha256:bbde71a705f8e9e4c3e9e33db69341d040c827c7afa6789b14c6e16776074f5a \ - --hash=sha256:beeebf760a9c1f4c07ef6a53465e8cfa776ea6a2021eda0d0417ec41043fe984 \ - --hash=sha256:c91b394f7601438ff79a4b93d16be92f216adb57d813a78be4446fe0f6bc2d8c \ - --hash=sha256:c97ff7fedf56d86bae92fa0a646ce1a0ec7509a7578e1ed238731ba13aabcd1c \ - --hash=sha256:cb53e2a99df28eee3b5f4fea166020d3ef9116fdc5764bc5117486e6d1211b25 \ - --hash=sha256:cbf445eb5628981a80f54087f9acdbf84f9b7d862756110d172993b9a5ae81aa \ - --hash=sha256:d06b24c686a34c86c8c1fba923181eae6b10565e4d80bdd7bc1c8e2f11247aa4 \ - --hash=sha256:d98e66a24497637dd31ccab090b34392dddb1f2f811c4b4cd80c230205c074a3 \ - --hash=sha256:db15ce28e1e127a0013dfb8ac243a8e392db8c61eae113337536edb28bdc1f97 \ - --hash=sha256:db842712984e91707437461930e6011e60b39136c7331e971952bb30465bc1a1 \ - --hash=sha256:e24bfe89c6ac4c31792793ad9f861b8f6dc4546ac6dc8f1c9083c7c4f2b335cd \ - --hash=sha256:e81c52638315ff4ac1b533d427f50bc0afc746deb949210bc85f05d4f15fd772 \ - --hash=sha256:e9393357f19954248b00bed7c56f29a25c930593a77630c719653d51e7669c2a \ - --hash=sha256:ee3941769bd2522fe39222206f6dd97ae83c442a94c90f2b7a25d847d40f4729 \ - --hash=sha256:f31ae06f1328595d762c9a2bf29dafd8621c7d3adc130cbb46278079758779ca \ - --hash=sha256:f94190df587738280d544971500b9cafc9b950d32efcb1fba9ac10d84e6aa4e6 \ - --hash=sha256:fa7d686ed9883f3d664d39d5a8e74d3c5f63e603c2e3ff0abcba23eac6542635 \ - --hash=sha256:fb532dd9900381d2e8f48172ddc5a59db4c445a11b9fab40b3b786da40d3b56b \ - --hash=sha256:fe32482b37b4b00c7a52a07211b479653b7fe4f22b2e481b9a9b099d8a430f2f - # via jinja2 -mdurl==0.1.2 \ - --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \ - --hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba - # via markdown-it-py -more-itertools==10.5.0 \ - --hash=sha256:037b0d3203ce90cca8ab1defbbdac29d5f993fc20131f3664dc8d6acfa872aef \ - --hash=sha256:5482bfef7849c25dc3c6dd53a6173ae4795da2a41a80faea6700d9f5846c5da6 - # via - # jaraco-classes - # jaraco-functools -nh3==0.2.18 \ - --hash=sha256:0411beb0589eacb6734f28d5497ca2ed379eafab8ad8c84b31bb5c34072b7164 \ - --hash=sha256:14c5a72e9fe82aea5fe3072116ad4661af5cf8e8ff8fc5ad3450f123e4925e86 \ - --hash=sha256:19aaba96e0f795bd0a6c56291495ff59364f4300d4a39b29a0abc9cb3774a84b \ - --hash=sha256:34c03fa78e328c691f982b7c03d4423bdfd7da69cd707fe572f544cf74ac23ad \ - --hash=sha256:36c95d4b70530b320b365659bb5034341316e6a9b30f0b25fa9c9eff4c27a204 \ - --hash=sha256:3a157ab149e591bb638a55c8c6bcb8cdb559c8b12c13a8affaba6cedfe51713a \ - --hash=sha256:42c64511469005058cd17cc1537578eac40ae9f7200bedcfd1fc1a05f4f8c200 \ - --hash=sha256:5f36b271dae35c465ef5e9090e1fdaba4a60a56f0bb0ba03e0932a66f28b9189 \ - --hash=sha256:6955369e4d9f48f41e3f238a9e60f9410645db7e07435e62c6a9ea6135a4907f \ - --hash=sha256:7b7c2a3c9eb1a827d42539aa64091640bd275b81e097cd1d8d82ef91ffa2e811 \ - --hash=sha256:8ce0f819d2f1933953fca255db2471ad58184a60508f03e6285e5114b6254844 \ - --hash=sha256:94a166927e53972a9698af9542ace4e38b9de50c34352b962f4d9a7d4c927af4 \ - --hash=sha256:a7f1b5b2c15866f2db413a3649a8fe4fd7b428ae58be2c0f6bca5eefd53ca2be \ - --hash=sha256:c8b3a1cebcba9b3669ed1a84cc65bf005728d2f0bc1ed2a6594a992e817f3a50 \ - --hash=sha256:de3ceed6e661954871d6cd78b410213bdcb136f79aafe22aa7182e028b8c7307 \ - --hash=sha256:f0eca9ca8628dbb4e916ae2491d72957fdd35f7a5d326b7032a345f111ac07fe - # via readme-renderer -nox==2024.10.9 \ - --hash=sha256:1d36f309a0a2a853e9bccb76bbef6bb118ba92fa92674d15604ca99adeb29eab \ - --hash=sha256:7aa9dc8d1c27e9f45ab046ffd1c3b2c4f7c91755304769df231308849ebded95 - # via -r requirements.in -packaging==24.1 \ - --hash=sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002 \ - --hash=sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124 - # via - # gcp-releasetool - # nox -pkginfo==1.10.0 \ - --hash=sha256:5df73835398d10db79f8eecd5cd86b1f6d29317589ea70796994d49399af6297 \ - --hash=sha256:889a6da2ed7ffc58ab5b900d888ddce90bce912f2d2de1dc1c26f4cb9fe65097 - # via twine -platformdirs==4.3.6 \ - --hash=sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907 \ - --hash=sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb - # via virtualenv -proto-plus==1.24.0 \ - --hash=sha256:30b72a5ecafe4406b0d339db35b56c4059064e69227b8c3bda7462397f966445 \ - --hash=sha256:402576830425e5f6ce4c2a6702400ac79897dab0b4343821aa5188b0fab81a12 - # via google-api-core -protobuf==5.28.2 \ - --hash=sha256:2c69461a7fcc8e24be697624c09a839976d82ae75062b11a0972e41fd2cd9132 \ - --hash=sha256:35cfcb15f213449af7ff6198d6eb5f739c37d7e4f1c09b5d0641babf2cc0c68f \ - --hash=sha256:52235802093bd8a2811abbe8bf0ab9c5f54cca0a751fdd3f6ac2a21438bffece \ - --hash=sha256:59379674ff119717404f7454647913787034f03fe7049cbef1d74a97bb4593f0 \ - --hash=sha256:5e8a95246d581eef20471b5d5ba010d55f66740942b95ba9b872d918c459452f \ - --hash=sha256:87317e9bcda04a32f2ee82089a204d3a2f0d3c8aeed16568c7daf4756e4f1fe0 \ - --hash=sha256:8ddc60bf374785fb7cb12510b267f59067fa10087325b8e1855b898a0d81d276 \ - --hash=sha256:a8b9403fc70764b08d2f593ce44f1d2920c5077bf7d311fefec999f8c40f78b7 \ - --hash=sha256:c0ea0123dac3399a2eeb1a1443d82b7afc9ff40241433296769f7da42d142ec3 \ - --hash=sha256:ca53faf29896c526863366a52a8f4d88e69cd04ec9571ed6082fa117fac3ab36 \ - --hash=sha256:eeea10f3dc0ac7e6b4933d32db20662902b4ab81bf28df12218aa389e9c2102d - # via - # gcp-docuploader - # gcp-releasetool - # google-api-core - # googleapis-common-protos - # proto-plus -pyasn1==0.6.1 \ - --hash=sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629 \ - --hash=sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034 - # via - # pyasn1-modules - # rsa -pyasn1-modules==0.4.1 \ - --hash=sha256:49bfa96b45a292b711e986f222502c1c9a5e1f4e568fc30e2574a6c7d07838fd \ - --hash=sha256:c28e2dbf9c06ad61c71a075c7e0f9fd0f1b0bb2d2ad4377f240d33ac2ab60a7c - # via google-auth -pycparser==2.22 \ - --hash=sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6 \ - --hash=sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc - # via cffi -pygments==2.18.0 \ - --hash=sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199 \ - --hash=sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a - # via - # readme-renderer - # rich -pyjwt==2.9.0 \ - --hash=sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850 \ - --hash=sha256:7e1e5b56cc735432a7369cbfa0efe50fa113ebecdc04ae6922deba8b84582d0c - # via gcp-releasetool -pyperclip==1.9.0 \ - --hash=sha256:b7de0142ddc81bfc5c7507eea19da920b92252b548b96186caf94a5e2527d310 - # via gcp-releasetool -python-dateutil==2.9.0.post0 \ - --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \ - --hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427 - # via gcp-releasetool -readme-renderer==44.0 \ - --hash=sha256:2fbca89b81a08526aadf1357a8c2ae889ec05fb03f5da67f9769c9a592166151 \ - --hash=sha256:8712034eabbfa6805cacf1402b4eeb2a73028f72d1166d6f5cb7f9c047c5d1e1 - # via twine -requests==2.32.3 \ - --hash=sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760 \ - --hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6 - # via - # gcp-releasetool - # google-api-core - # google-cloud-storage - # requests-toolbelt - # twine -requests-toolbelt==1.0.0 \ - --hash=sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6 \ - --hash=sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06 - # via twine -rfc3986==2.0.0 \ - --hash=sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd \ - --hash=sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c - # via twine -rich==13.9.2 \ - --hash=sha256:51a2c62057461aaf7152b4d611168f93a9fc73068f8ded2790f29fe2b5366d0c \ - --hash=sha256:8c82a3d3f8dcfe9e734771313e606b39d8247bb6b826e196f4914b333b743cf1 - # via twine -rsa==4.9 \ - --hash=sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7 \ - --hash=sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21 - # via google-auth -secretstorage==3.3.3 \ - --hash=sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77 \ - --hash=sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99 - # via keyring -six==1.16.0 \ - --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \ - --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 - # via - # gcp-docuploader - # python-dateutil -tomli==2.0.2 \ - --hash=sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38 \ - --hash=sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed - # via nox -twine==5.1.1 \ - --hash=sha256:215dbe7b4b94c2c50a7315c0275d2258399280fbb7d04182c7e55e24b5f93997 \ - --hash=sha256:9aa0825139c02b3434d913545c7b847a21c835e11597f5255842d457da2322db - # via -r requirements.in -typing-extensions==4.12.2 \ - --hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d \ - --hash=sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8 - # via - # -r requirements.in - # rich -urllib3==2.2.3 \ - --hash=sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac \ - --hash=sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9 - # via - # requests - # twine -virtualenv==20.26.6 \ - --hash=sha256:280aede09a2a5c317e409a00102e7077c6432c5a38f0ef938e643805a7ad2c48 \ - --hash=sha256:7345cc5b25405607a624d8418154577459c3e0277f5466dd79c49d5e492995f2 - # via nox -wheel==0.44.0 \ - --hash=sha256:2376a90c98cc337d18623527a97c31797bd02bad0033d41547043a1cbfbe448f \ - --hash=sha256:a29c3f2817e95ab89aa4660681ad547c0e9547f20e75b0562fe7723c9a2a9d49 - # via -r requirements.in -zipp==3.20.2 \ - --hash=sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350 \ - --hash=sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29 - # via importlib-metadata - -# The following packages are considered to be unsafe in a requirements file: -setuptools==75.1.0 \ - --hash=sha256:35ab7fd3bcd95e6b7fd704e4a1539513edad446c097797f2985e0e4b960772f2 \ - --hash=sha256:d59a21b17a275fb872a9c3dae73963160ae079f1049ed956880cd7c09b120538 - # via -r requirements.in From 8b9c5d9aa65fe276490be6382597ac37bc676df4 Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Tue, 20 May 2025 23:12:12 +0000 Subject: [PATCH 908/966] chore: update secret (#1764) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 8fa36e70b68c81f3fa76dbddb9048e7fa0290f2f..f54c3f987bfb67ce2a0b8dab99ef51a6f00104ad 100644 GIT binary patch literal 10324 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 literal 10324 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 Date: Wed, 21 May 2025 05:08:01 +0500 Subject: [PATCH 909/966] chore: remove sync response logs in AuthorizedSession (#1761) --- packages/google-auth/google/auth/transport/requests.py | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/google-auth/google/auth/transport/requests.py b/packages/google-auth/google/auth/transport/requests.py index 0540746f894a..2753912c6d91 100644 --- a/packages/google-auth/google/auth/transport/requests.py +++ b/packages/google-auth/google/auth/transport/requests.py @@ -545,7 +545,6 @@ def request( timeout=timeout, **kwargs ) - _helpers.response_log(_LOGGER, response) remaining_time = guard.remaining_timeout # If the response indicated that the credentials needed to be From e1bcc401bc4ef3f352b339a1133fcfb35ab154d9 Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Wed, 21 May 2025 00:51:38 +0000 Subject: [PATCH 910/966] fix: Update test to consider new error message from cryptography (#1765) --- packages/google-auth/tests/test__service_account_info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/tests/test__service_account_info.py b/packages/google-auth/tests/test__service_account_info.py index 4fa85a5992b3..be2657074803 100644 --- a/packages/google-auth/tests/test__service_account_info.py +++ b/packages/google-auth/tests/test__service_account_info.py @@ -53,7 +53,7 @@ def test_from_dict_bad_private_key(): with pytest.raises(ValueError) as excinfo: _service_account_info.from_dict(info) - assert excinfo.match(r"key") + assert excinfo.match(r"(?i)(key|PEM)") def test_from_dict_bad_format(): From 9a2f188761dbc875c46209f177565fae195457ee Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Wed, 21 May 2025 10:56:53 -0700 Subject: [PATCH 911/966] chore(main): release 2.40.2 (#1766) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 8 ++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index c43ab2b1c7e7..db4f1a58bc77 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,14 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.40.2](https://github.com/googleapis/google-auth-library-python/compare/v2.40.1...v2.40.2) (2025-05-21) + + +### Bug Fixes + +* Remove sync response logs in AuthorizedSession ([97ed1c8](https://github.com/googleapis/google-auth-library-python/commit/97ed1c8ef1a797af26c5639b618aa26360e9d868)) +* Update test to consider new error message from cryptography ([#1765](https://github.com/googleapis/google-auth-library-python/issues/1765)) ([44e38b6](https://github.com/googleapis/google-auth-library-python/commit/44e38b60002f9dbd524b1fe82fa8d4295afc68bc)) + ## [2.40.1](https://github.com/googleapis/google-auth-library-python/compare/v2.40.0...v2.40.1) (2025-05-06) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index ee0bb12609d1..13257b95f80b 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.40.1" +__version__ = "2.40.2" From cdc34a788936e2d9410b9ddf1d79b41aad4239a7 Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Wed, 21 May 2025 22:52:56 +0000 Subject: [PATCH 912/966] fix: retry 504 errors (#1767) --- packages/google-auth/google/auth/transport/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/google-auth/google/auth/transport/__init__.py b/packages/google-auth/google/auth/transport/__init__.py index 724568e5828e..4575763500b2 100644 --- a/packages/google-auth/google/auth/transport/__init__.py +++ b/packages/google-auth/google/auth/transport/__init__.py @@ -30,6 +30,7 @@ DEFAULT_RETRYABLE_STATUS_CODES = ( http_client.INTERNAL_SERVER_ERROR, http_client.SERVICE_UNAVAILABLE, + http_client.GATEWAY_TIMEOUT, http_client.REQUEST_TIMEOUT, http_client.TOO_MANY_REQUESTS, ) From b039156e37f2adcc6671223dd5fc299f63491779 Mon Sep 17 00:00:00 2001 From: Harkamal Jot Singh Kumar Date: Fri, 23 May 2025 14:37:19 -0700 Subject: [PATCH 913/966] fix: remove unnecessary call to mds service (#1769) --- .../google/auth/compute_engine/credentials.py | 20 ----- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../system_tests_sync/test_compute_engine.py | 6 +- .../tests/compute_engine/test_credentials.py | 77 ++---------------- 4 files changed, 10 insertions(+), 93 deletions(-) diff --git a/packages/google-auth/google/auth/compute_engine/credentials.py b/packages/google-auth/google/auth/compute_engine/credentials.py index f0126c0a80ef..74f12e7cc769 100644 --- a/packages/google-auth/google/auth/compute_engine/credentials.py +++ b/packages/google-auth/google/auth/compute_engine/credentials.py @@ -87,25 +87,6 @@ def __init__( self._universe_domain = universe_domain self._universe_domain_cached = True - def _retrieve_info(self, request): - """Retrieve information about the service account. - - Updates the scopes and retrieves the full service account email. - - Args: - request (google.auth.transport.Request): The object used to make - HTTP requests. - """ - info = _metadata.get_service_account_info( - request, service_account=self._service_account_email - ) - - self._service_account_email = info["email"] - - # Don't override scopes requested by the user. - if self._scopes is None: - self._scopes = info["scopes"] - def _metric_header_for_usage(self): return metrics.CRED_TYPE_SA_MDS @@ -123,7 +104,6 @@ def refresh(self, request): """ scopes = self._scopes if self._scopes is not None else self._default_scopes try: - self._retrieve_info(request) self.token, self.expiry = _metadata.get_service_account_token( request, service_account=self._service_account_email, scopes=scopes ) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index f54c3f987bfb67ce2a0b8dab99ef51a6f00104ad..c19e8785acd4dc496707e68ccea9c2f61430b0aa 100644 GIT binary patch literal 10324 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 literal 10324 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 diff --git a/packages/google-auth/system_tests/system_tests_sync/test_compute_engine.py b/packages/google-auth/system_tests/system_tests_sync/test_compute_engine.py index 1e0eaf11db9f..2ac1be5921fe 100644 --- a/packages/google-auth/system_tests/system_tests_sync/test_compute_engine.py +++ b/packages/google-auth/system_tests/system_tests_sync/test_compute_engine.py @@ -35,7 +35,7 @@ def check_gce_environment(http_request): pytest.skip("Compute Engine metadata service is not available.") -def test_refresh(http_request, token_info): +def test_refresh(http_request): credentials = compute_engine.Credentials() credentials.refresh(http_request) @@ -43,9 +43,7 @@ def test_refresh(http_request, token_info): assert credentials.token is not None assert credentials.service_account_email is not None - info = token_info(credentials.token) - info_scopes = _helpers.string_to_scopes(info["scope"]) - assert set(info_scopes) == set(credentials.scopes) + assert credentials.scopes is None def test_default(verify_refresh): diff --git a/packages/google-auth/tests/compute_engine/test_credentials.py b/packages/google-auth/tests/compute_engine/test_credentials.py index fddfb7f64d34..8485ece4b712 100644 --- a/packages/google-auth/tests/compute_engine/test_credentials.py +++ b/packages/google-auth/tests/compute_engine/test_credentials.py @@ -99,18 +99,7 @@ def test_default_state(self): ) @mock.patch("google.auth.compute_engine._metadata.get", autospec=True) def test_refresh_success(self, get, utcnow): - get.side_effect = [ - { - # First request is for sevice account info. - "email": "service-account@example.com", - "scopes": ["one", "two"], - }, - { - # Second request is for the token. - "access_token": "token", - "expires_in": 500, - }, - ] + get.side_effect = [{"access_token": "token", "expires_in": 500}] # Refresh credentials self.credentials.refresh(None) @@ -120,8 +109,8 @@ def test_refresh_success(self, get, utcnow): assert self.credentials.expiry == (utcnow() + datetime.timedelta(seconds=500)) # Check the credential info - assert self.credentials.service_account_email == "service-account@example.com" - assert self.credentials._scopes == ["one", "two"] + assert self.credentials.service_account_email == "default" + assert self.credentials._scopes is None # Check that the credentials are valid (have a token and are not # expired) @@ -137,18 +126,7 @@ def test_refresh_success(self, get, utcnow): ) @mock.patch("google.auth.compute_engine._metadata.get", autospec=True) def test_refresh_success_with_scopes(self, get, utcnow, mock_metrics_header_value): - get.side_effect = [ - { - # First request is for sevice account info. - "email": "service-account@example.com", - "scopes": ["one", "two"], - }, - { - # Second request is for the token. - "access_token": "token", - "expires_in": 500, - }, - ] + get.side_effect = [{"access_token": "token", "expires_in": 500}] # Refresh credentials scopes = ["three", "four"] @@ -160,7 +138,7 @@ def test_refresh_success_with_scopes(self, get, utcnow, mock_metrics_header_valu assert self.credentials.expiry == (utcnow() + datetime.timedelta(seconds=500)) # Check the credential info - assert self.credentials.service_account_email == "service-account@example.com" + assert self.credentials.service_account_email == "default" assert self.credentials._scopes == scopes # Check that the credentials are valid (have a token and are not @@ -184,18 +162,7 @@ def test_refresh_error(self, get): @mock.patch("google.auth.compute_engine._metadata.get", autospec=True) def test_before_request_refreshes(self, get): - get.side_effect = [ - { - # First request is for sevice account info. - "email": "service-account@example.com", - "scopes": "one two", - }, - { - # Second request is for the token. - "access_token": "token", - "expires_in": 500, - }, - ] + get.side_effect = [{"access_token": "token", "expires_in": 500}] # Credentials should start as invalid assert not self.credentials.valid @@ -473,20 +440,6 @@ def test_with_target_audience_integration(self): have been mocked. """ - # mock information about credentials - responses.add( - responses.GET, - "http://metadata.google.internal/computeMetadata/v1/instance/" - "service-accounts/default/?recursive=true", - status=200, - content_type="application/json", - json={ - "scopes": "email", - "email": "service-account@example.com", - "aliases": ["default"], - }, - ) - # mock information about universe_domain responses.add( responses.GET, @@ -501,7 +454,7 @@ def test_with_target_audience_integration(self): responses.add( responses.GET, "http://metadata.google.internal/computeMetadata/v1/instance/" - "service-accounts/service-account@example.com/token", + "service-accounts/default/token", status=200, content_type="application/json", json={ @@ -641,25 +594,11 @@ def test_with_quota_project_integration(self): have been mocked. """ - # mock information about credentials - responses.add( - responses.GET, - "http://metadata.google.internal/computeMetadata/v1/instance/" - "service-accounts/default/?recursive=true", - status=200, - content_type="application/json", - json={ - "scopes": "email", - "email": "service-account@example.com", - "aliases": ["default"], - }, - ) - # mock token for credentials responses.add( responses.GET, "http://metadata.google.internal/computeMetadata/v1/instance/" - "service-accounts/service-account@example.com/token", + "service-accounts/default/token", status=200, content_type="application/json", json={ From 4125dd8ee0274c138b2cb8bef0e9f3f2d7880158 Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Wed, 28 May 2025 21:35:39 +0000 Subject: [PATCH 914/966] chore: update secret (#1774) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index c19e8785acd4dc496707e68ccea9c2f61430b0aa..9a188ee8ef41234b1a3b94702371315d06ea4265 100644 GIT binary patch literal 10324 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 From 48b356eb0916b22e79973fd2a69dd1c486b05391 Mon Sep 17 00:00:00 2001 From: Harkamal Jot Singh Kumar Date: Tue, 3 Jun 2025 14:37:40 -0700 Subject: [PATCH 915/966] Revert "fix: remove unnecessary call to mds service (#1769)" (#1777) This reverts commit 7c61c7d0a42ceec3eab693065745a74f524acab0. --- .../google/auth/compute_engine/credentials.py | 20 +++++ .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../system_tests_sync/test_compute_engine.py | 6 +- .../tests/compute_engine/test_credentials.py | 77 ++++++++++++++++-- 4 files changed, 93 insertions(+), 10 deletions(-) diff --git a/packages/google-auth/google/auth/compute_engine/credentials.py b/packages/google-auth/google/auth/compute_engine/credentials.py index 74f12e7cc769..f0126c0a80ef 100644 --- a/packages/google-auth/google/auth/compute_engine/credentials.py +++ b/packages/google-auth/google/auth/compute_engine/credentials.py @@ -87,6 +87,25 @@ def __init__( self._universe_domain = universe_domain self._universe_domain_cached = True + def _retrieve_info(self, request): + """Retrieve information about the service account. + + Updates the scopes and retrieves the full service account email. + + Args: + request (google.auth.transport.Request): The object used to make + HTTP requests. + """ + info = _metadata.get_service_account_info( + request, service_account=self._service_account_email + ) + + self._service_account_email = info["email"] + + # Don't override scopes requested by the user. + if self._scopes is None: + self._scopes = info["scopes"] + def _metric_header_for_usage(self): return metrics.CRED_TYPE_SA_MDS @@ -104,6 +123,7 @@ def refresh(self, request): """ scopes = self._scopes if self._scopes is not None else self._default_scopes try: + self._retrieve_info(request) self.token, self.expiry = _metadata.get_service_account_token( request, service_account=self._service_account_email, scopes=scopes ) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 9a188ee8ef41234b1a3b94702371315d06ea4265..4e56a64044aa2319458f7ecfef3c1f80f456da7e 100644 GIT binary patch literal 10324 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 literal 10324 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 Date: Tue, 3 Jun 2025 15:43:01 -0700 Subject: [PATCH 916/966] fix: auth fetch token from default endpoint (#1779) --- .../google-auth/google/auth/compute_engine/credentials.py | 3 ++- packages/google-auth/tests/compute_engine/test_credentials.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/google-auth/google/auth/compute_engine/credentials.py b/packages/google-auth/google/auth/compute_engine/credentials.py index f0126c0a80ef..eb50d288b6a0 100644 --- a/packages/google-auth/google/auth/compute_engine/credentials.py +++ b/packages/google-auth/google/auth/compute_engine/credentials.py @@ -124,8 +124,9 @@ def refresh(self, request): scopes = self._scopes if self._scopes is not None else self._default_scopes try: self._retrieve_info(request) + # Always fetch token with default service account email. self.token, self.expiry = _metadata.get_service_account_token( - request, service_account=self._service_account_email, scopes=scopes + request, service_account="default", scopes=scopes ) except exceptions.TransportError as caught_exc: new_exc = exceptions.RefreshError(caught_exc) diff --git a/packages/google-auth/tests/compute_engine/test_credentials.py b/packages/google-auth/tests/compute_engine/test_credentials.py index fddfb7f64d34..03fe845b1f32 100644 --- a/packages/google-auth/tests/compute_engine/test_credentials.py +++ b/packages/google-auth/tests/compute_engine/test_credentials.py @@ -501,7 +501,7 @@ def test_with_target_audience_integration(self): responses.add( responses.GET, "http://metadata.google.internal/computeMetadata/v1/instance/" - "service-accounts/service-account@example.com/token", + "service-accounts/default/token", status=200, content_type="application/json", json={ @@ -659,7 +659,7 @@ def test_with_quota_project_integration(self): responses.add( responses.GET, "http://metadata.google.internal/computeMetadata/v1/instance/" - "service-accounts/service-account@example.com/token", + "service-accounts/default/token", status=200, content_type="application/json", json={ From 20f38f490271d2483b18aa9f5928f268939bf906 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Wed, 4 Jun 2025 10:58:57 -0700 Subject: [PATCH 917/966] chore(main): release 2.40.3 (#1768) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 9 +++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index db4f1a58bc77..a9880be63bda 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,15 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.40.3](https://github.com/googleapis/google-auth-library-python/compare/v2.40.2...v2.40.3) (2025-06-04) + + +### Bug Fixes + +* Auth fetch token from default endpoint ([#1779](https://github.com/googleapis/google-auth-library-python/issues/1779)) ([88891cc](https://github.com/googleapis/google-auth-library-python/commit/88891cc596640b0bb3a2891532e2d32f2c9f0ec3)) +* Remove unnecessary call to mds service ([#1769](https://github.com/googleapis/google-auth-library-python/issues/1769)) ([7c61c7d](https://github.com/googleapis/google-auth-library-python/commit/7c61c7d0a42ceec3eab693065745a74f524acab0)) +* Retry 504 errors ([#1767](https://github.com/googleapis/google-auth-library-python/issues/1767)) ([554f967](https://github.com/googleapis/google-auth-library-python/commit/554f967620da2b02e5d44ac7463dcc2407ace5dd)) + ## [2.40.2](https://github.com/googleapis/google-auth-library-python/compare/v2.40.1...v2.40.2) (2025-05-21) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 13257b95f80b..318bf723b432 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.40.2" +__version__ = "2.40.3" From 3f7a1eb5fbd127a24ea18aa0968c950d2aaa7cb0 Mon Sep 17 00:00:00 2001 From: Harkamal Jot Singh Kumar Date: Tue, 15 Jul 2025 08:23:07 -0700 Subject: [PATCH 918/966] chore: update secrets (#1795) * update secrets * 1 * 2 --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 4e56a64044aa2319458f7ecfef3c1f80f456da7e..c65a1891988439b1e64a7d24fe72f8ee89466ee2 100644 GIT binary patch literal 10324 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 literal 10324 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 From 52d840a8398c26e31c386960895d6e9484545208 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Tue, 15 Jul 2025 11:53:54 -0400 Subject: [PATCH 919/966] tests: update default runtime used for tests (#1788) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * tests: update default runtime used for tests * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * also disable 3.7/3.8 samples tests * revert * specify sessions for Kokoro job * update secrets --------- Co-authored-by: Owl Bot Co-authored-by: Harkamal Jot Singh Kumar --- .../{system-3.7.cfg => system-3.10.cfg} | 0 packages/google-auth/noxfile.py | 35 ++++++++++++++++--- packages/google-auth/system_tests/noxfile.py | 4 +-- 3 files changed, 32 insertions(+), 7 deletions(-) rename packages/google-auth/.kokoro/presubmit/{system-3.7.cfg => system-3.10.cfg} (100%) diff --git a/packages/google-auth/.kokoro/presubmit/system-3.7.cfg b/packages/google-auth/.kokoro/presubmit/system-3.10.cfg similarity index 100% rename from packages/google-auth/.kokoro/presubmit/system-3.7.cfg rename to packages/google-auth/.kokoro/presubmit/system-3.10.cfg diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index 1071be0ad557..728e8c7cce01 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -33,8 +33,33 @@ "docs/conf.py", ] +DEFAULT_PYTHON_VERSION = "3.10" +# TODO(https://github.com/googleapis/google-auth-library-python/issues/1787): +# Remove or restore testing for Python 3.7/3.8 +UNIT_TEST_PYTHON_VERSIONS = ["3.9", "3.10", "3.11", "3.12", "3.13"] + +# Error if a python version is missing +nox.options.error_on_missing_interpreters = True + +# pypy will be run as a github action instead of through Kokoro +nox.options.sessions = [ + "lint", + "blacken", + "mypy", + # TODO(https://github.com/googleapis/google-auth-library-python/issues/1787): + # Remove or restore testing for Python 3.7/3.8 + "unit-3.9", + "unit-3.10", + "unit-3.11", + "unit-3.12", + "unit-3.13", + # cover must be last to avoid error `No data to report` + "cover", + "docs", +] + -@nox.session(python="3.8") +@nox.session(python=DEFAULT_PYTHON_VERSION) def lint(session): session.install( "flake8", "flake8-import-order", "docutils", CLICK_VERSION, BLACK_VERSION @@ -54,7 +79,7 @@ def lint(session): ) -@nox.session(python="3.8") +@nox.session(python=DEFAULT_PYTHON_VERSION) def blacken(session): """Run black. Format code to uniform standard. @@ -67,7 +92,7 @@ def blacken(session): session.run("black", *BLACK_PATHS) -@nox.session(python="3.8") +@nox.session(python=DEFAULT_PYTHON_VERSION) def mypy(session): """Verify type hints are mypy compatible.""" session.install("-e", ".") @@ -84,7 +109,7 @@ def mypy(session): session.run("mypy", "-p", "google", "-p", "tests", "-p", "tests_async") -@nox.session(python=["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]) +@nox.session(python=UNIT_TEST_PYTHON_VERSIONS) def unit(session): constraints_path = str( CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt" @@ -102,7 +127,7 @@ def unit(session): ) -@nox.session(python="3.8") +@nox.session(python=DEFAULT_PYTHON_VERSION) def cover(session): session.install("-e", ".[testing]") session.run( diff --git a/packages/google-auth/system_tests/noxfile.py b/packages/google-auth/system_tests/noxfile.py index 56d39224d60c..8630f9cedb2a 100644 --- a/packages/google-auth/system_tests/noxfile.py +++ b/packages/google-auth/system_tests/noxfile.py @@ -163,8 +163,8 @@ def configure_cloud_sdk(session, application_default_credentials, project=False) TEST_DEPENDENCIES_ASYNC = ["aiohttp", "pytest-asyncio", "nest-asyncio", "mock"] TEST_DEPENDENCIES_SYNC = ["pytest", "requests", "mock", "pyjwt"] -PYTHON_VERSIONS_ASYNC = ["3.7"] -PYTHON_VERSIONS_SYNC = ["3.7"] +PYTHON_VERSIONS_ASYNC = ["3.10"] +PYTHON_VERSIONS_SYNC = ["3.10"] def default(session, *test_paths): From cc108659fcf726a11786c1849c84e06294613d64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yannick=20P=C3=89ROUX?= Date: Tue, 15 Jul 2025 19:36:55 +0200 Subject: [PATCH 920/966] feat: add support for cachetools 6.0 (#1773) --- packages/google-auth/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 3874354fded0..20f79ce66974 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -20,7 +20,7 @@ DEPENDENCIES = ( - "cachetools>=2.0.0,<6.0", + "cachetools>=2.0.0,<7.0", "pyasn1-modules>=0.2.1", # rsa==4.5 is the last version to support 2.7 # https://github.com/sybrenstuvel/python-rsa/issues/152#issuecomment-643470233 From d44e1bbb667e65872e13105cc5128f76e7605a30 Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Wed, 6 Aug 2025 09:58:06 -0700 Subject: [PATCH 921/966] chore: update secret (#1801) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index c65a1891988439b1e64a7d24fe72f8ee89466ee2..528481fce726de87f6c7f249713f524084ed4689 100644 GIT binary patch literal 10324 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 literal 10324 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 From 3ebfb7ed7604a03d9b57c61aa8f7c87379aebf53 Mon Sep 17 00:00:00 2001 From: nbayati <99771966+nbayati@users.noreply.github.com> Date: Thu, 7 Aug 2025 12:42:59 -0700 Subject: [PATCH 922/966] feat: Add trust boundary support for service accounts and impersonation. (#1778) * feat: adds trust boundary lookup support for SA and impersonated credentials * Add feature flag, fix ud bug, and update no-op response. * Add _build_trust_boundary_lookup_url to external account. * Implement additional unit tests for the trust boundary * implement trust boundary in compute_engine to support GCE instances. * Fix failing unit test, and change acceptable values for the env variable * Add unit tests for gce trust boundary. * Use no op method instead of directly comparing values * fix a typo in calling the method noop * Add x-allowed-location header to all IAM requests. * Support self-signed jwt and refactor refresh to handle refreshing trust boundary in the base class. * Fix failing unit tests * Revert changes to external account class file. * Additional unit tests and update some old ones. * Revert changes to idtoken * Revert the self signed jwt token workaround * revert idtoken and jwt trust boundary tests. * Fix failing github check * Fix failing unit tests in compute engine * fix linter issues * Fix linter issues * remove trust boundary related code from idtoken as it is not a supported credential type * remove unused line in test --- packages/google-auth/google/auth/_helpers.py | 41 ++ .../google/auth/compute_engine/credentials.py | 73 +++- .../google-auth/google/auth/credentials.py | 179 +++++++- .../google/auth/environment_vars.py | 4 + .../google/auth/external_account.py | 18 +- .../google/auth/impersonated_credentials.py | 50 ++- packages/google-auth/google/oauth2/_client.py | 117 ++++++ .../google/oauth2/service_account.py | 43 +- .../tests/compute_engine/test_credentials.py | 384 +++++++++++++++++- .../google-auth/tests/oauth2/test__client.py | 177 +++++++- .../tests/oauth2/test_service_account.py | 293 ++++++++++++- packages/google-auth/tests/test__helpers.py | 29 +- packages/google-auth/tests/test_aws.py | 10 +- .../google-auth/tests/test_credentials.py | 186 +++++++-- .../tests/test_external_account.py | 52 +-- .../google-auth/tests/test_identity_pool.py | 6 +- .../tests/test_impersonated_credentials.py | 302 +++++++++++++- 17 files changed, 1840 insertions(+), 124 deletions(-) diff --git a/packages/google-auth/google/auth/_helpers.py b/packages/google-auth/google/auth/_helpers.py index 78fe22f72644..fba0ba3faf80 100644 --- a/packages/google-auth/google/auth/_helpers.py +++ b/packages/google-auth/google/auth/_helpers.py @@ -21,6 +21,7 @@ import hashlib import json import logging +import os import sys from typing import Any, Dict, Mapping, Optional, Union import urllib @@ -287,6 +288,46 @@ def unpadded_urlsafe_b64encode(value): return base64.urlsafe_b64encode(value).rstrip(b"=") +def get_bool_from_env(variable_name, default=False): + """Gets a boolean value from an environment variable. + + The environment variable is interpreted as a boolean with the following + (case-insensitive) rules: + - "true", "1" are considered true. + - "false", "0" are considered false. + Any other values will raise an exception. + + Args: + variable_name (str): The name of the environment variable. + default (bool): The default value if the environment variable is not + set. + + Returns: + bool: The boolean value of the environment variable. + + Raises: + google.auth.exceptions.InvalidValue: If the environment variable is + set to a value that can not be interpreted as a boolean. + """ + value = os.environ.get(variable_name) + + if value is None: + return default + + value = value.lower() + + if value in ("true", "1"): + return True + elif value in ("false", "0"): + return False + else: + raise exceptions.InvalidValue( + 'Environment variable "{}" must be one of "true", "false", "1", or "0".'.format( + variable_name + ) + ) + + def is_python_3(): """Check if the Python interpreter is Python 2 or 3. diff --git a/packages/google-auth/google/auth/compute_engine/credentials.py b/packages/google-auth/google/auth/compute_engine/credentials.py index eb50d288b6a0..0f518166abc5 100644 --- a/packages/google-auth/google/auth/compute_engine/credentials.py +++ b/packages/google-auth/google/auth/compute_engine/credentials.py @@ -30,11 +30,16 @@ from google.auth.compute_engine import _metadata from google.oauth2 import _client +_TRUST_BOUNDARY_LOOKUP_ENDPOINT = ( + "https://iamcredentials.{}/v1/projects/-/serviceAccounts/{}/allowedLocations" +) + class Credentials( credentials.Scoped, credentials.CredentialsWithQuotaProject, credentials.CredentialsWithUniverseDomain, + credentials.CredentialsWithTrustBoundary, ): """Compute Engine Credentials. @@ -61,6 +66,7 @@ def __init__( scopes=None, default_scopes=None, universe_domain=None, + trust_boundary=None, ): """ Args: @@ -76,6 +82,7 @@ def __init__( provided or None, credential will attempt to fetch the value from metadata server. If metadata server doesn't have universe domain endpoint, then the default googleapis.com will be used. + trust_boundary (Mapping[str,str]): A credential trust boundary. """ super(Credentials, self).__init__() self._service_account_email = service_account_email @@ -86,6 +93,7 @@ def __init__( if universe_domain: self._universe_domain = universe_domain self._universe_domain_cached = True + self._trust_boundary = trust_boundary def _retrieve_info(self, request): """Retrieve information about the service account. @@ -100,16 +108,22 @@ def _retrieve_info(self, request): request, service_account=self._service_account_email ) + if not info or "email" not in info: + raise exceptions.RefreshError( + "Unexpected response from metadata server: " + "service account info is missing 'email' field." + ) + self._service_account_email = info["email"] # Don't override scopes requested by the user. if self._scopes is None: - self._scopes = info["scopes"] + self._scopes = info.get("scopes") def _metric_header_for_usage(self): return metrics.CRED_TYPE_SA_MDS - def refresh(self, request): + def _refresh_token(self, request): """Refresh the access token and scopes. Args: @@ -132,6 +146,37 @@ def refresh(self, request): new_exc = exceptions.RefreshError(caught_exc) raise new_exc from caught_exc + def _build_trust_boundary_lookup_url(self): + """Builds and returns the URL for the trust boundary lookup API for GCE.""" + # If the service account email is 'default', we need to get the + # actual email address from the metadata server. + if self._service_account_email == "default": + from google.auth.transport import requests as google_auth_requests + + request = google_auth_requests.Request() + try: + info = _metadata.get_service_account_info(request, "default") + if not info or "email" not in info: + raise exceptions.RefreshError( + "Unexpected response from metadata server: " + "service account info is missing 'email' field." + ) + self._service_account_email = info["email"] + + except exceptions.TransportError as e: + # If fetching the service account email fails due to a transport error, + # it means we cannot build the trust boundary lookup URL. + # Wrap this in a RefreshError so it's caught by _refresh_trust_boundary. + raise exceptions.RefreshError( + "Failed to get service account email for trust boundary lookup: {}".format( + e + ) + ) from e + + return _TRUST_BOUNDARY_LOOKUP_ENDPOINT.format( + self.universe_domain, self.service_account_email + ) + @property def service_account_email(self): """The service account email. @@ -173,8 +218,9 @@ def with_quota_project(self, quota_project_id): quota_project_id=quota_project_id, scopes=self._scopes, default_scopes=self._default_scopes, + universe_domain=self._universe_domain, + trust_boundary=self._trust_boundary, ) - creds._universe_domain = self._universe_domain creds._universe_domain_cached = self._universe_domain_cached return creds @@ -188,8 +234,9 @@ def with_scopes(self, scopes, default_scopes=None): default_scopes=default_scopes, service_account_email=self._service_account_email, quota_project_id=self._quota_project_id, + universe_domain=self._universe_domain, + trust_boundary=self._trust_boundary, ) - creds._universe_domain = self._universe_domain creds._universe_domain_cached = self._universe_domain_cached return creds @@ -200,9 +247,23 @@ def with_universe_domain(self, universe_domain): default_scopes=self._default_scopes, service_account_email=self._service_account_email, quota_project_id=self._quota_project_id, + trust_boundary=self._trust_boundary, universe_domain=universe_domain, ) + @_helpers.copy_docstring(credentials.CredentialsWithTrustBoundary) + def with_trust_boundary(self, trust_boundary): + creds = self.__class__( + service_account_email=self._service_account_email, + quota_project_id=self._quota_project_id, + scopes=self._scopes, + default_scopes=self._default_scopes, + universe_domain=self._universe_domain, + trust_boundary=trust_boundary, + ) + creds._universe_domain_cached = self._universe_domain_cached + return creds + _DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds _DEFAULT_TOKEN_URI = "https://www.googleapis.com/oauth2/v4/token" @@ -275,7 +336,7 @@ def __init__( if use_metadata_identity_endpoint: if token_uri or additional_claims or service_account_email or signer: - raise exceptions.MalformedError( + raise ValueError( "If use_metadata_identity_endpoint is set, token_uri, " "additional_claims, service_account_email, signer arguments" " must not be set" @@ -366,7 +427,7 @@ def with_token_uri(self, token_uri): # since the signer is already instantiated, # the request is not needed if self._use_metadata_identity_endpoint: - raise exceptions.MalformedError( + raise ValueError( "If use_metadata_identity_endpoint is set, token_uri" " must not be set" ) else: diff --git a/packages/google-auth/google/auth/credentials.py b/packages/google-auth/google/auth/credentials.py index 2c67e04432a4..6ed94fe70091 100644 --- a/packages/google-auth/google/auth/credentials.py +++ b/packages/google-auth/google/auth/credentials.py @@ -23,9 +23,12 @@ from google.auth import exceptions from google.auth import metrics from google.auth._credentials_base import _BaseCredentials +from google.auth._default import _LOGGER from google.auth._refresh_worker import RefreshThreadManager DEFAULT_UNIVERSE_DOMAIN = "googleapis.com" +NO_OP_TRUST_BOUNDARY_LOCATIONS: list[str] = [] +NO_OP_TRUST_BOUNDARY_ENCODED_LOCATIONS = "0x0" class Credentials(_BaseCredentials): @@ -178,22 +181,7 @@ def apply(self, headers, token=None): token (Optional[str]): If specified, overrides the current access token. """ - self._apply(headers, token=token) - """Trust boundary value will be a cached value from global lookup. - - The response of trust boundary will be a list of regions and a hex - encoded representation. - - An example of global lookup response: - { - "locations": [ - "us-central1", "us-east1", "europe-west1", "asia-east1" - ] - "encoded_locations": "0xA30" - } - """ - if self._trust_boundary is not None: - headers["x-allowed-locations"] = self._trust_boundary["encoded_locations"] + self._apply(headers, token) if self.quota_project_id: headers["x-goog-user-project"] = self.quota_project_id @@ -299,6 +287,162 @@ def with_universe_domain(self, universe_domain): ) +class CredentialsWithTrustBoundary(Credentials): + """Abstract base for credentials supporting ``with_trust_boundary`` factory""" + + @abc.abstractmethod + def _refresh_token(self, request): + """Refreshes the access token. + + Args: + request (google.auth.transport.Request): The object used to make + HTTP requests. + + Raises: + google.auth.exceptions.RefreshError: If the credentials could + not be refreshed. + """ + raise NotImplementedError("_refresh_token must be implemented") + + def with_trust_boundary(self, trust_boundary): + """Returns a copy of these credentials with a modified trust boundary. + + Args: + trust_boundary Mapping[str, str]: The trust boundary to use for the + credential. This should be a map with a "locations" key that maps to + a list of GCP regions, and a "encodedLocations" key that maps to a + hex string. + + Returns: + google.auth.credentials.Credentials: A new credentials instance. + """ + raise NotImplementedError("This credential does not support trust boundaries.") + + def _is_trust_boundary_lookup_required(self): + """Checks if a trust boundary lookup is required. + + A lookup is required if the feature is enabled via an environment + variable, the universe domain is supported, and a no-op boundary + is not already cached. + + Returns: + bool: True if a trust boundary lookup is required, False otherwise. + """ + # 1. Check if the feature is enabled via environment variable. + if not _helpers.get_bool_from_env( + environment_vars.GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED, default=False + ): + return False + + # 2. Skip trust boundary flow for non-default universe domains. + if self.universe_domain != DEFAULT_UNIVERSE_DOMAIN: + return False + + # 3. Do not trigger refresh if credential has a cached no-op trust boundary. + return not self._has_no_op_trust_boundary() + + def _get_trust_boundary_header(self): + if self._trust_boundary is not None: + if self._has_no_op_trust_boundary(): + # STS expects an empty string if the trust boundary value is no-op. + return {"x-allowed-locations": ""} + else: + return {"x-allowed-locations": self._trust_boundary["encodedLocations"]} + return {} + + def apply(self, headers, token=None): + """Apply the token to the authentication header.""" + super().apply(headers, token) + headers.update(self._get_trust_boundary_header()) + + def refresh(self, request): + """Refreshes the access token and the trust boundary. + + This method calls the subclass's token refresh logic and then + refreshes the trust boundary if applicable. + """ + self._refresh_token(request) + self._refresh_trust_boundary(request) + + def _refresh_trust_boundary(self, request): + """Triggers a refresh of the trust boundary and updates the cache if necessary. + + Args: + request (google.auth.transport.Request): The object used to make + HTTP requests. + + Raises: + google.auth.exceptions.RefreshError: If the trust boundary could + not be refreshed and no cached value is available. + """ + if not self._is_trust_boundary_lookup_required(): + return + try: + self._trust_boundary = self._lookup_trust_boundary(request) + except exceptions.RefreshError as error: + # If the call to the lookup API failed, check if there is a trust boundary + # already cached. If there is, do nothing. If not, then throw the error. + if self._trust_boundary is None: + raise error + if _helpers.is_logging_enabled(_LOGGER): + _LOGGER.debug( + "Using cached trust boundary due to refresh error: %s", error + ) + return + + def _lookup_trust_boundary(self, request): + """Calls the trust boundary lookup API to refresh the trust boundary cache. + + Args: + request (google.auth.transport.Request): The object used to make + HTTP requests. + + Returns: + trust_boundary (dict): The trust boundary object returned by the lookup API. + + Raises: + google.auth.exceptions.RefreshError: If the trust boundary could not be + retrieved. + """ + from google.oauth2 import _client + + url = self._build_trust_boundary_lookup_url() + if not url: + raise exceptions.InvalidValue("Failed to build trust boundary lookup URL.") + + headers = {} + self._apply(headers) + headers.update(self._get_trust_boundary_header()) + return _client._lookup_trust_boundary(request, url, headers=headers) + + @abc.abstractmethod + def _build_trust_boundary_lookup_url(self): + """ + Builds and returns the URL for the trust boundary lookup API. + + This method should be implemented by subclasses to provide the + specific URL based on the credential type and its properties. + + Returns: + str: The URL for the trust boundary lookup endpoint, or None + if lookup should be skipped (e.g., for non-applicable universe domains). + """ + raise NotImplementedError( + "_build_trust_boundary_lookup_url must be implemented" + ) + + def _has_no_op_trust_boundary(self): + # A no-op trust boundary is indicated by encodedLocations being "0x0". + # The "locations" list may or may not be present as an empty list. + if ( + self._trust_boundary is not None + and self._trust_boundary["encodedLocations"] + == NO_OP_TRUST_BOUNDARY_ENCODED_LOCATIONS + ): + return True + return False + + class AnonymousCredentials(Credentials): """Credentials that do not provide any authentication information. @@ -382,8 +526,7 @@ def default_scopes(self): @abc.abstractproperty def requires_scopes(self): - """True if these credentials require scopes to obtain an access token. - """ + """True if these credentials require scopes to obtain an access token.""" return False def has_scopes(self, scopes): diff --git a/packages/google-auth/google/auth/environment_vars.py b/packages/google-auth/google/auth/environment_vars.py index 81f31571eb80..e5f3598e8147 100644 --- a/packages/google-auth/google/auth/environment_vars.py +++ b/packages/google-auth/google/auth/environment_vars.py @@ -82,3 +82,7 @@ AWS_SESSION_TOKEN = "AWS_SESSION_TOKEN" AWS_REGION = "AWS_REGION" AWS_DEFAULT_REGION = "AWS_DEFAULT_REGION" + +GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED = "GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED" +"""Environment variable controlling whether to enable trust boundary feature. +The default value is false. Users have to explicitly set this value to true.""" diff --git a/packages/google-auth/google/auth/external_account.py b/packages/google-auth/google/auth/external_account.py index 161e6c50ce32..b72f4c20f03a 100644 --- a/packages/google-auth/google/auth/external_account.py +++ b/packages/google-auth/google/auth/external_account.py @@ -59,18 +59,18 @@ @dataclass class SupplierContext: """A context class that contains information about the requested third party credential that is passed - to AWS security credential and subject token suppliers. + to AWS security credential and subject token suppliers. - Attributes: - subject_token_type (str): The requested subject token type based on the Oauth2.0 token exchange spec. - Expected values include:: + Attributes: + subject_token_type (str): The requested subject token type based on the Oauth2.0 token exchange spec. + Expected values include:: - “urn:ietf:params:oauth:token-type:jwt” - “urn:ietf:params:oauth:token-type:id-token” - “urn:ietf:params:oauth:token-type:saml2” - “urn:ietf:params:aws:token-type:aws4_request” + “urn:ietf:params:oauth:token-type:jwt” + “urn:ietf:params:oauth:token-type:id-token” + “urn:ietf:params:oauth:token-type:saml2” + “urn:ietf:params:aws:token-type:aws4_request” - audience (str): The requested audience for the subject token. + audience (str): The requested audience for the subject token. """ subject_token_type: str diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index d49998cfbdc6..4d8b72941843 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -46,6 +46,9 @@ _DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds _GOOGLE_OAUTH2_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token" +_TRUST_BOUNDARY_LOOKUP_ENDPOINT = ( + "https://iamcredentials.{}/v1/projects/-/serviceAccounts/{}/allowedLocations" +) _SOURCE_CREDENTIAL_AUTHORIZED_USER_TYPE = "authorized_user" _SOURCE_CREDENTIAL_SERVICE_ACCOUNT_TYPE = "service_account" @@ -117,7 +120,10 @@ def _make_iam_token_request( class Credentials( - credentials.Scoped, credentials.CredentialsWithQuotaProject, credentials.Signing + credentials.Scoped, + credentials.CredentialsWithQuotaProject, + credentials.Signing, + credentials.CredentialsWithTrustBoundary, ): """This module defines impersonated credentials which are essentially impersonated identities. @@ -190,6 +196,7 @@ def __init__( lifetime=_DEFAULT_TOKEN_LIFETIME_SECS, quota_project_id=None, iam_endpoint_override=None, + trust_boundary=None, ): """ Args: @@ -220,6 +227,7 @@ def __init__( subject (Optional[str]): sub field of a JWT. This field should only be set if you wish to impersonate as a user. This feature is useful when using domain wide delegation. + trust_boundary (Mapping[str,str]): A credential trust boundary. """ super(Credentials, self).__init__() @@ -251,15 +259,12 @@ def __init__( self._quota_project_id = quota_project_id self._iam_endpoint_override = iam_endpoint_override self._cred_file_path = None + self._trust_boundary = trust_boundary def _metric_header_for_usage(self): return metrics.CRED_TYPE_SA_IMPERSONATE - @_helpers.copy_docstring(credentials.Credentials) - def refresh(self, request): - self._update_token(request) - - def _update_token(self, request): + def _refresh_token(self, request): """Updates credentials with a new access_token representing the impersonated account. @@ -331,6 +336,28 @@ def _update_token(self, request): iam_endpoint_override=self._iam_endpoint_override, ) + def _build_trust_boundary_lookup_url(self): + """Builds and returns the URL for the trust boundary lookup API. + + This method constructs the specific URL for the IAM Credentials API's + `allowedLocations` endpoint, using the credential's universe domain + and service account email. + + Raises: + ValueError: If `self.service_account_email` is None or an empty + string, as it's required to form the URL. + + Returns: + str: The URL for the trust boundary lookup endpoint. + """ + if not self.service_account_email: + raise ValueError( + "Service account email is required to build the trust boundary lookup URL." + ) + return _TRUST_BOUNDARY_LOOKUP_ENDPOINT.format( + self.universe_domain, self.service_account_email + ) + def sign_bytes(self, message): from google.auth.transport.requests import AuthorizedSession @@ -400,10 +427,17 @@ def _make_copy(self): lifetime=self._lifetime, quota_project_id=self._quota_project_id, iam_endpoint_override=self._iam_endpoint_override, + trust_boundary=self._trust_boundary, ) cred._cred_file_path = self._cred_file_path return cred + @_helpers.copy_docstring(credentials.CredentialsWithTrustBoundary) + def with_trust_boundary(self, trust_boundary): + cred = self._make_copy() + cred._trust_boundary = trust_boundary + return cred + @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) def with_quota_project(self, quota_project_id): cred = self._make_copy() @@ -487,9 +521,7 @@ def from_impersonated_service_account_info(cls, info, scopes=None): class IDTokenCredentials(credentials.CredentialsWithQuotaProject): - """Open ID Connect ID Token-based service account credentials. - - """ + """Open ID Connect ID Token-based service account credentials.""" def __init__( self, diff --git a/packages/google-auth/google/oauth2/_client.py b/packages/google-auth/google/oauth2/_client.py index 5a9fc3503c53..9c0e630981e4 100644 --- a/packages/google-auth/google/oauth2/_client.py +++ b/packages/google-auth/google/oauth2/_client.py @@ -337,6 +337,8 @@ def call_iam_generate_id_token_endpoint( generateIdToken endpoint. audience (str): The audience for the ID token. access_token (str): The access token used to call the IAM endpoint. + universe_domain (str): The universe domain for the request. The + default is ``googleapis.com``. Returns: Tuple[str, datetime]: The ID token and expiration. @@ -506,3 +508,118 @@ def refresh_grant( request, token_uri, body, can_retry=can_retry ) return _handle_refresh_grant_response(response_data, refresh_token) + + +def _lookup_trust_boundary(request, url, headers=None): + """Implements the global lookup of a credential trust boundary. + For the lookup, we send a request to the global lookup endpoint and then + parse the response. Service account credentials, workload identity + pools and workforce pools implementation may have trust boundaries configured. + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + url (str): The trust boundary lookup url. + headers (Optional[Mapping[str, str]]): The headers for the request. + Returns: + Mapping[str,list|str]: A dictionary containing + "locations" as a list of allowed locations as strings and + "encodedLocations" as a hex string. + e.g: + { + "locations": [ + "us-central1", "us-east1", "europe-west1", "asia-east1" + ], + "encodedLocations": "0xA30" + } + If the credential is not set up with explicit trust boundaries, a trust boundary + of "all" will be returned as a default response. + { + "locations": [], + "encodedLocations": "0x0" + } + Raises: + exceptions.RefreshError: If the response status code is not 200. + exceptions.MalformedError: If the response is not in a valid format. + """ + + response_data = _lookup_trust_boundary_request(request, url, headers=headers) + # In case of no-op response, the "locations" list may or may not be present as an empty list. + if "encodedLocations" not in response_data: + raise exceptions.MalformedError( + "Invalid trust boundary info: {}".format(response_data) + ) + return response_data + + +def _lookup_trust_boundary_request(request, url, can_retry=True, headers=None): + """Makes a request to the trust boundary lookup endpoint. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + url (str): The trust boundary lookup url. + can_retry (bool): Enable or disable request retry behavior. Defaults to true. + headers (Optional[Mapping[str, str]]): The headers for the request. + + Returns: + Mapping[str, str]: The JSON-decoded response data. + + Raises: + google.auth.exceptions.RefreshError: If the token endpoint returned + an error. + """ + response_status_ok, response_data, retryable_error = _lookup_trust_boundary_request_no_throw( + request, url, can_retry, headers + ) + if not response_status_ok: + _handle_error_response(response_data, retryable_error) + return response_data + + +def _lookup_trust_boundary_request_no_throw(request, url, can_retry=True, headers=None): + """Makes a request to the trust boundary lookup endpoint. This + function doesn't throw on response errors. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + url (str): The trust boundary lookup url. + can_retry (bool): Enable or disable request retry behavior. Defaults to true. + headers (Optional[Mapping[str, str]]): The headers for the request. + + Returns: + Tuple(bool, Mapping[str, str], Optional[bool]): A boolean indicating + if the request is successful, a mapping for the JSON-decoded response + data and in the case of an error a boolean indicating if the error + is retryable. + """ + + response_data = {} + retryable_error = False + + retries = _exponential_backoff.ExponentialBackoff() + for _ in retries: + response = request(method="GET", url=url, headers=headers) + response_body = ( + response.data.decode("utf-8") + if hasattr(response.data, "decode") + else response.data + ) + + try: + # response_body should be a JSON + response_data = json.loads(response_body) + except ValueError: + response_data = response_body + + if response.status == http_client.OK: + return True, response_data, None + + retryable_error = _can_retry( + status_code=response.status, response_data=response_data + ) + + if not can_retry or not retryable_error: + return False, response_data, retryable_error + + return False, response_data, retryable_error diff --git a/packages/google-auth/google/oauth2/service_account.py b/packages/google-auth/google/oauth2/service_account.py index 3e84194ac7d1..55e020125a4d 100644 --- a/packages/google-auth/google/oauth2/service_account.py +++ b/packages/google-auth/google/oauth2/service_account.py @@ -84,6 +84,9 @@ _DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds _GOOGLE_OAUTH2_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token" +_TRUST_BOUNDARY_LOOKUP_ENDPOINT = ( + "https://iamcredentials.{}/v1/projects/-/serviceAccounts/{}/allowedLocations" +) class Credentials( @@ -91,6 +94,7 @@ class Credentials( credentials.Scoped, credentials.CredentialsWithQuotaProject, credentials.CredentialsWithTokenUri, + credentials.CredentialsWithTrustBoundary, ): """Service account credentials @@ -164,7 +168,7 @@ def __init__( universe_domain (str): The universe domain. The default universe domain is googleapis.com. For default value self signed jwt is used for token refresh. - trust_boundary (str): String representation of trust boundary meta. + trust_boundary (Mapping[str,str]): A credential trust boundary. .. note:: Typically one of the helper constructors :meth:`from_service_account_file` or @@ -194,7 +198,7 @@ def __init__( self._additional_claims = additional_claims else: self._additional_claims = {} - self._trust_boundary = {"locations": [], "encoded_locations": "0x0"} + self._trust_boundary = trust_boundary @classmethod def _from_signer_and_info(cls, signer, info, **kwargs): @@ -294,6 +298,7 @@ def _make_copy(self): additional_claims=self._additional_claims.copy(), always_use_jwt_access=self._always_use_jwt_access, universe_domain=self._universe_domain, + trust_boundary=self._trust_boundary, ) cred._cred_file_path = self._cred_file_path return cred @@ -381,6 +386,12 @@ def with_token_uri(self, token_uri): cred._token_uri = token_uri return cred + @_helpers.copy_docstring(credentials.CredentialsWithTrustBoundary) + def with_trust_boundary(self, trust_boundary): + cred = self._make_copy() + cred._trust_boundary = trust_boundary + return cred + def _make_authorization_grant_assertion(self): """Create the OAuth 2.0 assertion. @@ -424,8 +435,8 @@ def _metric_header_for_usage(self): return metrics.CRED_TYPE_SA_JWT return metrics.CRED_TYPE_SA_ASSERTION - @_helpers.copy_docstring(credentials.Credentials) - def refresh(self, request): + @_helpers.copy_docstring(credentials.CredentialsWithTrustBoundary) + def _refresh_token(self, request): if self._always_use_jwt_access and not self._jwt_credentials: # If self signed jwt should be used but jwt credential is not # created, try to create one with scopes @@ -491,6 +502,28 @@ def _create_self_signed_jwt(self, audience): self, audience ) + def _build_trust_boundary_lookup_url(self): + """Builds and returns the URL for the trust boundary lookup API. + + This method constructs the specific URL for the IAM Credentials API's + `allowedLocations` endpoint, using the credential's universe domain + and service account email. + + Raises: + ValueError: If `self.service_account_email` is None or an empty + string, as it's required to form the URL. + + Returns: + str: The URL for the trust boundary lookup endpoint. + """ + if not self.service_account_email: + raise ValueError( + "Service account email is required to build the trust boundary lookup URL." + ) + return _TRUST_BOUNDARY_LOOKUP_ENDPOINT.format( + self._universe_domain, self._service_account_email + ) + @_helpers.copy_docstring(credentials.Signing) def sign_bytes(self, message): return self._signer.sign(message) @@ -591,6 +624,7 @@ def __init__( token endponint is used for token refresh. Note that iam.serviceAccountTokenCreator role is required to use the IAM endpoint. + .. note:: Typically one of the helper constructors :meth:`from_service_account_file` or :meth:`from_service_account_info` are used instead of calling the @@ -806,6 +840,7 @@ def _refresh_with_iam_endpoint(self, request): additional_claims={"scope": "https://www.googleapis.com/auth/iam"}, ) jwt_credentials.refresh(request) + self.token, self.expiry = _client.call_iam_generate_id_token_endpoint( request, self._iam_id_token_endpoint, diff --git a/packages/google-auth/tests/compute_engine/test_credentials.py b/packages/google-auth/tests/compute_engine/test_credentials.py index 03fe845b1f32..1c77069938d6 100644 --- a/packages/google-auth/tests/compute_engine/test_credentials.py +++ b/packages/google-auth/tests/compute_engine/test_credentials.py @@ -13,17 +13,20 @@ # limitations under the License. import base64 import datetime +import os import mock import pytest # type: ignore import responses # type: ignore from google.auth import _helpers +from google.auth import environment_vars from google.auth import exceptions from google.auth import jwt from google.auth import transport from google.auth.compute_engine import credentials from google.auth.transport import requests +from google.oauth2 import _client SAMPLE_ID_TOKEN_EXP = 1584393400 @@ -49,7 +52,6 @@ ID_TOKEN_REQUEST_METRICS_HEADER_VALUE = ( "gl-python/3.7 auth/1.1 auth-request-type/it cred-type/mds" ) - FAKE_SERVICE_ACCOUNT_EMAIL = "foo@bar.com" FAKE_QUOTA_PROJECT_ID = "fake-quota-project" FAKE_SCOPES = ["scope1", "scope2"] @@ -60,6 +62,9 @@ class TestCredentials(object): credentials = None credentials_with_all_fields = None + VALID_TRUST_BOUNDARY = {"encodedLocations": "valid-encoded-locations"} + NO_OP_TRUST_BOUNDARY = {"encodedLocations": ""} + EXPECTED_TRUST_BOUNDARY_LOOKUP_URL_DEFAULT_UNIVERSE = "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/default/allowedLocations" @pytest.fixture(autouse=True) def credentials_fixture(self): @@ -173,6 +178,18 @@ def test_refresh_success_with_scopes(self, get, utcnow, mock_metrics_header_valu "x-goog-api-client": ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE } + @mock.patch("google.auth.compute_engine._metadata.get", autospec=True) + def test_refresh_no_email(self, get): + get.return_value = { + # No "email" field. + "scopes": ["one", "two"] + } + + with pytest.raises(exceptions.RefreshError) as excinfo: + self.credentials.refresh(None) + + assert excinfo.match(r"missing 'email' field") + @mock.patch("google.auth.compute_engine._metadata.get", autospec=True) def test_refresh_error(self, get): get.side_effect = exceptions.TransportError("http error") @@ -241,6 +258,18 @@ def test_with_universe_domain(self): assert creds.universe_domain == "universe_domain" assert creds._universe_domain_cached + def test_with_trust_boundary(self): + creds = self.credentials_with_all_fields + new_boundary = {"encodedLocations": "new_boundary"} + new_creds = creds.with_trust_boundary(new_boundary) + + assert new_creds is not creds + assert new_creds._trust_boundary == new_boundary + assert new_creds._service_account_email == creds._service_account_email + assert new_creds._quota_project_id == creds._quota_project_id + assert new_creds._scopes == creds._scopes + assert new_creds._default_scopes == creds._default_scopes + def test_token_usage_metrics(self): self.credentials.token = "token" self.credentials.expiry = None @@ -280,6 +309,355 @@ def test_user_provided_universe_domain(self, get_universe_domain): # domain endpoint. get_universe_domain.assert_not_called() + @mock.patch("google.oauth2._client._lookup_trust_boundary", autospec=True) + @mock.patch("google.auth.compute_engine._metadata.get", autospec=True) + def test_refresh_trust_boundary_lookup_skipped_if_env_var_not_true( + self, mock_metadata_get, mock_lookup_tb + ): + creds = self.credentials + request = mock.Mock() + + mock_metadata_get.side_effect = [ + # from _retrieve_info + {"email": "default", "scopes": ["scope1"]}, + # from get_service_account_token + {"access_token": "mock_token", "expires_in": 3600}, + ] + + with mock.patch.dict( + os.environ, {environment_vars.GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED: "false"} + ): + creds.refresh(request) + + mock_lookup_tb.assert_not_called() + assert creds._trust_boundary is None + + @mock.patch("google.oauth2._client._lookup_trust_boundary", autospec=True) + @mock.patch("google.auth.compute_engine._metadata.get", autospec=True) + def test_refresh_trust_boundary_lookup_skipped_if_env_var_missing( + self, mock_metadata_get, mock_lookup_tb + ): + creds = self.credentials + request = mock.Mock() + + mock_metadata_get.side_effect = [ + # from _retrieve_info + {"email": "default", "scopes": ["scope1"]}, + # from get_service_account_token + {"access_token": "mock_token", "expires_in": 3600}, + ] + + with mock.patch.dict(os.environ, clear=True): + creds.refresh(request) + + mock_lookup_tb.assert_not_called() + assert creds._trust_boundary is None + + @mock.patch.object(_client, "_lookup_trust_boundary", autospec=True) + @mock.patch("google.auth.compute_engine._metadata.get", autospec=True) + def test_refresh_trust_boundary_lookup_success( + self, mock_metadata_get, mock_lookup_tb + ): + mock_lookup_tb.return_value = { + "locations": ["us-central1"], + "encodedLocations": "0xABC", + } + creds = self.credentials + request = mock.Mock() + + # The first call to _metadata.get is for service account info, the second + # for the access token, and the third for the universe domain. + mock_metadata_get.side_effect = [ + # from _retrieve_info + {"email": "resolved-email@example.com", "scopes": ["scope1"]}, + # from get_service_account_token + {"access_token": "mock_token", "expires_in": 3600}, + # from get_universe_domain + "", + ] + + with mock.patch.dict( + os.environ, {environment_vars.GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED: "true"} + ): + creds.refresh(request) + + # Verify _metadata.get was called three times. + assert mock_metadata_get.call_count == 3 + # Verify lookup_trust_boundary was called with correct URL and token + expected_url = "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/resolved-email@example.com/allowedLocations" + mock_lookup_tb.assert_called_once_with( + request, expected_url, headers={"authorization": "Bearer mock_token"} + ) + # Verify trust boundary was set + assert creds._trust_boundary == { + "locations": ["us-central1"], + "encodedLocations": "0xABC", + } + + # Verify x-allowed-locations header is set by apply() + headers_applied = {} + creds.apply(headers_applied) + assert headers_applied["x-allowed-locations"] == "0xABC" + + @mock.patch("google.auth.compute_engine._metadata.get", autospec=True) + @mock.patch.object(_client, "_lookup_trust_boundary", autospec=True) + def test_refresh_trust_boundary_lookup_fails_no_cache( + self, mock_lookup_tb, mock_metadata_get + ): + mock_lookup_tb.side_effect = exceptions.RefreshError("Lookup failed") + creds = self.credentials + request = mock.Mock() + + # Mock metadata calls for token, universe domain, and service account info + mock_metadata_get.side_effect = [ + # from _retrieve_info + {"email": "resolved-email@example.com", "scopes": ["scope1"]}, + # from get_service_account_token + {"access_token": "mock_token", "expires_in": 3600}, + # from get_universe_domain + "", + ] + + with mock.patch.dict( + os.environ, {environment_vars.GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED: "true"} + ): + with pytest.raises(exceptions.RefreshError, match="Lookup failed"): + creds.refresh(request) + + assert creds._trust_boundary is None + assert mock_metadata_get.call_count == 3 + mock_lookup_tb.assert_called_once() + + @mock.patch("google.auth.compute_engine._metadata.get", autospec=True) + @mock.patch.object(_client, "_lookup_trust_boundary", autospec=True) + def test_refresh_trust_boundary_lookup_fails_with_cached_data( + self, mock_lookup_tb, mock_metadata_get + ): + # First refresh: Successfully fetch a valid trust boundary. + mock_lookup_tb.return_value = { + "locations": ["us-central1"], + "encodedLocations": "0xABC", + } + mock_metadata_get.side_effect = [ + # from _retrieve_info + {"email": "resolved-email@example.com", "scopes": ["scope1"]}, + # from get_service_account_token + {"access_token": "mock_token_1", "expires_in": 3600}, + # from get_universe_domain + "", + ] + creds = self.credentials + request = mock.Mock() + + with mock.patch.dict( + os.environ, {environment_vars.GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED: "true"} + ): + creds.refresh(request) + + assert creds._trust_boundary == { + "locations": ["us-central1"], + "encodedLocations": "0xABC", + } + mock_lookup_tb.assert_called_once() + + # Second refresh: Mock lookup to fail, but expect cached data to be preserved. + mock_lookup_tb.reset_mock() + mock_lookup_tb.side_effect = exceptions.RefreshError("Lookup failed") + + with mock.patch.dict( + os.environ, {environment_vars.GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED: "true"} + ): + # This refresh should not raise an error because a cached value exists. + mock_metadata_get.reset_mock() + mock_metadata_get.side_effect = [ + # from _retrieve_info + {"email": "resolved-email@example.com", "scopes": ["scope1"]}, + # from get_service_account_token + {"access_token": "mock_token_2", "expires_in": 3600}, + # from get_universe_domain + "", + ] + creds.refresh(request) + + assert creds._trust_boundary == { + "locations": ["us-central1"], + "encodedLocations": "0xABC", + } + mock_lookup_tb.assert_called_once() + + @mock.patch("google.auth.compute_engine._metadata.get", autospec=True) + @mock.patch.object(_client, "_lookup_trust_boundary", autospec=True) + def test_refresh_fetches_no_op_trust_boundary( + self, mock_lookup_tb, mock_metadata_get + ): + mock_lookup_tb.return_value = {"locations": [], "encodedLocations": "0x0"} + creds = self.credentials + request = mock.Mock() + + mock_metadata_get.side_effect = [ + # from _retrieve_info + {"email": "resolved-email@example.com", "scopes": ["scope1"]}, + # from get_service_account_token + {"access_token": "mock_token", "expires_in": 3600}, + # from get_universe_domain + "", + ] + + with mock.patch.dict( + os.environ, {environment_vars.GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED: "true"} + ): + creds.refresh(request) + + assert creds._trust_boundary == {"locations": [], "encodedLocations": "0x0"} + assert mock_metadata_get.call_count == 3 + expected_url = "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/resolved-email@example.com/allowedLocations" + mock_lookup_tb.assert_called_once_with( + request, expected_url, headers={"authorization": "Bearer mock_token"} + ) + # Verify that an empty header was added. + headers_applied = {} + creds.apply(headers_applied) + assert headers_applied["x-allowed-locations"] == "" + + @mock.patch("google.auth.compute_engine._metadata.get", autospec=True) + @mock.patch.object(_client, "_lookup_trust_boundary", autospec=True) + def test_refresh_starts_with_no_op_trust_boundary_skips_lookup( + self, mock_lookup_tb, mock_metadata_get + ): + creds = self.credentials + # Use pre-cache universe domain to avoid an extra metadata call. + creds._universe_domain_cached = True + creds._trust_boundary = {"locations": [], "encodedLocations": "0x0"} + request = mock.Mock() + + mock_metadata_get.side_effect = [ + # from _retrieve_info + {"email": "resolved-email@example.com", "scopes": ["scope1"]}, + # from get_service_account_token + {"access_token": "mock_token", "expires_in": 3600}, + ] + + with mock.patch.dict( + os.environ, {environment_vars.GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED: "true"} + ): + creds.refresh(request) + + # Verify trust boundary remained NO_OP + assert creds._trust_boundary == {"locations": [], "encodedLocations": "0x0"} + # Lookup should be skipped + mock_lookup_tb.assert_not_called() + # Two metadata calls for token refresh should have happened. + assert mock_metadata_get.call_count == 2 + + # Verify that an empty header was added. + headers_applied = {} + creds.apply(headers_applied) + assert headers_applied["x-allowed-locations"] == "" + + @mock.patch( + "google.auth.compute_engine._metadata.get_service_account_info", autospec=True + ) + @mock.patch( + "google.auth.compute_engine._metadata.get_universe_domain", autospec=True + ) + def test_build_trust_boundary_lookup_url_default_email( + self, mock_get_universe_domain, mock_get_service_account_info + ): + # Test with default service account email, which needs resolution + creds = self.credentials + creds._service_account_email = "default" + mock_get_service_account_info.return_value = { + "email": "resolved-email@example.com" + } + mock_get_universe_domain.return_value = "googleapis.com" + + url = creds._build_trust_boundary_lookup_url() + + mock_get_service_account_info.assert_called_once_with(mock.ANY, "default") + mock_get_universe_domain.assert_called_once_with(mock.ANY) + assert url == ( + "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/resolved-email@example.com/allowedLocations" + ) + + @mock.patch( + "google.auth.compute_engine._metadata.get_service_account_info", autospec=True + ) + @mock.patch( + "google.auth.compute_engine._metadata.get_universe_domain", autospec=True + ) + def test_build_trust_boundary_lookup_url_explicit_email( + self, mock_get_universe_domain, mock_get_service_account_info + ): + # Test with an explicit service account email, no resolution needed + creds = self.credentials + creds._service_account_email = FAKE_SERVICE_ACCOUNT_EMAIL + mock_get_universe_domain.return_value = "googleapis.com" + + url = creds._build_trust_boundary_lookup_url() + + mock_get_service_account_info.assert_not_called() + mock_get_universe_domain.assert_called_once_with(mock.ANY) + assert url == ( + "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/foo@bar.com/allowedLocations" + ) + + @mock.patch( + "google.auth.compute_engine._metadata.get_service_account_info", autospec=True + ) + @mock.patch( + "google.auth.compute_engine._metadata.get_universe_domain", autospec=True + ) + def test_build_trust_boundary_lookup_url_non_default_universe( + self, mock_get_universe_domain, mock_get_service_account_info + ): + # Test with a non-default universe domain + creds = self.credentials_with_all_fields + + url = creds._build_trust_boundary_lookup_url() + + # Universe domain is cached and email is explicit, so no metadata calls needed. + mock_get_service_account_info.assert_not_called() + mock_get_universe_domain.assert_not_called() + assert url == ( + "https://iamcredentials.fake-universe-domain/v1/projects/-/serviceAccounts/foo@bar.com/allowedLocations" + ) + + @mock.patch( + "google.auth.compute_engine._metadata.get_service_account_info", autospec=True + ) + def test_build_trust_boundary_lookup_url_get_service_account_info_fails( + self, mock_get_service_account_info + ): + # Test scenario where get_service_account_info fails + mock_get_service_account_info.side_effect = exceptions.TransportError( + "Failed to get info" + ) + creds = self.credentials + creds._service_account_email = "default" + + with pytest.raises( + exceptions.RefreshError, + match=r"Failed to get service account email for trust boundary lookup: .*", + ): + creds._build_trust_boundary_lookup_url() + + @mock.patch( + "google.auth.compute_engine._metadata.get_service_account_info", autospec=True + ) + def test_build_trust_boundary_lookup_url_no_email( + self, mock_get_service_account_info + ): + # Test with default service account email, which needs resolution, but metadata + # returns no email. + creds = self.credentials + creds._service_account_email = "default" + mock_get_service_account_info.return_value = {"scopes": ["one", "two"]} + + with pytest.raises(exceptions.RefreshError) as excinfo: + creds._build_trust_boundary_lookup_url() + + assert excinfo.match(r"missing 'email' field") + class TestIDTokenCredentials(object): credentials = None @@ -466,7 +844,7 @@ def test_with_target_audience(self, sign, get, utcnow): @responses.activate def test_with_target_audience_integration(self): - """ Test that it is possible to refresh credentials + """Test that it is possible to refresh credentials generated from `with_target_audience`. Instead of mocking the methods, the HTTP responses @@ -634,7 +1012,7 @@ def test_with_token_uri_exception(self, sign, get, utcnow): @responses.activate def test_with_quota_project_integration(self): - """ Test that it is possible to refresh credentials + """Test that it is possible to refresh credentials generated from `with_quota_project`. Instead of mocking the methods, the HTTP responses diff --git a/packages/google-auth/tests/oauth2/test__client.py b/packages/google-auth/tests/oauth2/test__client.py index 6a085729f4c2..b17ba542d085 100644 --- a/packages/google-auth/tests/oauth2/test__client.py +++ b/packages/google-auth/tests/oauth2/test__client.py @@ -324,7 +324,7 @@ def test_call_iam_generate_id_token_endpoint(): "fake_email", "fake_audience", "fake_access_token", - "googleapis.com", + universe_domain="googleapis.com", ) assert ( @@ -630,3 +630,178 @@ def test__token_endpoint_request_no_throw_with_retry(can_retry): assert mock_request.call_count == 3 else: assert mock_request.call_count == 1 + + +def test_lookup_trust_boundary(): + response_data = { + "locations": ["us-central1", "us-east1"], + "encodedLocations": "0x80080000000000", + } + + mock_response = mock.create_autospec(transport.Response, instance=True) + mock_response.status = http_client.OK + mock_response.data = json.dumps(response_data).encode("utf-8") + + mock_request = mock.create_autospec(transport.Request) + mock_request.return_value = mock_response + + url = "http://example.com" + headers = {"Authorization": "Bearer access_token"} + response = _client._lookup_trust_boundary(mock_request, url, headers=headers) + + assert response["encodedLocations"] == "0x80080000000000" + assert response["locations"] == ["us-central1", "us-east1"] + + mock_request.assert_called_once_with(method="GET", url=url, headers=headers) + + +def test_lookup_trust_boundary_no_op_response_without_locations(): + response_data = {"encodedLocations": "0x0"} + + mock_response = mock.create_autospec(transport.Response, instance=True) + mock_response.status = http_client.OK + mock_response.data = json.dumps(response_data).encode("utf-8") + + mock_request = mock.create_autospec(transport.Request) + mock_request.return_value = mock_response + + url = "http://example.com" + headers = {"Authorization": "Bearer access_token"} + # for the response to be valid, we only need encodedLocations to be present. + response = _client._lookup_trust_boundary(mock_request, url, headers=headers) + assert response["encodedLocations"] == "0x0" + assert "locations" not in response + + mock_request.assert_called_once_with(method="GET", url=url, headers=headers) + + +def test_lookup_trust_boundary_no_op_response(): + response_data = {"locations": [], "encodedLocations": "0x0"} + + mock_response = mock.create_autospec(transport.Response, instance=True) + mock_response.status = http_client.OK + mock_response.data = json.dumps(response_data).encode("utf-8") + + mock_request = mock.create_autospec(transport.Request) + mock_request.return_value = mock_response + + url = "http://example.com" + headers = {"Authorization": "Bearer access_token"} + response = _client._lookup_trust_boundary(mock_request, url, headers=headers) + + assert response["encodedLocations"] == "0x0" + assert response["locations"] == [] + + mock_request.assert_called_once_with(method="GET", url=url, headers=headers) + + +def test_lookup_trust_boundary_error(): + mock_response = mock.create_autospec(transport.Response, instance=True) + mock_response.status = http_client.INTERNAL_SERVER_ERROR + mock_response.data = "this is an error message" + + mock_request = mock.create_autospec(transport.Request) + mock_request.return_value = mock_response + + url = "http://example.com" + headers = {"Authorization": "Bearer access_token"} + with pytest.raises(exceptions.RefreshError) as excinfo: + _client._lookup_trust_boundary(mock_request, url, headers=headers) + assert excinfo.match("this is an error message") + + mock_request.assert_called_with(method="GET", url=url, headers=headers) + + +def test_lookup_trust_boundary_missing_encoded_locations(): + response_data = {"locations": [], "bad_field": "0x0"} + + mock_response = mock.create_autospec(transport.Response, instance=True) + mock_response.status = http_client.OK + mock_response.data = json.dumps(response_data).encode("utf-8") + + mock_request = mock.create_autospec(transport.Request) + mock_request.return_value = mock_response + + url = "http://example.com" + headers = {"Authorization": "Bearer access_token"} + with pytest.raises(exceptions.MalformedError) as excinfo: + _client._lookup_trust_boundary(mock_request, url, headers=headers) + assert excinfo.match("Invalid trust boundary info") + + mock_request.assert_called_once_with(method="GET", url=url, headers=headers) + + +def test_lookup_trust_boundary_internal_failure_and_retry_failure_error(): + retryable_error = mock.create_autospec(transport.Response, instance=True) + retryable_error.status = http_client.BAD_REQUEST + retryable_error.data = json.dumps({"error_description": "internal_failure"}).encode( + "utf-8" + ) + + unretryable_error = mock.create_autospec(transport.Response, instance=True) + unretryable_error.status = http_client.BAD_REQUEST + unretryable_error.data = json.dumps({"error_description": "invalid_scope"}).encode( + "utf-8" + ) + + request = mock.create_autospec(transport.Request) + + request.side_effect = [retryable_error, retryable_error, unretryable_error] + headers = {"Authorization": "Bearer access_token"} + + with pytest.raises(exceptions.RefreshError): + _client._lookup_trust_boundary_request( + request, "http://example.com", headers=headers + ) + # request should be called three times. Two retryable errors and one + # unretryable error to break the retry loop. + assert request.call_count == 3 + for call in request.call_args_list: + assert call[1]["headers"] == headers + + +def test_lookup_trust_boundary_internal_failure_and_retry_succeeds(): + retryable_error = mock.create_autospec(transport.Response, instance=True) + retryable_error.status = http_client.BAD_REQUEST + retryable_error.data = json.dumps({"error_description": "internal_failure"}).encode( + "utf-8" + ) + + response_data = {"locations": [], "encodedLocations": "0x0"} + response = mock.create_autospec(transport.Response, instance=True) + response.status = http_client.OK + response.data = json.dumps(response_data).encode("utf-8") + + request = mock.create_autospec(transport.Request) + + headers = {"Authorization": "Bearer access_token"} + request.side_effect = [retryable_error, response] + + _ = _client._lookup_trust_boundary_request( + request, "http://example.com", headers=headers + ) + + assert request.call_count == 2 + for call in request.call_args_list: + assert call[1]["headers"] == headers + + +def test_lookup_trust_boundary_with_headers(): + response_data = { + "locations": ["us-central1", "us-east1"], + "encodedLocations": "0x80080000000000", + } + + mock_response = mock.create_autospec(transport.Response, instance=True) + mock_response.status = http_client.OK + mock_response.data = json.dumps(response_data).encode("utf-8") + + mock_request = mock.create_autospec(transport.Request) + mock_request.return_value = mock_response + headers = {"Authorization": "Bearer access_token", "x-test-header": "test-value"} + + _client._lookup_trust_boundary(mock_request, "http://example.com", headers=headers) + + mock_request.assert_called_once_with( + method="GET", url="http://example.com", headers=headers + ) diff --git a/packages/google-auth/tests/oauth2/test_service_account.py b/packages/google-auth/tests/oauth2/test_service_account.py index 91a7d93e0a5f..d23746fdff67 100644 --- a/packages/google-auth/tests/oauth2/test_service_account.py +++ b/packages/google-auth/tests/oauth2/test_service_account.py @@ -20,7 +20,9 @@ import pytest # type: ignore from google.auth import _helpers +from google.auth import credentials from google.auth import crypt +from google.auth import environment_vars from google.auth import exceptions from google.auth import iam from google.auth import jwt @@ -58,14 +60,31 @@ class TestCredentials(object): SERVICE_ACCOUNT_EMAIL = "service-account@example.com" TOKEN_URI = "https://example.com/oauth2/token" + NO_OP_TRUST_BOUNDARY = { + "locations": credentials.NO_OP_TRUST_BOUNDARY_LOCATIONS, + "encodedLocations": credentials.NO_OP_TRUST_BOUNDARY_ENCODED_LOCATIONS, + } + VALID_TRUST_BOUNDARY = { + "locations": ["us-central1", "us-east1"], + "encodedLocations": "0xVALIDHEXSA", + } + EXPECTED_TRUST_BOUNDARY_LOOKUP_URL_DEFAULT_UNIVERSE = ( + "https://iamcredentials.googleapis.com/v1/projects/-" + "/serviceAccounts/service-account@example.com/allowedLocations" + ) @classmethod - def make_credentials(cls, universe_domain=DEFAULT_UNIVERSE_DOMAIN): + def make_credentials( + cls, + universe_domain=DEFAULT_UNIVERSE_DOMAIN, + trust_boundary=None, # Align with Credentials class default + ): return service_account.Credentials( SIGNER, cls.SERVICE_ACCOUNT_EMAIL, cls.TOKEN_URI, universe_domain=universe_domain, + trust_boundary=trust_boundary, ) def test_get_cred_info(self): @@ -251,6 +270,18 @@ def test__with_always_use_jwt_access_non_default_universe_domain(self): "always_use_jwt_access should be True for non-default universe domain" ) + def test_with_trust_boundary(self): + credentials = self.make_credentials() + new_boundary = {"encodedLocations": "new_boundary"} + new_credentials = credentials.with_trust_boundary(new_boundary) + + assert new_credentials is not credentials + assert new_credentials._trust_boundary == new_boundary + assert new_credentials._signer == credentials._signer + assert ( + new_credentials.service_account_email == credentials.service_account_email + ) + def test__make_authorization_grant_assertion(self): credentials = self.make_credentials() token = credentials._make_authorization_grant_assertion() @@ -495,10 +526,42 @@ def test_refresh_success(self, jwt_grant): # Check that the credentials have the token. assert credentials.token == token - # Check that the credentials are valid (have a token and are not - # expired) + # Check that the credentials are valid (have a token and are not expired). assert credentials.valid + # Trust boundary should be None since env var is not set and no initial + # boundary was provided. + assert credentials._trust_boundary is None + + @mock.patch("google.oauth2._client._lookup_trust_boundary") + @mock.patch("google.oauth2._client.jwt_grant", autospec=True) + def test_refresh_skips_trust_boundary_lookup_non_default_universe( + self, mock_jwt_grant, mock_lookup_trust_boundary + ): + # Create credentials with a non-default universe domain + credentials = self.make_credentials(universe_domain=FAKE_UNIVERSE_DOMAIN) + token = "token" + mock_jwt_grant.return_value = ( + token, + _helpers.utcnow() + datetime.timedelta(seconds=500), + {}, + ) + request = mock.create_autospec(transport.Request, instance=True) + + with mock.patch.dict( + os.environ, {environment_vars.GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED: "true"} + ): + credentials.refresh(request) + + # Ensure jwt_grant was called (token refresh happened) + mock_jwt_grant.assert_called_once() + # Ensure trust boundary lookup was not called + mock_lookup_trust_boundary.assert_not_called() + # Verify that x-allowed-locations header is not set by apply() + headers_applied = {} + credentials.apply(headers_applied) + assert "x-allowed-locations" not in headers_applied + @mock.patch("google.oauth2._client.jwt_grant", autospec=True) def test_before_request_refreshes(self, jwt_grant): credentials = self.make_credentials() @@ -607,6 +670,208 @@ def test_refresh_non_gdu_domain_wide_delegation_not_supported(self): credentials.refresh(None) assert excinfo.match("domain wide delegation is not supported") + @mock.patch("google.oauth2._client._lookup_trust_boundary") + @mock.patch("google.oauth2._client.jwt_grant", autospec=True) + def test_refresh_success_with_valid_trust_boundary( + self, mock_jwt_grant, mock_lookup_trust_boundary + ): + # Start with no boundary. + credentials = self.make_credentials(trust_boundary=None) + token = "token" + mock_jwt_grant.return_value = ( + token, + _helpers.utcnow() + datetime.timedelta(seconds=500), + {}, + ) + request = mock.create_autospec(transport.Request, instance=True) + + # Mock the trust boundary lookup to return a valid boundary. + mock_lookup_trust_boundary.return_value = self.VALID_TRUST_BOUNDARY + + with mock.patch.dict( + os.environ, {environment_vars.GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED: "true"} + ): + credentials.refresh(request) + + assert credentials.valid + assert credentials.token == token + + # Verify trust boundary was set. + assert credentials._trust_boundary == self.VALID_TRUST_BOUNDARY + + # Verify the mock was called with the correct URL. + mock_lookup_trust_boundary.assert_called_once_with( + request, + self.EXPECTED_TRUST_BOUNDARY_LOOKUP_URL_DEFAULT_UNIVERSE, + headers={"authorization": "Bearer token"}, + ) + + # Verify x-allowed-locations header is set correctly by apply(). + headers_applied = {} + credentials.apply(headers_applied) + assert ( + headers_applied["x-allowed-locations"] + == self.VALID_TRUST_BOUNDARY["encodedLocations"] + ) + + @mock.patch("google.oauth2._client._lookup_trust_boundary") + @mock.patch("google.oauth2._client.jwt_grant", autospec=True) + def test_refresh_fetches_no_op_trust_boundary( + self, mock_jwt_grant, mock_lookup_trust_boundary + ): + # Start with no trust boundary + credentials = self.make_credentials(trust_boundary=None) + token = "token" + mock_jwt_grant.return_value = ( + token, + _helpers.utcnow() + datetime.timedelta(seconds=500), + {}, + ) + request = mock.create_autospec(transport.Request, instance=True) + + mock_lookup_trust_boundary.return_value = self.NO_OP_TRUST_BOUNDARY + + with mock.patch.dict( + os.environ, {environment_vars.GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED: "true"} + ): + credentials.refresh(request) + + assert credentials.valid + assert credentials.token == token + assert credentials._trust_boundary == self.NO_OP_TRUST_BOUNDARY + mock_lookup_trust_boundary.assert_called_once_with( + request, + self.EXPECTED_TRUST_BOUNDARY_LOOKUP_URL_DEFAULT_UNIVERSE, + headers={"authorization": "Bearer token"}, + ) + headers_applied = {} + credentials.apply(headers_applied) + assert headers_applied["x-allowed-locations"] == "" + + @mock.patch("google.oauth2._client._lookup_trust_boundary") + @mock.patch("google.oauth2._client.jwt_grant", autospec=True) + def test_refresh_starts_with_no_op_trust_boundary_skips_lookup( + self, mock_jwt_grant, mock_lookup_trust_boundary + ): + credentials = self.make_credentials( + trust_boundary=self.NO_OP_TRUST_BOUNDARY + ) # Start with NO_OP + token = "token" + mock_jwt_grant.return_value = ( + token, + _helpers.utcnow() + datetime.timedelta(seconds=500), + {}, + ) + request = mock.create_autospec(transport.Request, instance=True) + + with mock.patch.dict( + os.environ, {environment_vars.GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED: "true"} + ): + credentials.refresh(request) + + assert credentials.valid + assert credentials.token == token + # Verify trust boundary remained NO_OP + assert credentials._trust_boundary == self.NO_OP_TRUST_BOUNDARY + + # Lookup should be skipped + mock_lookup_trust_boundary.assert_not_called() + + # Verify that an empty header was added. + headers_applied = {} + credentials.apply(headers_applied) + assert headers_applied["x-allowed-locations"] == "" + + @mock.patch("google.oauth2._client._lookup_trust_boundary") + @mock.patch("google.oauth2._client.jwt_grant", autospec=True) + def test_refresh_trust_boundary_lookup_fails_no_cache( + self, mock_jwt_grant, mock_lookup_trust_boundary + ): + # Start with no trust boundary + credentials = self.make_credentials(trust_boundary=None) + mock_lookup_trust_boundary.side_effect = exceptions.RefreshError( + "Lookup failed" + ) + mock_jwt_grant.return_value = ( + "mock_access_token", + _helpers.utcnow() + datetime.timedelta(seconds=3600), + {}, + ) + request = mock.create_autospec(transport.Request, instance=True) + + # Mock the trust boundary lookup to raise an error + with mock.patch.dict( + os.environ, {environment_vars.GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED: "true"} + ), pytest.raises(exceptions.RefreshError, match="Lookup failed"): + credentials.refresh(request) + + assert credentials._trust_boundary is None + mock_lookup_trust_boundary.assert_called_once() + + @mock.patch("google.oauth2._client._lookup_trust_boundary") + @mock.patch("google.oauth2._client.jwt_grant", autospec=True) + def test_refresh_trust_boundary_lookup_fails_with_cached_data( + self, mock_jwt_grant, mock_lookup_trust_boundary + ): + # Initial setup: Credentials with no trust boundary. + credentials = self.make_credentials(trust_boundary=None) + token = "token" + mock_jwt_grant.return_value = ( + token, + _helpers.utcnow() + datetime.timedelta(seconds=500), + {}, + ) + request = mock.create_autospec(transport.Request, instance=True) + + # First refresh: Successfully fetch a valid trust boundary. + mock_lookup_trust_boundary.return_value = self.VALID_TRUST_BOUNDARY + with mock.patch.dict( + os.environ, {environment_vars.GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED: "true"} + ): + credentials.refresh(request) + + assert credentials.valid + assert credentials.token == token + assert credentials._trust_boundary == self.VALID_TRUST_BOUNDARY + mock_lookup_trust_boundary.assert_called_once_with( + request, + self.EXPECTED_TRUST_BOUNDARY_LOOKUP_URL_DEFAULT_UNIVERSE, + headers={"authorization": "Bearer token"}, + ) + + # Second refresh: Mock lookup to fail, but expect cached data to be preserved. + mock_lookup_trust_boundary.reset_mock() + mock_lookup_trust_boundary.side_effect = exceptions.RefreshError( + "Lookup failed" + ) + + with mock.patch.dict( + os.environ, {environment_vars.GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED: "true"} + ): + credentials.refresh(request) # This should NOT raise an exception + + assert credentials.valid # Credentials should still be valid + assert ( + credentials._trust_boundary == self.VALID_TRUST_BOUNDARY + ) # Cached data should be preserved + mock_lookup_trust_boundary.assert_called_once_with( + request, + self.EXPECTED_TRUST_BOUNDARY_LOOKUP_URL_DEFAULT_UNIVERSE, + headers={ + "authorization": "Bearer token", + "x-allowed-locations": self.VALID_TRUST_BOUNDARY["encodedLocations"], + }, + ) # Lookup should have been attempted again + + def test_build_trust_boundary_lookup_url_no_email(self): + credentials = self.make_credentials() + credentials._service_account_email = None + + with pytest.raises(ValueError) as excinfo: + credentials._build_trust_boundary_lookup_url() + + assert "Service account email is required" in str(excinfo.value) + class TestIDTokenCredentials(object): SERVICE_ACCOUNT_EMAIL = "service-account@example.com" @@ -789,9 +1054,14 @@ def test_refresh_iam_flow(self, call_iam_generate_id_token_endpoint): ) request = mock.Mock() credentials.refresh(request) - req, iam_endpoint, signer_email, target_audience, access_token, universe_domain = call_iam_generate_id_token_endpoint.call_args[ - 0 - ] + ( + req, + iam_endpoint, + signer_email, + target_audience, + access_token, + universe_domain, + ) = call_iam_generate_id_token_endpoint.call_args[0] assert req == request assert iam_endpoint == iam._IAM_IDTOKEN_ENDPOINT assert signer_email == "service-account@example.com" @@ -811,9 +1081,14 @@ def test_refresh_iam_flow_non_gdu(self, call_iam_generate_id_token_endpoint): ) request = mock.Mock() credentials.refresh(request) - req, iam_endpoint, signer_email, target_audience, access_token, universe_domain = call_iam_generate_id_token_endpoint.call_args[ - 0 - ] + ( + req, + iam_endpoint, + signer_email, + target_audience, + access_token, + universe_domain, + ) = call_iam_generate_id_token_endpoint.call_args[0] assert req == request assert ( iam_endpoint diff --git a/packages/google-auth/tests/test__helpers.py b/packages/google-auth/tests/test__helpers.py index a4337c01608c..ce3ec11e29c1 100644 --- a/packages/google-auth/tests/test__helpers.py +++ b/packages/google-auth/tests/test__helpers.py @@ -20,7 +20,7 @@ import pytest # type: ignore -from google.auth import _helpers +from google.auth import _helpers, exceptions # _MOCK_BASE_LOGGER_NAME is the base logger namespace used for testing. _MOCK_BASE_LOGGER_NAME = "foogle" @@ -234,6 +234,33 @@ def test_unpadded_urlsafe_b64encode(): assert _helpers.unpadded_urlsafe_b64encode(case) == expected +def test_get_bool_from_env(monkeypatch): + # Test default value when environment variable is not set. + assert _helpers.get_bool_from_env("TEST_VAR") is False + assert _helpers.get_bool_from_env("TEST_VAR", default=True) is True + + # Test true values (case-insensitive) + for true_value in ("true", "True", "TRUE", "1"): + monkeypatch.setenv("TEST_VAR", true_value) + assert _helpers.get_bool_from_env("TEST_VAR") is True + + # Test false values (case-insensitive) + for false_value in ("false", "False", "FALSE", "0"): + monkeypatch.setenv("TEST_VAR", false_value) + assert _helpers.get_bool_from_env("TEST_VAR") is False + + # Test invalid value + monkeypatch.setenv("TEST_VAR", "invalid_value") + with pytest.raises(exceptions.InvalidValue) as excinfo: + _helpers.get_bool_from_env("TEST_VAR") + assert 'must be one of "true", "false", "1", or "0"' in str(excinfo.value) + + # Test empty string value + monkeypatch.setenv("TEST_VAR", "") + with pytest.raises(exceptions.InvalidValue): + _helpers.get_bool_from_env("TEST_VAR") + + def test_hash_sensitive_info_basic(): test_data = { "expires_in": 3599, diff --git a/packages/google-auth/tests/test_aws.py b/packages/google-auth/tests/test_aws.py index df1f02e7d702..41ce970d1e82 100644 --- a/packages/google-auth/tests/test_aws.py +++ b/packages/google-auth/tests/test_aws.py @@ -920,7 +920,7 @@ def assert_token_request_kwargs( assert request_kwargs["body"] is not None body_tuples = urllib.parse.parse_qsl(request_kwargs["body"]) assert len(body_tuples) == len(request_data.keys()) - for (k, v) in body_tuples: + for k, v in body_tuples: assert v.decode("utf-8") == request_data[k.decode("utf-8")] @classmethod @@ -2057,7 +2057,9 @@ def test_refresh_success_with_impersonation_ignore_default_scopes( "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]), "x-goog-user-project": QUOTA_PROJECT_ID, "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, - "x-allowed-locations": "0x0", + # TODO(negarb): Uncomment and update when trust boundary is supported + # for external account credentials. + # "x-allowed-locations": "0x0", } impersonation_request_data = { "delegates": None, @@ -2150,7 +2152,7 @@ def test_refresh_success_with_impersonation_use_default_scopes( "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]), "x-goog-user-project": QUOTA_PROJECT_ID, "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, - "x-allowed-locations": "0x0", + # "x-allowed-locations": "0x0", } impersonation_request_data = { "delegates": None, @@ -2345,7 +2347,7 @@ def test_refresh_success_with_supplier_with_impersonation( "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]), "x-goog-user-project": QUOTA_PROJECT_ID, "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, - "x-allowed-locations": "0x0", + # "x-allowed-locations": "0x0", } impersonation_request_data = { "delegates": None, diff --git a/packages/google-auth/tests/test_credentials.py b/packages/google-auth/tests/test_credentials.py index e11bcb4e551f..1fb880096916 100644 --- a/packages/google-auth/tests/test_credentials.py +++ b/packages/google-auth/tests/test_credentials.py @@ -13,17 +13,21 @@ # limitations under the License. import datetime +import os import mock import pytest # type: ignore from google.auth import _helpers from google.auth import credentials +from google.auth import environment_vars +from google.auth import exceptions +from google.oauth2 import _client -class CredentialsImpl(credentials.Credentials): - def refresh(self, request): - self.token = request +class CredentialsImpl(credentials.CredentialsWithTrustBoundary): + def _refresh_token(self, request): + self.token = "refreshed-token" self.expiry = ( datetime.datetime.utcnow() + _helpers.REFRESH_THRESHOLD @@ -33,6 +37,10 @@ def refresh(self, request): def with_quota_project(self, quota_project_id): raise NotImplementedError() + def _build_trust_boundary_lookup_url(self): + # Using self.token here to make the URL dynamic for testing purposes + return "http://mock.url/lookup_for_{}".format(self.token) + class CredentialsImplWithMetrics(credentials.Credentials): def refresh(self, request): @@ -89,49 +97,49 @@ def test_expired_and_valid(): def test_before_request(): credentials = CredentialsImpl() - request = "token" + request = mock.Mock() headers = {} # First call should call refresh, setting the token. credentials.before_request(request, "http://example.com", "GET", headers) assert credentials.valid - assert credentials.token == "token" - assert headers["authorization"] == "Bearer token" + assert credentials.token == "refreshed-token" + assert headers["authorization"] == "Bearer refreshed-token" assert "x-allowed-locations" not in headers - request = "token2" + request = mock.Mock() headers = {} # Second call shouldn't call refresh. credentials.before_request(request, "http://example.com", "GET", headers) assert credentials.valid - assert credentials.token == "token" - assert headers["authorization"] == "Bearer token" + assert credentials.token == "refreshed-token" + assert headers["authorization"] == "Bearer refreshed-token" assert "x-allowed-locations" not in headers def test_before_request_with_trust_boundary(): DUMMY_BOUNDARY = "0xA30" credentials = CredentialsImpl() - credentials._trust_boundary = {"locations": [], "encoded_locations": DUMMY_BOUNDARY} - request = "token" + credentials._trust_boundary = {"locations": [], "encodedLocations": DUMMY_BOUNDARY} + request = mock.Mock() headers = {} # First call should call refresh, setting the token. credentials.before_request(request, "http://example.com", "GET", headers) assert credentials.valid - assert credentials.token == "token" - assert headers["authorization"] == "Bearer token" + assert credentials.token == "refreshed-token" + assert headers["authorization"] == "Bearer refreshed-token" assert headers["x-allowed-locations"] == DUMMY_BOUNDARY - request = "token2" + request = mock.Mock() headers = {} # Second call shouldn't call refresh. credentials.before_request(request, "http://example.com", "GET", headers) assert credentials.valid - assert credentials.token == "token" - assert headers["authorization"] == "Bearer token" + assert credentials.token == "refreshed-token" + assert headers["authorization"] == "Bearer refreshed-token" assert headers["x-allowed-locations"] == DUMMY_BOUNDARY @@ -198,6 +206,18 @@ def test_readonly_scoped_credentials_scopes(): assert credentials.has_scopes(["one", "two"]) assert not credentials.has_scopes(["three"]) + # Test with default scopes + credentials_with_default = ReadOnlyScopedCredentialsImpl() + credentials_with_default._default_scopes = ["one", "two"] + assert credentials_with_default.has_scopes(["one", "two"]) + assert not credentials_with_default.has_scopes(["three"]) + + # Test with no scopes + credentials_no_scopes = ReadOnlyScopedCredentialsImpl() + assert not credentials_no_scopes.has_scopes(["one"]) + + assert credentials_no_scopes.has_scopes([]) + def test_readonly_scoped_credentials_requires_scopes(): credentials = ReadOnlyScopedCredentialsImpl() @@ -245,7 +265,7 @@ def test_nonblocking_refresh_fresh_credentials(): c._refresh_worker = mock.MagicMock() - request = "token" + request = mock.Mock() c.refresh(request) assert c.token_state == credentials.TokenState.FRESH @@ -258,7 +278,7 @@ def test_nonblocking_refresh_invalid_credentials(): c = CredentialsImpl() c.with_non_blocking_refresh() - request = "token" + request = mock.Mock() headers = {} assert c.token_state == credentials.TokenState.INVALID @@ -266,8 +286,8 @@ def test_nonblocking_refresh_invalid_credentials(): c.before_request(request, "http://example.com", "GET", headers) assert c.token_state == credentials.TokenState.FRESH assert c.valid - assert c.token == "token" - assert headers["authorization"] == "Bearer token" + assert c.token == "refreshed-token" + assert headers["authorization"] == "Bearer refreshed-token" assert "x-identity-trust-boundary" not in headers @@ -275,7 +295,7 @@ def test_nonblocking_refresh_stale_credentials(): c = CredentialsImpl() c.with_non_blocking_refresh() - request = "token" + request = mock.Mock() headers = {} # Invalid credentials MUST require a blocking refresh. @@ -296,8 +316,8 @@ def test_nonblocking_refresh_stale_credentials(): assert c.token_state == credentials.TokenState.FRESH assert c.valid - assert c.token == "token" - assert headers["authorization"] == "Bearer token" + assert c.token == "refreshed-token" + assert headers["authorization"] == "Bearer refreshed-token" assert "x-identity-trust-boundary" not in headers @@ -305,7 +325,7 @@ def test_nonblocking_refresh_failed_credentials(): c = CredentialsImpl() c.with_non_blocking_refresh() - request = "token" + request = mock.Mock() headers = {} # Invalid credentials MUST require a blocking refresh. @@ -328,18 +348,130 @@ def test_nonblocking_refresh_failed_credentials(): assert c.token_state == credentials.TokenState.FRESH assert c.valid - assert c.token == "token" - assert headers["authorization"] == "Bearer token" + assert c.token == "refreshed-token" + assert headers["authorization"] == "Bearer refreshed-token" assert "x-identity-trust-boundary" not in headers def test_token_state_no_expiry(): c = CredentialsImpl() - request = "token" + request = mock.Mock() c.refresh(request) c.expiry = None assert c.token_state == credentials.TokenState.FRESH c.before_request(request, "http://example.com", "GET", {}) + + +class TestCredentialsWithTrustBoundary(object): + @mock.patch.object(_client, "_lookup_trust_boundary") + def test_lookup_trust_boundary_env_var_not_true(self, mock_lookup_tb): + creds = CredentialsImpl() + request = mock.Mock() + + # Ensure env var is not "true" + with mock.patch.dict( + os.environ, {environment_vars.GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED: "false"} + ): + result = creds._refresh_trust_boundary(request) + + assert result is None + mock_lookup_tb.assert_not_called() + + @mock.patch.object(_client, "_lookup_trust_boundary") + def test_lookup_trust_boundary_env_var_missing(self, mock_lookup_tb): + creds = CredentialsImpl() + request = mock.Mock() + + # Ensure env var is missing + with mock.patch.dict(os.environ, clear=True): + result = creds._refresh_trust_boundary(request) + + assert result is None + mock_lookup_tb.assert_not_called() + + @mock.patch.object(_client, "_lookup_trust_boundary") + def test_lookup_trust_boundary_non_default_universe(self, mock_lookup_tb): + creds = CredentialsImpl() + creds._universe_domain = "my.universe.com" # Non-GDU + request = mock.Mock() + + with mock.patch.dict( + os.environ, {environment_vars.GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED: "true"} + ): + result = creds._refresh_trust_boundary(request) + + assert result is None + mock_lookup_tb.assert_not_called() + + @mock.patch.object(_client, "_lookup_trust_boundary") + def test_lookup_trust_boundary_calls_client_and_build_url(self, mock_lookup_tb): + creds = CredentialsImpl() + creds.token = "test_token" # For _build_trust_boundary_lookup_url + request = mock.Mock() + expected_url = "http://mock.url/lookup_for_test_token" + expected_boundary_info = {"encodedLocations": "0xABC"} + mock_lookup_tb.return_value = expected_boundary_info + + # Mock _build_trust_boundary_lookup_url to ensure it's called. + mock_build_url = mock.Mock(return_value=expected_url) + creds._build_trust_boundary_lookup_url = mock_build_url + + with mock.patch.dict( + os.environ, {environment_vars.GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED: "true"} + ): + result = creds._lookup_trust_boundary(request) + + assert result == expected_boundary_info + mock_build_url.assert_called_once() + expected_headers = {"authorization": "Bearer test_token"} + mock_lookup_tb.assert_called_once_with( + request, expected_url, headers=expected_headers + ) + + @mock.patch.object(_client, "_lookup_trust_boundary") + def test_lookup_trust_boundary_build_url_returns_none(self, mock_lookup_tb): + creds = CredentialsImpl() + request = mock.Mock() + + # Mock _build_trust_boundary_lookup_url to return None + mock_build_url = mock.Mock(return_value=None) + creds._build_trust_boundary_lookup_url = mock_build_url + + with mock.patch.dict( + os.environ, {environment_vars.GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED: "true"} + ): + with pytest.raises( + exceptions.InvalidValue, + match="Failed to build trust boundary lookup URL.", + ): + creds._lookup_trust_boundary(request) + + mock_build_url.assert_called_once() # Ensure _build_trust_boundary_lookup_url was called + mock_lookup_tb.assert_not_called() # Ensure _client.lookup_trust_boundary was not called + + @mock.patch("google.auth.credentials._LOGGER") + @mock.patch("google.auth._helpers.is_logging_enabled", return_value=True) + @mock.patch.object(_client, "_lookup_trust_boundary") + def test_refresh_trust_boundary_fails_with_cached_data_and_logging( + self, mock_lookup_tb, mock_is_logging_enabled, mock_logger + ): + creds = CredentialsImpl() + creds._trust_boundary = {"encodedLocations": "0xABC"} + request = mock.Mock() + + refresh_error = exceptions.RefreshError("Lookup failed") + mock_lookup_tb.side_effect = refresh_error + + with mock.patch.dict( + os.environ, {environment_vars.GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED: "true"} + ): + creds.refresh(request) + + mock_lookup_tb.assert_called_once() + mock_is_logging_enabled.assert_called_once_with(mock_logger) + mock_logger.debug.assert_called_once_with( + "Using cached trust boundary due to refresh error: %s", refresh_error + ) diff --git a/packages/google-auth/tests/test_external_account.py b/packages/google-auth/tests/test_external_account.py index bddcb4afa1af..d86a19bef160 100644 --- a/packages/google-auth/tests/test_external_account.py +++ b/packages/google-auth/tests/test_external_account.py @@ -247,7 +247,7 @@ def assert_token_request_kwargs( assert "cert" not in request_kwargs assert request_kwargs["body"] is not None body_tuples = urllib.parse.parse_qsl(request_kwargs["body"]) - for (k, v) in body_tuples: + for k, v in body_tuples: assert v.decode("utf-8") == request_data[k.decode("utf-8")] assert len(body_tuples) == len(request_data.keys()) @@ -920,7 +920,9 @@ def test_refresh_impersonation_without_client_auth_success( "Content-Type": "application/json", "authorization": "Bearer {}".format(token_response["access_token"]), "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, - "x-allowed-locations": "0x0", + # TODO(negarb): Uncomment and update when trust boundary is supported + # for external account credentials. + # "x-allowed-locations": "0x0", } impersonation_request_data = { "delegates": None, @@ -1010,7 +1012,7 @@ def test_refresh_impersonation_with_mtls_success( "Content-Type": "application/json", "authorization": "Bearer {}".format(token_response["access_token"]), "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, - "x-allowed-locations": "0x0", + # "x-allowed-locations": "0x0", } impersonation_request_data = { "delegates": None, @@ -1097,7 +1099,7 @@ def test_refresh_workforce_impersonation_without_client_auth_success( "Content-Type": "application/json", "authorization": "Bearer {}".format(token_response["access_token"]), "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, - "x-allowed-locations": "0x0", + # "x-allowed-locations": "0x0", } impersonation_request_data = { "delegates": None, @@ -1331,7 +1333,7 @@ def test_refresh_impersonation_with_client_auth_success_ignore_default_scopes( "Content-Type": "application/json", "authorization": "Bearer {}".format(token_response["access_token"]), "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, - "x-allowed-locations": "0x0", + # "x-allowed-locations": "0x0", } impersonation_request_data = { "delegates": None, @@ -1415,7 +1417,7 @@ def test_refresh_impersonation_with_client_auth_success_use_default_scopes( "Content-Type": "application/json", "authorization": "Bearer {}".format(token_response["access_token"]), "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, - "x-allowed-locations": "0x0", + # "x-allowed-locations": "0x0", } impersonation_request_data = { "delegates": None, @@ -1471,7 +1473,7 @@ def test_apply_without_quota_project_id(self): assert headers == { "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]), - "x-allowed-locations": "0x0", + # "x-allowed-locations": "0x0", } def test_apply_workforce_without_quota_project_id(self): @@ -1488,7 +1490,7 @@ def test_apply_workforce_without_quota_project_id(self): assert headers == { "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]), - "x-allowed-locations": "0x0", + # "x-allowed-locations": "0x0", } def test_apply_impersonation_without_quota_project_id(self): @@ -1520,7 +1522,7 @@ def test_apply_impersonation_without_quota_project_id(self): assert headers == { "authorization": "Bearer {}".format(impersonation_response["accessToken"]), - "x-allowed-locations": "0x0", + # "x-allowed-locations": "0x0", } def test_apply_with_quota_project_id(self): @@ -1537,7 +1539,7 @@ def test_apply_with_quota_project_id(self): "other": "header-value", "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]), "x-goog-user-project": self.QUOTA_PROJECT_ID, - "x-allowed-locations": "0x0", + # "x-allowed-locations": "0x0", } def test_apply_impersonation_with_quota_project_id(self): @@ -1572,7 +1574,7 @@ def test_apply_impersonation_with_quota_project_id(self): "other": "header-value", "authorization": "Bearer {}".format(impersonation_response["accessToken"]), "x-goog-user-project": self.QUOTA_PROJECT_ID, - "x-allowed-locations": "0x0", + # "x-allowed-locations": "0x0", } def test_before_request(self): @@ -1588,7 +1590,7 @@ def test_before_request(self): assert headers == { "other": "header-value", "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]), - "x-allowed-locations": "0x0", + # "x-allowed-locations": "0x0", } # Second call shouldn't call refresh. @@ -1597,7 +1599,7 @@ def test_before_request(self): assert headers == { "other": "header-value", "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]), - "x-allowed-locations": "0x0", + # "x-allowed-locations": "0x0", } def test_before_request_workforce(self): @@ -1615,7 +1617,7 @@ def test_before_request_workforce(self): assert headers == { "other": "header-value", "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]), - "x-allowed-locations": "0x0", + # "x-allowed-locations": "0x0", } # Second call shouldn't call refresh. @@ -1624,7 +1626,7 @@ def test_before_request_workforce(self): assert headers == { "other": "header-value", "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]), - "x-allowed-locations": "0x0", + # "x-allowed-locations": "0x0", } def test_before_request_impersonation(self): @@ -1655,7 +1657,7 @@ def test_before_request_impersonation(self): assert headers == { "other": "header-value", "authorization": "Bearer {}".format(impersonation_response["accessToken"]), - "x-allowed-locations": "0x0", + # "x-allowed-locations": "0x0", } # Second call shouldn't call refresh. @@ -1664,7 +1666,7 @@ def test_before_request_impersonation(self): assert headers == { "other": "header-value", "authorization": "Bearer {}".format(impersonation_response["accessToken"]), - "x-allowed-locations": "0x0", + # "x-allowed-locations": "0x0", } @mock.patch("google.auth._helpers.utcnow") @@ -1693,7 +1695,7 @@ def test_before_request_expired(self, utcnow): # Cached token should be used. assert headers == { "authorization": "Bearer token", - "x-allowed-locations": "0x0", + # "x-allowed-locations": "0x0", } # Next call should simulate 1 second passed. @@ -1709,7 +1711,7 @@ def test_before_request_expired(self, utcnow): # New token should be retrieved. assert headers == { "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]), - "x-allowed-locations": "0x0", + # "x-allowed-locations": "0x0", } @mock.patch("google.auth._helpers.utcnow") @@ -1754,7 +1756,7 @@ def test_before_request_impersonation_expired(self, utcnow): # Cached token should be used. assert headers == { "authorization": "Bearer token", - "x-allowed-locations": "0x0", + # "x-allowed-locations": "0x0", } # Next call should simulate 1 second passed. This will trigger the expiration @@ -1773,7 +1775,7 @@ def test_before_request_impersonation_expired(self, utcnow): # New token should be retrieved. assert headers == { "authorization": "Bearer {}".format(impersonation_response["accessToken"]), - "x-allowed-locations": "0x0", + # "x-allowed-locations": "0x0", } @pytest.mark.parametrize( @@ -1872,7 +1874,7 @@ def test_get_project_id_cloud_resource_manager_success( "x-goog-user-project": self.QUOTA_PROJECT_ID, "authorization": "Bearer {}".format(token_response["access_token"]), "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, - "x-allowed-locations": "0x0", + # "x-allowed-locations": "0x0", } impersonation_request_data = { "delegates": None, @@ -1926,7 +1928,7 @@ def test_get_project_id_cloud_resource_manager_success( "authorization": "Bearer {}".format( impersonation_response["accessToken"] ), - "x-allowed-locations": "0x0", + # "x-allowed-locations": "0x0", }, ) @@ -1998,7 +2000,7 @@ def test_workforce_pool_get_project_id_cloud_resource_manager_success( "authorization": "Bearer {}".format( self.SUCCESS_RESPONSE["access_token"] ), - "x-allowed-locations": "0x0", + # "x-allowed-locations": "0x0", }, ) @@ -2048,7 +2050,7 @@ def test_refresh_impersonation_with_lifetime( "Content-Type": "application/json", "authorization": "Bearer {}".format(token_response["access_token"]), "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, - "x-allowed-locations": "0x0", + # "x-allowed-locations": "0x0", } impersonation_request_data = { "delegates": None, diff --git a/packages/google-auth/tests/test_identity_pool.py b/packages/google-auth/tests/test_identity_pool.py index 41fd18892a32..ec0f5074c4b5 100644 --- a/packages/google-auth/tests/test_identity_pool.py +++ b/packages/google-auth/tests/test_identity_pool.py @@ -284,7 +284,7 @@ def assert_token_request_kwargs( assert request_kwargs["body"] is not None body_tuples = urllib.parse.parse_qsl(request_kwargs["body"]) assert len(body_tuples) == len(request_data.keys()) - for (k, v) in body_tuples: + for k, v in body_tuples: assert v.decode("utf-8") == request_data[k.decode("utf-8")] @classmethod @@ -383,7 +383,9 @@ def assert_underlying_credentials_refresh( "Content-Type": "application/json", "authorization": "Bearer {}".format(token_response["access_token"]), "x-goog-api-client": metrics_header_value, - "x-allowed-locations": "0x0", + # TODO(negarb): Uncomment and update when trust boundary is supported + # for external account credentials. + # "x-allowed-locations": "0x0", } impersonation_request_data = { "delegates": None, diff --git a/packages/google-auth/tests/test_impersonated_credentials.py b/packages/google-auth/tests/test_impersonated_credentials.py index 4aa357e3e07e..05cdb9dcc64d 100644 --- a/packages/google-auth/tests/test_impersonated_credentials.py +++ b/packages/google-auth/tests/test_impersonated_credentials.py @@ -22,7 +22,9 @@ import pytest # type: ignore from google.auth import _helpers +from google.auth import credentials as auth_credentials from google.auth import crypt +from google.auth import environment_vars from google.auth import exceptions from google.auth import impersonated_credentials from google.auth import transport @@ -127,8 +129,21 @@ class TestImpersonatedCredentials(object): # Because Python 2.7: DELEGATES = [] # type: ignore LIFETIME = 3600 + NO_OP_TRUST_BOUNDARY = { + "locations": auth_credentials.NO_OP_TRUST_BOUNDARY_LOCATIONS, + "encodedLocations": auth_credentials.NO_OP_TRUST_BOUNDARY_ENCODED_LOCATIONS, + } + VALID_TRUST_BOUNDARY = { + "locations": ["us-central1", "us-east1"], + "encodedLocations": "0xVALIDHEX", + } + EXPECTED_TRUST_BOUNDARY_LOOKUP_URL_DEFAULT_UNIVERSE = ( + "https://iamcredentials.googleapis.com/v1/projects/-" + "/serviceAccounts/impersonated@project.iam.gserviceaccount.com/allowedLocations" + ) + FAKE_UNIVERSE_DOMAIN = "universe.foo" SOURCE_CREDENTIALS = service_account.Credentials( - SIGNER, SERVICE_ACCOUNT_EMAIL, TOKEN_URI + SIGNER, SERVICE_ACCOUNT_EMAIL, TOKEN_URI, trust_boundary=NO_OP_TRUST_BOUNDARY ) USER_SOURCE_CREDENTIALS = credentials.Credentials(token="ABCDE") IAM_ENDPOINT_OVERRIDE = ( @@ -143,6 +158,7 @@ def make_credentials( target_principal=TARGET_PRINCIPAL, subject=None, iam_endpoint_override=None, + trust_boundary=None, # Align with Credentials class default ): return Credentials( @@ -153,6 +169,7 @@ def make_credentials( lifetime=lifetime, subject=subject, iam_endpoint_override=iam_endpoint_override, + trust_boundary=trust_boundary, ) def test_from_impersonated_service_account_info(self): @@ -162,7 +179,7 @@ def test_from_impersonated_service_account_info(self): assert isinstance(credentials, impersonated_credentials.Credentials) def test_from_impersonated_service_account_info_with_invalid_source_credentials_type( - self + self, ): info = copy.deepcopy(IMPERSONATED_SERVICE_ACCOUNT_AUTHORIZED_USER_SOURCE_INFO) assert "source_credentials" in info @@ -177,7 +194,7 @@ def test_from_impersonated_service_account_info_with_invalid_source_credentials_ ) def test_from_impersonated_service_account_info_with_invalid_impersonation_url( - self + self, ): info = copy.deepcopy(IMPERSONATED_SERVICE_ACCOUNT_AUTHORIZED_USER_SOURCE_INFO) info["service_account_impersonation_url"] = "invalid_url" @@ -262,8 +279,12 @@ def test_token_usage_metrics(self): assert headers["x-goog-api-client"] == "cred-type/imp" @pytest.mark.parametrize("use_data_bytes", [True, False]) - def test_refresh_success(self, use_data_bytes, mock_donor_credentials): - credentials = self.make_credentials(lifetime=None) + @mock.patch("google.oauth2._client._lookup_trust_boundary") + def test_refresh_success( + self, mock_lookup_trust_boundary, use_data_bytes, mock_donor_credentials + ): + # Start with no boundary. + credentials = self.make_credentials(lifetime=None, trust_boundary=None) token = "token" expire_time = ( @@ -277,7 +298,12 @@ def test_refresh_success(self, use_data_bytes, mock_donor_credentials): use_data_bytes=use_data_bytes, ) - with mock.patch( + # Mock the trust boundary lookup to return a valid value. + mock_lookup_trust_boundary.return_value = self.VALID_TRUST_BOUNDARY + + with mock.patch.dict( + os.environ, {environment_vars.GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED: "true"} + ), mock.patch( "google.auth.metrics.token_request_access_token_impersonate", return_value=ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, ): @@ -290,6 +316,239 @@ def test_refresh_success(self, use_data_bytes, mock_donor_credentials): == ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE ) + # Verify that the x-allowed-locations header from the source credential + # was applied. The source credential has a NO_OP boundary, so the + # header should be an empty string. + request_kwargs = request.call_args[1] + assert "headers" in request_kwargs + assert "x-allowed-locations" in request_kwargs["headers"] + assert request_kwargs["headers"]["x-allowed-locations"] == "" + + # Verify trust boundary was set. + assert credentials._trust_boundary == self.VALID_TRUST_BOUNDARY + + # Verify the mock was called with the correct URL. + mock_lookup_trust_boundary.assert_called_once_with( + request, + self.EXPECTED_TRUST_BOUNDARY_LOOKUP_URL_DEFAULT_UNIVERSE, + headers={"authorization": "Bearer token"}, + ) + + # Verify x-allowed-locations header is set correctly by apply(). + headers_applied = {} + credentials.apply(headers_applied) + assert ( + headers_applied["x-allowed-locations"] + == self.VALID_TRUST_BOUNDARY["encodedLocations"] + ) + + def test_refresh_source_creds_no_trust_boundary(self): + # Use a source credential that does not support trust boundaries. + source_credentials = credentials.Credentials(token="source_token") + creds = self.make_credentials(source_credentials=source_credentials) + token = "impersonated_token" + + expire_time = ( + _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=500) + ).isoformat("T") + "Z" + response_body = {"accessToken": token, "expireTime": expire_time} + + request = self.make_request( + data=json.dumps(response_body), status=http_client.OK + ) + + creds.refresh(request) + + # Verify that the x-allowed-locations header was NOT applied because + # the source credential does not support trust boundaries. + request_kwargs = request.call_args[1] + assert "x-allowed-locations" not in request_kwargs["headers"] + + @mock.patch("google.oauth2._client._lookup_trust_boundary") + def test_refresh_trust_boundary_lookup_fails_no_cache( + self, mock_lookup_trust_boundary, mock_donor_credentials + ): + # Start with no trust boundary + credentials = self.make_credentials(lifetime=None, trust_boundary=None) + token = "token" + + expire_time = ( + _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=500) + ).isoformat("T") + "Z" + response_body = {"accessToken": token, "expireTime": expire_time} + + request = self.make_request( + data=json.dumps(response_body), status=http_client.OK + ) + + # Mock the trust boundary lookup to raise an error + mock_lookup_trust_boundary.side_effect = exceptions.RefreshError( + "Lookup failed" + ) + + with mock.patch.dict( + os.environ, {environment_vars.GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED: "true"} + ), pytest.raises(exceptions.RefreshError) as excinfo: + credentials.refresh(request) + + assert "Lookup failed" in str(excinfo.value) + assert credentials._trust_boundary is None # Still no trust boundary + mock_lookup_trust_boundary.assert_called_once() + + @mock.patch("google.oauth2._client._lookup_trust_boundary") + def test_refresh_fetches_no_op_trust_boundary( + self, mock_lookup_trust_boundary, mock_donor_credentials + ): + # Start with no trust boundary + credentials = self.make_credentials(lifetime=None, trust_boundary=None) + token = "token" + expire_time = ( + _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=500) + ).isoformat("T") + "Z" + response_body = {"accessToken": token, "expireTime": expire_time} + request = self.make_request( + data=json.dumps(response_body), status=http_client.OK + ) + + mock_lookup_trust_boundary.return_value = ( + self.NO_OP_TRUST_BOUNDARY + ) # Mock returns NO_OP + + with mock.patch.dict( + os.environ, {environment_vars.GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED: "true"} + ), mock.patch( + "google.auth.metrics.token_request_access_token_impersonate", + return_value=ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, + ): + credentials.refresh(request) + + assert credentials._trust_boundary == self.NO_OP_TRUST_BOUNDARY + mock_lookup_trust_boundary.assert_called_once_with( + request, + self.EXPECTED_TRUST_BOUNDARY_LOOKUP_URL_DEFAULT_UNIVERSE, + headers={"authorization": "Bearer token"}, + ) + headers_applied = {} + credentials.apply(headers_applied) + assert headers_applied["x-allowed-locations"] == "" + + @mock.patch("google.oauth2._client._lookup_trust_boundary") + def test_refresh_skips_trust_boundary_lookup_non_default_universe( + self, mock_lookup_trust_boundary + ): + # Create source credentials with a non-default universe domain + source_credentials = service_account.Credentials( + SIGNER, + "some@email.com", + TOKEN_URI, + universe_domain=self.FAKE_UNIVERSE_DOMAIN, + ) + # Create impersonated credentials using the non-default source credentials + credentials = self.make_credentials(source_credentials=source_credentials) + + # Mock the IAM credentials API call for generateAccessToken + token = "token" + expire_time = ( + _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=500) + ).isoformat("T") + "Z" + response_body = {"accessToken": token, "expireTime": expire_time} + request = self.make_request( + data=json.dumps(response_body), status=http_client.OK + ) + + with mock.patch.dict( + os.environ, {environment_vars.GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED: "true"} + ): + credentials.refresh(request) + + # Ensure trust boundary lookup was not called + mock_lookup_trust_boundary.assert_not_called() + # Verify that x-allowed-locations header is not set by apply() + headers_applied = {} + credentials.apply(headers_applied) + assert "x-allowed-locations" not in headers_applied + + @mock.patch("google.oauth2._client._lookup_trust_boundary") + def test_refresh_starts_with_no_op_trust_boundary_skips_lookup( + self, mock_lookup_trust_boundary, mock_donor_credentials + ): + credentials = self.make_credentials( + lifetime=None, trust_boundary=self.NO_OP_TRUST_BOUNDARY + ) # Start with NO_OP + token = "token" + expire_time = ( + _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=500) + ).isoformat("T") + "Z" + response_body = {"accessToken": token, "expireTime": expire_time} + request = self.make_request( + data=json.dumps(response_body), status=http_client.OK + ) + with mock.patch.dict( + os.environ, {environment_vars.GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED: "true"} + ), mock.patch( + "google.auth.metrics.token_request_access_token_impersonate", + return_value=ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, + ): + credentials.refresh(request) + + # Verify trust boundary remained NO_OP + assert credentials._trust_boundary == self.NO_OP_TRUST_BOUNDARY + + # Lookup should be skipped + mock_lookup_trust_boundary.assert_not_called() + + # Verify that an empty header was added. + headers_applied = {} + credentials.apply(headers_applied) + assert headers_applied["x-allowed-locations"] == "" + + @mock.patch("google.oauth2._client._lookup_trust_boundary") + def test_refresh_trust_boundary_lookup_fails_with_cached_data2( + self, mock_lookup_trust_boundary, mock_donor_credentials + ): + # Start with no trust boundary + credentials = self.make_credentials(lifetime=None, trust_boundary=None) + token = "token" + + expire_time = ( + _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=500) + ).isoformat("T") + "Z" + response_body = {"accessToken": token, "expireTime": expire_time} + + request = self.make_request( + data=json.dumps(response_body), status=http_client.OK + ) + + # First refresh: Successfully fetch a valid trust boundary. + mock_lookup_trust_boundary.return_value = self.VALID_TRUST_BOUNDARY + with mock.patch.dict( + os.environ, {environment_vars.GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED: "true"} + ), mock.patch( + "google.auth.metrics.token_request_access_token_impersonate", + return_value=ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, + ): + credentials.refresh(request) + + assert credentials.valid + # Verify trust boundary was set. + assert credentials._trust_boundary == self.VALID_TRUST_BOUNDARY + mock_lookup_trust_boundary.assert_called_once() + + # Second refresh: Mock lookup to fail, but expect cached data to be preserved. + mock_lookup_trust_boundary.reset_mock() + mock_lookup_trust_boundary.side_effect = exceptions.RefreshError( + "Lookup failed" + ) + + with mock.patch.dict( + os.environ, {environment_vars.GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED: "true"} + ): + credentials.refresh(request) + + assert credentials.valid + assert credentials._trust_boundary == self.VALID_TRUST_BOUNDARY + mock_lookup_trust_boundary.assert_called_once() + @pytest.mark.parametrize("use_data_bytes", [True, False]) def test_refresh_with_subject_success(self, use_data_bytes, mock_dwd_credentials): credentials = self.make_credentials(subject="test@email.com", lifetime=None) @@ -672,6 +931,37 @@ def test_with_scopes(self): assert credentials.requires_scopes is False assert credentials._target_scopes == ["fake_scope1", "fake_scope2"] + def test_with_trust_boundary(self): + credentials = self.make_credentials() + new_boundary = {"encodedLocations": "new_boundary"} + new_credentials = credentials.with_trust_boundary(new_boundary) + + assert new_credentials is not credentials + assert new_credentials._trust_boundary == new_boundary + # The source credentials should be a copy, not the same object. + # But they should be functionally equivalent. + assert ( + new_credentials._source_credentials is not credentials._source_credentials + ) + + assert ( + new_credentials._source_credentials.service_account_email + == credentials._source_credentials.service_account_email + ) + assert ( + new_credentials._source_credentials._signer + == credentials._source_credentials._signer + ) + assert new_credentials._target_principal == credentials._target_principal + + def test_build_trust_boundary_lookup_url_no_email(self): + credentials = self.make_credentials(target_principal=None) + + with pytest.raises(ValueError) as excinfo: + credentials._build_trust_boundary_lookup_url() + + assert "Service account email is required" in str(excinfo.value) + def test_with_scopes_provide_default_scopes(self): credentials = self.make_credentials() credentials._target_scopes = [] From 50edc242d0bb0a0c0062f47e9102b22444a8a1ce Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Tue, 12 Aug 2025 09:34:30 -0700 Subject: [PATCH 923/966] chore: update secret (#1803) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 528481fce726de87f6c7f249713f524084ed4689..b826f451ea5c6a6d45f305b9040fec8daa8b6f85 100644 GIT binary patch literal 10324 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 literal 10324 zcmV-aD67{BB>?tKRTD%MN!3V(PoH7X3U6{oRXD9qXK)}>&pw6QL3gHbuVoUdPyh`& z(zZ)`z^2OMdFoNmf`ORhdL`wM6VDu21qV2X0umGphHf;dF-PGHDM~i`i7M4|U~k8W zBg@}PLEYg@q42dk3Y~D>lJegpuIWUL5N9P2MHQ#go$;I=LGaXGj4^dM?IF5zpxMcA zZ(L|e;Y2xX#0I=xJdo!3zHxSdSWl>+5Ej2Q6m%G zfW+6b)=c8jeCwd!qbs6OAfd_<6l?F;+Ny=6hF41IXUSQ~Kt)J=!VGTLo3Tbnc))ih z^Omx%ax-rskbuJJWh{FZxPmWHaaWEhHe3UvidZ4!vytBOU>g|C$iab>u5{69p!&Tb zgXZg^{Pee8x@i%vXv)cOVARE8GF?-c&u^177{hTx;gHd1Xeq$$3Ur+K=6wetu1Py3gpHtO&1p5Br*chGBDjTv~;QbkkXqi}!pCy;|%H*Q_$dsicM?5N(7w<78 z(inM=zoAml2lV?^rLrQBS9AS$9P{dbL|<|_Fh8qo$V(m>9Mz8%h#e^$!(JiU`UZg- zFlaJiL<`(8P!$}vOrJH1$5u;oZoCQ>u_9m_3{(z38&V|HpdivRF1vVwJ}0P|5$Aq= zwH2S2Naw|b7YX#e-AfM{dxS$U8R9s6<4Cs7F zL~^wS%2OXfoFH@xy9_)@$)x%&nK6gPp1QA;9#&YX0`1X}lNfQm2#;%}UH z5q1~M^iM;RgV!#V{1u3n-K9L4U!I!trEib^4xILkQ!ZcbRGa``zu`L=oeAW z2x~z#nOZYsAJA}B;)9f%XEz@I+W0jt&w$fOqd;)F^jkc;<>J3REK1+GD~tV#w+7za zTz-ub(>&tqsoMDE*9q3!srs9j8Yc#L-O&KMUxN<32&!YUhFX8S1~ytd;*Fb1{y-Lc zSFL6hj1=I(K-ea`M2^khJPY=FC#+L$VRjYu`VmS6g!0=hd0$n{)kmCK1sHqMqA{FR zD983ih-^9=Dsd^LB+?qVz-`$?xXSZtp8~dB#OXgsFKM8ig7y2++395p@-+Q7dOclv zLQLWVaGuCc-oOZ|d2DjMk!ClHO2{GHXjxRI11HIP!aQ*b1~sCuo_E}m{5$D9_OtMy z7COGAW-2O1WAXv_eMU5Rp)O{H>MXP^w1lN33*&68m+V2L?dKr8^PKCOdjB0s!xIS@ zkcCX)4$6F&O}{>pX;Bb=ac~>(@a}^m$c@y-@+CQdesV-g*Th z8gH$EM-(3wj@!hv*!B#FczqO(Bv-0Ti*fO3XZy=k3S;?;Fm`S#jGG-H`k~=tb!06= znG~~?wqv$!&gGo=D08Y~@PA0G`S(rGc@|D45tdtqBu^=_^;S=Y-U#+MnZAk|y6_4SvOMU3 z96Fdrn8I13%x>CzEcfI*y-C>wP${=Lv}8dMy%1Fx+lp`u&o+k@qV3fKOL#n?ujUlQ zOF%{O9h-3kiEKmTv1`bG)^!ZJFM>_(OafkhRC^O7Fr}6?cmEQ=xy27W=$sXnk&PWY z;#G3pUBldHOsj!*>^1YN#Z>FwIA^9Cf#_2TU2a{;e%gN5?azK= z1Q>-;xu3+`9QDLyD*1xFjHfG`kcNx;b~av)hY8}AAgY`Y%skxD>ESLV|d^I!;*=OSUsbV2xH$o-Bsz(x+^zXsLL%I z74F$TH$3!4e}BtuTR2$1F_6aoodtlc$pUE{-Lr^jPM_=u!@kw#t`t8fPo8ei))I1) zqPx+Jy}OMWS^6m;3j{DV7eed^rO;(TrF|O!H4B?vrt|Y|tF%)ppS=jTU$A1r)*qiicJZ6Z0bTy(Ql zfrdmm!Su}kn8|$kmL)~RnU0g5BiznHBGScLR}3!A=4LAPdaLYBVkkSH)$`l$eLjO^ z;h)&}afY3n@Lsf-ZR$DyJh_9We!~1eW?i>?ke!k@A${sYFyt(6xbK@y+dnI*EXDKHi&PkxGez$s0JuN9CS6VEgl` zfe#c;?&6{%>GaVB9`~(8?jZ06Vg@&si9e@n*i(GuI6()hPkfeESNe9i_FoxD3Eq=? zFrnG(XRuF~3C#*dUeZOnxzVV10U^Y9(953H#g5OEPEgwgYYe(sb-D?Vapc*}V%$@? zPn2h8?5DiP2^PRYyq0wNw_N?pLib6~2QR`Q^fITaYu-MNPaB*$hA3_p7+`A#XS;u- zznCbrRo+D_&pvMRnu}w_nh4qFt9)D$%U|IE;x^PmCiiFX-QBw)Q>t%#j$r<`5f$+)3e zkcdyzn{HKatqj;0H>FzoseB(aR@eP4J#&S*`O++VnenUPoo3&N5U9cwAcUO_kY`%c z5Kx=bnk-D3V}ERep!79$dl9+{r6w)3{d}(h`}#*rjk3A>C+UIQOW&Ie>){f)xMB{J z^5r!0jH&ZrtR75AHM-)1J>^~U92yKg6udr=(10gu$Lpk;%Eil7Jmx_KZYDJ)gZ|~$ z?0LYRG5eYZ;5ZF*`BS3t7xK@il8$GK<`~WS=FmaWjG!my#_HGn)#Wdizf5V=L?V^& z-FDDjsLPI^{v4=6<{vPTlr*gzUpS~k)~bR2-I1GLqtt-Kc`qI}0HM2K%_)_~P2<&^ z%(vrGA0VS(!=2p^=cee-LPAITy?fmn_fL@tXO=WCcOrq_kkLYaYGc?#au82;*YQ{L zWs3gPL?jeclR&&3%FQaC57$_}$S7?pH~`bXTlQ3E15q2uyHW2@k9Se8-f&t={9Z7q zEw-`TIDvm7EO0hs4U{`A8{KNUm~hd|*Q3=tfJ}T_Y^`Jc(|M6jg@hl?Up!`JQO(Z8 z!7YVV>V{a6Tfz5qtj;;3q-S|b$sHV@5l}3GJ3Cy`9rSVTgU<_?XDn~KHu=;2I`-&w z?>rj47};pqKy7As=9w3V6tEWQIO{LnP9XELn)gbNDuYSP=snE9xJy4Wn#_nY(ZgN2 zQtKAs&@5oi!ZxgQbYiEPH4WVKSO1;u?tqZkqbC2kf z+sTx_4XIca&uV(`=l88n6dsOM-3Zre#{wP@5nwwuoZ_=m zrpM8+>aKm-ym2nnI9W*M;k1qY_>wi2sfl5Mb|1Rpu{m>UHtyzKo^PCs2IbZj`Jlxe zDEv`E7aP045Y{p@O0q|YF}|I{K#`Mn&m-KZz?5^>_ae!9kHZ0uxjl=A<4J$2)+}CN z>#ddEWfnE*SP58+r*i7xt#HP$8~G&8PMe+m3Y(Y$i%HB;R{ThBA#iMDwkI6rPg`nv zm5_{&VD;`$IYlt4X}1kSi+juu-WM8!<;H}#Y|;h`=em0w0v}objwLkfBQ&cY?^>l| z%*n{<>+hsD1Mk=lJePGWni@%0KCod@fLkBwO^dz3QMX`VNY;F`0Fl0^PIMwrB08YXBPt7%%HSGA8Q*w3A)HXS^gvw@Y^AUb{8EM=MIyGc>)PDvwyU-EsL;s@LdH%gvLZfjK?E zw-{B_k%1LUZHl}m@ei4@hVFpd;a?}76Zd8W&L5#p)|$aerml{DrNQ&NR8?QYv91Gg zy9P0t&FmQoCHSX$@WmG1>n9)zUOV8-N8UZEc#MK-$#j|QjOzh(zCw$%sTtcT z=+sCtV+EZBO<-0uARA2X{3(+S8#O=1(h0C{mm~`AEr36-TR&+u{uBSZr2h6Dip7=e zk7xJ%>;RC;lI|w+x`hTu#7sBYICy`#9tm*YIS}M7cy|kEH>e#1Aj&qofBYt;W)t%^UgQ;TFV${IdGz61xx19$&mUbvoW&gM}S#}vIi{PW)?F6m5qlo5=pwWTN|w-s^dr_!zju&)h|T;TCSb*KAB0gSh`1~WQWxc_9tArE27aobI zyz?>UM(+O8WaxQxV8pg->7YW-*+or6n|^+sTkql1Az{iA>t}kzH0F~7>c+)HO(sCN z0HIW}pQJli<+ql(G2nF@`G_qb9-_bJ5OL$*iHFZmT=s5VW}+Puk7ijwlxe=Bis!Ax*j zSt0Gc547GoE21T?aS9FoDAtri3n&#kxnW|Qw`=^A5ss2f&O`(b`XUxLJ9!!jLu8>x z4cc3i@biY^+A*bfO{hZ((W?F#D|BNh>Oma%1n`Wj@ zj5zMe#)*IwMI|5ToD~b^#R=oh92-@v)>iye*KZTD?Zh!E^zAqV$h>W+-Rw4$OphNF zk`AVGJ(#9bPzEKo*HS}pxH?3WP6r;#7Xb+6s0XV-SOI|ks!GZ%1xm!*J6K{tSWV&| z9}|S#Y)`sEU=k;L+i0G1#4!BgNh)I`GY&qz*sAP_NE>OxzvTRWg&Uf7v#wf?WvT6N zL}zf77jb|SDzrVlk|Gdoh42<~%#}73U78N1X=SE?nP+W+6zQY@*>i`NgU8yu0acdnMRGi2!-OTLx4j0MJCDqNt<*DlQtF9s$1%au?Z&q3bo~Hx9hNQULfZ zJS>-9^S)zH&@WF^yW~WcdvZeYr(9B2m+sM)DS@9dq5bG?NNHv`QU^0@KR!%3kwnni}%rb7rXPA$45wni+;DKqUp(28iw}HSw$WN@f z``#h{Tax;6#<#GZ7HPBJA`_B5kb>b^(Sz5Pn*PDRm+&5bM(##7Mx;JRq0_#%ocw{W zluo0FzR%w64IAbF?#R0kyQ$8Vi`lW}waeh@>@iEkVUG#phCjIZ+eXou$=;4S^ZQ8d zv;hh%f&Vo$} z`NoRMbKu1=8Pemg6pJ7Qvb55nwYJ@pCvx?Fhd}zOs9+Gm{wV+&PRkJOG`q9hY_yYQ ztwb6l{3FJ2kBlq)3g3_}v8#>%6xqHz!8;2-f0*+&bldzRrgEE}(6Y2sLN?YCJdyRl zWo~eN#KDFGB@bH=vk2y7&#dr&Xrp-~8_T(7c$Uxv43#f#fGW1A#B6JsZKseO_!;5h zOj@Zz=nWjwtMgw>|C(`wvLj@hagvee%P5TfB;N|{Fx}XYeECT8i%kps11<-la!pMH^<_X z)I=qsSw~s@2}^Z@1KJ7s2+9#*JU5Zj&wD&JZCw(5?jrZeb5to#KH+I@8=i%^jB0+K zhPx3}OnEa}n@?P)s#T9smOtcwc4=-?71hZrf6$X-#Xv5 z<}Oky>Pm2I-} z?Oe^!uMdA2vT`(e2JpIqFx0GW`T0u#gq_kRHs6I+uXuk2N;NeaoYgh?Tb{-Fiqvsa z8HeRq0@8CIj)Nh{yXq42bA3E{TIT2IV{3}gLwcc5i>j|67Cye)0~?FIS-<~uFNY)~ zr~|3m(+dQtcSUylSaHDwi8picqX6w^4Hh0!`Qk6lgQH_+X$hC!4nqFygSlV{h2JBa ztmyH8y^lSqC zNI=Byw=w?CDJd1_qo(xJ?4gcauBKQ+5YJG7RsU+Od(D@>1|qrU({IazUep3ciu+^b z=SHj-z~TiO1l5=$%uTsF8a3{tlTs=#t(Yj~2Eo1Wn%%N#mwQw2I?t&-&A3zfSRVK7 z7WU_ASgj0c@H_^~sw9j7II4A$kSJ&;e?k+v=3)Jjb%-8p6|Iu8jqxMb$H>aA&>W~D zbchIy-s3@y0b9ux92r2Ndt{Uj1(m?8CXp%xY=eK_g`~#pXIE6cjji@4NzL|&HoAHBqrjfzEwW9# zu3NrxQkTO%7Djr+a`Be8@+YbQ25qZRzUO{+e$)Dk9@;Uj9dnvTlmpGJ80xtf#azQV zd!tFHBTZ;b6pqiA7iK!Y?0=XazbYt`S656BFZhD7sAJB)=*G%cn+X|UG#pEbpr~#% zMVqX5Tt2{g3g)kLY}ov}-|hmVqF@R>STOeS)-U75-vt*(mtUToGJToAEfXh84X|Vc z(zT6i+-L;7oV)Cx`U5HC%QkHWy*sM)Lw$Cn32kR~H!P;U(6XSm#h*ky9q;$CuBd=z z$#jL@DZT~-PBlENdM^3$zd+TaRbx6(g+zS~A6$rQ_xiAsSS^-TRXOGSeH!)XW0@fy zgWZ^Nxrn;KoLHkcp7)IZ5a^>fFJ+#4ztk#=W?v+!CGpuoBH`H<@F)!C5h$<5sIjDU zCA#_J%;;JgvFLwZL)dzapuLFP&R<5V9+wKz#L}u(lL%9?GpMh%sZPG102pp#KuALaH3}I1kez(}F*wtzeQ|Z!Un+$G;m>0;&ngde0~k}P0X+LCRXFL zS@vlXUX8Yj3%y>ON=Gm$)%M-Clts|EaOU5(a+e6|a|mo}vT}Kfye(~Sni`O zNl`=YNi??+8s(aW(LbkkMY8(0sr#Vj-~2gzH{sy2p)`$o(ADGB8GYg-33BVy4r)mZ z9lXVe&p6lzVhaZp5_;@Y|5dqSa~Ve(pz2dgRJTFRHBCmrw?g=aI~Ex$L2?q8kLyOV z29%2$*SqdZXlD%%0{=MP)TEJj7Gw0T<&>~;t$`#K9KyJal>F9FN=d&aGQln;Yg!}} zC&0zT5&{=Lr19Jb@;lSGmVU7DGuMXtIBCjkINfO>wMxGz|9{Da<)MN3j^y(Qou^AFFwa@0lmF1})ekEuNs13U#7A1nE%ReM6Cjv>WifAPhaTI zNblFQ{C-xK55ayM?97Bql|d>p*jfiHbuhRmw_=$C4=-`vqNDK)qbVQB$GZ~QgcKBn zs8vdk#nRC%tvY^q8IF6>L;r;Dulpk*9pD1!_(fPnl?kozG0n<0-l>~irt z!Bi_RIvvWlPL8e%+GYZOKd$0Hdbv%`Wix$#g@^V|ce(v_F+5|Ortp~-wW8Vo@$qEm z9<@+lnB%>?T+ShwWb(=x7nfRv_6PNeT_DvrkH~FNd>jme)HX%+)kM=nT07b}6c4ad zUUnZd*Yu`eCZmQ6tP)b=@P)kd*fwa;U?;(c>Jt}xW6>um(6fK;-lx0NnWdZoI;(mO zA5<&E%|Sv*LOA+X) zT$`$GQg#;)4KRI;%C=*nE&I$KVHw++E@`I$nZChg9ih)JqPS04nmd3cLyFSCgU@Uz zTQlbxqh>{M1@J4xc_;mAI@_av{_<`jH-810YjGMNxXM(j9hE5i)s z$G-+SoZR^lGxPQT-d1DfRt-aUeKL9;Oq0*v$0AC!0{;9824j2de>(X)Ue19-G-I** z5zN?EZ;TEQeQ20;(JoFf!`ccmv#w{*aZQMsZ&S`c62#Q9*De-~}l zeuZ(0eB|oi5Ipt<(Kq1vbkzW2AG8#Bo8}=Pxoz=Bto$5p}`DwaZ zoth&g*Ae%H<<@^Z#Tu9m=`xI7*uL<#q`@iD_Q`iqR-(o)14}^$E5jb8ekS>v$8e!v z4IAPZ1BJL*(3%{P(9oc{u;sqBEKKz(MfSmshzj`%UrH`|) ze67yxJ5y9O?v|%k1;5$Oni(_QrmMq)G!w~S70NX%0i=gb14L}KzG*(anC_!yF196V z5f5s@3oT09s?&&gEeL<2{WT;D52J*cNTm zuYv)zkhZr~lz1e$#vNyk!79FGUb32BsO2AP3SnZw(v9f~@OjW>CU>-O>099Nu)+({ zUFm5}m;~Uf-I~&^!>K1d5|28-@iKr{9YPb)Ew!jYlucxbk0+V^r2Zx}^!_&?o*cRC zejg9+0KjD`*(40%@kNGQX`HWnaupd51$Z#Y%l{WZ7o!f%HB1&9yVRh{lb%G{SPYQo?TfDEpf+2vqB;EIE9xpx%;&sBV%A@*T4z_ME1C+ zf%b<+MLK|AK^a;O-vYpVM64L`rDi9^HT)*!*{{Gi znz)1eEaZ512hpve85M=#3)cVZjF{eFMF#3raTf^DCr5ShLJ$y6vJLqv1n=`=K?}BA zklshC7CEjix2fNdiZoiFdD)CKD>74M2wQ@NrO0FuKmpjAjD0X)P9>Ng m@l#Pz2V8(B!F$Y?Lt8tT2JZ&6#=@>qOy7Kd5++yce6(Ps2?}Tc From e7befed12aa71f25bc5adc307c1c732da2d30a9d Mon Sep 17 00:00:00 2001 From: nbayati <99771966+nbayati@users.noreply.github.com> Date: Tue, 12 Aug 2025 11:25:57 -0700 Subject: [PATCH 924/966] docs: update user guide to include x509 feature. (#1802) * docs: update README to include x509 feature. * Fix hyperlink --- packages/google-auth/docs/user-guide.rst | 82 ++++++++++++++++++++++-- 1 file changed, 78 insertions(+), 4 deletions(-) diff --git a/packages/google-auth/docs/user-guide.rst b/packages/google-auth/docs/user-guide.rst index 04dffaf89862..67d04174db42 100644 --- a/packages/google-auth/docs/user-guide.rst +++ b/packages/google-auth/docs/user-guide.rst @@ -23,8 +23,8 @@ credentials. Credentials from external accounts (workload identity federation) are used to identify a particular application from an on-prem or non-Google Cloud platform -including Amazon Web Services (AWS), Microsoft Azure or any identity provider -that supports OpenID Connect (OIDC). +including Amazon Web Services (AWS), Microsoft Azure, any identity provider +that supports OpenID Connect (OIDC), or via X.509 certificates. Obtaining credentials --------------------- @@ -196,8 +196,8 @@ External credentials (Workload identity federation) +++++++++++++++++++++++++++++++++++++++++++++++++++ Using workload identity federation, your application can access Google Cloud -resources from Amazon Web Services (AWS), Microsoft Azure or any identity -provider that supports OpenID Connect (OIDC). +resources from Amazon Web Services (AWS), Microsoft Azure, any identity +provider that supports OpenID Connect (OIDC), or via X.509 certificates. Traditionally, applications running outside Google Cloud have used service account keys to access Google Cloud resources. Using identity federation, @@ -290,6 +290,80 @@ Follow the detailed instructions on how to .. _Configure Workload Identity Federation from an OIDC identity provider: https://cloud.google.com/iam/docs/access-resources-oidc +.. _accessing-resources-using-x509-certificate-sourced-credentials: + +Accessing resources using X.509 certificate-sourced credentials +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +For `X.509 certificate-sourced credentials`_, the authentication library uses an X.509 certificate and private key to prove your application's identity. The certificate has a built-in expiration date and must be renewed to maintain access. + +The library constructs a subject token by creating a JSON array containing the base64-encoded leaf certificate, followed by any intermediate certificates from a provided trust chain. + +**Generating Configuration Files for X.509 Federation** + +To configure X.509 certificate-sourced credentials, you need to generate two separate configuration files: a primary **credential configuration file** and a **certificate configuration file**. The ``gcloud iam workload-identity-pools create-cred-config`` command can be used to create both. + +The location where the certificate configuration file is created depends on whether you use the ``--credential-cert-configuration-output-file`` flag. + +**Default Behavior (Recommended)** + +If you omit the ``--credential-cert-configuration-output-file`` flag, gcloud creates the certificate configuration file at a default, well-known location that the auth library can automatically discover. This is the simplest approach for most use cases. + +**Example Command (Default Behavior):** + +.. code-block:: bash + + gcloud iam workload-identity-pools create-cred-config \ + projects/$PROJECT_NUMBER/locations/global/workloadIdentityPools/$POOL_ID/providers/$PROVIDER_ID \ + --service-account $SERVICE_ACCOUNT_EMAIL \ + --credential-cert-path "$PATH_TO_CERTIFICATE" \ + --credential-cert-private-key-path "$PATH_TO_PRIVATE_KEY" \ + --credential-cert-trust-chain-path "$PATH_TO_TRUST_CHAIN" \ + --output-file /path/to/config.json + +Where the following variables need to be substituted: + +* ``$PROJECT_NUMBER``: The unique, numerical identifier for your Google Cloud project. This is not the Project ID string. +* ``$POOL_ID``: The workload identity pool ID. +* ``$PROVIDER_ID``: The provider ID. +* ``$SERVICE_ACCOUNT_EMAIL``: The email of the service account to impersonate. +* ``$PATH_TO_CERTIFICATE``: The file path where your leaf X.509 certificate is located. +* ``$PATH_TO_PRIVATE_KEY``: The file path where the corresponding private key for the leaf certificate is located. +* ``$PATH_TO_TRUST_CHAIN``: The file path of the X.509 certificate trust chain file. This file should be a PEM-formatted file containing any intermediate certificates required to complete the trust chain between the leaf certificate and the trust store configured in the Workload Identity Federation pool. The leaf certificate is optional in this file. + +This command results in: + +* ``/path/to/config.json``: Created at the path you specified. This file will contain ``"use_default_certificate_config": true`` to instruct clients to look for the certificate configuration at the default path. +* ``certificate_config.json``: Created at the default gcloud configuration path, which is typically ``~/.config/gcloud/certificate_config.json`` on Linux and macOS, or ``%APPDATA%\gcloud\certificate_config.json`` on Windows. + + +**Custom Location Behavior** + +If you need to store the certificate configuration file in a non-default location, use the ``--credential-cert-configuration-output-file`` flag. + +**Example Command (Custom Location):** + +.. code-block:: bash + + gcloud iam workload-identity-pools create-cred-config \ + projects/$PROJECT_NUMBER/locations/global/workloadIdentityPools/$POOL_ID/providers/$PROVIDER_ID \ + --service-account $SERVICE_ACCOUNT_EMAIL \ + --credential-cert-path "$PATH_TO_CERTIFICATE" \ + --credential-cert-private-key-path "$PATH_TO_PRIVATE_KEY" \ + --credential-cert-trust-chain-path "$PATH_TO_TRUST_CHAIN" \ + --credential-cert-configuration-output-file "/custom/path/cert_config.json" \ + --output-file /path/to/config.json + + +This command results in: + +* ``/path/to/config.json``: Created at the path you specified. This file will contain a ``"certificate_config_location"`` field that points to your custom path. +* ``cert_config.json``: Created at ``/custom/path/cert_config.json``, as specified by the flag. + +You can now use the Auth library to call Google Cloud resources with X.509 certificate-sourced credentials. + +.. _X.509 certificate-sourced credentials: https://cloud.google.com/iam/docs/workload-identity-federation-with-x509-certificates + + Using Executable-sourced credentials with OIDC and SAML ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From e41c86e9593f7ed30fe6c9b56a090c315e1de4df Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Thu, 14 Aug 2025 22:34:40 +0000 Subject: [PATCH 925/966] chore: secret update (#1806) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index b826f451ea5c6a6d45f305b9040fec8daa8b6f85..1867d2ba2458719e687e2855973a3f14c2757fda 100644 GIT binary patch literal 10324 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 literal 10324 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 From dccad5e4ee8370fb2d18601a702e804977776179 Mon Sep 17 00:00:00 2001 From: nbayati <99771966+nbayati@users.noreply.github.com> Date: Thu, 14 Aug 2025 16:00:19 -0700 Subject: [PATCH 926/966] fix: fix type error in credentials.py for python 3.7 and 3.8 (#1805) * fix: fix type error in credentials.py for python 3.7 and 3.8 * fix linting error --- packages/google-auth/google/auth/credentials.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/google-auth/google/auth/credentials.py b/packages/google-auth/google/auth/credentials.py index 6ed94fe70091..82c73c3bfd5f 100644 --- a/packages/google-auth/google/auth/credentials.py +++ b/packages/google-auth/google/auth/credentials.py @@ -18,6 +18,7 @@ import abc from enum import Enum import os +from typing import List from google.auth import _helpers, environment_vars from google.auth import exceptions @@ -27,7 +28,7 @@ from google.auth._refresh_worker import RefreshThreadManager DEFAULT_UNIVERSE_DOMAIN = "googleapis.com" -NO_OP_TRUST_BOUNDARY_LOCATIONS: list[str] = [] +NO_OP_TRUST_BOUNDARY_LOCATIONS: List[str] = [] NO_OP_TRUST_BOUNDARY_ENCODED_LOCATIONS = "0x0" @@ -434,13 +435,12 @@ def _build_trust_boundary_lookup_url(self): def _has_no_op_trust_boundary(self): # A no-op trust boundary is indicated by encodedLocations being "0x0". # The "locations" list may or may not be present as an empty list. - if ( - self._trust_boundary is not None - and self._trust_boundary["encodedLocations"] + if self._trust_boundary is None: + return False + return ( + self._trust_boundary.get("encodedLocations") == NO_OP_TRUST_BOUNDARY_ENCODED_LOCATIONS - ): - return True - return False + ) class AnonymousCredentials(Credentials): From 5ab4b58e447cea676df2899c6c7a4b011de62d9c Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Thu, 25 Sep 2025 10:39:54 -0700 Subject: [PATCH 927/966] chore: update owners (#1813) * chore: update owners * remove yoshi --- packages/google-auth/.github/CODEOWNERS | 46 +++++++++--------- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/packages/google-auth/.github/CODEOWNERS b/packages/google-auth/.github/CODEOWNERS index a6a81621d430..d854e6f57539 100644 --- a/packages/google-auth/.github/CODEOWNERS +++ b/packages/google-auth/.github/CODEOWNERS @@ -4,27 +4,27 @@ # For syntax help see: # https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners#codeowners-syntax -# The @googleapis/googleapis-auth and @googleapis/yoshi-python is the default owner for changes in this repo -* @googleapis/googleapis-auth @googleapis/yoshi-python -google/auth/_default.py @googleapis/googleapis-auth @googleapis/aion-sdk -google/auth/aws.py @googleapis/googleapis-auth @googleapis/aion-sdk -google/auth/credentials.py @googleapis/googleapis-auth @googleapis/aion-sdk -google/auth/downscoped.py @googleapis/googleapis-auth @googleapis/aion-sdk -google/auth/external_account.py @googleapis/googleapis-auth @googleapis/aion-sdk -google/auth/external_account_authorized_user.py @googleapis/googleapis-auth @googleapis/aion-sdk -google/auth/identity_pool.py @googleapis/googleapis-auth @googleapis/aion-sdk -google/auth/pluggable.py @googleapis/googleapis-auth @googleapis/aion-sdk -google/auth/sts.py @googleapis/googleapis-auth @googleapis/aion-sdk -google/auth/impersonated_credentials.py @googleapis/googleapis-auth @googleapis/aion-sdk -tests/test__default.py @googleapis/googleapis-auth @googleapis/aion-sdk -tests/test_aws.py @googleapis/googleapis-auth @googleapis/aion-sdk -tests/test_credentials.py @googleapis/googleapis-auth @googleapis/aion-sdk -tests/test_downscoped.py @googleapis/googleapis-auth @googleapis/aion-sdk -tests/test_external_account.py @googleapis/googleapis-auth @googleapis/aion-sdk -tests/test_external_account_authorized_user.py @googleapis/googleapis-auth @googleapis/aion-sdk -tests/test_identity_pool.py @googleapis/googleapis-auth @googleapis/aion-sdk -tests/test_pluggable.py @googleapis/googleapis-auth @googleapis/aion-sdk -tests/test_sts.py @googleapis/googleapis-auth @googleapis/aion-sdk -tests/test_impersonated_credentials.py @googleapis/googleapis-auth @googleapis/aion-sdk -/samples/ @googleapis/googleapis-auth @googleapis/aion-sdk @googleapis/python-samples-owners +# The @googleapis/googleapis-auth and @googleapis/python-core-client-libraries is the default owner for changes in this repo +* @googleapis/googleapis-auth @googleapis/python-core-client-libraries +google/auth/_default.py @googleapis/googleapis-auth @googleapis/aion-sdk @googleapis/python-core-client-libraries +google/auth/aws.py @googleapis/googleapis-auth @googleapis/aion-sdk @googleapis/python-core-client-libraries +google/auth/credentials.py @googleapis/googleapis-auth @googleapis/aion-sdk @googleapis/python-core-client-libraries +google/auth/downscoped.py @googleapis/googleapis-auth @googleapis/aion-sdk @googleapis/python-core-client-libraries +google/auth/external_account.py @googleapis/googleapis-auth @googleapis/aion-sdk @googleapis/python-core-client-libraries +google/auth/external_account_authorized_user.py @googleapis/googleapis-auth @googleapis/aion-sdk @googleapis/python-core-client-libraries +google/auth/identity_pool.py @googleapis/googleapis-auth @googleapis/aion-sdk @googleapis/python-core-client-libraries +google/auth/pluggable.py @googleapis/googleapis-auth @googleapis/aion-sdk @googleapis/python-core-client-libraries +google/auth/sts.py @googleapis/googleapis-auth @googleapis/aion-sdk @googleapis/python-core-client-libraries +google/auth/impersonated_credentials.py @googleapis/googleapis-auth @googleapis/aion-sdk @googleapis/python-core-client-libraries +tests/test__default.py @googleapis/googleapis-auth @googleapis/aion-sdk @googleapis/python-core-client-libraries +tests/test_aws.py @googleapis/googleapis-auth @googleapis/aion-sdk @googleapis/python-core-client-libraries +tests/test_credentials.py @googleapis/googleapis-auth @googleapis/aion-sdk @googleapis/python-core-client-libraries +tests/test_downscoped.py @googleapis/googleapis-auth @googleapis/aion-sdk @googleapis/python-core-client-libraries +tests/test_external_account.py @googleapis/googleapis-auth @googleapis/aion-sdk @googleapis/python-core-client-libraries +tests/test_external_account_authorized_user.py @googleapis/googleapis-auth @googleapis/aion-sdk @googleapis/python-core-client-libraries +tests/test_identity_pool.py @googleapis/googleapis-auth @googleapis/aion-sdk @googleapis/python-core-client-libraries +tests/test_pluggable.py @googleapis/googleapis-auth @googleapis/aion-sdk @googleapis/python-core-client-libraries +tests/test_sts.py @googleapis/googleapis-auth @googleapis/aion-sdk @googleapis/python-core-client-libraries +tests/test_impersonated_credentials.py @googleapis/googleapis-auth @googleapis/aion-sdk @googleapis/python-core-client-libraries +/samples/ @googleapis/googleapis-auth @googleapis/aion-sdk @googleapis/python-samples-owners @googleapis/python-core-client-libraries system_tests/secrets.tar.enc # Remove noise from test creds. diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 1867d2ba2458719e687e2855973a3f14c2757fda..3b3315dee0691aa45569750e28b0bc75685a180b 100644 GIT binary patch literal 10324 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 literal 10324 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 From 4b09a81ee55bc57c2d7c0536cd34a9da7a545b11 Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Mon, 29 Sep 2025 11:51:46 -0700 Subject: [PATCH 928/966] fix: Deprecating generic load methods and adding warnings on few cred types (#1812) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: Deprecating generic load methods and adding warnings on few cred types * update secret * extra line * remove being * method to class * secret update * secret update * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot --- packages/google-auth/google/auth/_default.py | 49 ++++++++++++++++++ .../google/auth/external_account.py | 25 ++++++++- .../auth/external_account_authorized_user.py | 25 ++++++++- .../google-auth/google/auth/identity_pool.py | 26 +++++++++- .../google/auth/impersonated_credentials.py | 16 ++++++ packages/google-auth/google/auth/pluggable.py | 26 +++++++++- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 7 files changed, 163 insertions(+), 4 deletions(-) diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index cf0cdd772988..2df2b4e02b6d 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -59,6 +59,38 @@ https://cloud.google.com/docs/authentication/adc-troubleshooting/user-creds. \ """ +_GENERIC_LOAD_METHOD_WARNING = """\ +The {} method is deprecated because of a potential security risk. + +This method does not validate the credential configuration. The security +risk occurs when a credential configuration is accepted from a source that +is not under your control and used without validation on your side. + +If you know that you will be loading credential configurations of a +specific type, it is recommended to use a credential-type-specific +load method. +This will ensure that an unexpected credential type with potential for +malicious intent is not loaded unintentionally. You might still have to do +validation for certain credential types. Please follow the recommendations +for that method. For example, if you want to load only service accounts, +you can create the service account credentials explicitly: + +``` +from google.oauth2 import service_account +creds = service_account.Credentials.from_service_account_file(filename) +``` + +If you are loading your credential configuration from an untrusted source and have +not mitigated the risks (e.g. by validating the configuration yourself), make +these changes as soon as possible to prevent security risks to your environment. + +Regardless of the method used, it is always your responsibility to validate +configurations received from external sources. + +Refer to https://cloud.google.com/docs/authentication/external/externally-sourced-credentials +for more details. +""" + # The subject token type used for AWS external_account credentials. _AWS_SUBJECT_TOKEN_TYPE = "urn:ietf:params:aws:token-type:aws4_request" @@ -76,6 +108,20 @@ def _warn_about_problematic_credentials(credentials): warnings.warn(_CLOUD_SDK_CREDENTIALS_WARNING) +def _warn_about_generic_load_method(method_name): # pragma: NO COVER + """Warns that a generic load method is being used. + + This is to discourage use of the generic load methods in favor of + more specific methods. The generic methods are more likely to lead to + security issues if the input is not validated. + + Args: + method_name (str): The name of the method being used. + """ + + warnings.warn(_GENERIC_LOAD_METHOD_WARNING.format(method_name), DeprecationWarning) + + def load_credentials_from_file( filename, scopes=None, default_scopes=None, quota_project_id=None, request=None ): @@ -121,6 +167,8 @@ def load_credentials_from_file( google.auth.exceptions.DefaultCredentialsError: if the file is in the wrong format or is missing. """ + _warn_about_generic_load_method("load_credentials_from_file") + if not os.path.exists(filename): raise exceptions.DefaultCredentialsError( "File {} was not found.".format(filename) @@ -184,6 +232,7 @@ def load_credentials_from_dict( google.auth.exceptions.DefaultCredentialsError: if the file is in the wrong format or is missing. """ + _warn_about_generic_load_method("load_credentials_from_dict") if not isinstance(info, dict): raise exceptions.DefaultCredentialsError( "info object was of type {} but dict type was expected.".format(type(info)) diff --git a/packages/google-auth/google/auth/external_account.py b/packages/google-auth/google/auth/external_account.py index b72f4c20f03a..e4eb908f6bc0 100644 --- a/packages/google-auth/google/auth/external_account.py +++ b/packages/google-auth/google/auth/external_account.py @@ -89,7 +89,14 @@ class Credentials( credentials for Google access token and authorizing requests to Google APIs. The base class implements the common logic for exchanging external account credentials for Google access tokens. - """ + + **IMPORTANT**: + This class does not validate the credential configuration. A security + risk occurs when a credential configuration configured with malicious urls + is used. + When the credential configuration is accepted from an + untrusted source, you should validate it before using. + Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details.""" def __init__( self, @@ -576,6 +583,14 @@ def _get_mtls_cert_and_key_paths(self): def from_info(cls, info, **kwargs): """Creates a Credentials instance from parsed external account info. + **IMPORTANT**: + This method does not validate the credential configuration. A security + risk occurs when a credential configuration configured with malicious urls + is used. + When the credential configuration is accepted from an + untrusted source, you should validate it before using with this method. + Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details. + Args: info (Mapping[str, str]): The external account info in Google format. @@ -615,6 +630,14 @@ def from_info(cls, info, **kwargs): def from_file(cls, filename, **kwargs): """Creates a Credentials instance from an external account json file. + **IMPORTANT**: + This method does not validate the credential configuration. A security + risk occurs when a credential configuration configured with malicious urls + is used. + When the credential configuration is accepted from an + untrusted source, you should validate it before using with this method. + Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details. + Args: filename (str): The path to the external account json file. kwargs: Additional arguments to pass to the constructor. diff --git a/packages/google-auth/google/auth/external_account_authorized_user.py b/packages/google-auth/google/auth/external_account_authorized_user.py index 4d0c3c680697..53f75cf9322a 100644 --- a/packages/google-auth/google/auth/external_account_authorized_user.py +++ b/packages/google-auth/google/auth/external_account_authorized_user.py @@ -60,7 +60,14 @@ class Credentials( The credentials are considered immutable. If you want to modify the quota project, use `with_quota_project` and if you want to modify the token uri, use `with_token_uri`. - """ + + **IMPORTANT**: + This class does not validate the credential configuration. A security + risk occurs when a credential configuration configured with malicious urls + is used. + When the credential configuration is accepted from an + untrusted source, you should validate it before using. + Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details.""" def __init__( self, @@ -328,6 +335,14 @@ def with_universe_domain(self, universe_domain): def from_info(cls, info, **kwargs): """Creates a Credentials instance from parsed external account info. + **IMPORTANT**: + This method does not validate the credential configuration. A security + risk occurs when a credential configuration configured with malicious urls + is used. + When the credential configuration is accepted from an + untrusted source, you should validate it before using with this method. + Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details. + Args: info (Mapping[str, str]): The external account info in Google format. @@ -367,6 +382,14 @@ def from_info(cls, info, **kwargs): def from_file(cls, filename, **kwargs): """Creates a Credentials instance from an external account json file. + **IMPORTANT**: + This method does not validate the credential configuration. A security + risk occurs when a credential configuration configured with malicious urls + is used. + When the credential configuration is accepted from an + untrusted source, you should validate it before using with this method. + Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details. + Args: filename (str): The path to the external account json file. kwargs: Additional arguments to pass to the constructor. diff --git a/packages/google-auth/google/auth/identity_pool.py b/packages/google-auth/google/auth/identity_pool.py index c06f88428702..79b7de920563 100644 --- a/packages/google-auth/google/auth/identity_pool.py +++ b/packages/google-auth/google/auth/identity_pool.py @@ -253,7 +253,15 @@ def _parse_token_data(token_content, format_type="text", subject_token_field_nam class Credentials(external_account.Credentials): - """External account credentials sourced from files and URLs.""" + """External account credentials sourced from files and URLs. + + **IMPORTANT**: + This class does not validate the credential configuration. A security + risk occurs when a credential configuration configured with malicious urls + is used. + When the credential configuration is accepted from an + untrusted source, you should validate it before using. + Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details.""" def __init__( self, @@ -497,6 +505,14 @@ def _validate_single_source(self): def from_info(cls, info, **kwargs): """Creates an Identity Pool Credentials instance from parsed external account info. + **IMPORTANT**: + This method does not validate the credential configuration. A security + risk occurs when a credential configuration configured with malicious urls + is used. + When the credential configuration is accepted from an + untrusted source, you should validate it before using with this method. + Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details. + Args: info (Mapping[str, str]): The Identity Pool external account info in Google format. @@ -517,6 +533,14 @@ def from_info(cls, info, **kwargs): def from_file(cls, filename, **kwargs): """Creates an IdentityPool Credentials instance from an external account json file. + **IMPORTANT**: + This method does not validate the credential configuration. A security + risk occurs when a credential configuration configured with malicious urls + is used. + When the credential configuration is accepted from an + untrusted source, you should validate it before using with this method. + Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details. + Args: filename (str): The path to the IdentityPool external account json file. kwargs: Additional arguments to pass to the constructor. diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index 4d8b72941843..1b67e4406fc3 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -184,6 +184,14 @@ class Credentials( buckets = client.list_buckets(project='your_project') for bucket in buckets: print(bucket.name) + + **IMPORTANT**: + This class does not validate the credential configuration. A security + risk occurs when a credential configuration configured with malicious urls + is used. + When the credential configuration is accepted from an + untrusted source, you should validate it before using. + Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details. """ def __init__( @@ -454,6 +462,14 @@ def with_scopes(self, scopes, default_scopes=None): def from_impersonated_service_account_info(cls, info, scopes=None): """Creates a Credentials instance from parsed impersonated service account credentials info. + **IMPORTANT**: + This method does not validate the credential configuration. A security + risk occurs when a credential configuration configured with malicious urls + is used. + When the credential configuration is accepted from an + untrusted source, you should validate it before using with this method. + Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details. + Args: info (Mapping[str, str]): The impersonated service account credentials info in Google format. diff --git a/packages/google-auth/google/auth/pluggable.py b/packages/google-auth/google/auth/pluggable.py index d725188f87a9..fd349537d4d5 100644 --- a/packages/google-auth/google/auth/pluggable.py +++ b/packages/google-auth/google/auth/pluggable.py @@ -57,7 +57,15 @@ class Credentials(external_account.Credentials): - """External account credentials sourced from executables.""" + """External account credentials sourced from executables. + + **IMPORTANT**: + This class does not validate the credential configuration. A security + risk occurs when a credential configuration configured with malicious urls + is used. + When the credential configuration is accepted from an + untrusted source, you should validate it before using. + Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details.""" def __init__( self, @@ -300,6 +308,14 @@ def external_account_id(self): def from_info(cls, info, **kwargs): """Creates a Pluggable Credentials instance from parsed external account info. + **IMPORTANT**: + This method does not validate the credential configuration. A security + risk occurs when a credential configuration configured with malicious urls + is used. + When the credential configuration is accepted from an + untrusted source, you should validate it before using with this method. + Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details. + Args: info (Mapping[str, str]): The Pluggable external account info in Google format. @@ -319,6 +335,14 @@ def from_info(cls, info, **kwargs): def from_file(cls, filename, **kwargs): """Creates an Pluggable Credentials instance from an external account json file. + **IMPORTANT**: + This method does not validate the credential configuration. A security + risk occurs when a credential configuration configured with malicious urls + is used. + When the credential configuration is accepted from an + untrusted source, you should validate it before using with this method. + Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details. + Args: filename (str): The path to the Pluggable external account json file. kwargs: Additional arguments to pass to the constructor. diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 3b3315dee0691aa45569750e28b0bc75685a180b..4e24faa87d1f3aae812cea2d1659fdcf506c828e 100644 GIT binary patch literal 10324 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 literal 10324 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 From 363787e215aecbec311be9406f3c26ac2dc0a4b3 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 29 Sep 2025 21:26:28 +0000 Subject: [PATCH 929/966] chore(main): release 2.41.0 (#1797) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 20 ++++++++++++++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index a9880be63bda..5f9a52491f92 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,26 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.41.0](https://github.com/googleapis/google-auth-library-python/compare/v2.40.3...v2.41.0) (2025-09-29) + + +### Features + +* Add support for cachetools 6.0 ([#1773](https://github.com/googleapis/google-auth-library-python/issues/1773)) ([af18060](https://github.com/googleapis/google-auth-library-python/commit/af18060d521baf86c219d66a26631decb3b28e79)) +* Add trust boundary support for service accounts and impersonation. ([#1778](https://github.com/googleapis/google-auth-library-python/issues/1778)) ([99be2ce](https://github.com/googleapis/google-auth-library-python/commit/99be2ce19401296718f880c7a80cd2e841df78bf)) + + +### Bug Fixes + +* Deprecating [load_credentials_from_dict](https://googleapis.dev/python/google-auth/latest/reference/google.auth.html#google.auth.load_credentials_from_dict) ([58b66ec](https://github.com/googleapis/google-auth-library-python/commit/58b66ec8069bfe5304c7da512fe89a8e838ce1ca)) +* Deprecating [load_credentials_from_file](https://googleapis.dev/python/google-auth/latest/reference/google.auth.html#google.auth.load_credentials_from_file) ([58b66ec](https://github.com/googleapis/google-auth-library-python/commit/58b66ec8069bfe5304c7da512fe89a8e838ce1ca)) +* Fix type error in credentials.py for python 3.7 and 3.8 ([#1805](https://github.com/googleapis/google-auth-library-python/issues/1805)) ([c30a6a7](https://github.com/googleapis/google-auth-library-python/commit/c30a6a781d3e385598a0ac28a370a7f4800010cc)) + + +### Documentation + +* Update user guide to include x509 feature. ([#1802](https://github.com/googleapis/google-auth-library-python/issues/1802)) ([2d89ab4](https://github.com/googleapis/google-auth-library-python/commit/2d89ab4d85568564e1f462f5b463991ffd9b82b1)) + ## [2.40.3](https://github.com/googleapis/google-auth-library-python/compare/v2.40.2...v2.40.3) (2025-06-04) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 318bf723b432..f309c8d48f36 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.40.3" +__version__ = "2.41.0" From 1fad2126f417c85f38b3986d15cf8c3b9267cf79 Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Tue, 30 Sep 2025 14:32:18 -0700 Subject: [PATCH 930/966] fix: suppress deprecation warning for ADC (#1815) * fix: suppress deprecation warning for ADC * secret * lint --- packages/google-auth/google/auth/_default.py | 29 ++++++++++-------- .../google-auth/google/auth/_default_async.py | 26 ++++++++++------ .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 3 files changed, 33 insertions(+), 22 deletions(-) diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index 2df2b4e02b6d..a96f7108be23 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -305,15 +305,17 @@ def _get_gcloud_sdk_credentials(quota_project_id=None): _LOGGER.debug("Cloud SDK credentials not found on disk; not using them") return None, None - credentials, project_id = load_credentials_from_file( - credentials_filename, quota_project_id=quota_project_id - ) - credentials._cred_file_path = credentials_filename + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + credentials, project_id = load_credentials_from_file( + credentials_filename, quota_project_id=quota_project_id + ) + credentials._cred_file_path = credentials_filename - if not project_id: - project_id = _cloud_sdk.get_project_id() + if not project_id: + project_id = _cloud_sdk.get_project_id() - return credentials, project_id + return credentials, project_id def _get_explicit_environ_credentials(quota_project_id=None): @@ -339,12 +341,15 @@ def _get_explicit_environ_credentials(quota_project_id=None): return _get_gcloud_sdk_credentials(quota_project_id=quota_project_id) if explicit_file is not None: - credentials, project_id = load_credentials_from_file( - os.environ[environment_vars.CREDENTIALS], quota_project_id=quota_project_id - ) - credentials._cred_file_path = f"{explicit_file} file via the GOOGLE_APPLICATION_CREDENTIALS environment variable" + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + credentials, project_id = load_credentials_from_file( + os.environ[environment_vars.CREDENTIALS], + quota_project_id=quota_project_id, + ) + credentials._cred_file_path = f"{explicit_file} file via the GOOGLE_APPLICATION_CREDENTIALS environment variable" - return credentials, project_id + return credentials, project_id else: return None, None diff --git a/packages/google-auth/google/auth/_default_async.py b/packages/google-auth/google/auth/_default_async.py index 2e53e2088759..44bc6719f97a 100644 --- a/packages/google-auth/google/auth/_default_async.py +++ b/packages/google-auth/google/auth/_default_async.py @@ -20,6 +20,7 @@ import io import json import os +import warnings from google.auth import _default from google.auth import environment_vars @@ -116,14 +117,16 @@ def _get_gcloud_sdk_credentials(quota_project_id=None): if not os.path.isfile(credentials_filename): return None, None - credentials, project_id = load_credentials_from_file( - credentials_filename, quota_project_id=quota_project_id - ) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + credentials, project_id = load_credentials_from_file( + credentials_filename, quota_project_id=quota_project_id + ) - if not project_id: - project_id = _cloud_sdk.get_project_id() + if not project_id: + project_id = _cloud_sdk.get_project_id() - return credentials, project_id + return credentials, project_id def _get_explicit_environ_credentials(quota_project_id=None): @@ -141,11 +144,14 @@ def _get_explicit_environ_credentials(quota_project_id=None): return _get_gcloud_sdk_credentials(quota_project_id=quota_project_id) if explicit_file is not None: - credentials, project_id = load_credentials_from_file( - os.environ[environment_vars.CREDENTIALS], quota_project_id=quota_project_id - ) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + credentials, project_id = load_credentials_from_file( + os.environ[environment_vars.CREDENTIALS], + quota_project_id=quota_project_id, + ) - return credentials, project_id + return credentials, project_id else: return None, None diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 4e24faa87d1f3aae812cea2d1659fdcf506c828e..8456a41bd29a27a4d81863649cf1c82db1ac1317 100644 GIT binary patch literal 10324 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 literal 10324 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 From 7b20c83559f500d0696b6b7314d598166a9918c8 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 30 Sep 2025 15:41:28 -0700 Subject: [PATCH 931/966] chore(main): release 2.41.1 (#1816) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 5f9a52491f92..90299bdf5277 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.41.1](https://github.com/googleapis/google-auth-library-python/compare/v2.41.0...v2.41.1) (2025-09-30) + + +### Bug Fixes + +* Suppress deprecation warning for ADC ([#1815](https://github.com/googleapis/google-auth-library-python/issues/1815)) ([751ce3f](https://github.com/googleapis/google-auth-library-python/commit/751ce3f625eb24029e9f0c59c081bdd3e18eb583)) + ## [2.41.0](https://github.com/googleapis/google-auth-library-python/compare/v2.40.3...v2.41.0) (2025-09-29) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index f309c8d48f36..6f67e6b34eed 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.41.0" +__version__ = "2.41.1" From 7ef8b403d329243b44e3a675dbaff340609f174a Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Wed, 1 Oct 2025 14:40:18 -0700 Subject: [PATCH 932/966] test: Add tests to cover deprecation and suppression (#1817) * test: Add tests to covere deprecation and suppression * lint * blacken --- packages/google-auth/tests/test__default.py | 56 +++++++++++++++++++-- 1 file changed, 53 insertions(+), 3 deletions(-) diff --git a/packages/google-auth/tests/test__default.py b/packages/google-auth/tests/test__default.py index e42b4dd9437d..78874c01a6c0 100644 --- a/packages/google-auth/tests/test__default.py +++ b/packages/google-auth/tests/test__default.py @@ -14,6 +14,7 @@ import json import os +import warnings import mock import pytest # type: ignore @@ -398,7 +399,6 @@ def test_load_credentials_from_file_impersonated_passing_scopes(): def test_load_credentials_from_file_impersonated_wrong_target_principal(tmpdir): - with open(IMPERSONATED_SERVICE_ACCOUNT_AUTHORIZED_USER_SOURCE_FILE) as fh: impersonated_credentials_info = json.load(fh) impersonated_credentials_info[ @@ -414,7 +414,6 @@ def test_load_credentials_from_file_impersonated_wrong_target_principal(tmpdir): def test_load_credentials_from_file_impersonated_wrong_source_type(tmpdir): - with open(IMPERSONATED_SERVICE_ACCOUNT_AUTHORIZED_USER_SOURCE_FILE) as fh: impersonated_credentials_info = json.load(fh) impersonated_credentials_info["source_credentials"]["type"] = "external_account" @@ -1325,7 +1324,7 @@ def test_default_impersonated_service_account_set_default_scopes(get_adc_path): "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True ) def test_default_impersonated_service_account_set_both_scopes_and_default_scopes( - get_adc_path + get_adc_path, ): get_adc_path.return_value = IMPERSONATED_SERVICE_ACCOUNT_AUTHORIZED_USER_SOURCE_FILE scopes = ["scope1", "scope2"] @@ -1410,3 +1409,54 @@ def test_quota_gce_credentials(unused_get, unused_ping): quota_project_id=explicit_quota ) assert credentials.quota_project_id == explicit_quota + + +def test_load_credentials_from_file_deprecation_warning(): + with pytest.warns( + DeprecationWarning, match="The load_credentials_from_file method is deprecated" + ): + _default.load_credentials_from_file(SERVICE_ACCOUNT_FILE) + + +def test_load_credentials_from_dict_deprecation_warning(): + with pytest.warns( + DeprecationWarning, match="The load_credentials_from_dict method is deprecated" + ): + _default.load_credentials_from_dict(SERVICE_ACCOUNT_FILE_DATA) + + +@mock.patch("google.auth._cloud_sdk.get_project_id", return_value=None, autospec=True) +@mock.patch("os.path.isfile", return_value=True, autospec=True) +@mock.patch( + "google.auth._cloud_sdk.get_application_default_credentials_path", + return_value=SERVICE_ACCOUNT_FILE, + autospec=True, +) +def test_get_gcloud_sdk_credentials_suppresses_deprecation_warning( + get_adc_path, isfile, get_project_id +): + with warnings.catch_warnings(record=True) as caught_warnings: + warnings.simplefilter("always") + + _default._get_gcloud_sdk_credentials() + + assert not any( + isinstance(w.message, DeprecationWarning) + and "load_credentials_from_file" in str(w.message) + for w in caught_warnings + ) + + +def test_get_explicit_environ_credentials_suppresses_deprecation_warning(monkeypatch): + monkeypatch.setenv(environment_vars.CREDENTIALS, SERVICE_ACCOUNT_FILE) + + with warnings.catch_warnings(record=True) as caught_warnings: + warnings.simplefilter("always") + + _default._get_explicit_environ_credentials() + + assert not any( + isinstance(w.message, DeprecationWarning) + and "load_credentials_from_file" in str(w.message) + for w in caught_warnings + ) From affbb5e68b2ecbf9aaee5ceaa347e3c46f264c92 Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Mon, 6 Oct 2025 12:37:34 -0700 Subject: [PATCH 933/966] chore: secret update (#1818) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 8456a41bd29a27a4d81863649cf1c82db1ac1317..be81fb93274fb9174f676c58c3fa77d6f223ff12 100644 GIT binary patch literal 10324 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 qTwx5i^^Nq_f93_LA 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 literal 10324 zcmV-aD67{BB>?tKRTC>oNApkzyHa$7_zv*Nj(hkOBd1kbR1(>z(E!7 ziXP05p@Eh!Hf;i;xz^|G(+KgZW|w!cKwSLRw6A^iFWU;N0Ai)-83RJ-lysE#1RT6G zP2B)tRGuG1=8TgDyaL?OtCTf(Y&zukct8hDyX3_`EH?P^+%P()0@O-tJOvPGFqpkJ zn#|sg@2d%}Yz}|L{IQUd^4*DBjX{zOR=xi0)`dwvGw-CR)8-O8YFGFbxxB&=FOj+>pPe!;0>Fx%G>3w7;{P^iwqIW75uU~*gMnWQ3PfmA8pxL zubQkMURGiB*tvJnuaBB_@=8U0Fph#<(NS{wlA+eSRJ`%iYQW6w3lB4L*eTgeq@Umc zPJ5@q_Urhs%X;OMSAnR}ALyAA_h+>!lpmmBEq;vwURB^E(^m6+SEiO#_=5F= z#mMw14XPnL)L30H3Z?*Um-hIjdQo9ljhgF#;2zFse)tI@n;s(omx_-PSoPx6gu#UG zL&INu*uHNmY`K!wY^rn12p#!cT{E|?J={Lpi;3%}Cfhg0P?@SKQW+vu91U5nZ9!wo zhqw68%gq%yz78X%8OWSLQ`N<7c;Sy*Xe{a6ePNRIoE2Ayc0lUn4`tw%nJr%k?yev} zc{u)!bIR~UYet#S%{V|$XzSH%p%sKA#lHn>G3atA`W?vYA~8+8O=j)fNfV(du!b{h zA*!moxupgDppeU~hOf`E1!rpz_n{VmGBo3hVm1?R)878aRaWW?1yPF6ONUC8-7pwn z>TGc}aD#+Y&xfg^;Mz8&QdmRexR4bU-|vh>vvWY9x6$Sj^BU#XkjCmtUJ+PsB;K#FHU9)GBy*dzWhXMv_o34M`k3OY_3J?EY@$P?Z zaH0-m(y5t3j|O{e{W1Yhjz;rIa(YrJ$Qht?>Pc=WsepC5^#F~#$KW=J@|GVUzlf^E z#g6Xo_H-nnOF9@{6I6uuJVkA?UvSCoT{CO{d_W}P^`9+nRQR(-glcnG4 zAc^Qk*lt=UB(!#%1#y3BrijY$tF}Su-1; z_}|9d%x~?@i{qCSI6%xsk9KsMwtVvToS-;q!VJcx=Wv>{&{EfP=c1t6aeJFVoAj2i zBN2$8?0O-{Xwos97%>PDEbak4Ph7v{_$ynJr>IZ+OHHYzDKSFo$&Pqh5#baJ)Dr^K zx2Z$(?v7e^8W$~QlWmWQqZ?>*boHk3ok?`w-R+}*}52nz+Qu3;yYSUPRG$Tytp2;mtp}jXipO*@XJ;>PE7Dn z91WHjluNT@xkVvYvrSwz>;kjTb9i%m$-zwFbIqcWVr=jR$;gs(CKzJcX_e$9J_&-` zV6+A@yz#>pzD;TMG;g51-W59EMp$@8#ZB19A*O}&cD$U^ZlxXBhkCd)C zIhFZFWdck)sxHOc9W}#V+)C(Dr<=qDKxT1|cT3KZkyuIy(3za~b1hlNh&{x!B$H>{ zxB;A1Q8J5Ucl<$*_RCMlA*yD|c>Ig)BRl*7Z)E%6Nh4&Y2VNCuD)^oP%VOQPf}Q2b z7`Unyh_R~_yX!2$vo+a<1RC77ovWhAex)Q4czcZ~YVllQ3Fgy+cv+cz%OAO3q~Y&M zzihnPSm_Uumfe2ojy6eR;R*UWqc?$BBBYTB6Tm?rRv$78!2x7B{b2Iz^d`wYag73& z-*S^ye9LrpCnG?%cWH3*shsWg1Tnq1QrbfJZ=J8+xjLZZ6YV#LvG61A*~5U63B3}S zng!@rl#MA;aq^RTb|Loyc5w)@^aDaBggwdV`EX+Rm$aTV=W_y((Tl3K&w)K*$j$a60@Ko}nI!Sf7jgpUdt4Svr}xsz~cTFGZS5^WuuC02eiNocDV@y*34>vV=Ai z7cy6wL)!*Hvz-qgE}X%q)|E*JemvJQ1AM2xGvxjb*b!af_mU_6usCz=f^e+;gT`CF z?dQq!1cy#q%xO{Q*KmHFF>;ie&OFXxHdDQihp z6a>xl2N6OqYj7*!3mO^Vz0@*PPrK(lWjSbm{~ihVx|5E@+Zz!DFO1ZEpy^=cG z2j(##t1Lb}JbX6lt6=iD$q*klONBm?2m%eFg>t>$BslTmh*KRmzDxgV46C`SlEH1f-MfU0p^%d9^1z<-AjJVIJ9}y0f&Rc@sT19lcX8|8cyn z#=z_~7g{ewhs5fYb2Laz>SRFSsKYL{V}W8iQ41Q(O4GdC(ZT&yjW3rp)tLm{f+*9P`rEFp|@QEtbdd?9?{{KI+R(WN;!A z1A;(E%!A3>fqm>gA*&qRXSB$PlsQ4^vlx&q(Fvw~Pa(MHc@wUE-{)2E_u<298dc+1 z(tH!W54Ca6Ay!6`j*duyCS3T20xk`(JjkL^`i?Th2495}Q}o#iXj)UT0w!vB^x@5@ zQ9KTY8*?Nni*lLfMgah2D;NTT;F~oKMyEl>XDiZ0{wO3Gd0*B4i`S78Y3pT7Ark@` zh_}Z}mbCJ@isux}n9p8x6shXj;;=wYBcxdQF6@WmaBk!LQ)Ke*&I*Kc%H;MH9R7>x zsyN|c8o&gGFs*+ClypCptYspjbho_AuL|ySCJu#T=&ln_8Si1;(P~9R#kBi?wD)5x zq*<3EYMv;JL<w&ezf*!$M6h&;u7yj3}=3se+e z5O31SL{~t*$0O-t=1{QZGN$`P?f+pw?BU5()^m^4BHW7F%CoJ_cyeH3Wh7jyZ2Nkv ztDRV1y69Ty>=@StWaO$!s#ldQ6+yqN+6GeUIp-`#qa+Rbt$W}jhVUm=%^Y5a0ELdu z04@E>M&ZOFcI8$mNLA&{-tg>&#vB)JU^8YUU6jh~RFKF|YXK}I`;8Jr`nud4qb*3o20z+TZFor#>0YrnyVDkdmTuUK*fzaQK zD0%P08-sD{43u$O!8OPq%8XE_^NE1tqv|408fJW@CDyqcD{?A(Dw{AXXJSMtwwLGG zXF8XxNMp8EURD;Q{OKp};+>LgB|21{>4Zx;S!z04)CoNXp-ty|F^VnV+w_7`91?MT2=S0)?Ycd;dS1$to?V$MybkPAXjVdlu zvd}1K#B3jdcR+DA{=j-uZgU@?v*vMPUpDyi$@|c92L6HZ(0>GtQYW#(ue&*}G4|3* zyL|smw%lY^SUAf)MdZ3F+|3Zs3UGSH@>uf4s4XuayKd@{&oVgbrdlfeXOq!^oNnXT zQ3KJ_)e*vyV-3@^eZ{TlJyUgAq!5F&fb8O4!jv-z!a^v?@68BnX(q|M=7bx@d}gn* zHJ$ss*GmoP{Bi4G{o&_%Ijb*L>g@N~`n}GOON-nht!x6|cZ~kz*5Vf*^wvUR&=W?W|Gb+q_Uq-0!C|)gU z#TM!eQ%KNh?2NhX;rxC1s=}5=jb9r54`J7!4}d=7fnL#j0mqNzKT^i#&R2dlzHNVp zVzu}TRSQG2Y} z$H~?kcbf}#ux>+mlG7YR=`LEUMyZU0%D{?g!vsv(tjC@fZyV&C4=ipzb()5yq)AOX zDCSrMnS#fPLudWkJh^G-djPuLMttmtgnMwA=`cCj+B`=F!>ek#G)-~v<7W>@58`PQLEbRa$!QANFLmi~%bLc=@*P z?ZgcsQDKxBlyX*#C|z#kM)0EP?w7gu*Eqhw$V0U;3@^%*lTG#czKp4ZPYq<2i5U;7 zXI!q9Z^aaUvNs3JuE z{@}<(Kw48?=3i&3PwJogRbDh(=_a?@T)~H6WT1J@b${`w_-*+mM#0GpTrvDkU7a#j z{s2Hgdl_9Q2-ve?`UH!^eAn<`GWArV&$k~6fE>*XR54!fgF|O*9%{tm&Kb5BOA4HeSWvAxubYOmnzd36Lx*bLICY zk^}+*ib(_*tr)3LYz6IVf5@O;xBdn}{+cPAq*?Yj{Xp0+e%*n%;BNwsO08UgMN)!! zo?M2e20aALIT|QlBLW7wceW{E@V#wgN+c5IhVg%+%h5TZ&p0Rz`alIkGx2kO zy?vD!ywNmJO!=khE+vagAUG_nJhYTvR)$^R^?|t?!l!#v`T259eX%SOJ=n+)xgxeb zq46AmWq6*Whi;s{LuG;-TADrBJo{_Y_ss)Go22e3(Pr{fP^qebI(uUtN;S|?jtI?tAmMTVKTPFDJID*BTeJhC7DqL(KNe5z+QUKi9l0^@ zypTMVRZ%K>8%h<5N`zOZY5oj5D;6<)nqQ59M_O}aeD@vAsah zN6gwl?FV}4Q)xCV%csJ+L<%a_zPnT%XxwRpy&!@X(f(t7`~hSdz?ww^1?rHMO+|BdRzJD=A^@K$t~{ zE=}mH?8|mZL_}Z}>D#Cx!l_w~Ckh*P)K0Rcb;VW`r+)T!p6H0V77eV5Ds2jwGyq#u z1_*_m%}<|gYPSj49KMT6eZstaQ^7K8sDpoMv0_#_i9|~KLfFV7oBiHAhG$As6mu3g z!(8`k_?j_r^H#m{r-P8#3fk5xNe3Gv^V>vDz82GZehVZN2q4Zso&^T&QPD)ZH^CT7 z_(m2c-e0D_;V<*J=rhY8#ii3jeq`}uicN{+eFo<$>Ln|&A%!7ivRfL4>w(SxZEKrX zo;XX1rQ88T>z`8`i24aZ^ipXNmE|Hpoa>?{)j90o4~Lv1MUfO~2fb-#+?Xs5h`RG@ z02?%gU`BlXezfUs$KH^lp18>NN2uTE(3-$nbyQtadEZ(s8G}N!v9BPQ36Ap21P_~x z6e%?2o?Ws24r6#lWMOB8SnV^^oD0_oS_v@;6&{*yDrZ`KG9r@2cNZIIZZn56SlYGM z%=C*&O;NDd7GZ_f0Dx}I_y3(N$5q~=o3!XoHCO`(<;^7TPIn!cfz=Fz#X`*Wn0XSX zbSIOlJ@aWK(uxYjeJLZ+XtRI`Z+7s_#~ih$dzaA;=kR+`kvVL4HLjxsC}u#>f!b|0 ze#W+B>*p(UJ)oDOzK?2l>svwu+^xz zPfs5Bih$u#6{O&!aRi@71>&FC*A>moxoyQf-+Z&t;eAaM=bHE3cn_bUQYQZhr2$&m zCA#x_XJFN-;jD+B!N& zYs&F2!RH|2m<5~@eXa5IFZ-6v<+t@r$iltES~zzxC^*g#w$*gcic{OBwZXZJOv85} zL3f=$(iTTN@4?$PL^zt{v?-yctYy0FyAZuTeIvYo-EEDnU(2&ux9L(^vQxlrFl9py z0g^_j-{Jm`uv@2ecmy9=awXbS(E!T1=d*4-h3l#Bv}52jIO zC;Jg}a-s@;A+>0&jB>5!RtQx=6X0|#6S@TJ{du8pr3ymdPxYzua6(%uHqVObRfv4W zeA5tO?u~IBDm+J#ek}e|bpXZ0G^3!AQrKF{HqsR!z3YFP(w}o{BElej(L1rsF!aZm znf83Iv)BBfpxrqBHJw|xptR0_V4%rjX(IS18#Q-#paGQNy`sdoG3lGYkq|n#n`dj2 zXIc1Y-Oh*SnvNyu=oidkZdSGZr1h0}0Ku22ozJw%ZilchLNQso{l-Cj6BNw--m)8j4=-Wo}mJ85p( z%t^H>DOpRC=bO0y*8H0b!~%)#L?R6=r}h#P(fWW>1r2w8viLvbYK^YdbFkS=!WVXW z2?yOyIit~#UZeJY0?C=0@O-jnJA;phG>?Sf@C@4Nl+$so`S+QpH;ZraDG|7Zb5w4qU&=g6k*nrYH5UGEk>Ln?z3%`<_wZX zB&Y!86ZY=I|^gniQ^!PxkTU=OU*-tLJY`)=qL^%vy}2JH?TBc-F`8 zUsbx3fC_ctOCMhMYv2$nc@%_g|M{DL-WBeGi&CnN!1#&33VWT zarc;>z2HEVLPn0KCSVeSlRUkCdY{tG^S0Y~W0A~{ zp!JuRh0fh^7A$>ERr|y{Yu5xT1%|mL3Rt7l!a64eW2qP$`Z61KBDg@lD!celzTO72 z1{~!C4qB)Msg32;c?`ZZk)D|Ar+AOTU2#_}Qf2$p#|bgWbK9K5hejoM0U8(%doN%5 zrqwjcaC`lddH?9B@KG$1TE+7Be4~G*-0TNKBqdU7gpmmWfjNfZT%h6%xob2zuRk)L zW_nnFf1f%tc_WaqILEO$5X{>+(@rgo0qGEoW3zI9Mx{$EB0p=->2B!;;E`J63!Qgq zbY9ha4ZWEID&5USY4u%K6sDbfNW{Z}DgAi|1(!ki!Z4`L8M(~;65*XmK`7U0J8xGN zu%4y}K#M3AMNzD)P}6m?t@Sq+M}W($xUV|g$v`sbdV!uTU5D@sE%AR4B+6-)(Po@L z%>FM1c%4M$w^^!}Dsq8g;aVD@{=EV*_}OIO94hE5rRzy#MVJ}{Z%d6+I6L%>K9Nv`s+Y}<)pts-K6 z90Gi8HUSk6k4{ym2#cj#j^pR-S!4Kv+?%{>U;@~Yvbej?A_ohmkNamzty;dKpiOMH zSq9~|+ttfE`BSK2JQ%qUj?LYZF_;`JyUDqzN@f^6U*v>#6IfqtU*)k%6q4zo zzWk!{?)OUjd~OA(dAIV(Jv+yOMZ^SLEEewoC&6U7#6u4cprxV#x}2r)@&=5L)2;Ng zI7KwoZ#>LJsn71+BSLjen&z)wKh7{;jD5*B{))@lUZ7X&$NJ_B#J=gqeMvagvD~De z+HJaPvn)m_D1Ghx3t@V~Bv6pNeDBQI5>zrcCJ4OM1d~&Yp2oPQ)4C49321Jcq*v5W zY7v|oOe6C0=GLFHq~$5q^U#OjUHL8;X!xsjXbuD$4HDm&|7S^V5#(#C zAhYB_`&fq7Mx%HGv3@U`6rz z`(vbd=6?y3d-fC@$8-yws+ETo`cgfGSXA(KiNlzy#8yxI(lcLD!!`FeXrVS)1HOFL z@$YQg(J3P!sdW)7@Xq>@CN~d6iIM`so!029M%foX!E>66Znwx4y8YBKlAN=JQG6Gm z8vES~gP27jk@6H}8NbGav>bQpV?_N6$|_sYl2`cVlMUE03VuZZ*MY-$Io1^ zym1c?fMRRz^07a@Cyf|2t9d>=#}XHebH9le@)M`s}z>Y5Ls6-%m!>?kJ~5jX3m z!nNcb|L&*ljWtO*8sNb4OT(ZU00&N=>2zgS0_Q$kI99j$>`5O1=_WgYx9_N^_#pCT z4Rv}u$bPW{4R!stGh`DO;ca&&+dnF@axqq-~u0{%)lnUot<0o+X8M^mKN9m@K{I_$y*Pi)foxA-vG)>NJTf}*`JMplJp4_z92oNX$4oIR* znq))|49W8+MW{r@wHSCM0kcSH*1HH-?=3Og)&!-JxkChEAYfNbVvqtp>X?jEIBZ!k z^Ib2JA-Rzyp{p}3;o6JnxiNiAmTqiFv7md=Jh|7|t(L6eMF1({`6@CRe?9|;3vP3c zm;rpU8IXFbD+Am(b6|FRse$UY&^H3#`-{rQx=msOeOR50Mylh zJ}MVoBDVH*!p4iR)9JbB^#sMK47=Uk?vprOYK;UbW+wYnZ_-WXUrr^BQogF`nir_g zAMy@xGJ*@{4lZuf9zSECtvQO_{YkHuEZ<}7emZN^ry%Is7|5KpO(U9b`~6DbKG8rS z%2;o;=%7xT^NL|)r@V#Hq*^0x%hzM_hb$LNJV?ukfa0nehd7tZ5s~LDuMA`>rp!x) zkbK|~pn?Exyiu>7I}HFE%RSCxn$A{GuI`DPTswSGEDpda%?hj1X6fRerh+T|Y{{OB zCM3&EargLM-PQjrA27CW9>#Lp$jg9bAnCLqh2hRDO@LdDL{C;&@u_);{{#`qn5i2X zm5C0OWX;mtX0XsCeKtzqY#$Ko>DNBUgz}#s)Mi~X#|C7b{D>}{AMb;VcA?rNl+3}H z?cf{RZ>~uIz;(oQvx;VIv!e&@If}|;nbwybAAV%A^z({{$Uit1(@j239js#kWcAKw z`d@Oo$o#kyf4uH2Y&g;KQi76cG0SNEYIdTa`?v-vLgb+9ZQk}#LK{lv-7f{)4?S8r z`UkiYLRcMIRTAfE5&SEx^K(`SXvm^_r8=gVATVNvGP@Cy{C}T=yye5L<*C1xO)h@L zVD;s1%7R#O{*d{nOpsfisfzt4qqKlF=1O=JQAgl9H1`tgyCEd-15zNCObZ)!-%DmGtEjBJ z<;^>!faAbu0cos7JdK%1tL3&}W~(ycR8>tfrWD2#>E|KCd!MOstVcx~AZL&A>?EQv zg$eM@%D2e(0s`t^8Y&!WlqxUnx{H295YM>?Uc?x~S zzSpNqn;Q?(uCEM1k^32`yr*+3>ODo6#Jch-5WF!U^gRb3?J|jOOd%NwD*3uY8(yvt zcxd~)SrT2q`Tf=CBJPANu-$-Bs$vd#t+@-fnii3S<8;3XRMFBJRBsje+ zUIN4Kc}mLKBz+}8{QNtCRnRjSkx`(wn;TcI1D(+`g*}qW$VZuWxA%qi>P%_{wD+SD zr1-(ZxMsKyhLhJ&URs3N4}hf=_LLf=9JnLv>hf4bWl)ttC5XWsO;je1NJQzL#dVM} z-lr(4SvP}@H*x$r$_JPv+m2_EG%UXqJ|)hbHR*l-c%_Ts^E?1rw&akB4-fp%$6t!` zzb(%pqs%zK{gJ;CvwDPl4;kfoqE7V;eWtc6wUY$CL44_tVp~yu2Y%))%$);8-)!YG zkE5P#Am@S9s)7wdffc4&p!+xnv2Fk;kqcTs3vaVN7NTj6ud#(m1+EEMWqVc7Bqs1`jhB zr}zkt#nrWw%GzHKZoKurHbHp(kLJv0bZ5wvH3q1~lGbVi9de8mY@mqdrO;g;0ZqFc zfX_>3iq@>N>eLJq%e|5igF?Sz#PJ`qW{zOQt`C#+tEV>e3JP&eTaz|`WIsS~^GJl^ zrs(0IJ{P#JwQG(FwjpD7FgdOA^N5BKUex&N7so@8^leMDp-_$?r+!9i+#(iMZLr8~$jvfcUJuP zbw7RXi12aT#<}=kGy&#CuO7UWAQ~t0?mmE170LBX_M)f{CFqYf^?JX=u|0VgunH1K mI(uLJ|Lcg?ftv3FN$EU*LUS^1`j=flby7E)QTo2By6|y{b`H4! From abbf62981adcfe7ad19230313d6f642b9980f0d7 Mon Sep 17 00:00:00 2001 From: nbayati <99771966+nbayati@users.noreply.github.com> Date: Mon, 6 Oct 2025 18:11:38 -0700 Subject: [PATCH 934/966] feat: Add trust boundary support for external accounts. (#1809) * feat: Add trust boundary support for external accounts. * Add trust boundary support to external account authorized users. * Fix lint issues * Implement additional unit tests for external account authorized user. * fix formatting issue * Add a unit test with invalid audiance * add missing unit tests --- .../google-auth/google/auth/_constants.py | 5 + .../google/auth/external_account.py | 67 +++- .../auth/external_account_authorized_user.py | 54 ++- .../google/auth/impersonated_credentials.py | 4 +- .../google/oauth2/service_account.py | 9 +- packages/google-auth/tests/test_aws.py | 5 + .../tests/test_external_account.py | 371 ++++++++++++++++-- .../test_external_account_authorized_user.py | 103 ++++- .../google-auth/tests/test_identity_pool.py | 7 + .../tests/test_impersonated_credentials.py | 24 +- packages/google-auth/tests/test_pluggable.py | 4 + 11 files changed, 571 insertions(+), 82 deletions(-) create mode 100644 packages/google-auth/google/auth/_constants.py diff --git a/packages/google-auth/google/auth/_constants.py b/packages/google-auth/google/auth/_constants.py new file mode 100644 index 000000000000..28e47025fc4d --- /dev/null +++ b/packages/google-auth/google/auth/_constants.py @@ -0,0 +1,5 @@ +"""Shared constants.""" + +_SERVICE_ACCOUNT_TRUST_BOUNDARY_LOOKUP_ENDPOINT = "https://iamcredentials.{universe_domain}/v1/projects/-/serviceAccounts/{service_account_email}/allowedLocations" +_WORKFORCE_POOL_TRUST_BOUNDARY_LOOKUP_ENDPOINT = "https://iamcredentials.{universe_domain}/v1/locations/global/workforcePools/{pool_id}/allowedLocations" +_WORKLOAD_IDENTITY_POOL_TRUST_BOUNDARY_LOOKUP_ENDPOINT = "https://iamcredentials.{universe_domain}/v1/projects/{project_number}/locations/global/workloadIdentityPools/{pool_id}/allowedLocations" diff --git a/packages/google-auth/google/auth/external_account.py b/packages/google-auth/google/auth/external_account.py index e4eb908f6bc0..8eba0d2495fd 100644 --- a/packages/google-auth/google/auth/external_account.py +++ b/packages/google-auth/google/auth/external_account.py @@ -36,6 +36,7 @@ import json import re +from google.auth import _constants from google.auth import _helpers from google.auth import credentials from google.auth import exceptions @@ -81,6 +82,7 @@ class Credentials( credentials.Scoped, credentials.CredentialsWithQuotaProject, credentials.CredentialsWithTokenUri, + credentials.CredentialsWithTrustBoundary, metaclass=abc.ABCMeta, ): """Base class for all external account credentials. @@ -173,10 +175,7 @@ def __init__( self._scopes = scopes self._default_scopes = default_scopes self._workforce_pool_user_project = workforce_pool_user_project - self._trust_boundary = { - "locations": [], - "encoded_locations": "0x0", - } # expose a placeholder trust boundary value. + self._trust_boundary = trust_boundary if self._client_id: self._client_auth = utils.ClientAuthentication( @@ -242,6 +241,7 @@ def _constructor_args(self): "scopes": self._scopes, "default_scopes": self._default_scopes, "universe_domain": self._universe_domain, + "trust_boundary": self._trust_boundary, } if not self.is_workforce_pool: args.pop("workforce_pool_user_project") @@ -412,8 +412,23 @@ def get_project_id(self, request): return None - @_helpers.copy_docstring(credentials.Credentials) def refresh(self, request): + """Refreshes the access token. + + For impersonated credentials, this method will refresh the underlying + source credentials and the impersonated credentials. For non-impersonated + credentials, it will refresh the access token and the trust boundary. + """ + self._refresh_token(request) + # If we are impersonating, the trust boundary is handled by the + # impersonated credentials object. We need to get it from there. + if self._service_account_impersonation_url: + self._trust_boundary = self._impersonated_credentials._trust_boundary + else: + # Otherwise, refresh the trust boundary for the external account. + self._refresh_trust_boundary(request) + + def _refresh_token(self, request): scopes = self._scopes if self._scopes is not None else self._default_scopes # Inject client certificate into request. @@ -463,6 +478,40 @@ def refresh(self, request): self.expiry = now + lifetime + def _build_trust_boundary_lookup_url(self): + """Builds and returns the URL for the trust boundary lookup API.""" + url = None + # Try to parse as a workload identity pool. + # Audience format: //iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID + workload_match = re.search( + r"projects/([^/]+)/locations/global/workloadIdentityPools/([^/]+)", + self._audience, + ) + if workload_match: + project_number, pool_id = workload_match.groups() + url = _constants._WORKLOAD_IDENTITY_POOL_TRUST_BOUNDARY_LOOKUP_ENDPOINT.format( + universe_domain=self._universe_domain, + project_number=project_number, + pool_id=pool_id, + ) + else: + # If that fails, try to parse as a workforce pool. + # Audience format: //iam.googleapis.com/locations/global/workforcePools/POOL_ID/providers/PROVIDER_ID + workforce_match = re.search( + r"locations/[^/]+/workforcePools/([^/]+)", self._audience + ) + if workforce_match: + pool_id = workforce_match.groups()[0] + url = _constants._WORKFORCE_POOL_TRUST_BOUNDARY_LOOKUP_ENDPOINT.format( + universe_domain=self._universe_domain, pool_id=pool_id + ) + + if url: + return url + else: + # If both fail, the audience format is invalid. + raise exceptions.InvalidValue("Invalid audience format.") + def _make_copy(self): kwargs = self._constructor_args() new_cred = self.__class__(**kwargs) @@ -489,6 +538,12 @@ def with_universe_domain(self, universe_domain): cred._universe_domain = universe_domain return cred + @_helpers.copy_docstring(credentials.CredentialsWithTrustBoundary) + def with_trust_boundary(self, trust_boundary): + cred = self._make_copy() + cred._trust_boundary = trust_boundary + return cred + def _should_initialize_impersonated_credentials(self): return ( self._service_account_impersonation_url is not None @@ -537,6 +592,7 @@ def _initialize_impersonated_credentials(self): lifetime=self._service_account_impersonation_options.get( "token_lifetime_seconds" ), + trust_boundary=self._trust_boundary, ) def _create_default_metrics_options(self): @@ -623,6 +679,7 @@ def from_info(cls, info, **kwargs): universe_domain=info.get( "universe_domain", credentials.DEFAULT_UNIVERSE_DOMAIN ), + trust_boundary=info.get("trust_boundary"), **kwargs ) diff --git a/packages/google-auth/google/auth/external_account_authorized_user.py b/packages/google-auth/google/auth/external_account_authorized_user.py index 53f75cf9322a..f8fbf950beb2 100644 --- a/packages/google-auth/google/auth/external_account_authorized_user.py +++ b/packages/google-auth/google/auth/external_account_authorized_user.py @@ -36,7 +36,9 @@ import datetime import io import json +import re +from google.auth import _constants from google.auth import _helpers from google.auth import credentials from google.auth import exceptions @@ -50,6 +52,7 @@ class Credentials( credentials.CredentialsWithQuotaProject, credentials.ReadOnlyScoped, credentials.CredentialsWithTokenUri, + credentials.CredentialsWithTrustBoundary, ): """Credentials for External Account Authorized Users. @@ -83,6 +86,7 @@ def __init__( scopes=None, quota_project_id=None, universe_domain=credentials.DEFAULT_UNIVERSE_DOMAIN, + trust_boundary=None, ): """Instantiates a external account authorized user credentials object. @@ -108,6 +112,7 @@ def __init__( create the credentials. universe_domain (Optional[str]): The universe domain. The default value is googleapis.com. + trust_boundary (Mapping[str,str]): A credential trust boundary. Returns: google.auth.external_account_authorized_user.Credentials: The @@ -118,7 +123,7 @@ def __init__( self.token = token self.expiry = expiry self._audience = audience - self._refresh_token = refresh_token + self._refresh_token_val = refresh_token self._token_url = token_url self._token_info_url = token_info_url self._client_id = client_id @@ -128,6 +133,7 @@ def __init__( self._scopes = scopes self._universe_domain = universe_domain or credentials.DEFAULT_UNIVERSE_DOMAIN self._cred_file_path = None + self._trust_boundary = trust_boundary if not self.valid and not self.can_refresh: raise exceptions.InvalidOperation( @@ -164,7 +170,7 @@ def info(self): def constructor_args(self): return { "audience": self._audience, - "refresh_token": self._refresh_token, + "refresh_token": self._refresh_token_val, "token_url": self._token_url, "token_info_url": self._token_info_url, "client_id": self._client_id, @@ -175,6 +181,7 @@ def constructor_args(self): "scopes": self._scopes, "quota_project_id": self._quota_project_id, "universe_domain": self._universe_domain, + "trust_boundary": self._trust_boundary, } @property @@ -184,7 +191,7 @@ def scopes(self): @property def requires_scopes(self): - """ False: OAuth 2.0 credentials have their scopes set when + """False: OAuth 2.0 credentials have their scopes set when the initial token is requested and can not be changed.""" return False @@ -201,13 +208,13 @@ def client_secret(self): @property def audience(self): """Optional[str]: The STS audience which contains the resource name for the - workforce pool and the provider identifier in that pool.""" + workforce pool and the provider identifier in that pool.""" return self._audience @property def refresh_token(self): """Optional[str]: The OAuth 2.0 refresh token.""" - return self._refresh_token + return self._refresh_token_val @property def token_url(self): @@ -226,13 +233,18 @@ def revoke_url(self): @property def is_user(self): - """ True: This credential always represents a user.""" + """True: This credential always represents a user.""" return True @property def can_refresh(self): return all( - (self._refresh_token, self._token_url, self._client_id, self._client_secret) + ( + self._refresh_token_val, + self._token_url, + self._client_id, + self._client_secret, + ) ) def get_project_id(self, request=None): @@ -266,7 +278,7 @@ def to_json(self, strip=None): strip = strip if strip else [] return json.dumps({k: v for (k, v) in self.info.items() if k not in strip}) - def refresh(self, request): + def _refresh_token(self, request): """Refreshes the access token. Args: @@ -285,7 +297,7 @@ def refresh(self, request): ) now = _helpers.utcnow() - response_data = self._make_sts_request(request) + response_data = self._sts_client.refresh_token(request, self._refresh_token_val) self.token = response_data.get("access_token") @@ -293,10 +305,21 @@ def refresh(self, request): self.expiry = now + lifetime if "refresh_token" in response_data: - self._refresh_token = response_data["refresh_token"] + self._refresh_token_val = response_data["refresh_token"] + + def _build_trust_boundary_lookup_url(self): + """Builds and returns the URL for the trust boundary lookup API.""" + # Audience format: //iam.googleapis.com/locations/global/workforcePools/POOL_ID/providers/PROVIDER_ID + match = re.search(r"locations/[^/]+/workforcePools/([^/]+)", self._audience) + + if not match: + raise exceptions.InvalidValue("Invalid workforce pool audience format.") + + pool_id = match.groups()[0] - def _make_sts_request(self, request): - return self._sts_client.refresh_token(request, self._refresh_token) + return _constants._WORKFORCE_POOL_TRUST_BOUNDARY_LOOKUP_ENDPOINT.format( + universe_domain=self._universe_domain, pool_id=pool_id + ) @_helpers.copy_docstring(credentials.Credentials) def get_cred_info(self): @@ -331,6 +354,12 @@ def with_universe_domain(self, universe_domain): cred._universe_domain = universe_domain return cred + @_helpers.copy_docstring(credentials.CredentialsWithTrustBoundary) + def with_trust_boundary(self, trust_boundary): + cred = self._make_copy() + cred._trust_boundary = trust_boundary + return cred + @classmethod def from_info(cls, info, **kwargs): """Creates a Credentials instance from parsed external account info. @@ -375,6 +404,7 @@ def from_info(cls, info, **kwargs): universe_domain=info.get( "universe_domain", credentials.DEFAULT_UNIVERSE_DOMAIN ), + trust_boundary=info.get("trust_boundary"), **kwargs ) diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index 1b67e4406fc3..d6635a14c14a 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -286,7 +286,7 @@ def _refresh_token(self, request): self._source_credentials.token_state == credentials.TokenState.STALE or self._source_credentials.token_state == credentials.TokenState.INVALID ): - self._source_credentials.refresh(request) + self._source_credentials._refresh_token(request) body = { "delegates": self._delegates, @@ -526,6 +526,7 @@ def from_impersonated_service_account_info(cls, info, scopes=None): target_principal = impersonation_url[start_index + 1 : end_index] delegates = info.get("delegates") quota_project_id = info.get("quota_project_id") + trust_boundary = info.get("trust_boundary") return cls( source_credentials, @@ -533,6 +534,7 @@ def from_impersonated_service_account_info(cls, info, scopes=None): scopes, delegates, quota_project_id=quota_project_id, + trust_boundary=trust_boundary, ) diff --git a/packages/google-auth/google/oauth2/service_account.py b/packages/google-auth/google/oauth2/service_account.py index 55e020125a4d..7520fe3bb355 100644 --- a/packages/google-auth/google/oauth2/service_account.py +++ b/packages/google-auth/google/oauth2/service_account.py @@ -73,6 +73,7 @@ import copy import datetime +from google.auth import _constants from google.auth import _helpers from google.auth import _service_account_info from google.auth import credentials @@ -84,9 +85,6 @@ _DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds _GOOGLE_OAUTH2_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token" -_TRUST_BOUNDARY_LOOKUP_ENDPOINT = ( - "https://iamcredentials.{}/v1/projects/-/serviceAccounts/{}/allowedLocations" -) class Credentials( @@ -520,8 +518,9 @@ def _build_trust_boundary_lookup_url(self): raise ValueError( "Service account email is required to build the trust boundary lookup URL." ) - return _TRUST_BOUNDARY_LOOKUP_ENDPOINT.format( - self._universe_domain, self._service_account_email + return _constants._SERVICE_ACCOUNT_TRUST_BOUNDARY_LOOKUP_ENDPOINT.format( + universe_domain=self._universe_domain, + service_account_email=self._service_account_email, ) @_helpers.copy_docstring(credentials.Signing) diff --git a/packages/google-auth/tests/test_aws.py b/packages/google-auth/tests/test_aws.py index 41ce970d1e82..4f70bda4f6b8 100644 --- a/packages/google-auth/tests/test_aws.py +++ b/packages/google-auth/tests/test_aws.py @@ -971,6 +971,7 @@ def test_from_info_full_options(self, mock_init): quota_project_id=QUOTA_PROJECT_ID, workforce_pool_user_project=None, universe_domain=DEFAULT_UNIVERSE_DOMAIN, + trust_boundary=None, ) @mock.patch.object(aws.Credentials, "__init__", return_value=None) @@ -1000,6 +1001,7 @@ def test_from_info_required_options_only(self, mock_init): quota_project_id=None, workforce_pool_user_project=None, universe_domain=DEFAULT_UNIVERSE_DOMAIN, + trust_boundary=None, ) @mock.patch.object(aws.Credentials, "__init__", return_value=None) @@ -1031,6 +1033,7 @@ def test_from_info_supplier(self, mock_init): quota_project_id=None, workforce_pool_user_project=None, universe_domain=DEFAULT_UNIVERSE_DOMAIN, + trust_boundary=None, ) @mock.patch.object(aws.Credentials, "__init__", return_value=None) @@ -1068,6 +1071,7 @@ def test_from_file_full_options(self, mock_init, tmpdir): quota_project_id=QUOTA_PROJECT_ID, workforce_pool_user_project=None, universe_domain=DEFAULT_UNIVERSE_DOMAIN, + trust_boundary=None, ) @mock.patch.object(aws.Credentials, "__init__", return_value=None) @@ -1098,6 +1102,7 @@ def test_from_file_required_options_only(self, mock_init, tmpdir): quota_project_id=None, workforce_pool_user_project=None, universe_domain=DEFAULT_UNIVERSE_DOMAIN, + trust_boundary=None, ) def test_constructor_invalid_credential_source(self): diff --git a/packages/google-auth/tests/test_external_account.py b/packages/google-auth/tests/test_external_account.py index d86a19bef160..2fa64361db88 100644 --- a/packages/google-auth/tests/test_external_account.py +++ b/packages/google-auth/tests/test_external_account.py @@ -15,12 +15,14 @@ import datetime import http.client as http_client import json +import os import urllib import mock import pytest # type: ignore from google.auth import _helpers +from google.auth import environment_vars from google.auth import exceptions from google.auth import external_account from google.auth import transport @@ -126,6 +128,11 @@ class TestCredentials(object): "status": "INVALID_ARGUMENT", } } + NO_OP_TRUST_BOUNDARY = {"locations": [], "encodedLocations": "0x0"} + VALID_TRUST_BOUNDARY = { + "locations": ["us-central1", "us-east1"], + "encodedLocations": "0xVALIDHEXSA", + } PROJECT_ID = "my-proj-id" CLOUD_RESOURCE_MANAGER_URL = ( "https://cloudresourcemanager.googleapis.com/v1/projects/" @@ -151,6 +158,7 @@ def make_credentials( service_account_impersonation_url=None, service_account_impersonation_options={}, universe_domain=DEFAULT_UNIVERSE_DOMAIN, + trust_boundary=None, ): return CredentialsImpl( audience=cls.AUDIENCE, @@ -166,6 +174,7 @@ def make_credentials( scopes=scopes, default_scopes=default_scopes, universe_domain=universe_domain, + trust_boundary=trust_boundary, ) @classmethod @@ -178,6 +187,7 @@ def make_workforce_pool_credentials( default_scopes=None, service_account_impersonation_url=None, workforce_pool_user_project=None, + trust_boundary=None, ): return CredentialsImpl( audience=cls.WORKFORCE_AUDIENCE, @@ -191,6 +201,7 @@ def make_workforce_pool_credentials( scopes=scopes, default_scopes=default_scopes, workforce_pool_user_project=workforce_pool_user_project, + trust_boundary=trust_boundary, ) @classmethod @@ -322,7 +333,7 @@ def test_default_state(self): assert not credentials.token_info_url def test_nonworkforce_with_workforce_pool_user_project(self): - with pytest.raises(ValueError) as excinfo: + with pytest.raises(exceptions.InvalidValue) as excinfo: CredentialsImpl( audience=self.AUDIENCE, subject_token_type=self.SUBJECT_TOKEN_TYPE, @@ -424,6 +435,7 @@ def test_with_scopes_full_options_propagated(self): scopes=["email"], default_scopes=["default2"], universe_domain=DEFAULT_UNIVERSE_DOMAIN, + trust_boundary=None, ) def test_with_token_uri(self): @@ -512,6 +524,7 @@ def test_with_quota_project_full_options_propagated(self): scopes=self.SCOPES, default_scopes=["default1"], universe_domain=DEFAULT_UNIVERSE_DOMAIN, + trust_boundary=None, ) # Confirm with_quota_project sets the correct quota project after @@ -706,6 +719,258 @@ def test_refresh_without_client_auth_success( assert not credentials.expired assert credentials.token == response["access_token"] + @mock.patch("google.auth.external_account.Credentials._lookup_trust_boundary") + def test_refresh_skips_trust_boundary_lookup_when_disabled( + self, mock_lookup_trust_boundary + ): + credentials = self.make_credentials() + request = self.make_mock_request( + status=http_client.OK, data=self.SUCCESS_RESPONSE + ) + + credentials.refresh(request) + + assert credentials.valid + assert credentials.token == self.SUCCESS_RESPONSE["access_token"] + mock_lookup_trust_boundary.assert_not_called() + headers_applied = {} + credentials.apply(headers_applied) + assert "x-allowed-locations" not in headers_applied + + def test_refresh_skips_sending_allowed_locations_header_with_trust_boundary(self): + # This test verifies that the x-allowed-locations header is not sent with + # the STS request even if a trust boundary is cached. + trust_boundary_value = {"encodedLocations": "0x12345"} + headers = { + "Content-Type": "application/x-www-form-urlencoded", + "x-goog-api-client": "gl-python/3.7 auth/1.1 google-byoid-sdk sa-impersonation/false config-lifetime/false", + } + request_data = { + "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange", + "audience": self.AUDIENCE, + "requested_token_type": "urn:ietf:params:oauth:token-type:access_token", + "subject_token": "subject_token_0", + "subject_token_type": self.SUBJECT_TOKEN_TYPE, + } + request = self.make_mock_request( + status=http_client.OK, data=self.SUCCESS_RESPONSE + ) + credentials = self.make_credentials() + # Set a cached trust boundary. + credentials._trust_boundary = trust_boundary_value + + with mock.patch( + "google.auth.metrics.python_and_auth_lib_version", + return_value=LANG_LIBRARY_METRICS_HEADER_VALUE, + ): + credentials.refresh(request) + + self.assert_token_request_kwargs(request.call_args[1], headers, request_data) + + def test_refresh_on_impersonated_credential_skips_parent_trust_boundary_lookup( + self, + ): + # This test verifies that the top-level impersonating credential + # does not perform a trust boundary lookup. + request = self.make_mock_request( + status=http_client.OK, + data=self.SUCCESS_RESPONSE, + impersonation_status=http_client.OK, + impersonation_data={ + "accessToken": "SA_ACCESS_TOKEN", + "expireTime": "2025-01-01T00:00:00Z", + }, + ) + credentials = self.make_credentials( + service_account_impersonation_url=self.SERVICE_ACCOUNT_IMPERSONATION_URL + ) + + with mock.patch.object( + credentials, "_refresh_trust_boundary", autospec=True + ) as mock_refresh_trust_boundary: + credentials.refresh(request) + + mock_refresh_trust_boundary.assert_not_called() + + def test_refresh_fetches_no_op_trust_boundary(self): + request = self.make_mock_request( + status=http_client.OK, data=self.SUCCESS_RESPONSE + ) + credentials = self.make_credentials() + + with mock.patch.object( + credentials, + "_lookup_trust_boundary", + return_value=self.NO_OP_TRUST_BOUNDARY, + ) as mock_lookup, mock.patch.dict( + os.environ, {environment_vars.GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED: "true"} + ): + credentials.refresh(request) + + mock_lookup.assert_called_once() + headers = {} + credentials.apply(headers) + assert headers["x-allowed-locations"] == "" + + def test_refresh_skips_lookup_with_cached_no_op_boundary(self): + request = self.make_mock_request( + status=http_client.OK, data=self.SUCCESS_RESPONSE + ) + credentials = self.make_credentials() + credentials._trust_boundary = self.NO_OP_TRUST_BOUNDARY + + with mock.patch.object( + credentials, "_lookup_trust_boundary" + ) as mock_lookup, mock.patch.dict( + os.environ, {environment_vars.GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED: "true"} + ): + credentials.refresh(request) + + mock_lookup.assert_not_called() + headers = {} + credentials.apply(headers) + assert headers["x-allowed-locations"] == "" + + def test_refresh_fails_on_lookup_failure_with_no_cache(self): + request = self.make_mock_request( + status=http_client.OK, data=self.SUCCESS_RESPONSE + ) + credentials = self.make_credentials() + + with mock.patch.object( + credentials, + "_lookup_trust_boundary", + side_effect=exceptions.RefreshError("Lookup failed"), + ) as mock_lookup, mock.patch.dict( + os.environ, {environment_vars.GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED: "true"} + ), pytest.raises( + exceptions.RefreshError, match="Lookup failed" + ): + credentials.refresh(request) + + mock_lookup.assert_called_once() + + def test_refresh_uses_cached_boundary_on_lookup_failure(self): + request = self.make_mock_request( + status=http_client.OK, data=self.SUCCESS_RESPONSE + ) + credentials = self.make_credentials() + credentials._trust_boundary = {"encodedLocations": "0x123"} + + with mock.patch.object( + credentials, + "_lookup_trust_boundary", + side_effect=exceptions.RefreshError("Lookup failed"), + ) as mock_lookup, mock.patch.dict( + os.environ, {environment_vars.GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED: "true"} + ): + credentials.refresh(request) + + mock_lookup.assert_called_once() + headers = {} + credentials.apply(headers) + assert headers["x-allowed-locations"] == "0x123" + + def test_refresh_propagates_trust_boundary_to_impersonated_credential(self): + request = self.make_mock_request( + status=http_client.OK, data=self.SUCCESS_RESPONSE + ) + credentials = self.make_credentials( + service_account_impersonation_url=self.SERVICE_ACCOUNT_IMPERSONATION_URL, + trust_boundary=self.VALID_TRUST_BOUNDARY, + ) + impersonated_creds_mock = mock.Mock() + impersonated_creds_mock._trust_boundary = self.VALID_TRUST_BOUNDARY + + with mock.patch( + "google.auth.external_account.impersonated_credentials.Credentials", + return_value=impersonated_creds_mock, + ) as mock_impersonated_creds, mock.patch.dict( + os.environ, {environment_vars.GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED: "true"} + ): + credentials.refresh(request) + + mock_impersonated_creds.assert_called_once_with( + source_credentials=mock.ANY, + target_principal=mock.ANY, + target_scopes=mock.ANY, + quota_project_id=mock.ANY, + iam_endpoint_override=mock.ANY, + lifetime=mock.ANY, + trust_boundary=self.VALID_TRUST_BOUNDARY, + ) + assert credentials._trust_boundary == self.VALID_TRUST_BOUNDARY + + def test_build_trust_boundary_lookup_url_workload(self): + credentials = self.make_credentials() + expected_url = "https://iamcredentials.googleapis.com/v1/projects/123456/locations/global/workloadIdentityPools/POOL_ID/allowedLocations" + assert credentials._build_trust_boundary_lookup_url() == expected_url + + def test_build_trust_boundary_lookup_url_workforce(self): + credentials = self.make_workforce_pool_credentials() + expected_url = "https://iamcredentials.googleapis.com/v1/locations/global/workforcePools/POOL_ID/allowedLocations" + assert credentials._build_trust_boundary_lookup_url() == expected_url + + @pytest.mark.parametrize( + "audience", + [ + "invalid", + "//iam.googleapis.com/projects/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID", + "//iam.googleapis.com/locations/global/workforcsePools//providers/provider-id", + ], + ) + def test_build_trust_boundary_lookup_url_invalid_audience(self, audience): + credentials = self.make_credentials() + credentials._audience = audience + with pytest.raises(exceptions.InvalidValue, match="Invalid audience format."): + credentials._build_trust_boundary_lookup_url() + + def test_refresh_fetches_trust_boundary_workload(self): + request = self.make_mock_request( + status=http_client.OK, data=self.SUCCESS_RESPONSE + ) + credentials = self.make_credentials() + + with mock.patch.object( + credentials, + "_lookup_trust_boundary", + return_value=self.VALID_TRUST_BOUNDARY, + ) as mock_lookup, mock.patch.dict( + os.environ, {environment_vars.GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED: "true"} + ): + credentials.refresh(request) + + mock_lookup.assert_called_once() + headers = {} + credentials.apply(headers) + assert ( + headers["x-allowed-locations"] + == self.VALID_TRUST_BOUNDARY["encodedLocations"] + ) + + def test_refresh_fetches_trust_boundary_workforce(self): + request = self.make_mock_request( + status=http_client.OK, data=self.SUCCESS_RESPONSE + ) + credentials = self.make_workforce_pool_credentials() + + with mock.patch.object( + credentials, + "_lookup_trust_boundary", + return_value=self.VALID_TRUST_BOUNDARY, + ) as mock_lookup, mock.patch.dict( + os.environ, {environment_vars.GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED: "true"} + ): + credentials.refresh(request) + + mock_lookup.assert_called_once() + headers = {} + credentials.apply(headers) + assert ( + headers["x-allowed-locations"] + == self.VALID_TRUST_BOUNDARY["encodedLocations"] + ) + @mock.patch( "google.auth.metrics.python_and_auth_lib_version", return_value=LANG_LIBRARY_METRICS_HEADER_VALUE, @@ -920,9 +1185,6 @@ def test_refresh_impersonation_without_client_auth_success( "Content-Type": "application/json", "authorization": "Bearer {}".format(token_response["access_token"]), "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, - # TODO(negarb): Uncomment and update when trust boundary is supported - # for external account credentials. - # "x-allowed-locations": "0x0", } impersonation_request_data = { "delegates": None, @@ -1012,7 +1274,6 @@ def test_refresh_impersonation_with_mtls_success( "Content-Type": "application/json", "authorization": "Bearer {}".format(token_response["access_token"]), "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, - # "x-allowed-locations": "0x0", } impersonation_request_data = { "delegates": None, @@ -1099,7 +1360,6 @@ def test_refresh_workforce_impersonation_without_client_auth_success( "Content-Type": "application/json", "authorization": "Bearer {}".format(token_response["access_token"]), "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, - # "x-allowed-locations": "0x0", } impersonation_request_data = { "delegates": None, @@ -1262,6 +1522,21 @@ def test_refresh_impersonation_invalid_impersonated_url_error(self): assert not credentials.expired assert credentials.token is None + def test_refresh_impersonation_invalid_url_format_error(self): + credentials = self.make_credentials( + service_account_impersonation_url="https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/:generateAccessToken/invalid", + scopes=self.SCOPES, + ) + + with pytest.raises(exceptions.RefreshError) as excinfo: + credentials.refresh(None) + + assert excinfo.match( + r"Unable to determine target principal from service account impersonation URL." + ) + assert not credentials.expired + assert credentials.token is None + @mock.patch( "google.auth.metrics.python_and_auth_lib_version", return_value=LANG_LIBRARY_METRICS_HEADER_VALUE, @@ -1333,7 +1608,6 @@ def test_refresh_impersonation_with_client_auth_success_ignore_default_scopes( "Content-Type": "application/json", "authorization": "Bearer {}".format(token_response["access_token"]), "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, - # "x-allowed-locations": "0x0", } impersonation_request_data = { "delegates": None, @@ -1417,7 +1691,6 @@ def test_refresh_impersonation_with_client_auth_success_use_default_scopes( "Content-Type": "application/json", "authorization": "Bearer {}".format(token_response["access_token"]), "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, - # "x-allowed-locations": "0x0", } impersonation_request_data = { "delegates": None, @@ -1472,8 +1745,7 @@ def test_apply_without_quota_project_id(self): credentials.apply(headers) assert headers == { - "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]), - # "x-allowed-locations": "0x0", + "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]) } def test_apply_workforce_without_quota_project_id(self): @@ -1489,8 +1761,7 @@ def test_apply_workforce_without_quota_project_id(self): credentials.apply(headers) assert headers == { - "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]), - # "x-allowed-locations": "0x0", + "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]) } def test_apply_impersonation_without_quota_project_id(self): @@ -1521,8 +1792,7 @@ def test_apply_impersonation_without_quota_project_id(self): credentials.apply(headers) assert headers == { - "authorization": "Bearer {}".format(impersonation_response["accessToken"]), - # "x-allowed-locations": "0x0", + "authorization": "Bearer {}".format(impersonation_response["accessToken"]) } def test_apply_with_quota_project_id(self): @@ -1539,7 +1809,6 @@ def test_apply_with_quota_project_id(self): "other": "header-value", "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]), "x-goog-user-project": self.QUOTA_PROJECT_ID, - # "x-allowed-locations": "0x0", } def test_apply_impersonation_with_quota_project_id(self): @@ -1574,7 +1843,6 @@ def test_apply_impersonation_with_quota_project_id(self): "other": "header-value", "authorization": "Bearer {}".format(impersonation_response["accessToken"]), "x-goog-user-project": self.QUOTA_PROJECT_ID, - # "x-allowed-locations": "0x0", } def test_before_request(self): @@ -1590,7 +1858,6 @@ def test_before_request(self): assert headers == { "other": "header-value", "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]), - # "x-allowed-locations": "0x0", } # Second call shouldn't call refresh. @@ -1599,7 +1866,6 @@ def test_before_request(self): assert headers == { "other": "header-value", "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]), - # "x-allowed-locations": "0x0", } def test_before_request_workforce(self): @@ -1617,7 +1883,6 @@ def test_before_request_workforce(self): assert headers == { "other": "header-value", "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]), - # "x-allowed-locations": "0x0", } # Second call shouldn't call refresh. @@ -1626,7 +1891,6 @@ def test_before_request_workforce(self): assert headers == { "other": "header-value", "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]), - # "x-allowed-locations": "0x0", } def test_before_request_impersonation(self): @@ -1657,7 +1921,6 @@ def test_before_request_impersonation(self): assert headers == { "other": "header-value", "authorization": "Bearer {}".format(impersonation_response["accessToken"]), - # "x-allowed-locations": "0x0", } # Second call shouldn't call refresh. @@ -1666,7 +1929,6 @@ def test_before_request_impersonation(self): assert headers == { "other": "header-value", "authorization": "Bearer {}".format(impersonation_response["accessToken"]), - # "x-allowed-locations": "0x0", } @mock.patch("google.auth._helpers.utcnow") @@ -1693,10 +1955,7 @@ def test_before_request_expired(self, utcnow): credentials.before_request(request, "POST", "https://example.com/api", headers) # Cached token should be used. - assert headers == { - "authorization": "Bearer token", - # "x-allowed-locations": "0x0", - } + assert headers == {"authorization": "Bearer token"} # Next call should simulate 1 second passed. utcnow.return_value = datetime.datetime.min + datetime.timedelta(seconds=1) @@ -1710,10 +1969,38 @@ def test_before_request_expired(self, utcnow): # New token should be retrieved. assert headers == { - "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]), - # "x-allowed-locations": "0x0", + "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]) } + def test_refresh_impersonation_trust_boundary(self): + request = self.make_mock_request( + status=http_client.OK, + data=self.SUCCESS_RESPONSE, + impersonation_status=http_client.OK, + impersonation_data={ + "accessToken": "SA_ACCESS_TOKEN", + "expireTime": "2025-01-01T00:00:00Z", + }, + ) + credentials = self.make_credentials( + service_account_impersonation_url=self.SERVICE_ACCOUNT_IMPERSONATION_URL + ) + impersonated_creds_mock = mock.Mock() + impersonated_creds_mock._trust_boundary = self.VALID_TRUST_BOUNDARY + + with mock.patch( + "google.auth.external_account.impersonated_credentials.Credentials", + return_value=impersonated_creds_mock, + ): + credentials.refresh(request) + + assert credentials._trust_boundary == self.VALID_TRUST_BOUNDARY + + def test_with_trust_boundary(self): + credentials = self.make_credentials() + new_credentials = credentials.with_trust_boundary(self.VALID_TRUST_BOUNDARY) + assert new_credentials._trust_boundary == self.VALID_TRUST_BOUNDARY + @mock.patch("google.auth._helpers.utcnow") def test_before_request_impersonation_expired(self, utcnow): headers = {} @@ -1754,10 +2041,7 @@ def test_before_request_impersonation_expired(self, utcnow): assert credentials.token_state == TokenState.FRESH # Cached token should be used. - assert headers == { - "authorization": "Bearer token", - # "x-allowed-locations": "0x0", - } + assert headers == {"authorization": "Bearer token"} # Next call should simulate 1 second passed. This will trigger the expiration # threshold. @@ -1774,8 +2058,7 @@ def test_before_request_impersonation_expired(self, utcnow): # New token should be retrieved. assert headers == { - "authorization": "Bearer {}".format(impersonation_response["accessToken"]), - # "x-allowed-locations": "0x0", + "authorization": "Bearer {}".format(impersonation_response["accessToken"]) } @pytest.mark.parametrize( @@ -1874,7 +2157,6 @@ def test_get_project_id_cloud_resource_manager_success( "x-goog-user-project": self.QUOTA_PROJECT_ID, "authorization": "Bearer {}".format(token_response["access_token"]), "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, - # "x-allowed-locations": "0x0", } impersonation_request_data = { "delegates": None, @@ -1928,7 +2210,6 @@ def test_get_project_id_cloud_resource_manager_success( "authorization": "Bearer {}".format( impersonation_response["accessToken"] ), - # "x-allowed-locations": "0x0", }, ) @@ -2000,7 +2281,6 @@ def test_workforce_pool_get_project_id_cloud_resource_manager_success( "authorization": "Bearer {}".format( self.SUCCESS_RESPONSE["access_token"] ), - # "x-allowed-locations": "0x0", }, ) @@ -2050,7 +2330,6 @@ def test_refresh_impersonation_with_lifetime( "Content-Type": "application/json", "authorization": "Bearer {}".format(token_response["access_token"]), "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, - # "x-allowed-locations": "0x0", } impersonation_request_data = { "delegates": None, @@ -2107,6 +2386,22 @@ def test_get_project_id_cloud_resource_manager_error(self): # Only 2 requests to STS and cloud resource manager should be sent. assert len(request.call_args_list) == 2 + def test_refresh_with_existing_impersonated_credentials(self): + credentials = self.make_credentials( + service_account_impersonation_url=self.SERVICE_ACCOUNT_IMPERSONATION_URL + ) + credentials._impersonated_credentials = mock.Mock() + request = self.make_mock_request() + + credentials.refresh(request) + + credentials._impersonated_credentials.refresh.assert_called_once_with(request) + + def test_get_mtls_cert_and_key_paths(self): + credentials = self.make_credentials() + with pytest.raises(NotImplementedError): + credentials._get_mtls_cert_and_key_paths() + def test_supplier_context(): context = external_account.SupplierContext("TestTokenType", "TestAudience") diff --git a/packages/google-auth/tests/test_external_account_authorized_user.py b/packages/google-auth/tests/test_external_account_authorized_user.py index 93926a1314c6..a4e1217817bc 100644 --- a/packages/google-auth/tests/test_external_account_authorized_user.py +++ b/packages/google-auth/tests/test_external_account_authorized_user.py @@ -15,10 +15,12 @@ import datetime import http.client as http_client import json +import os import mock import pytest # type: ignore +from google.auth import environment_vars from google.auth import exceptions from google.auth import external_account_authorized_user from google.auth import transport @@ -27,15 +29,12 @@ TOKEN_URL = "https://sts.googleapis.com/v1/token" TOKEN_INFO_URL = "https://sts.googleapis.com/v1/introspect" REVOKE_URL = "https://sts.googleapis.com/v1/revoke" -PROJECT_NUMBER = "123456" QUOTA_PROJECT_ID = "654321" POOL_ID = "POOL_ID" PROVIDER_ID = "PROVIDER_ID" -AUDIENCE = ( - "//iam.googleapis.com/projects/{}" - "/locations/global/workloadIdentityPools/{}" - "/providers/{}" -).format(PROJECT_NUMBER, POOL_ID, PROVIDER_ID) +AUDIENCE = "//iam.googleapis.com/locations/global/workforcePools/{}/providers/{}".format( + POOL_ID, PROVIDER_ID +) REFRESH_TOKEN = "REFRESH_TOKEN" NEW_REFRESH_TOKEN = "NEW_REFRESH_TOKEN" ACCESS_TOKEN = "ACCESS_TOKEN" @@ -193,7 +192,7 @@ def test_refresh_auth_success(self, utcnow): assert creds.valid assert not creds.requires_scopes assert creds.is_user - assert creds._refresh_token == REFRESH_TOKEN + assert creds._refresh_token_val == REFRESH_TOKEN request.assert_called_once_with( url=TOKEN_URL, @@ -227,7 +226,7 @@ def test_refresh_auth_success_new_refresh_token(self, utcnow): assert creds.valid assert not creds.requires_scopes assert creds.is_user - assert creds._refresh_token == NEW_REFRESH_TOKEN + assert creds._refresh_token_val == NEW_REFRESH_TOKEN request.assert_called_once_with( url=TOKEN_URL, @@ -465,7 +464,7 @@ def test_with_quota_project(self): ) new_creds = creds.with_quota_project(QUOTA_PROJECT_ID) assert new_creds._audience == creds._audience - assert new_creds._refresh_token == creds._refresh_token + assert new_creds._refresh_token_val == creds.refresh_token assert new_creds._token_url == creds._token_url assert new_creds._token_info_url == creds._token_info_url assert new_creds._client_id == creds._client_id @@ -484,7 +483,7 @@ def test_with_token_uri(self): ) new_creds = creds.with_token_uri("https://google.com") assert new_creds._audience == creds._audience - assert new_creds._refresh_token == creds._refresh_token + assert new_creds._refresh_token_val == creds.refresh_token assert new_creds._token_url == "https://google.com" assert new_creds._token_info_url == creds._token_info_url assert new_creds._client_id == creds._client_id @@ -503,7 +502,7 @@ def test_with_universe_domain(self): ) new_creds = creds.with_universe_domain(FAKE_UNIVERSE_DOMAIN) assert new_creds._audience == creds._audience - assert new_creds._refresh_token == creds._refresh_token + assert new_creds._refresh_token_val == creds.refresh_token assert new_creds._token_url == creds._token_url assert new_creds._token_info_url == creds._token_info_url assert new_creds._client_id == creds._client_id @@ -514,6 +513,26 @@ def test_with_universe_domain(self): assert new_creds._quota_project_id == QUOTA_PROJECT_ID assert new_creds.universe_domain == FAKE_UNIVERSE_DOMAIN + def test_with_trust_boundary(self): + creds = self.make_credentials( + token=ACCESS_TOKEN, + expiry=NOW, + revoke_url=REVOKE_URL, + quota_project_id=QUOTA_PROJECT_ID, + ) + new_creds = creds.with_trust_boundary({"encodedLocations": "new_boundary"}) + assert new_creds._audience == creds._audience + assert new_creds._refresh_token_val == creds.refresh_token + assert new_creds._token_url == creds._token_url + assert new_creds._token_info_url == creds._token_info_url + assert new_creds._client_id == creds._client_id + assert new_creds._client_secret == creds._client_secret + assert new_creds.token == creds.token + assert new_creds.expiry == creds.expiry + assert new_creds._revoke_url == creds._revoke_url + assert new_creds._quota_project_id == QUOTA_PROJECT_ID + assert new_creds._trust_boundary == {"encodedLocations": "new_boundary"} + def test_from_file_required_options_only(self, tmpdir): from_creds = self.make_credentials() config_file = tmpdir.join("config.json") @@ -557,3 +576,65 @@ def test_from_file_full_options(self, tmpdir): assert creds.scopes == SCOPES assert creds._revoke_url == REVOKE_URL assert creds._quota_project_id == QUOTA_PROJECT_ID + + def test_refresh_fetches_trust_boundary(self): + request = self.make_mock_request( + status=http_client.OK, + data={"access_token": ACCESS_TOKEN, "expires_in": 3600}, + ) + credentials = self.make_credentials() + + with mock.patch.object( + credentials, + "_lookup_trust_boundary", + return_value={"encodedLocations": "0x123"}, + ) as mock_lookup, mock.patch.dict( + os.environ, {environment_vars.GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED: "true"} + ): + credentials.refresh(request) + + mock_lookup.assert_called_once() + headers = {} + credentials.apply(headers) + assert headers["x-allowed-locations"] == "0x123" + + def test_refresh_skips_trust_boundary_lookup_when_disabled(self): + request = self.make_mock_request( + status=http_client.OK, + data={"access_token": ACCESS_TOKEN, "expires_in": 3600}, + ) + credentials = self.make_credentials() + + with mock.patch.object( + credentials, "_lookup_trust_boundary" + ) as mock_lookup, mock.patch.dict(os.environ, {}, clear=True): + credentials.refresh(request) + + mock_lookup.assert_not_called() + headers = {} + credentials.apply(headers) + assert "x-allowed-locations" not in headers + + def test_build_trust_boundary_lookup_url(self): + credentials = self.make_credentials() + expected_url = "https://iamcredentials.googleapis.com/v1/locations/global/workforcePools/POOL_ID/allowedLocations" + assert credentials._build_trust_boundary_lookup_url() == expected_url + + @pytest.mark.parametrize( + "audience", + [ + "invalid", + "//iam.googleapis.com/locations/global/workforcePools/", + "//iam.googleapis.com/locations/global/providers/", + "//iam.googleapis.com/workforcePools/POOL_ID/providers/PROVIDER_ID", + ], + ) + def test_build_trust_boundary_lookup_url_invalid_audience(self, audience): + credentials = self.make_credentials(audience=audience) + with pytest.raises(exceptions.InvalidValue): + credentials._build_trust_boundary_lookup_url() + + def test_build_trust_boundary_lookup_url_different_universe(self): + credentials = self.make_credentials(universe_domain=FAKE_UNIVERSE_DOMAIN) + expected_url = "https://iamcredentials.fake-universe-domain/v1/locations/global/workforcePools/POOL_ID/allowedLocations" + assert credentials._build_trust_boundary_lookup_url() == expected_url diff --git a/packages/google-auth/tests/test_identity_pool.py b/packages/google-auth/tests/test_identity_pool.py index ec0f5074c4b5..dbbdbf53a4c2 100644 --- a/packages/google-auth/tests/test_identity_pool.py +++ b/packages/google-auth/tests/test_identity_pool.py @@ -506,6 +506,7 @@ def test_from_info_full_options(self, mock_init): quota_project_id=QUOTA_PROJECT_ID, workforce_pool_user_project=None, universe_domain=DEFAULT_UNIVERSE_DOMAIN, + trust_boundary=None, ) @mock.patch.object(identity_pool.Credentials, "__init__", return_value=None) @@ -535,6 +536,7 @@ def test_from_info_required_options_only(self, mock_init): quota_project_id=None, workforce_pool_user_project=None, universe_domain=DEFAULT_UNIVERSE_DOMAIN, + trust_boundary=None, ) @mock.patch.object(identity_pool.Credentials, "__init__", return_value=None) @@ -566,6 +568,7 @@ def test_from_info_supplier(self, mock_init): quota_project_id=None, workforce_pool_user_project=None, universe_domain=DEFAULT_UNIVERSE_DOMAIN, + trust_boundary=None, ) @mock.patch.object(identity_pool.Credentials, "__init__", return_value=None) @@ -596,6 +599,7 @@ def test_from_info_workforce_pool(self, mock_init): quota_project_id=None, workforce_pool_user_project=WORKFORCE_POOL_USER_PROJECT, universe_domain=DEFAULT_UNIVERSE_DOMAIN, + trust_boundary=None, ) @mock.patch.object(identity_pool.Credentials, "__init__", return_value=None) @@ -632,6 +636,7 @@ def test_from_file_full_options(self, mock_init, tmpdir): quota_project_id=QUOTA_PROJECT_ID, workforce_pool_user_project=None, universe_domain=DEFAULT_UNIVERSE_DOMAIN, + trust_boundary=None, ) @mock.patch.object(identity_pool.Credentials, "__init__", return_value=None) @@ -662,6 +667,7 @@ def test_from_file_required_options_only(self, mock_init, tmpdir): quota_project_id=None, workforce_pool_user_project=None, universe_domain=DEFAULT_UNIVERSE_DOMAIN, + trust_boundary=None, ) @mock.patch.object(identity_pool.Credentials, "__init__", return_value=None) @@ -693,6 +699,7 @@ def test_from_file_workforce_pool(self, mock_init, tmpdir): quota_project_id=None, workforce_pool_user_project=WORKFORCE_POOL_USER_PROJECT, universe_domain=DEFAULT_UNIVERSE_DOMAIN, + trust_boundary=None, ) def test_constructor_nonworkforce_with_workforce_pool_user_project(self): diff --git a/packages/google-auth/tests/test_impersonated_credentials.py b/packages/google-auth/tests/test_impersonated_credentials.py index 05cdb9dcc64d..8eb6801d62ca 100644 --- a/packages/google-auth/tests/test_impersonated_credentials.py +++ b/packages/google-auth/tests/test_impersonated_credentials.py @@ -178,6 +178,15 @@ def test_from_impersonated_service_account_info(self): ) assert isinstance(credentials, impersonated_credentials.Credentials) + def test_from_impersonated_service_account_info_with_trust_boundary(self): + info = copy.deepcopy(IMPERSONATED_SERVICE_ACCOUNT_AUTHORIZED_USER_SOURCE_INFO) + info["trust_boundary"] = self.VALID_TRUST_BOUNDARY + credentials = impersonated_credentials.Credentials.from_impersonated_service_account_info( + info + ) + assert isinstance(credentials, impersonated_credentials.Credentials) + assert credentials._trust_boundary == self.VALID_TRUST_BOUNDARY + def test_from_impersonated_service_account_info_with_invalid_source_credentials_type( self, ): @@ -646,8 +655,8 @@ def test_refresh_source_credentials(self, time_skew): credentials._source_credentials.token = "Token" with mock.patch( - "google.oauth2.service_account.Credentials.refresh", autospec=True - ) as source_cred_refresh: + "google.oauth2.service_account.Credentials._refresh_token", autospec=True + ) as source_cred_refresh_token: expire_time = ( _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=500) @@ -659,15 +668,10 @@ def test_refresh_source_credentials(self, time_skew): credentials.refresh(request) - assert credentials.valid - assert not credentials.expired - - # Source credentials is refreshed only if it is expired within - # _helpers.REFRESH_THRESHOLD - if time_skew > 0: - source_cred_refresh.assert_not_called() + if time_skew <= 0: + source_cred_refresh_token.assert_called_once() else: - source_cred_refresh.assert_called_once() + source_cred_refresh_token.assert_not_called() def test_refresh_failure_malformed_expire_time(self, mock_donor_credentials): credentials = self.make_credentials(lifetime=None) diff --git a/packages/google-auth/tests/test_pluggable.py b/packages/google-auth/tests/test_pluggable.py index 6bee054c5b1d..066920b22dd8 100644 --- a/packages/google-auth/tests/test_pluggable.py +++ b/packages/google-auth/tests/test_pluggable.py @@ -272,6 +272,7 @@ def test_from_info_full_options(self, mock_init): quota_project_id=QUOTA_PROJECT_ID, workforce_pool_user_project=None, universe_domain=DEFAULT_UNIVERSE_DOMAIN, + trust_boundary=None, ) @mock.patch.object(pluggable.Credentials, "__init__", return_value=None) @@ -300,6 +301,7 @@ def test_from_info_required_options_only(self, mock_init): quota_project_id=None, workforce_pool_user_project=None, universe_domain=DEFAULT_UNIVERSE_DOMAIN, + trust_boundary=None, ) @mock.patch.object(pluggable.Credentials, "__init__", return_value=None) @@ -335,6 +337,7 @@ def test_from_file_full_options(self, mock_init, tmpdir): quota_project_id=QUOTA_PROJECT_ID, workforce_pool_user_project=None, universe_domain=DEFAULT_UNIVERSE_DOMAIN, + trust_boundary=None, ) @mock.patch.object(pluggable.Credentials, "__init__", return_value=None) @@ -364,6 +367,7 @@ def test_from_file_required_options_only(self, mock_init, tmpdir): quota_project_id=None, workforce_pool_user_project=None, universe_domain=DEFAULT_UNIVERSE_DOMAIN, + trust_boundary=None, ) def test_constructor_invalid_options(self): From 7e9d495b52098b5a18b96a9cf65d4a4fe0e8d540 Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Wed, 22 Oct 2025 12:46:26 -0700 Subject: [PATCH 935/966] fix: Read scopes from ADC json for impersoanted cred (#1820) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: Read scopes from ADC json for impersoanted cred * secret * secret update * secret update * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot --- .../google/auth/impersonated_credentials.py | 1 + .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../tests/test_impersonated_credentials.py | 17 +++++++++++++++++ 3 files changed, 18 insertions(+) diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index d6635a14c14a..334573428e96 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -526,6 +526,7 @@ def from_impersonated_service_account_info(cls, info, scopes=None): target_principal = impersonation_url[start_index + 1 : end_index] delegates = info.get("delegates") quota_project_id = info.get("quota_project_id") + scopes = scopes or info.get("scopes") trust_boundary = info.get("trust_boundary") return cls( diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index be81fb93274fb9174f676c58c3fa77d6f223ff12..3ecb9c7d8da6ff6bad946b3f0a59fcb0a3f74c82 100644 GIT binary patch literal 10324 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 literal 10324 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 qTwx5i^^Nq_f93_LA zjDS;Wh?g6RlS1y<%mN|3H}`?r3_CdzXUNy7p0LcRVg=Go;ud5;H94JvbpWTJC9s$Y z%G?2Vng$jjt+?pKIvG@<7?ZE29m=K~mepEYSA~to&sv0%AW*v|752MBTIwl4Diktv zsSUJM+lyUQ-%h}IQR0!JItj&hM}W3ko({^aECOi^9TOT~NQ>-gvf<^df# zuEO6U8_RBAg1kE}VX1M1mr~YsI$B3MZNnR4gdK98OCdR zE{#Jd&dTX36C@K4_|w1;QGZ0gK1;Vqm!_C8J{dIqL4UN*YySj5?-MPBQP>_Q{s=*9 z-rzp_mq7~2u)Eoh0GilB`c_eruQv-o+WYHgomE26W+0&Tx!|W^@_Eizv%^6lwgvzV z&wy||Ne1@{OoX&>k~9GAqh!}XPx9MrdP_@e%KTe5PiVP<*IKM^%)1QCT;{`slXU1f z@UlJ^k~Bz?j#GVE<_CJ`+%^dN=^#dDjC>w-KQg#+`Xk@~;b zw)x30KM@jS?~f^W-vmHix*()7-I@xYvIduMbS9*Uq$Rn! z!ItFGuh2@m6$)JS^>^AYjMf|jqfu`{u2K!~^jJ{Vip;v?IH6mSAgW61!IC4VT=kM^ z8tw+QjzK$lPZ>z_;^kMDnFnRXS~0P-HN&qdgM5aGe2+ivrvscOx##heAqc>NvPp^# zQXLcm^WZND-~b1tgfU}}byYTFrwpVQz*W0pOOa=b1F>Q455VROx$PFW^A&CMk))*CvtdiHXH8-D6Y?Sw%b8U4d7L01$U~ zgmT8OA!Dw*VB#ha}N*-Z7M!)%!%W%xwY&%Be&wn&fcyNN<5b z480k6+Xdx~Ju53=yfu3OQ4<;X^k{7^_kNS^NLMrohA&}rQT-Y8yfPC(S_aQ)PELnb zj0&?V|Ee7ju0?X1ADkL9l5OrtH1Qz+4I%{urr8Oy7lw?NdASWrB8xK<8J8sG&shHS=O}A^laZpD z#O}*_Goso+h4G(3MI<=JPk8d*gU(G?mi^9Z>+rw39$*+=5|qw1XXS|yT+%YB1C!0- zp%~qc%H|p`-Lw}NY+93V*ApGdH{_ti1I%S{&*%WEhvVQT^6+T_$)R`wn>my+4sD-3 zc`DHuP{Z)FZ30TDZzrIc+xGCeHcmxO8ks2LfanL+%)sj*XqNESi5hUN(UHqA*3x{? z`TOS+MrMs{oHDWDf&D~Di0Ozvd_DPhzBwXh?C*KcKVp#bJXHR)UBME|*pz)j&U1-f zSzFYMjrLqYNYzc3I^<0cRd8FbkqqsohsbFCFDzj!IXV6fqJdKSr@DnmrAaiUKb2k<4DQ^Y%bKg+r*M0q@qm9_2>ua;{ zfs|{D`Ipx7M-N5AbqMuISR9?RY3#q)<*>q|IEJ{;giA#yLG-d)Wwnx#(5ItyHrfdV z6Mp~Nu8O?a!|2UAf@Bn|Y34JrYA9>4@zWa$^k`kM_y8Nzsvf=J8tnMRdOH4sboCTJ ztR|zl^via59nI#~CORxWQfpw}E`nUvXH~LEJGPTC{IP5onqj{B-qkXbL}{R@3jBl* z|C~sldZ8fu4;XiwEiujH!l`T_7HqHNkz7WCF;#WOKYf;n@t=_bf&-+ZlL;J(ud?l7 zU;6K2$aAoS@pMzcsiq-nw9y*L(%;2r zLu^S(Lbg>reNMOH5CfN0jOV)^mp0f&4u>us5aBqu#R?FTb2Q zbDm6nIYB?{Ne`4t%lROoLFpxE#Xz$z<1RJXPG=#P3(ad_R!Y~POo~mw%$0l1oN63f zf{gYE2Z=;R_biM2k6i)Xeu#4Am}cOqSR6LdF>hzU(}QGphK(rdQRop5E1P?`A`{hs?Zjnb2|-#Am=1a-}+swQK^^T zP!UyIQ1})kIm|?8dj`at*k0S_p)P)q=t)tgse({&zwf{UY|rK`83)0R1f&Lvz`e!+ z&I1XDxpf_4sO^>#b4FqR37p!WH@w);5=WXLoj8;ZN#}2081qB34PX9Q9!z!6!gia4XB<^rKNMY`P=H1`8MfQhedfnK zOq-v^DKZyH*-V5WaG(%kOAMKNWu8!_6>Em~;$~?MO|)hx?bq;Tx=S?+(d&CWm50)a zjiAZo3&HjF+NVNqKk;`ZAops$QX3lHM6w<;F2Z%h>%O_}ZRa@rUGjkvf_F<>nJNhT z?M0JQ=_~pjuvCXRTUqU7g01!N2GPf+r_+?SkkAkU?(DLNv0JFz9N}Mi5YNo-cs8Rt zvE8o{Z-hvbrT}y`nnBts{j8L)znjf01)O@|v1) z#0@765@7Ns<8&n1UVoYq*(LyTJ6b%nrTHXnE{Y1aWqZNuw!EkN6WI6(T)kwU>kjVo zo)c&r{stY_>1jTnf_sfdr8b{wp7{2s;zN~mg{`8k*F?{q9e1zZlrVlc{FZ9kpY9vV z045qB>n+-EM)HOF#Yn3YkVi1)6>HC7{Vuh^;5Day$|RmmVgRr5lH{sQ6AT=8PREE| zR9`1QSxHjM6kb|DGNzqn_@?Q+UA3ek`*A=XI3SlcK1;sLh`0tZ(mGo-Nye2&aHDlQ z^CVpd8|hdG!35^f^rm$dcP6c_xK}VZ*R4&@RQ9aSo?N`R_Q3A|4Q)?8AQwq;>v5qu zQc{a`BA#n6^`_OBE6<+1?t>d^fr&{deBNlY(1;i`UM+XG-je zrvHr12~Bn{w;-=7;}an&jQk#Z6C<2>bo@-8Nvh~4FKfi3Mx#{pPslcG*pTKP`Ts$c z0Ihz8-Ya?WZ-!Gm!GWKcuaDK-v{W!0JIb5pBTdM&^>%q(ht7Q2^XyQs?5g z*j&%TgWg^)K#zmV2A&zX>y0EJPJtnc7j_sE7VJWf?FS2O7(|7863dwYdG9hj|7Zu` z2>I`dSZoC}PsVs?@mUo@=tTO;x~rp-29T0RF23VGsdOT-cb zeiG*kZAB}ZP^gga`O7fQTCflrhGVg}^+Qgb7StAOAE*i+?5+w@!0lgCXIl>!A*QKn zpn4&(G7%qCOk^6e%=@2l!#3G4Rkwk!qHwIfvoqH_Tv5YOzgPXp-5II)1#A_5O)39GLz;p zdT5t!69wZlP11XXUAaRX7sQp-j8yKT*FlqJ`m?cvE%U5ET_lq75+$xY-ptSDIaa zhbB3&;LNwszNZv7S0fOYmAd$^E*-i(Y3G1464o^B m|H#$Uu^QNb{2ei?fQdaF@C&1(22*Q_;B!}I+p!bF3#Tjet?)(w diff --git a/packages/google-auth/tests/test_impersonated_credentials.py b/packages/google-auth/tests/test_impersonated_credentials.py index 8eb6801d62ca..2cfc05bef2dc 100644 --- a/packages/google-auth/tests/test_impersonated_credentials.py +++ b/packages/google-auth/tests/test_impersonated_credentials.py @@ -213,6 +213,23 @@ def test_from_impersonated_service_account_info_with_invalid_impersonation_url( ) assert excinfo.match(r"Cannot extract target principal from") + def test_from_impersonated_service_account_info_with_scopes(self): + info = copy.deepcopy(IMPERSONATED_SERVICE_ACCOUNT_AUTHORIZED_USER_SOURCE_INFO) + info["scopes"] = ["scope1", "scope2"] + credentials = impersonated_credentials.Credentials.from_impersonated_service_account_info( + info + ) + assert credentials._target_scopes == ["scope1", "scope2"] + + def test_from_impersonated_service_account_info_with_scopes_param(self): + info = copy.deepcopy(IMPERSONATED_SERVICE_ACCOUNT_AUTHORIZED_USER_SOURCE_INFO) + info["scopes"] = ["scope_from_info_1", "scope_from_info_2"] + scopes_param = ["scope_from_param_1", "scope_from_param_2"] + credentials = impersonated_credentials.Credentials.from_impersonated_service_account_info( + info, scopes=scopes_param + ) + assert credentials._target_scopes == scopes_param + def test_get_cred_info(self): credentials = self.make_credentials() assert not credentials.get_cred_info() From c3804c5f40c7cd42d61de6c10789a2f5c50731ff Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Fri, 24 Oct 2025 13:45:50 -0700 Subject: [PATCH 936/966] chore: secret update (#1847) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 3ecb9c7d8da6ff6bad946b3f0a59fcb0a3f74c82..38fbef135b6cc24b4c1c3f6e9ddb6e8c2504ab82 100644 GIT binary patch literal 10324 zcmV-aD67{BB>?tKRTD+1R#GIDNg9u0YATEoL7 ziXIyzILJgcvk}>)*9{3Q;wyI!e=j4$YyXu?Gw|S~goEJXz^HT)f9lh^R>bZ?xv5M! zF93Q&%fw|CGc0^tPImhg8NEb?{287t#5mB5pzKI?*T|C-#3>1QSpfhBXU}eTyY}*C zImR0f7@)hUTlOor?X5k^5gJFmfxU2r@)yF7=JFZkbZK-2r0YgjpD9u#-m^%P`g=$= zsugS91nEH-i{DQMa28F!_$z=x>Nl_J)5IM=$J00J%+f~q6t@*roxS0QTc^y4eac zmhOu@yn@d`*66K3u%%spy|q^BztAOQ6}IZr#t^5w-!iY;&XTqhQ4V|eJwmP{ev)8i z^&ZDBqMLB7JvUbe+Axy)#!6hf=JPIjZ!1bMWvbq`Ws&lnijvM%xxTrNV-pI&Ag!C3 z^)P_IcMfzwo2=;)I)v$6j+F{UN=w@}(D;>o0>`^2gE=2y@AzrTaSM0T&~W!pJ$au0 zcF@361TGj+Z1M6fZQ=3tN-Z}{%(+-SYe7b|!je@9M#ruSXr{JH+NRdx^o=!z<4kWQ6zO8c;9CY=Yj9(|*CviQ`bI5pSxT8}Mu|^suT383 z5yJjROyCz_NV0B*+-}YQCNd;?0b}NKlYJ0FW2na}yu5&E5&-jl>R9}D~% zKR3(Z{KeUZU0}>d0WK~*)U%fjd#ux^d{_n(n0&D4>I=XOb|gHh<7WSBb zM`i(TxM$~qKXm!}dCQQBQ4TCrH7jmvri7x0^c3KY;m2pYTPb{%Gr!fO|@ z(mu?UXYsXlEb)q3LBXg-tet(8;eO6GG11UpQW_W<{HR7NHPatZp2kV8j2YJ+%@i6V zo1v?1b4UGjWLqxWsDxVz=9IsL8h{(+ zN@TraVB0p%glnv++|^YzC#oDBO$^WG-ZFY7$SO+(axpTK!&jT^gj_IcUG5f?0@RIW znx*^!IqHXp1?M7;jC-j*2KlyCQr+DzPLpT&?>dM!9-SQgmytzR!t!!4I1Z%#;V08G z_l8TLD56^$~h_ip&%V@U)JW;{-Xu8xA9>poUH zQt+f|erPljDA}YW>k_>#_;;>XBiPq)!ds!jVj|s~WLBvc-ZQySqlj7n4M15GDVQ(W z>1bU>CCv+e5NAt|DZy33dJn^#b&kdOk^geY`_je`jGskhpi@`7_n;gf^1vmPlVk0D ziMXh_sU2s1VnDz<^yKEB)T9}OZz=w?T5)$k2hTzSVcC7WvDvZV)aey~r&dL{Wi-r(F%pX~PwmCLY2YI2B zJkW|RzJz!&>hvdmS>6Jw_mLieInXi6KpdN z$d~8IWI?tYWR)X)7mCry>mu6F60lSLQXeVwFU=pm_Y6|+C~ zB<10n{|~nd1)fM@_hY@`*m;!Wnz2zH*c|Co`2igcNq@YG=Nb_iGkjrxdtKK)Q7MQ+ zWeC{KNgCMs+r-)-b0b17@2dr`hFB%?vVm`!(U-0nd!dknMnRubSYy!Ovq&Sw3{toCdROZ)0qvz}{1RG; z{CoPh6F=^>orwFP2Q7(EcH7_HL}7pwM`c#`&2s2HsZguogycQp?df7;F)YJ%d7%$n z{fP){7Zhx^lww4@0&UXNcSGY4_(EdkpHSvY*CWz9l1;jA1#&|aFon-nu`8my6TM6? zDQGd5T+U|@kK7f*LMD&teyUC>@=}H(im?iMzf~&*ADhpYR=o}T`oeRpnai!aUljl( zA*D&~MS5m`t22g?%KG+API(NLxm^~5I}7Q&0dQfG`iU@>|6hHNHLm^M_rAZsjUXVDE&;*H%GXP`D1j9Nz#O>4^{qmzqD(7oi$AbEkI&o)|XXP4MKpN7e z);^PE5pN#!@zPpdsKY5gYMORUQ#Hli8Ubl>(T&e_F~|axgrm_%HTMjLhbgTdK?&*? z=zhpy+0+E$8Z7X3nEACZ)s%-`Kyoa}y=kX{Jl{0-l69PZxb)qEPE(j#v3@IHUz8gGz%tFaG7WSYd<8I=BWt5V zUqK)YeQd|jaWNn94C-POfF~vW-K%+hvf-F{=g|8Somb10|4@Ymq;1IGl#HUZL=ADo z=y&Na#3dEH>elXQ+99M-b*X4EztSqmnhyQ{aqFz|gUqO+;w34c)!_lPt;L_+n%?P_ zIfZf%r`ifJjeMAfj@62SZ31!Z`G)R73&=J@bOP0uU0)$C&)#b+Z9t6q994FB3GU0> z^`Xw#7@J$6Ttp(W)teoUx=Sn^?h~nLWgl?QoK}lmedNV$V@az~$<^}*OY1CEl=pyL zoC>5(2`&|*R^Ebfdm1xyrUt8=>U=2o=(w>Wxj=dq1iwrFV|aPW!{$!zCwBW$WY-^O z9^MwF?gD5zmPkyTR+K8ozje&vr()m9q%Tt)>!G>@OP~+o{qdWL9Vox<4XlP2k^AoA zuambHCtl(j{d)4I%vH$f?36#N)QFV1Ap(@B+Ad&Rj6Q474-flZR)ag!ZHv|d&6ttw zKz?Ob2LmkJxEJa+%ez-OZBU_E)E+vt_XW8yJRGmT9Yht<`j;!e+4~c#T6Cg#!A0yi zej2-FP~M0U=_XAjG+094F1pS)ulGmp8VX*~d&=;j83JkpEO)=DJTb?AASRl+8ydHJ zFB4L(TPFHEL{O>DQ%z9sZKuESMIpVL>L-hoIuL~vyGkNg&3XgNr9-g*%`r#V4=rgPpN1b?4_OEa5G{k2q6QNW6}TO?T=!AEJ>+=1iRtAkTmT&6s4>q zd!;5XJ}`^b(^tXOnlY(G*h$+yyowh?>~M4-Y%SiR1$v^;y%W4qTa*uDb^=ri7J*c8 zT23OC;WW!^4Bi*WKE$ZRU5qd>;4tCBE_gknY^4$txp`iGR?aG{1uY0P=42tVHj)G} z&4U-b;q?}gC~wnkT9DZA7)o^%Nh)gC9lu(4OscNh#gZJ@QyYcYvFi;6`pHSMMwfA< z`J?u2dFHdLYA4YeXer95k`}VcO*Magnf;SD%hD4~3t18VvBLhyo*k<|M|z=j{dbNo znbt=pa_U7?nfSSx4)~(yoh@k&QSb@x?(9hm2I;fo%a)NNk4ytF2Dtx!QZQmXLUcvL zc4)0%2(SOE67guI3U1u1Yg!r3C-@9PCzve`YBz5kyE zjP}iR_WRVyhW{v|xn+Q*j=ZRPZg{UJhW1{l8Etp3)aM?Krb^Mg(l|r6pl@CTacc)K3GKz$^6In_+YMLveKa>lia zmj(%TGSAvb=U;DWYw)YPU=Q4+{n~kfkWj3Bx{YVQOxQ3-Y=8C;j8Ghf5oG7%oL7+Z zU<5yXrEV`0_P5UG?XtuX@*`XMdS+01e zek*3gSPTfWcf8JnS3*+{{6I}|g2yL(mpB5EV~!!ndBO|*rQlj$)L64e+&T^(+tC9vk0$>{Tb#vUE7`oqFI-toXpv2QSwcPpg38;o)4-o9WY4qr&Np z<~qNCfD?94uwTm@_vr`Yg02WmRZ(TcCCXC{-5WN~NeyDl&NBxFu;#YTWli+h&6*Hl zD%)=LR*;xr}PX3^~@;O_u^`l6Ci@hh&bY^Wm8hZuf%F zM%fL~vun!6{85vYWqD~iar^QTL6etT94Q<8Rf_|qqTty*#N~wG)!);Sm2J#6uperK z>2aW%9VAQ9zs6$|ZX2kWcj8d#^|e=Z1d&fwT5l%b$WGh)AsQTpP+R)o!dJy8TDSC~ zoiSksFi%k)53P?mr!*S$n6);l#F59uZ9f15LnFkW9XOYKp<=9(2W=%p@uO28c z2ksl-lucOXhNDiB;=g+l42z}?fYozx3Rx5XYGELIDwmGc0(|yD(Pu!T)2}x`p1X~V z$CvQ>s6tmbyw^9t7I)hqVhrFBGv*zbX9*B!O%H_dpRzWi`QxB1OFuishj#;aQMo&(fpr zd`#_d_#EEUX`UWbL}wSzDq0%?I4*_;Wgg(xf@=f~AU~PyrI@{$s^ica)|2u9;mwzF zG*7p7{js-}l;EA}_Rx#ZzbMrF&PieueI?vW|IHpe_W?3QwNxELYUQU<@aIy-IHbC@ zHPkwcT>~`6QSforZ@5i)zi$EK4udV%%Absk31YyW21pMVu`Y5>PolJhW_1w~2fI8e zs1om8Xoy!R>69U=&aAXM9Dxlhaak6sA|J4vcYA6=J(_g+H1nJ{9Uhj83Hk>Nkk}5s z)Jd>w;x@C0J1YOCB54peHmUBj%(m*Kf2c*xX870l%r1a9-)+j5rC|!xE^Fz2NA;9W zt#F5&RD`r;hm9Omrfn@W#M-MhaVTXZ~re4x;YC)d1QfDIZ(mCBa6+G0EIzeR? zn`MB8n2|P!4fht?nur?amx(PU^MoNM*Fm+80j5J1rb?c z8*19|00ONolbfc{MTeN@oEr%(Qy{-9bg2)KcMV@oVYK~O0fQAZ_0>CjRZ@|mJ?|p% zlI~nRu$>YwC7EyLu4bzc~Ha=dhoD(n&9KG!!=dYNzM4~sbRa*?`5esce`4B{xNCLZU)VFR-4nMZozLqhi4G%s40-~`SQS(BZ49zal7(;q6D%@{*X1xa$oLkxFODhKnk~iXm-Id2 zeZ)8GBT~^+%oO~9X}b=*@%0XIOR)o>D7e6RAw6Y`;y{Pq(Ll8SH@}rZGNmPj${et8 zb?r%D51obhzMq}1>sdGVj?f_Mspom}9bnWf+ju3ws~j}!4NT*Ag47)`{pN{bxCJ^L z*I&2BohfCUs*}s)sHzSB!umZH@H?NMblWx@t&s}uxDX3w6IVi9G}WtuV6WY$w^ezQ ziuh`q4s3=pKh1}j2&YZ1Y@CPO?VZPP2a6#beqU4KPv^8_S@%pmXRLg)W8$yt2HOAl z#8%3DuaN`O z5#f_{y(rtRu>$I5hj^!3{33jWP04!N5(LU}1=LMM!N0O>MN! zC-MIPAvvab(BVKc#3S;Lt~oih=Gt!UY}9XC7;+Sa=y2hZCRm%4Z6NW8jX6gMKnz6G z+`)i8WBx~{d@ZweC43GFPL1$yN=X$X-QxvTMh?VlR_?ukqf@&Jj81yIFkpshq`k3V zW`UE)pM34$Eg#*>BF|HKH(}*63^?Zy8`7@xDDA@gBN^zu7N5KM1T{8ec<%pndbWsS z{F>m*D8`M(_v)`3aT(@x7%R9BA0;t+los)?J%5(UZj&sdG%W(BJ=5N=G`NqT1E z1qZa%7`S^S4B1W*Z$+!>L)^{xAgpQYpbr34f53ZFcLEqmO zeL4fl0P73#ja(kor{64(Ra0$!cQb7cpF=9pFr`?)H+d7r??gf z^xX6LHA*(Clnl{2z3q>wA+cspI1cA)P|iaGlpl(RAxATknXby+>yu|BzWzOA1bvFGK1|>%cu%M`jP+1RANKPS)3;_?lLYA3-OUiJAn#t@C zi_EmS$kMilnnokO8atX@HH7chEQZy|jT!!qNo3uF7Qcumz(Xa;5#r3mhcou-6wr49 z)n)kURr!N!iHZIO4rnnvuSzlt7YJFGvquz&Dew1eHWJeV|N1w>D!Z_RC5FIwXHqe3 z{E`z6b`_+#7aC?&ipkq=IBkFi;z-WCuAP*=phJjmGKkbqu(gSI%9LCELf_N$LrcgA zvZAUN?yQ|aK}Lwl|HN}DiC2&Z3QCt#0Fo`<_4sf07b3(HVC6eZ)-}#0vhE3(&Aj-o zsZ6rJs!_>f)5fWMwu396TdC8y*gN!c~PuD|(FA`=>Lvar%5O#2ERyJ)zr()b!B zqlW{AdBY;Bl&7Rfgo22YT0o;HROW)sXyTE=QazP^8<<^~2W zq|>q;=x9b0fd@&G97#!dDv2>S_gONv;REYZCH-;N&cFf*lx)`6KI2tQ0jZu*?!Jcz zj9x}<2rvcv+9GHg@lsOvs|0EC;W9n9ial*%x<5Td#SUH?if+cO!lDMfuO(YX3na=spS;` z!j(#MOR%tS6oE>SRC*C!0n+|&hmA`IK&)ao`Z6n@ev_zCX^u#^p%+DContdn$Uo0* zvB*mhpojE^tN)f`_OMk1cOZ0zQ$-jn-0Nr=<~yUpnY+kx?Uf&z3)Ee(5b*eo%1+2i z!K>@Pn)U_-Hnqb-1zO(_X8VooNM3un^0mRjK2fe|Smd!_{@;0Jx1?2PbK@|(0g-o3 ztbdIy05n3R59htanh8E9znM#;7scFsiiBv?v20 zXF{2crxG$Y?VY_{*xvgJeL|gza$`EB2TeN7p3bb|NiKi>u^qT8n_gm25)f&mW>K(S;WL{boaUv~XadMN~gH^uI91Cj*&s9)vj9P zpC5#y8u*aP{~+R^%&e9jZy1CUp7g8Tfl^#wA=a@Tk;^a!Y>R2L3r&Yqqg;-$U~(cw zN61OyGARu>E>g1-WV84Kk zZ0@obbvu`wQ1c&E*RKFRb?NIjQbcC}nO}o@7%S?8GmDo2@APh8GKbR#snhu$9Bf-d z_{;I!-{F#x{*O0=CA1NX8mQ>o1pW)ul|xsS1vZ%sSv*NtU)&aGYA4i>e)Nq`Q=u^m zn)RaeE+5uhH@`9p z{>Fb+b3B1D-)Ak0vN$?yeS8{?w2Vd>_|i8D%XL901ApT4v=#qv8nw)&A5 za2fyXpwzEHUlYSKo(iz~IDr$bxj)Lx;|us0kg>nz>b-Y>(#m)Zi=ay*w-%Kpu2$?!*to*Ja$O z%ERKuT#MPKP*h_K>01Z&rdckRAbk4wk#+A@!Mwt04pumZ!bbV$DTG7HcO&)OJ{E@YeBkg@gdTU8E+7BcRx% zS^qmy+hA#oQSAu`_d)qCOpa@}SR0 zB{(kr_EbDT4W5~O(^5nj^f}LpK>4^`kS22um`N8AzasjGUWFusD*Xj#Ai;wx5Nvva zToAuC^cbtsf~9dc`g2vkMaJ$aOvb7=)_(#FJ2`R0e1twMC$at6A0plE9tP`(9KYGE zhZ0fn4)_OUwP?ka6LHpSIz_n#fR;65>)<7UW0MX#o#51H3x|fLJN>Sf-DZTgIJ#sK zr3`D?mHwZyC0*L}c&ey2K)_&a)}q7k3sWsKaT%M1G^F@tfF_M&SWuI_AZdh%gpH1D zZs&{qFInTD)am;mh2M7R#W7ifi{#qn4bI zqyf`b7qhyYUsMz@nT@$XsP0|<8cBMWCGw=%%acDd{fabBxad^50@4K&a*-3=ou&4_ z-i`}Pu7ZXzBXCks=r3`j!Af6O|Hw4ve!oZadgwQg_=67s)>bcT&AEumBzCd* z$KMWCdQ=!rj4=u)W*@5az=@~JSqCC>r{tMfe`;*9(`Go|gCR*}RQG8FO*xh%lNT+( zVKw&j1Dy@CvR1Z1-lly24!N_#asPHx$#zIcq!t%$+DDCkjnA^^<2E%jtadz;c$hP_ z4ZLrk#Gam!b8QS{u_;AdHZbOJSV0NL2wHLb+>p8TP!DhRdjm)v!&Ij^J9Vs(N^<3p zjWoYk7sdUn{xGIbLC$}WV(9tI*EeKdIZP-~Kk68QNEJ;p3At{yWRm3CKSqn2Ov5U_AQCYBf$nF zm6*-jsw+&HkIYNGX>$wdv;5o6(#K|o12jiz*3iwvu<&eTsS;!v;9E=Yx2Rs<7ZxR4 zhUj-QLDG+Y8$K9%hd{0vg9eBq>LOzMkLMO}m!xqG2MQ{Mfkyg9j*)^!w`W7J!|Ta& zJ`Y73jMmzMw-yaE(4dJ9ZSEU5y{l5226|ljY}qj^SAH#}_f;eompV8D?IwL=QlL3L zlfauvf75m#0Y1Q~+maM^j8k4UedfH(7DvWF__{wAEZ;ExcxGr$Yu%!yrQf<#Bu*Be zXe85v=p%~GwEE3AjUTldLM;EtUBc&Cr5@D~GjPnp7>AAAQL~onA359e{LWCQWvhJ=Q_Dy#NFVm#b+n;Pc2h zh`m<(Mj9{4eW>P7)=?tpn3Db?J+j22cAfLdN85hCR1UqL&XLFNUTIIkQnaH=(10I9 zbn7J5#k@P>ejIi8bM znj?=ZN@oo>QaM0g=h^%Lx$dsQiz~%Nbf!G}bk4=oSVGmwJYs4)+HyyrFtVNO)h z7F*ji1G&Fg6_BEf3$IKcZLdxdzTfZlB&j{4%56#H7UQEq)%)>lk=mYyeCRWy@4r;%k1Dg%rRFU=s#ugP?_J^PE8`RMzJ2# z-HzXcW_}cD7kuRPb?ud+%DHrB@|wi#M*i4rhYD26=)E_VT%5;AsKZ8T^ohh509ils zR4w0sCSBPz!Ks(*!v45Lk~COL+5~~=65b?xfktG;jEp0T#z`atj#|wJ?I0_5SSQdW zRIcQpm-TUHmZ+k=_J8Y&lQ`vXX&O=*NL*XF6ADAEHErRE^ckkmRL4IV_LkX_k}=I^ zMWfH?tKRTHFuAav?1p4QK}fdd4i(|CP1zf^`?!45qk(7kFnuS626Pyl>7 ziXJzEg^hL%XP|$@;?-%j@`fhl?*iF|YXs1v4Be{h2nS33RA!jIvrqqF$xar?Qe9zK z!*(mo``=&_;d~C)6lz{@W?9!*DM3Z>6Oc3V2|OPC59D{FMB9+R*F3C`(jed@8DlIl z{f#zYU2jv|T;dfmwtbDCjy-le{tS85G2A=fl$C^2W*Cx%d2zk#cA=x9TsO)dwVll4 z@3EwF#z~+Im8&lcj9QvcZeM9m;?yCy$y4DbbbVTPY(d{`s z-L*3yolE-2AHwD!EKmP+I~({_J3y|cr+BrK>t)F}fg}HByvUr;TBO3kvN8xxR*3%_ z%q~1#7i!Xq)0)>vxYb)QU+_?@O?MO5tU^mtRBTqfArvbC$pNY^nuLQH&iF%L4!=t+ z%HhWyyd{+?@+4)Ewsh`N>@5K78hL01q#fyW4kF?Ao(4noF)O?V;y zL}a>DyX!%WoXxLn5aoY_2+S!gc&mAmx$A*&82y6K@k`eAGE^WD<9tEg*661*tF8f?aqI-psu(mVO4mGs zN8bb5I!Ab}$uBQ9>PRV1nb?{{~@f{ zV<0OR7^SA8Lupfhrn3*6j!bD|4ouyiF7TbSqk^ILw_I-H$p&k8Hp}`&sRRa*ZrR>1 z!n1(uwdp|5*&#(o?TwB)151c8>=M3ckIr)XN`NhR&mQo3)&K&`VCRtibqD5zaaYH% zKk0vKdEy>&(OI!5h9}TnOD`s4^4~iHQmD`iX-Kqh(xUmOupJ}fKy03e$=a>`7glUq zqWzbYG5^o6kH>+uu^gReR~KG^Qjkvd4n%r7i8Ha;!vXGDM=E4t5P9_(P>!wPG7HVE zZFu8R>0H6Ep`}$SM+EyTB{mj%6%z>@;op_kvz4zfa`A!Dm1P|80h8jnZKaiAQ4s+8 zLzxpd^*9beNqd_MCMqdHkt>RSuZD6al`>GLU+pElB&V{uCO(LuHb8pw3i=#-*0yDL z4XIEQe3LLtB0f=#GpnCRV6fK{MYNvHce9cd{+0?yvhFy*3DVRb?z-dMA8jtgp-Q0N zr8g~QNH%9@acym#$X%QKPt|w8&9s201Y=WS6T`cxeZ@Mk&?{qZIa6aVdJa%l?=&K` zVZ!}i`dC;26>%JCC?Z$9Ba+? z)C7n==HUD7PRhGh=^R_~B>R(l_m7)7_bt{_T)@cbKxU7Jl+ll)(imR&**?{B?|KWK z{8>2j1{aWQ@voP0LK4ve#b>26M(racH6+YjJ`hksI;yBV9sg!wUdgmLaAxl5SX zw5tRin#PUoHi0B%x8qUzbM{ySERLo7h?NIi(n+5^7*^3Yz!85#L3WZJwA;&6mn9-q zI)_o6>EMvoMKzc3qZ6pL&WJJ>P0CRL+^M!_)!z1~INA`6ugO8xA^l|;%Ow9`(Cl#J zV=&`|9ST6dF%0w1WcVhhkKx@BX7SWyulwM0$I>YP3;3%CtlI+&#GxnpasD6~+ zXu>?wQp*x^!*AurPsE>Gsdpi2X^fc2$Bxm@Xbsp|zoFrR--}-T^dS&NI$|?GRSKy6 zJgrnSKj9&9zft;3qlD%o4e$9;@q_LQfqSH9)~*mR)FKmt#6@menz;8l5Sn16+H&_2 z)*K%#&qQ>#b-tT+1LMZX^#DgusIiV}{AT5)6k|=8`sUn)KvV3~8Ftcq!Kx8oEu!5E z(hI24k?mNn1W&nPC(ZyBZ~vu3F2LgMbx82YuYd~@}x0ck!#B$d^)IlYTc z83)S&HMJOMvF!=NL=*2#XHDOR3g-SIJu~cR{!Z`{5qQp;Vx88U+f!iHAdaSlL|uut zKE)L|OA!QQh?l-by(~by*W@(yI0_qn&2*~6(z!Q%O_CDJ;Fqmb%T^d@zxy<)Cx7SwXH6 zPd*Dl8PVo3^Ea62+$;lHr8b+Mxa)TbsK!{v`iXEC{a;I=0Hc*QV5Tzl5NzI|t!eB3 zsSpZqKt{k=Hw8CjJvP`sY1)w~P4X+hK*-nO?Jjl@f(}Ph!(Hh@rXiSGo`)hr!<2M& ziQez;tLTjK(4(|`4IWU@>a`I&1EW^I= z{m_s!2^y`W@`BO-y{2dbpTYKEF(j=k(8O{P7s`lzO%)%IBpNt-MbU~b30j|Fc0|zmrS;F;CkU83|v|H>6JH zh^T=7Q)`Y1wQPDp`^@9R{1jZx{Bc=o9oXfw&vX2O6n}){0Op;(s=jXf(wcuWZGp8k z5ng)qXtlF3fQ#ImNMZ)FK=u{s21+)b0LqQ*@y{Gy_LNzN{hb|R>x|>Y(lmjMEFDc` zIfnwqR&JnDd|J726L6S3Uw7(dyh9^H=n7Ea;k&#WZzS3xfGs`dM=h*R-K&wQMJG?Z z8PIM&#duC*YFY_&A6nGnXG(BCHWU~Pq6etxbYwX_^qW|ivq|$WZ4W&-qen-aZVp#j zk5aCLl&YG2a_zJlwzA-o1`1~Ng6`+f0DuzOU!D7E6B}C(_kk4n{@A+akMd`CO*W4^ z*WpFuWB62k0bE7`9BKQ6Uq6^uq-cEdf(1~X$|?G_Ig%_amxo@`%GZP$aOUV^qem6{ z(#@;P<+eof20Q|E4&4>5O_)vVyVEWp3uKH|d8o-w_uEztfnfDd#1MjsVS7UU#W+c9 z<}*7JF8v{?*$@LF+LTfld$y<|nk)Ta>l29q*=HEeOWyU`GyDqQnfqr(qjd(Cx!?O@ z0@)t7pxC35PAnJ>u^_yh<*1vpTQU~07TX>`Zgmd1Yt4;c01ys*rnQqh<^NsXjM3Yl zLPA=$3eBS*s1EHwiX*&!VpoL*v^;`EM_{W0cClH|qEOYA<2KDJZ6|{V+K5JQNKx=a zUmTN6LEZ(DvXDy3k0=Pb`6;sW+?g-DNyVHdj()m#AQ{uu%H0aENI3(KTonZVV1?gw?rYjntCSR; zpd2Gj2Krs!3R}K!eSnKd$llaETf_~q9I=W4aUmIJ0>VXD29DP-rT;dfFHJn|(8O|- ztUD45Vu{&Fn@U?rt#gYF7;9oXc>ALMLu}oFAzSDv@S@E^(Fmb$p35-eL4j6&SIef= zS-CW7p&d$PF?pa0Uu6;7CB39WL~-LIFIjaaZ@aCbGDgBC84tvFa|n3&R_na~INm-L z8cfW0kV){%XF?j&rSl4>=XdI}ej);YvbQsvf{jP%^ijtmf91-avF z_~iIGUr1e{On=I|GW&?3x{4;~7NH`0*Pvk4 zN&-K>9i0;*wyA>WbXpiNEVDgHAC@L_MCn*IoGzC*Y5nzygglM9Z_%@@F1>YHPol^w z`{)7R(23*^x%ggGDFTfy)JDL9wu&4!n4ms?JAOso+9iUGEq+kf-&342`#}r!%My7w z#btG8`1A>a_DpGWG;_`diyMf;bt!f!pman+QejSo_VqVjst5rr0%WYaIr4kxlb$GX zi=vrI4g)@+{KQs8ljsM&VJ6L;6@hfinGOJaHID-5;(kXajX-0Dp*9S4J10^=?mbaO z+#BRR!z%i4M&cEOoeKT5)51*K3IOWQ%svV;u~Ui4*S_f<`OXkWk(FB=>_? zX$#FNvL?4ZQNlMrh!R#ixRk(W0DQGy3(2LYt8~L#$iO^~pKT6p zAs+p3yGLB;;!v28C)?whPcVzCH-+zYx9T&pq8m=Y_A85{)Evh2m`;4{pxn}~jr_RF z-(0OfrADdxJa0cF@|1N8lGf?boJuJ8z;QCoplz4RWfhYe0MHAH_8I~UxqKF zvY-n7pOmG2 zx`0V^l4`Jdktq_Vo})UULj-U0=HWi#8{&N0Y90IP%#N-{zx&f~yLT;sZCnn6U<%@v zey2lZCdfrXUmUp5fGRdYaSuyOQH{II9$AaoizlJQs>Mn!hewuRy6gubJ$-QC-ti{z zCWkmpC&ou3nF(r#uv^8>8nZxR-neHl89@yR@h+jq>V=WO$p>~*CjD`ClC2p8`&yrr zUP+j?Db+59f}Zg+sPFj6Ks5V;fAV{F8_t@}c&9PfyJBRdrtc=^M;P!7`apG9f#Fc& zY}0<^pP9_6>uPwC_>Jefk4lCekv@etlU=|&P^@wuIa0_OO({`r9pjy~7A_W$k|cx& z63K&8GtS-2Ca^n!u9YCeV2+h0^fkWeQ;u~F?6*ql!^(nsM__&TIezkJy4pYAohOWr zFZmveK4go4fBHUozQPhOtNxcJ!2U~oJRmQAM^N}vgP3H9lO#5#U^W2fn(@~e5#i{* z5{=LKrwHxOAPivOfsEHtf0`c*5g$WS&cP9&olVqB#!WJU-T+St+{c4=mw?qNhC1ar zZ}rOu&cga6hY&~VHU0tCf_ly3R7{Vn(2^_XpKex5tlhTMj<0hz zI%u^+>%tz9{ubV~C@2Mb3fQf?4`p8=J2JaO!xXE1CyxMXnB>It$I;P<#}Tdo4Fz|2 zhLlqc2IuR)>SAcgklZ!6D<#GOWdnJ70n4x*shOwXa(az9EOvrd;ezIV!Y1EnwuA}r zYi)W$%YDr>R-Hg9KW^d;brI9LK|rjoeS<^n#r;cE=$Iw~9g*hhM-VCAa@o(bLpjLq zHb1-0{76e4aHEu`r~ZyCAKmj}APJXZs@HOlyVLr`y;1gR+Wu(sV42Fz%$7=Rw^wD7 zr8s`Uf6Dain^32Pk8D=Mqp3#<+bOdw%i|C*g0Y!8uXClmz9$Ca+CwD4t`UueOmWRyqO(Bnw(=4%2_2nTu%XrMa#tlQKuwHhkM)0bdfp>?~>ga(NWnUEjU zSq1FrXgHCLjYV8}D`;U?FxDHMi9isU;ve_4hTxu+#d%z`I)V;jjJ;7LcBZ?JA!>nve1LbUUV^&2?cn3;Gvg6glu^Lp)pB4pPPOfD+tFBMb z9j4~YQ@>%#NuiF~qrrB6Os=_X7cO9HXWlvg-I4(%eKq)HiltF<5vdt2zK1Jr8XyBY z^r`hP+1V?ZT<@G#`_c-DAsvot%AZ|!kq@j@)RgNX?`!Be6WPF0a+&l7Fp$8X1WoYb z=0k}93g9*8ZTT0oVbXh{lzObTJu+o2{5au9l|;8W!xrBRxPWK!_i&5zw?VAafZ#Aa$*aEPfk_`bIiPo%8xTSZbZA21@zBXG?OQhn`joKNG>X> z?73qCrV|2cSFZfWkIp4r?|2s=@TSUWO?C?vR2ipdVb&dvesuO!VWP<0ql&v3U3?^` zANsGM6rGfm>jkv{`{9325Iul0(FtyI572E{vzb#2As&eJ$Ud^qRy3!V05btM5KM4) z^%)jWT-2D7!ek!0|68vqza4_9Q%N6}U0;f@lZPBZF&Lg~gbiVJ$LgP+Z)bIxy4vXM z4^8eA)HUgUO*N5i7EA)~!w$^U;0nS+q%y>jm%_^>;CA7VdrS(S7)50p9zE_FBNc0C zF9|8*_ZUJRIDJlQ@{S?G^-c=XN)B>kFl4f$86zx>n55L=>SE3;5~uO*!)~P(|1j=< zHw7G0HjFuVD*^YYmh=dIj`rcx-F-y9YgcC+Ulw* zA>v=3Yh{1AVAXuIYohRzs4uStLqZs!{9ql*Go*T$yxX8p<$P_X;PHeGN zW(lu!ZLpCkO94P&K0v?BVaCK?9iJO$S+A&MMEJ0K`b`E8s@{*29RZ{TI84&5-nza- zFz;)ibHLZVUF}_%sdO>A^QOauxC?fZoB03nj7asrV58j6a_qYR5}tge11ck%496$f z#l(9ScWGg}B{p+i?t7%+N`;JH7gIj;I?%Pgzu$#i^8g8yAf!LoRnx9nK+uyRmukKP zvGzW}n!-j2*Hf`Y!sgh(vE%8zfQ$WwIFi!j>5?gE<{mcy!}z z$yJq_Rs49SR&wD#Wb_@3Oc+TC<^hWJgM0&LNHEVWIon4#A6_?s;c?Ew?2i-0?4P=IDEwK}+{&7R+-p_Ls=-Ip@s{IzPjkrIW=IC^dZ#}l>{i~PE#R*y51yEEm z^kM^9$9RT!n%$u6qe~mH=7kRuuXWTAEmp9^lse)8 zW(uT7ZGx#~K2FVBdzeSr&G7rXTp6?$Oy9JueG)GZ#c-bX<2-#RFq&aPR~Z#(JCyXMyrrPwHQIv}3KbUIYPOci>{A-;x3^P9{^S(RWvnM@}+o z=z28xaUV3dszIC}$bUbOYf8=W+3SQI25P$qsEB*wYfx@SLuE@@+HzOe*1U5_aoMkL zn)*X-T9iLQ`=hPk}n2Fh&^#Eqrl>3n&{=)O&?=*j?|!*mtFy1TH<4h)Cc zc;}K^B{E}#Y73$EZ_;!THjh*)Eg+eOJ_cZ8^e?(~Mz>#TnBnd`st(?5w^R|yHK6uSEVHdlO|zC_X)-Zc4z*wBxbW>OqfXm0b8a`rz!@Pk zWGYF1IT<3Xg^fn23LQvX-&?PF`^)u6aeWhFl_z;aVtCn)P0*tZt8MHk>ns5O?s_<9 zNHS_KWjmD-_CTnKOSn?KEsoDQj#hxHHm8b4qaUX4UL&fs+`lt7sR z7Zc7@%R$&{MeY&~o=jddUKPt^?rBUm$^I>67pHJrm?oFo*3aMlglW(=H4`D*cp#1A z=EZKZc)D`au%qKCUG>A+;uh& zkI1oAahC9|G)1rjk%OUg_O%gWoHjma<;C#cgr_8OcT2p)3l5Ph<^+tZSh{%WMjhzP zy~v339L;Vfqk|*MHHhZTNv;lu?QD9hl`ZPjuTb0UwXs<-GAR_2rDmSo8T7SgP~@{7 zd@wTrzHhsW#b^cmsY8jrZ0?mwm_dD>h$KF9A|;5Ht~Kj@uW*=_GA1Nv#<97xb|QE{ zSdAK#uU_4?folRrB!nTdcT8Xwf4GoY)d(VX*=V*cD&S8mGb?tTTSH zh3g(H!_^7Q}Qx?%d+a$l7j@1sGJwGQHp3aw0`uEhO)^9!5F&+3hZd*HAK;e^8--*}JgYzIWT=gmaHwT;$5?viRJ|FCkS^6eM|d23VL1N@9s z=N$qo*+!G~vKJhr#qsw9DF0d*M~&x45@VuGz5rra+v;fqv9*d^%jv2e#Ltch9W|6= z1}Hs?OIPaqL+Q*OsZTWU-faFt8k5oWVT>Is`!WzZM6I;F3;NCl-&YUX6e@uwXZm7b z6EPo`Gc$UIaJMq&TmaKF6~#oGzwCRF`PSZ~!KkHdaugWBZ9}Oy&x3$bjcIg6aGyJ` zwicweh8;?>-P$^GriWy`x&sbP^NHi;!IR!jnkpS7<7p^MeB&lEDB5f1Htu*pzJwCa zxYtfqzd=>usc?RiPvS3k7O#Z%v)a&wyV*$hue+|)tZF>$Nlvb&yx+?wt1R#gg}_i? z%N?f#wR$C37AUmNW9rj);xQow#IX;_Zq;ybq+8LbWoD48=++mpx*YJgulQib@SbUR z>gEq|d-bk$DHJ@LLhnQiGe6aJO}A%Gz!8m;zR)}BUIMh~b3z%6B*y`}CyAoX4=6{y z=9tebP;lnZuu33iY7jN91l_4r6x7iZ0!TgU-{^kSCa>E-2Pk>NV zkZ-2I>-`|^aj( zqp1WvyN4JZC!904Dzss{KbbM6zbI)aml&jppAXrjCt{F%ShT$m<4l(m=S0O|b7sDaS2X4c zKE4RFt2?bEFrIaF}v@*7uqGRyRqzMQBgj5^0{7NgGzc}kSIX~F+ z%(F(G^l~Fax#Si!Km)HN-zAc7_?NNiIDj2w^yici~B zeH0m3ca)osFb{EQId4)c9thHoIL8#(4SX)nc8x_u~-0RDm!e zEaxSCr^dWcm~-##@diN0*ObW|2Q6}I%UZ-YxCE1DDOP}?RG8gN!IU+vFvWbNIclwe z#mBW8vk_rkjd79b9Ap*`&(Yw{`h7As=A)NT4t$0!F|)^)pZDIt6#{Ta)w31m6l7Ji zW@-Ze&F~%^svgti?5qZ?6zRjN7!L{rR^NtqE%Ct+XGxd zl{EyjIH3~0d;8Rzl{WTa2Wr}OM*t^@*wf%M*&J(GXBW6L*iGP%06D-HZ-DjLGv=tq z2r^)b$sXekK8nP=Q^?-?_gr1j*VNwXSXz!!oFO~5d|xxiv_DM1c5-3=W~=ryA?k=> zm82F;x1y4Q7@6f=w1!;R9fLN{{TonGkav9&L5o0PY>W#>(I{nY_1=MG-H-IGcqcXx zuwJC3?DT}{8Azk9g1!yH!7u4V9twG|MDB;u8|nYd8D6TvadlIaXaQ!YIG;|^-|$ZUJ!my z_#H9-GUU2YU}`2fhibPJTDacWf&$~b;L(zlA7>YXVOEvKjdXvIkMiCK$1~KV>*Ayp z?*daA(#`s@PqU|kqQe+IJ4s@cEMdV#;p$!1#B<7_D3ei=WdElCRN1h~nzU|#`afjX zvu1=n4>UULOWI51jdE$HGmP_Vr8}fHZ($}qG=|FEv;#_!4B;#9I*&j=1Y!T=S|*2s zHWHv5j+C&&)2fz`B(w*k&L`5FNM*_$P)h<1P23zZ6b`&Z_Unx{`PR0g?XK8jk7^bsa;0ivC5Y}gqwh%p zI%}3H6IqK)NHMH(`9&gj6Lk}QViaO$^}YB!Aty;g5SKz%c+U|AVplKEP%=31MxnGb zSxW!}up?$l1vhaZg^p@@H;9h0Tle`%@4ymkql@;s?i&OWPVR^91wd4byJJxn&^LWZ z)XNHMPla-wQLeL+dLm1K4+5dfOC`4Zd~-x5m`{tomDoUBtrsxe0^SFl-QU8$S@Eoa zJwC5H3M;H(piLH*Gr5_OgfR~p3ik1B(=4XpDljZm!ieBeemc mO&zwKy2k65jmbE0P>P@7;=jn1{9hluQc7yplW=&+G1`kdXy)kv From ec80b4e14e36a28b5b4cfdf2628090d87d578bb0 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 28 Oct 2025 10:31:56 -0700 Subject: [PATCH 937/966] chore(main): release 2.42.0 (#1819) :robot: I have created a release *beep* *boop* --- ## [2.42.0](https://github.com/googleapis/google-auth-library-python/compare/v2.41.1...v2.42.0) (2025-10-24) ### Features * Add trust boundary support for external accounts. ([#1809](https://github.com/googleapis/google-auth-library-python/issues/1809)) ([36ecb1d](https://github.com/googleapis/google-auth-library-python/commit/36ecb1d65883477d27faf9c2281fc289659b9903)) ### Bug Fixes * Read scopes from ADC json for impersoanted cred ([#1820](https://github.com/googleapis/google-auth-library-python/issues/1820)) ([62c0fc8](https://github.com/googleapis/google-auth-library-python/commit/62c0fc82a3625542381f85c698595446fc99ddae)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 12 ++++++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 90299bdf5277..070b6f4cd544 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,18 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.42.0](https://github.com/googleapis/google-auth-library-python/compare/v2.41.1...v2.42.0) (2025-10-24) + + +### Features + +* Add trust boundary support for external accounts. ([#1809](https://github.com/googleapis/google-auth-library-python/issues/1809)) ([36ecb1d](https://github.com/googleapis/google-auth-library-python/commit/36ecb1d65883477d27faf9c2281fc289659b9903)) + + +### Bug Fixes + +* Read scopes from ADC json for impersoanted cred ([#1820](https://github.com/googleapis/google-auth-library-python/issues/1820)) ([62c0fc8](https://github.com/googleapis/google-auth-library-python/commit/62c0fc82a3625542381f85c698595446fc99ddae)) + ## [2.41.1](https://github.com/googleapis/google-auth-library-python/compare/v2.41.0...v2.41.1) (2025-09-30) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 6f67e6b34eed..208942661bb4 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.41.1" +__version__ = "2.42.0" From 5d9fb33a45e3a8aa7e134c598bc306746e9687c4 Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Wed, 29 Oct 2025 17:21:07 -0700 Subject: [PATCH 938/966] chore: secret update (#1851) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 38fbef135b6cc24b4c1c3f6e9ddb6e8c2504ab82..fa5030046b22e0fa296abffb6f643e2a3ca43fd4 100644 GIT binary patch literal 10324 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 m^fI;yZCNuwx(yCi^vx+@o5(#ipE$g~%+a%lo literal 10324 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 Date: Wed, 29 Oct 2025 21:55:26 -0700 Subject: [PATCH 939/966] fix: catch ValueError for json.loads() (#1842) see internal bug 448976223 TODO: - [x] add test - [x] match the exception string so we don't catch unexpected cases --- packages/google-auth/google/auth/_helpers.py | 2 +- packages/google-auth/tests/test__helpers.py | 28 ++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/_helpers.py b/packages/google-auth/google/auth/_helpers.py index fba0ba3faf80..4a69d4c61f9d 100644 --- a/packages/google-auth/google/auth/_helpers.py +++ b/packages/google-auth/google/auth/_helpers.py @@ -489,7 +489,7 @@ def _parse_request_body(body: Optional[bytes], content_type: str = "") -> Any: if not content_type or "application/json" in content_type: try: return json.loads(body_str) - except (json.JSONDecodeError, TypeError): + except (TypeError, ValueError): return body_str if "application/x-www-form-urlencoded" in content_type: parsed_query = urllib.parse.parse_qs(body_str) diff --git a/packages/google-auth/tests/test__helpers.py b/packages/google-auth/tests/test__helpers.py index ce3ec11e29c1..2aecc0b7edab 100644 --- a/packages/google-auth/tests/test__helpers.py +++ b/packages/google-auth/tests/test__helpers.py @@ -623,6 +623,34 @@ def test_parse_request_body_other_type(): assert _helpers._parse_request_body("string") is None +def test_parse_request_body_json_type_error(): + body = b'{"key": "value"}' + with mock.patch("json.loads", side_effect=TypeError): + # json.loads should raise a TypeError, and the function should return the + # original string + assert _helpers._parse_request_body(body, "application/json") == body.decode( + "utf-8" + ) + + +def test_parse_request_body_json_value_error(): + body = b'{"key": "value"}' + content_type = "application/json" + with mock.patch("json.loads", side_effect=ValueError): + # json.loads should raise a ValueError, and the function should return the + # original string + assert _helpers._parse_request_body(body, content_type) == body.decode("utf-8") + + +def test_parse_request_body_json_decode_error(): + body = b'{"key": "value"}' + content_type = "application/json" + with mock.patch("json.loads", side_effect=json.JSONDecodeError("msg", "doc", 0)): + # json.loads should raise a JSONDecodeError, and the function should return the + # original string + assert _helpers._parse_request_body(body, content_type) == body.decode("utf-8") + + def test_parse_response_json_valid(): class MockResponse: def json(self): From f96e5d01a6cf353b65f15ec5bb3baeb9a4a5ca44 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 30 Oct 2025 09:31:11 -0700 Subject: [PATCH 940/966] chore(main): release 2.42.1 (#1853) :robot: I have created a release *beep* *boop* --- ## [2.42.1](https://github.com/googleapis/google-auth-library-python/compare/v2.42.0...v2.42.1) (2025-10-30) ### Bug Fixes * Catch ValueError for json.loads() ([#1842](https://github.com/googleapis/google-auth-library-python/issues/1842)) ([b074cad](https://github.com/googleapis/google-auth-library-python/commit/b074cad460589633adfc6744c01726ae86f2aa2b)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 070b6f4cd544..1bb06882a50d 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.42.1](https://github.com/googleapis/google-auth-library-python/compare/v2.42.0...v2.42.1) (2025-10-30) + + +### Bug Fixes + +* Catch ValueError for json.loads() ([#1842](https://github.com/googleapis/google-auth-library-python/issues/1842)) ([b074cad](https://github.com/googleapis/google-auth-library-python/commit/b074cad460589633adfc6744c01726ae86f2aa2b)) + ## [2.42.0](https://github.com/googleapis/google-auth-library-python/compare/v2.41.1...v2.42.0) (2025-10-24) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 208942661bb4..4b6c4fb25de5 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.42.0" +__version__ = "2.42.1" From 43e63d9519a7c7dfc631c81ec0c02521016350f4 Mon Sep 17 00:00:00 2001 From: ohmayr Date: Thu, 30 Oct 2025 16:36:53 -0700 Subject: [PATCH 941/966] feat: onboard `google-auth` to librarian (#1838) This PR onboards `google-auth` library to the Librarian system. Wait for https://github.com/googleapis/google-auth-library-python/pull/1819. --- packages/google-auth/.github/.OwlBot.lock.yaml | 17 ----------------- packages/google-auth/.github/.OwlBot.yaml | 18 ------------------ .../google-auth/.github/release-please.yml | 2 -- .../google-auth/.github/release-trigger.yml | 1 - packages/google-auth/.librarian/config.yaml | 6 ++++++ packages/google-auth/.librarian/state.yaml | 11 +++++++++++ 6 files changed, 17 insertions(+), 38 deletions(-) delete mode 100644 packages/google-auth/.github/.OwlBot.lock.yaml delete mode 100644 packages/google-auth/.github/.OwlBot.yaml delete mode 100644 packages/google-auth/.github/release-please.yml delete mode 100644 packages/google-auth/.github/release-trigger.yml create mode 100644 packages/google-auth/.librarian/config.yaml create mode 100644 packages/google-auth/.librarian/state.yaml diff --git a/packages/google-auth/.github/.OwlBot.lock.yaml b/packages/google-auth/.github/.OwlBot.lock.yaml deleted file mode 100644 index c4e82889dc81..000000000000 --- a/packages/google-auth/.github/.OwlBot.lock.yaml +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -docker: - image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:023a21377a2a00008057f99f0118edadc30a19d1636a3fee47189ebec2f3921c -# created: 2025-03-31T16:51:40.130756953Z diff --git a/packages/google-auth/.github/.OwlBot.yaml b/packages/google-auth/.github/.OwlBot.yaml deleted file mode 100644 index ed6155aab50f..000000000000 --- a/packages/google-auth/.github/.OwlBot.yaml +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright 2021 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -docker: - image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - -begin-after-commit-hash: ee56c3493ec6aeb237ff515ecea949710944a20f diff --git a/packages/google-auth/.github/release-please.yml b/packages/google-auth/.github/release-please.yml deleted file mode 100644 index 466597e5b196..000000000000 --- a/packages/google-auth/.github/release-please.yml +++ /dev/null @@ -1,2 +0,0 @@ -releaseType: python -handleGHRelease: true diff --git a/packages/google-auth/.github/release-trigger.yml b/packages/google-auth/.github/release-trigger.yml deleted file mode 100644 index d4ca94189e16..000000000000 --- a/packages/google-auth/.github/release-trigger.yml +++ /dev/null @@ -1 +0,0 @@ -enabled: true diff --git a/packages/google-auth/.librarian/config.yaml b/packages/google-auth/.librarian/config.yaml new file mode 100644 index 000000000000..111f94dd5cad --- /dev/null +++ b/packages/google-auth/.librarian/config.yaml @@ -0,0 +1,6 @@ +global_files_allowlist: + # Allow the container to read and write the root `CHANGELOG.md` + # file during the `release` step to update the latest client library + # versions which are hardcoded in the file. + - path: "CHANGELOG.md" + permissions: "read-write" diff --git a/packages/google-auth/.librarian/state.yaml b/packages/google-auth/.librarian/state.yaml new file mode 100644 index 000000000000..aec8620b416d --- /dev/null +++ b/packages/google-auth/.librarian/state.yaml @@ -0,0 +1,11 @@ +image: us-central1-docker.pkg.dev/cloud-sdk-librarian-prod/images-prod/python-librarian-generator:latest +libraries: + - id: google-auth + version: 2.41.1 + last_generated_commit: 102d9f92ac6ed649a61efd9b208e4d1de278e9bb + apis: [] + source_roots: + - . + preserve_regex: [] + remove_regex: [] + tag_format: v{version} From e3303486b73b256abc348f97923e879517377b93 Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Mon, 3 Nov 2025 15:40:04 -0800 Subject: [PATCH 942/966] chore: secret upadte (#1857) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index fa5030046b22e0fa296abffb6f643e2a3ca43fd4..2004600a3db09f8fe8dc2574b29683ecab801144 100644 GIT binary patch literal 10324 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 literal 10324 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 m^fI;yZCNuwx(yCi^vx+@o5(#ipE$g~%+a%lo From 9a64b0df8d40b29c6fc12938543b2319f9741c5f Mon Sep 17 00:00:00 2001 From: agrawalradhika-cell Date: Mon, 3 Nov 2025 16:45:01 -0800 Subject: [PATCH 943/966] feat: Enable mTLS if GOOGLE_API_USE_CLIENT_CERTIFICATE is not set, if the MWID/X.509 cert sources detected (#1848) The Python SDK will use a hybrid approach for mTLS enablement: - If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable is set (either true or false), the SDK will respect that setting. This is necessary for test scenarios and users who need to explicitly control mTLS behavior. - If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable is not set, the SDK will automatically enable mTLS only if it detects Managed Workload Identity (MWID) or X.509 Workforce Identity Federation (WIF) certificate sources. In other cases where the variable is not set, mTLS will remain disabled. ** This change also adds the helper method `check_use_client_cert` and it's unit test, which will be used for checking the criteria for setting the mTLS to true ** This change is only for Auth-Library, other changes will be created for Client-Library use-cases. --------- Signed-off-by: Radhika Agrawal Co-authored-by: Daniel Sanche --- .../google/auth/transport/_mtls_helper.py | 46 +++++++++++- .../google-auth/google/auth/transport/grpc.py | 10 +-- .../google/auth/transport/requests.py | 7 +- .../google/auth/transport/urllib3.py | 7 +- .../tests/transport/test__mtls_helper.py | 72 +++++++++++++++++++ 5 files changed, 121 insertions(+), 21 deletions(-) diff --git a/packages/google-auth/google/auth/transport/_mtls_helper.py b/packages/google-auth/google/auth/transport/_mtls_helper.py index 68568dd60395..5ad105a52806 100644 --- a/packages/google-auth/google/auth/transport/_mtls_helper.py +++ b/packages/google-auth/google/auth/transport/_mtls_helper.py @@ -16,7 +16,7 @@ import json import logging -from os import environ, path +from os import environ, getenv, path import re import subprocess @@ -405,3 +405,47 @@ def client_cert_callback(): # Then dump the decrypted key bytes return crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey) + + +def check_use_client_cert(): + """Returns the value of the GOOGLE_API_USE_CLIENT_CERTIFICATE variable, + or an inferred value('true' or 'false') if unset. + + This value is meant to be interpreted as a "true" or "false" value + representing whether the client certificate should be used, but could be any + arbitrary string. + + If GOOGLE_API_USE_CLIENT_CERTIFICATE is unset, the value value will be + inferred by reading a file pointed at by GOOGLE_API_CERTIFICATE_CONFIG, and + verifying it contains a "workload" section. If so, the function will return + "true", otherwise "false". + + Returns: + str: The value of GOOGLE_API_USE_CLIENT_CERTIFICATE, or an inferred value + ("true" or "false") if unset. This string should contain a value, but may + be an any arbitrary string read from the user's set + GOOGLE_API_USE_CLIENT_CERTIFICATE. + """ + use_client_cert = getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE") + # Check if the value of GOOGLE_API_USE_CLIENT_CERTIFICATE is set. + if use_client_cert: + return use_client_cert.lower() + else: + # Check if the value of GOOGLE_API_CERTIFICATE_CONFIG is set. + cert_path = getenv("GOOGLE_API_CERTIFICATE_CONFIG") + if cert_path: + try: + with open(cert_path, "r") as f: + content = json.load(f) + # verify json has workload key + content["cert_configs"]["workload"] + return "true" + except ( + FileNotFoundError, + OSError, + KeyError, + TypeError, + json.JSONDecodeError, + ) as e: + _LOGGER.debug("error decoding certificate: %s", e) + return "false" diff --git a/packages/google-auth/google/auth/transport/grpc.py b/packages/google-auth/google/auth/transport/grpc.py index 1ebe1379579e..747c1dcb2511 100644 --- a/packages/google-auth/google/auth/transport/grpc.py +++ b/packages/google-auth/google/auth/transport/grpc.py @@ -17,9 +17,7 @@ from __future__ import absolute_import import logging -import os -from google.auth import environment_vars from google.auth import exceptions from google.auth.transport import _mtls_helper from google.oauth2 import service_account @@ -256,9 +254,7 @@ def my_client_cert_callback(): # If SSL credentials are not explicitly set, try client_cert_callback and ADC. if not ssl_credentials: - use_client_cert = os.getenv( - environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE, "false" - ) + use_client_cert = _mtls_helper.check_use_client_cert() if use_client_cert == "true" and client_cert_callback: # Use the callback if provided. cert, key = client_cert_callback() @@ -295,9 +291,7 @@ class SslCredentials: """ def __init__(self): - use_client_cert = os.getenv( - environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE, "false" - ) + use_client_cert = _mtls_helper.check_use_client_cert() if use_client_cert != "true": self._is_mtls = False else: diff --git a/packages/google-auth/google/auth/transport/requests.py b/packages/google-auth/google/auth/transport/requests.py index 2753912c6d91..9e1f15751bfd 100644 --- a/packages/google-auth/google/auth/transport/requests.py +++ b/packages/google-auth/google/auth/transport/requests.py @@ -19,7 +19,6 @@ import functools import logging import numbers -import os import time try: @@ -35,7 +34,6 @@ ) # pylint: disable=ungrouped-imports from google.auth import _helpers -from google.auth import environment_vars from google.auth import exceptions from google.auth import transport import google.auth.transport._mtls_helper @@ -444,13 +442,10 @@ def configure_mtls_channel(self, client_cert_callback=None): google.auth.exceptions.MutualTLSChannelError: If mutual TLS channel creation failed for any reason. """ - use_client_cert = os.getenv( - environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE, "false" - ) + use_client_cert = google.auth.transport._mtls_helper.check_use_client_cert() if use_client_cert != "true": self._is_mtls = False return - try: import OpenSSL except ImportError as caught_exc: diff --git a/packages/google-auth/google/auth/transport/urllib3.py b/packages/google-auth/google/auth/transport/urllib3.py index 03ed75aa2f52..01be1dd0540b 100644 --- a/packages/google-auth/google/auth/transport/urllib3.py +++ b/packages/google-auth/google/auth/transport/urllib3.py @@ -17,7 +17,6 @@ from __future__ import absolute_import import logging -import os import warnings # Certifi is Mozilla's certificate bundle. Urllib3 needs a certificate bundle @@ -51,7 +50,6 @@ from google.auth import _helpers -from google.auth import environment_vars from google.auth import exceptions from google.auth import transport from google.oauth2 import service_account @@ -335,12 +333,9 @@ def configure_mtls_channel(self, client_cert_callback=None): google.auth.exceptions.MutualTLSChannelError: If mutual TLS channel creation failed for any reason. """ - use_client_cert = os.getenv( - environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE, "false" - ) + use_client_cert = transport._mtls_helper.check_use_client_cert() if use_client_cert != "true": return False - try: import OpenSSL except ImportError as caught_exc: diff --git a/packages/google-auth/tests/transport/test__mtls_helper.py b/packages/google-auth/tests/transport/test__mtls_helper.py index f6e20b726a86..c4959c1bc57e 100644 --- a/packages/google-auth/tests/transport/test__mtls_helper.py +++ b/packages/google-auth/tests/transport/test__mtls_helper.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import json import os import re @@ -638,3 +639,74 @@ def test_crypto_error(self): _mtls_helper.decrypt_private_key( ENCRYPTED_EC_PRIVATE_KEY, b"wrong_password" ) + + def test_check_use_client_cert(self, monkeypatch): + monkeypatch.setenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "true") + use_client_cert = _mtls_helper.check_use_client_cert() + assert use_client_cert == "true" + + def test_check_use_client_cert_for_workload_with_config_file(self, monkeypatch): + config_data = { + "version": 1, + "cert_configs": { + "workload": { + "cert_path": "path/to/cert/file", + "key_path": "path/to/key/file", + } + }, + } + config_filename = "mock_certificate_config.json" + config_file_content = json.dumps(config_data) + monkeypatch.setenv("GOOGLE_API_CERTIFICATE_CONFIG", config_filename) + monkeypatch.setenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "") + # Use mock_open to simulate the file in memory + mock_file_handle = mock.mock_open(read_data=config_file_content) + with mock.patch("builtins.open", mock_file_handle): + use_client_cert = _mtls_helper.check_use_client_cert() + assert use_client_cert == "true" + + def test_check_use_client_cert_false(self, monkeypatch): + monkeypatch.setenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false") + use_client_cert = _mtls_helper.check_use_client_cert() + assert use_client_cert == "false" + + def test_check_use_client_cert_for_workload_with_config_file_not_found( + self, monkeypatch + ): + monkeypatch.setenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "") + use_client_cert = _mtls_helper.check_use_client_cert() + assert use_client_cert == "false" + + def test_check_use_client_cert_for_workload_with_config_file_not_json( + self, monkeypatch + ): + config_filename = "mock_certificate_config.json" + config_file_content = "not_valid_json" + monkeypatch.setenv("GOOGLE_API_CERTIFICATE_CONFIG", config_filename) + monkeypatch.setenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "") + # Use mock_open to simulate the file in memory + mock_file_handle = mock.mock_open(read_data=config_file_content) + with mock.patch("builtins.open", mock_file_handle): + use_client_cert = _mtls_helper.check_use_client_cert() + assert use_client_cert == "false" + + def test_check_use_client_cert_for_workload_with_config_file_no_workload( + self, monkeypatch + ): + config_data = {"version": 1, "cert_configs": {"dummy_key": {}}} + config_filename = "mock_certificate_config.json" + config_file_content = json.dumps(config_data) + monkeypatch.setenv("GOOGLE_API_CERTIFICATE_CONFIG", config_filename) + monkeypatch.setenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "") + # Use mock_open to simulate the file in memory + mock_file_handle = mock.mock_open(read_data=config_file_content) + with mock.patch("builtins.open", mock_file_handle): + use_client_cert = _mtls_helper.check_use_client_cert() + assert use_client_cert == "false" + + def test_check_use_client_cert_when_file_does_not_exist(self, monkeypatch): + config_filename = "mock_certificate_config.json" + monkeypatch.setenv("GOOGLE_API_CERTIFICATE_CONFIG", config_filename) + monkeypatch.setenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "") + use_client_cert = _mtls_helper.check_use_client_cert() + assert use_client_cert == "false" From 15184113c9893e93a52a537cfccb7756ced52cd7 Mon Sep 17 00:00:00 2001 From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com> Date: Wed, 5 Nov 2025 07:27:47 -0800 Subject: [PATCH 944/966] chore: update secret (#1860) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 2004600a3db09f8fe8dc2574b29683ecab801144..b4502815379a4a9a495cea73f619cbb9fe20f8d3 100644 GIT binary patch literal 10324 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 From 757393416711096b2239349b62b4f759abdae4b0 Mon Sep 17 00:00:00 2001 From: agrawalradhika-cell Date: Wed, 5 Nov 2025 12:41:58 -0800 Subject: [PATCH 945/966] feat: Add public wrapper for _mtls_helper.check_use_client_cert which enables mTLS if GOOGLE_API_USE_CLIENT_CERTIFICATE is not set, when the MWID/X.509 cert sources detected (#1859) Add public wrapper for check_use_client_cert which enables mTLS if GOOGLE_API_USE_CLIENT_CERTIFICATE is not set, when the MWID/X.509 cert sources detected. Also, fix check_use_client_cert to return boolean value. Change #1848 added the check_use_client_cert method that helps know if client cert should be used for mTLS connection. However, that was in a private class, thus, created a public wrapper of the same function so that it can be used by python Client Libraries. Also, updated check_use_client_cert to return a boolean value instead of existing string value for better readability and future scope. --------- Signed-off-by: Radhika Agrawal Co-authored-by: Daniel Sanche --- .../google/auth/transport/_mtls_helper.py | 29 ++++++++----------- .../google-auth/google/auth/transport/grpc.py | 6 ++-- .../google-auth/google/auth/transport/mtls.py | 17 +++++++++++ .../google/auth/transport/requests.py | 2 +- .../google/auth/transport/urllib3.py | 2 +- .../tests/transport/test__mtls_helper.py | 19 +++++++----- .../google-auth/tests/transport/test_mtls.py | 9 ++++++ 7 files changed, 55 insertions(+), 29 deletions(-) diff --git a/packages/google-auth/google/auth/transport/_mtls_helper.py b/packages/google-auth/google/auth/transport/_mtls_helper.py index 5ad105a52806..7740f2fe8152 100644 --- a/packages/google-auth/google/auth/transport/_mtls_helper.py +++ b/packages/google-auth/google/auth/transport/_mtls_helper.py @@ -408,28 +408,23 @@ def client_cert_callback(): def check_use_client_cert(): - """Returns the value of the GOOGLE_API_USE_CLIENT_CERTIFICATE variable, - or an inferred value('true' or 'false') if unset. + """Returns boolean for whether the client certificate should be used for mTLS. - This value is meant to be interpreted as a "true" or "false" value - representing whether the client certificate should be used, but could be any - arbitrary string. - - If GOOGLE_API_USE_CLIENT_CERTIFICATE is unset, the value value will be - inferred by reading a file pointed at by GOOGLE_API_CERTIFICATE_CONFIG, and - verifying it contains a "workload" section. If so, the function will return - "true", otherwise "false". + If GOOGLE_API_USE_CLIENT_CERTIFICATE is set to true or false, a corresponding + bool value will be returned. If the value is set to an unexpected string, it + will default to False. + If GOOGLE_API_USE_CLIENT_CERTIFICATE is unset, the value will be inferred + by reading a file pointed at by GOOGLE_API_CERTIFICATE_CONFIG, and verifying + it contains a "workload" section. If so, the function will return True, + otherwise False. Returns: - str: The value of GOOGLE_API_USE_CLIENT_CERTIFICATE, or an inferred value - ("true" or "false") if unset. This string should contain a value, but may - be an any arbitrary string read from the user's set - GOOGLE_API_USE_CLIENT_CERTIFICATE. + bool: Whether the client certificate should be used for mTLS connection. """ use_client_cert = getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE") # Check if the value of GOOGLE_API_USE_CLIENT_CERTIFICATE is set. if use_client_cert: - return use_client_cert.lower() + return use_client_cert.lower() == "true" else: # Check if the value of GOOGLE_API_CERTIFICATE_CONFIG is set. cert_path = getenv("GOOGLE_API_CERTIFICATE_CONFIG") @@ -439,7 +434,7 @@ def check_use_client_cert(): content = json.load(f) # verify json has workload key content["cert_configs"]["workload"] - return "true" + return True except ( FileNotFoundError, OSError, @@ -448,4 +443,4 @@ def check_use_client_cert(): json.JSONDecodeError, ) as e: _LOGGER.debug("error decoding certificate: %s", e) - return "false" + return False diff --git a/packages/google-auth/google/auth/transport/grpc.py b/packages/google-auth/google/auth/transport/grpc.py index 747c1dcb2511..d9185e7aa4f1 100644 --- a/packages/google-auth/google/auth/transport/grpc.py +++ b/packages/google-auth/google/auth/transport/grpc.py @@ -255,13 +255,13 @@ def my_client_cert_callback(): # If SSL credentials are not explicitly set, try client_cert_callback and ADC. if not ssl_credentials: use_client_cert = _mtls_helper.check_use_client_cert() - if use_client_cert == "true" and client_cert_callback: + if use_client_cert and client_cert_callback: # Use the callback if provided. cert, key = client_cert_callback() ssl_credentials = grpc.ssl_channel_credentials( certificate_chain=cert, private_key=key ) - elif use_client_cert == "true": + elif use_client_cert: # Use application default SSL credentials. adc_ssl_credentils = SslCredentials() ssl_credentials = adc_ssl_credentils.ssl_credentials @@ -292,7 +292,7 @@ class SslCredentials: def __init__(self): use_client_cert = _mtls_helper.check_use_client_cert() - if use_client_cert != "true": + if not use_client_cert: self._is_mtls = False else: # Load client SSL credentials. diff --git a/packages/google-auth/google/auth/transport/mtls.py b/packages/google-auth/google/auth/transport/mtls.py index e7a7304f60ee..75e43687fc2f 100644 --- a/packages/google-auth/google/auth/transport/mtls.py +++ b/packages/google-auth/google/auth/transport/mtls.py @@ -110,3 +110,20 @@ def callback(): return cert_path, key_path, passphrase_bytes return callback + + +def should_use_client_cert(): + """Returns boolean for whether the client certificate should be used for mTLS. + + This is a wrapper around _mtls_helper.check_use_client_cert(). + If GOOGLE_API_USE_CLIENT_CERTIFICATE is set to true or false, a corresponding + bool value will be returned + If GOOGLE_API_USE_CLIENT_CERTIFICATE is unset, the value will be inferred by + reading a file pointed at by GOOGLE_API_CERTIFICATE_CONFIG, and verifying it + contains a "workload" section. If so, the function will return True, + otherwise False. + + Returns: + bool: indicating whether the client certificate should be used for mTLS. + """ + return _mtls_helper.check_use_client_cert() diff --git a/packages/google-auth/google/auth/transport/requests.py b/packages/google-auth/google/auth/transport/requests.py index 9e1f15751bfd..d1ff8f368cce 100644 --- a/packages/google-auth/google/auth/transport/requests.py +++ b/packages/google-auth/google/auth/transport/requests.py @@ -443,7 +443,7 @@ def configure_mtls_channel(self, client_cert_callback=None): creation failed for any reason. """ use_client_cert = google.auth.transport._mtls_helper.check_use_client_cert() - if use_client_cert != "true": + if not use_client_cert: self._is_mtls = False return try: diff --git a/packages/google-auth/google/auth/transport/urllib3.py b/packages/google-auth/google/auth/transport/urllib3.py index 01be1dd0540b..353cb8e08eb1 100644 --- a/packages/google-auth/google/auth/transport/urllib3.py +++ b/packages/google-auth/google/auth/transport/urllib3.py @@ -334,7 +334,7 @@ def configure_mtls_channel(self, client_cert_callback=None): creation failed for any reason. """ use_client_cert = transport._mtls_helper.check_use_client_cert() - if use_client_cert != "true": + if not use_client_cert: return False try: import OpenSSL diff --git a/packages/google-auth/tests/transport/test__mtls_helper.py b/packages/google-auth/tests/transport/test__mtls_helper.py index c4959c1bc57e..01d5e3a40937 100644 --- a/packages/google-auth/tests/transport/test__mtls_helper.py +++ b/packages/google-auth/tests/transport/test__mtls_helper.py @@ -643,7 +643,7 @@ def test_crypto_error(self): def test_check_use_client_cert(self, monkeypatch): monkeypatch.setenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "true") use_client_cert = _mtls_helper.check_use_client_cert() - assert use_client_cert == "true" + assert use_client_cert is True def test_check_use_client_cert_for_workload_with_config_file(self, monkeypatch): config_data = { @@ -663,19 +663,24 @@ def test_check_use_client_cert_for_workload_with_config_file(self, monkeypatch): mock_file_handle = mock.mock_open(read_data=config_file_content) with mock.patch("builtins.open", mock_file_handle): use_client_cert = _mtls_helper.check_use_client_cert() - assert use_client_cert == "true" + assert use_client_cert is True def test_check_use_client_cert_false(self, monkeypatch): monkeypatch.setenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false") use_client_cert = _mtls_helper.check_use_client_cert() - assert use_client_cert == "false" + assert use_client_cert is False + + def test_check_use_client_cert_unsupported_value(self, monkeypatch): + monkeypatch.setenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "dummy") + use_client_cert = _mtls_helper.check_use_client_cert() + assert use_client_cert is False def test_check_use_client_cert_for_workload_with_config_file_not_found( self, monkeypatch ): monkeypatch.setenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "") use_client_cert = _mtls_helper.check_use_client_cert() - assert use_client_cert == "false" + assert use_client_cert is False def test_check_use_client_cert_for_workload_with_config_file_not_json( self, monkeypatch @@ -688,7 +693,7 @@ def test_check_use_client_cert_for_workload_with_config_file_not_json( mock_file_handle = mock.mock_open(read_data=config_file_content) with mock.patch("builtins.open", mock_file_handle): use_client_cert = _mtls_helper.check_use_client_cert() - assert use_client_cert == "false" + assert use_client_cert is False def test_check_use_client_cert_for_workload_with_config_file_no_workload( self, monkeypatch @@ -702,11 +707,11 @@ def test_check_use_client_cert_for_workload_with_config_file_no_workload( mock_file_handle = mock.mock_open(read_data=config_file_content) with mock.patch("builtins.open", mock_file_handle): use_client_cert = _mtls_helper.check_use_client_cert() - assert use_client_cert == "false" + assert use_client_cert is False def test_check_use_client_cert_when_file_does_not_exist(self, monkeypatch): config_filename = "mock_certificate_config.json" monkeypatch.setenv("GOOGLE_API_CERTIFICATE_CONFIG", config_filename) monkeypatch.setenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "") use_client_cert = _mtls_helper.check_use_client_cert() - assert use_client_cert == "false" + assert use_client_cert is False diff --git a/packages/google-auth/tests/transport/test_mtls.py b/packages/google-auth/tests/transport/test_mtls.py index ea549ae142b2..ef305395827c 100644 --- a/packages/google-auth/tests/transport/test_mtls.py +++ b/packages/google-auth/tests/transport/test_mtls.py @@ -94,3 +94,12 @@ def test_default_client_encrypted_cert_source( callback = mtls.default_client_encrypted_cert_source("cert_path", "key_path") with pytest.raises(exceptions.MutualTLSChannelError): callback() + + +@mock.patch("google.auth.transport._mtls_helper.check_use_client_cert", autospec=True) +def test_should_use_client_cert(check_use_client_cert): + check_use_client_cert.return_value = mock.Mock() + assert mtls.should_use_client_cert() + + check_use_client_cert.return_value = False + assert not mtls.should_use_client_cert() From aec782f360398110be19d26a70a82155acee2c88 Mon Sep 17 00:00:00 2001 From: ohmayr Date: Wed, 5 Nov 2025 15:04:09 -0800 Subject: [PATCH 946/966] chore: update prev version in state (#1862) --- packages/google-auth/.librarian/state.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/.librarian/state.yaml b/packages/google-auth/.librarian/state.yaml index aec8620b416d..9a5f68c874c7 100644 --- a/packages/google-auth/.librarian/state.yaml +++ b/packages/google-auth/.librarian/state.yaml @@ -1,7 +1,7 @@ image: us-central1-docker.pkg.dev/cloud-sdk-librarian-prod/images-prod/python-librarian-generator:latest libraries: - id: google-auth - version: 2.41.1 + version: 2.42.1 last_generated_commit: 102d9f92ac6ed649a61efd9b208e4d1de278e9bb apis: [] source_roots: From 475513c6e39f0597c8e84183f0369477df4ff8a4 Mon Sep 17 00:00:00 2001 From: Lingqing Gan Date: Wed, 5 Nov 2025 15:29:35 -0800 Subject: [PATCH 947/966] chore: librarian release pull request: 20251105T230735Z (#1863) Librarian Version: v0.5.0 Language Image: us-central1-docker.pkg.dev/cloud-sdk-librarian-prod/images-prod/python-librarian-generator:latest

        google-auth: 2.43.0 ## [2.43.0](https://github.com/googleapis/google-auth-library-python/compare/v2.42.1...v2.43.0) (2025-11-05) ### Features * Add public wrapper for _mtls_helper.check_use_client_cert which enables mTLS if GOOGLE_API_USE_CLIENT_CERTIFICATE is not set, when the MWID/X.509 cert sources detected (#1859) ([1535eccb](https://github.com/googleapis/google-auth-library-python/commit/1535eccb)) * Enable mTLS if GOOGLE_API_USE_CLIENT_CERTIFICATE is not set, if the MWID/X.509 cert sources detected (#1848) ([395e405b](https://github.com/googleapis/google-auth-library-python/commit/395e405b)) * onboard `google-auth` to librarian (#1838) ([c503eaa5](https://github.com/googleapis/google-auth-library-python/commit/c503eaa5))
        --- packages/google-auth/.librarian/state.yaml | 2 +- packages/google-auth/CHANGELOG.md | 36 +++++++++++++++++++++ packages/google-auth/google/auth/version.py | 2 +- 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/.librarian/state.yaml b/packages/google-auth/.librarian/state.yaml index 9a5f68c874c7..9b7e2ca0966c 100644 --- a/packages/google-auth/.librarian/state.yaml +++ b/packages/google-auth/.librarian/state.yaml @@ -1,7 +1,7 @@ image: us-central1-docker.pkg.dev/cloud-sdk-librarian-prod/images-prod/python-librarian-generator:latest libraries: - id: google-auth - version: 2.42.1 + version: 2.43.0 last_generated_commit: 102d9f92ac6ed649a61efd9b208e4d1de278e9bb apis: [] source_roots: diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 1bb06882a50d..a71cd68e3c88 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,42 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.43.0](https://github.com/googleapis/google-cloud-python/compare/google-auth-v2.42.1...google-auth-v2.43.0) (2025-11-05) + + +### Features + +* Add public wrapper for _mtls_helper.check_use_client_cert which enables mTLS if GOOGLE_API_USE_CLIENT_CERTIFICATE is not set, when the MWID/X.509 cert sources detected (#1859) Add public wrapper for check_use_client_cert which enables mTLS if +GOOGLE_API_USE_CLIENT_CERTIFICATE is not set, when the MWID/X.509 cert +sources detected. Also, fix check_use_client_cert to return boolean +value. +Change #1848 added the check_use_client_cert method that helps know if +client cert should be used for mTLS connection. However, that was in a +private class, thus, created a public wrapper of the same function so +that it can be used by python Client Libraries. Also, updated +check_use_client_cert to return a boolean value instead of existing +string value for better readability and future scope. +--------- ([1535eccbff0ad8f3fd6a9775316ac8b77dca66ba](https://github.com/googleapis/google-cloud-python/commit/1535eccbff0ad8f3fd6a9775316ac8b77dca66ba)) +* Enable mTLS if GOOGLE_API_USE_CLIENT_CERTIFICATE is not set, if the MWID/X.509 cert sources detected (#1848) The Python SDK will use a hybrid approach for mTLS enablement: +- If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable is set +(either true or false), the SDK will respect that setting. This is +necessary for test scenarios and users who need to explicitly control +mTLS behavior. +- If the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable is not +set, the SDK will automatically enable mTLS only if it detects Managed +Workload Identity (MWID) or X.509 Workforce Identity Federation (WIF) +certificate sources. In other cases where the variable is not set, mTLS +will remain disabled. +** This change also adds the helper method `check_use_client_cert` and +it's unit test, which will be used for checking the criteria for setting +the mTLS to true +** This change is only for Auth-Library, other changes will be created +for Client-Library use-cases. +--------- ([395e405b64b56ddb82ee639958c2e8056ad2e82b](https://github.com/googleapis/google-cloud-python/commit/395e405b64b56ddb82ee639958c2e8056ad2e82b)) +* onboard `google-auth` to librarian (#1838) This PR onboards `google-auth` library to the Librarian system. +Wait for +https://github.com/googleapis/google-auth-library-python/pull/1819. ([c503eaa511357d7a76cc1e1f1d3a3be2dabd5bca](https://github.com/googleapis/google-cloud-python/commit/c503eaa511357d7a76cc1e1f1d3a3be2dabd5bca)) + ## [2.42.1](https://github.com/googleapis/google-auth-library-python/compare/v2.42.0...v2.42.1) (2025-10-30) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 4b6c4fb25de5..20f2c8c0a90d 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.42.1" +__version__ = "2.43.0" From 23c4d3980d752dd53cd0f4eb9ee42986a4540fe6 Mon Sep 17 00:00:00 2001 From: Lingqing Gan Date: Mon, 10 Nov 2025 15:58:56 -0800 Subject: [PATCH 948/966] chore: update secret and fix pytest issue (#1868) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../tests/transport/aio/test_sessions.py | 22 +++++++++++++----- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index b4502815379a4a9a495cea73f619cbb9fe20f8d3..af9af5e620bb2bc052d49a2c7e1c1c765ea238d1 100644 GIT binary patch literal 10324 zcmV-aD67{BB>?tKRTFP9wIDLhV1DBOK_P=hL7{vl?LuL0hZsHQe{~%?jdc>LPyl>7 ziXOe48xQb@1+x+mZkX5a(hC zK$oEVV9T8bL1oQZ;Xjuz0sVCYY2elrTJO)|^BgpjTepv~?E)1l2+iQMIrZ!Ymw>Z>&Y_vfh?Mgys zr`Uh4?%kyRCx~1`HF_otfCX{r#8Flw(M{piN3Lyo@T)2 zIjdytf39FJwx7kCXY3nO#{Klc;x#53JAEB>=7q?SQn^_K0G_|Va#}(l;4ZH;o~f)2 zCLx?^?_d*oE0lv*ZeNJ*(1A~1!wdq*F2Pk<|8+3nM$Z07X$DU7?UyRGZxhsaJrOn% zr1fwECqUsqjdj6Ya@4LagIKHZIW>I6baFB9-HH3>DU=ggTkb=kq=n>YB!k7ZJGL7v zAZ=^S60~jmIDCNhRL$m5A=!$Sc!af3mU>x9Z|SErqU3$4ll0y@6pu>Kj77go=7A1W zU1a6l-OpIcr|=-b^tbA$ET_Jt&ElE*j(4~c4DcK4{jIKG0C3QC%8LCof@i!s0N;z2s!S5^^U4nOLo9ro zji;x-?Y$KAAy^ggQ%d&2zSR(STY!q+ZcvGHQY2#tejp}4eebuCfO-1ie};t8vC+65 zniW;98k3TdoD=#SD(W64dp}%p+u4z(u{4m4*QRv*X2B4y8<1chwi;fR^Ulx}Qa3PT z+4wi)kFKL81X;M3-VEuK-_9Ch5AbogMoXDDIUo&=kcmyqg1Dj)caj0tX)kER^3lj1 z$mwQ##Kzd@!t`4w$k*>I4j`tHNRIkQYrg|W7f1iyOF$mAJfV32R3dOj{*)Zi9UytV zd_m}2kb6DQDgo$25CikUscpFvFi!IK$LSZImy@#`ZOHbDeIo7zdHl_(AWWac8C0V= zA@(!Fd-+wdJ#)rKnA1%U_R-RDG%VsDl!QkFi9ATMW2oo`# z^p-r>!&h*feIBh>YfL9f@&`@YADFmePYpPr{G4a{%=!N^DOXS(4E!pR$y38tU0wS6 zrz4b~E}~Xg9u`I}qZw1#0O8gOD!50Q>0}5xRU9Z;Bn5L)V55@L#{C^rMDb4l(l;RK ze`*j}>jf|NZwTK)Cf!FR;}QMTFw;;$~R*|0BcfoF#Wct?ac*N<0~zc@O{#b z-~R>?V{Gp&VjPu;XaLE?34;<2Q9ZsaKgw%(&Y=`05;XR)B~I<=1drWz%r@Q@tvsUA zh8bCq@NFVO?e>#g`ih|AfnH#P(m!X8ii-fyn=w8~a@FIWz5J#rP6M$U%L8Rmq(wjr zB9L05PC&`bdM-=>odI@mdqO+ALZpHkz~x3h{y!qO-sGJ(lDoa9YXb8GJ^aX zKMTW1DosvX+9~ys6q1ju*6M5lMmFDe259MR!jKKc6Y26dPvzpy3d^P<9CH3ce9K^d z>36KChJgHmkG~hQaO@JVF~*M5+;n;SKiimmao(9BZd6u*4;&eQ9f$vR8Y|?v5ZKGH zB_7`uA(8UZ-2N}lg#AG_vGfNbr;F?L#duj8MUTOJzPG5B9O%&J)_=N6giHvjp2z!k zXks)kvdb`Hagj>dKVx3%sHU=+lughL%`7*g-w`!*QG5;4kis((I`SF~#) zvF?Bg?lF9nY@M>Iy_MERUqvL12Lv>AY{as`8jUCd#em0*ELq_Smq;g)pOXZ0Ky6-= znNVzJj5=Q3e4yI~3{%-xl*ll3_qOr9_3WMqgZkj^gzG#=ZyvJd2c0HVr^k%Ii^>U@ zKi()Hv8JwKi&TD0sQH$W7sRTh@>;CiB!=)zubl2h>%mXZ)+e}G)7n^+Ncm$qL`{;rfx{`oy7xb<9GR-~Mid!Hy$NCDAIzAo_Gc z*Z3K^2h>SvzAtr?dO|FTD+>L2aymBl)s8OQJ99Bw6i9x#1IGYgzTjuiWO>3@>!P5wlDC|r!O zt5qZ!GY17VGH0CTW>T|dD{=#pcEg*^MLj+{THWIZF~_a}Gt-I@&vg{eT)xbZ(6%C( z!+xbi7n?UjPm06!xT!Q{cZ_VB7++ZqTte(T%mp(gO6Kup5V;_zj|NrZ5XgnQiri3Q zB7MK>$&=n-m@QY@_YB<5JylEV4nquoR-4LW(XAy4m+CX*XzxVDY78z|3*T@zKLlva z1XVW=*y#Dqeq)7?EW3HajEdd!X|N`7w}GSnUJ?@QOfvzhpt3qF7nN67`5bWQTXTJN zGT*{=Ge!5>G+-uH5GvP}Was--19+X|v}LTYj%oy5!K(3^7OjQ*5=WU04I&Ly<BN=2^)s98y5RMug~sHujmEw9F5 z*3cHXMv(sFUki1sGq=Iq46@WjwSc=)rFfq<{(Ce=iN4e68}j*O-X#F-T~avZczt$F z!we@VWI*3!x^&Xm&0BvkDCJxWPPiY{FpG%~75(L;KUDdqDH3x7l3(*3{!|ygpv=}W zLeA__=G&l(Tva+P@F0A$tEQ9F5tg%`7}K__B>)-c57T$tTIx6Rs#zhe<+sNLMNFUs z%m`pfT$*>!(_&ATHW<@jqnFd^i_*}Z%-3Wb0#u%?UqPe1g~!$kuXwYzjM$W1tJ(hn znMt4xH}(*nAXCkWaML}pHv)!IV7t6=w^89RM8Nh9gkXi1<%9dkZ6mWlN6ifNY*h)*q(?AXpLY);y8b{DnXJLaRZR`Kuq? zB<2O9+`WUsL~9@o-ryFVvKM>}fOp{+Zn>+i6Q!>JR3MN2*GAFmPEAp1(?@AWm4Ml2 zCle0eOTW8!|IIZzMf#}c%1=*p6+lb3HwtU*OlxN}2$a2P!!J*997R_s7@pU7%2$8v zN1FAM#GRc5VpnX0@Sg}QlYBF_Ko|jiGV~LcQyG<3AC`&CPDyw&1N0l=}NjTa}dC=TPoBM(^t{-Qf8uZ@<>qQK#0)r!KX#Z6DW3JMO*PYZGU&YJ38u zGb++-dXU7~gqfTJZgf$LaxFC!R(O3ji|Dq^1Z7ZeuWel22nNJWWOJ?X6eM z-n`*(rBZ=Uc1H)u?V0)@A)e~X)*3K{w~CPgE!J>DhBTwo;sLX=X-;UvFvKg%M*PJ)54O2*iRRp1*Y>9 z(+{YSvIaUiTTLwTzF_PuY9dKhSx5+~(#Lpst}4V7ULSdZyx1hBD~wL5Zc|q+tA8Zl zEC3OfCG$UbWXADnvhU7|b$=m0)7P!U38Dpz94*=??JatvXUiAqi~7t+;R%?fY7$Cu zFF3W4t0niW3r4N|7dJns3u^WZDD7fWMzHLV(7HsQ=Og z`I!~0nm^``RPRN;I)<$jGb-N0K!aDGYqZ1_7t&yDXs7Ynt$_)pnL_pip)?0hr+R8h zU#`(o737d`bP^TQt8r)1sQ>%>0tjv=GWS%w(}1ku2SDPzPtyJ&0veuMWVlY_mb=K_ zOylg7}{KBx$f$lg#i)ft1P*1SWc^2VZ#{>Ft@r9CzGKoReW4-e$pH|rm?`A z_!sL?m^!rEtLk(rg&+{-#g0og&HlW9i#vwHzp}&?<85wE*aJ2WF0WMdu^>B`0?xrI zuD}>A!V)n6tSRSX3B3YkaY7@4YuCsDC8cnZtOlMA^XcFE1BKpYkD@x8uFqT->zkaD ze+J(nWwKGKUZs|tp9Mlrk)obl{RBv7&e z*4}|7{>BHL3~dTJiM$>Swqt)GQ|&yGAU9*AMeZT%*E)52!xmQ*l97Gzq~Q{ohR*qL za(bTIrx{$_1%UgdIZ$pTc57c&hKmrHE$?tzkvLi+AY~FV3|1b!W+0~rN7@D)+4cJ4 zoZG;Q@1#y18}0md!7(aJyTRwxq0kg{nqFT2_m8j&^wI47WTCsym$*OYb>H)SqaD}g zybsI2dU1hl4oz%ZI??WAD}+DKp<))oUR;&p5!k)a#-pc(Yvf?D?a4F}{4-mvbr$q; zO=|LL&zgqw0?#96&Q=xv8EPqHq`|%vjLW}I1ZTu4oAJxU$6G1gnZvyjJ#Ki85SOgE z8`9dOz*ZML<5fLsEhAk7MT;G%U784dMltv7doeRInWSPV#R%?}`?P&szgFjRdt-8x zPxZ&=D4KC%K&8&Y3!Ba|ZwPdw3BU~1;Wq(7q{>vk=M9bo$?sf|$ptfmp5Kp5{lRTO zo@`k{<7O!gJhre*nU;Frmt>S0wF`3h-yVW|cmJ*@{O|Fh8M!v%p%BBH>?&k!aF^R6 zD&{wwR;cZocM>KT#FkcHN{vT+6I*=JQt~+ot!{ABGuW|jt7lZtl|^MRY-OkNGda;?HJhP!R?={}hZK zcFA_sOo1E0?Js({bsLQ+jCUk{dq-h-jA)yksDzf6Q4`aiPR$-y5u z0wKQJ9sE#=NR}e;s7KbCeU?*!-|Ac^u$~iTDdUb7$eLQeB#=n}V&uiyFKXRi^;Z(& z^LT?lo&d0nrmcSFETy5AYM~1c+_BaeMOWu|#0vr_*yRLdZQyT^IHSS3YMzr{4O1rO7XA4q+$6IoHx8#dLp;K7U?;>1i`us;h^cIe-*p+kG!ld%p8Kz zUT5gc3rtSr!CN~De0w#n*NmIe6oTX{tcO7kiA7I=q;Qt#?j0P;=8Yw95iLX7m6h|Y)Mnu013-P7_}zLz843$3XauA zFSq0(hkIh#g7^)`Hoz$UMxgdv8Y0w{lL5u2Y#}}VV^Bm=vP!ssL1Ln0YYK3YB6{e7 zW(!>S3%*B%>&4H@dI&0$T?sjeGIb0qo@>3_WGUN&cQh@k+*niMSL@i=p0=Oh3f42D zXONsJah4PP%j`)sx8a$VoUX3R2_HrH3~=}h$Z7Iw;Gq9Eme8g|y=n^ZVYNRv(9C3Z zrObnz4TJIBXlyu$N+bl@dosWs1#GWgR{lj`V}s6iX3tEOTU`3~uN(_5>EC}vR|1R3 zX9v(qniGr7_SP7gpB8(8=zgjAcm3KNmi7PYCHD?3N+E*?(!u4qrD>YPUdT!**OfHA z!3{0WtyA?!BR9U9mIMZ3p5}k*FyQ0gW9*LPT)v~f9Wg6b4r;%tW?|(pnDA4t61q4P zy~?<@h67_ecEv#HqZKpIX|GEuX=s7YW*Ww2Zwe&79D_-_OrQr;3zlK^rk7x5QVYx# zI9?PBh^oVHZqk$rV#tq*{svkbyF`Cn5*edD@!-IuVR;{YW%m$kiscH#pY1z)6DM7L zU9_D1q*2NCh$Oge%ODf!I`Go;NtgW@bgSm~t6}~7sIC)HrcR}x{MVIAi$X5iCzI2sWQgQL~~D@8ypzjuCeNMYmCE~P;Z6wPmqF|Xrz~XkfpGgs`)XMa+)(V zVfkdCzA-4y=Wj7q{qGQFE)$X2S(V_RVf0VQgL_+QL~OBt9EKHA=ApRFEcU%*wdSK% zG=w^-f=5AES;=gI*Kw8Pc>Hf_UGcB~CL?osUnKE7phXJdUs&LPE0(5xKp#Y189X{WKTfjoL?WU}oT zzf|7ebN&NXss+Mw!D!WF4ss}^%O97W6{Z{Spj&v2a*hx@a zmn6gOF9pF{*)4v?-MFqSmk2<6JE$hbbrc zYialVuJK_;Ty1`wB{_>FBd&jWK0In?!W0vIKJ70gEip^1otP>#f(sLbt4?f9UN~ zfrXrZg~c?u7KoVyjye>4r-KW5TCjiV%mA1)%sq3y+&vPMmJnDw_g&CO@ z+@O-8)st&VBHXuxO2jUmgo6dJt>N(Fcg@k^YkahkGiDd`i|RG?`@M2$HzP#9Okv74 z|4?16dDY->`u#k+?;L#91?K(|QvQ+SQoVXwJ6SG3l%_-Zf*hzBz&wb+9*QRAEPbnfVTMVYx=0m{o4CzuXRkDLg0Nj z+xuPB#~fHG3D;2I?2TRaL+fW_opLt&Q+Cyl|(0R-6~((9)cQl>ctNySkoD5sPy52X9r+ zkNy=?E?^J&dnW(0e`Y%P0<(;#7WoO(EUqyp|C)a6|;Ft@8a#Oc4 z07w+K%_URk(C7}PFdaT7-8GnU@P5(3z3sFX5?CkR{9aJT3 zy4tGaeOIrv!JY9A2ECltcDAntP@u9(#s?$5cgs*Z!;ckn-JIYxpnVsx=(=MR>g)C+ zZSb+KnEMCtozYjU3ru}(Z?!Xbp_id&`T~9fTGe&)f@}(Y5frB__g69G&c>{HJd;dUoP{4A_%OH?EHUqLBj2D}EX@l;m2Z=u%~AhE z=l-aUjcZm`%yc!)zojmZ_(~_qSL(Lq70#ToaxtP70xH7e1rE+S(0RblROM-k#jqtG z6h4L3u~WhGAk^qN*>fVFiVOCRD7Npq{C*(JDG#V5i@ao{sBDwo1pwhR&)omFG5P@Q zMOYf=-vToiqtNu8lJUa9xHlvx-(eqBQsKuD!s|8(Mt}x6oFdjyK@#i}=t`_SL@6sr zeU}R>rsvDMfZjq0?uCZT?}2#6@-rgSp|PUoj}+>*(^9Sps-AN#3=eeXO~diK@B5dV zC$DxBnmGXD5E9i`)L%#n$Bz8HB-YNLu(L7c}?=n{P>UHQJZhQ>Lv;4>OlqMB0 zvg#z3AAS!fN1VQIWH_~RS!;ys*XFAC$wQAIqheRL5L`SNXs63X*20{zsLw}8&Z0L> z2Ydya2DSpIi=ktihuTsOmQ{RG^?r9GW_gV>*;jzQqCCt<7k<9(L7#_RI@t*=P)-bw z*Fv7}3A2}3Ojs{Lm%=M4Nlk|P=*|*7B7q-@*`fEd9Rq!@P)AOn6QDGE|0F{e1ZWfUc(N9>7rm74iVBw%03{JqDyDNW`6QTr*(5 zr#0Ghr3-f!pY+~{PtRV?9oS~3*-moylln+)Q4i#Uv_e#uFs8@FSoISpfjp#=|7APAlJkr%M zUjb8&02EM;4%9U0yyv=mG0H63Eczlg-u4WF8}HQMG`#f|lUsccDZw0C9R*HuN&{l| z%YVO}&!wn+N&fc3pB|Hs^blxz`WJbu4e#QS(%KS_YEFaygzM*fTD4G6ak<`I98|X5 zhPe}wm5^3#ncZoQCS2-q+a!IlvGY14Lo^jvu2eg{7AXc@ML1tkl|u|cj7OI+(uVff ztm)I)+Vi%MlD#)a0(_XQ-^r?_0`*X0fm>aCk`zuOu=|dVRhPuz6nb&@vE%9Yay<5< zFDU^2pxHgI=Z9pwGo{r)5=UL>##Fr7wA=_H0|HEb9Q;#vFZn%GyCR|2T!7DL5iJWk zkkYo-nw3$@E{9g?lEzl>N&FS^q6Eop4z7oW^COM;fZ@GHNfVUpL@2~FXyF~%pSnB} zeCup*B;}NOfWEl0a|@04P&PT-YG08^L%}qT-1&s6Cw<9fe73ykkzPq7c6`^~nyu%vQig2dE*1 zV>2RsMD3nvpRCXDjwe>Y_RC3cf@*<0nMP2s8E| zigVMESYGk>OGdf5k3^9pufbhNgs(BR9)1=+@tW9$1D0EcS*Sqn>wXc==|f7KI6N z)I4}K5l+^=bw?{!1s0G2I!n~Wr`Sf?I`J@4YPhxbM%MU;jdsUP8Sad$vvpT4x_q2;cEqsbUykdKs`c2CM0B zmCW0T<2=bB`3Iye*!i~+Ovp8;&Y81;3i2A;f?s@P^M|`K+(R~UkntG9pz%)`aA-GY zH-lmJGvfA_XXc0HgeS>+m+)@N2glR@ zOr3GD-0HwqDmqq&3?TeltK2UmQ)I_|4HjD?n%WO$!AxvVeCLHMrm|P^mVEH74ae_e zV<#8xa7$>D7>MFJZN)VD3j3}UFD@;wo=%q}eI<{s@TZ%NdzFx(K5XjF>W0M^ zn#pNI_70GqA@zkcyZg_J;=mO{h_pdxxDCD~5(0V82jKaY3uxn%D24J5NW{3TVq_AbxsQlfF64F9K zV>jcyov%b3b25Tx^H}3y3%K1b^pZ)xumeC?e92MJ-KUFMxR04JY_lVkEce|7{om8q zcfbianJexz)lq#{lRIV?Ck>9;)$iJ<#orlqd!Y(A)KUNi&>0x_zmD6&`utwDiV>5Pnr*Ge-Nhz!@pW6&>Ixfs(v{(k=3%yL65<6e?8s4}hJ9S5l`Jiu<(@8pGe9_?(IndHOW?x9n z$3^HOTA5-LQtZaa5mgFTqQ(X^((MJ{0}z=k98`&m=+V!8i34vPg6x= z+^3T^P*1HJk2Y@fP5vH|zza>|4ip2q%-K)?i&)TiZ;n6naB|`z=2oZVeIghOrtgu# zBwmZzNOIb$wE0oNRqQf;DxESe_?r0)RsXcHH-abk(y=R5;_bV9c*zrb%`ayV9#Rf| z)W_Izz=t4#n36zV#NyOylpF68(<_r~a3;-sYFS}_2qwxDNwz}Pi)o&&$t(pGwK=oe zI}*^({e_ai>o49VkcS1AphDbeno@}S$^8R>lBbqO5MVCZmyys1=f!L=)jzkRV2eb& z4RY>HLo0`b;(=1sg=!vm zne6gOi}FQ@hS5slS4))`ln&<j4Z5dcxNCE8-KMb^IFa~Jb$6-9#qnN!pkMo?d@$pGVm z#)N3F6}z`5(1I~g=qJ#Vi76ObC~Tf4BZ|&UqDS(-BZV5yYnR-b_4!t?ZX{Dx;;Hp6 z<*R|a7QQkCFWQGb9-E(WH^BsfL#Ky|5tn*oaY6xHUC6@p$_OvZgM!nA7RWgSSIMK^ zub!`fXD1*{7UWXzN8ecWDj9F8QKp+h*|Uz?994vYbX1DObnBjVkKJNKse!Zhgyv;# z&>@K9c>r`Y$MmN&>`h`=J0yqE-2j4P3^1tTt0!>oL{Fyon@JMeK$^h2cd4{1cs1@q zz1imx4ndZM<;d;U;@PlucsYWXEp5HElf;xui(y$Wm16-JKeC{y*-&mI=Ff%}RLi8f zcj#M}z{HwDMFM%(i;1bi-9AgmZIO=v!_KE*SCu5;}l>VVbX#|NX?%{K;Ggq!jbRv=6O literal 10324 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 Date: Mon, 10 Nov 2025 16:20:28 -0800 Subject: [PATCH 949/966] test: create and delete temp folder in scripts (#1869) --- packages/google-auth/scripts/decrypt-secrets.sh | 4 ++++ packages/google-auth/scripts/encrypt-secrets.sh | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/scripts/decrypt-secrets.sh b/packages/google-auth/scripts/decrypt-secrets.sh index f0ef994edcd7..7e7f03bdca53 100755 --- a/packages/google-auth/scripts/decrypt-secrets.sh +++ b/packages/google-auth/scripts/decrypt-secrets.sh @@ -20,6 +20,10 @@ ROOT=$( dirname "$DIR" ) # Work from the project root. cd $ROOT +# Create working directory if not exists. system_tests/data is not tracked by +# Git to prevent the secrets from being leaked online. +mkdir -p system_tests/data + gcloud kms decrypt \ --location=global \ --keyring=ci \ diff --git a/packages/google-auth/scripts/encrypt-secrets.sh b/packages/google-auth/scripts/encrypt-secrets.sh index b6521e8f596f..fba27fba0f9d 100755 --- a/packages/google-auth/scripts/encrypt-secrets.sh +++ b/packages/google-auth/scripts/encrypt-secrets.sh @@ -29,4 +29,6 @@ gcloud kms encrypt \ --plaintext-file=system_tests/secrets.tar \ --ciphertext-file=system_tests/secrets.tar.enc -rm system_tests/secrets.tar \ No newline at end of file +rm system_tests/secrets.tar + +rm system_tests/data \ No newline at end of file From 1e5c79726a5364686b7bdb7a58ccd07980367344 Mon Sep 17 00:00:00 2001 From: werman Date: Mon, 10 Nov 2025 16:42:24 -0800 Subject: [PATCH 950/966] doc: Custom Credential Suppliers for AWS and Okta. (#1830) Documenting Custom Credential Suppliers for: 1. Aws Workload. 2. Okta Workload. The readme updates for these have already been made: [Link](https://github.com/googleapis/google-auth-library-python/pull/1496/files) --------- Co-authored-by: Chalmer Lowe Co-authored-by: Daniel Sanche --- .../snippets/custom_aws_supplier.py | 117 +++++++++++ .../snippets/custom_okta_supplier.py | 190 ++++++++++++++++++ .../cloud-client/snippets/requirements.txt | 5 +- 3 files changed, 311 insertions(+), 1 deletion(-) create mode 100644 packages/google-auth/samples/cloud-client/snippets/custom_aws_supplier.py create mode 100644 packages/google-auth/samples/cloud-client/snippets/custom_okta_supplier.py diff --git a/packages/google-auth/samples/cloud-client/snippets/custom_aws_supplier.py b/packages/google-auth/samples/cloud-client/snippets/custom_aws_supplier.py new file mode 100644 index 000000000000..ec5bf8a10fb0 --- /dev/null +++ b/packages/google-auth/samples/cloud-client/snippets/custom_aws_supplier.py @@ -0,0 +1,117 @@ +# Copyright 2025 Google LLC +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import os +import sys + +import boto3 +from dotenv import load_dotenv +from google.auth.aws import Credentials as AwsCredentials +from google.auth.aws import AwsSecurityCredentials, AwsSecurityCredentialsSupplier +from google.auth.exceptions import GoogleAuthError +from google.auth.transport.requests import AuthorizedSession + +load_dotenv() + + +class CustomAwsSupplier(AwsSecurityCredentialsSupplier): + """Custom AWS Security Credentials Supplier.""" + + def __init__(self): + """Initializes the Boto3 session, prioritizing environment variables for region.""" + # Explicitly read the region from the environment first. This ensures that + # a value from a .env file is picked up reliably for local testing. + region = os.getenv("AWS_REGION") or os.getenv("AWS_DEFAULT_REGION") + + # If region is None, Boto3's discovery chain will be used when needed. + self.session = boto3.Session(region_name=region) + self._cached_region = None + print(f"[INFO] CustomAwsSupplier initialized. Region from env: {region}") + + def get_aws_region(self, context, request) -> str: + """Returns the AWS region using Boto3's default provider chain.""" + if self._cached_region: + return self._cached_region + + # Accessing region_name will use the value from the constructor if provided, + # otherwise it triggers Boto3's lazy-loading discovery (e.g., metadata service). + self._cached_region = self.session.region_name + + if not self._cached_region: + print("[ERROR] Boto3 was unable to resolve an AWS region.", file=sys.stderr) + raise GoogleAuthError("Boto3 was unable to resolve an AWS region.") + + print(f"[INFO] Boto3 resolved AWS Region: {self._cached_region}") + return self._cached_region + + def get_aws_security_credentials(self, context, request=None) -> AwsSecurityCredentials: + """Retrieves AWS security credentials using Boto3's default provider chain.""" + aws_credentials = self.session.get_credentials() + if not aws_credentials: + print("[ERROR] Unable to resolve AWS credentials.", file=sys.stderr) + raise GoogleAuthError("Unable to resolve AWS credentials from the provider chain.") + + print(f"[INFO] Resolved AWS Access Key ID: {aws_credentials.access_key}") + + return AwsSecurityCredentials( + access_key_id=aws_credentials.access_key, + secret_access_key=aws_credentials.secret_key, + session_token=aws_credentials.token, + ) + + +def main(): + """Main function to demonstrate the custom AWS supplier.""" + print("--- Starting Script ---") + + gcp_audience = os.getenv("GCP_WORKLOAD_AUDIENCE") + sa_impersonation_url = os.getenv("GCP_SERVICE_ACCOUNT_IMPERSONATION_URL") + gcs_bucket_name = os.getenv("GCS_BUCKET_NAME") + + print(f"GCP_WORKLOAD_AUDIENCE: {gcp_audience}") + print(f"GCS_BUCKET_NAME: {gcs_bucket_name}") + + if not all([gcp_audience, sa_impersonation_url, gcs_bucket_name]): + print("[ERROR] Missing required environment variables.", file=sys.stderr) + raise GoogleAuthError("Missing required environment variables.") + + custom_supplier = CustomAwsSupplier() + + credentials = AwsCredentials( + audience=gcp_audience, + subject_token_type="urn:ietf:params:aws:token-type:aws4_request", + service_account_impersonation_url=sa_impersonation_url, + aws_security_credentials_supplier=custom_supplier, + scopes=['https://www.googleapis.com/auth/devstorage.read_write'], + ) + + bucket_url = f"https://storage.googleapis.com/storage/v1/b/{gcs_bucket_name}" + print(f"Request URL: {bucket_url}") + + authed_session = AuthorizedSession(credentials) + try: + print("Attempting to make authenticated request to Google Cloud Storage...") + res = authed_session.get(bucket_url) + res.raise_for_status() + print("\n--- SUCCESS! ---") + print("Successfully authenticated and retrieved bucket data:") + print(json.dumps(res.json(), indent=2)) + except Exception as e: + print("--- FAILED --- ", file=sys.stderr) + print(e, file=sys.stderr) + exit(1) + + +if __name__ == "__main__": + main() diff --git a/packages/google-auth/samples/cloud-client/snippets/custom_okta_supplier.py b/packages/google-auth/samples/cloud-client/snippets/custom_okta_supplier.py new file mode 100644 index 000000000000..12f83dcfaca0 --- /dev/null +++ b/packages/google-auth/samples/cloud-client/snippets/custom_okta_supplier.py @@ -0,0 +1,190 @@ +# Copyright 2025 Google LLC +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import urllib.parse +import os +import time + +import requests +from dotenv import load_dotenv +from google.auth.exceptions import GoogleAuthError +from google.auth.identity_pool import Credentials as IdentityPoolClient +from google.auth.transport.requests import AuthorizedSession + +load_dotenv() + +# Workload Identity Pool Configuration +GCP_WORKLOAD_AUDIENCE = os.getenv("GCP_WORKLOAD_AUDIENCE") +SERVICE_ACCOUNT_IMPERSONATION_URL = os.getenv("GCP_SERVICE_ACCOUNT_IMPERSONATION_URL") +GCS_BUCKET_NAME = os.getenv("GCS_BUCKET_NAME") + +# Okta Configuration +OKTA_DOMAIN = os.getenv("OKTA_DOMAIN") +OKTA_CLIENT_ID = os.getenv("OKTA_CLIENT_ID") +OKTA_CLIENT_SECRET = os.getenv("OKTA_CLIENT_SECRET") + +# Constants +TOKEN_URL = "https://sts.googleapis.com/v1/token" +SUBJECT_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:jwt" + + +class OktaClientCredentialsSupplier: + """A custom SubjectTokenSupplier that authenticates with Okta. + + This supplier uses the Client Credentials grant flow for machine-to-machine + (M2M) authentication with Okta. + """ + + def __init__(self, domain, client_id, client_secret): + self.okta_token_url = f"{domain}/oauth2/default/v1/token" + self.client_id = client_id + self.client_secret = client_secret + self.access_token = None + self.expiry_time = 0 + print("OktaClientCredentialsSupplier initialized.") + + def get_subject_token(self, context, request=None) -> str: + """Fetches a new token if the current one is expired or missing. + + Args: + context: The context object, not used in this implementation. + + Returns: + The Okta Access token. + """ + # Check if the current token is still valid (with a 60-second buffer). + is_token_valid = self.access_token and time.time() < self.expiry_time - 60 + + if is_token_valid: + print("[Supplier] Returning cached Okta Access token.") + return self.access_token + + print( + "[Supplier] Token is missing or expired. Fetching new Okta Access token..." + ) + self._fetch_okta_access_token() + return self.access_token + + def _fetch_okta_access_token(self): + """Performs the Client Credentials grant flow with Okta.""" + headers = { + "Content-Type": "application/x-www-form-urlencoded", + "Accept": "application/json", + } + data = { + "grant_type": "client_credentials", + "scope": "gcp.test.read", + } + encoded_data = urllib.parse.urlencode(data) + + try: + response = requests.post( + self.okta_token_url, + headers=headers, + data=encoded_data, + auth=(self.client_id, self.client_secret), + ) + response.raise_for_status() + token_data = response.json() + + if "access_token" in token_data and "expires_in" in token_data: + self.access_token = token_data["access_token"] + self.expiry_time = time.time() + token_data["expires_in"] + print( + f"[Supplier] Successfully received Access Token from Okta. " + f"Expires in {token_data['expires_in']} seconds." + ) + else: + raise GoogleAuthError( + "Access token or expires_in not found in Okta response." + ) + except requests.exceptions.RequestException as e: + print(f"[Supplier] Error fetching token from Okta: {e}") + if e.response: + print(f"[Supplier] Okta response: {e.response.text}") + raise GoogleAuthError( + "Failed to authenticate with Okta using Client Credentials grant." + ) from e + + +def main(): + """Main function to demonstrate the custom Okta supplier. + + TODO(Developer): + 1. Before running this sample, set up your environment variables. You can do + this by creating a .env file in the same directory as this script and + populating it with the following variables: + - GCP_WORKLOAD_AUDIENCE: The audience for the GCP workload identity pool. + - GCP_SERVICE_ACCOUNT_IMPERSONATION_URL: The URL for service account impersonation (optional). + - GCS_BUCKET_NAME: The name of the GCS bucket to access. + - OKTA_DOMAIN: Your Okta domain (e.g., https://dev-12345.okta.com). + - OKTA_CLIENT_ID: The Client ID of your Okta M2M application. + - OKTA_CLIENT_SECRET: The Client Secret of your Okta M2M application. + """ + if not all( + [ + GCP_WORKLOAD_AUDIENCE, + GCS_BUCKET_NAME, + OKTA_DOMAIN, + OKTA_CLIENT_ID, + OKTA_CLIENT_SECRET, + ] + ): + raise GoogleAuthError( + "Missing required environment variables. Please check your .env file." + ) + + # 1. Instantiate the custom supplier with Okta credentials. + okta_supplier = OktaClientCredentialsSupplier( + OKTA_DOMAIN, OKTA_CLIENT_ID, OKTA_CLIENT_SECRET + ) + + # 2. Instantiate an IdentityPoolClient. + client = IdentityPoolClient( + audience=GCP_WORKLOAD_AUDIENCE, + subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + subject_token_supplier=okta_supplier, + # If you choose to provide explicit scopes: use the `scopes` parameter. + default_scopes=['https://www.googleapis.com/auth/cloud-platform'], + service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + ) + + # 3. Construct the URL for the Cloud Storage JSON API. + bucket_url = f"https://storage.googleapis.com/storage/v1/b/{GCS_BUCKET_NAME}" + print(f"[Test] Getting metadata for bucket: {GCS_BUCKET_NAME}...") + print(f"[Test] Request URL: {bucket_url}") + + # 4. Use the client to make an authenticated request. + authed_session = AuthorizedSession(client) + try: + res = authed_session.get(bucket_url) + res.raise_for_status() + print("\n--- SUCCESS! ---") + print("Successfully authenticated and retrieved bucket data:") + print(json.dumps(res.json(), indent=2)) + except requests.exceptions.RequestException as e: + print("\n--- FAILED ---") + print(f"Request failed: {e}") + if e.response: + print(f"Response: {e.response.text}") + exit(1) + except GoogleAuthError as e: + print("\n--- FAILED ---") + print(f"Authentication or request failed: {e}") + exit(1) + + +if __name__ == "__main__": + main() diff --git a/packages/google-auth/samples/cloud-client/snippets/requirements.txt b/packages/google-auth/samples/cloud-client/snippets/requirements.txt index 416c56b94671..97f256bd8003 100644 --- a/packages/google-auth/samples/cloud-client/snippets/requirements.txt +++ b/packages/google-auth/samples/cloud-client/snippets/requirements.txt @@ -1,4 +1,7 @@ google-cloud-compute==1.5.1 google-cloud-storage==3.1.0 -google-auth==2.38.0 +google-auth==2.41.1 pytest==7.1.2 +boto3>=1.26.0 +requests==2.32.3 +python-dotenv==1.1.1 \ No newline at end of file From 22986d3fa28ab158113667d86c557a3c6af960cb Mon Sep 17 00:00:00 2001 From: nbayati <99771966+nbayati@users.noreply.github.com> Date: Mon, 10 Nov 2025 17:19:57 -0800 Subject: [PATCH 951/966] feat: Add shlex to correctly parse executable commands with spaces (#1855) The `subprocess.run` command was using `.split()` which does not handle quoted paths with spaces correctly. This would cause a `FileNotFoundError` when the path to the executable contained spaces. This change replaces `.split()` with `shlex.split()` to correctly parse the command string. A test case has been added to verify the fix and prevent regressions. This was reported in b/237606033 Co-authored-by: Daniel Sanche --- packages/google-auth/google/auth/pluggable.py | 5 ++-- packages/google-auth/tests/test_pluggable.py | 30 +++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/google/auth/pluggable.py b/packages/google-auth/google/auth/pluggable.py index fd349537d4d5..730a72c28350 100644 --- a/packages/google-auth/google/auth/pluggable.py +++ b/packages/google-auth/google/auth/pluggable.py @@ -37,6 +37,7 @@ from collections import Mapping # type: ignore import json import os +import shlex import subprocess import sys import time @@ -220,7 +221,7 @@ def retrieve_subject_token(self, request): exe_stderr = sys.stdout if self.interactive else subprocess.STDOUT result = subprocess.run( - self._credential_source_executable_command.split(), + shlex.split(self._credential_source_executable_command), timeout=exe_timeout, stdin=exe_stdin, stdout=exe_stdout, @@ -273,7 +274,7 @@ def revoke(self, request): # Run executable result = subprocess.run( - self._credential_source_executable_command.split(), + shlex.split(self._credential_source_executable_command), timeout=self._credential_source_executable_interactive_timeout_millis / 1000, stdout=subprocess.PIPE, diff --git a/packages/google-auth/tests/test_pluggable.py b/packages/google-auth/tests/test_pluggable.py index 066920b22dd8..d15ebb88bab8 100644 --- a/packages/google-auth/tests/test_pluggable.py +++ b/packages/google-auth/tests/test_pluggable.py @@ -1239,6 +1239,36 @@ def test_retrieve_subject_token_python_2(self): assert excinfo.match(r"Pluggable auth is only supported for python 3.7+") + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) + def test_retrieve_subject_token_with_quoted_command(self): + command_with_spaces = '"/path/with spaces/to/executable" "arg with spaces"' + credential_source = { + "executable": {"command": command_with_spaces, "timeout_millis": 30000} + } + + with mock.patch( + "subprocess.run", + return_value=subprocess.CompletedProcess( + args=[], + stdout=json.dumps( + self.EXECUTABLE_SUCCESSFUL_OIDC_RESPONSE_ID_TOKEN + ).encode("UTF-8"), + returncode=0, + ), + ) as mock_run: + credentials = self.make_pluggable(credential_source=credential_source) + subject_token = credentials.retrieve_subject_token(None) + + assert subject_token == self.EXECUTABLE_OIDC_TOKEN + mock_run.assert_called_once_with( + ["/path/with spaces/to/executable", "arg with spaces"], + timeout=30.0, + stdin=None, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + env=mock.ANY, + ) + @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) def test_revoke_subject_token_python_2(self): with mock.patch("sys.version_info", (2, 7)): From 4179ce1237c297e81184baca6df00ac363c10630 Mon Sep 17 00:00:00 2001 From: kdeniz-git Date: Tue, 11 Nov 2025 09:15:11 -0800 Subject: [PATCH 952/966] =?UTF-8?q?feat:=20Implement=20token=20revocation?= =?UTF-8?q?=20in=20STS=20client=20and=20add=20revoke()=20metho=E2=80=A6=20?= =?UTF-8?q?(#1849)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …d to ExternalAccountAuthorizedUser credentials * Add support for OAuth 2.0 token revocation to the STS client, aligning with the specification in RFC7009. * A new revoke_token method is introduced, which makes a POST request to a revocation endpoint. The underlying request handler has also been updated to correctly process successful but empty HTTP responses, as specified by the standard for revocation. * Building on the STS client's new capabilities, this change exposes a public revoke() method on the ExternalAccountAuthorizedUser credentials class. * This method encapsulates the logic for revoking the refresh token by calling the underlying STS client's revoke_token function. It simplifies the process for client applications, like gcloud, to revoke these specific credentials without needing to interact directly with the STS client. * Unit tests are included to verify successful revocation and to ensure appropriate errors are raised if required fields (like revoke_url) are missing. --------- Co-authored-by: Daniel Sanche Co-authored-by: nbayati <99771966+nbayati@users.noreply.github.com> --- .../auth/external_account_authorized_user.py | 24 ++++++ packages/google-auth/google/oauth2/sts.py | 35 ++++++-- packages/google-auth/tests/oauth2/test_sts.py | 84 +++++++++++++++++-- .../test_external_account_authorized_user.py | 44 ++++++++++ 4 files changed, 176 insertions(+), 11 deletions(-) diff --git a/packages/google-auth/google/auth/external_account_authorized_user.py b/packages/google-auth/google/auth/external_account_authorized_user.py index f8fbf950beb2..2594e048f388 100644 --- a/packages/google-auth/google/auth/external_account_authorized_user.py +++ b/packages/google-auth/google/auth/external_account_authorized_user.py @@ -321,6 +321,30 @@ def _build_trust_boundary_lookup_url(self): universe_domain=self._universe_domain, pool_id=pool_id ) + def revoke(self, request): + """Revokes the refresh token. + + Args: + request (google.auth.transport.Request): The object used to make + HTTP requests. + + Raises: + google.auth.exceptions.OAuthError: If the token could not be + revoked. + """ + if not self._revoke_url or not self._refresh_token_val: + raise exceptions.OAuthError( + "The credentials do not contain the necessary fields to " + "revoke the refresh token. You must specify revoke_url and " + "refresh_token." + ) + + self._sts_client.revoke_token( + request, self._refresh_token_val, "refresh_token", self._revoke_url + ) + self.token = None + self._refresh_token = None + @_helpers.copy_docstring(credentials.Credentials) def get_cred_info(self): if self._cred_file_path: diff --git a/packages/google-auth/google/oauth2/sts.py b/packages/google-auth/google/oauth2/sts.py index ad3962735f7a..60d6f83c4d9f 100644 --- a/packages/google-auth/google/oauth2/sts.py +++ b/packages/google-auth/google/oauth2/sts.py @@ -57,7 +57,7 @@ def __init__(self, token_exchange_endpoint, client_authentication=None): super(Client, self).__init__(client_authentication) self._token_exchange_endpoint = token_exchange_endpoint - def _make_request(self, request, headers, request_body): + def _make_request(self, request, headers, request_body, url=None): # Initialize request headers. request_headers = _URLENCODED_HEADERS.copy() @@ -69,9 +69,12 @@ def _make_request(self, request, headers, request_body): # Apply OAuth client authentication. self.apply_client_authentication_options(request_headers, request_body) + # Use default token exchange endpoint if no url is provided. + url = url or self._token_exchange_endpoint + # Execute request. response = request( - url=self._token_exchange_endpoint, + url=url, method="POST", headers=request_headers, body=urllib.parse.urlencode(request_body).encode("utf-8"), @@ -87,10 +90,12 @@ def _make_request(self, request, headers, request_body): if response.status != http_client.OK: utils.handle_error_response(response_body) - response_data = json.loads(response_body) + # A successful token revocation returns an empty response body. + if not response_body: + return {} - # Return successful response. - return response_data + # Other successful responses should be valid JSON. + return json.loads(response_body) def exchange_token( self, @@ -174,3 +179,23 @@ def refresh_token(self, request, refresh_token): None, {"grant_type": "refresh_token", "refresh_token": refresh_token}, ) + + def revoke_token(self, request, token, token_type_hint, revoke_url): + """Revokes the provided token based on the RFC7009 spec. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + token (str): The OAuth 2.0 token to revoke. + token_type_hint (str): Hint for the type of token being revoked. + revoke_url (str): The STS endpoint URL for revoking tokens. + + Raises: + google.auth.exceptions.OAuthError: If the token revocation endpoint + returned an error. + """ + request_body = {"token": token} + if token_type_hint: + request_body["token_type_hint"] = token_type_hint + + return self._make_request(request, None, request_body, revoke_url) diff --git a/packages/google-auth/tests/oauth2/test_sts.py b/packages/google-auth/tests/oauth2/test_sts.py index e0fb4ae23e66..e9075e40688b 100644 --- a/packages/google-auth/tests/oauth2/test_sts.py +++ b/packages/google-auth/tests/oauth2/test_sts.py @@ -41,6 +41,9 @@ class TestStsClient(object): ACTOR_TOKEN = "HEADER.ACTOR_TOKEN_PAYLOAD.SIGNATURE" ACTOR_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:jwt" TOKEN_EXCHANGE_ENDPOINT = "https://example.com/token.oauth2" + REVOKE_URL = "https://example.com/revoke.oauth2" + TOKEN_TO_REVOKE = "TOKEN_TO_REVOKE" + TOKEN_TYPE_HINT = "refresh_token" ADDON_HEADERS = {"x-client-version": "0.1.2"} ADDON_OPTIONS = {"additional": {"non-standard": ["options"], "other": "some-value"}} SUCCESS_RESPONSE = { @@ -72,10 +75,13 @@ def make_client(cls, client_auth=None): return sts.Client(cls.TOKEN_EXCHANGE_ENDPOINT, client_auth) @classmethod - def make_mock_request(cls, data, status=http_client.OK): + def make_mock_request(cls, data, status=http_client.OK, use_json=True): response = mock.create_autospec(transport.Response, instance=True) response.status = status - response.data = json.dumps(data).encode("utf-8") + if use_json: + response.data = json.dumps(data).encode("utf-8") + else: + response.data = data.encode("utf-8") request = mock.create_autospec(transport.Request) request.return_value = response @@ -83,10 +89,10 @@ def make_mock_request(cls, data, status=http_client.OK): return request @classmethod - def assert_request_kwargs(cls, request_kwargs, headers, request_data): - """Asserts the request was called with the expected parameters. - """ - assert request_kwargs["url"] == cls.TOKEN_EXCHANGE_ENDPOINT + def assert_request_kwargs(cls, request_kwargs, headers, request_data, url=None): + """Asserts the request was called with the expected parameters.""" + url = url or cls.TOKEN_EXCHANGE_ENDPOINT + assert request_kwargs["url"] == url assert request_kwargs["method"] == "POST" assert request_kwargs["headers"] == headers assert request_kwargs["body"] is not None @@ -447,6 +453,63 @@ def test_refresh_token_failure(self): r"Error code invalid_request: Invalid subject token - https://tools.ietf.org/html/rfc6749" ) + def test_revoke_token_success(self): + """Test revoke token with successful response.""" + client = self.make_client(self.CLIENT_AUTH_BASIC) + request = self.make_mock_request(data="", status=http_client.OK, use_json=False) + + response = client.revoke_token( + request, self.TOKEN_TO_REVOKE, self.TOKEN_TYPE_HINT, self.REVOKE_URL + ) + + headers = { + "Authorization": "Basic {}".format(BASIC_AUTH_ENCODING), + "Content-Type": "application/x-www-form-urlencoded", + } + request_data = { + "token": self.TOKEN_TO_REVOKE, + "token_type_hint": self.TOKEN_TYPE_HINT, + } + self.assert_request_kwargs( + request.call_args[1], headers, request_data, url=self.REVOKE_URL + ) + assert response == {} + + def test_revoke_token_success_no_hint(self): + """Test revoke token with successful response.""" + client = self.make_client(self.CLIENT_AUTH_BASIC) + request = self.make_mock_request(data="", status=http_client.OK, use_json=False) + + response = client.revoke_token( + request, self.TOKEN_TO_REVOKE, None, self.REVOKE_URL + ) + + headers = { + "Authorization": "Basic {}".format(BASIC_AUTH_ENCODING), + "Content-Type": "application/x-www-form-urlencoded", + } + request_data = {"token": self.TOKEN_TO_REVOKE} + self.assert_request_kwargs( + request.call_args[1], headers, request_data, url=self.REVOKE_URL + ) + assert response == {} + + def test_revoke_token_failure(self): + """Test revoke token with failure response.""" + client = self.make_client(self.CLIENT_AUTH_BASIC) + request = self.make_mock_request( + status=http_client.BAD_REQUEST, data=self.ERROR_RESPONSE + ) + + with pytest.raises(exceptions.OAuthError) as excinfo: + client.revoke_token( + request, self.TOKEN_TO_REVOKE, self.TOKEN_TYPE_HINT, self.REVOKE_URL + ) + + assert excinfo.match( + r"Error code invalid_request: Invalid subject token - https://tools.ietf.org/html/rfc6749" + ) + def test__make_request_success(self): """Test base method with successful response.""" client = self.make_client(self.CLIENT_AUTH_BASIC) @@ -478,3 +541,12 @@ def test_make_request_failure(self): assert excinfo.match( r"Error code invalid_request: Invalid subject token - https://tools.ietf.org/html/rfc6749" ) + + def test__make_request_empty_response(self): + """Test _make_request with a successful but empty response body.""" + client = self.make_client() + request = self.make_mock_request(data="", status=http_client.OK, use_json=False) + + response = client._make_request(request, {}, {}) + + assert response == {} diff --git a/packages/google-auth/tests/test_external_account_authorized_user.py b/packages/google-auth/tests/test_external_account_authorized_user.py index a4e1217817bc..0a54af56d5f2 100644 --- a/packages/google-auth/tests/test_external_account_authorized_user.py +++ b/packages/google-auth/tests/test_external_account_authorized_user.py @@ -349,6 +349,50 @@ def test_refresh_without_client_secret(self): request.assert_not_called() + def test_revoke_auth_success(self): + request = self.make_mock_request(status=http_client.OK, data={}) + creds = self.make_credentials(revoke_url=REVOKE_URL) + + creds.revoke(request) + + request.assert_called_once_with( + url=REVOKE_URL, + method="POST", + headers={ + "Content-Type": "application/x-www-form-urlencoded", + "Authorization": "Basic " + BASIC_AUTH_ENCODING, + }, + body=("token=" + REFRESH_TOKEN + "&token_type_hint=refresh_token").encode( + "utf-8" + ), + ) + assert creds.token is None + assert creds._refresh_token is None + + def test_revoke_without_revoke_url(self): + request = self.make_mock_request() + creds = self.make_credentials(token=ACCESS_TOKEN) + + with pytest.raises(exceptions.OAuthError) as excinfo: + creds.revoke(request) + + assert excinfo.match( + r"The credentials do not contain the necessary fields to revoke the refresh token. You must specify revoke_url and refresh_token." + ) + + def test_revoke_without_refresh_token(self): + request = self.make_mock_request() + creds = self.make_credentials( + refresh_token=None, token=ACCESS_TOKEN, revoke_url=REVOKE_URL + ) + + with pytest.raises(exceptions.OAuthError) as excinfo: + creds.revoke(request) + + assert excinfo.match( + r"The credentials do not contain the necessary fields to revoke the refresh token. You must specify revoke_url and refresh_token." + ) + def test_info(self): creds = self.make_credentials() info = creds.info From b7a0298f2560c1dfb5abb6250ccd329728b47a2c Mon Sep 17 00:00:00 2001 From: Nolan Eastin <80856764+nolanleastin@users.noreply.github.com> Date: Wed, 19 Nov 2025 11:55:36 -0800 Subject: [PATCH 953/966] chore: update secret (#1874) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index af9af5e620bb2bc052d49a2c7e1c1c765ea238d1..6a4f1f3081812426117834f30ce315cba1f1535f 100644 GIT binary patch literal 10324 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 From e7d2c40b815ab8d846230148d5b88e74ed4ebd54 Mon Sep 17 00:00:00 2001 From: Nolan Eastin <80856764+nolanleastin@users.noreply.github.com> Date: Wed, 19 Nov 2025 13:37:00 -0800 Subject: [PATCH 954/966] feat: MDS connections use mTLS (#1856) Use mTLS/HTTPS when connecting to MDS **Feature Gating** The `GCE_METADATA_MTLS_MODE` environment variable is introduced, which can be set to strict, none, or default. The `should_use_mds_mtls` function determines whether to use mTLS based on the environment variable and the existence of the certificate files in well-known location ((https://docs.cloud.google.com/compute/docs/metadata/overview#https-mds-certificates). **Description of changes** A custom `MdsMtlsAdapter` is implemented to handle the SSL context for mTLS. MdsMtlsAdapter loads MDS mTLS certificates from well-known location. MdsMtlsAdapter is mounted into the provided request.Session. **Behavior** If mode == none: Continue to use HTTP. If mode == default: Use HTTPS if certificates exist. If HTTPS/mTLS fails, falls back to HTTP. If mode == strict: Use HTTPS always, even if certificates don't exist (will result in error). **Integrating with existing code** compute_engine/_metadata.py: - The metadata server URL construction is now dynamic, supporting both http and https schemes based on whether mTLS is enabled. - ping and get functions are updated to use mTLS when it's enabled. --- .../google/auth/compute_engine/_metadata.py | 111 ++++++- .../google/auth/compute_engine/_mtls.py | 164 ++++++++++ .../google/auth/environment_vars.py | 6 + .../tests/compute_engine/test__metadata.py | 216 +++++++++++-- .../tests/compute_engine/test__mtls.py | 288 ++++++++++++++++++ 5 files changed, 749 insertions(+), 36 deletions(-) create mode 100644 packages/google-auth/google/auth/compute_engine/_mtls.py create mode 100644 packages/google-auth/tests/compute_engine/test__mtls.py diff --git a/packages/google-auth/google/auth/compute_engine/_metadata.py b/packages/google-auth/google/auth/compute_engine/_metadata.py index ddbe8ac2f708..96f1ff526ef2 100644 --- a/packages/google-auth/google/auth/compute_engine/_metadata.py +++ b/packages/google-auth/google/auth/compute_engine/_metadata.py @@ -24,15 +24,23 @@ import os from urllib.parse import urljoin +import requests + from google.auth import _helpers from google.auth import environment_vars from google.auth import exceptions from google.auth import metrics from google.auth import transport from google.auth._exponential_backoff import ExponentialBackoff +from google.auth.compute_engine import _mtls + _LOGGER = logging.getLogger(__name__) +_GCE_DEFAULT_MDS_IP = "169.254.169.254" +_GCE_DEFAULT_HOST = "metadata.google.internal" +_GCE_DEFAULT_MDS_HOSTS = [_GCE_DEFAULT_HOST, _GCE_DEFAULT_MDS_IP] + # Environment variable GCE_METADATA_HOST is originally named # GCE_METADATA_ROOT. For compatibility reasons, here it checks # the new variable first; if not set, the system falls back @@ -40,15 +48,48 @@ _GCE_METADATA_HOST = os.getenv(environment_vars.GCE_METADATA_HOST, None) if not _GCE_METADATA_HOST: _GCE_METADATA_HOST = os.getenv( - environment_vars.GCE_METADATA_ROOT, "metadata.google.internal" + environment_vars.GCE_METADATA_ROOT, _GCE_DEFAULT_HOST + ) + + +def _validate_gce_mds_configured_environment(): + """Validates the GCE metadata server environment configuration for mTLS. + + mTLS is only supported when connecting to the default metadata server hosts. + If we are in strict mode (which requires mTLS), ensure that the metadata host + has not been overridden to a custom value (which means mTLS will fail). + + Raises: + google.auth.exceptions.MutualTLSChannelError: if the environment + configuration is invalid for mTLS. + """ + mode = _mtls._parse_mds_mode() + if mode == _mtls.MdsMtlsMode.STRICT: + # mTLS is only supported when connecting to the default metadata host. + # Raise an exception if we are in strict mode (which requires mTLS) + # but the metadata host has been overridden to a custom MDS. (which means mTLS will fail) + if _GCE_METADATA_HOST not in _GCE_DEFAULT_MDS_HOSTS: + raise exceptions.MutualTLSChannelError( + "Mutual TLS is required, but the metadata host has been overridden. " + "mTLS is only supported when connecting to the default metadata host." + ) + + +def _get_metadata_root(use_mtls: bool): + """Returns the metadata server root URL.""" + + scheme = "https" if use_mtls else "http" + return "{}://{}/computeMetadata/v1/".format(scheme, _GCE_METADATA_HOST) + + +def _get_metadata_ip_root(use_mtls: bool): + """Returns the metadata server IP root URL.""" + scheme = "https" if use_mtls else "http" + return "{}://{}".format( + scheme, os.getenv(environment_vars.GCE_METADATA_IP, _GCE_DEFAULT_MDS_IP) ) -_METADATA_ROOT = "http://{}/computeMetadata/v1/".format(_GCE_METADATA_HOST) -# This is used to ping the metadata server, it avoids the cost of a DNS -# lookup. -_METADATA_IP_ROOT = "http://{}".format( - os.getenv(environment_vars.GCE_METADATA_IP, "169.254.169.254") -) + _METADATA_FLAVOR_HEADER = "metadata-flavor" _METADATA_FLAVOR_VALUE = "Google" _METADATA_HEADERS = {_METADATA_FLAVOR_HEADER: _METADATA_FLAVOR_VALUE} @@ -102,6 +143,33 @@ def detect_gce_residency_linux(): return content.startswith(_GOOGLE) +def _prepare_request_for_mds(request, use_mtls=False) -> None: + """Prepares a request for the metadata server. + + This will check if mTLS should be used and mount the mTLS adapter if needed. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + use_mtls (bool): Whether to use mTLS for the request. + + Returns: + google.auth.transport.Request: A request object to use. + If mTLS is enabled, the request will have the mTLS adapter mounted. + Otherwise, the original request will be returned unchanged. + """ + # Only modify the request if mTLS is enabled. + if use_mtls: + # Ensure the request has a session to mount the adapter to. + if not request.session: + request.session = requests.Session() + + adapter = _mtls.MdsMtlsAdapter() + # Mount the adapter for all default GCE metadata hosts. + for host in _GCE_DEFAULT_MDS_HOSTS: + request.session.mount(f"https://{host}/", adapter) + + def ping(request, timeout=_METADATA_DEFAULT_TIMEOUT, retry_count=3): """Checks to see if the metadata server is available. @@ -115,6 +183,8 @@ def ping(request, timeout=_METADATA_DEFAULT_TIMEOUT, retry_count=3): Returns: bool: True if the metadata server is reachable, False otherwise. """ + use_mtls = _mtls.should_use_mds_mtls() + _prepare_request_for_mds(request, use_mtls=use_mtls) # NOTE: The explicit ``timeout`` is a workaround. The underlying # issue is that resolving an unknown host on some networks will take # 20-30 seconds; making this timeout short fixes the issue, but @@ -129,7 +199,10 @@ def ping(request, timeout=_METADATA_DEFAULT_TIMEOUT, retry_count=3): for attempt in backoff: try: response = request( - url=_METADATA_IP_ROOT, method="GET", headers=headers, timeout=timeout + url=_get_metadata_ip_root(use_mtls), + method="GET", + headers=headers, + timeout=timeout, ) metadata_flavor = response.headers.get(_METADATA_FLAVOR_HEADER) @@ -153,7 +226,7 @@ def ping(request, timeout=_METADATA_DEFAULT_TIMEOUT, retry_count=3): def get( request, path, - root=_METADATA_ROOT, + root=None, params=None, recursive=False, retry_count=5, @@ -168,7 +241,8 @@ def get( HTTP requests. path (str): The resource to retrieve. For example, ``'instance/service-accounts/default'``. - root (str): The full path to the metadata server root. + root (Optional[str]): The full path to the metadata server root. If not + provided, the default root will be used. params (Optional[Mapping[str, str]]): A mapping of query parameter keys to values. recursive (bool): Whether to do a recursive query of metadata. See @@ -189,7 +263,24 @@ def get( Raises: google.auth.exceptions.TransportError: if an error occurred while retrieving metadata. + google.auth.exceptions.MutualTLSChannelError: if using mtls and the environment + configuration is invalid for mTLS (for example, the metadata host + has been overridden in strict mTLS mode). + """ + use_mtls = _mtls.should_use_mds_mtls() + # Prepare the request object for mTLS if needed. + # This will create a new request object with the mTLS session. + _prepare_request_for_mds(request, use_mtls=use_mtls) + + if root is None: + root = _get_metadata_root(use_mtls) + + # mTLS is only supported when connecting to the default metadata host. + # If we are in strict mode (which requires mTLS), ensure that the metadata host + # has not been overridden to a non-default host value (which means mTLS will fail). + _validate_gce_mds_configured_environment() + base_url = urljoin(root, path) query_params = {} if params is None else params diff --git a/packages/google-auth/google/auth/compute_engine/_mtls.py b/packages/google-auth/google/auth/compute_engine/_mtls.py new file mode 100644 index 000000000000..6525dd03e1bd --- /dev/null +++ b/packages/google-auth/google/auth/compute_engine/_mtls.py @@ -0,0 +1,164 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +"""Mutual TLS for Google Compute Engine metadata server.""" + +from dataclasses import dataclass, field +import enum +import logging +import os +from pathlib import Path +import ssl +from urllib.parse import urlparse, urlunparse + +import requests +from requests.adapters import HTTPAdapter + +from google.auth import environment_vars, exceptions + + +_LOGGER = logging.getLogger(__name__) + +_WINDOWS_OS_NAME = "nt" + +# MDS mTLS certificate paths based on OS. +# Documentation to well known locations can be found at: +# https://cloud.google.com/compute/docs/metadata/overview#https-mds-certificates +_WINDOWS_MTLS_COMPONENTS_BASE_PATH = Path("C:/ProgramData/Google/ComputeEngine") +_MTLS_COMPONENTS_BASE_PATH = Path("/run/google-mds-mtls") + + +def _get_mds_root_crt_path(): + if os.name == _WINDOWS_OS_NAME: + return _WINDOWS_MTLS_COMPONENTS_BASE_PATH / "mds-mtls-root.crt" + else: + return _MTLS_COMPONENTS_BASE_PATH / "root.crt" + + +def _get_mds_client_combined_cert_path(): + if os.name == _WINDOWS_OS_NAME: + return _WINDOWS_MTLS_COMPONENTS_BASE_PATH / "mds-mtls-client.key" + else: + return _MTLS_COMPONENTS_BASE_PATH / "client.key" + + +@dataclass +class MdsMtlsConfig: + ca_cert_path: Path = field( + default_factory=_get_mds_root_crt_path + ) # path to CA certificate + client_combined_cert_path: Path = field( + default_factory=_get_mds_client_combined_cert_path + ) # path to file containing client certificate and key + + +def _certs_exist(mds_mtls_config: MdsMtlsConfig): + """Checks if the mTLS certificates exist.""" + return os.path.exists(mds_mtls_config.ca_cert_path) and os.path.exists( + mds_mtls_config.client_combined_cert_path + ) + + +class MdsMtlsMode(enum.Enum): + """MDS mTLS mode. Used to configure connection behavior when connecting to MDS. + + STRICT: Always use HTTPS/mTLS. If certificates are not found locally, an error will be returned. + NONE: Never use mTLS. Requests will use regular HTTP. + DEFAULT: Use mTLS if certificates are found locally, otherwise use regular HTTP. + """ + + STRICT = "strict" + NONE = "none" + DEFAULT = "default" + + +def _parse_mds_mode(): + """Parses the GCE_METADATA_MTLS_MODE environment variable.""" + mode_str = os.environ.get( + environment_vars.GCE_METADATA_MTLS_MODE, "default" + ).lower() + try: + return MdsMtlsMode(mode_str) + except ValueError: + raise ValueError( + "Invalid value for GCE_METADATA_MTLS_MODE. Must be one of 'strict', 'none', or 'default'." + ) + + +def should_use_mds_mtls(mds_mtls_config: MdsMtlsConfig = MdsMtlsConfig()): + """Determines if mTLS should be used for the metadata server.""" + mode = _parse_mds_mode() + if mode == MdsMtlsMode.STRICT: + if not _certs_exist(mds_mtls_config): + raise exceptions.MutualTLSChannelError( + "mTLS certificates not found in strict mode." + ) + return True + elif mode == MdsMtlsMode.NONE: + return False + else: # Default mode + return _certs_exist(mds_mtls_config) + + +class MdsMtlsAdapter(HTTPAdapter): + """An HTTP adapter that uses mTLS for the metadata server.""" + + def __init__( + self, mds_mtls_config: MdsMtlsConfig = MdsMtlsConfig(), *args, **kwargs + ): + self.ssl_context = ssl.create_default_context() + self.ssl_context.load_verify_locations(cafile=mds_mtls_config.ca_cert_path) + self.ssl_context.load_cert_chain( + certfile=mds_mtls_config.client_combined_cert_path + ) + super(MdsMtlsAdapter, self).__init__(*args, **kwargs) + + def init_poolmanager(self, *args, **kwargs): + kwargs["ssl_context"] = self.ssl_context + return super(MdsMtlsAdapter, self).init_poolmanager(*args, **kwargs) + + def proxy_manager_for(self, *args, **kwargs): + kwargs["ssl_context"] = self.ssl_context + return super(MdsMtlsAdapter, self).proxy_manager_for(*args, **kwargs) + + def send(self, request, **kwargs): + # If we are in strict mode, always use mTLS (no HTTP fallback) + if _parse_mds_mode() == MdsMtlsMode.STRICT: + return super(MdsMtlsAdapter, self).send(request, **kwargs) + + # In default mode, attempt mTLS first, then fallback to HTTP on failure + try: + response = super(MdsMtlsAdapter, self).send(request, **kwargs) + response.raise_for_status() + return response + except ( + ssl.SSLError, + requests.exceptions.SSLError, + requests.exceptions.HTTPError, + ) as e: + _LOGGER.warning( + "mTLS connection to Compute Engine Metadata server failed. " + "Falling back to standard HTTP. Reason: %s", + e, + ) + # Fallback to standard HTTP + parsed_original_url = urlparse(request.url) + http_fallback_url = urlunparse(parsed_original_url._replace(scheme="http")) + request.url = http_fallback_url + + # Use a standard HTTPAdapter for the fallback + http_adapter = HTTPAdapter() + return http_adapter.send(request, **kwargs) diff --git a/packages/google-auth/google/auth/environment_vars.py b/packages/google-auth/google/auth/environment_vars.py index e5f3598e8147..5da3a7382391 100644 --- a/packages/google-auth/google/auth/environment_vars.py +++ b/packages/google-auth/google/auth/environment_vars.py @@ -60,6 +60,12 @@ """Environment variable providing an alternate ip:port to be used for ip-only GCE metadata requests.""" +GCE_METADATA_MTLS_MODE = "GCE_METADATA_MTLS_MODE" +"""Environment variable controlling the mTLS behavior for GCE metadata requests. + +Can be one of "strict", "none", or "default". +""" + GOOGLE_API_USE_CLIENT_CERTIFICATE = "GOOGLE_API_USE_CLIENT_CERTIFICATE" """Environment variable controlling whether to use client certificate or not. diff --git a/packages/google-auth/tests/compute_engine/test__metadata.py b/packages/google-auth/tests/compute_engine/test__metadata.py index c90bc603a801..adb63f667a75 100644 --- a/packages/google-auth/tests/compute_engine/test__metadata.py +++ b/packages/google-auth/tests/compute_engine/test__metadata.py @@ -20,12 +20,14 @@ import mock import pytest # type: ignore +import requests from google.auth import _helpers from google.auth import environment_vars from google.auth import exceptions from google.auth import transport from google.auth.compute_engine import _metadata +from google.auth.transport import requests as google_auth_requests PATH = "instance/service-accounts/default" @@ -104,7 +106,7 @@ def test_ping_success(mock_metrics_header_value): request.assert_called_once_with( method="GET", - url=_metadata._METADATA_IP_ROOT, + url="http://169.254.169.254", headers=MDS_PING_REQUEST_HEADER, timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) @@ -118,7 +120,7 @@ def test_ping_success_retry(mock_metrics_header_value): request.assert_called_with( method="GET", - url=_metadata._METADATA_IP_ROOT, + url="http://169.254.169.254", headers=MDS_PING_REQUEST_HEADER, timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) @@ -172,7 +174,7 @@ def test_get_success_json(): request.assert_called_once_with( method="GET", - url=_metadata._METADATA_ROOT + PATH, + url="http://metadata.google.internal/computeMetadata/v1/" + PATH, headers=_metadata._METADATA_HEADERS, timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) @@ -191,7 +193,7 @@ def test_get_success_json_content_type_charset(): request.assert_called_once_with( method="GET", - url=_metadata._METADATA_ROOT + PATH, + url="http://metadata.google.internal/computeMetadata/v1/" + PATH, headers=_metadata._METADATA_HEADERS, timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) @@ -211,7 +213,7 @@ def test_get_success_retry(mock_sleep): request.assert_called_with( method="GET", - url=_metadata._METADATA_ROOT + PATH, + url="http://metadata.google.internal/computeMetadata/v1/" + PATH, headers=_metadata._METADATA_HEADERS, timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) @@ -227,7 +229,7 @@ def test_get_success_text(): request.assert_called_once_with( method="GET", - url=_metadata._METADATA_ROOT + PATH, + url="http://metadata.google.internal/computeMetadata/v1/" + PATH, headers=_metadata._METADATA_HEADERS, timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) @@ -243,7 +245,9 @@ def test_get_success_params(): request.assert_called_once_with( method="GET", - url=_metadata._METADATA_ROOT + PATH + "?recursive=true", + url="http://metadata.google.internal/computeMetadata/v1/" + + PATH + + "?recursive=true", headers=_metadata._METADATA_HEADERS, timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) @@ -258,7 +262,9 @@ def test_get_success_recursive_and_params(): request.assert_called_once_with( method="GET", - url=_metadata._METADATA_ROOT + PATH + "?recursive=true", + url="http://metadata.google.internal/computeMetadata/v1/" + + PATH + + "?recursive=true", headers=_metadata._METADATA_HEADERS, timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) @@ -273,7 +279,9 @@ def test_get_success_recursive(): request.assert_called_once_with( method="GET", - url=_metadata._METADATA_ROOT + PATH + "?recursive=true", + url="http://metadata.google.internal/computeMetadata/v1/" + + PATH + + "?recursive=true", headers=_metadata._METADATA_HEADERS, timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) @@ -322,6 +330,21 @@ def test_get_success_custom_root_old_variable(): ) +def test_get_success_custom_root(): + request = make_request("{}", headers={"content-type": "application/json"}) + + fake_root = "http://another.metadata.service" + + _metadata.get(request, PATH, root=fake_root) + + request.assert_called_once_with( + method="GET", + url="{}/{}".format(fake_root, PATH), + headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, + ) + + @mock.patch("time.sleep", return_value=None) def test_get_failure(mock_sleep): request = make_request("Metadata error", status=http_client.NOT_FOUND) @@ -333,7 +356,7 @@ def test_get_failure(mock_sleep): request.assert_called_once_with( method="GET", - url=_metadata._METADATA_ROOT + PATH, + url="http://metadata.google.internal/computeMetadata/v1/" + PATH, headers=_metadata._METADATA_HEADERS, timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) @@ -346,7 +369,7 @@ def test_get_return_none_for_not_found_error(): request.assert_called_once_with( method="GET", - url=_metadata._METADATA_ROOT + PATH, + url="http://metadata.google.internal/computeMetadata/v1/" + PATH, headers=_metadata._METADATA_HEADERS, timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) @@ -366,7 +389,7 @@ def test_get_failure_connection_failed(mock_sleep): request.assert_called_with( method="GET", - url=_metadata._METADATA_ROOT + PATH, + url="http://metadata.google.internal/computeMetadata/v1/" + PATH, headers=_metadata._METADATA_HEADERS, timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) @@ -385,7 +408,7 @@ def test_get_too_many_requests_retryable_error_failure(): request.assert_called_with( method="GET", - url=_metadata._METADATA_ROOT + PATH, + url="http://metadata.google.internal/computeMetadata/v1/" + PATH, headers=_metadata._METADATA_HEADERS, timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) @@ -402,7 +425,7 @@ def test_get_failure_bad_json(): request.assert_called_once_with( method="GET", - url=_metadata._METADATA_ROOT + PATH, + url="http://metadata.google.internal/computeMetadata/v1/" + PATH, headers=_metadata._METADATA_HEADERS, timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) @@ -416,7 +439,7 @@ def test_get_project_id(): request.assert_called_once_with( method="GET", - url=_metadata._METADATA_ROOT + "project/project-id", + url="http://metadata.google.internal/computeMetadata/v1/project/project-id", headers=_metadata._METADATA_HEADERS, timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) @@ -432,7 +455,7 @@ def test_get_universe_domain_success(): request.assert_called_once_with( method="GET", - url=_metadata._METADATA_ROOT + "universe/universe-domain", + url="http://metadata.google.internal/computeMetadata/v1/universe/universe-domain", headers=_metadata._METADATA_HEADERS, timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) @@ -446,7 +469,7 @@ def test_get_universe_domain_success_empty_response(): request.assert_called_once_with( method="GET", - url=_metadata._METADATA_ROOT + "universe/universe-domain", + url="http://metadata.google.internal/computeMetadata/v1/universe/universe-domain", headers=_metadata._METADATA_HEADERS, timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) @@ -462,7 +485,7 @@ def test_get_universe_domain_not_found(): request.assert_called_once_with( method="GET", - url=_metadata._METADATA_ROOT + "universe/universe-domain", + url="http://metadata.google.internal/computeMetadata/v1/universe/universe-domain", headers=_metadata._METADATA_HEADERS, timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) @@ -483,7 +506,7 @@ def test_get_universe_domain_retryable_error_failure(): request.assert_called_with( method="GET", - url=_metadata._METADATA_ROOT + "universe/universe-domain", + url="http://metadata.google.internal/computeMetadata/v1/universe/universe-domain", headers=_metadata._METADATA_HEADERS, timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) @@ -526,13 +549,13 @@ def request(self, *args, **kwargs): request_error.assert_called_once_with( method="GET", - url=_metadata._METADATA_ROOT + "universe/universe-domain", + url="http://metadata.google.internal/computeMetadata/v1/universe/universe-domain", headers=_metadata._METADATA_HEADERS, timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) request_ok.assert_called_once_with( method="GET", - url=_metadata._METADATA_ROOT + "universe/universe-domain", + url="http://metadata.google.internal/computeMetadata/v1/universe/universe-domain", headers=_metadata._METADATA_HEADERS, timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) @@ -552,7 +575,7 @@ def test_get_universe_domain_other_error(): request.assert_called_once_with( method="GET", - url=_metadata._METADATA_ROOT + "universe/universe-domain", + url="http://metadata.google.internal/computeMetadata/v1/universe/universe-domain", headers=_metadata._METADATA_HEADERS, timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) @@ -574,7 +597,7 @@ def test_get_service_account_token(utcnow, mock_metrics_header_value): request.assert_called_once_with( method="GET", - url=_metadata._METADATA_ROOT + PATH + "/token", + url="http://metadata.google.internal/computeMetadata/v1/" + PATH + "/token", headers={ "metadata-flavor": "Google", "x-goog-api-client": ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, @@ -601,7 +624,10 @@ def test_get_service_account_token_with_scopes_list(utcnow, mock_metrics_header_ request.assert_called_once_with( method="GET", - url=_metadata._METADATA_ROOT + PATH + "/token" + "?scopes=foo%2Cbar", + url="http://metadata.google.internal/computeMetadata/v1/" + + PATH + + "/token" + + "?scopes=foo%2Cbar", headers={ "metadata-flavor": "Google", "x-goog-api-client": ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, @@ -630,7 +656,10 @@ def test_get_service_account_token_with_scopes_string( request.assert_called_once_with( method="GET", - url=_metadata._METADATA_ROOT + PATH + "/token" + "?scopes=foo%2Cbar", + url="http://metadata.google.internal/computeMetadata/v1/" + + PATH + + "/token" + + "?scopes=foo%2Cbar", headers={ "metadata-flavor": "Google", "x-goog-api-client": ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, @@ -651,9 +680,144 @@ def test_get_service_account_info(): request.assert_called_once_with( method="GET", - url=_metadata._METADATA_ROOT + PATH + "/?recursive=true", + url="http://metadata.google.internal/computeMetadata/v1/" + + PATH + + "/?recursive=true", headers=_metadata._METADATA_HEADERS, timeout=_metadata._METADATA_DEFAULT_TIMEOUT, ) assert info[key] == value + + +def test__get_metadata_root_mtls(): + assert ( + _metadata._get_metadata_root(use_mtls=True) + == "https://metadata.google.internal/computeMetadata/v1/" + ) + + +def test__get_metadata_root_no_mtls(): + assert ( + _metadata._get_metadata_root(use_mtls=False) + == "http://metadata.google.internal/computeMetadata/v1/" + ) + + +def test__get_metadata_ip_root_mtls(): + assert _metadata._get_metadata_ip_root(use_mtls=True) == "https://169.254.169.254" + + +def test__get_metadata_ip_root_no_mtls(): + assert _metadata._get_metadata_ip_root(use_mtls=False) == "http://169.254.169.254" + + +@mock.patch("google.auth.compute_engine._mtls.MdsMtlsAdapter") +def test__prepare_request_for_mds_mtls(mock_mds_mtls_adapter): + request = google_auth_requests.Request(mock.create_autospec(requests.Session)) + _metadata._prepare_request_for_mds(request, use_mtls=True) + mock_mds_mtls_adapter.assert_called_once() + assert request.session.mount.call_count == len(_metadata._GCE_DEFAULT_MDS_HOSTS) + + +def test__prepare_request_for_mds_no_mtls(): + request = mock.Mock() + _metadata._prepare_request_for_mds(request, use_mtls=False) + request.session.mount.assert_not_called() + + +@mock.patch("google.auth.metrics.mds_ping", return_value=MDS_PING_METRICS_HEADER_VALUE) +@mock.patch("google.auth.compute_engine._mtls.MdsMtlsAdapter") +@mock.patch("google.auth.compute_engine._mtls.should_use_mds_mtls", return_value=True) +@mock.patch("google.auth.transport.requests.Request") +def test_ping_mtls( + mock_request, mock_should_use_mtls, mock_mds_mtls_adapter, mock_metrics_header_value +): + response = mock.create_autospec(transport.Response, instance=True) + response.status = http_client.OK + response.headers = _metadata._METADATA_HEADERS + mock_request.return_value = response + + assert _metadata.ping(mock_request) + + mock_should_use_mtls.assert_called_once() + mock_mds_mtls_adapter.assert_called_once() + mock_request.assert_called_once_with( + url="https://169.254.169.254", + method="GET", + headers=MDS_PING_REQUEST_HEADER, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, + ) + + +@mock.patch("google.auth.compute_engine._mtls.MdsMtlsAdapter") +@mock.patch("google.auth.compute_engine._mtls.should_use_mds_mtls", return_value=True) +@mock.patch("google.auth.transport.requests.Request") +def test_get_mtls(mock_request, mock_should_use_mtls, mock_mds_mtls_adapter): + response = mock.create_autospec(transport.Response, instance=True) + response.status = http_client.OK + response.data = _helpers.to_bytes("{}") + response.headers = {"content-type": "application/json"} + mock_request.return_value = response + + _metadata.get(mock_request, "some/path") + + mock_should_use_mtls.assert_called_once() + mock_mds_mtls_adapter.assert_called_once() + mock_request.assert_called_once_with( + url="https://metadata.google.internal/computeMetadata/v1/some/path", + method="GET", + headers=_metadata._METADATA_HEADERS, + timeout=_metadata._METADATA_DEFAULT_TIMEOUT, + ) + + +@pytest.mark.parametrize( + "mds_mode, metadata_host, expect_exception", + [ + (_metadata._mtls.MdsMtlsMode.STRICT, _metadata._GCE_DEFAULT_HOST, False), + (_metadata._mtls.MdsMtlsMode.STRICT, _metadata._GCE_DEFAULT_MDS_IP, False), + (_metadata._mtls.MdsMtlsMode.STRICT, "custom.host", True), + (_metadata._mtls.MdsMtlsMode.NONE, "custom.host", False), + (_metadata._mtls.MdsMtlsMode.DEFAULT, _metadata._GCE_DEFAULT_HOST, False), + (_metadata._mtls.MdsMtlsMode.DEFAULT, _metadata._GCE_DEFAULT_MDS_IP, False), + ], +) +@mock.patch("google.auth.compute_engine._mtls._parse_mds_mode") +def test_validate_gce_mds_configured_environment( + mock_parse_mds_mode, mds_mode, metadata_host, expect_exception +): + mock_parse_mds_mode.return_value = mds_mode + with mock.patch( + "google.auth.compute_engine._metadata._GCE_METADATA_HOST", new=metadata_host + ): + if expect_exception: + with pytest.raises(exceptions.MutualTLSChannelError): + _metadata._validate_gce_mds_configured_environment() + else: + _metadata._validate_gce_mds_configured_environment() + mock_parse_mds_mode.assert_called_once() + + +@mock.patch("google.auth.compute_engine._mtls.MdsMtlsAdapter") +def test__prepare_request_for_mds_mtls_session_exists(mock_mds_mtls_adapter): + mock_session = mock.create_autospec(requests.Session) + request = google_auth_requests.Request(mock_session) + _metadata._prepare_request_for_mds(request, use_mtls=True) + + mock_mds_mtls_adapter.assert_called_once() + assert mock_session.mount.call_count == len(_metadata._GCE_DEFAULT_MDS_HOSTS) + + +@mock.patch("google.auth.compute_engine._mtls.MdsMtlsAdapter") +def test__prepare_request_for_mds_mtls_no_session(mock_mds_mtls_adapter): + request = google_auth_requests.Request(None) + # Explicitly set session to None to avoid a session being created in the Request constructor. + request.session = None + + with mock.patch("requests.Session") as mock_session_class: + _metadata._prepare_request_for_mds(request, use_mtls=True) + + mock_session_class.assert_called_once() + mock_mds_mtls_adapter.assert_called_once() + assert request.session.mount.call_count == len(_metadata._GCE_DEFAULT_MDS_HOSTS) diff --git a/packages/google-auth/tests/compute_engine/test__mtls.py b/packages/google-auth/tests/compute_engine/test__mtls.py new file mode 100644 index 000000000000..fdd61a07d03d --- /dev/null +++ b/packages/google-auth/tests/compute_engine/test__mtls.py @@ -0,0 +1,288 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from pathlib import Path + +import mock +import pytest # type: ignore +import requests + +from google.auth import environment_vars, exceptions +from google.auth.compute_engine import _mtls + + +@pytest.fixture +def mock_mds_mtls_config(): + return _mtls.MdsMtlsConfig( + ca_cert_path=Path("/fake/ca.crt"), + client_combined_cert_path=Path("/fake/client.key"), + ) + + +@mock.patch("os.name", "nt") +def test__MdsMtlsConfig_windows_defaults(): + config = _mtls.MdsMtlsConfig() + assert ( + str(config.ca_cert_path) + == "C:/ProgramData/Google/ComputeEngine/mds-mtls-root.crt" + ) + assert ( + str(config.client_combined_cert_path) + == "C:/ProgramData/Google/ComputeEngine/mds-mtls-client.key" + ) + + +@mock.patch("os.name", "posix") +def test__MdsMtlsConfig_non_windows_defaults(): + config = _mtls.MdsMtlsConfig() + assert str(config.ca_cert_path) == "/run/google-mds-mtls/root.crt" + assert str(config.client_combined_cert_path) == "/run/google-mds-mtls/client.key" + + +def test__parse_mds_mode_default(monkeypatch): + monkeypatch.delenv(environment_vars.GCE_METADATA_MTLS_MODE, raising=False) + assert _mtls._parse_mds_mode() == _mtls.MdsMtlsMode.DEFAULT + + +@pytest.mark.parametrize( + "mode_str, expected_mode", + [ + ("strict", _mtls.MdsMtlsMode.STRICT), + ("none", _mtls.MdsMtlsMode.NONE), + ("default", _mtls.MdsMtlsMode.DEFAULT), + ("STRICT", _mtls.MdsMtlsMode.STRICT), + ], +) +def test__parse_mds_mode_valid(monkeypatch, mode_str, expected_mode): + monkeypatch.setenv(environment_vars.GCE_METADATA_MTLS_MODE, mode_str) + assert _mtls._parse_mds_mode() == expected_mode + + +def test__parse_mds_mode_invalid(monkeypatch): + monkeypatch.setenv(environment_vars.GCE_METADATA_MTLS_MODE, "invalid_mode") + with pytest.raises(ValueError): + _mtls._parse_mds_mode() + + +@mock.patch("os.path.exists") +def test__certs_exist_true(mock_exists, mock_mds_mtls_config): + mock_exists.return_value = True + assert _mtls._certs_exist(mock_mds_mtls_config) is True + + +@mock.patch("os.path.exists") +def test__certs_exist_false(mock_exists, mock_mds_mtls_config): + mock_exists.return_value = False + assert _mtls._certs_exist(mock_mds_mtls_config) is False + + +@pytest.mark.parametrize( + "mtls_mode, certs_exist, expected_result", + [ + ("strict", True, True), + ("strict", False, exceptions.MutualTLSChannelError), + ("none", True, False), + ("none", False, False), + ("default", True, True), + ("default", False, False), + ], +) +@mock.patch("os.path.exists") +def test_should_use_mds_mtls( + mock_exists, monkeypatch, mtls_mode, certs_exist, expected_result +): + monkeypatch.setenv(environment_vars.GCE_METADATA_MTLS_MODE, mtls_mode) + mock_exists.return_value = certs_exist + + if isinstance(expected_result, type) and issubclass(expected_result, Exception): + with pytest.raises(expected_result): + _mtls.should_use_mds_mtls() + else: + assert _mtls.should_use_mds_mtls() is expected_result + + +@mock.patch("ssl.create_default_context") +def test_mds_mtls_adapter_init(mock_ssl_context, mock_mds_mtls_config): + adapter = _mtls.MdsMtlsAdapter(mock_mds_mtls_config) + mock_ssl_context.assert_called_once() + adapter.ssl_context.load_verify_locations.assert_called_once_with( + cafile=mock_mds_mtls_config.ca_cert_path + ) + adapter.ssl_context.load_cert_chain.assert_called_once_with( + certfile=mock_mds_mtls_config.client_combined_cert_path + ) + + +@mock.patch("ssl.create_default_context") +@mock.patch("requests.adapters.HTTPAdapter.init_poolmanager") +def test_mds_mtls_adapter_init_poolmanager( + mock_init_poolmanager, mock_ssl_context, mock_mds_mtls_config +): + adapter = _mtls.MdsMtlsAdapter(mock_mds_mtls_config) + mock_init_poolmanager.assert_called_with( + 10, 10, block=False, ssl_context=adapter.ssl_context + ) + + +@mock.patch("ssl.create_default_context") +@mock.patch("requests.adapters.HTTPAdapter.proxy_manager_for") +def test_mds_mtls_adapter_proxy_manager_for( + mock_proxy_manager_for, mock_ssl_context, mock_mds_mtls_config +): + adapter = _mtls.MdsMtlsAdapter(mock_mds_mtls_config) + adapter.proxy_manager_for("test_proxy") + mock_proxy_manager_for.assert_called_once_with( + "test_proxy", ssl_context=adapter.ssl_context + ) + + +@mock.patch("requests.adapters.HTTPAdapter.send") # Patch the PARENT class method +@mock.patch("ssl.create_default_context") +def test_mds_mtls_adapter_session_request( + mock_ssl_context, mock_super_send, mock_mds_mtls_config +): + adapter = _mtls.MdsMtlsAdapter(mock_mds_mtls_config) + session = requests.Session() + session.mount("https://", adapter) + + # Setup the parent class send return value + response = requests.Response() + response.status_code = 200 + mock_super_send.return_value = response + + response = session.get("https://fake-mds.com") + + # Assert that the request was successful + assert response.status_code == 200 + mock_super_send.assert_called_once() + + +@mock.patch("requests.adapters.HTTPAdapter.send") +@mock.patch("google.auth.compute_engine._mtls._parse_mds_mode") +@mock.patch("ssl.create_default_context") +def test_mds_mtls_adapter_send_success( + mock_ssl_context, mock_parse_mds_mode, mock_super_send, mock_mds_mtls_config +): + """Test the explicit 'happy path' where mTLS succeeds without error.""" + mock_parse_mds_mode.return_value = _mtls.MdsMtlsMode.DEFAULT + adapter = _mtls.MdsMtlsAdapter(mock_mds_mtls_config) + + # Setup the parent class send return value to be successful (200 OK) + mock_response = requests.Response() + mock_response.status_code = 200 + mock_super_send.return_value = mock_response + + request = requests.Request(method="GET", url="https://fake-mds.com").prepare() + + # Call send directly + response = adapter.send(request) + + # Verify we got the response back and no fallback happened + assert response == mock_response + mock_super_send.assert_called_once() + + +@mock.patch("google.auth.compute_engine._mtls.HTTPAdapter") +@mock.patch("google.auth.compute_engine._mtls._parse_mds_mode") +@mock.patch("ssl.create_default_context") +def test_mds_mtls_adapter_send_fallback_default_mode( + mock_ssl_context, mock_parse_mds_mode, mock_http_adapter_class, mock_mds_mtls_config +): + mock_parse_mds_mode.return_value = _mtls.MdsMtlsMode.DEFAULT + adapter = _mtls.MdsMtlsAdapter(mock_mds_mtls_config) + + mock_fallback_send = mock.Mock() + mock_http_adapter_class.return_value.send = mock_fallback_send + + # Simulate SSLError on the super().send() call + with mock.patch( + "requests.adapters.HTTPAdapter.send", side_effect=requests.exceptions.SSLError + ): + request = requests.Request(method="GET", url="https://fake-mds.com").prepare() + adapter.send(request) + + # Check that fallback to HTTPAdapter.send occurred + mock_http_adapter_class.assert_called_once() + mock_fallback_send.assert_called_once() + fallback_request = mock_fallback_send.call_args[0][0] + assert fallback_request.url == "http://fake-mds.com/" + + +@mock.patch("google.auth.compute_engine._mtls.HTTPAdapter") +@mock.patch("google.auth.compute_engine._mtls._parse_mds_mode") +@mock.patch("ssl.create_default_context") +def test_mds_mtls_adapter_send_fallback_http_error( + mock_ssl_context, mock_parse_mds_mode, mock_http_adapter_class, mock_mds_mtls_config +): + mock_parse_mds_mode.return_value = _mtls.MdsMtlsMode.DEFAULT + adapter = _mtls.MdsMtlsAdapter(mock_mds_mtls_config) + + mock_fallback_send = mock.Mock() + mock_http_adapter_class.return_value.send = mock_fallback_send + + # Simulate HTTPError on the super().send() call + mock_mtls_response = requests.Response() + mock_mtls_response.status_code = 404 + with mock.patch( + "requests.adapters.HTTPAdapter.send", return_value=mock_mtls_response + ): + request = requests.Request(method="GET", url="https://fake-mds.com").prepare() + adapter.send(request) + + # Check that fallback to HTTPAdapter.send occurred + mock_http_adapter_class.assert_called_once() + mock_fallback_send.assert_called_once() + fallback_request = mock_fallback_send.call_args[0][0] + assert fallback_request.url == "http://fake-mds.com/" + + +@mock.patch("requests.adapters.HTTPAdapter.send") +@mock.patch("google.auth.compute_engine._mtls._parse_mds_mode") +@mock.patch("ssl.create_default_context") +def test_mds_mtls_adapter_send_no_fallback_other_exception( + mock_ssl_context, mock_parse_mds_mode, mock_http_adapter_send, mock_mds_mtls_config +): + mock_parse_mds_mode.return_value = _mtls.MdsMtlsMode.DEFAULT + adapter = _mtls.MdsMtlsAdapter(mock_mds_mtls_config) + + # Simulate HTTP exception + with mock.patch( + "requests.adapters.HTTPAdapter.send", + side_effect=requests.exceptions.ConnectionError, + ): + request = requests.Request(method="GET", url="https://fake-mds.com").prepare() + with pytest.raises(requests.exceptions.ConnectionError): + adapter.send(request) + + mock_http_adapter_send.assert_not_called() + + +@mock.patch("google.auth.compute_engine._mtls._parse_mds_mode") +@mock.patch("ssl.create_default_context") +def test_mds_mtls_adapter_send_no_fallback_strict_mode( + mock_ssl_context, mock_parse_mds_mode, mock_mds_mtls_config +): + mock_parse_mds_mode.return_value = _mtls.MdsMtlsMode.STRICT + adapter = _mtls.MdsMtlsAdapter(mock_mds_mtls_config) + + # Simulate SSLError on the super().send() call + with mock.patch( + "requests.adapters.HTTPAdapter.send", side_effect=requests.exceptions.SSLError + ): + request = requests.Request(method="GET", url="https://fake-mds.com").prepare() + with pytest.raises(requests.exceptions.SSLError): + adapter.send(request) From 01c25c120d61ed8af42e34b31b18d7477fead13d Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Tue, 25 Nov 2025 13:33:14 -0800 Subject: [PATCH 955/966] chore(tests): update secret (#1876) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 6a4f1f3081812426117834f30ce315cba1f1535f..4b1108092b18953009508d6d4932fb35290b90ec 100644 GIT binary patch literal 10324 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 literal 10324 zcmV-aD67{BB>?tKRTDxzIz309grisuK!jo`<(3212>Pbm48E%}_(^^Y7bFs@Pyl>7 ziXNYgO;n(8A9?5#t=Czr|B@&8O+eBkqvgG&nD-jN26g9C6X^d)RMmn+gi0TIUyXJ> ze_G&b*LLR^8@=UW0*|rH(J{5`Uj9ghG;9;bTUB}7vh9#_xoYgre|dffS&a4xPAR2T zT@v&VMR<<`>tX0vNECTCO2dtLED#76l&&$6Vi65Vm~nx_SDPBDk{iY7mj45M{I`FMzsx@%cD#w}h64bD)_ z<`EXcW_uBd*UFOYc&TbrVIX{F*sj&_WEehDqU=*PmSUXKz>?8R7X$k*ueN{Fch&2u zkz)TMC)v49-_K|%m}MIBKR@b3q$HvflxM#jiA58BIhvgU|42}3FX$+-C%~RmoTg6G z)(Ho#PgycSe2SwQ6pjBNV{wqcGn@O@+6KN~>&pP;qO3d);Q8Ed%9_0}HHRKLy1|B@ z{W(($oZ3FGLRMqOZ}wscbxDx4jV5x4?zSBNW^0-3P6gWZ3`TYwpr0KW0eU<34?fan z=WUedo{iYT9LcyM2c3*cr$1M6eq;Dql4^>y(sNMAQU?32e$FAyI&f~K-QG|C z$C?LVSZOo;`r5Wz6{-lkI6oexi|)M;0qZi;ZzVg6(TNxz*>nkC>`%yfVzp_plGHEa z@IVA8lifz2lEGbI$Cv$|s3Fy>7iG=+&#c+Y4RtY;w3dNPHvR&Fk%7!QnA;p~P~^JkIOwQ?a0 zAd``f1BVr!o1X+L#6J8t)A^t~?OlyA&M=|FF$*r=A0zI>@|7!BFDs{~6_1gcm(;M* zE;DRKW4@q5@%s2t8&y#h9Tmq!dclKCVFClK2_CbGBtRG&+H@CjoGWz{bKrHmtA2PS z5@uVWXZVrIYGb>w&LcG?I|4l_b8j2T7*BX#p7jwx-bJQo3GXF}^Y*Tt@K2p#9y5Bb z$lhze&XZ<`iWu?}DhUA*Cty*EKIC} z&~8;C6Sm%gDJATJ4}y>AewnC>EFPhdj@#gup{C7Mj}=AUxotvCE4c#)GzyDsPieX4 zJ5$1m2IJH5)l)_1s@^M7QFt6BI5(fmoAAyMA;s106e|4Gan?jBi!i zSn!@*5U$ zj;gV61b9t-9^_(!pnu0-<#eR#49RG=q=1%FW7o=jvcxVYw9J<5I1PQ$QKnWLu;5Ez znDZ?}$Zv~8f|>1HwyNaVZ;3?%yPj!cZ_nZFN#K=vmU*$S6ugO&8q%U?M?H1|Q{k9Y ziD+r|gBk9gN~3K@^co=-0_f$(fqH=`zaC2)KdfXF*daPTBX`{ zR9HHj>_2Bl#$yi`-MS%!Tj!5F>s(`}1rlOq-PYlTZpBQh>#{WG4esv{Tz0e+@UVX;LIU z;88ZtazSI*0>nY-IcfSKBB%iKsf0!jeL~Q)7Un|yWQ9-*lTR}(^Ib@%-ns%V0lKm*7q|x?giVMvu zB{rjPl@(u7x%qcLC2W!%w{`&EpkK37b4fD9IWbI}~ZG2@#NV;J2YP$6QtNEj4mjQZ=a-QEh5O&t$BVx&Fq{(m#TNCSpy8kDU z^j`MY=k>U4gY(mGt>(y^guYYQ_@pTpq(-6!8zlC4!I}^yUHz%=bAp30zLx0e>Nqw^ ze#R;V(mQW0H`bz~l8-v19Z233YI^7myE}}~*nF8RA`+hbI{owXrmT#@zZjCeDzK4{ zT;0x#whK%TO`e9ODeu*RjdMD~_Y?8&pZ*L`4L%*gPQ7llo-NdN)pO{6c+iD}JxPuf zq5?&`M`yd2&c5GTQ!VOuNQ#V7^pt?+$cyhh3Wa9}!mtQw_iU3JhyZ<jBp>%Xx6j7-Lt6p@FxAQS zSpF(m&`eW@^oRe@x~*;3Nnro%iJrvDcHJG+YKk@wnE3Y?`$W=nveF22ocQ6jIlQOs zY8G|l{Q4OQ9u@jPcCCx0pA^I(};lJpI-wqO~NK&#(otVo9yFL zBQoTd6`T~8gApQGS~uFTpntD!)7?{;GX!z%;j6behtePhr)VAxE1zAgC1$YWF|?$k zl*P{RJe@vzwwxzhCamk5-22Hlorj^bp!yUPh^GE@==Q4NZ7ep1SlQ9R&=IcVn5PGFrp}s;g(9~&SUS|trUJ=2S(76GBi?e0YZv*kOf~*FqahV0^`NUTX zqxqXHv-5Srkj^-SWfU}cOgQ3ffc_X0W_vKF(ukx|k}p3>i)XpJz0TDP>gPBn$Qd^X zz4sqqc8Rb&d;q0YNN22ntDL=SB`Zc&L3)>ewKzSu(*cb$R1jJ%j*L){<*&A@zWuzw z%kd;z;-_Kv1DHz|3Tkl-nX*j?)t*kOLs;ig;!RHmUwTFGJ)Q)ef({qJ>je0f@5NVM z#Xa!N1thNHQy|2SS5Y3t3Q6CgvD-pe@{JV&!s~boFpjd=!H~>kLN*R7O%0fH1C#*r z&}9AgNXE9G@Za;Yy#KeSi(JjmIT(~5XZCmmu$aUFqynF`QEGcL#VHw6w5tLc`EUKv zk%qAZyZx^tanS??qN};w!vTHByhf+`rmDNv`2>^7Qb_##Z;_Kx5x~n1-X!MQD>5Fw zvZ983A3j=6=+V#qDll(M=>XFC1h3I0MvCi%?4x7P?VY+7;EErdyS(}V1PVKwQWY^J zXPLJw&?ZCGcDYWo(b4v-^l11-SQ#_Qgz8T6Dv3a8aGk|3g` z`(wt+XLm2f%v})clhzdr5sB7ExaGt}R;PHcoJP-AW>4f@4F^v^g*<5$6~7eFGH=NdSJSKP6#BTG29=dG?|{RLPr8c`Cn zmr%X-dQpUvXjwMq{Y5=Ees?rSC%t5(K{67H*rKaP0_kK0q#e~a3D8H#FJF{7`azV6 zGLFtL(#y$R5XZiO5TyYP=Oj{qI;$h`WCU5lr)VixSFzsw?ZyHCu}6nVSD(JZOT2Y_ zKP;Q`z#m|i#z8}Zq&qGox$$PqUGh<|PK$|S z+^fHXI?{OZy{ud~WC3luAv0dD^y8f9>!iagBP}s)^`QG))NB^BqhO#`MWW6pl;DX3 z2{$}~HMg&Pll0Cd63f=%Xey;#1_}F&Oo2YGz>xl*~qc=m2g~}%0hb8Z; zBb({Ov(Rd7asgz-PfKnC zdmbxkirF;yAN*1+ce<8=WpZ+vRqwyAqAKmv_bygPR+WiJtpK#5_ji`1uamc8a5lBW zpiP@cNNMF29Y;dTyknIQm`jZRAkkoBd{|)X_yQ>ls?&*&e`sP6J!|cCunS4sJr2QL z2~<(cciX=3RmyMax&tAv9;GYQ4J@wkwl3pnjtu0!ebRKa0>K=VSkP+`;#)4001&_D zAbO^$5d5*XySc;8Roa*ObA1H;u+OZFHzq!6PI^%{8Q)NymTlqbV-k&h44`-+>+uG$ z4co9_eiYFl7zNP=@jJt3e`VvIg!_{0736(f?UlA$R9YK?Yj^%m`jwRCiGI;N4sy!#s0*TLrP+#~Q2w)jn00f^ zFJF^xDvE%IuDEW2*ygidA=IG6Xopv1R@(u%mfEcaT$81RJgv%``0g|ZO|wQN3Wnul zh>GtCOs+Ewoq#nRr}L3uuENPbBHinSMf~$J=zUDWiQnji>Xz`aL!;xgU4ZeEvK*ey z*H>bfnMmXAKmnFp7Q2Jxu*^^H<8<>!s-@k1N3T&`uIkbZS_^S}gYHobvuZ?wdc!26 zt|N0~tN~76npTo1S|y2H6|!ubE(yYPu`5d`FK5*V3dA`PY-*m>x|&~q5UlhLkJ@;s zalu@({-gm;c3^E8ah4@abZ1qtxn<;KCD=843V6K;z;HS7YIRDeU<8aGo(%VibVBz9 zI%G>U5+aj7X~Vu9KM|am>iFt`)WVaZALGw#P!RqVSV}E@Cj&6%Q@I8a< z46}lU$Q;JDH1(GpYEunJu#>M|sa>yktevVr&`DFFHZpNUBsfd(t#Zr$0TUwfc2@G? zWMiD4fsl7{oBrBbU9GSkbVm<)N|SfDG+}Z3BAgLqR2>c0RJ^j*nAN{?7<8o)KwKhV z|2+|R9*{Z93q;sk!GhJAVCC7rPCK*$lvU}J5aPkqiuR1&tY!Jk7oU zl?ka%>z1KHCmmHq(D?0Ta7o8nxCqQTK#(TCZ<Q&ysPv{RyjhMXbTgv~g zx-}PX0VF3V!a<|=$PWX2od()v{`i5>+wJGGpc(+-=V9OQ|MBa4UIa3M#HgfqW=mMB z8CI^s#jVu8GF?p0@L&k8hm|#5Ua1^`$4jvlz+~pS81~`GzDXbnw71V6zwD@hHiE_G zP}+h1_aSb&wQ4Zyg_+kz{DI4deFi5%(;Sv1Fs|EFRMGYJfae&e)~j<3L~0(7ZWbg0 zVcvEGg2SKEbMGOU`j=ix6OjXWhJRGtw_<65%S#DbG7{_FZE^0*!iY-oQD$8w3n8!b zWcG`zE8Mp!EpcNGhG&sn0&N!E4(87t77iUI6bAYBpzgH`ig)W<*(`Yb=~B>nepV|P zt!sIa!Jz3uZ)P`Qvw%qq9E~oje2x&xHsqF9{AvA)O5>&Np%HP}W&})q{TFscv@R34 zk+0a~=`U^KwY$Y8Z%__iP>B*!G_{Je8SZq{m#PO(Dncw)D#*b0hBE*T6vuUG$(Q0Y ze3cfLZ1o2-zh>Nwn)cfuuFg?~`~5A8HIOMk_*!>q2G67ruLuj9jd`r#j00+Qfu-zQ zL?>XixLU(7=5~ul7?KBzln7SartxowkkrxyV?VBf`;>|7RNOMzBc0ZoY5Q87>zbFHdz&RSpFNG%m zLj?p;Vj?for%7>MCfYmh!D41x$c7!1c|)_<4YrzBS}tvy@clu7Wr7(}H{n-BqqZcO z*S=1sm&iz1a{u#8j-JfY0Fu@o?4eonctqWF8H)Ii?q_9G9ppzoh4b#r^(qr$?0Qrk zG5+DxBB!>h#{7`OKWy6BuJDD84A5z(HKWPur_?P55p8RRhV4S?dK|h+1{w%?&zj4K zVld@UMmKh51XmWg9@>(`+{^JIX&L4$4x0!=1M5}4zcT>r%F+w9lXosGxeBGjv6fXs z_u!nEUm6)Frch@hgSP&c=vO)9+iz_~UJtiOek$u^9|pw_{Z>4@at$ZW{WLzpDV6Xxj+?VwFuB z-y#IMXo>D86$j(oB+SWFfC?~3<*yE;*w>2z^D{IcgN>&ndE0(g3}eV9vL1QnJ(MI} zYIvm<)owmX;ZafEMNF7$d)s`;t@yCpD5Mqnp56}*Sz?)KF?IuN=jvBDk^b<_FGi^h z8~MC`1`%9H%xSwZ^Ne1f(o+KwE5d!?Nqc}&hpVM-;b|wsx5!Zw=f$q!`5x5s{u5nf ziJwi9TSlj{1o{yi_`g^>WoGjUi?}N~u2frb6|pralh~#xRSZ9^KCHxB6&0P7elf@b zLi3ROqAgddfUN7rn@%Sawt8QC!~{%zEC!{24F)q{KAU6!}dzYx|Pg zffuOa?b;U8zv!}jQ>GgH8xvJ{^BGm%J;UcW%i3cZNB2~3AjviXEjGFfqRqFTW^1-F zpJ(Rs$9p=zX(zb*2BvKi_R12xFv-HKRJ;kawq2DeM8TRy0RZS@s44+=Hz@1@DDHfmz$mC%sXPCyw}Sd^g$vmeNn* z$)fxPKUJ~Zu`8o&CL*-RrFP&;)PO{F@hxLdHl9W~U%oA<#PZ?h-?wZY9uT=kbS8QT z3r?)`Sf{Bn^${K3zXUNLUb4>AwQq_2uBfWs>O(c=7RlXR5!ujNPea7cDo>OrY@rDq zz=ncnW(U;T+iTIGU~ZdDibTR)3E`}|DS8#v-Yezfb9~GcKBiE!NE($*d-`@Fk)3fYzYKOl~*dsLnec8tws4p{ z6O@e{UmDwRmx1VQH8)rA&(}M%+m#su^I84DGtJ5LhEYUdVzTgVChHVO^AR&&6C6D% z?g-mIKDy@H$-Bw>5PF@cTP*=cXwW{+)M;IEL`EnSj#Zyn%ph(bU0Y)H%d@Krp`Zt? zRw3V?XDzl-6;=8OwC+Ia!Ky*DwSM(Ajd#3*AOx!FckUmLBT{e43vUEL9<4L)m0ugnY}8`2K?wo_qMSkrq<*bnVCI|!as_vi!ZHV4l#yC=NtG%Z-&I|^vq{AH zEnx|U-*KM6^<0LPje&9Vtq040e*L#<^ldfiANG;}ZR=Ls9`N?&KZ$u+8n7O9sOhuI zr7hbs;I-|jdfM2~{F*9g+x2GExsWJN20lg+yYsnp@!dlSq%p=qu*)&8 z=he@d4oh9T_~(pB-_4FOsYd7pRrLM#hx&>%>i+zP*4dT7G(Pc5p;?PSd-1i(f;0mk zmXuG?e5-dEM&Ttc@y_I|d`e=%%*Y2YR&mSKaF9Sl7h@8x_qCSAtzPLbweb54Td(8X z9j3$|zM&E8+>#Qx=zKUb@;9WaX*+Sf z-V|MYgF`fwTXqyxG#Yw@(ahMx;J`pvR}+tO(+ukGxd*2iOtICG^!6YULXN$Y7Xw^r z)fe{cH@S{UYDmq~vX*6-Pd-YREtUO{1}mLN zpN{7d+B~MRmxW~1=iMB$BgRZVL&bX^!5sV46d3ht{u(@5JZ!(T^DuGz>y88eN%8;Z zR0=gv0v5JRtd8@y4ZIy_ps+nYO^4XpI){V97$j&Cb8rqq${F^}FF(%6rUDf}M4f=Q zu-r6BwAgJH)OJG$zb=etJ{{<9!3*3pRbC|vhG_2GpG>n!lJx`dIq`NWo=*RrPXLxbii?NI>i^9XZ=ftsm^ zq*&Q?Uph@>GL$!0TPYQ;YF#n&>EUI>R8q{7Psg-uqqbcv8VnL< z8FxIkb~0Fg=(s1OVn-2~iBIbfte~^1VQ*b|5)$sn-aml+MgzaYG9dDLnwwP2liMW) zx4@Pa*Aea$>-}0~+*`~7`b%JEMx%&RU6L_2ul*=IB8+6REf5oFjtX$^kes6}JRVS4 zBm4y-hOuNxhY)8sghC)cAk+)=zVXH#sDJN|Fi%^beN@IA`rnkN!n#7f8}{=qx3X;G zWM+LlJ?-m!56&Elv}!R$hfT9}=G|*7q{Y|xxKNx9lE5yl80lkUqNfZa#jhxsx1$Yu zxLXe#r<7Gj*cI|Q$_n=b6yXKTYMmBY>{tiXSk>=fYk{#h_2|C}vi|>g3Qo%Uu7wHW zg{|}J6NB1M^t?%>HgH?4SKZ>&kgg{^lU(s;{g15M*1kQc-54lfF3Qp^9`>EFx4-=x zNy3vMATHeaKW0(G0uh>`)mm50^r*N@;SERvlgjq$ zY}uree{+|*9T5l&qkz|rLfxbpqdy*%#WZaw9^=J~a$V#%z9et)Hx5apl7xmf*_YKh zT1$e*peMcV-vB)T6vMe;sv@Me?-*KD^Nh>TnN>HH*Zla?;+C?qf(+*y{Q`dKlRaSX zv1*9U#j#g*fpW|AQVpBnqife7f6r36yM7r{s7x&doq(?d#Sn3jJw@c!_1S+OrHbS( zh*)%w$0R4qwBj(s89|G=-1IzK095sRlK7O~4!>?0)#ldeF|Dlal$xzR=fSRQY^%;O zpE^q(nu9ir*1y4VIH9L&NmH4l&FG_-=KNjO?6b-4F8`AxLDlAM1*Kfk7RA;&)cFFH z)zu8-Zs7eIbqdI&u1MTlL=E?$sY@Jv*lb>BQ1APVHA7dUBb@`fn)-P4RXd zbqUXI&^C(O>9tZH3FF|xEALW*L4OV{LNNzL)afi##5F$1DSI9|#o;PAb;5OjsQ?3w z8Y00o^mvdvJzl+{ZzBA4akWH=V)Chf@>GmZ(MOd1h$zb?7`3>bUTvCmRM!!3WWi!a z`qS~|aJ@?sM!il9uEHXphCEd*f>JXEzv)Qt3;=)V=8ly6KheRpS}c|jsbi<{YziId zL&e(po@&gFJ5T4;B*$4O50~TOF#&$Y7Tr$)luLV-=8vqA?dI@Q%M^tHa9*etZzWOY z7MVV7TB>yJ?zNQOP4;Ub#*4$$w;~aKLq6DgrDw3e3LHK@ElU2aVF?=v4)ZaY2#G!Q zr(g+PyKEetnqkbvM-*^*k9Ivo|0@l}4Km%_F8kG(OvbP+mk+o0q%6b;{vy+8XIAhq znq*$D$DE~{#{(;u97gyC1d2+?rkQYp37N3jHe39=j z>#$v*1UVAh0A`UgSA)KTQl&y;`_}fKrf`AGrfzz|-b9v0eg-O%_oZPy9&5SzY%}js z%J~wm`i^57qC2YZT=zgF38Em(EeK}bmJb4Cb0ANR0jl^=R$_H;=rHz($=HD2AsRT1 z?H)vsIE&*)wg7^o? zD4WWso|RQb7brjtLZQaKWITh)0L*ya?J=u75plK4{ Date: Tue, 25 Nov 2025 14:47:19 -0800 Subject: [PATCH 956/966] feat: add ecdsa p-384 support (#1872) GDC (Google Distributed Cloud) needs to support ECDSA-P384 keys for compliance. This change creates an EsSigner and EsVerifier class that is capable of supporting both ECDSA-P256 and ECDSA-P384 keys for backwards compatibility. The EsSigner and EsVerifier classes are plumbed through to the GDC service accounts and are used to both sign and verify JWTs. This implementation was successfully tested against a GDC instance using both ECDSA-P256 and ECDSA-P384 keys. --------- Co-authored-by: Daniel Sanche --- .../google/auth/_service_account_info.py | 2 +- .../google-auth/google/auth/crypt/__init__.py | 17 +- packages/google-auth/google/auth/crypt/es.py | 221 ++++++++++++++++++ .../google-auth/google/auth/crypt/es256.py | 146 +----------- packages/google-auth/google/auth/jwt.py | 15 +- packages/google-auth/tests/crypt/test_es.py | 173 ++++++++++++++ .../tests/data/es384_privatekey.pem | 6 + .../tests/data/es384_public_cert.pem | 15 ++ .../tests/data/es384_publickey.pem | 5 + .../tests/data/es384_service_account.json | 9 + .../tests/test__service_account_info.py | 45 +++- packages/google-auth/tests/test_jwt.py | 36 ++- 12 files changed, 528 insertions(+), 162 deletions(-) create mode 100644 packages/google-auth/google/auth/crypt/es.py create mode 100644 packages/google-auth/tests/crypt/test_es.py create mode 100644 packages/google-auth/tests/data/es384_privatekey.pem create mode 100644 packages/google-auth/tests/data/es384_public_cert.pem create mode 100644 packages/google-auth/tests/data/es384_publickey.pem create mode 100644 packages/google-auth/tests/data/es384_service_account.json diff --git a/packages/google-auth/google/auth/_service_account_info.py b/packages/google-auth/google/auth/_service_account_info.py index 6b64adcaebb0..c432080a907d 100644 --- a/packages/google-auth/google/auth/_service_account_info.py +++ b/packages/google-auth/google/auth/_service_account_info.py @@ -56,7 +56,7 @@ def from_dict(data, require=None, use_rsa_signer=True): if use_rsa_signer: signer = crypt.RSASigner.from_service_account_info(data) else: - signer = crypt.ES256Signer.from_service_account_info(data) + signer = crypt.EsSigner.from_service_account_info(data) return signer diff --git a/packages/google-auth/google/auth/crypt/__init__.py b/packages/google-auth/google/auth/crypt/__init__.py index 6d147e706171..59519b475e5b 100644 --- a/packages/google-auth/google/auth/crypt/__init__.py +++ b/packages/google-auth/google/auth/crypt/__init__.py @@ -40,13 +40,19 @@ from google.auth.crypt import base from google.auth.crypt import rsa +# google.auth.crypt.es depends on the crytpography module which may not be +# successfully imported depending on the system. try: + from google.auth.crypt import es from google.auth.crypt import es256 except ImportError: # pragma: NO COVER + es = None # type: ignore es256 = None # type: ignore -if es256 is not None: # pragma: NO COVER +if es is not None and es256 is not None: # pragma: NO COVER __all__ = [ + "EsSigner", + "EsVerifier", "ES256Signer", "ES256Verifier", "RSASigner", @@ -54,6 +60,11 @@ "Signer", "Verifier", ] + + EsSigner = es.EsSigner + EsVerifier = es.EsVerifier + ES256Signer = es256.ES256Signer + ES256Verifier = es256.ES256Verifier else: # pragma: NO COVER __all__ = ["RSASigner", "RSAVerifier", "Signer", "Verifier"] @@ -65,10 +76,6 @@ RSASigner = rsa.RSASigner RSAVerifier = rsa.RSAVerifier -if es256 is not None: # pragma: NO COVER - ES256Signer = es256.ES256Signer - ES256Verifier = es256.ES256Verifier - def verify_signature(message, signature, certs, verifier_cls=rsa.RSAVerifier): """Verify an RSA or ECDSA cryptographic signature. diff --git a/packages/google-auth/google/auth/crypt/es.py b/packages/google-auth/google/auth/crypt/es.py new file mode 100644 index 000000000000..f9466af3c44f --- /dev/null +++ b/packages/google-auth/google/auth/crypt/es.py @@ -0,0 +1,221 @@ +# Copyright 2017 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""ECDSA verifier and signer that use the ``cryptography`` library. +""" + +from dataclasses import dataclass +from typing import Any, Dict, Optional, Union + +import cryptography.exceptions +from cryptography.hazmat import backends +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives.asymmetric import padding +from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature +from cryptography.hazmat.primitives.asymmetric.utils import encode_dss_signature +import cryptography.x509 + +from google.auth import _helpers +from google.auth.crypt import base + + +_CERTIFICATE_MARKER = b"-----BEGIN CERTIFICATE-----" +_BACKEND = backends.default_backend() +_PADDING = padding.PKCS1v15() + + +@dataclass +class _ESAttributes: + """A class that models ECDSA attributes. + + Attributes: + rs_size (int): Size for ASN.1 r and s size. + sha_algo (hashes.HashAlgorithm): Hash algorithm. + algorithm (str): Algorithm name. + """ + + rs_size: int + sha_algo: hashes.HashAlgorithm + algorithm: str + + @classmethod + def from_key( + cls, key: Union[ec.EllipticCurvePublicKey, ec.EllipticCurvePrivateKey] + ): + return cls.from_curve(key.curve) + + @classmethod + def from_curve(cls, curve: ec.EllipticCurve): + # ECDSA raw signature has (r||s) format where r,s are two + # integers of size 32 bytes for P-256 curve and 48 bytes + # for P-384 curve. For P-256 curve, we use SHA256 hash algo, + # and for P-384 curve we use SHA384 algo. + if isinstance(curve, ec.SECP384R1): + return cls(48, hashes.SHA384(), "ES384") + else: + # default to ES256 + return cls(32, hashes.SHA256(), "ES256") + + +class EsVerifier(base.Verifier): + """Verifies ECDSA cryptographic signatures using public keys. + + Args: + public_key ( + cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey): + The public key used to verify signatures. + """ + + def __init__(self, public_key: ec.EllipticCurvePublicKey) -> None: + self._pubkey = public_key + self._attributes = _ESAttributes.from_key(public_key) + + @_helpers.copy_docstring(base.Verifier) + def verify(self, message: bytes, signature: bytes) -> bool: + # First convert (r||s) raw signature to ASN1 encoded signature. + sig_bytes = _helpers.to_bytes(signature) + if len(sig_bytes) != self._attributes.rs_size * 2: + return False + r = int.from_bytes(sig_bytes[: self._attributes.rs_size], byteorder="big") + s = int.from_bytes(sig_bytes[self._attributes.rs_size :], byteorder="big") + asn1_sig = encode_dss_signature(r, s) + + message = _helpers.to_bytes(message) + try: + self._pubkey.verify(asn1_sig, message, ec.ECDSA(self._attributes.sha_algo)) + return True + except (ValueError, cryptography.exceptions.InvalidSignature): + return False + + @classmethod + def from_string(cls, public_key: Union[str, bytes]) -> "EsVerifier": + """Construct an Verifier instance from a public key or public + certificate string. + + Args: + public_key (Union[str, bytes]): The public key in PEM format or the + x509 public key certificate. + + Returns: + Verifier: The constructed verifier. + + Raises: + ValueError: If the public key can't be parsed. + """ + public_key_data = _helpers.to_bytes(public_key) + + if _CERTIFICATE_MARKER in public_key_data: + cert = cryptography.x509.load_pem_x509_certificate( + public_key_data, _BACKEND + ) + pubkey = cert.public_key() # type: Any + + else: + pubkey = serialization.load_pem_public_key(public_key_data, _BACKEND) + + if not isinstance(pubkey, ec.EllipticCurvePublicKey): + raise TypeError("Expected public key of type EllipticCurvePublicKey") + + return cls(pubkey) + + +class EsSigner(base.Signer, base.FromServiceAccountMixin): + """Signs messages with an ECDSA private key. + + Args: + private_key ( + cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey): + The private key to sign with. + key_id (str): Optional key ID used to identify this private key. This + can be useful to associate the private key with its associated + public key or certificate. + """ + + def __init__( + self, private_key: ec.EllipticCurvePrivateKey, key_id: Optional[str] = None + ) -> None: + self._key = private_key + self._key_id = key_id + self._attributes = _ESAttributes.from_key(private_key) + + @property + def algorithm(self) -> str: + """Name of the algorithm used to sign messages. + Returns: + str: The algorithm name. + """ + return self._attributes.algorithm + + @property # type: ignore + @_helpers.copy_docstring(base.Signer) + def key_id(self) -> Optional[str]: + return self._key_id + + @_helpers.copy_docstring(base.Signer) + def sign(self, message: bytes) -> bytes: + message = _helpers.to_bytes(message) + asn1_signature = self._key.sign(message, ec.ECDSA(self._attributes.sha_algo)) + + # Convert ASN1 encoded signature to (r||s) raw signature. + (r, s) = decode_dss_signature(asn1_signature) + return r.to_bytes(self._attributes.rs_size, byteorder="big") + s.to_bytes( + self._attributes.rs_size, byteorder="big" + ) + + @classmethod + def from_string( + cls, key: Union[bytes, str], key_id: Optional[str] = None + ) -> "EsSigner": + """Construct a RSASigner from a private key in PEM format. + + Args: + key (Union[bytes, str]): Private key in PEM format. + key_id (str): An optional key id used to identify the private key. + + Returns: + google.auth.crypt._cryptography_rsa.RSASigner: The + constructed signer. + + Raises: + ValueError: If ``key`` is not ``bytes`` or ``str`` (unicode). + UnicodeDecodeError: If ``key`` is ``bytes`` but cannot be decoded + into a UTF-8 ``str``. + ValueError: If ``cryptography`` "Could not deserialize key data." + """ + key_bytes = _helpers.to_bytes(key) + private_key = serialization.load_pem_private_key( + key_bytes, password=None, backend=_BACKEND + ) + + if not isinstance(private_key, ec.EllipticCurvePrivateKey): + raise TypeError("Expected private key of type EllipticCurvePrivateKey") + + return cls(private_key, key_id=key_id) + + def __getstate__(self) -> Dict[str, Any]: + """Pickle helper that serializes the _key attribute.""" + state = self.__dict__.copy() + state["_key"] = self._key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.PKCS8, + encryption_algorithm=serialization.NoEncryption(), + ) + return state + + def __setstate__(self, state: Dict[str, Any]) -> None: + """Pickle helper that deserializes the _key attribute.""" + state["_key"] = serialization.load_pem_private_key(state["_key"], None) + self.__dict__.update(state) diff --git a/packages/google-auth/google/auth/crypt/es256.py b/packages/google-auth/google/auth/crypt/es256.py index 820e4beccee7..e7bda5d3fc29 100644 --- a/packages/google-auth/google/auth/crypt/es256.py +++ b/packages/google-auth/google/auth/crypt/es256.py @@ -15,93 +15,22 @@ """ECDSA (ES256) verifier and signer that use the ``cryptography`` library. """ -from cryptography import utils # type: ignore -import cryptography.exceptions -from cryptography.hazmat import backends -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives.asymmetric import ec -from cryptography.hazmat.primitives.asymmetric import padding -from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature -from cryptography.hazmat.primitives.asymmetric.utils import encode_dss_signature -import cryptography.x509 +from google.auth.crypt.es import EsSigner +from google.auth.crypt.es import EsVerifier -from google.auth import _helpers -from google.auth.crypt import base - -_CERTIFICATE_MARKER = b"-----BEGIN CERTIFICATE-----" -_BACKEND = backends.default_backend() -_PADDING = padding.PKCS1v15() - - -class ES256Verifier(base.Verifier): +class ES256Verifier(EsVerifier): """Verifies ECDSA cryptographic signatures using public keys. Args: - public_key ( - cryptography.hazmat.primitives.asymmetric.ec.ECDSAPublicKey): - The public key used to verify signatures. + public_key (cryptography.hazmat.primitives.asymmetric.ec.ECDSAPublicKey): The public key used to verify + signatures. """ - def __init__(self, public_key): - self._pubkey = public_key - - @_helpers.copy_docstring(base.Verifier) - def verify(self, message, signature): - # First convert (r||s) raw signature to ASN1 encoded signature. - sig_bytes = _helpers.to_bytes(signature) - if len(sig_bytes) != 64: - return False - r = ( - int.from_bytes(sig_bytes[:32], byteorder="big") - if _helpers.is_python_3() - else utils.int_from_bytes(sig_bytes[:32], byteorder="big") - ) - s = ( - int.from_bytes(sig_bytes[32:], byteorder="big") - if _helpers.is_python_3() - else utils.int_from_bytes(sig_bytes[32:], byteorder="big") - ) - asn1_sig = encode_dss_signature(r, s) - - message = _helpers.to_bytes(message) - try: - self._pubkey.verify(asn1_sig, message, ec.ECDSA(hashes.SHA256())) - return True - except (ValueError, cryptography.exceptions.InvalidSignature): - return False - - @classmethod - def from_string(cls, public_key): - """Construct an Verifier instance from a public key or public - certificate string. - - Args: - public_key (Union[str, bytes]): The public key in PEM format or the - x509 public key certificate. - - Returns: - Verifier: The constructed verifier. - - Raises: - ValueError: If the public key can't be parsed. - """ - public_key_data = _helpers.to_bytes(public_key) - - if _CERTIFICATE_MARKER in public_key_data: - cert = cryptography.x509.load_pem_x509_certificate( - public_key_data, _BACKEND - ) - pubkey = cert.public_key() - - else: - pubkey = serialization.load_pem_public_key(public_key_data, _BACKEND) + pass - return cls(pubkey) - -class ES256Signer(base.Signer, base.FromServiceAccountMixin): +class ES256Signer(EsSigner): """Signs messages with an ECDSA private key. Args: @@ -113,63 +42,4 @@ class ES256Signer(base.Signer, base.FromServiceAccountMixin): public key or certificate. """ - def __init__(self, private_key, key_id=None): - self._key = private_key - self._key_id = key_id - - @property # type: ignore - @_helpers.copy_docstring(base.Signer) - def key_id(self): - return self._key_id - - @_helpers.copy_docstring(base.Signer) - def sign(self, message): - message = _helpers.to_bytes(message) - asn1_signature = self._key.sign(message, ec.ECDSA(hashes.SHA256())) - - # Convert ASN1 encoded signature to (r||s) raw signature. - (r, s) = decode_dss_signature(asn1_signature) - return ( - (r.to_bytes(32, byteorder="big") + s.to_bytes(32, byteorder="big")) - if _helpers.is_python_3() - else (utils.int_to_bytes(r, 32) + utils.int_to_bytes(s, 32)) - ) - - @classmethod - def from_string(cls, key, key_id=None): - """Construct a RSASigner from a private key in PEM format. - - Args: - key (Union[bytes, str]): Private key in PEM format. - key_id (str): An optional key id used to identify the private key. - - Returns: - google.auth.crypt._cryptography_rsa.RSASigner: The - constructed signer. - - Raises: - ValueError: If ``key`` is not ``bytes`` or ``str`` (unicode). - UnicodeDecodeError: If ``key`` is ``bytes`` but cannot be decoded - into a UTF-8 ``str``. - ValueError: If ``cryptography`` "Could not deserialize key data." - """ - key = _helpers.to_bytes(key) - private_key = serialization.load_pem_private_key( - key, password=None, backend=_BACKEND - ) - return cls(private_key, key_id=key_id) - - def __getstate__(self): - """Pickle helper that serializes the _key attribute.""" - state = self.__dict__.copy() - state["_key"] = self._key.private_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PrivateFormat.PKCS8, - encryption_algorithm=serialization.NoEncryption(), - ) - return state - - def __setstate__(self, state): - """Pickle helper that deserializes the _key attribute.""" - state["_key"] = serialization.load_pem_private_key(state["_key"], None) - self.__dict__.update(state) + pass diff --git a/packages/google-auth/google/auth/jwt.py b/packages/google-auth/google/auth/jwt.py index 1ebd565d4ecd..9b79f173b727 100644 --- a/packages/google-auth/google/auth/jwt.py +++ b/packages/google-auth/google/auth/jwt.py @@ -59,17 +59,18 @@ import google.auth.credentials try: - from google.auth.crypt import es256 + from google.auth.crypt import es except ImportError: # pragma: NO COVER - es256 = None # type: ignore + es = None # type: ignore _DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds _DEFAULT_MAX_CACHE_SIZE = 10 _ALGORITHM_TO_VERIFIER_CLASS = {"RS256": crypt.RSAVerifier} -_CRYPTOGRAPHY_BASED_ALGORITHMS = frozenset(["ES256"]) +_CRYPTOGRAPHY_BASED_ALGORITHMS = frozenset(["ES256", "ES384"]) -if es256 is not None: # pragma: NO COVER - _ALGORITHM_TO_VERIFIER_CLASS["ES256"] = es256.ES256Verifier # type: ignore +if es is not None: # pragma: NO COVER + _ALGORITHM_TO_VERIFIER_CLASS["ES256"] = es.EsVerifier # type: ignore + _ALGORITHM_TO_VERIFIER_CLASS["ES384"] = es.EsVerifier # type: ignore def encode(signer, payload, header=None, key_id=None): @@ -95,8 +96,8 @@ def encode(signer, payload, header=None, key_id=None): header.update({"typ": "JWT"}) if "alg" not in header: - if es256 is not None and isinstance(signer, es256.ES256Signer): - header.update({"alg": "ES256"}) + if es is not None and isinstance(signer, es.EsSigner): + header.update({"alg": signer.algorithm}) else: header.update({"alg": "RS256"}) diff --git a/packages/google-auth/tests/crypt/test_es.py b/packages/google-auth/tests/crypt/test_es.py new file mode 100644 index 000000000000..3a62c1413b2b --- /dev/null +++ b/packages/google-auth/tests/crypt/test_es.py @@ -0,0 +1,173 @@ +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import base64 +import json +import os +import pickle + +from cryptography.hazmat.primitives.asymmetric import ec +import pytest # type: ignore + +from google.auth import _helpers +from google.auth.crypt import base +from google.auth.crypt import es + + +DATA_DIR = os.path.join(os.path.dirname(__file__), "..", "data") + +# To generate es384_privatekey.pem, es384_privatekey.pub, and +# es384_public_cert.pem: +# $ openssl ecparam -genkey -name secp384r1 -noout -out es384_privatekey.pem +# $ openssl ec -in es384_privatekey.pem -pubout -out es384_publickey.pem +# $ openssl req -new -x509 -key es384_privatekey.pem -out \ +# > es384_public_cert.pem + +with open(os.path.join(DATA_DIR, "es384_privatekey.pem"), "rb") as fh: + PRIVATE_KEY_BYTES = fh.read() + PKCS1_KEY_BYTES = PRIVATE_KEY_BYTES + +with open(os.path.join(DATA_DIR, "es384_publickey.pem"), "rb") as fh: + PUBLIC_KEY_BYTES = fh.read() + +with open(os.path.join(DATA_DIR, "es384_public_cert.pem"), "rb") as fh: + PUBLIC_CERT_BYTES = fh.read() + +# RSA keys used to test for type errors in EsVerifier and EsSigner. +with open(os.path.join(DATA_DIR, "privatekey.pem"), "rb") as fh: + RSA_PRIVATE_KEY_BYTES = fh.read() + RSA_PKCS1_KEY_BYTES = RSA_PRIVATE_KEY_BYTES + +with open(os.path.join(DATA_DIR, "privatekey.pub"), "rb") as fh: + RSA_PUBLIC_KEY_BYTES = fh.read() + +SERVICE_ACCOUNT_JSON_FILE = os.path.join(DATA_DIR, "es384_service_account.json") + +with open(SERVICE_ACCOUNT_JSON_FILE, "rb") as fh: + SERVICE_ACCOUNT_INFO = json.load(fh) + + +class TestEsVerifier(object): + def test_verify_success(self): + to_sign = b"foo" + signer = es.EsSigner.from_string(PRIVATE_KEY_BYTES) + actual_signature = signer.sign(to_sign) + + verifier = es.EsVerifier.from_string(PUBLIC_KEY_BYTES) + assert verifier.verify(to_sign, actual_signature) + + def test_verify_unicode_success(self): + to_sign = u"foo" + signer = es.EsSigner.from_string(PRIVATE_KEY_BYTES) + actual_signature = signer.sign(to_sign) + + verifier = es.EsVerifier.from_string(PUBLIC_KEY_BYTES) + assert verifier.verify(to_sign, actual_signature) + + def test_verify_failure(self): + verifier = es.EsVerifier.from_string(PUBLIC_KEY_BYTES) + bad_signature1 = b"" + assert not verifier.verify(b"foo", bad_signature1) + bad_signature2 = b"a" + assert not verifier.verify(b"foo", bad_signature2) + + def test_verify_failure_with_wrong_raw_signature(self): + to_sign = b"foo" + + # This signature has a wrong "r" value in the "(r,s)" raw signature. + wrong_signature = base64.urlsafe_b64decode( + b"m7oaRxUDeYqjZ8qiMwo0PZLTMZWKJLFQREpqce1StMIa_yXQQ-C5WgeIRHW7OqlYSDL0XbUrj_uAw9i-QhfOJQ==" + ) + + verifier = es.EsVerifier.from_string(PUBLIC_KEY_BYTES) + assert not verifier.verify(to_sign, wrong_signature) + + def test_from_string_pub_key(self): + verifier = es.EsVerifier.from_string(PUBLIC_KEY_BYTES) + assert isinstance(verifier, es.EsVerifier) + assert isinstance(verifier._pubkey, ec.EllipticCurvePublicKey) + + def test_from_string_pub_key_unicode(self): + public_key = _helpers.from_bytes(PUBLIC_KEY_BYTES) + verifier = es.EsVerifier.from_string(public_key) + assert isinstance(verifier, es.EsVerifier) + assert isinstance(verifier._pubkey, ec.EllipticCurvePublicKey) + + def test_from_string_pub_cert(self): + verifier = es.EsVerifier.from_string(PUBLIC_CERT_BYTES) + assert isinstance(verifier, es.EsVerifier) + assert isinstance(verifier._pubkey, ec.EllipticCurvePublicKey) + + def test_from_string_pub_cert_unicode(self): + public_cert = _helpers.from_bytes(PUBLIC_CERT_BYTES) + verifier = es.EsVerifier.from_string(public_cert) + assert isinstance(verifier, es.EsVerifier) + assert isinstance(verifier._pubkey, ec.EllipticCurvePublicKey) + + def test_from_string_type_error(self): + with pytest.raises(TypeError): + es.EsVerifier.from_string(RSA_PUBLIC_KEY_BYTES) + + +class TestEsSigner(object): + def test_from_string_pkcs1(self): + signer = es.EsSigner.from_string(PKCS1_KEY_BYTES) + assert isinstance(signer, es.EsSigner) + assert isinstance(signer._key, ec.EllipticCurvePrivateKey) + + def test_from_string_pkcs1_unicode(self): + key_bytes = _helpers.from_bytes(PKCS1_KEY_BYTES) + signer = es.EsSigner.from_string(key_bytes) + assert isinstance(signer, es.EsSigner) + assert isinstance(signer._key, ec.EllipticCurvePrivateKey) + + def test_from_string_bogus_key(self): + key_bytes = "bogus-key" + with pytest.raises(ValueError): + es.EsSigner.from_string(key_bytes) + + def test_from_string_type_error(self): + key_bytes = _helpers.from_bytes(RSA_PKCS1_KEY_BYTES) + with pytest.raises(TypeError): + es.EsSigner.from_string(key_bytes) + + def test_from_service_account_info(self): + signer = es.EsSigner.from_service_account_info(SERVICE_ACCOUNT_INFO) + + assert signer.key_id == SERVICE_ACCOUNT_INFO[base._JSON_FILE_PRIVATE_KEY_ID] + assert isinstance(signer._key, ec.EllipticCurvePrivateKey) + + def test_from_service_account_info_missing_key(self): + with pytest.raises(ValueError) as excinfo: + es.EsSigner.from_service_account_info({}) + + assert excinfo.match(base._JSON_FILE_PRIVATE_KEY) + + def test_from_service_account_file(self): + signer = es.EsSigner.from_service_account_file(SERVICE_ACCOUNT_JSON_FILE) + + assert signer.key_id == SERVICE_ACCOUNT_INFO[base._JSON_FILE_PRIVATE_KEY_ID] + assert isinstance(signer._key, ec.EllipticCurvePrivateKey) + + def test_pickle(self): + signer = es.EsSigner.from_service_account_file(SERVICE_ACCOUNT_JSON_FILE) + + assert signer.key_id == SERVICE_ACCOUNT_INFO[base._JSON_FILE_PRIVATE_KEY_ID] + assert isinstance(signer._key, ec.EllipticCurvePrivateKey) + + pickled_signer = pickle.dumps(signer) + signer = pickle.loads(pickled_signer) + + assert signer.key_id == SERVICE_ACCOUNT_INFO[base._JSON_FILE_PRIVATE_KEY_ID] + assert isinstance(signer._key, ec.EllipticCurvePrivateKey) diff --git a/packages/google-auth/tests/data/es384_privatekey.pem b/packages/google-auth/tests/data/es384_privatekey.pem new file mode 100644 index 000000000000..12ff96291b89 --- /dev/null +++ b/packages/google-auth/tests/data/es384_privatekey.pem @@ -0,0 +1,6 @@ +-----BEGIN EC PRIVATE KEY----- +MIGkAgEBBDBz1wKJNXd2Rzy52A7F3f9LmLp6KaMUTbL1IT3JaDx1kOp4CUFpI9Zs +rdEx7b7kKQGgBwYFK4EEACKhZANiAATRLiEHuOwLr8bjJnJdYG2mrlWtMEPBHOrm +n7RukR80nV5uAcqt+M319T2togP0tQIe621FUpJq7+Hq0vJJbtI1MPuFSDtpZG04 +5se7BVAw63IPV1EdO6vGXxd5Fay88uU= +-----END EC PRIVATE KEY----- diff --git a/packages/google-auth/tests/data/es384_public_cert.pem b/packages/google-auth/tests/data/es384_public_cert.pem new file mode 100644 index 000000000000..e8d5d4c6851d --- /dev/null +++ b/packages/google-auth/tests/data/es384_public_cert.pem @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICYzCCAeqgAwIBAgIUeYyowQBkomEoMj72pNh754QlGvAwCgYIKoZIzj0EAwIw +aTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1Nb3VudGFpbiBW +aWV3MQ8wDQYDVQQKDAZHb29nbGUxDzANBgNVBAsMBkdvb2dsZTETMBEGA1UEAwwK +Z29vZ2xlLmNvbTAeFw0yNTExMTEwMDQzMTlaFw0yNTEyMTEwMDQzMTlaMGkxCzAJ +BgNVBAYTAlVTMQswCQYDVQQIDAJDQTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEP +MA0GA1UECgwGR29vZ2xlMQ8wDQYDVQQLDAZHb29nbGUxEzARBgNVBAMMCmdvb2ds +ZS5jb20wdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATRLiEHuOwLr8bjJnJdYG2mrlWt +MEPBHOrmn7RukR80nV5uAcqt+M319T2togP0tQIe621FUpJq7+Hq0vJJbtI1MPuF +SDtpZG045se7BVAw63IPV1EdO6vGXxd5Fay88uWjUzBRMB0GA1UdDgQWBBSRZkxR +63/X4JotxKDRWCI4PwIElDAfBgNVHSMEGDAWgBSRZkxR63/X4JotxKDRWCI4PwIE +lDAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA2cAMGQCMAU+2yy/luLTa+T6 +Jm86i9GiH/lPYdYwZFvwKJFTdj8FJpv7ySN0J80qzWxtBZTCMQIwZO0ZRdv8s7V3 +022yISIujmsPmgj7lvPuDZZaVn1DVYMG3YmBB+cTp+JTqF3x7lN+ +-----END CERTIFICATE----- diff --git a/packages/google-auth/tests/data/es384_publickey.pem b/packages/google-auth/tests/data/es384_publickey.pem new file mode 100644 index 000000000000..e78ac0f494fc --- /dev/null +++ b/packages/google-auth/tests/data/es384_publickey.pem @@ -0,0 +1,5 @@ +-----BEGIN PUBLIC KEY----- +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE0S4hB7jsC6/G4yZyXWBtpq5VrTBDwRzq +5p+0bpEfNJ1ebgHKrfjN9fU9raID9LUCHuttRVKSau/h6tLySW7SNTD7hUg7aWRt +OObHuwVQMOtyD1dRHTurxl8XeRWsvPLl +-----END PUBLIC KEY----- diff --git a/packages/google-auth/tests/data/es384_service_account.json b/packages/google-auth/tests/data/es384_service_account.json new file mode 100644 index 000000000000..8302344b1a7c --- /dev/null +++ b/packages/google-auth/tests/data/es384_service_account.json @@ -0,0 +1,9 @@ +{ + "type":"gdch_service_account", + "format_version":"1", + "project":"mytest", + "private_key_id":"1234567890", + "private_key":"-----BEGIN EC PRIVATE KEY-----\nMIGkAgEBBDAyqgUeNwuUOMCC9Bzyf4uT2rfZyISJFMq3ByfE+ytUbveUd6RtvoCT\nS9cYbmuj06OgBwYFK4EEACKhZANiAATrUB670cjyRUcarD//92jO52Rqo+jKi0x7\nkscWALlC8bx9zED5zpy948FrQhQgb/TLPhunkyTwWe22CzafS8ik5pCZKkWfiJRV\n9IBMJDTMyocCR013qDXKHZOpJ57wAUw=\n-----END EC PRIVATE KEY-----\n", + "name":"mytest", + "token_uri":"https://service-accounts.org.google.com/authenticate" +} diff --git a/packages/google-auth/tests/test__service_account_info.py b/packages/google-auth/tests/test__service_account_info.py index be2657074803..7e836861e4a7 100644 --- a/packages/google-auth/tests/test__service_account_info.py +++ b/packages/google-auth/tests/test__service_account_info.py @@ -23,13 +23,21 @@ DATA_DIR = os.path.join(os.path.dirname(__file__), "data") SERVICE_ACCOUNT_JSON_FILE = os.path.join(DATA_DIR, "service_account.json") -GDCH_SERVICE_ACCOUNT_JSON_FILE = os.path.join(DATA_DIR, "gdch_service_account.json") +GDCH_SERVICE_ACCOUNT_ES256_JSON_FILE = os.path.join( + DATA_DIR, "gdch_service_account.json" +) +GDCH_SERVICE_ACCOUNT_ES384_JSON_FILE = os.path.join( + DATA_DIR, "es384_service_account.json" +) with open(SERVICE_ACCOUNT_JSON_FILE, "r") as fh: SERVICE_ACCOUNT_INFO = json.load(fh) -with open(GDCH_SERVICE_ACCOUNT_JSON_FILE, "r") as fh: - GDCH_SERVICE_ACCOUNT_INFO = json.load(fh) +with open(GDCH_SERVICE_ACCOUNT_ES256_JSON_FILE, "r") as fh: + GDCH_SERVICE_ACCOUNT_ES256_INFO = json.load(fh) + +with open(GDCH_SERVICE_ACCOUNT_ES384_JSON_FILE, "r") as fh: + GDCH_SERVICE_ACCOUNT_ES384_INFO = json.load(fh) def test_from_dict(): @@ -40,10 +48,19 @@ def test_from_dict(): def test_from_dict_es256_signer(): signer = _service_account_info.from_dict( - GDCH_SERVICE_ACCOUNT_INFO, use_rsa_signer=False + GDCH_SERVICE_ACCOUNT_ES256_INFO, use_rsa_signer=False + ) + assert isinstance(signer, crypt.EsSigner) + assert signer.key_id == GDCH_SERVICE_ACCOUNT_ES256_INFO["private_key_id"] + + +def test_from_dict_es384_signer(): + signer = _service_account_info.from_dict( + GDCH_SERVICE_ACCOUNT_ES384_INFO, use_rsa_signer=False ) - assert isinstance(signer, crypt.ES256Signer) - assert signer.key_id == GDCH_SERVICE_ACCOUNT_INFO["private_key_id"] + assert isinstance(signer, crypt.EsSigner) + assert signer.key_id == GDCH_SERVICE_ACCOUNT_ES384_INFO["private_key_id"] + assert signer.algorithm == "ES384" def test_from_dict_bad_private_key(): @@ -75,8 +92,18 @@ def test_from_filename(): def test_from_filename_es256_signer(): _, signer = _service_account_info.from_filename( - GDCH_SERVICE_ACCOUNT_JSON_FILE, use_rsa_signer=False + GDCH_SERVICE_ACCOUNT_ES256_JSON_FILE, use_rsa_signer=False + ) + + assert isinstance(signer, crypt.EsSigner) + assert signer.key_id == GDCH_SERVICE_ACCOUNT_ES256_INFO["private_key_id"] + + +def test_from_filename_es384_signer(): + _, signer = _service_account_info.from_filename( + GDCH_SERVICE_ACCOUNT_ES384_JSON_FILE, use_rsa_signer=False ) - assert isinstance(signer, crypt.ES256Signer) - assert signer.key_id == GDCH_SERVICE_ACCOUNT_INFO["private_key_id"] + assert isinstance(signer, crypt.EsSigner) + assert signer.key_id == GDCH_SERVICE_ACCOUNT_ES384_INFO["private_key_id"] + assert signer.algorithm == "ES384" diff --git a/packages/google-auth/tests/test_jwt.py b/packages/google-auth/tests/test_jwt.py index 28660ea3306a..a5a904d7d362 100644 --- a/packages/google-auth/tests/test_jwt.py +++ b/packages/google-auth/tests/test_jwt.py @@ -43,6 +43,12 @@ with open(os.path.join(DATA_DIR, "es256_public_cert.pem"), "rb") as fh: EC_PUBLIC_CERT_BYTES = fh.read() +with open(os.path.join(DATA_DIR, "es384_privatekey.pem"), "rb") as fh: + EC384_PRIVATE_KEY_BYTES = fh.read() + +with open(os.path.join(DATA_DIR, "es384_public_cert.pem"), "rb") as fh: + EC384_PUBLIC_CERT_BYTES = fh.read() + SERVICE_ACCOUNT_JSON_FILE = os.path.join(DATA_DIR, "service_account.json") with open(SERVICE_ACCOUNT_JSON_FILE, "rb") as fh: @@ -84,6 +90,11 @@ def es256_signer(): return crypt.ES256Signer.from_string(EC_PRIVATE_KEY_BYTES, "1") +@pytest.fixture +def es384_signer(): + return crypt.EsSigner.from_string(EC384_PRIVATE_KEY_BYTES, "1") + + def test_encode_basic_es256(es256_signer): test_payload = {"test": "value"} encoded = jwt.encode(es256_signer, test_payload) @@ -92,9 +103,19 @@ def test_encode_basic_es256(es256_signer): assert header == {"typ": "JWT", "alg": "ES256", "kid": es256_signer.key_id} +def test_encode_basic_es384(es384_signer): + test_payload = {"test": "value"} + encoded = jwt.encode(es384_signer, test_payload) + header, payload, _, _ = jwt._unverified_decode(encoded) + assert payload == test_payload + assert header == {"typ": "JWT", "alg": "ES384", "kid": es384_signer.key_id} + + @pytest.fixture -def token_factory(signer, es256_signer): - def factory(claims=None, key_id=None, use_es256_signer=False): +def token_factory(signer, es256_signer, es384_signer): + def factory( + claims=None, key_id=None, use_es256_signer=False, use_es384_signer=False + ): now = _helpers.datetime_to_secs(_helpers.utcnow()) payload = { "aud": "audience@example.com", @@ -113,6 +134,8 @@ def factory(claims=None, key_id=None, use_es256_signer=False): if use_es256_signer: return jwt.encode(es256_signer, payload, key_id=key_id) + elif use_es384_signer: + return jwt.encode(es384_signer, payload, key_id=key_id) else: return jwt.encode(signer, payload, key_id=key_id) @@ -158,6 +181,15 @@ def test_decode_valid_es256(token_factory): assert payload["metadata"]["meta"] == "data" +def test_decode_valid_es384(token_factory): + payload = jwt.decode( + token_factory(use_es384_signer=True), certs=EC384_PUBLIC_CERT_BYTES + ) + assert payload["aud"] == "audience@example.com" + assert payload["user"] == "billy bob" + assert payload["metadata"]["meta"] == "data" + + def test_decode_valid_with_audience(token_factory): payload = jwt.decode( token_factory(), certs=PUBLIC_CERT_BYTES, audience="audience@example.com" From fd184d547c70e8e984faa637374db84e5e504228 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Wed, 17 Dec 2025 16:33:01 +0000 Subject: [PATCH 957/966] onboard librarian;clean up files --- .librarian/state.yaml | 9 + packages/google-auth/.github/CODEOWNERS | 30 -- packages/google-auth/.github/CONTRIBUTING.md | 28 - .../.github/ISSUE_TEMPLATE/bug_report.md | 31 -- .../.github/ISSUE_TEMPLATE/feature_request.md | 18 - .../.github/ISSUE_TEMPLATE/support_request.md | 7 - .../.github/sync-repo-settings.yaml | 26 - packages/google-auth/.gitignore | 50 -- .../google-auth/.kokoro/build-systests.sh | 48 -- packages/google-auth/.kokoro/build.sh | 40 -- packages/google-auth/.kokoro/common.cfg | 16 - .../google-auth/.kokoro/continuous/common.cfg | 27 - .../.kokoro/continuous/continuous.cfg | 1 - .../.kokoro/continuous/prerelease-deps.cfg | 7 - .../google-auth/.kokoro/populate-secrets.sh | 43 -- .../google-auth/.kokoro/presubmit/common.cfg | 27 - .../.kokoro/presubmit/prerelease-deps.cfg | 7 - .../.kokoro/presubmit/presubmit.cfg | 1 - .../.kokoro/presubmit/system-3.10.cfg | 5 - .../google-auth/.kokoro/samples-test-setup.sh | 48 -- .../.kokoro/samples/lint/common.cfg | 38 -- .../.kokoro/samples/lint/continuous.cfg | 6 - .../.kokoro/samples/lint/periodic.cfg | 6 - .../.kokoro/samples/lint/presubmit.cfg | 6 - .../.kokoro/samples/python3.10/common.cfg | 37 -- .../.kokoro/samples/python3.10/continuous.cfg | 6 - .../samples/python3.10/periodic-head.cfg | 11 - .../.kokoro/samples/python3.10/periodic.cfg | 6 - .../.kokoro/samples/python3.10/presubmit.cfg | 6 - .../.kokoro/samples/python3.11/common.cfg | 37 -- .../.kokoro/samples/python3.11/continuous.cfg | 6 - .../samples/python3.11/periodic-head.cfg | 11 - .../.kokoro/samples/python3.11/periodic.cfg | 6 - .../.kokoro/samples/python3.11/presubmit.cfg | 6 - .../.kokoro/samples/python3.12/common.cfg | 37 -- .../.kokoro/samples/python3.12/continuous.cfg | 6 - .../samples/python3.12/periodic-head.cfg | 11 - .../.kokoro/samples/python3.12/periodic.cfg | 6 - .../.kokoro/samples/python3.12/presubmit.cfg | 6 - .../.kokoro/samples/python3.13/common.cfg | 37 -- .../.kokoro/samples/python3.13/continuous.cfg | 6 - .../samples/python3.13/periodic-head.cfg | 11 - .../.kokoro/samples/python3.13/periodic.cfg | 6 - .../.kokoro/samples/python3.13/presubmit.cfg | 6 - .../.kokoro/samples/python3.7/common.cfg | 38 -- .../.kokoro/samples/python3.7/continuous.cfg | 6 - .../samples/python3.7/periodic-head.cfg | 11 - .../.kokoro/samples/python3.7/periodic.cfg | 6 - .../.kokoro/samples/python3.7/presubmit.cfg | 6 - .../.kokoro/samples/python3.8/common.cfg | 37 -- .../.kokoro/samples/python3.8/continuous.cfg | 6 - .../samples/python3.8/periodic-head.cfg | 11 - .../.kokoro/samples/python3.8/periodic.cfg | 6 - .../.kokoro/samples/python3.8/presubmit.cfg | 6 - .../.kokoro/samples/python3.9/common.cfg | 37 -- .../.kokoro/samples/python3.9/continuous.cfg | 6 - .../samples/python3.9/periodic-head.cfg | 11 - .../.kokoro/samples/python3.9/periodic.cfg | 6 - .../.kokoro/samples/python3.9/presubmit.cfg | 6 - .../.kokoro/test-samples-against-head.sh | 26 - .../google-auth/.kokoro/test-samples-impl.sh | 103 ---- packages/google-auth/.kokoro/test-samples.sh | 44 -- packages/google-auth/.kokoro/trampoline.sh | 28 - packages/google-auth/.kokoro/trampoline_v2.sh | 487 ------------------ packages/google-auth/.librarian/config.yaml | 6 - packages/google-auth/.librarian/state.yaml | 11 - packages/google-auth/.readthedocs.yaml | 22 - packages/google-auth/.repo-metadata.json | 4 +- packages/google-auth/.trampolinerc | 61 --- packages/google-auth/CODE_OF_CONDUCT.md | 43 -- packages/google-auth/CONTRIBUTING.rst | 4 +- packages/google-auth/README.rst | 4 +- packages/google-auth/SECURITY.md | 7 - packages/google-auth/docs/conf.py | 2 +- packages/google-auth/docs/index.rst | 6 +- 75 files changed, 19 insertions(+), 1827 deletions(-) delete mode 100644 packages/google-auth/.github/CODEOWNERS delete mode 100644 packages/google-auth/.github/CONTRIBUTING.md delete mode 100644 packages/google-auth/.github/ISSUE_TEMPLATE/bug_report.md delete mode 100644 packages/google-auth/.github/ISSUE_TEMPLATE/feature_request.md delete mode 100644 packages/google-auth/.github/ISSUE_TEMPLATE/support_request.md delete mode 100644 packages/google-auth/.github/sync-repo-settings.yaml delete mode 100644 packages/google-auth/.gitignore delete mode 100755 packages/google-auth/.kokoro/build-systests.sh delete mode 100755 packages/google-auth/.kokoro/build.sh delete mode 100644 packages/google-auth/.kokoro/common.cfg delete mode 100644 packages/google-auth/.kokoro/continuous/common.cfg delete mode 100644 packages/google-auth/.kokoro/continuous/continuous.cfg delete mode 100644 packages/google-auth/.kokoro/continuous/prerelease-deps.cfg delete mode 100755 packages/google-auth/.kokoro/populate-secrets.sh delete mode 100644 packages/google-auth/.kokoro/presubmit/common.cfg delete mode 100644 packages/google-auth/.kokoro/presubmit/prerelease-deps.cfg delete mode 100644 packages/google-auth/.kokoro/presubmit/presubmit.cfg delete mode 100644 packages/google-auth/.kokoro/presubmit/system-3.10.cfg delete mode 100755 packages/google-auth/.kokoro/samples-test-setup.sh delete mode 100644 packages/google-auth/.kokoro/samples/lint/common.cfg delete mode 100644 packages/google-auth/.kokoro/samples/lint/continuous.cfg delete mode 100644 packages/google-auth/.kokoro/samples/lint/periodic.cfg delete mode 100644 packages/google-auth/.kokoro/samples/lint/presubmit.cfg delete mode 100644 packages/google-auth/.kokoro/samples/python3.10/common.cfg delete mode 100644 packages/google-auth/.kokoro/samples/python3.10/continuous.cfg delete mode 100644 packages/google-auth/.kokoro/samples/python3.10/periodic-head.cfg delete mode 100644 packages/google-auth/.kokoro/samples/python3.10/periodic.cfg delete mode 100644 packages/google-auth/.kokoro/samples/python3.10/presubmit.cfg delete mode 100644 packages/google-auth/.kokoro/samples/python3.11/common.cfg delete mode 100644 packages/google-auth/.kokoro/samples/python3.11/continuous.cfg delete mode 100644 packages/google-auth/.kokoro/samples/python3.11/periodic-head.cfg delete mode 100644 packages/google-auth/.kokoro/samples/python3.11/periodic.cfg delete mode 100644 packages/google-auth/.kokoro/samples/python3.11/presubmit.cfg delete mode 100644 packages/google-auth/.kokoro/samples/python3.12/common.cfg delete mode 100644 packages/google-auth/.kokoro/samples/python3.12/continuous.cfg delete mode 100644 packages/google-auth/.kokoro/samples/python3.12/periodic-head.cfg delete mode 100644 packages/google-auth/.kokoro/samples/python3.12/periodic.cfg delete mode 100644 packages/google-auth/.kokoro/samples/python3.12/presubmit.cfg delete mode 100644 packages/google-auth/.kokoro/samples/python3.13/common.cfg delete mode 100644 packages/google-auth/.kokoro/samples/python3.13/continuous.cfg delete mode 100644 packages/google-auth/.kokoro/samples/python3.13/periodic-head.cfg delete mode 100644 packages/google-auth/.kokoro/samples/python3.13/periodic.cfg delete mode 100644 packages/google-auth/.kokoro/samples/python3.13/presubmit.cfg delete mode 100644 packages/google-auth/.kokoro/samples/python3.7/common.cfg delete mode 100644 packages/google-auth/.kokoro/samples/python3.7/continuous.cfg delete mode 100644 packages/google-auth/.kokoro/samples/python3.7/periodic-head.cfg delete mode 100644 packages/google-auth/.kokoro/samples/python3.7/periodic.cfg delete mode 100644 packages/google-auth/.kokoro/samples/python3.7/presubmit.cfg delete mode 100644 packages/google-auth/.kokoro/samples/python3.8/common.cfg delete mode 100644 packages/google-auth/.kokoro/samples/python3.8/continuous.cfg delete mode 100644 packages/google-auth/.kokoro/samples/python3.8/periodic-head.cfg delete mode 100644 packages/google-auth/.kokoro/samples/python3.8/periodic.cfg delete mode 100644 packages/google-auth/.kokoro/samples/python3.8/presubmit.cfg delete mode 100644 packages/google-auth/.kokoro/samples/python3.9/common.cfg delete mode 100644 packages/google-auth/.kokoro/samples/python3.9/continuous.cfg delete mode 100644 packages/google-auth/.kokoro/samples/python3.9/periodic-head.cfg delete mode 100644 packages/google-auth/.kokoro/samples/python3.9/periodic.cfg delete mode 100644 packages/google-auth/.kokoro/samples/python3.9/presubmit.cfg delete mode 100755 packages/google-auth/.kokoro/test-samples-against-head.sh delete mode 100755 packages/google-auth/.kokoro/test-samples-impl.sh delete mode 100755 packages/google-auth/.kokoro/test-samples.sh delete mode 100755 packages/google-auth/.kokoro/trampoline.sh delete mode 100755 packages/google-auth/.kokoro/trampoline_v2.sh delete mode 100644 packages/google-auth/.librarian/config.yaml delete mode 100644 packages/google-auth/.librarian/state.yaml delete mode 100644 packages/google-auth/.readthedocs.yaml delete mode 100644 packages/google-auth/.trampolinerc delete mode 100644 packages/google-auth/CODE_OF_CONDUCT.md delete mode 100644 packages/google-auth/SECURITY.md diff --git a/.librarian/state.yaml b/.librarian/state.yaml index fc72a1f57277..8416ffe13414 100644 --- a/.librarian/state.yaml +++ b/.librarian/state.yaml @@ -4304,3 +4304,12 @@ libraries: - README.rst - docs/summary_overview.md tag_format: '{id}-v{version}' + - id: google-auth + version: 2.43.0 + last_generated_commit: 102d9f92ac6ed649a61efd9b208e4d1de278e9bb + apis: [] + source_roots: + - . + preserve_regex: [] + remove_regex: [] + tag_format: v{version} diff --git a/packages/google-auth/.github/CODEOWNERS b/packages/google-auth/.github/CODEOWNERS deleted file mode 100644 index d854e6f57539..000000000000 --- a/packages/google-auth/.github/CODEOWNERS +++ /dev/null @@ -1,30 +0,0 @@ -# Code owners file. -# This file controls who is tagged for review for any given pull request. -# -# For syntax help see: -# https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners#codeowners-syntax - -# The @googleapis/googleapis-auth and @googleapis/python-core-client-libraries is the default owner for changes in this repo -* @googleapis/googleapis-auth @googleapis/python-core-client-libraries -google/auth/_default.py @googleapis/googleapis-auth @googleapis/aion-sdk @googleapis/python-core-client-libraries -google/auth/aws.py @googleapis/googleapis-auth @googleapis/aion-sdk @googleapis/python-core-client-libraries -google/auth/credentials.py @googleapis/googleapis-auth @googleapis/aion-sdk @googleapis/python-core-client-libraries -google/auth/downscoped.py @googleapis/googleapis-auth @googleapis/aion-sdk @googleapis/python-core-client-libraries -google/auth/external_account.py @googleapis/googleapis-auth @googleapis/aion-sdk @googleapis/python-core-client-libraries -google/auth/external_account_authorized_user.py @googleapis/googleapis-auth @googleapis/aion-sdk @googleapis/python-core-client-libraries -google/auth/identity_pool.py @googleapis/googleapis-auth @googleapis/aion-sdk @googleapis/python-core-client-libraries -google/auth/pluggable.py @googleapis/googleapis-auth @googleapis/aion-sdk @googleapis/python-core-client-libraries -google/auth/sts.py @googleapis/googleapis-auth @googleapis/aion-sdk @googleapis/python-core-client-libraries -google/auth/impersonated_credentials.py @googleapis/googleapis-auth @googleapis/aion-sdk @googleapis/python-core-client-libraries -tests/test__default.py @googleapis/googleapis-auth @googleapis/aion-sdk @googleapis/python-core-client-libraries -tests/test_aws.py @googleapis/googleapis-auth @googleapis/aion-sdk @googleapis/python-core-client-libraries -tests/test_credentials.py @googleapis/googleapis-auth @googleapis/aion-sdk @googleapis/python-core-client-libraries -tests/test_downscoped.py @googleapis/googleapis-auth @googleapis/aion-sdk @googleapis/python-core-client-libraries -tests/test_external_account.py @googleapis/googleapis-auth @googleapis/aion-sdk @googleapis/python-core-client-libraries -tests/test_external_account_authorized_user.py @googleapis/googleapis-auth @googleapis/aion-sdk @googleapis/python-core-client-libraries -tests/test_identity_pool.py @googleapis/googleapis-auth @googleapis/aion-sdk @googleapis/python-core-client-libraries -tests/test_pluggable.py @googleapis/googleapis-auth @googleapis/aion-sdk @googleapis/python-core-client-libraries -tests/test_sts.py @googleapis/googleapis-auth @googleapis/aion-sdk @googleapis/python-core-client-libraries -tests/test_impersonated_credentials.py @googleapis/googleapis-auth @googleapis/aion-sdk @googleapis/python-core-client-libraries -/samples/ @googleapis/googleapis-auth @googleapis/aion-sdk @googleapis/python-samples-owners @googleapis/python-core-client-libraries -system_tests/secrets.tar.enc # Remove noise from test creds. diff --git a/packages/google-auth/.github/CONTRIBUTING.md b/packages/google-auth/.github/CONTRIBUTING.md deleted file mode 100644 index 939e5341e74d..000000000000 --- a/packages/google-auth/.github/CONTRIBUTING.md +++ /dev/null @@ -1,28 +0,0 @@ -# How to Contribute - -We'd love to accept your patches and contributions to this project. There are -just a few small guidelines you need to follow. - -## Contributor License Agreement - -Contributions to this project must be accompanied by a Contributor License -Agreement. You (or your employer) retain the copyright to your contribution; -this simply gives us permission to use and redistribute your contributions as -part of the project. Head over to to see -your current agreements on file or to sign a new one. - -You generally only need to submit a CLA once, so if you've already submitted one -(even if it was for a different project), you probably don't need to do it -again. - -## Code reviews - -All submissions, including submissions by project members, require review. We -use GitHub pull requests for this purpose. Consult -[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more -information on using pull requests. - -## Community Guidelines - -This project follows [Google's Open Source Community -Guidelines](https://opensource.google.com/conduct/). diff --git a/packages/google-auth/.github/ISSUE_TEMPLATE/bug_report.md b/packages/google-auth/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index e43ad68cb49c..000000000000 --- a/packages/google-auth/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve - ---- - -Thanks for stopping by to let us know something could be better! - -**PLEASE READ**: If you have a support contract with Google, please create an issue in the [support console](https://cloud.google.com/support/) instead of filing on GitHub. This will ensure a timely response. - -Please run down the following list and make sure you've tried the usual "quick fixes": - - - Search the issues already opened: https://github.com/googleapis/google-auth-library-python/issues - -If you are still having issues, please be sure to include as much information as possible: - -#### Environment details - - - OS: - - Python version: - - pip version: - - `google-auth` version: - -#### Steps to reproduce - - 1. ? - 2. ? - -Making sure to follow these steps will guarantee the quickest resolution possible. - -Thanks! diff --git a/packages/google-auth/.github/ISSUE_TEMPLATE/feature_request.md b/packages/google-auth/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 6365857f33c6..000000000000 --- a/packages/google-auth/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this library - ---- - -Thanks for stopping by to let us know something could be better! - -**PLEASE READ**: If you have a support contract with Google, please create an issue in the [support console](https://cloud.google.com/support/) instead of filing on GitHub. This will ensure a timely response. - - **Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - **Describe the solution you'd like** -A clear and concise description of what you want to happen. - **Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - **Additional context** -Add any other context or screenshots about the feature request here. diff --git a/packages/google-auth/.github/ISSUE_TEMPLATE/support_request.md b/packages/google-auth/.github/ISSUE_TEMPLATE/support_request.md deleted file mode 100644 index 995869032125..000000000000 --- a/packages/google-auth/.github/ISSUE_TEMPLATE/support_request.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -name: Support request -about: If you have a support contract with Google, please create an issue in the Google Cloud Support console. - ---- - -**PLEASE READ**: If you have a support contract with Google, please create an issue in the [support console](https://cloud.google.com/support/) instead of filing on GitHub. This will ensure a timely response. diff --git a/packages/google-auth/.github/sync-repo-settings.yaml b/packages/google-auth/.github/sync-repo-settings.yaml deleted file mode 100644 index 5ebb96e6a219..000000000000 --- a/packages/google-auth/.github/sync-repo-settings.yaml +++ /dev/null @@ -1,26 +0,0 @@ -# https://github.com/googleapis/repo-automation-bots/tree/main/packages/sync-repo-settings -# Rules for main branch protection -branchProtectionRules: -# Identifies the protection rule pattern. Name of the branch to be protected. -# Defaults to `main` -- pattern: main - requiresCodeOwnerReviews: true - requiresStrictStatusChecks: true - requiredStatusCheckContexts: - - 'cla/google' - - 'OwlBot Post Processor' - - 'Kokoro system-3.7' - - 'Kokoro' - - 'Samples - Python 3.7' - - 'Samples - Python 3.8' - - 'Samples - Python 3.9' - - 'Samples - Python 3.10' - - 'Samples - Python 3.11' - - 'Samples - Python 3.12' -permissionRules: - - team: actools-python - permission: admin - - team: actools - permission: admin - - team: yoshi-python - permission: push diff --git a/packages/google-auth/.gitignore b/packages/google-auth/.gitignore deleted file mode 100644 index a5c5f84141e0..000000000000 --- a/packages/google-auth/.gitignore +++ /dev/null @@ -1,50 +0,0 @@ -# Build artifacts -*.py[cod] -__pycache__ -*.egg-info/ -build/ -dist/ - -# Documentation-related -docs/_build - -# Test files -.nox/ -.tox/ -.cache/ -.pytest_cache/ -cert_path -key_path - -# Django test database -db.sqlite3 - -# Coverage files -.coverage -coverage.xml -*sponge_log.xml -nosetests.xml -htmlcov/ - -# Files with private / local data -scripts/local_test_setup -tests/data/key.json -tests/data/key.p12 -tests/data/user-key.json -system_tests/data/ - -# PyCharm configuration: -.idea -venv/ - -# Generated files -pylintrc -pylintrc.test -pytype_output/ - -.python-version -.DS_Store -cert_path -key_path -env/ -.vscode/ \ No newline at end of file diff --git a/packages/google-auth/.kokoro/build-systests.sh b/packages/google-auth/.kokoro/build-systests.sh deleted file mode 100755 index 3c8dcf85b418..000000000000 --- a/packages/google-auth/.kokoro/build-systests.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/bin/bash -# Copyright 2018 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -set -eo pipefail - -if [[ -z "${PROJECT_ROOT:-}" ]]; then - PROJECT_ROOT="github/google-auth-library-python" -fi - -cd "${PROJECT_ROOT}" - -# Disable buffering, so that the logs stream through. -export PYTHONUNBUFFERED=1 - -# Remove old nox -python3 -m pip uninstall --yes --quiet nox-automation - -# Install nox -python3 -m pip install nox==2024.10.9 -python3 -m nox --version - -# Setup service account credentials. -export GOOGLE_APPLICATION_CREDENTIALS=${KOKORO_GFILE_DIR}/service-account.json - -# Setup project id. -export PROJECT_ID=$(cat "${KOKORO_GFILE_DIR}/project-id.txt") - -# Activate gcloud with service account credentials -gcloud auth activate-service-account --key-file=$GOOGLE_APPLICATION_CREDENTIALS -gcloud config set project ${PROJECT_ID} - -# Decrypt system test secrets -./scripts/decrypt-secrets.sh - -# Run system tests which use a different noxfile -python3 -m nox -f system_tests/noxfile.py diff --git a/packages/google-auth/.kokoro/build.sh b/packages/google-auth/.kokoro/build.sh deleted file mode 100755 index 04ab45c19c9b..000000000000 --- a/packages/google-auth/.kokoro/build.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/bin/bash -# Copyright 2018 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -set -eo pipefail - -if [[ -z "${PROJECT_ROOT:-}" ]]; then - PROJECT_ROOT="github/google-auth-library-python" -fi - -cd "${PROJECT_ROOT}" - -# Disable buffering, so that the logs stream through. -export PYTHONUNBUFFERED=1 - -# Remove old nox -python3 -m pip uninstall --yes --quiet nox-automation - -# Install nox -python3 -m pip install --upgrade --quiet nox -python3 -m nox --version - -# If NOX_SESSION is set, it only runs the specified session, -# otherwise run all the sessions. -if [[ -n "${NOX_SESSION:-}" ]]; then - python3 -m nox -s ${NOX_SESSION:-} -else - python3 -m nox -fi diff --git a/packages/google-auth/.kokoro/common.cfg b/packages/google-auth/.kokoro/common.cfg deleted file mode 100644 index 81f431a99733..000000000000 --- a/packages/google-auth/.kokoro/common.cfg +++ /dev/null @@ -1,16 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -# Download trampoline resources. These will be in ${KOKORO_GFILE_DIR} -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" - -# Download resources for tests -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-auth-library-python" - -# All builds use the trampoline script to run in docker. -build_file: "google-auth-library-python/.kokoro/trampoline.sh" - -# Use the Python worker docker iamge. -env_vars: { - key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-public-resources/python-multi" -} diff --git a/packages/google-auth/.kokoro/continuous/common.cfg b/packages/google-auth/.kokoro/continuous/common.cfg deleted file mode 100644 index 10910e357558..000000000000 --- a/packages/google-auth/.kokoro/continuous/common.cfg +++ /dev/null @@ -1,27 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -# Build logs will be here -action { - define_artifacts { - regex: "**/*sponge_log.xml" - } -} - -# Download trampoline resources. -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" - -# Download resources for system tests (service account key, etc.) -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-auth-library-python" - -# Use the trampoline script to run in docker. -build_file: "google-auth-library-python/.kokoro/trampoline.sh" - -# Configure the docker image for kokoro-trampoline. -env_vars: { - key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-kokoro-resources/python-multi" -} -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/google-auth-library-python/.kokoro/build.sh" -} diff --git a/packages/google-auth/.kokoro/continuous/continuous.cfg b/packages/google-auth/.kokoro/continuous/continuous.cfg deleted file mode 100644 index 8f43917d92fe..000000000000 --- a/packages/google-auth/.kokoro/continuous/continuous.cfg +++ /dev/null @@ -1 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto \ No newline at end of file diff --git a/packages/google-auth/.kokoro/continuous/prerelease-deps.cfg b/packages/google-auth/.kokoro/continuous/prerelease-deps.cfg deleted file mode 100644 index 3595fb43f5c0..000000000000 --- a/packages/google-auth/.kokoro/continuous/prerelease-deps.cfg +++ /dev/null @@ -1,7 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -# Only run this nox session. -env_vars: { - key: "NOX_SESSION" - value: "prerelease_deps" -} diff --git a/packages/google-auth/.kokoro/populate-secrets.sh b/packages/google-auth/.kokoro/populate-secrets.sh deleted file mode 100755 index c435402f473e..000000000000 --- a/packages/google-auth/.kokoro/populate-secrets.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash -# Copyright 2024 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -set -eo pipefail - -function now { date +"%Y-%m-%d %H:%M:%S" | tr -d '\n' ;} -function msg { println "$*" >&2 ;} -function println { printf '%s\n' "$(now) $*" ;} - - -# Populates requested secrets set in SECRET_MANAGER_KEYS from service account: -# kokoro-trampoline@cloud-devrel-kokoro-resources.iam.gserviceaccount.com -SECRET_LOCATION="${KOKORO_GFILE_DIR}/secret_manager" -msg "Creating folder on disk for secrets: ${SECRET_LOCATION}" -mkdir -p ${SECRET_LOCATION} -for key in $(echo ${SECRET_MANAGER_KEYS} | sed "s/,/ /g") -do - msg "Retrieving secret ${key}" - docker run --entrypoint=gcloud \ - --volume=${KOKORO_GFILE_DIR}:${KOKORO_GFILE_DIR} \ - gcr.io/google.com/cloudsdktool/cloud-sdk \ - secrets versions access latest \ - --project cloud-devrel-kokoro-resources \ - --secret ${key} > \ - "${SECRET_LOCATION}/${key}" - if [[ $? == 0 ]]; then - msg "Secret written to ${SECRET_LOCATION}/${key}" - else - msg "Error retrieving secret ${key}" - fi -done diff --git a/packages/google-auth/.kokoro/presubmit/common.cfg b/packages/google-auth/.kokoro/presubmit/common.cfg deleted file mode 100644 index 10910e357558..000000000000 --- a/packages/google-auth/.kokoro/presubmit/common.cfg +++ /dev/null @@ -1,27 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -# Build logs will be here -action { - define_artifacts { - regex: "**/*sponge_log.xml" - } -} - -# Download trampoline resources. -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" - -# Download resources for system tests (service account key, etc.) -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-auth-library-python" - -# Use the trampoline script to run in docker. -build_file: "google-auth-library-python/.kokoro/trampoline.sh" - -# Configure the docker image for kokoro-trampoline. -env_vars: { - key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-kokoro-resources/python-multi" -} -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/google-auth-library-python/.kokoro/build.sh" -} diff --git a/packages/google-auth/.kokoro/presubmit/prerelease-deps.cfg b/packages/google-auth/.kokoro/presubmit/prerelease-deps.cfg deleted file mode 100644 index 3595fb43f5c0..000000000000 --- a/packages/google-auth/.kokoro/presubmit/prerelease-deps.cfg +++ /dev/null @@ -1,7 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -# Only run this nox session. -env_vars: { - key: "NOX_SESSION" - value: "prerelease_deps" -} diff --git a/packages/google-auth/.kokoro/presubmit/presubmit.cfg b/packages/google-auth/.kokoro/presubmit/presubmit.cfg deleted file mode 100644 index 8f43917d92fe..000000000000 --- a/packages/google-auth/.kokoro/presubmit/presubmit.cfg +++ /dev/null @@ -1 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto \ No newline at end of file diff --git a/packages/google-auth/.kokoro/presubmit/system-3.10.cfg b/packages/google-auth/.kokoro/presubmit/system-3.10.cfg deleted file mode 100644 index 0393b98b13f3..000000000000 --- a/packages/google-auth/.kokoro/presubmit/system-3.10.cfg +++ /dev/null @@ -1,5 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/google-auth-library-python/.kokoro/build-systests.sh" -} diff --git a/packages/google-auth/.kokoro/samples-test-setup.sh b/packages/google-auth/.kokoro/samples-test-setup.sh deleted file mode 100755 index 5be9713ef7a0..000000000000 --- a/packages/google-auth/.kokoro/samples-test-setup.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/bin/bash -# Copyright 2018 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -set -eo pipefail - -if [[ -z "${PROJECT_ROOT:-}" ]]; then - PROJECT_ROOT="github/google-auth-library-python" -fi - -cd "${PROJECT_ROOT}" - -# Disable buffering, so that the logs stream through. -export PYTHONUNBUFFERED=1 - -# Remove old nox -python3 -m pip uninstall --yes --quiet nox-automation - -# Install nox -python3 -m pip install --upgrade --quiet nox -python3 -m nox --version - -# Setup service account credentials. -export GOOGLE_APPLICATION_CREDENTIALS=${KOKORO_GFILE_DIR}/service-account.json - -# Setup project id. -export PROJECT_ID=$(cat "${KOKORO_GFILE_DIR}/project-id.txt") - -# Activate gcloud with service account credentials -gcloud auth activate-service-account --key-file=$GOOGLE_APPLICATION_CREDENTIALS -gcloud config set project ${PROJECT_ID} - -# Decrypt system test secrets -./scripts/decrypt-secrets.sh - -# Run samples tests which use a different noxfile -python3 -m nox -f samples/cloud-client/snippets/noxfile.py -s "$RUN_TESTS_SESSION" diff --git a/packages/google-auth/.kokoro/samples/lint/common.cfg b/packages/google-auth/.kokoro/samples/lint/common.cfg deleted file mode 100644 index e38920a08be4..000000000000 --- a/packages/google-auth/.kokoro/samples/lint/common.cfg +++ /dev/null @@ -1,38 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -# Build logs will be here -action { - define_artifacts { - regex: "**/*sponge_log.xml" - } -} - -# Specify which tests to run -env_vars: { - key: "RUN_TESTS_SESSION" - value: "lint" -} - - -# Download trampoline resources. -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" - -# Download resources for system tests (service account key, etc.) -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-auth-library-python" - -# Use the trampoline script to run in docker. -build_file: "google-auth-library-python/.kokoro/trampoline.sh" - -# Configure the docker image for kokoro-trampoline. -env_vars: { - key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-kokoro-resources/python-multi" -} -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/google-auth-library-python/.kokoro/build.sh" -} -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/google-auth-library-python/.kokoro/samples-test-setup.sh" -} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/lint/continuous.cfg b/packages/google-auth/.kokoro/samples/lint/continuous.cfg deleted file mode 100644 index a1c8d9759c88..000000000000 --- a/packages/google-auth/.kokoro/samples/lint/continuous.cfg +++ /dev/null @@ -1,6 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -env_vars: { - key: "INSTALL_LIBRARY_FROM_SOURCE" - value: "True" -} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/lint/periodic.cfg b/packages/google-auth/.kokoro/samples/lint/periodic.cfg deleted file mode 100644 index 50fec9649732..000000000000 --- a/packages/google-auth/.kokoro/samples/lint/periodic.cfg +++ /dev/null @@ -1,6 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -env_vars: { - key: "INSTALL_LIBRARY_FROM_SOURCE" - value: "False" -} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/lint/presubmit.cfg b/packages/google-auth/.kokoro/samples/lint/presubmit.cfg deleted file mode 100644 index a1c8d9759c88..000000000000 --- a/packages/google-auth/.kokoro/samples/lint/presubmit.cfg +++ /dev/null @@ -1,6 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -env_vars: { - key: "INSTALL_LIBRARY_FROM_SOURCE" - value: "True" -} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.10/common.cfg b/packages/google-auth/.kokoro/samples/python3.10/common.cfg deleted file mode 100644 index 022eaafda85c..000000000000 --- a/packages/google-auth/.kokoro/samples/python3.10/common.cfg +++ /dev/null @@ -1,37 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -# Build logs will be here -action { - define_artifacts { - regex: "**/*sponge_log.xml" - } -} - -# Specify which tests to run -env_vars: { - key: "RUN_TESTS_SESSION" - value: "unit-3.10" -} - -# Download trampoline resources. -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" - -# Download resources for system tests (service account key, etc.) -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-auth-library-python" - -# Use the trampoline script to run in docker. -build_file: "google-auth-library-python/.kokoro/trampoline.sh" - -# Configure the docker image for kokoro-trampoline. -env_vars: { - key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-kokoro-resources/python-multi" -} -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/google-auth-library-python/.kokoro/build.sh" -} -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/google-auth-library-python/.kokoro/samples-test-setup.sh" -} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.10/continuous.cfg b/packages/google-auth/.kokoro/samples/python3.10/continuous.cfg deleted file mode 100644 index a1c8d9759c88..000000000000 --- a/packages/google-auth/.kokoro/samples/python3.10/continuous.cfg +++ /dev/null @@ -1,6 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -env_vars: { - key: "INSTALL_LIBRARY_FROM_SOURCE" - value: "True" -} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.10/periodic-head.cfg b/packages/google-auth/.kokoro/samples/python3.10/periodic-head.cfg deleted file mode 100644 index 83eace873ccb..000000000000 --- a/packages/google-auth/.kokoro/samples/python3.10/periodic-head.cfg +++ /dev/null @@ -1,11 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -env_vars: { - key: "INSTALL_LIBRARY_FROM_SOURCE" - value: "True" -} - -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/google-auth-library-python/.kokoro/test-samples-against-head.sh" -} diff --git a/packages/google-auth/.kokoro/samples/python3.10/periodic.cfg b/packages/google-auth/.kokoro/samples/python3.10/periodic.cfg deleted file mode 100644 index 71cd1e597e38..000000000000 --- a/packages/google-auth/.kokoro/samples/python3.10/periodic.cfg +++ /dev/null @@ -1,6 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -env_vars: { - key: "INSTALL_LIBRARY_FROM_SOURCE" - value: "False" -} diff --git a/packages/google-auth/.kokoro/samples/python3.10/presubmit.cfg b/packages/google-auth/.kokoro/samples/python3.10/presubmit.cfg deleted file mode 100644 index a1c8d9759c88..000000000000 --- a/packages/google-auth/.kokoro/samples/python3.10/presubmit.cfg +++ /dev/null @@ -1,6 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -env_vars: { - key: "INSTALL_LIBRARY_FROM_SOURCE" - value: "True" -} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.11/common.cfg b/packages/google-auth/.kokoro/samples/python3.11/common.cfg deleted file mode 100644 index 25be29326bc2..000000000000 --- a/packages/google-auth/.kokoro/samples/python3.11/common.cfg +++ /dev/null @@ -1,37 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -# Build logs will be here -action { - define_artifacts { - regex: "**/*sponge_log.xml" - } -} - -# Specify which tests to run -env_vars: { - key: "RUN_TESTS_SESSION" - value: "unit-3.11" -} - -# Download trampoline resources. -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" - -# Download resources for system tests (service account key, etc.) -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-auth-library-python" - -# Use the trampoline script to run in docker. -build_file: "google-auth-library-python/.kokoro/trampoline.sh" - -# Configure the docker image for kokoro-trampoline. -env_vars: { - key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-kokoro-resources/python-multi" -} -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/google-auth-library-python/.kokoro/build.sh" -} -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/google-auth-library-python/.kokoro/samples-test-setup.sh" -} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.11/continuous.cfg b/packages/google-auth/.kokoro/samples/python3.11/continuous.cfg deleted file mode 100644 index a1c8d9759c88..000000000000 --- a/packages/google-auth/.kokoro/samples/python3.11/continuous.cfg +++ /dev/null @@ -1,6 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -env_vars: { - key: "INSTALL_LIBRARY_FROM_SOURCE" - value: "True" -} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.11/periodic-head.cfg b/packages/google-auth/.kokoro/samples/python3.11/periodic-head.cfg deleted file mode 100644 index 83eace873ccb..000000000000 --- a/packages/google-auth/.kokoro/samples/python3.11/periodic-head.cfg +++ /dev/null @@ -1,11 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -env_vars: { - key: "INSTALL_LIBRARY_FROM_SOURCE" - value: "True" -} - -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/google-auth-library-python/.kokoro/test-samples-against-head.sh" -} diff --git a/packages/google-auth/.kokoro/samples/python3.11/periodic.cfg b/packages/google-auth/.kokoro/samples/python3.11/periodic.cfg deleted file mode 100644 index 71cd1e597e38..000000000000 --- a/packages/google-auth/.kokoro/samples/python3.11/periodic.cfg +++ /dev/null @@ -1,6 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -env_vars: { - key: "INSTALL_LIBRARY_FROM_SOURCE" - value: "False" -} diff --git a/packages/google-auth/.kokoro/samples/python3.11/presubmit.cfg b/packages/google-auth/.kokoro/samples/python3.11/presubmit.cfg deleted file mode 100644 index a1c8d9759c88..000000000000 --- a/packages/google-auth/.kokoro/samples/python3.11/presubmit.cfg +++ /dev/null @@ -1,6 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -env_vars: { - key: "INSTALL_LIBRARY_FROM_SOURCE" - value: "True" -} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.12/common.cfg b/packages/google-auth/.kokoro/samples/python3.12/common.cfg deleted file mode 100644 index 25d2ea1deccc..000000000000 --- a/packages/google-auth/.kokoro/samples/python3.12/common.cfg +++ /dev/null @@ -1,37 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -# Build logs will be here -action { - define_artifacts { - regex: "**/*sponge_log.xml" - } -} - -# Specify which tests to run -env_vars: { - key: "RUN_TESTS_SESSION" - value: "unit-3.12" -} - -# Download trampoline resources. -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" - -# Download resources for system tests (service account key, etc.) -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-auth-library-python" - -# Use the trampoline script to run in docker. -build_file: "google-auth-library-python/.kokoro/trampoline.sh" - -# Configure the docker image for kokoro-trampoline. -env_vars: { - key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-kokoro-resources/python-multi" -} -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/google-auth-library-python/.kokoro/build.sh" -} -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/google-auth-library-python/.kokoro/samples-test-setup.sh" -} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.12/continuous.cfg b/packages/google-auth/.kokoro/samples/python3.12/continuous.cfg deleted file mode 100644 index a1c8d9759c88..000000000000 --- a/packages/google-auth/.kokoro/samples/python3.12/continuous.cfg +++ /dev/null @@ -1,6 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -env_vars: { - key: "INSTALL_LIBRARY_FROM_SOURCE" - value: "True" -} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.12/periodic-head.cfg b/packages/google-auth/.kokoro/samples/python3.12/periodic-head.cfg deleted file mode 100644 index 83eace873ccb..000000000000 --- a/packages/google-auth/.kokoro/samples/python3.12/periodic-head.cfg +++ /dev/null @@ -1,11 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -env_vars: { - key: "INSTALL_LIBRARY_FROM_SOURCE" - value: "True" -} - -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/google-auth-library-python/.kokoro/test-samples-against-head.sh" -} diff --git a/packages/google-auth/.kokoro/samples/python3.12/periodic.cfg b/packages/google-auth/.kokoro/samples/python3.12/periodic.cfg deleted file mode 100644 index 71cd1e597e38..000000000000 --- a/packages/google-auth/.kokoro/samples/python3.12/periodic.cfg +++ /dev/null @@ -1,6 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -env_vars: { - key: "INSTALL_LIBRARY_FROM_SOURCE" - value: "False" -} diff --git a/packages/google-auth/.kokoro/samples/python3.12/presubmit.cfg b/packages/google-auth/.kokoro/samples/python3.12/presubmit.cfg deleted file mode 100644 index a1c8d9759c88..000000000000 --- a/packages/google-auth/.kokoro/samples/python3.12/presubmit.cfg +++ /dev/null @@ -1,6 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -env_vars: { - key: "INSTALL_LIBRARY_FROM_SOURCE" - value: "True" -} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.13/common.cfg b/packages/google-auth/.kokoro/samples/python3.13/common.cfg deleted file mode 100644 index c1dd4316bc30..000000000000 --- a/packages/google-auth/.kokoro/samples/python3.13/common.cfg +++ /dev/null @@ -1,37 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -# Build logs will be here -action { - define_artifacts { - regex: "**/*sponge_log.xml" - } -} - -# Specify which tests to run -env_vars: { - key: "RUN_TESTS_SESSION" - value: "unit-3.13" -} - -# Download trampoline resources. -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" - -# Download resources for system tests (service account key, etc.) -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-auth-library-python" - -# Use the trampoline script to run in docker. -build_file: "google-auth-library-python/.kokoro/trampoline.sh" - -# Configure the docker image for kokoro-trampoline. -env_vars: { - key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-kokoro-resources/python-multi" -} -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/google-auth-library-python/.kokoro/build.sh" -} -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/google-auth-library-python/.kokoro/samples-test-setup.sh" -} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.13/continuous.cfg b/packages/google-auth/.kokoro/samples/python3.13/continuous.cfg deleted file mode 100644 index a1c8d9759c88..000000000000 --- a/packages/google-auth/.kokoro/samples/python3.13/continuous.cfg +++ /dev/null @@ -1,6 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -env_vars: { - key: "INSTALL_LIBRARY_FROM_SOURCE" - value: "True" -} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.13/periodic-head.cfg b/packages/google-auth/.kokoro/samples/python3.13/periodic-head.cfg deleted file mode 100644 index 83eace873ccb..000000000000 --- a/packages/google-auth/.kokoro/samples/python3.13/periodic-head.cfg +++ /dev/null @@ -1,11 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -env_vars: { - key: "INSTALL_LIBRARY_FROM_SOURCE" - value: "True" -} - -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/google-auth-library-python/.kokoro/test-samples-against-head.sh" -} diff --git a/packages/google-auth/.kokoro/samples/python3.13/periodic.cfg b/packages/google-auth/.kokoro/samples/python3.13/periodic.cfg deleted file mode 100644 index 71cd1e597e38..000000000000 --- a/packages/google-auth/.kokoro/samples/python3.13/periodic.cfg +++ /dev/null @@ -1,6 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -env_vars: { - key: "INSTALL_LIBRARY_FROM_SOURCE" - value: "False" -} diff --git a/packages/google-auth/.kokoro/samples/python3.13/presubmit.cfg b/packages/google-auth/.kokoro/samples/python3.13/presubmit.cfg deleted file mode 100644 index a1c8d9759c88..000000000000 --- a/packages/google-auth/.kokoro/samples/python3.13/presubmit.cfg +++ /dev/null @@ -1,6 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -env_vars: { - key: "INSTALL_LIBRARY_FROM_SOURCE" - value: "True" -} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.7/common.cfg b/packages/google-auth/.kokoro/samples/python3.7/common.cfg deleted file mode 100644 index 123c3e122324..000000000000 --- a/packages/google-auth/.kokoro/samples/python3.7/common.cfg +++ /dev/null @@ -1,38 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -# Build logs will be here -action { - define_artifacts { - regex: "**/*sponge_log.xml" - } -} - - -# Specify which tests to run -env_vars: { - key: "RUN_TESTS_SESSION" - value: "unit-3.7" -} - -# Download trampoline resources. -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" - -# Download resources for system tests (service account key, etc.) -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-auth-library-python" - -# Use the trampoline script to run in docker. -build_file: "google-auth-library-python/.kokoro/trampoline.sh" - -# Configure the docker image for kokoro-trampoline. -env_vars: { - key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-kokoro-resources/python-multi" -} -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/google-auth-library-python/.kokoro/build.sh" -} -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/google-auth-library-python/.kokoro/samples-test-setup.sh" -} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.7/continuous.cfg b/packages/google-auth/.kokoro/samples/python3.7/continuous.cfg deleted file mode 100644 index a1c8d9759c88..000000000000 --- a/packages/google-auth/.kokoro/samples/python3.7/continuous.cfg +++ /dev/null @@ -1,6 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -env_vars: { - key: "INSTALL_LIBRARY_FROM_SOURCE" - value: "True" -} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.7/periodic-head.cfg b/packages/google-auth/.kokoro/samples/python3.7/periodic-head.cfg deleted file mode 100644 index 83eace873ccb..000000000000 --- a/packages/google-auth/.kokoro/samples/python3.7/periodic-head.cfg +++ /dev/null @@ -1,11 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -env_vars: { - key: "INSTALL_LIBRARY_FROM_SOURCE" - value: "True" -} - -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/google-auth-library-python/.kokoro/test-samples-against-head.sh" -} diff --git a/packages/google-auth/.kokoro/samples/python3.7/periodic.cfg b/packages/google-auth/.kokoro/samples/python3.7/periodic.cfg deleted file mode 100644 index 71cd1e597e38..000000000000 --- a/packages/google-auth/.kokoro/samples/python3.7/periodic.cfg +++ /dev/null @@ -1,6 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -env_vars: { - key: "INSTALL_LIBRARY_FROM_SOURCE" - value: "False" -} diff --git a/packages/google-auth/.kokoro/samples/python3.7/presubmit.cfg b/packages/google-auth/.kokoro/samples/python3.7/presubmit.cfg deleted file mode 100644 index a1c8d9759c88..000000000000 --- a/packages/google-auth/.kokoro/samples/python3.7/presubmit.cfg +++ /dev/null @@ -1,6 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -env_vars: { - key: "INSTALL_LIBRARY_FROM_SOURCE" - value: "True" -} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.8/common.cfg b/packages/google-auth/.kokoro/samples/python3.8/common.cfg deleted file mode 100644 index 62108f6be0ab..000000000000 --- a/packages/google-auth/.kokoro/samples/python3.8/common.cfg +++ /dev/null @@ -1,37 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -# Build logs will be here -action { - define_artifacts { - regex: "**/*sponge_log.xml" - } -} - -# Specify which tests to run -env_vars: { - key: "RUN_TESTS_SESSION" - value: "unit-3.8" -} - -# Download trampoline resources. -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" - -# Download resources for system tests (service account key, etc.) -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-auth-library-python" - -# Use the trampoline script to run in docker. -build_file: "google-auth-library-python/.kokoro/trampoline.sh" - -# Configure the docker image for kokoro-trampoline. -env_vars: { - key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-kokoro-resources/python-multi" -} -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/google-auth-library-python/.kokoro/build.sh" -} -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/google-auth-library-python/.kokoro/samples-test-setup.sh" -} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.8/continuous.cfg b/packages/google-auth/.kokoro/samples/python3.8/continuous.cfg deleted file mode 100644 index a1c8d9759c88..000000000000 --- a/packages/google-auth/.kokoro/samples/python3.8/continuous.cfg +++ /dev/null @@ -1,6 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -env_vars: { - key: "INSTALL_LIBRARY_FROM_SOURCE" - value: "True" -} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.8/periodic-head.cfg b/packages/google-auth/.kokoro/samples/python3.8/periodic-head.cfg deleted file mode 100644 index 83eace873ccb..000000000000 --- a/packages/google-auth/.kokoro/samples/python3.8/periodic-head.cfg +++ /dev/null @@ -1,11 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -env_vars: { - key: "INSTALL_LIBRARY_FROM_SOURCE" - value: "True" -} - -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/google-auth-library-python/.kokoro/test-samples-against-head.sh" -} diff --git a/packages/google-auth/.kokoro/samples/python3.8/periodic.cfg b/packages/google-auth/.kokoro/samples/python3.8/periodic.cfg deleted file mode 100644 index 71cd1e597e38..000000000000 --- a/packages/google-auth/.kokoro/samples/python3.8/periodic.cfg +++ /dev/null @@ -1,6 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -env_vars: { - key: "INSTALL_LIBRARY_FROM_SOURCE" - value: "False" -} diff --git a/packages/google-auth/.kokoro/samples/python3.8/presubmit.cfg b/packages/google-auth/.kokoro/samples/python3.8/presubmit.cfg deleted file mode 100644 index a1c8d9759c88..000000000000 --- a/packages/google-auth/.kokoro/samples/python3.8/presubmit.cfg +++ /dev/null @@ -1,6 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -env_vars: { - key: "INSTALL_LIBRARY_FROM_SOURCE" - value: "True" -} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.9/common.cfg b/packages/google-auth/.kokoro/samples/python3.9/common.cfg deleted file mode 100644 index 2649c6c6eeed..000000000000 --- a/packages/google-auth/.kokoro/samples/python3.9/common.cfg +++ /dev/null @@ -1,37 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -# Build logs will be here -action { - define_artifacts { - regex: "**/*sponge_log.xml" - } -} - -# Specify which tests to run -env_vars: { - key: "RUN_TESTS_SESSION" - value: "unit-3.9" -} - -# Download trampoline resources. -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" - -# Download resources for system tests (service account key, etc.) -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-auth-library-python" - -# Use the trampoline script to run in docker. -build_file: "google-auth-library-python/.kokoro/trampoline.sh" - -# Configure the docker image for kokoro-trampoline. -env_vars: { - key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-kokoro-resources/python-multi" -} -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/google-auth-library-python/.kokoro/build.sh" -} -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/google-auth-library-python/.kokoro/samples-test-setup.sh" -} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.9/continuous.cfg b/packages/google-auth/.kokoro/samples/python3.9/continuous.cfg deleted file mode 100644 index a1c8d9759c88..000000000000 --- a/packages/google-auth/.kokoro/samples/python3.9/continuous.cfg +++ /dev/null @@ -1,6 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -env_vars: { - key: "INSTALL_LIBRARY_FROM_SOURCE" - value: "True" -} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.9/periodic-head.cfg b/packages/google-auth/.kokoro/samples/python3.9/periodic-head.cfg deleted file mode 100644 index 83eace873ccb..000000000000 --- a/packages/google-auth/.kokoro/samples/python3.9/periodic-head.cfg +++ /dev/null @@ -1,11 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -env_vars: { - key: "INSTALL_LIBRARY_FROM_SOURCE" - value: "True" -} - -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/google-auth-library-python/.kokoro/test-samples-against-head.sh" -} diff --git a/packages/google-auth/.kokoro/samples/python3.9/periodic.cfg b/packages/google-auth/.kokoro/samples/python3.9/periodic.cfg deleted file mode 100644 index 71cd1e597e38..000000000000 --- a/packages/google-auth/.kokoro/samples/python3.9/periodic.cfg +++ /dev/null @@ -1,6 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -env_vars: { - key: "INSTALL_LIBRARY_FROM_SOURCE" - value: "False" -} diff --git a/packages/google-auth/.kokoro/samples/python3.9/presubmit.cfg b/packages/google-auth/.kokoro/samples/python3.9/presubmit.cfg deleted file mode 100644 index a1c8d9759c88..000000000000 --- a/packages/google-auth/.kokoro/samples/python3.9/presubmit.cfg +++ /dev/null @@ -1,6 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -env_vars: { - key: "INSTALL_LIBRARY_FROM_SOURCE" - value: "True" -} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/test-samples-against-head.sh b/packages/google-auth/.kokoro/test-samples-against-head.sh deleted file mode 100755 index e9d8bd79a644..000000000000 --- a/packages/google-auth/.kokoro/test-samples-against-head.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash -# Copyright 2024 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# A customized test runner for samples. -# -# For periodic builds, you can specify this file for testing against head. - -# `-e` enables the script to automatically fail when a command fails -# `-o pipefail` sets the exit code to the rightmost comment to exit with a non-zero -set -eo pipefail -# Enables `**` to include files nested inside sub-folders -shopt -s globstar - -exec .kokoro/test-samples-impl.sh diff --git a/packages/google-auth/.kokoro/test-samples-impl.sh b/packages/google-auth/.kokoro/test-samples-impl.sh deleted file mode 100755 index 53e365bc4e79..000000000000 --- a/packages/google-auth/.kokoro/test-samples-impl.sh +++ /dev/null @@ -1,103 +0,0 @@ -#!/bin/bash -# Copyright 2024 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -# `-e` enables the script to automatically fail when a command fails -# `-o pipefail` sets the exit code to the rightmost comment to exit with a non-zero -set -eo pipefail -# Enables `**` to include files nested inside sub-folders -shopt -s globstar - -# Exit early if samples don't exist -if ! find samples -name 'requirements.txt' | grep -q .; then - echo "No tests run. './samples/**/requirements.txt' not found" - exit 0 -fi - -# Disable buffering, so that the logs stream through. -export PYTHONUNBUFFERED=1 - -# Debug: show build environment -env | grep KOKORO - -# Install nox -# `virtualenv==20.26.6` is added for Python 3.7 compatibility -python3.9 -m pip install --upgrade --quiet nox virtualenv==20.26.6 - -# Use secrets acessor service account to get secrets -if [[ -f "${KOKORO_GFILE_DIR}/secrets_viewer_service_account.json" ]]; then - gcloud auth activate-service-account \ - --key-file="${KOKORO_GFILE_DIR}/secrets_viewer_service_account.json" \ - --project="cloud-devrel-kokoro-resources" -fi - -# This script will create 3 files: -# - testing/test-env.sh -# - testing/service-account.json -# - testing/client-secrets.json -./scripts/decrypt-secrets.sh - -source ./testing/test-env.sh -export GOOGLE_APPLICATION_CREDENTIALS=$(pwd)/testing/service-account.json - -# For cloud-run session, we activate the service account for gcloud sdk. -gcloud auth activate-service-account \ - --key-file "${GOOGLE_APPLICATION_CREDENTIALS}" - -export GOOGLE_CLIENT_SECRETS=$(pwd)/testing/client-secrets.json - -echo -e "\n******************** TESTING PROJECTS ********************" - -# Switch to 'fail at end' to allow all tests to complete before exiting. -set +e -# Use RTN to return a non-zero value if the test fails. -RTN=0 -ROOT=$(pwd) -# Find all requirements.txt in the samples directory (may break on whitespace). -for file in samples/**/requirements.txt; do - cd "$ROOT" - # Navigate to the project folder. - file=$(dirname "$file") - cd "$file" - - echo "------------------------------------------------------------" - echo "- testing $file" - echo "------------------------------------------------------------" - - # Use nox to execute the tests for the project. - python3.9 -m nox -s "$RUN_TESTS_SESSION" - EXIT=$? - - # If this is a periodic build, send the test log to the FlakyBot. - # See https://github.com/googleapis/repo-automation-bots/tree/main/packages/flakybot. - if [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"periodic"* ]]; then - chmod +x $KOKORO_GFILE_DIR/linux_amd64/flakybot - $KOKORO_GFILE_DIR/linux_amd64/flakybot - fi - - if [[ $EXIT -ne 0 ]]; then - RTN=1 - echo -e "\n Testing failed: Nox returned a non-zero exit code. \n" - else - echo -e "\n Testing completed.\n" - fi - -done -cd "$ROOT" - -# Workaround for Kokoro permissions issue: delete secrets -rm testing/{test-env.sh,client-secrets.json,service-account.json} - -exit "$RTN" diff --git a/packages/google-auth/.kokoro/test-samples.sh b/packages/google-auth/.kokoro/test-samples.sh deleted file mode 100755 index 7933d820149a..000000000000 --- a/packages/google-auth/.kokoro/test-samples.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/bin/bash -# Copyright 2024 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# The default test runner for samples. -# -# For periodic builds, we rewinds the repo to the latest release, and -# run test-samples-impl.sh. - -# `-e` enables the script to automatically fail when a command fails -# `-o pipefail` sets the exit code to the rightmost comment to exit with a non-zero -set -eo pipefail -# Enables `**` to include files nested inside sub-folders -shopt -s globstar - -# Run periodic samples tests at latest release -if [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"periodic"* ]]; then - # preserving the test runner implementation. - cp .kokoro/test-samples-impl.sh "${TMPDIR}/test-samples-impl.sh" - echo "--- IMPORTANT IMPORTANT IMPORTANT ---" - echo "Now we rewind the repo back to the latest release..." - LATEST_RELEASE=$(git describe --abbrev=0 --tags) - git checkout $LATEST_RELEASE - echo "The current head is: " - echo $(git rev-parse --verify HEAD) - echo "--- IMPORTANT IMPORTANT IMPORTANT ---" - # move back the test runner implementation if there's no file. - if [ ! -f .kokoro/test-samples-impl.sh ]; then - cp "${TMPDIR}/test-samples-impl.sh" .kokoro/test-samples-impl.sh - fi -fi - -exec .kokoro/test-samples-impl.sh diff --git a/packages/google-auth/.kokoro/trampoline.sh b/packages/google-auth/.kokoro/trampoline.sh deleted file mode 100755 index 48f79699706e..000000000000 --- a/packages/google-auth/.kokoro/trampoline.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash -# Copyright 2024 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -set -eo pipefail - -# Always run the cleanup script, regardless of the success of bouncing into -# the container. -function cleanup() { - chmod +x ${KOKORO_GFILE_DIR}/trampoline_cleanup.sh - ${KOKORO_GFILE_DIR}/trampoline_cleanup.sh - echo "cleanup"; -} -trap cleanup EXIT - -$(dirname $0)/populate-secrets.sh # Secret Manager secrets. -python3 "${KOKORO_GFILE_DIR}/trampoline_v1.py" \ No newline at end of file diff --git a/packages/google-auth/.kokoro/trampoline_v2.sh b/packages/google-auth/.kokoro/trampoline_v2.sh deleted file mode 100755 index 35fa529231dc..000000000000 --- a/packages/google-auth/.kokoro/trampoline_v2.sh +++ /dev/null @@ -1,487 +0,0 @@ -#!/usr/bin/env bash -# Copyright 2024 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# trampoline_v2.sh -# -# This script does 3 things. -# -# 1. Prepare the Docker image for the test -# 2. Run the Docker with appropriate flags to run the test -# 3. Upload the newly built Docker image -# -# in a way that is somewhat compatible with trampoline_v1. -# -# To run this script, first download few files from gcs to /dev/shm. -# (/dev/shm is passed into the container as KOKORO_GFILE_DIR). -# -# gsutil cp gs://cloud-devrel-kokoro-resources/python-docs-samples/secrets_viewer_service_account.json /dev/shm -# gsutil cp gs://cloud-devrel-kokoro-resources/python-docs-samples/automl_secrets.txt /dev/shm -# -# Then run the script. -# .kokoro/trampoline_v2.sh -# -# These environment variables are required: -# TRAMPOLINE_IMAGE: The docker image to use. -# TRAMPOLINE_DOCKERFILE: The location of the Dockerfile. -# -# You can optionally change these environment variables: -# TRAMPOLINE_IMAGE_UPLOAD: -# (true|false): Whether to upload the Docker image after the -# successful builds. -# TRAMPOLINE_BUILD_FILE: The script to run in the docker container. -# TRAMPOLINE_WORKSPACE: The workspace path in the docker container. -# Defaults to /workspace. -# Potentially there are some repo specific envvars in .trampolinerc in -# the project root. - - -set -euo pipefail - -TRAMPOLINE_VERSION="2.0.5" - -if command -v tput >/dev/null && [[ -n "${TERM:-}" ]]; then - readonly IO_COLOR_RED="$(tput setaf 1)" - readonly IO_COLOR_GREEN="$(tput setaf 2)" - readonly IO_COLOR_YELLOW="$(tput setaf 3)" - readonly IO_COLOR_RESET="$(tput sgr0)" -else - readonly IO_COLOR_RED="" - readonly IO_COLOR_GREEN="" - readonly IO_COLOR_YELLOW="" - readonly IO_COLOR_RESET="" -fi - -function function_exists { - [ $(LC_ALL=C type -t $1)"" == "function" ] -} - -# Logs a message using the given color. The first argument must be one -# of the IO_COLOR_* variables defined above, such as -# "${IO_COLOR_YELLOW}". The remaining arguments will be logged in the -# given color. The log message will also have an RFC-3339 timestamp -# prepended (in UTC). You can disable the color output by setting -# TERM=vt100. -function log_impl() { - local color="$1" - shift - local timestamp="$(date -u "+%Y-%m-%dT%H:%M:%SZ")" - echo "================================================================" - echo "${color}${timestamp}:" "$@" "${IO_COLOR_RESET}" - echo "================================================================" -} - -# Logs the given message with normal coloring and a timestamp. -function log() { - log_impl "${IO_COLOR_RESET}" "$@" -} - -# Logs the given message in green with a timestamp. -function log_green() { - log_impl "${IO_COLOR_GREEN}" "$@" -} - -# Logs the given message in yellow with a timestamp. -function log_yellow() { - log_impl "${IO_COLOR_YELLOW}" "$@" -} - -# Logs the given message in red with a timestamp. -function log_red() { - log_impl "${IO_COLOR_RED}" "$@" -} - -readonly tmpdir=$(mktemp -d -t ci-XXXXXXXX) -readonly tmphome="${tmpdir}/h" -mkdir -p "${tmphome}" - -function cleanup() { - rm -rf "${tmpdir}" -} -trap cleanup EXIT - -RUNNING_IN_CI="${RUNNING_IN_CI:-false}" - -# The workspace in the container, defaults to /workspace. -TRAMPOLINE_WORKSPACE="${TRAMPOLINE_WORKSPACE:-/workspace}" - -pass_down_envvars=( - # TRAMPOLINE_V2 variables. - # Tells scripts whether they are running as part of CI or not. - "RUNNING_IN_CI" - # Indicates which CI system we're in. - "TRAMPOLINE_CI" - # Indicates the version of the script. - "TRAMPOLINE_VERSION" -) - -log_yellow "Building with Trampoline ${TRAMPOLINE_VERSION}" - -# Detect which CI systems we're in. If we're in any of the CI systems -# we support, `RUNNING_IN_CI` will be true and `TRAMPOLINE_CI` will be -# the name of the CI system. Both envvars will be passing down to the -# container for telling which CI system we're in. -if [[ -n "${KOKORO_BUILD_ID:-}" ]]; then - # descriptive env var for indicating it's on CI. - RUNNING_IN_CI="true" - TRAMPOLINE_CI="kokoro" - if [[ "${TRAMPOLINE_USE_LEGACY_SERVICE_ACCOUNT:-}" == "true" ]]; then - if [[ ! -f "${KOKORO_GFILE_DIR}/kokoro-trampoline.service-account.json" ]]; then - log_red "${KOKORO_GFILE_DIR}/kokoro-trampoline.service-account.json does not exist. Did you forget to mount cloud-devrel-kokoro-resources/trampoline? Aborting." - exit 1 - fi - # This service account will be activated later. - TRAMPOLINE_SERVICE_ACCOUNT="${KOKORO_GFILE_DIR}/kokoro-trampoline.service-account.json" - else - if [[ "${TRAMPOLINE_VERBOSE:-}" == "true" ]]; then - gcloud auth list - fi - log_yellow "Configuring Container Registry access" - gcloud auth configure-docker --quiet - fi - pass_down_envvars+=( - # KOKORO dynamic variables. - "KOKORO_BUILD_NUMBER" - "KOKORO_BUILD_ID" - "KOKORO_JOB_NAME" - "KOKORO_GIT_COMMIT" - "KOKORO_GITHUB_COMMIT" - "KOKORO_GITHUB_PULL_REQUEST_NUMBER" - "KOKORO_GITHUB_PULL_REQUEST_COMMIT" - # For FlakyBot - "KOKORO_GITHUB_COMMIT_URL" - "KOKORO_GITHUB_PULL_REQUEST_URL" - ) -elif [[ "${TRAVIS:-}" == "true" ]]; then - RUNNING_IN_CI="true" - TRAMPOLINE_CI="travis" - pass_down_envvars+=( - "TRAVIS_BRANCH" - "TRAVIS_BUILD_ID" - "TRAVIS_BUILD_NUMBER" - "TRAVIS_BUILD_WEB_URL" - "TRAVIS_COMMIT" - "TRAVIS_COMMIT_MESSAGE" - "TRAVIS_COMMIT_RANGE" - "TRAVIS_JOB_NAME" - "TRAVIS_JOB_NUMBER" - "TRAVIS_JOB_WEB_URL" - "TRAVIS_PULL_REQUEST" - "TRAVIS_PULL_REQUEST_BRANCH" - "TRAVIS_PULL_REQUEST_SHA" - "TRAVIS_PULL_REQUEST_SLUG" - "TRAVIS_REPO_SLUG" - "TRAVIS_SECURE_ENV_VARS" - "TRAVIS_TAG" - ) -elif [[ -n "${GITHUB_RUN_ID:-}" ]]; then - RUNNING_IN_CI="true" - TRAMPOLINE_CI="github-workflow" - pass_down_envvars+=( - "GITHUB_WORKFLOW" - "GITHUB_RUN_ID" - "GITHUB_RUN_NUMBER" - "GITHUB_ACTION" - "GITHUB_ACTIONS" - "GITHUB_ACTOR" - "GITHUB_REPOSITORY" - "GITHUB_EVENT_NAME" - "GITHUB_EVENT_PATH" - "GITHUB_SHA" - "GITHUB_REF" - "GITHUB_HEAD_REF" - "GITHUB_BASE_REF" - ) -elif [[ "${CIRCLECI:-}" == "true" ]]; then - RUNNING_IN_CI="true" - TRAMPOLINE_CI="circleci" - pass_down_envvars+=( - "CIRCLE_BRANCH" - "CIRCLE_BUILD_NUM" - "CIRCLE_BUILD_URL" - "CIRCLE_COMPARE_URL" - "CIRCLE_JOB" - "CIRCLE_NODE_INDEX" - "CIRCLE_NODE_TOTAL" - "CIRCLE_PREVIOUS_BUILD_NUM" - "CIRCLE_PROJECT_REPONAME" - "CIRCLE_PROJECT_USERNAME" - "CIRCLE_REPOSITORY_URL" - "CIRCLE_SHA1" - "CIRCLE_STAGE" - "CIRCLE_USERNAME" - "CIRCLE_WORKFLOW_ID" - "CIRCLE_WORKFLOW_JOB_ID" - "CIRCLE_WORKFLOW_UPSTREAM_JOB_IDS" - "CIRCLE_WORKFLOW_WORKSPACE_ID" - ) -fi - -# Configure the service account for pulling the docker image. -function repo_root() { - local dir="$1" - while [[ ! -d "${dir}/.git" ]]; do - dir="$(dirname "$dir")" - done - echo "${dir}" -} - -# Detect the project root. In CI builds, we assume the script is in -# the git tree and traverse from there, otherwise, traverse from `pwd` -# to find `.git` directory. -if [[ "${RUNNING_IN_CI:-}" == "true" ]]; then - PROGRAM_PATH="$(realpath "$0")" - PROGRAM_DIR="$(dirname "${PROGRAM_PATH}")" - PROJECT_ROOT="$(repo_root "${PROGRAM_DIR}")" -else - PROJECT_ROOT="$(repo_root $(pwd))" -fi - -log_yellow "Changing to the project root: ${PROJECT_ROOT}." -cd "${PROJECT_ROOT}" - -# To support relative path for `TRAMPOLINE_SERVICE_ACCOUNT`, we need -# to use this environment variable in `PROJECT_ROOT`. -if [[ -n "${TRAMPOLINE_SERVICE_ACCOUNT:-}" ]]; then - - mkdir -p "${tmpdir}/gcloud" - gcloud_config_dir="${tmpdir}/gcloud" - - log_yellow "Using isolated gcloud config: ${gcloud_config_dir}." - export CLOUDSDK_CONFIG="${gcloud_config_dir}" - - log_yellow "Using ${TRAMPOLINE_SERVICE_ACCOUNT} for authentication." - gcloud auth activate-service-account \ - --key-file "${TRAMPOLINE_SERVICE_ACCOUNT}" - log_yellow "Configuring Container Registry access" - gcloud auth configure-docker --quiet -fi - -required_envvars=( - # The basic trampoline configurations. - "TRAMPOLINE_IMAGE" - "TRAMPOLINE_BUILD_FILE" -) - -if [[ -f "${PROJECT_ROOT}/.trampolinerc" ]]; then - source "${PROJECT_ROOT}/.trampolinerc" -fi - -log_yellow "Checking environment variables." -for e in "${required_envvars[@]}" -do - if [[ -z "${!e:-}" ]]; then - log "Missing ${e} env var. Aborting." - exit 1 - fi -done - -# We want to support legacy style TRAMPOLINE_BUILD_FILE used with V1 -# script: e.g. "github/repo-name/.kokoro/run_tests.sh" -TRAMPOLINE_BUILD_FILE="${TRAMPOLINE_BUILD_FILE#github/*/}" -log_yellow "Using TRAMPOLINE_BUILD_FILE: ${TRAMPOLINE_BUILD_FILE}" - -# ignore error on docker operations and test execution -set +e - -log_yellow "Preparing Docker image." -# We only download the docker image in CI builds. -if [[ "${RUNNING_IN_CI:-}" == "true" ]]; then - # Download the docker image specified by `TRAMPOLINE_IMAGE` - - # We may want to add --max-concurrent-downloads flag. - - log_yellow "Start pulling the Docker image: ${TRAMPOLINE_IMAGE}." - if docker pull "${TRAMPOLINE_IMAGE}"; then - log_green "Finished pulling the Docker image: ${TRAMPOLINE_IMAGE}." - has_image="true" - else - log_red "Failed pulling the Docker image: ${TRAMPOLINE_IMAGE}." - has_image="false" - fi -else - # For local run, check if we have the image. - if docker images "${TRAMPOLINE_IMAGE}:latest" | grep "${TRAMPOLINE_IMAGE}"; then - has_image="true" - else - has_image="false" - fi -fi - - -# The default user for a Docker container has uid 0 (root). To avoid -# creating root-owned files in the build directory we tell docker to -# use the current user ID. -user_uid="$(id -u)" -user_gid="$(id -g)" -user_name="$(id -un)" - -# To allow docker in docker, we add the user to the docker group in -# the host os. -docker_gid=$(cut -d: -f3 < <(getent group docker)) - -update_cache="false" -if [[ "${TRAMPOLINE_DOCKERFILE:-none}" != "none" ]]; then - # Build the Docker image from the source. - context_dir=$(dirname "${TRAMPOLINE_DOCKERFILE}") - docker_build_flags=( - "-f" "${TRAMPOLINE_DOCKERFILE}" - "-t" "${TRAMPOLINE_IMAGE}" - "--build-arg" "UID=${user_uid}" - "--build-arg" "USERNAME=${user_name}" - ) - if [[ "${has_image}" == "true" ]]; then - docker_build_flags+=("--cache-from" "${TRAMPOLINE_IMAGE}") - fi - - log_yellow "Start building the docker image." - if [[ "${TRAMPOLINE_VERBOSE:-false}" == "true" ]]; then - echo "docker build" "${docker_build_flags[@]}" "${context_dir}" - fi - - # ON CI systems, we want to suppress docker build logs, only - # output the logs when it fails. - if [[ "${RUNNING_IN_CI:-}" == "true" ]]; then - if docker build "${docker_build_flags[@]}" "${context_dir}" \ - > "${tmpdir}/docker_build.log" 2>&1; then - if [[ "${TRAMPOLINE_VERBOSE:-}" == "true" ]]; then - cat "${tmpdir}/docker_build.log" - fi - - log_green "Finished building the docker image." - update_cache="true" - else - log_red "Failed to build the Docker image, aborting." - log_yellow "Dumping the build logs:" - cat "${tmpdir}/docker_build.log" - exit 1 - fi - else - if docker build "${docker_build_flags[@]}" "${context_dir}"; then - log_green "Finished building the docker image." - update_cache="true" - else - log_red "Failed to build the Docker image, aborting." - exit 1 - fi - fi -else - if [[ "${has_image}" != "true" ]]; then - log_red "We do not have ${TRAMPOLINE_IMAGE} locally, aborting." - exit 1 - fi -fi - -# We use an array for the flags so they are easier to document. -docker_flags=( - # Remove the container after it exists. - "--rm" - - # Use the host network. - "--network=host" - - # Run in priviledged mode. We are not using docker for sandboxing or - # isolation, just for packaging our dev tools. - "--privileged" - - # Run the docker script with the user id. Because the docker image gets to - # write in ${PWD} you typically want this to be your user id. - # To allow docker in docker, we need to use docker gid on the host. - "--user" "${user_uid}:${docker_gid}" - - # Pass down the USER. - "--env" "USER=${user_name}" - - # Mount the project directory inside the Docker container. - "--volume" "${PROJECT_ROOT}:${TRAMPOLINE_WORKSPACE}" - "--workdir" "${TRAMPOLINE_WORKSPACE}" - "--env" "PROJECT_ROOT=${TRAMPOLINE_WORKSPACE}" - - # Mount the temporary home directory. - "--volume" "${tmphome}:/h" - "--env" "HOME=/h" - - # Allow docker in docker. - "--volume" "/var/run/docker.sock:/var/run/docker.sock" - - # Mount the /tmp so that docker in docker can mount the files - # there correctly. - "--volume" "/tmp:/tmp" - # Pass down the KOKORO_GFILE_DIR and KOKORO_KEYSTORE_DIR - # TODO(tmatsuo): This part is not portable. - "--env" "TRAMPOLINE_SECRET_DIR=/secrets" - "--volume" "${KOKORO_GFILE_DIR:-/dev/shm}:/secrets/gfile" - "--env" "KOKORO_GFILE_DIR=/secrets/gfile" - "--volume" "${KOKORO_KEYSTORE_DIR:-/dev/shm}:/secrets/keystore" - "--env" "KOKORO_KEYSTORE_DIR=/secrets/keystore" -) - -# Add an option for nicer output if the build gets a tty. -if [[ -t 0 ]]; then - docker_flags+=("-it") -fi - -# Passing down env vars -for e in "${pass_down_envvars[@]}" -do - if [[ -n "${!e:-}" ]]; then - docker_flags+=("--env" "${e}=${!e}") - fi -done - -# If arguments are given, all arguments will become the commands run -# in the container, otherwise run TRAMPOLINE_BUILD_FILE. -if [[ $# -ge 1 ]]; then - log_yellow "Running the given commands '" "${@:1}" "' in the container." - readonly commands=("${@:1}") - if [[ "${TRAMPOLINE_VERBOSE:-}" == "true" ]]; then - echo docker run "${docker_flags[@]}" "${TRAMPOLINE_IMAGE}" "${commands[@]}" - fi - docker run "${docker_flags[@]}" "${TRAMPOLINE_IMAGE}" "${commands[@]}" -else - log_yellow "Running the tests in a Docker container." - docker_flags+=("--entrypoint=${TRAMPOLINE_BUILD_FILE}") - if [[ "${TRAMPOLINE_VERBOSE:-}" == "true" ]]; then - echo docker run "${docker_flags[@]}" "${TRAMPOLINE_IMAGE}" - fi - docker run "${docker_flags[@]}" "${TRAMPOLINE_IMAGE}" -fi - - -test_retval=$? - -if [[ ${test_retval} -eq 0 ]]; then - log_green "Build finished with ${test_retval}" -else - log_red "Build finished with ${test_retval}" -fi - -# Only upload it when the test passes. -if [[ "${update_cache}" == "true" ]] && \ - [[ $test_retval == 0 ]] && \ - [[ "${TRAMPOLINE_IMAGE_UPLOAD:-false}" == "true" ]]; then - log_yellow "Uploading the Docker image." - if docker push "${TRAMPOLINE_IMAGE}"; then - log_green "Finished uploading the Docker image." - else - log_red "Failed uploading the Docker image." - fi - # Call trampoline_after_upload_hook if it's defined. - if function_exists trampoline_after_upload_hook; then - trampoline_after_upload_hook - fi - -fi - -exit "${test_retval}" diff --git a/packages/google-auth/.librarian/config.yaml b/packages/google-auth/.librarian/config.yaml deleted file mode 100644 index 111f94dd5cad..000000000000 --- a/packages/google-auth/.librarian/config.yaml +++ /dev/null @@ -1,6 +0,0 @@ -global_files_allowlist: - # Allow the container to read and write the root `CHANGELOG.md` - # file during the `release` step to update the latest client library - # versions which are hardcoded in the file. - - path: "CHANGELOG.md" - permissions: "read-write" diff --git a/packages/google-auth/.librarian/state.yaml b/packages/google-auth/.librarian/state.yaml deleted file mode 100644 index 9b7e2ca0966c..000000000000 --- a/packages/google-auth/.librarian/state.yaml +++ /dev/null @@ -1,11 +0,0 @@ -image: us-central1-docker.pkg.dev/cloud-sdk-librarian-prod/images-prod/python-librarian-generator:latest -libraries: - - id: google-auth - version: 2.43.0 - last_generated_commit: 102d9f92ac6ed649a61efd9b208e4d1de278e9bb - apis: [] - source_roots: - - . - preserve_regex: [] - remove_regex: [] - tag_format: v{version} diff --git a/packages/google-auth/.readthedocs.yaml b/packages/google-auth/.readthedocs.yaml deleted file mode 100644 index 2fb28446e3ab..000000000000 --- a/packages/google-auth/.readthedocs.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Read the Docs configuration file -# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details - -# Required -version: 2 - -# Set the OS, Python version, and other tools you might need -build: - os: ubuntu-24.04 - tools: - python: "3.10" - -# Build documentation in the "docs/" directory with Sphinx -sphinx: - configuration: docs/conf.py - -python: - install: - - requirements: docs/requirements-docs.txt - # Install our python package before building the docs - - method: pip - path: . diff --git a/packages/google-auth/.repo-metadata.json b/packages/google-auth/.repo-metadata.json index f6a3d96a0c97..0b76117d99d0 100644 --- a/packages/google-auth/.repo-metadata.json +++ b/packages/google-auth/.repo-metadata.json @@ -2,10 +2,10 @@ "name": "google-auth", "name_pretty": "Google Auth Python Library", "client_documentation": "https://googleapis.dev/python/google-auth/latest", - "issue_tracker": "https://github.com/googleapis/google-auth-library-python/issues", + "issue_tracker": "https://github.com/googleapis/google-cloud-python/issues", "release_level": "stable", "language": "python", "library_type": "AUTH", - "repo": "googleapis/google-auth-library-python", + "repo": "googleapis/google-cloud-python", "distribution_name": "google-auth" } diff --git a/packages/google-auth/.trampolinerc b/packages/google-auth/.trampolinerc deleted file mode 100644 index 0080152373d5..000000000000 --- a/packages/google-auth/.trampolinerc +++ /dev/null @@ -1,61 +0,0 @@ -# Copyright 2024 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Add required env vars here. -required_envvars+=( -) - -# Add env vars which are passed down into the container here. -pass_down_envvars+=( - "NOX_SESSION" - ############### - # Docs builds - ############### - "STAGING_BUCKET" - "V2_STAGING_BUCKET" - ################## - # Samples builds - ################## - "INSTALL_LIBRARY_FROM_SOURCE" - "RUN_TESTS_SESSION" - "BUILD_SPECIFIC_GCLOUD_PROJECT" - # Target directories. - "RUN_TESTS_DIRS" - # The nox session to run. - "RUN_TESTS_SESSION" -) - -# Prevent unintentional override on the default image. -if [[ "${TRAMPOLINE_IMAGE_UPLOAD:-false}" == "true" ]] && \ - [[ -z "${TRAMPOLINE_IMAGE:-}" ]]; then - echo "Please set TRAMPOLINE_IMAGE if you want to upload the Docker image." - exit 1 -fi - -# Define the default value if it makes sense. -if [[ -z "${TRAMPOLINE_IMAGE_UPLOAD:-}" ]]; then - TRAMPOLINE_IMAGE_UPLOAD="" -fi - -if [[ -z "${TRAMPOLINE_IMAGE:-}" ]]; then - TRAMPOLINE_IMAGE="" -fi - -if [[ -z "${TRAMPOLINE_DOCKERFILE:-}" ]]; then - TRAMPOLINE_DOCKERFILE="" -fi - -if [[ -z "${TRAMPOLINE_BUILD_FILE:-}" ]]; then - TRAMPOLINE_BUILD_FILE="" -fi diff --git a/packages/google-auth/CODE_OF_CONDUCT.md b/packages/google-auth/CODE_OF_CONDUCT.md deleted file mode 100644 index 46b2a08ea6d1..000000000000 --- a/packages/google-auth/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,43 +0,0 @@ -# Contributor Code of Conduct - -As contributors and maintainers of this project, -and in the interest of fostering an open and welcoming community, -we pledge to respect all people who contribute through reporting issues, -posting feature requests, updating documentation, -submitting pull requests or patches, and other activities. - -We are committed to making participation in this project -a harassment-free experience for everyone, -regardless of level of experience, gender, gender identity and expression, -sexual orientation, disability, personal appearance, -body size, race, ethnicity, age, religion, or nationality. - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery -* Personal attacks -* Trolling or insulting/derogatory comments -* Public or private harassment -* Publishing other's private information, -such as physical or electronic -addresses, without explicit permission -* Other unethical or unprofessional conduct. - -Project maintainers have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct. -By adopting this Code of Conduct, -project maintainers commit themselves to fairly and consistently -applying these principles to every aspect of managing this project. -Project maintainers who do not follow or enforce the Code of Conduct -may be permanently removed from the project team. - -This code of conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. - -Instances of abusive, harassing, or otherwise unacceptable behavior -may be reported by opening an issue -or contacting one or more of the project maintainers. - -This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0, -available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/) diff --git a/packages/google-auth/CONTRIBUTING.rst b/packages/google-auth/CONTRIBUTING.rst index 3975a3d2a85b..b19282f3255d 100644 --- a/packages/google-auth/CONTRIBUTING.rst +++ b/packages/google-auth/CONTRIBUTING.rst @@ -6,12 +6,12 @@ Contributing #. Make sure that your commit messages clearly describe the changes. #. Send a pull request. -Here are some guidelines for hacking on ``google-auth-library-python``. +Here are some guidelines for hacking on ``google-auth``. Making changes -------------- -A few notes on making changes to ``google-auth-library-python``. +A few notes on making changes to ``google-auth``. - If you've added a new feature or modified an existing feature, be sure to add or update any applicable documentation in docstrings and in the diff --git a/packages/google-auth/README.rst b/packages/google-auth/README.rst index e058f24713b8..5499651cbbf6 100644 --- a/packages/google-auth/README.rst +++ b/packages/google-auth/README.rst @@ -82,11 +82,11 @@ Contributions to this library are always welcome and highly encouraged. See `CONTRIBUTING.rst`_ for more information on how to get started. -.. _CONTRIBUTING.rst: https://github.com/googleapis/google-auth-library-python/blob/main/CONTRIBUTING.rst +.. _CONTRIBUTING.rst: https://github.com/googleapis/google-cloud-python/blob/main/packages/google-auth/CONTRIBUTING.rst License ------- Apache 2.0 - See `the LICENSE`_ for more information. -.. _the LICENSE: https://github.com/googleapis/google-auth-library-python/blob/main/LICENSE +.. _the LICENSE: https://github.com/googleapis/google-cloud-python/blob/main/packages/google-auth/LICENSE diff --git a/packages/google-auth/SECURITY.md b/packages/google-auth/SECURITY.md deleted file mode 100644 index 8b58ae9c01ae..000000000000 --- a/packages/google-auth/SECURITY.md +++ /dev/null @@ -1,7 +0,0 @@ -# Security Policy - -To report a security issue, please use [g.co/vulnz](https://g.co/vulnz). - -The Google Security Team will respond within 5 working days of your report on g.co/vulnz. - -We use g.co/vulnz for our intake, and do coordination and disclosure here using GitHub Security Advisory to privately discuss and fix the issue. diff --git a/packages/google-auth/docs/conf.py b/packages/google-auth/docs/conf.py index 8ab609390712..41721eeec30f 100644 --- a/packages/google-auth/docs/conf.py +++ b/packages/google-auth/docs/conf.py @@ -137,7 +137,7 @@ html_theme_options = { "description": "Google Auth Library for Python", "github_user": "GoogleCloudPlatform", - "github_repo": "google-auth-library-python", + "github_repo": "google-cloud-python", "github_banner": True, "travis_button": True, "font_family": "'Roboto', Georgia, sans", diff --git a/packages/google-auth/docs/index.rst b/packages/google-auth/docs/index.rst index 8a5f13a6daf4..a0a65778510c 100644 --- a/packages/google-auth/docs/index.rst +++ b/packages/google-auth/docs/index.rst @@ -45,7 +45,7 @@ For more information on setting up your Python development environment, please r .. _`Python Development Environment Setup Guide`: https://cloud.google.com/python/setup .. _pip: https://pip.pypa.io -.. _GitHub: https://github.com/GoogleCloudPlatform/google-auth-library-python +.. _GitHub: https://github.com/googleapis/google-cloud-python/packages/google-auth Usage ----- @@ -62,7 +62,7 @@ google-auth is made available under the Apache License, Version 2.0. For more details, see `LICENSE`_ .. _LICENSE: - https://github.com/GoogleCloudPlatform/google-auth-library-python/blob/main/LICENSE + https://github.com/googleapis/google-cloud-python/blob/main/packages/google-auth/LICENSE Contributing ------------ @@ -71,4 +71,4 @@ We happily welcome contributions, please see our `contributing`_ documentation for details. .. _contributing: - https://github.com/GoogleCloudPlatform/google-auth-library-python/blob/main/CONTRIBUTING.rst + https://github.com/googleapis/google-cloud-python/blob/main/packages/google-auth/CONTRIBUTING.rst From 416c662139a956f0297eac04fb6a23f0af30b9f4 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Wed, 17 Dec 2025 16:34:37 +0000 Subject: [PATCH 958/966] update url --- packages/google-auth/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 20f79ce66974..380847c50c20 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -110,7 +110,7 @@ author_email="googleapis-packages@google.com", description="Google Authentication Library", long_description=long_description, - url="https://github.com/googleapis/google-auth-library-python", + url="https://github.com/googleapis/google-cloud-python/tree/main/packages/google-auth", packages=find_namespace_packages( exclude=("tests*", "system_tests*", "docs*", "samples*") ), From 61bdf1eb3875622951022d73f821499f8f42dfd1 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Wed, 17 Dec 2025 16:38:22 +0000 Subject: [PATCH 959/966] fix docs --- packages/google-auth/noxfile.py | 48 ++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index 728e8c7cce01..4693d9c44499 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -143,7 +143,7 @@ def cover(session): session.run("coverage", "report", "--show-missing", "--fail-under=100") -@nox.session(python="3.9") +@nox.session(python="3.10") def docs(session): """Build the docs for this library.""" @@ -165,6 +165,52 @@ def docs(session): ) +@nox.session(python="3.10") +def docfx(session): + """Build the docfx yaml files for this library.""" + + session.install("-e", ".") + session.install( + # We need to pin to specific versions of the `sphinxcontrib-*` packages + # which still support sphinx 4.x. + # See https://github.com/googleapis/sphinx-docfx-yaml/issues/344 + # and https://github.com/googleapis/sphinx-docfx-yaml/issues/345. + "sphinxcontrib-applehelp==1.0.4", + "sphinxcontrib-devhelp==1.0.2", + "sphinxcontrib-htmlhelp==2.0.1", + "sphinxcontrib-qthelp==1.0.3", + "sphinxcontrib-serializinghtml==1.1.5", + "gcp-sphinx-docfx-yaml", + "alabaster", + "recommonmark", + ) + + shutil.rmtree(os.path.join("docs", "_build"), ignore_errors=True) + session.run( + "sphinx-build", + "-T", # show full traceback on exception + "-N", # no colors + "-D", + ( + "extensions=sphinx.ext.autodoc," + "sphinx.ext.autosummary," + "docfx_yaml.extension," + "sphinx.ext.intersphinx," + "sphinx.ext.coverage," + "sphinx.ext.napoleon," + "sphinx.ext.todo," + "sphinx.ext.viewcode," + "recommonmark" + ), + "-b", + "html", + "-d", + os.path.join("docs", "_build", "doctrees", ""), + os.path.join("docs", ""), + os.path.join("docs", "_build", "html", ""), + ) + + @nox.session(python="pypy") def pypy(session): session.install("-e", ".[testing]") From 51b3d83984adfb73e735c59d7724bfb688f17c1c Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Wed, 17 Dec 2025 16:46:12 +0000 Subject: [PATCH 960/966] update noxfile to current --- packages/google-auth/noxfile.py | 53 ++++----------------------------- 1 file changed, 5 insertions(+), 48 deletions(-) diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index 4693d9c44499..e91b7d5d6b27 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -36,7 +36,7 @@ DEFAULT_PYTHON_VERSION = "3.10" # TODO(https://github.com/googleapis/google-auth-library-python/issues/1787): # Remove or restore testing for Python 3.7/3.8 -UNIT_TEST_PYTHON_VERSIONS = ["3.9", "3.10", "3.11", "3.12", "3.13"] +UNIT_TEST_PYTHON_VERSIONS = ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] # Error if a python version is missing nox.options.error_on_missing_interpreters = True @@ -53,6 +53,7 @@ "unit-3.11", "unit-3.12", "unit-3.13", + "unit-3.14", # cover must be last to avoid error `No data to report` "cover", "docs", @@ -105,6 +106,7 @@ def mypy(session): "types-requests", "types-setuptools", "types-mock", + "pytest<8.0.0", ) session.run("mypy", "-p", "google", "-p", "tests", "-p", "tests_async") @@ -129,6 +131,7 @@ def unit(session): @nox.session(python=DEFAULT_PYTHON_VERSION) def cover(session): + session.env["PIP_EXTRA_INDEX_URL"] = "https://pypi.org/simple" session.install("-e", ".[testing]") session.run( "pytest", @@ -143,7 +146,7 @@ def cover(session): session.run("coverage", "report", "--show-missing", "--fail-under=100") -@nox.session(python="3.10") +@nox.session(python="3.9") def docs(session): """Build the docs for this library.""" @@ -165,52 +168,6 @@ def docs(session): ) -@nox.session(python="3.10") -def docfx(session): - """Build the docfx yaml files for this library.""" - - session.install("-e", ".") - session.install( - # We need to pin to specific versions of the `sphinxcontrib-*` packages - # which still support sphinx 4.x. - # See https://github.com/googleapis/sphinx-docfx-yaml/issues/344 - # and https://github.com/googleapis/sphinx-docfx-yaml/issues/345. - "sphinxcontrib-applehelp==1.0.4", - "sphinxcontrib-devhelp==1.0.2", - "sphinxcontrib-htmlhelp==2.0.1", - "sphinxcontrib-qthelp==1.0.3", - "sphinxcontrib-serializinghtml==1.1.5", - "gcp-sphinx-docfx-yaml", - "alabaster", - "recommonmark", - ) - - shutil.rmtree(os.path.join("docs", "_build"), ignore_errors=True) - session.run( - "sphinx-build", - "-T", # show full traceback on exception - "-N", # no colors - "-D", - ( - "extensions=sphinx.ext.autodoc," - "sphinx.ext.autosummary," - "docfx_yaml.extension," - "sphinx.ext.intersphinx," - "sphinx.ext.coverage," - "sphinx.ext.napoleon," - "sphinx.ext.todo," - "sphinx.ext.viewcode," - "recommonmark" - ), - "-b", - "html", - "-d", - os.path.join("docs", "_build", "doctrees", ""), - os.path.join("docs", ""), - os.path.join("docs", "_build", "html", ""), - ) - - @nox.session(python="pypy") def pypy(session): session.install("-e", ".[testing]") From f8b9308b6b8e2adb0ee6bf288b7e8e9b7b615b28 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Wed, 17 Dec 2025 16:46:42 +0000 Subject: [PATCH 961/966] fix docs/default python --- packages/google-auth/noxfile.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index e91b7d5d6b27..c0c72ee406b3 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -33,7 +33,7 @@ "docs/conf.py", ] -DEFAULT_PYTHON_VERSION = "3.10" +DEFAULT_PYTHON_VERSION = "3.14" # TODO(https://github.com/googleapis/google-auth-library-python/issues/1787): # Remove or restore testing for Python 3.7/3.8 UNIT_TEST_PYTHON_VERSIONS = ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] @@ -146,7 +146,7 @@ def cover(session): session.run("coverage", "report", "--show-missing", "--fail-under=100") -@nox.session(python="3.9") +@nox.session(python="3.10") def docs(session): """Build the docs for this library.""" From 9575ff65aa25651882623c2d878b094b79b7b833 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Wed, 17 Dec 2025 16:51:37 +0000 Subject: [PATCH 962/966] restore docfx --- packages/google-auth/noxfile.py | 46 +++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index c0c72ee406b3..31b9aabbb9e4 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -168,6 +168,52 @@ def docs(session): ) +@nox.session(python="3.10") +def docfx(session): + """Build the docfx yaml files for this library.""" + + session.install("-e", ".") + session.install( + # We need to pin to specific versions of the `sphinxcontrib-*` packages + # which still support sphinx 4.x. + # See https://github.com/googleapis/sphinx-docfx-yaml/issues/344 + # and https://github.com/googleapis/sphinx-docfx-yaml/issues/345. + "sphinxcontrib-applehelp==1.0.4", + "sphinxcontrib-devhelp==1.0.2", + "sphinxcontrib-htmlhelp==2.0.1", + "sphinxcontrib-qthelp==1.0.3", + "sphinxcontrib-serializinghtml==1.1.5", + "gcp-sphinx-docfx-yaml", + "alabaster", + "recommonmark", + ) + + shutil.rmtree(os.path.join("docs", "_build"), ignore_errors=True) + session.run( + "sphinx-build", + "-T", # show full traceback on exception + "-N", # no colors + "-D", + ( + "extensions=sphinx.ext.autodoc," + "sphinx.ext.autosummary," + "docfx_yaml.extension," + "sphinx.ext.intersphinx," + "sphinx.ext.coverage," + "sphinx.ext.napoleon," + "sphinx.ext.todo," + "sphinx.ext.viewcode," + "recommonmark" + ), + "-b", + "html", + "-d", + os.path.join("docs", "_build", "doctrees", ""), + os.path.join("docs", ""), + os.path.join("docs", "_build", "html", ""), + ) + + @nox.session(python="pypy") def pypy(session): session.install("-e", ".[testing]") From e51c9f44ef0688bb665e306433cc53b324707df7 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Wed, 17 Dec 2025 16:57:13 +0000 Subject: [PATCH 963/966] add prerelease_deps test --- packages/google-auth/noxfile.py | 71 +++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index 31b9aabbb9e4..ff3d8be0a284 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -14,6 +14,7 @@ import os import pathlib +import re import shutil import nox @@ -226,3 +227,73 @@ def pypy(session): "tests", "tests_async", ) + + +@nox.session(python=DEFAULT_PYTHON_VERSION) +@nox.parametrize( + "protobuf_implementation", + ["python", "upb", "cpp"], +) +def prerelease_deps(session, protobuf_implementation): + """ + Run all tests with pre-release versions of dependencies installed + rather than the standard non pre-release versions. + Pre-release versions can be installed using + `pip install --pre `. + """ + + if protobuf_implementation == "cpp" and session.python in ( + "3.11", + "3.12", + "3.13", + "3.14", + ): + session.skip("cpp implementation is not supported in python 3.11+") + + # Install all dependencies + session.install("-e", ".[testing]") + + # Because we test minimum dependency versions on the minimum Python + # version, the first version we test with in the unit tests sessions has a + # constraints file containing all dependencies and extras. + with open( + CURRENT_DIRECTORY + / "testing" + / f"constraints-{UNIT_TEST_PYTHON_VERSIONS[0]}.txt", + encoding="utf-8", + ) as constraints_file: + constraints_text = constraints_file.read() + + # Ignore leading whitespace and comment lines. + constraints_deps = [ + match.group(1) + for match in re.finditer( + r"^\s*(\S+)(?===\S+)", constraints_text, flags=re.MULTILINE + ) + ] + + # Install dependencies specified in `testing/constraints-X.txt`. + session.install(*constraints_deps) + + # Note: If a dependency is added to the `prerel_deps` list, + # the `core_dependencies_from_source` list in the `core_deps_from_source` + # nox session should also be updated. + prerel_deps = [ + "cachetools", + "pyasn1-modules", + "cryptography", + "requests", + "aiohttp", + ] + + for dep in prerel_deps: + session.install("--pre", "--no-deps", "--ignore-installed", dep) + + session.run( + "py.test", + "tests/unit", + env={ + "PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION": protobuf_implementation, + }, + ) + From b7ef4e47e0dfe544293bb5f4935ac1a4e6d2b977 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Wed, 17 Dec 2025 17:02:10 +0000 Subject: [PATCH 964/966] update black to 23.7.0 --- .../google-auth/google/auth/_cloud_sdk.py | 2 +- packages/google-auth/google/auth/_default.py | 10 ++++--- .../google/auth/_refresh_worker.py | 4 +-- packages/google-auth/google/auth/aws.py | 19 +++++++------- .../google/auth/compute_engine/credentials.py | 2 -- .../google/auth/external_account.py | 3 ++- .../auth/external_account_authorized_user.py | 3 ++- .../google-auth/google/auth/identity_pool.py | 13 +++++----- packages/google-auth/google/auth/jwt.py | 3 +-- packages/google-auth/google/auth/metrics.py | 2 ++ packages/google-auth/google/auth/pluggable.py | 19 +++++++------- .../auth/transport/_aiohttp_requests.py | 2 -- .../google/auth/transport/_http_client.py | 1 - .../google/auth/transport/requests.py | 1 - .../google/auth/transport/urllib3.py | 1 - packages/google-auth/google/oauth2/_client.py | 14 +++++++--- .../google/oauth2/_client_async.py | 6 ++++- .../google/oauth2/_id_token_async.py | 6 +++-- .../google/oauth2/_reauth_async.py | 8 +++--- packages/google-auth/google/oauth2/reauth.py | 6 ++++- .../google/oauth2/service_account.py | 1 - packages/google-auth/noxfile.py | 7 ++--- .../tests/crypt/test__cryptography_rsa.py | 2 +- .../tests/crypt/test__python_rsa.py | 2 +- packages/google-auth/tests/crypt/test_es.py | 2 +- .../google-auth/tests/crypt/test_es256.py | 2 +- .../tests/oauth2/test_challenges.py | 1 - packages/google-auth/tests/oauth2/test_sts.py | 5 ++-- packages/google-auth/tests/test__default.py | 8 +++--- .../tests/test__exponential_backoff.py | 4 +-- .../google-auth/tests/test__oauth2client.py | 1 - packages/google-auth/tests/test_aws.py | 6 +++-- packages/google-auth/tests/test_downscoped.py | 5 ++-- .../test_external_account_authorized_user.py | 6 +++-- .../google-auth/tests/test_identity_pool.py | 9 +++---- .../tests/test_impersonated_credentials.py | 26 ++++++++++++------- packages/google-auth/tests/test_jwt.py | 8 +++--- packages/google-auth/tests/test_pluggable.py | 9 ++++--- .../tests_async/oauth2/test__client_async.py | 3 --- .../oauth2/test_credentials_async.py | 1 - .../transport/test_aiohttp_requests.py | 1 - 41 files changed, 126 insertions(+), 108 deletions(-) diff --git a/packages/google-auth/google/auth/_cloud_sdk.py b/packages/google-auth/google/auth/_cloud_sdk.py index a94411949bdf..85b3c4f99be3 100644 --- a/packages/google-auth/google/auth/_cloud_sdk.py +++ b/packages/google-auth/google/auth/_cloud_sdk.py @@ -83,7 +83,7 @@ def get_application_default_credentials_path(): def _run_subprocess_ignore_stderr(command): - """ Return subprocess.check_output with the given command and ignores stderr.""" + """Return subprocess.check_output with the given command and ignores stderr.""" with open(os.devnull, "w") as devnull: output = subprocess.check_output(command, stderr=devnull) return output diff --git a/packages/google-auth/google/auth/_default.py b/packages/google-auth/google/auth/_default.py index a96f7108be23..8046b4c73d3d 100644 --- a/packages/google-auth/google/auth/_default.py +++ b/packages/google-auth/google/auth/_default.py @@ -538,8 +538,10 @@ def _get_impersonated_service_account_credentials(filename, info, scopes): from google.auth import impersonated_credentials try: - credentials = impersonated_credentials.Credentials.from_impersonated_service_account_info( - info, scopes=scopes + credentials = ( + impersonated_credentials.Credentials.from_impersonated_service_account_info( + info, scopes=scopes + ) ) except ValueError as caught_exc: msg = "Failed to load impersonated service account credentials from {}".format( @@ -554,8 +556,8 @@ def _get_gdch_service_account_credentials(filename, info): from google.oauth2 import gdch_credentials try: - credentials = gdch_credentials.ServiceAccountCredentials.from_service_account_info( - info + credentials = ( + gdch_credentials.ServiceAccountCredentials.from_service_account_info(info) ) except ValueError as caught_exc: msg = "Failed to load GDCH service account credentials from {}".format(filename) diff --git a/packages/google-auth/google/auth/_refresh_worker.py b/packages/google-auth/google/auth/_refresh_worker.py index 674032d8496b..1bab21a69e40 100644 --- a/packages/google-auth/google/auth/_refresh_worker.py +++ b/packages/google-auth/google/auth/_refresh_worker.py @@ -61,8 +61,8 @@ def start_refresh(self, cred, request): def clear_error(self): """ - Removes any errors that were stored from previous background refreshes. - """ + Removes any errors that were stored from previous background refreshes. + """ with self._lock: if self._worker: self._worker._error_info = None diff --git a/packages/google-auth/google/auth/aws.py b/packages/google-auth/google/auth/aws.py index 28c065d3c751..8e6c6789c98e 100644 --- a/packages/google-auth/google/auth/aws.py +++ b/packages/google-auth/google/auth/aws.py @@ -348,10 +348,10 @@ def _generate_authentication_header_map( class AwsSecurityCredentials: """A class that models AWS security credentials with an optional session token. - Attributes: - access_key_id (str): The AWS security credentials access key id. - secret_access_key (str): The AWS security credentials secret access key. - session_token (Optional[str]): The optional AWS security credentials session token. This should be set when using temporary credentials. + Attributes: + access_key_id (str): The AWS security credentials access key id. + secret_access_key (str): The AWS security credentials secret access key. + session_token (Optional[str]): The optional AWS security credentials session token. This should be set when using temporary credentials. """ access_key_id: str @@ -420,7 +420,6 @@ def __init__(self, credential_source): @_helpers.copy_docstring(AwsSecurityCredentialsSupplier) def get_aws_security_credentials(self, context, request): - # Check environment variables for permanent credentials first. # https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html env_aws_access_key_id = os.environ.get(environment_vars.AWS_ACCESS_KEY_ID) @@ -688,8 +687,8 @@ def __init__( ) else: environment_id = credential_source.get("environment_id") or "" - self._aws_security_credentials_supplier = _DefaultAwsSecurityCredentialsSupplier( - credential_source + self._aws_security_credentials_supplier = ( + _DefaultAwsSecurityCredentialsSupplier(credential_source) ) self._cred_verification_url = credential_source.get( "regional_cred_verification_url" @@ -759,8 +758,10 @@ def retrieve_subject_token(self, request): # Retrieve the AWS security credentials needed to generate the signed # request. - aws_security_credentials = self._aws_security_credentials_supplier.get_aws_security_credentials( - self._supplier_context, request + aws_security_credentials = ( + self._aws_security_credentials_supplier.get_aws_security_credentials( + self._supplier_context, request + ) ) # Generate the signed request to AWS STS GetCallerIdentity API. # Use the required regional endpoint. Otherwise, the request will fail. diff --git a/packages/google-auth/google/auth/compute_engine/credentials.py b/packages/google-auth/google/auth/compute_engine/credentials.py index 0f518166abc5..192b811b52a8 100644 --- a/packages/google-auth/google/auth/compute_engine/credentials.py +++ b/packages/google-auth/google/auth/compute_engine/credentials.py @@ -399,7 +399,6 @@ def with_target_audience(self, target_audience): @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) def with_quota_project(self, quota_project_id): - # since the signer is already instantiated, # the request is not needed if self._use_metadata_identity_endpoint: @@ -423,7 +422,6 @@ def with_quota_project(self, quota_project_id): @_helpers.copy_docstring(credentials.CredentialsWithTokenUri) def with_token_uri(self, token_uri): - # since the signer is already instantiated, # the request is not needed if self._use_metadata_identity_endpoint: diff --git a/packages/google-auth/google/auth/external_account.py b/packages/google-auth/google/auth/external_account.py index 8eba0d2495fd..06536d963523 100644 --- a/packages/google-auth/google/auth/external_account.py +++ b/packages/google-auth/google/auth/external_account.py @@ -98,7 +98,8 @@ class Credentials( is used. When the credential configuration is accepted from an untrusted source, you should validate it before using. - Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details.""" + Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details. + """ def __init__( self, diff --git a/packages/google-auth/google/auth/external_account_authorized_user.py b/packages/google-auth/google/auth/external_account_authorized_user.py index 2594e048f388..6ca89628f11c 100644 --- a/packages/google-auth/google/auth/external_account_authorized_user.py +++ b/packages/google-auth/google/auth/external_account_authorized_user.py @@ -70,7 +70,8 @@ class Credentials( is used. When the credential configuration is accepted from an untrusted source, you should validate it before using. - Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details.""" + Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details. + """ def __init__( self, diff --git a/packages/google-auth/google/auth/identity_pool.py b/packages/google-auth/google/auth/identity_pool.py index 79b7de920563..ebcd59e8000f 100644 --- a/packages/google-auth/google/auth/identity_pool.py +++ b/packages/google-auth/google/auth/identity_pool.py @@ -83,9 +83,9 @@ def get_subject_token(self, context, request): class _TokenContent(NamedTuple): """Models the token content response from file and url internal suppliers. - Attributes: - content (str): The string content of the file or URL response. - location (str): The location the content was retrieved from. This will either be a file location or a URL. + Attributes: + content (str): The string content of the file or URL response. + location (str): The location the content was retrieved from. This will either be a file location or a URL. """ content: str @@ -93,7 +93,7 @@ class _TokenContent(NamedTuple): class _FileSupplier(SubjectTokenSupplier): - """ Internal implementation of subject token supplier which supports reading a subject token from a file.""" + """Internal implementation of subject token supplier which supports reading a subject token from a file.""" def __init__(self, path, format_type, subject_token_field_name): self._path = path @@ -114,7 +114,7 @@ def get_subject_token(self, context, request): class _UrlSupplier(SubjectTokenSupplier): - """ Internal implementation of subject token supplier which supports retrieving a subject token by calling a URL endpoint.""" + """Internal implementation of subject token supplier which supports retrieving a subject token by calling a URL endpoint.""" def __init__(self, url, format_type, subject_token_field_name, headers): self._url = url @@ -261,7 +261,8 @@ class Credentials(external_account.Credentials): is used. When the credential configuration is accepted from an untrusted source, you should validate it before using. - Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details.""" + Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details. + """ def __init__( self, diff --git a/packages/google-auth/google/auth/jwt.py b/packages/google-auth/google/auth/jwt.py index 9b79f173b727..3b28bb5184dd 100644 --- a/packages/google-auth/google/auth/jwt.py +++ b/packages/google-auth/google/auth/jwt.py @@ -586,7 +586,7 @@ def signer(self): @property # type: ignore def additional_claims(self): - """ Additional claims the JWT object was created with.""" + """Additional claims the JWT object was created with.""" return self._additional_claims @@ -760,7 +760,6 @@ def with_claims(self, issuer=None, subject=None, additional_claims=None): @_helpers.copy_docstring(google.auth.credentials.CredentialsWithQuotaProject) def with_quota_project(self, quota_project_id): - return self.__class__( self._signer, issuer=self._issuer, diff --git a/packages/google-auth/google/auth/metrics.py b/packages/google-auth/google/auth/metrics.py index 11e4b0773077..5511f581f658 100644 --- a/packages/google-auth/google/auth/metrics.py +++ b/packages/google-auth/google/auth/metrics.py @@ -48,6 +48,7 @@ def python_and_auth_lib_version(): # Token request metric header values + # x-goog-api-client header value for access token request via metadata server. # Example: "gl-python/3.7 auth/1.1 auth-request-type/at cred-type/mds" def token_request_access_token_mds(): @@ -108,6 +109,7 @@ def token_request_user(): # Miscellenous metrics + # x-goog-api-client header value for metadata server ping. # Example: "gl-python/3.7 auth/1.1 auth-request-type/mds" def mds_ping(): diff --git a/packages/google-auth/google/auth/pluggable.py b/packages/google-auth/google/auth/pluggable.py index 730a72c28350..87d03e737a9a 100644 --- a/packages/google-auth/google/auth/pluggable.py +++ b/packages/google-auth/google/auth/pluggable.py @@ -66,7 +66,8 @@ class Credentials(external_account.Credentials): is used. When the credential configuration is accepted from an untrusted source, you should validate it before using. - Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details.""" + Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details. + """ def __init__( self, @@ -129,17 +130,17 @@ def __init__( raise exceptions.MalformedError( "Missing credential_source. An 'executable' must be provided." ) - self._credential_source_executable_command = self._credential_source_executable.get( - "command" + self._credential_source_executable_command = ( + self._credential_source_executable.get("command") ) - self._credential_source_executable_timeout_millis = self._credential_source_executable.get( - "timeout_millis" + self._credential_source_executable_timeout_millis = ( + self._credential_source_executable.get("timeout_millis") ) - self._credential_source_executable_interactive_timeout_millis = self._credential_source_executable.get( - "interactive_timeout_millis" + self._credential_source_executable_interactive_timeout_millis = ( + self._credential_source_executable.get("interactive_timeout_millis") ) - self._credential_source_executable_output_file = self._credential_source_executable.get( - "output_file" + self._credential_source_executable_output_file = ( + self._credential_source_executable.get("output_file") ) # Dummy value. This variable is only used via injection, not exposed to ctor diff --git a/packages/google-auth/google/auth/transport/_aiohttp_requests.py b/packages/google-auth/google/auth/transport/_aiohttp_requests.py index 36366be51086..2465b6a9703a 100644 --- a/packages/google-auth/google/auth/transport/_aiohttp_requests.py +++ b/packages/google-auth/google/auth/transport/_aiohttp_requests.py @@ -276,7 +276,6 @@ async def request( auto_decompress=False, **kwargs, ): - """Implementation of Authorized Session aiohttp request. Args: @@ -358,7 +357,6 @@ async def request( response.status in self._refresh_status_codes and _credential_refresh_attempt < self._max_refresh_attempts ): - requests._LOGGER.info( "Refreshing credentials due to a %s response. Attempt %s/%s.", response.status, diff --git a/packages/google-auth/google/auth/transport/_http_client.py b/packages/google-auth/google/auth/transport/_http_client.py index 898a86519b7c..ecd51bdf8a75 100644 --- a/packages/google-auth/google/auth/transport/_http_client.py +++ b/packages/google-auth/google/auth/transport/_http_client.py @@ -100,7 +100,6 @@ def __call__( connection = http_client.HTTPConnection(parts.netloc, timeout=timeout) try: - _helpers.request_log(_LOGGER, method, url, body, headers) connection.request(method, path, body=body, headers=headers, **kwargs) response = connection.getresponse() diff --git a/packages/google-auth/google/auth/transport/requests.py b/packages/google-auth/google/auth/transport/requests.py index d1ff8f368cce..7a9612872d11 100644 --- a/packages/google-auth/google/auth/transport/requests.py +++ b/packages/google-auth/google/auth/transport/requests.py @@ -551,7 +551,6 @@ def request( response.status_code in self._refresh_status_codes and _credential_refresh_attempt < self._max_refresh_attempts ): - _LOGGER.info( "Refreshing credentials due to a %s response. Attempt %s/%s.", response.status_code, diff --git a/packages/google-auth/google/auth/transport/urllib3.py b/packages/google-auth/google/auth/transport/urllib3.py index 353cb8e08eb1..607a7e3b58ee 100644 --- a/packages/google-auth/google/auth/transport/urllib3.py +++ b/packages/google-auth/google/auth/transport/urllib3.py @@ -402,7 +402,6 @@ def urlopen(self, method, url, body=None, headers=None, **kwargs): response.status in self._refresh_status_codes and _credential_refresh_attempt < self._max_refresh_attempts ): - _LOGGER.info( "Refreshing credentials due to a %s response. Attempt %s/%s.", response.status, diff --git a/packages/google-auth/google/oauth2/_client.py b/packages/google-auth/google/oauth2/_client.py index 9c0e630981e4..b2fe982e5609 100644 --- a/packages/google-auth/google/oauth2/_client.py +++ b/packages/google-auth/google/oauth2/_client.py @@ -256,7 +256,11 @@ def _token_endpoint_request( an error. """ - response_status_ok, response_data, retryable_error = _token_endpoint_request_no_throw( + ( + response_status_ok, + response_data, + retryable_error, + ) = _token_endpoint_request_no_throw( request, token_uri, body, @@ -568,9 +572,11 @@ def _lookup_trust_boundary_request(request, url, can_retry=True, headers=None): google.auth.exceptions.RefreshError: If the token endpoint returned an error. """ - response_status_ok, response_data, retryable_error = _lookup_trust_boundary_request_no_throw( - request, url, can_retry, headers - ) + ( + response_status_ok, + response_data, + retryable_error, + ) = _lookup_trust_boundary_request_no_throw(request, url, can_retry, headers) if not response_status_ok: _handle_error_response(response_data, retryable_error) return response_data diff --git a/packages/google-auth/google/oauth2/_client_async.py b/packages/google-auth/google/oauth2/_client_async.py index 8867f0a52740..768f8eac2a05 100644 --- a/packages/google-auth/google/oauth2/_client_async.py +++ b/packages/google-auth/google/oauth2/_client_async.py @@ -127,7 +127,11 @@ async def _token_endpoint_request( an error. """ - response_status_ok, response_data, retryable_error = await _token_endpoint_request_no_throw( + ( + response_status_ok, + response_data, + retryable_error, + ) = await _token_endpoint_request_no_throw( request, token_uri, body, diff --git a/packages/google-auth/google/oauth2/_id_token_async.py b/packages/google-auth/google/oauth2/_id_token_async.py index 6594e416aea0..a7f77a1c785b 100644 --- a/packages/google-auth/google/oauth2/_id_token_async.py +++ b/packages/google-auth/google/oauth2/_id_token_async.py @@ -252,8 +252,10 @@ async def fetch_id_token(request, audience): info = json.load(f) if info.get("type") == "service_account": - credentials = service_account.IDTokenCredentials.from_service_account_info( - info, target_audience=audience + credentials = ( + service_account.IDTokenCredentials.from_service_account_info( + info, target_audience=audience + ) ) await credentials.refresh(request) return credentials.token diff --git a/packages/google-auth/google/oauth2/_reauth_async.py b/packages/google-auth/google/oauth2/_reauth_async.py index de3675c5239c..eeb8e9fb0273 100644 --- a/packages/google-auth/google/oauth2/_reauth_async.py +++ b/packages/google-auth/google/oauth2/_reauth_async.py @@ -290,9 +290,11 @@ async def refresh_grant( if rapt_token: body["rapt"] = rapt_token - response_status_ok, response_data, retryable_error = await _client_async._token_endpoint_request_no_throw( - request, token_uri, body - ) + ( + response_status_ok, + response_data, + retryable_error, + ) = await _client_async._token_endpoint_request_no_throw(request, token_uri, body) if ( not response_status_ok and response_data.get("error") == reauth._REAUTH_NEEDED_ERROR diff --git a/packages/google-auth/google/oauth2/reauth.py b/packages/google-auth/google/oauth2/reauth.py index 1e39e0bc7f41..abf691d58a64 100644 --- a/packages/google-auth/google/oauth2/reauth.py +++ b/packages/google-auth/google/oauth2/reauth.py @@ -330,7 +330,11 @@ def refresh_grant( body["rapt"] = rapt_token metrics_header = {metrics.API_CLIENT_HEADER: metrics.token_request_user()} - response_status_ok, response_data, retryable_error = _client._token_endpoint_request_no_throw( + ( + response_status_ok, + response_data, + retryable_error, + ) = _client._token_endpoint_request_no_throw( request, token_uri, body, headers=metrics_header ) diff --git a/packages/google-auth/google/oauth2/service_account.py b/packages/google-auth/google/oauth2/service_account.py index 7520fe3bb355..8ac589d10984 100644 --- a/packages/google-auth/google/oauth2/service_account.py +++ b/packages/google-auth/google/oauth2/service_account.py @@ -482,7 +482,6 @@ def _create_self_signed_jwt(self, audience): self._jwt_credentials is None or self._jwt_credentials._audience != audience ): - self._jwt_credentials = jwt.Credentials.from_signing_credentials( self, audience ) diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index ff3d8be0a284..c9398bbe58f5 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -21,10 +21,8 @@ CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() -# https://github.com/psf/black/issues/2964, pin click version to 8.0.4 to -# avoid incompatiblity with black. -CLICK_VERSION = "click==8.0.4" -BLACK_VERSION = "black==19.3b0" +CLICK_VERSION = "click" +BLACK_VERSION = "black==23.7.0" BLACK_PATHS = [ "google", "tests", @@ -296,4 +294,3 @@ def prerelease_deps(session, protobuf_implementation): "PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION": protobuf_implementation, }, ) - diff --git a/packages/google-auth/tests/crypt/test__cryptography_rsa.py b/packages/google-auth/tests/crypt/test__cryptography_rsa.py index 1199f8d1bd18..7f5406b626a6 100644 --- a/packages/google-auth/tests/crypt/test__cryptography_rsa.py +++ b/packages/google-auth/tests/crypt/test__cryptography_rsa.py @@ -70,7 +70,7 @@ def test_verify_success(self): assert verifier.verify(to_sign, actual_signature) def test_verify_unicode_success(self): - to_sign = u"foo" + to_sign = "foo" signer = _cryptography_rsa.RSASigner.from_string(PRIVATE_KEY_BYTES) actual_signature = signer.sign(to_sign) diff --git a/packages/google-auth/tests/crypt/test__python_rsa.py b/packages/google-auth/tests/crypt/test__python_rsa.py index 4a4ebe44e98c..451c2834155e 100644 --- a/packages/google-auth/tests/crypt/test__python_rsa.py +++ b/packages/google-auth/tests/crypt/test__python_rsa.py @@ -72,7 +72,7 @@ def test_verify_success(self): assert verifier.verify(to_sign, actual_signature) def test_verify_unicode_success(self): - to_sign = u"foo" + to_sign = "foo" signer = _python_rsa.RSASigner.from_string(PRIVATE_KEY_BYTES) actual_signature = signer.sign(to_sign) diff --git a/packages/google-auth/tests/crypt/test_es.py b/packages/google-auth/tests/crypt/test_es.py index 3a62c1413b2b..fdbf9d2edfef 100644 --- a/packages/google-auth/tests/crypt/test_es.py +++ b/packages/google-auth/tests/crypt/test_es.py @@ -68,7 +68,7 @@ def test_verify_success(self): assert verifier.verify(to_sign, actual_signature) def test_verify_unicode_success(self): - to_sign = u"foo" + to_sign = "foo" signer = es.EsSigner.from_string(PRIVATE_KEY_BYTES) actual_signature = signer.sign(to_sign) diff --git a/packages/google-auth/tests/crypt/test_es256.py b/packages/google-auth/tests/crypt/test_es256.py index f87648db4a4c..fa5070f89d55 100644 --- a/packages/google-auth/tests/crypt/test_es256.py +++ b/packages/google-auth/tests/crypt/test_es256.py @@ -60,7 +60,7 @@ def test_verify_success(self): assert verifier.verify(to_sign, actual_signature) def test_verify_unicode_success(self): - to_sign = u"foo" + to_sign = "foo" signer = es256.ES256Signer.from_string(PRIVATE_KEY_BYTES) actual_signature = signer.sign(to_sign) diff --git a/packages/google-auth/tests/oauth2/test_challenges.py b/packages/google-auth/tests/oauth2/test_challenges.py index 4116b913abd9..8be39c6ef5af 100644 --- a/packages/google-auth/tests/oauth2/test_challenges.py +++ b/packages/google-auth/tests/oauth2/test_challenges.py @@ -87,7 +87,6 @@ def test_security_key(): "google.oauth2.challenges.SecurityKeyChallenge._obtain_challenge_input_webauthn", return_value={"securityKey": "security key response"}, ): - assert challenge.obtain_challenge_input(metadata) == { "securityKey": "security key response" } diff --git a/packages/google-auth/tests/oauth2/test_sts.py b/packages/google-auth/tests/oauth2/test_sts.py index e9075e40688b..71856a6232bc 100644 --- a/packages/google-auth/tests/oauth2/test_sts.py +++ b/packages/google-auth/tests/oauth2/test_sts.py @@ -97,7 +97,7 @@ def assert_request_kwargs(cls, request_kwargs, headers, request_data, url=None): assert request_kwargs["headers"] == headers assert request_kwargs["body"] is not None body_tuples = urllib.parse.parse_qsl(request_kwargs["body"]) - for (k, v) in body_tuples: + for k, v in body_tuples: assert v.decode("utf-8") == request_data[k.decode("utf-8")] assert len(body_tuples) == len(request_data.keys()) @@ -172,8 +172,7 @@ def test_exchange_token_partial_success_without_auth(self): assert response == self.SUCCESS_RESPONSE def test_exchange_token_non200_without_auth(self): - """Test token exchange without client auth responding with non-200 status. - """ + """Test token exchange without client auth responding with non-200 status.""" client = self.make_client() request = self.make_mock_request( status=http_client.BAD_REQUEST, data=self.ERROR_RESPONSE diff --git a/packages/google-auth/tests/test__default.py b/packages/google-auth/tests/test__default.py index 78874c01a6c0..4bf21b4224ea 100644 --- a/packages/google-auth/tests/test__default.py +++ b/packages/google-auth/tests/test__default.py @@ -154,9 +154,11 @@ DATA_DIR, "impersonated_service_account_service_account_source.json" ) -IMPERSONATED_SERVICE_ACCOUNT_EXTERNAL_ACCOUNT_AUTHORIZED_USER_SOURCE_FILE = os.path.join( - DATA_DIR, - "impersonated_service_account_external_account_authorized_user_source.json", +IMPERSONATED_SERVICE_ACCOUNT_EXTERNAL_ACCOUNT_AUTHORIZED_USER_SOURCE_FILE = ( + os.path.join( + DATA_DIR, + "impersonated_service_account_external_account_authorized_user_source.json", + ) ) EXTERNAL_ACCOUNT_AUTHORIZED_USER_FILE = os.path.join( diff --git a/packages/google-auth/tests/test__exponential_backoff.py b/packages/google-auth/tests/test__exponential_backoff.py index b7b6877b2c06..358083edad05 100644 --- a/packages/google-auth/tests/test__exponential_backoff.py +++ b/packages/google-auth/tests/test__exponential_backoff.py @@ -35,7 +35,7 @@ def test_exponential_backoff(mock_time): assert (curr_wait - jitter) <= backoff_interval <= (curr_wait + jitter) assert attempt == iteration_count + 1 assert eb.backoff_count == iteration_count + 1 - assert eb._current_wait_in_seconds == eb._multiplier ** iteration_count + assert eb._current_wait_in_seconds == eb._multiplier**iteration_count curr_wait = eb._current_wait_in_seconds iteration_count += 1 @@ -75,7 +75,7 @@ async def test_exponential_backoff_async(mock_time_async): assert (curr_wait - jitter) <= backoff_interval <= (curr_wait + jitter) assert attempt == iteration_count + 1 assert eb.backoff_count == iteration_count + 1 - assert eb._current_wait_in_seconds == eb._multiplier ** iteration_count + assert eb._current_wait_in_seconds == eb._multiplier**iteration_count curr_wait = eb._current_wait_in_seconds iteration_count += 1 diff --git a/packages/google-auth/tests/test__oauth2client.py b/packages/google-auth/tests/test__oauth2client.py index 2d4a809bb0d0..0639be102675 100644 --- a/packages/google-auth/tests/test__oauth2client.py +++ b/packages/google-auth/tests/test__oauth2client.py @@ -115,7 +115,6 @@ def mock_oauth2client_gae_imports(mock_non_existent_module): def test__convert_appengine_app_assertion_credentials( app_identity, mock_oauth2client_gae_imports ): - # `oauth2client` requires `cgi` which was removed in Python 3.13 # See https://github.com/googleapis/oauth2client/blob/50d20532a748f18e53f7d24ccbe6647132c979a9/oauth2client/contrib/appengine.py#L20 # oauth2client is no longer being updated so this test must be skipped on newer Python Runtimes diff --git a/packages/google-auth/tests/test_aws.py b/packages/google-auth/tests/test_aws.py index 4f70bda4f6b8..5aa979c859e1 100644 --- a/packages/google-auth/tests/test_aws.py +++ b/packages/google-auth/tests/test_aws.py @@ -42,8 +42,10 @@ SERVICE_ACCOUNT_IMPERSONATION_URL_BASE = ( "https://us-east1-iamcredentials.googleapis.com" ) -SERVICE_ACCOUNT_IMPERSONATION_URL_ROUTE = "/v1/projects/-/serviceAccounts/{}:generateAccessToken".format( - SERVICE_ACCOUNT_EMAIL +SERVICE_ACCOUNT_IMPERSONATION_URL_ROUTE = ( + "/v1/projects/-/serviceAccounts/{}:generateAccessToken".format( + SERVICE_ACCOUNT_EMAIL + ) ) SERVICE_ACCOUNT_IMPERSONATION_URL = ( SERVICE_ACCOUNT_IMPERSONATION_URL_BASE + SERVICE_ACCOUNT_IMPERSONATION_URL_ROUTE diff --git a/packages/google-auth/tests/test_downscoped.py b/packages/google-auth/tests/test_downscoped.py index fe6e291c7561..7c8f970182b8 100644 --- a/packages/google-auth/tests/test_downscoped.py +++ b/packages/google-auth/tests/test_downscoped.py @@ -484,14 +484,13 @@ def make_mock_request(data, status=http_client.OK): def assert_request_kwargs( request_kwargs, headers, request_data, token_endpoint=TOKEN_EXCHANGE_ENDPOINT ): - """Asserts the request was called with the expected parameters. - """ + """Asserts the request was called with the expected parameters.""" assert request_kwargs["url"] == token_endpoint assert request_kwargs["method"] == "POST" assert request_kwargs["headers"] == headers assert request_kwargs["body"] is not None body_tuples = urllib.parse.parse_qsl(request_kwargs["body"]) - for (k, v) in body_tuples: + for k, v in body_tuples: assert v.decode("utf-8") == request_data[k.decode("utf-8")] assert len(body_tuples) == len(request_data.keys()) diff --git a/packages/google-auth/tests/test_external_account_authorized_user.py b/packages/google-auth/tests/test_external_account_authorized_user.py index 0a54af56d5f2..71bfaa39c9c8 100644 --- a/packages/google-auth/tests/test_external_account_authorized_user.py +++ b/packages/google-auth/tests/test_external_account_authorized_user.py @@ -32,8 +32,10 @@ QUOTA_PROJECT_ID = "654321" POOL_ID = "POOL_ID" PROVIDER_ID = "PROVIDER_ID" -AUDIENCE = "//iam.googleapis.com/locations/global/workforcePools/{}/providers/{}".format( - POOL_ID, PROVIDER_ID +AUDIENCE = ( + "//iam.googleapis.com/locations/global/workforcePools/{}/providers/{}".format( + POOL_ID, PROVIDER_ID + ) ) REFRESH_TOKEN = "REFRESH_TOKEN" NEW_REFRESH_TOKEN = "NEW_REFRESH_TOKEN" diff --git a/packages/google-auth/tests/test_identity_pool.py b/packages/google-auth/tests/test_identity_pool.py index dbbdbf53a4c2..0a616cae90c5 100644 --- a/packages/google-auth/tests/test_identity_pool.py +++ b/packages/google-auth/tests/test_identity_pool.py @@ -38,8 +38,10 @@ SERVICE_ACCOUNT_IMPERSONATION_URL_BASE = ( "https://us-east1-iamcredentials.googleapis.com" ) -SERVICE_ACCOUNT_IMPERSONATION_URL_ROUTE = "/v1/projects/-/serviceAccounts/{}:generateAccessToken".format( - SERVICE_ACCOUNT_EMAIL +SERVICE_ACCOUNT_IMPERSONATION_URL_ROUTE = ( + "/v1/projects/-/serviceAccounts/{}:generateAccessToken".format( + SERVICE_ACCOUNT_EMAIL + ) ) SERVICE_ACCOUNT_IMPERSONATION_URL = ( SERVICE_ACCOUNT_IMPERSONATION_URL_BASE + SERVICE_ACCOUNT_IMPERSONATION_URL_ROUTE @@ -1051,7 +1053,6 @@ def test_retrieve_subject_token_certificate_trust_chain_without_leaf( def test_retrieve_subject_token_certificate_trust_chain_invalid_order( self, mock_get_workload_cert_and_key_paths ): - credentials = self.make_credentials( credential_source=self.CREDENTIAL_SOURCE_CERTIFICATE_TRUST_CHAIN_WRONG_ORDER ) @@ -1070,7 +1071,6 @@ def test_retrieve_subject_token_certificate_trust_chain_invalid_order( def test_retrieve_subject_token_certificate_trust_chain_file_does_not_exist( self, mock_get_workload_cert_and_key_paths ): - credentials = self.make_credentials( credential_source={ "certificate": { @@ -1092,7 +1092,6 @@ def test_retrieve_subject_token_certificate_trust_chain_file_does_not_exist( def test_retrieve_subject_token_certificate_invalid_trust_chain_file( self, mock_get_workload_cert_and_key_paths ): - credentials = self.make_credentials( credential_source={ "certificate": { diff --git a/packages/google-auth/tests/test_impersonated_credentials.py b/packages/google-auth/tests/test_impersonated_credentials.py index 2cfc05bef2dc..ab92e0bd9297 100644 --- a/packages/google-auth/tests/test_impersonated_credentials.py +++ b/packages/google-auth/tests/test_impersonated_credentials.py @@ -121,7 +121,6 @@ def mock_authorizedsession_idtoken(): class TestImpersonatedCredentials(object): - SERVICE_ACCOUNT_EMAIL = "service-account@example.com" TARGET_PRINCIPAL = "impersonated@project.iam.gserviceaccount.com" TARGET_SCOPES = ["https://www.googleapis.com/auth/devstorage.read_only"] @@ -160,7 +159,6 @@ def make_credentials( iam_endpoint_override=None, trust_boundary=None, # Align with Credentials class default ): - return Credentials( source_credentials=source_credentials, target_principal=target_principal, @@ -173,16 +171,20 @@ def make_credentials( ) def test_from_impersonated_service_account_info(self): - credentials = impersonated_credentials.Credentials.from_impersonated_service_account_info( - IMPERSONATED_SERVICE_ACCOUNT_AUTHORIZED_USER_SOURCE_INFO + credentials = ( + impersonated_credentials.Credentials.from_impersonated_service_account_info( + IMPERSONATED_SERVICE_ACCOUNT_AUTHORIZED_USER_SOURCE_INFO + ) ) assert isinstance(credentials, impersonated_credentials.Credentials) def test_from_impersonated_service_account_info_with_trust_boundary(self): info = copy.deepcopy(IMPERSONATED_SERVICE_ACCOUNT_AUTHORIZED_USER_SOURCE_INFO) info["trust_boundary"] = self.VALID_TRUST_BOUNDARY - credentials = impersonated_credentials.Credentials.from_impersonated_service_account_info( - info + credentials = ( + impersonated_credentials.Credentials.from_impersonated_service_account_info( + info + ) ) assert isinstance(credentials, impersonated_credentials.Credentials) assert credentials._trust_boundary == self.VALID_TRUST_BOUNDARY @@ -216,8 +218,10 @@ def test_from_impersonated_service_account_info_with_invalid_impersonation_url( def test_from_impersonated_service_account_info_with_scopes(self): info = copy.deepcopy(IMPERSONATED_SERVICE_ACCOUNT_AUTHORIZED_USER_SOURCE_INFO) info["scopes"] = ["scope1", "scope2"] - credentials = impersonated_credentials.Credentials.from_impersonated_service_account_info( - info + credentials = ( + impersonated_credentials.Credentials.from_impersonated_service_account_info( + info + ) ) assert credentials._target_scopes == ["scope1", "scope2"] @@ -225,8 +229,10 @@ def test_from_impersonated_service_account_info_with_scopes_param(self): info = copy.deepcopy(IMPERSONATED_SERVICE_ACCOUNT_AUTHORIZED_USER_SOURCE_INFO) info["scopes"] = ["scope_from_info_1", "scope_from_info_2"] scopes_param = ["scope_from_param_1", "scope_from_param_2"] - credentials = impersonated_credentials.Credentials.from_impersonated_service_account_info( - info, scopes=scopes_param + credentials = ( + impersonated_credentials.Credentials.from_impersonated_service_account_info( + info, scopes=scopes_param + ) ) assert credentials._target_scopes == scopes_param diff --git a/packages/google-auth/tests/test_jwt.py b/packages/google-auth/tests/test_jwt.py index a5a904d7d362..87cafab8b45e 100644 --- a/packages/google-auth/tests/test_jwt.py +++ b/packages/google-auth/tests/test_jwt.py @@ -343,9 +343,9 @@ def test_decode_no_key_id(token_factory): def test_decode_unknown_alg(): - headers = json.dumps({u"kid": u"1", u"alg": u"fakealg"}) + headers = json.dumps({"kid": "1", "alg": "fakealg"}) token = b".".join( - map(lambda seg: base64.b64encode(seg.encode("utf-8")), [headers, u"{}", u"sig"]) + map(lambda seg: base64.b64encode(seg.encode("utf-8")), [headers, "{}", "sig"]) ) with pytest.raises(ValueError) as excinfo: @@ -355,9 +355,9 @@ def test_decode_unknown_alg(): def test_decode_missing_crytography_alg(monkeypatch): monkeypatch.delitem(jwt._ALGORITHM_TO_VERIFIER_CLASS, "ES256") - headers = json.dumps({u"kid": u"1", u"alg": u"ES256"}) + headers = json.dumps({"kid": "1", "alg": "ES256"}) token = b".".join( - map(lambda seg: base64.b64encode(seg.encode("utf-8")), [headers, u"{}", u"sig"]) + map(lambda seg: base64.b64encode(seg.encode("utf-8")), [headers, "{}", "sig"]) ) with pytest.raises(ValueError) as excinfo: diff --git a/packages/google-auth/tests/test_pluggable.py b/packages/google-auth/tests/test_pluggable.py index d15ebb88bab8..5c1b4e6600bc 100644 --- a/packages/google-auth/tests/test_pluggable.py +++ b/packages/google-auth/tests/test_pluggable.py @@ -32,8 +32,10 @@ SERVICE_ACCOUNT_IMPERSONATION_URL_BASE = ( "https://us-east1-iamcredentials.googleapis.com" ) -SERVICE_ACCOUNT_IMPERSONATION_URL_ROUTE = "/v1/projects/-/serviceAccounts/{}:generateAccessToken".format( - SERVICE_ACCOUNT_EMAIL +SERVICE_ACCOUNT_IMPERSONATION_URL_ROUTE = ( + "/v1/projects/-/serviceAccounts/{}:generateAccessToken".format( + SERVICE_ACCOUNT_EMAIL + ) ) SERVICE_ACCOUNT_IMPERSONATION_URL = ( SERVICE_ACCOUNT_IMPERSONATION_URL_BASE + SERVICE_ACCOUNT_IMPERSONATION_URL_ROUTE @@ -543,7 +545,6 @@ def test_retrieve_subject_token_saml(self): @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) def test_retrieve_subject_token_saml_interactive_mode(self, tmpdir): - ACTUAL_CREDENTIAL_SOURCE_EXECUTABLE_OUTPUT_FILE = tmpdir.join( "actual_output_file" ) @@ -1108,7 +1109,7 @@ def test_retrieve_subject_token_non_workforce_fail_interactive_mode(self): @mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"}) def test_retrieve_subject_token_fail_on_validation_missing_interactive_timeout( - self + self, ): CREDENTIAL_SOURCE_EXECUTABLE = { "command": self.CREDENTIAL_SOURCE_EXECUTABLE_COMMAND, diff --git a/packages/google-auth/tests_async/oauth2/test__client_async.py b/packages/google-auth/tests_async/oauth2/test__client_async.py index 7ffbc7ae136c..d4b8d3427384 100644 --- a/packages/google-auth/tests_async/oauth2/test__client_async.py +++ b/packages/google-auth/tests_async/oauth2/test__client_async.py @@ -42,7 +42,6 @@ def make_request(response_data, status=http_client.OK, text=False): @pytest.mark.asyncio async def test__token_endpoint_request(): - request = make_request({"test": "response"}) result = await _client._token_endpoint_request( @@ -63,7 +62,6 @@ async def test__token_endpoint_request(): @pytest.mark.asyncio async def test__token_endpoint_request_text(): - request = make_request("response", text=True) result = await _client._token_endpoint_request( @@ -84,7 +82,6 @@ async def test__token_endpoint_request_text(): @pytest.mark.asyncio async def test__token_endpoint_request_json(): - request = make_request({"test": "response"}) access_token = "access_token" diff --git a/packages/google-auth/tests_async/oauth2/test_credentials_async.py b/packages/google-auth/tests_async/oauth2/test_credentials_async.py index fba0c3cf93c8..4930a70a7591 100644 --- a/packages/google-auth/tests_async/oauth2/test_credentials_async.py +++ b/packages/google-auth/tests_async/oauth2/test_credentials_async.py @@ -29,7 +29,6 @@ class TestCredentials: - TOKEN_URI = "https://example.com/oauth2/token" REFRESH_TOKEN = "refresh_token" CLIENT_ID = "client_id" diff --git a/packages/google-auth/tests_async/transport/test_aiohttp_requests.py b/packages/google-auth/tests_async/transport/test_aiohttp_requests.py index d00955a7debb..7bb709183ad2 100644 --- a/packages/google-auth/tests_async/transport/test_aiohttp_requests.py +++ b/packages/google-auth/tests_async/transport/test_aiohttp_requests.py @@ -39,7 +39,6 @@ def test__is_compressed_not(self): @pytest.mark.asyncio async def test_raw_content(self): - mock_response = mock.AsyncMock() mock_response.content.read.return_value = mock.sentinel.read combined_response = aiohttp_requests._CombinedResponse(response=mock_response) From 9ac4ef526a716f51285d34a7c09da82d98931213 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Wed, 17 Dec 2025 17:04:37 +0000 Subject: [PATCH 965/966] restore 3.7/3.8 --- packages/google-auth/noxfile.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index c9398bbe58f5..25794d5f2ed0 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -33,9 +33,7 @@ ] DEFAULT_PYTHON_VERSION = "3.14" -# TODO(https://github.com/googleapis/google-auth-library-python/issues/1787): -# Remove or restore testing for Python 3.7/3.8 -UNIT_TEST_PYTHON_VERSIONS = ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] +UNIT_TEST_PYTHON_VERSIONS = ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] # Error if a python version is missing nox.options.error_on_missing_interpreters = True From 7990860c919603b9ef87b6f0fa3f8df378512e13 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Wed, 17 Dec 2025 17:27:25 +0000 Subject: [PATCH 966/966] update coverage presubmit --- packages/google-auth/noxfile.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index 25794d5f2ed0..b0e4a100c92a 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -128,19 +128,14 @@ def unit(session): @nox.session(python=DEFAULT_PYTHON_VERSION) def cover(session): - session.env["PIP_EXTRA_INDEX_URL"] = "https://pypi.org/simple" - session.install("-e", ".[testing]") - session.run( - "pytest", - "--cov=google.auth", - "--cov=google.oauth2", - "--cov=tests", - "--cov=tests_async", - "--cov-report=term-missing", - "tests", - "tests_async", - ) + """Run the final coverage report. + + This outputs the coverage report aggregating coverage from the unit + test runs (not system test runs), and then erases coverage data. + """ + session.install("coverage", "pytest-cov") session.run("coverage", "report", "--show-missing", "--fail-under=100") + session.run("coverage", "erase") @nox.session(python="3.10")

        2SB5S6qwesP#}FHeQ%I~D;c4+ejY)>eraB0!6_>4GRb*1MDOw{ZWb z)m;+u%4<)6i-ZHZz5l$c&AGhct$)i6HvxuM3=QD_m94=rzzdejR>x(gfz(4%t@j!S zaI!g+9F3xR8za3=K6RS4YN^~g2+A0*%rM=?Pw3Fvzd8eHEp#MD5=i!YmX@%Wqg1dh z^9WV6yD=OYK=spW2J>Iow&-o10QSxW`@(*I6(;(hz5Rdj5XV<%(0K7W1pH%x7o-xQ zmzir=+#zOf-+=9SNAO)H9|YLuKds}u>{x<}FqP}aO)%hexR|$3n^(uq1K@j?&l<8; zP*BEJ4>XGfb)4}Gme8WwallDKZ9H+u@zJqy-}z{2vUbQ5eO{~P`=E92Z>g8>KYFFY zaZUI(Mj6j)NM5W%hZ5BU#^tuy!MrQQN+@&u0&)|=7+~$mE;1Wc7uoz_k=~vSXijMO z%g9c4v{D9y*38vs)jZLnUYifKw^Ca?acU@mu&YH(Q!-E8=i>g=gw}f3%JJ=%kQJa_Lz@>yJTaw3FBkXr{7KQ+)$)Rf`ifd8of=uUx|q9xiRK*D;3pPK;w4{Vq5 zD>4?2T{AjQIuF2K6!kB;iW)Io8MS+t6j)M*K-$iF!p?npeP`r7Ah!E&?c1=!0fcnM z?MVeQX>K^Vn7VlU-rYlEMn8`~Q(d#cdW}S^oGA>G;t1cmxOK-)!Xjh#l|k zFetWCd@RLQR-3fK@gm%nD1YNh2h_Z&|EN>Lt6)DJiQBq)wmPzn^E4-r-Q5Xa{byA@j)RYrf`fnnBe2_fi0} z`ZLiG?Q^I@0JZof@T3R*MdEnfmwL!OP<7N~wLwQGysapp4;i3QT*m^#%mW{D&PB_chn}TR%Gpxa59WOh6zbS$>S3TX!$(Em`l4AQ#8KrY!C#H=T#VVadH;1zg;w z?uc&L+1x_5%Jd5++yLX@6$9oS32!ACHj-tVm}Z2X^v?qqM=jYadmuu)grporDOxB6 z@>D%h)`v93pWO%zz9m8}-Ucr=T~>Pff|yd)QJZ{qkWagZlhCDf&p1vJ2{9^luG3Do z#TULFDshJ3ckjNJad_2x**m~6@f;_SE9&il3$cAH@*IzXaVVxPR zRMZ;Q^*<*I8s4{Oec6Q7LcCC(dgtG@KFmk3>#o-xm2gYqf2mq(IHizcCTaNp^#pOx zrhr0V(?z7Dv+B!I^(YU#yBN%G1Q##*shlSHNnc?8_7v!BVhQ7CuvaA#*>#3)I)0{| zHg5_jJ1M4W_3;*-wwX;EW0X<%QD!;~P}fDjYfab8xQAIulUe$hn m(CEf7Y*rC%*F@|}eQS-eKv?dTCI|dzM*{cF(_i*a0kSkr+XUbM From 3b2eeccdcded2fe20cbd389d9ed35ee99f01be04 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 31 Oct 2023 12:34:16 -0700 Subject: [PATCH 765/966] chore(main): release 2.23.4 (#1404) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 50606fa7d9b9..ab1ea28e78d4 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.23.4](https://github.com/googleapis/google-auth-library-python/compare/v2.23.3...v2.23.4) (2023-10-31) + + +### Bug Fixes + +* Export detect_gce_residency_linux function ([#1403](https://github.com/googleapis/google-auth-library-python/issues/1403)) ([809da13](https://github.com/googleapis/google-auth-library-python/commit/809da13cedcc2945b7e81f9c5b86b84f75e9113c)) + ## [2.23.3](https://github.com/googleapis/google-auth-library-python/compare/v2.23.2...v2.23.3) (2023-10-05) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 0fdb14ce3787..960d9e9511bf 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.23.3" +__version__ = "2.23.4" From 21bde7b6fd6c9df1489d936e94cbbded5c44b81a Mon Sep 17 00:00:00 2001 From: Jin Date: Mon, 6 Nov 2023 10:29:17 -0800 Subject: [PATCH 766/966] chore: update token (#1407) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 722dfcd24704ef8c0b25e9b21d2dc40c62c303fd..0f9bbd79c534f25a8ce9c6bad313d1488a110fc1 100644 GIT binary patch literal 10324 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 literal 10324 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 Date: Wed, 8 Nov 2023 11:01:15 -0800 Subject: [PATCH 767/966] chore: update token (#1411) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 0f9bbd79c534f25a8ce9c6bad313d1488a110fc1..45df55cb33fd2b5cf0c96c191bb73871bf11ca07 100644 GIT binary patch literal 10324 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 From 95f59a0cac294f8daa8fcd9920e113d9b18e803b Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Wed, 8 Nov 2023 14:42:18 -0500 Subject: [PATCH 768/966] build: remove pkg_resources in docs/conf.py (#1410) Co-authored-by: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> --- packages/google-auth/docs/conf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/docs/conf.py b/packages/google-auth/docs/conf.py index b01c7b6cbd87..8ab609390712 100644 --- a/packages/google-auth/docs/conf.py +++ b/packages/google-auth/docs/conf.py @@ -13,7 +13,7 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import pkg_resources +import google.auth # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the @@ -66,7 +66,7 @@ # built documents. # # The short X.Y version. -version = pkg_resources.get_distribution("google-auth").version +version = google.auth.__version__ # The full version, including alpha/beta/rc tags. release = version From 18d492dfb1815ee95dea0b41e487d2e26c3aeafe Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Fri, 10 Nov 2023 15:54:52 -0800 Subject: [PATCH 769/966] chore: Refresh system test creds. (#1415) Co-authored-by: Carl Lundin --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 45df55cb33fd2b5cf0c96c191bb73871bf11ca07..329b32dca897c4bb28be3aa2183a290d7d667717 100644 GIT binary patch literal 10324 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 literal 10324 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 Date: Fri, 10 Nov 2023 16:34:40 -0800 Subject: [PATCH 770/966] fix: migrate datetime.utcnow for python 3.12 (#1413) --- packages/google-auth/google/auth/_helpers.py | 9 ++++++++- packages/google-auth/tests/test_credentials.py | 6 ++---- .../google-auth/tests_async/test_credentials_async.py | 6 ++---- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/packages/google-auth/google/auth/_helpers.py b/packages/google-auth/google/auth/_helpers.py index f321bc834d0d..e7e5d97cfbce 100644 --- a/packages/google-auth/google/auth/_helpers.py +++ b/packages/google-auth/google/auth/_helpers.py @@ -92,7 +92,14 @@ def utcnow(): Returns: datetime: The current time in UTC. """ - return datetime.datetime.utcnow() + # We used datetime.utcnow() before, since it's deprecated from python 3.12, + # we are using datetime.now(timezone.utc) now. "utcnow()" is offset-native + # (no timezone info), but "now()" is offset-aware (with timezone info). + # This will cause datetime comparison problem. For backward compatibility, + # we need to remove the timezone info. + now = datetime.datetime.now(datetime.timezone.utc) + now = now.replace(tzinfo=None) + return now def datetime_to_secs(value): diff --git a/packages/google-auth/tests/test_credentials.py b/packages/google-auth/tests/test_credentials.py index 5eee35c98e3a..d64f3abb5065 100644 --- a/packages/google-auth/tests/test_credentials.py +++ b/packages/google-auth/tests/test_credentials.py @@ -55,9 +55,7 @@ def test_expired_and_valid(): # Set the expiration to one second more than now plus the clock skew # accomodation. These credentials should be valid. credentials.expiry = ( - datetime.datetime.utcnow() - + _helpers.REFRESH_THRESHOLD - + datetime.timedelta(seconds=1) + _helpers.utcnow() + _helpers.REFRESH_THRESHOLD + datetime.timedelta(seconds=1) ) assert credentials.valid @@ -65,7 +63,7 @@ def test_expired_and_valid(): # Set the credentials expiration to now. Because of the clock skew # accomodation, these credentials should report as expired. - credentials.expiry = datetime.datetime.utcnow() + credentials.expiry = _helpers.utcnow() assert not credentials.valid assert credentials.expired diff --git a/packages/google-auth/tests_async/test_credentials_async.py b/packages/google-auth/tests_async/test_credentials_async.py index 9db5fc9ae754..7d82758c38b6 100644 --- a/packages/google-auth/tests_async/test_credentials_async.py +++ b/packages/google-auth/tests_async/test_credentials_async.py @@ -46,9 +46,7 @@ def test_expired_and_valid(): # Set the expiration to one second more than now plus the clock skew # accomodation. These credentials should be valid. credentials.expiry = ( - datetime.datetime.utcnow() - + _helpers.REFRESH_THRESHOLD - + datetime.timedelta(seconds=1) + _helpers.utcnow() + _helpers.REFRESH_THRESHOLD + datetime.timedelta(seconds=1) ) assert credentials.valid @@ -56,7 +54,7 @@ def test_expired_and_valid(): # Set the credentials expiration to now. Because of the clock skew # accomodation, these credentials should report as expired. - credentials.expiry = datetime.datetime.utcnow() + credentials.expiry = _helpers.utcnow() assert not credentials.valid assert credentials.expired From a31ca71a134cd1d8f3b5e066d72a50ea1cd248c0 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Fri, 10 Nov 2023 20:47:17 -0800 Subject: [PATCH 771/966] docs: update user cred doc (#1414) --- packages/google-auth/google/oauth2/credentials.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/google/oauth2/credentials.py b/packages/google-auth/google/oauth2/credentials.py index 4643fdbea67a..ae204b45a887 100644 --- a/packages/google-auth/google/oauth2/credentials.py +++ b/packages/google-auth/google/oauth2/credentials.py @@ -54,8 +54,9 @@ class Credentials(credentials.ReadOnlyScoped, credentials.CredentialsWithQuotaProject): """Credentials using OAuth 2.0 access and refresh tokens. - The credentials are considered immutable. If you want to modify the - quota project, use :meth:`with_quota_project` or :: + The credentials are considered immutable except the tokens and the token + expiry, which are updated after refresh. If you want to modify the quota + project, use :meth:`with_quota_project` or :: credentials = credentials.with_quota_project('myproject-123') From 0c4e04b073c7a227ba942cf0658913038aaa7f08 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Tue, 14 Nov 2023 13:00:10 -0800 Subject: [PATCH 772/966] fix: auto create self signed jwt cred (#1418) --- .../google/oauth2/service_account.py | 12 +++++------- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../tests/oauth2/test_service_account.py | 14 +++++++++----- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/packages/google-auth/google/oauth2/service_account.py b/packages/google-auth/google/oauth2/service_account.py index 803b13070917..2b5e0d3901b9 100644 --- a/packages/google-auth/google/oauth2/service_account.py +++ b/packages/google-auth/google/oauth2/service_account.py @@ -417,13 +417,11 @@ def _metric_header_for_usage(self): @_helpers.copy_docstring(credentials.Credentials) def refresh(self, request): - if ( - self._universe_domain != _DEFAULT_UNIVERSE_DOMAIN - and not self._jwt_credentials - ): - raise exceptions.RefreshError( - "self._jwt_credentials is missing for non-default universe domain" - ) + if self._always_use_jwt_access and not self._jwt_credentials: + # If self signed jwt should be used but jwt credential is not + # created, try to create one with scopes + self._create_self_signed_jwt(None) + if self._universe_domain != _DEFAULT_UNIVERSE_DOMAIN and self._subject: raise exceptions.RefreshError( "domain wide delegation is not supported for non-default universe domain" diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 329b32dca897c4bb28be3aa2183a290d7d667717..966e9f8b2718d4c5576cceff88d79cfd088e2fd5 100644 GIT binary patch literal 10324 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 literal 10324 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 diff --git a/packages/google-auth/tests/oauth2/test_service_account.py b/packages/google-auth/tests/oauth2/test_service_account.py index b963b157c5e2..f9e0c1186965 100644 --- a/packages/google-auth/tests/oauth2/test_service_account.py +++ b/packages/google-auth/tests/oauth2/test_service_account.py @@ -557,12 +557,16 @@ def test_refresh_jwt_not_used_for_domain_wide_delegation( assert jwt_grant.called assert not self_signed_jwt_refresh.called - def test_refresh_non_gdu_missing_jwt_credentials(self): - credentials = self.make_credentials(universe_domain="foo") + def test_refresh_missing_jwt_credentials(self): + credentials = self.make_credentials() + credentials = credentials.with_scopes(["foo", "bar"]) + credentials = credentials.with_always_use_jwt_access(True) + assert not credentials._jwt_credentials - with pytest.raises(exceptions.RefreshError) as excinfo: - credentials.refresh(None) - assert excinfo.match("self._jwt_credentials is missing") + credentials.refresh(mock.Mock()) + + # jwt credentials should have been automatically created with scopes + assert credentials._jwt_credentials is not None def test_refresh_non_gdu_domain_wide_delegation_not_supported(self): credentials = self.make_credentials(universe_domain="foo") From 3bc9d3277549199955e481a78de2983f7c91197e Mon Sep 17 00:00:00 2001 From: Carl Lundin <108372512+clundin25@users.noreply.github.com> Date: Tue, 14 Nov 2023 20:00:07 -0800 Subject: [PATCH 773/966] feat: Modify the token refresh window (#1419) Set the token refresh window from 30s to 3 minutes and 45 seconds. --- packages/google-auth/google/auth/_helpers.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/google-auth/google/auth/_helpers.py b/packages/google-auth/google/auth/_helpers.py index e7e5d97cfbce..a6c07f7d8296 100644 --- a/packages/google-auth/google/auth/_helpers.py +++ b/packages/google-auth/google/auth/_helpers.py @@ -23,11 +23,9 @@ from google.auth import exceptions -# Token server doesn't provide a new a token when doing refresh unless the -# token is expiring within 30 seconds, so refresh threshold should not be -# more than 30 seconds. Otherwise auth lib will send tons of refresh requests -# until 30 seconds before the expiration, and cause a spike of CPU usage. -REFRESH_THRESHOLD = datetime.timedelta(seconds=20) +# The smallest MDS cache used by this library stores tokens until 4 minutes from +# expiry. +REFRESH_THRESHOLD = datetime.timedelta(minutes=3, seconds=45) def copy_docstring(source_class): From d34b00335543a15f807b76e5b8c79eca10684192 Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Wed, 15 Nov 2023 10:41:12 -0500 Subject: [PATCH 774/966] chore: bump urllib3 from 1.26.12 to 1.26.18 (#1412) Source-Link: https://github.com/googleapis/synthtool/commit/febacccc98d6d224aff9d0bd0373bb5a4cd5969c Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:caffe0a9277daeccc4d1de5c9b55ebba0901b57c2f713ec9c876b0d4ec064f61 Co-authored-by: Owl Bot Co-authored-by: ohmayr --- .../google-auth/.github/.OwlBot.lock.yaml | 4 +- packages/google-auth/.kokoro/requirements.txt | 533 +++++++++--------- 2 files changed, 278 insertions(+), 259 deletions(-) diff --git a/packages/google-auth/.github/.OwlBot.lock.yaml b/packages/google-auth/.github/.OwlBot.lock.yaml index a3da1b0d4cd3..453b540c1e58 100644 --- a/packages/google-auth/.github/.OwlBot.lock.yaml +++ b/packages/google-auth/.github/.OwlBot.lock.yaml @@ -13,5 +13,5 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:3e3800bb100af5d7f9e810d48212b37812c1856d20ffeafb99ebe66461b61fc7 -# created: 2023-08-02T10:53:29.114535628Z + digest: sha256:caffe0a9277daeccc4d1de5c9b55ebba0901b57c2f713ec9c876b0d4ec064f61 +# created: 2023-11-08T19:46:45.022803742Z diff --git a/packages/google-auth/.kokoro/requirements.txt b/packages/google-auth/.kokoro/requirements.txt index 029bd342de94..8957e21104e2 100644 --- a/packages/google-auth/.kokoro/requirements.txt +++ b/packages/google-auth/.kokoro/requirements.txt @@ -4,91 +4,75 @@ # # pip-compile --allow-unsafe --generate-hashes requirements.in # -argcomplete==2.0.0 \ - --hash=sha256:6372ad78c89d662035101418ae253668445b391755cfe94ea52f1b9d22425b20 \ - --hash=sha256:cffa11ea77999bb0dd27bb25ff6dc142a6796142f68d45b1a26b11f58724561e +argcomplete==3.1.4 \ + --hash=sha256:72558ba729e4c468572609817226fb0a6e7e9a0a7d477b882be168c0b4a62b94 \ + --hash=sha256:fbe56f8cda08aa9a04b307d8482ea703e96a6a801611acb4be9bf3942017989f # via nox -attrs==22.1.0 \ - --hash=sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6 \ - --hash=sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c +attrs==23.1.0 \ + --hash=sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04 \ + --hash=sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015 # via gcp-releasetool -bleach==5.0.1 \ - --hash=sha256:085f7f33c15bd408dd9b17a4ad77c577db66d76203e5984b1bd59baeee948b2a \ - --hash=sha256:0d03255c47eb9bd2f26aa9bb7f2107732e7e8fe195ca2f64709fcf3b0a4a085c - # via readme-renderer -cachetools==5.2.0 \ - --hash=sha256:6a94c6402995a99c3970cc7e4884bb60b4a8639938157eeed436098bf9831757 \ - --hash=sha256:f9f17d2aec496a9aa6b76f53e3b614c965223c061982d434d160f930c698a9db +cachetools==5.3.2 \ + --hash=sha256:086ee420196f7b2ab9ca2db2520aca326318b68fe5ba8bc4d49cca91add450f2 \ + --hash=sha256:861f35a13a451f94e301ce2bec7cac63e881232ccce7ed67fab9b5df4d3beaa1 # via google-auth certifi==2023.7.22 \ --hash=sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082 \ --hash=sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9 # via requests -cffi==1.15.1 \ - --hash=sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5 \ - --hash=sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef \ - --hash=sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104 \ - --hash=sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426 \ - --hash=sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405 \ - --hash=sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375 \ - --hash=sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a \ - --hash=sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e \ - --hash=sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc \ - --hash=sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf \ - --hash=sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185 \ - --hash=sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497 \ - --hash=sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3 \ - --hash=sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35 \ - --hash=sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c \ - --hash=sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83 \ - --hash=sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21 \ - --hash=sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca \ - --hash=sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984 \ - --hash=sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac \ - --hash=sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd \ - --hash=sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee \ - --hash=sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a \ - --hash=sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2 \ - --hash=sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192 \ - --hash=sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7 \ - --hash=sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585 \ - --hash=sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f \ - --hash=sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e \ - --hash=sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27 \ - --hash=sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b \ - --hash=sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e \ - --hash=sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e \ - --hash=sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d \ - --hash=sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c \ - --hash=sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415 \ - --hash=sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82 \ - --hash=sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02 \ - --hash=sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314 \ - --hash=sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325 \ - --hash=sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c \ - --hash=sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3 \ - --hash=sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914 \ - --hash=sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045 \ - --hash=sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d \ - --hash=sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9 \ - --hash=sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5 \ - --hash=sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2 \ - --hash=sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c \ - --hash=sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3 \ - --hash=sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2 \ - --hash=sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8 \ - --hash=sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d \ - --hash=sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d \ - --hash=sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9 \ - --hash=sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162 \ - --hash=sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76 \ - --hash=sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4 \ - --hash=sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e \ - --hash=sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9 \ - --hash=sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6 \ - --hash=sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b \ - --hash=sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01 \ - --hash=sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0 +cffi==1.16.0 \ + --hash=sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc \ + --hash=sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a \ + --hash=sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417 \ + --hash=sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab \ + --hash=sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520 \ + --hash=sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36 \ + --hash=sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743 \ + --hash=sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8 \ + --hash=sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed \ + --hash=sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684 \ + --hash=sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56 \ + --hash=sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324 \ + --hash=sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d \ + --hash=sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235 \ + --hash=sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e \ + --hash=sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088 \ + --hash=sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000 \ + --hash=sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7 \ + --hash=sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e \ + --hash=sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673 \ + --hash=sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c \ + --hash=sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe \ + --hash=sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2 \ + --hash=sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098 \ + --hash=sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8 \ + --hash=sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a \ + --hash=sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0 \ + --hash=sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b \ + --hash=sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896 \ + --hash=sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e \ + --hash=sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9 \ + --hash=sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2 \ + --hash=sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b \ + --hash=sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6 \ + --hash=sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404 \ + --hash=sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f \ + --hash=sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0 \ + --hash=sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4 \ + --hash=sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc \ + --hash=sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936 \ + --hash=sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba \ + --hash=sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872 \ + --hash=sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb \ + --hash=sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614 \ + --hash=sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1 \ + --hash=sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d \ + --hash=sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969 \ + --hash=sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b \ + --hash=sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4 \ + --hash=sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627 \ + --hash=sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956 \ + --hash=sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357 # via cryptography charset-normalizer==2.1.1 \ --hash=sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845 \ @@ -109,78 +93,74 @@ colorlog==6.7.0 \ # via # gcp-docuploader # nox -commonmark==0.9.1 \ - --hash=sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60 \ - --hash=sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9 - # via rich -cryptography==41.0.3 \ - --hash=sha256:0d09fb5356f975974dbcb595ad2d178305e5050656affb7890a1583f5e02a306 \ - --hash=sha256:23c2d778cf829f7d0ae180600b17e9fceea3c2ef8b31a99e3c694cbbf3a24b84 \ - --hash=sha256:3fb248989b6363906827284cd20cca63bb1a757e0a2864d4c1682a985e3dca47 \ - --hash=sha256:41d7aa7cdfded09b3d73a47f429c298e80796c8e825ddfadc84c8a7f12df212d \ - --hash=sha256:42cb413e01a5d36da9929baa9d70ca90d90b969269e5a12d39c1e0d475010116 \ - --hash=sha256:4c2f0d35703d61002a2bbdcf15548ebb701cfdd83cdc12471d2bae80878a4207 \ - --hash=sha256:4fd871184321100fb400d759ad0cddddf284c4b696568204d281c902fc7b0d81 \ - --hash=sha256:5259cb659aa43005eb55a0e4ff2c825ca111a0da1814202c64d28a985d33b087 \ - --hash=sha256:57a51b89f954f216a81c9d057bf1a24e2f36e764a1ca9a501a6964eb4a6800dd \ - --hash=sha256:652627a055cb52a84f8c448185922241dd5217443ca194d5739b44612c5e6507 \ - --hash=sha256:67e120e9a577c64fe1f611e53b30b3e69744e5910ff3b6e97e935aeb96005858 \ - --hash=sha256:6af1c6387c531cd364b72c28daa29232162010d952ceb7e5ca8e2827526aceae \ - --hash=sha256:6d192741113ef5e30d89dcb5b956ef4e1578f304708701b8b73d38e3e1461f34 \ - --hash=sha256:7efe8041897fe7a50863e51b77789b657a133c75c3b094e51b5e4b5cec7bf906 \ - --hash=sha256:84537453d57f55a50a5b6835622ee405816999a7113267739a1b4581f83535bd \ - --hash=sha256:8f09daa483aedea50d249ef98ed500569841d6498aa9c9f4b0531b9964658922 \ - --hash=sha256:95dd7f261bb76948b52a5330ba5202b91a26fbac13ad0e9fc8a3ac04752058c7 \ - --hash=sha256:a74fbcdb2a0d46fe00504f571a2a540532f4c188e6ccf26f1f178480117b33c4 \ - --hash=sha256:a983e441a00a9d57a4d7c91b3116a37ae602907a7618b882c8013b5762e80574 \ - --hash=sha256:ab8de0d091acbf778f74286f4989cf3d1528336af1b59f3e5d2ebca8b5fe49e1 \ - --hash=sha256:aeb57c421b34af8f9fe830e1955bf493a86a7996cc1338fe41b30047d16e962c \ - --hash=sha256:ce785cf81a7bdade534297ef9e490ddff800d956625020ab2ec2780a556c313e \ - --hash=sha256:d0d651aa754ef58d75cec6edfbd21259d93810b73f6ec246436a21b7841908de +cryptography==41.0.5 \ + --hash=sha256:0c327cac00f082013c7c9fb6c46b7cc9fa3c288ca702c74773968173bda421bf \ + --hash=sha256:0d2a6a598847c46e3e321a7aef8af1436f11c27f1254933746304ff014664d84 \ + --hash=sha256:227ec057cd32a41c6651701abc0328135e472ed450f47c2766f23267b792a88e \ + --hash=sha256:22892cc830d8b2c89ea60148227631bb96a7da0c1b722f2aac8824b1b7c0b6b8 \ + --hash=sha256:392cb88b597247177172e02da6b7a63deeff1937fa6fec3bbf902ebd75d97ec7 \ + --hash=sha256:3be3ca726e1572517d2bef99a818378bbcf7d7799d5372a46c79c29eb8d166c1 \ + --hash=sha256:573eb7128cbca75f9157dcde974781209463ce56b5804983e11a1c462f0f4e88 \ + --hash=sha256:580afc7b7216deeb87a098ef0674d6ee34ab55993140838b14c9b83312b37b86 \ + --hash=sha256:5a70187954ba7292c7876734183e810b728b4f3965fbe571421cb2434d279179 \ + --hash=sha256:73801ac9736741f220e20435f84ecec75ed70eda90f781a148f1bad546963d81 \ + --hash=sha256:7d208c21e47940369accfc9e85f0de7693d9a5d843c2509b3846b2db170dfd20 \ + --hash=sha256:8254962e6ba1f4d2090c44daf50a547cd5f0bf446dc658a8e5f8156cae0d8548 \ + --hash=sha256:88417bff20162f635f24f849ab182b092697922088b477a7abd6664ddd82291d \ + --hash=sha256:a48e74dad1fb349f3dc1d449ed88e0017d792997a7ad2ec9587ed17405667e6d \ + --hash=sha256:b948e09fe5fb18517d99994184854ebd50b57248736fd4c720ad540560174ec5 \ + --hash=sha256:c707f7afd813478e2019ae32a7c49cd932dd60ab2d2a93e796f68236b7e1fbf1 \ + --hash=sha256:d38e6031e113b7421db1de0c1b1f7739564a88f1684c6b89234fbf6c11b75147 \ + --hash=sha256:d3977f0e276f6f5bf245c403156673db103283266601405376f075c849a0b936 \ + --hash=sha256:da6a0ff8f1016ccc7477e6339e1d50ce5f59b88905585f77193ebd5068f1e797 \ + --hash=sha256:e270c04f4d9b5671ebcc792b3ba5d4488bf7c42c3c241a3748e2599776f29696 \ + --hash=sha256:e886098619d3815e0ad5790c973afeee2c0e6e04b4da90b88e6bd06e2a0b1b72 \ + --hash=sha256:ec3b055ff8f1dce8e6ef28f626e0972981475173d7973d63f271b29c8a2897da \ + --hash=sha256:fba1e91467c65fe64a82c689dc6cf58151158993b13eb7a7f3f4b7f395636723 # via # gcp-releasetool # secretstorage -distlib==0.3.6 \ - --hash=sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46 \ - --hash=sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e +distlib==0.3.7 \ + --hash=sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057 \ + --hash=sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8 # via virtualenv -docutils==0.19 \ - --hash=sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6 \ - --hash=sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc +docutils==0.20.1 \ + --hash=sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6 \ + --hash=sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b # via readme-renderer -filelock==3.8.0 \ - --hash=sha256:55447caa666f2198c5b6b13a26d2084d26fa5b115c00d065664b2124680c4edc \ - --hash=sha256:617eb4e5eedc82fc5f47b6d61e4d11cb837c56cb4544e39081099fa17ad109d4 +filelock==3.13.1 \ + --hash=sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e \ + --hash=sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c # via virtualenv -gcp-docuploader==0.6.4 \ - --hash=sha256:01486419e24633af78fd0167db74a2763974765ee8078ca6eb6964d0ebd388af \ - --hash=sha256:70861190c123d907b3b067da896265ead2eeb9263969d6955c9e0bb091b5ccbf +gcp-docuploader==0.6.5 \ + --hash=sha256:30221d4ac3e5a2b9c69aa52fdbef68cc3f27d0e6d0d90e220fc024584b8d2318 \ + --hash=sha256:b7458ef93f605b9d46a4bf3a8dc1755dad1f31d030c8679edf304e343b347eea # via -r requirements.in -gcp-releasetool==1.10.5 \ - --hash=sha256:174b7b102d704b254f2a26a3eda2c684fd3543320ec239baf771542a2e58e109 \ - --hash=sha256:e29d29927fe2ca493105a82958c6873bb2b90d503acac56be2c229e74de0eec9 +gcp-releasetool==1.16.0 \ + --hash=sha256:27bf19d2e87aaa884096ff941aa3c592c482be3d6a2bfe6f06afafa6af2353e3 \ + --hash=sha256:a316b197a543fd036209d0caba7a8eb4d236d8e65381c80cbc6d7efaa7606d63 # via -r requirements.in -google-api-core==2.10.2 \ - --hash=sha256:10c06f7739fe57781f87523375e8e1a3a4674bf6392cd6131a3222182b971320 \ - --hash=sha256:34f24bd1d5f72a8c4519773d99ca6bf080a6c4e041b4e9f024fe230191dda62e +google-api-core==2.12.0 \ + --hash=sha256:c22e01b1e3c4dcd90998494879612c38d0a3411d1f7b679eb89e2abe3ce1f553 \ + --hash=sha256:ec6054f7d64ad13b41e43d96f735acbd763b0f3b695dabaa2d579673f6a6e160 # via # google-cloud-core # google-cloud-storage -google-auth==2.14.1 \ - --hash=sha256:ccaa901f31ad5cbb562615eb8b664b3dd0bf5404a67618e642307f00613eda4d \ - --hash=sha256:f5d8701633bebc12e0deea4df8abd8aff31c28b355360597f7f2ee60f2e4d016 +google-auth==2.23.4 \ + --hash=sha256:79905d6b1652187def79d491d6e23d0cbb3a21d3c7ba0dbaa9c8a01906b13ff3 \ + --hash=sha256:d4bbc92fe4b8bfd2f3e8d88e5ba7085935da208ee38a134fc280e7ce682a05f2 # via # gcp-releasetool # google-api-core # google-cloud-core # google-cloud-storage -google-cloud-core==2.3.2 \ - --hash=sha256:8417acf6466be2fa85123441696c4badda48db314c607cf1e5d543fa8bdc22fe \ - --hash=sha256:b9529ee7047fd8d4bf4a2182de619154240df17fbe60ead399078c1ae152af9a +google-cloud-core==2.3.3 \ + --hash=sha256:37b80273c8d7eee1ae816b3a20ae43585ea50506cb0e60f3cf5be5f87f1373cb \ + --hash=sha256:fbd11cad3e98a7e5b0343dc07cb1039a5ffd7a5bb96e1f1e27cee4bda4a90863 # via google-cloud-storage -google-cloud-storage==2.6.0 \ - --hash=sha256:104ca28ae61243b637f2f01455cc8a05e8f15a2a18ced96cb587241cdd3820f5 \ - --hash=sha256:4ad0415ff61abdd8bb2ae81c1f8f7ec7d91a1011613f2db87c614c550f97bfe9 +google-cloud-storage==2.13.0 \ + --hash=sha256:ab0bf2e1780a1b74cf17fccb13788070b729f50c252f0c94ada2aae0ca95437d \ + --hash=sha256:f62dc4c7b6cd4360d072e3deb28035fbdad491ac3d9b0b1815a12daea10f37c7 # via gcp-docuploader google-crc32c==1.5.0 \ --hash=sha256:024894d9d3cfbc5943f8f230e23950cd4906b2fe004c72e29b209420a1e6b05a \ @@ -251,29 +231,31 @@ google-crc32c==1.5.0 \ --hash=sha256:f583edb943cf2e09c60441b910d6a20b4d9d626c75a36c8fcac01a6c96c01183 \ --hash=sha256:fd8536e902db7e365f49e7d9029283403974ccf29b13fc7028b97e2295b33556 \ --hash=sha256:fe70e325aa68fa4b5edf7d1a4b6f691eb04bbccac0ace68e34820d283b5f80d4 - # via google-resumable-media -google-resumable-media==2.4.0 \ - --hash=sha256:2aa004c16d295c8f6c33b2b4788ba59d366677c0a25ae7382436cb30f776deaa \ - --hash=sha256:8d5518502f92b9ecc84ac46779bd4f09694ecb3ba38a3e7ca737a86d15cbca1f + # via + # google-cloud-storage + # google-resumable-media +google-resumable-media==2.6.0 \ + --hash=sha256:972852f6c65f933e15a4a210c2b96930763b47197cdf4aa5f5bea435efb626e7 \ + --hash=sha256:fc03d344381970f79eebb632a3c18bb1828593a2dc5572b5f90115ef7d11e81b # via google-cloud-storage -googleapis-common-protos==1.57.0 \ - --hash=sha256:27a849d6205838fb6cc3c1c21cb9800707a661bb21c6ce7fb13e99eb1f8a0c46 \ - --hash=sha256:a9f4a1d7f6d9809657b7f1316a1aa527f6664891531bcfcc13b6696e685f443c +googleapis-common-protos==1.61.0 \ + --hash=sha256:22f1915393bb3245343f6efe87f6fe868532efc12aa26b391b15132e1279f1c0 \ + --hash=sha256:8a64866a97f6304a7179873a465d6eee97b7a24ec6cfd78e0f575e96b821240b # via google-api-core idna==3.4 \ --hash=sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4 \ --hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2 # via requests -importlib-metadata==5.0.0 \ - --hash=sha256:da31db32b304314d044d3c12c79bd59e307889b287ad12ff387b3500835fc2ab \ - --hash=sha256:ddb0e35065e8938f867ed4928d0ae5bf2a53b7773871bfe6bcc7e4fcdc7dea43 +importlib-metadata==6.8.0 \ + --hash=sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb \ + --hash=sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743 # via # -r requirements.in # keyring # twine -jaraco-classes==3.2.3 \ - --hash=sha256:2353de3288bc6b82120752201c6b1c1a14b058267fa424ed5ce5984e3b922158 \ - --hash=sha256:89559fa5c1d3c34eff6f631ad80bb21f378dbcbb35dd161fd2c6b93f5be2f98a +jaraco-classes==3.3.0 \ + --hash=sha256:10afa92b6743f25c0cf5f37c6bb6e18e2c5bb84a16527ccfc0040ea377e7aaeb \ + --hash=sha256:c063dd08e89217cee02c8d5e5ec560f2c8ce6cdc2fcdc2e68f7b2e5547ed3621 # via keyring jeepney==0.8.0 \ --hash=sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806 \ @@ -285,75 +267,121 @@ jinja2==3.1.2 \ --hash=sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852 \ --hash=sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61 # via gcp-releasetool -keyring==23.11.0 \ - --hash=sha256:3dd30011d555f1345dec2c262f0153f2f0ca6bca041fb1dc4588349bb4c0ac1e \ - --hash=sha256:ad192263e2cdd5f12875dedc2da13534359a7e760e77f8d04b50968a821c2361 +keyring==24.2.0 \ + --hash=sha256:4901caaf597bfd3bbd78c9a0c7c4c29fcd8310dab2cffefe749e916b6527acd6 \ + --hash=sha256:ca0746a19ec421219f4d713f848fa297a661a8a8c1504867e55bfb5e09091509 # via # gcp-releasetool # twine -markupsafe==2.1.1 \ - --hash=sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003 \ - --hash=sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88 \ - --hash=sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5 \ - --hash=sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7 \ - --hash=sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a \ - --hash=sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603 \ - --hash=sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1 \ - --hash=sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135 \ - --hash=sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247 \ - --hash=sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6 \ - --hash=sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601 \ - --hash=sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77 \ - --hash=sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02 \ - --hash=sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e \ - --hash=sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63 \ - --hash=sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f \ - --hash=sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980 \ - --hash=sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b \ - --hash=sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812 \ - --hash=sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff \ - --hash=sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96 \ - --hash=sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1 \ - --hash=sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925 \ - --hash=sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a \ - --hash=sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6 \ - --hash=sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e \ - --hash=sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f \ - --hash=sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4 \ - --hash=sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f \ - --hash=sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3 \ - --hash=sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c \ - --hash=sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a \ - --hash=sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417 \ - --hash=sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a \ - --hash=sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a \ - --hash=sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37 \ - --hash=sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452 \ - --hash=sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933 \ - --hash=sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a \ - --hash=sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7 +markdown-it-py==3.0.0 \ + --hash=sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1 \ + --hash=sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb + # via rich +markupsafe==2.1.3 \ + --hash=sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e \ + --hash=sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e \ + --hash=sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431 \ + --hash=sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686 \ + --hash=sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c \ + --hash=sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559 \ + --hash=sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc \ + --hash=sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb \ + --hash=sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939 \ + --hash=sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c \ + --hash=sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0 \ + --hash=sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4 \ + --hash=sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9 \ + --hash=sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575 \ + --hash=sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba \ + --hash=sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d \ + --hash=sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd \ + --hash=sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3 \ + --hash=sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00 \ + --hash=sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155 \ + --hash=sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac \ + --hash=sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52 \ + --hash=sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f \ + --hash=sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8 \ + --hash=sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b \ + --hash=sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007 \ + --hash=sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24 \ + --hash=sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea \ + --hash=sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198 \ + --hash=sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0 \ + --hash=sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee \ + --hash=sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be \ + --hash=sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2 \ + --hash=sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1 \ + --hash=sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707 \ + --hash=sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6 \ + --hash=sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c \ + --hash=sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58 \ + --hash=sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823 \ + --hash=sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779 \ + --hash=sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636 \ + --hash=sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c \ + --hash=sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad \ + --hash=sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee \ + --hash=sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc \ + --hash=sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2 \ + --hash=sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48 \ + --hash=sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7 \ + --hash=sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e \ + --hash=sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b \ + --hash=sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa \ + --hash=sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5 \ + --hash=sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e \ + --hash=sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb \ + --hash=sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9 \ + --hash=sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57 \ + --hash=sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc \ + --hash=sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc \ + --hash=sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2 \ + --hash=sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11 # via jinja2 -more-itertools==9.0.0 \ - --hash=sha256:250e83d7e81d0c87ca6bd942e6aeab8cc9daa6096d12c5308f3f92fa5e5c1f41 \ - --hash=sha256:5a6257e40878ef0520b1803990e3e22303a41b5714006c32a3fd8304b26ea1ab +mdurl==0.1.2 \ + --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \ + --hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba + # via markdown-it-py +more-itertools==10.1.0 \ + --hash=sha256:626c369fa0eb37bac0291bce8259b332fd59ac792fa5497b59837309cd5b114a \ + --hash=sha256:64e0735fcfdc6f3464ea133afe8ea4483b1c5fe3a3d69852e6503b43a0b222e6 # via jaraco-classes -nox==2022.11.21 \ - --hash=sha256:0e41a990e290e274cb205a976c4c97ee3c5234441a8132c8c3fd9ea3c22149eb \ - --hash=sha256:e21c31de0711d1274ca585a2c5fde36b1aa962005ba8e9322bf5eeed16dcd684 +nh3==0.2.14 \ + --hash=sha256:116c9515937f94f0057ef50ebcbcc10600860065953ba56f14473ff706371873 \ + --hash=sha256:18415df36db9b001f71a42a3a5395db79cf23d556996090d293764436e98e8ad \ + --hash=sha256:203cac86e313cf6486704d0ec620a992c8bc164c86d3a4fd3d761dd552d839b5 \ + --hash=sha256:2b0be5c792bd43d0abef8ca39dd8acb3c0611052ce466d0401d51ea0d9aa7525 \ + --hash=sha256:377aaf6a9e7c63962f367158d808c6a1344e2b4f83d071c43fbd631b75c4f0b2 \ + --hash=sha256:525846c56c2bcd376f5eaee76063ebf33cf1e620c1498b2a40107f60cfc6054e \ + --hash=sha256:5529a3bf99402c34056576d80ae5547123f1078da76aa99e8ed79e44fa67282d \ + --hash=sha256:7771d43222b639a4cd9e341f870cee336b9d886de1ad9bec8dddab22fe1de450 \ + --hash=sha256:88c753efbcdfc2644a5012938c6b9753f1c64a5723a67f0301ca43e7b85dcf0e \ + --hash=sha256:93a943cfd3e33bd03f77b97baa11990148687877b74193bf777956b67054dcc6 \ + --hash=sha256:9be2f68fb9a40d8440cbf34cbf40758aa7f6093160bfc7fb018cce8e424f0c3a \ + --hash=sha256:a0c509894fd4dccdff557068e5074999ae3b75f4c5a2d6fb5415e782e25679c4 \ + --hash=sha256:ac8056e937f264995a82bf0053ca898a1cb1c9efc7cd68fa07fe0060734df7e4 \ + --hash=sha256:aed56a86daa43966dd790ba86d4b810b219f75b4bb737461b6886ce2bde38fd6 \ + --hash=sha256:e8986f1dd3221d1e741fda0a12eaa4a273f1d80a35e31a1ffe579e7c621d069e \ + --hash=sha256:f99212a81c62b5f22f9e7c3e347aa00491114a5647e1f13bbebd79c3e5f08d75 + # via readme-renderer +nox==2023.4.22 \ + --hash=sha256:0b1adc619c58ab4fa57d6ab2e7823fe47a32e70202f287d78474adcc7bda1891 \ + --hash=sha256:46c0560b0dc609d7d967dc99e22cb463d3c4caf54a5fda735d6c11b5177e3a9f # via -r requirements.in -packaging==21.3 \ - --hash=sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb \ - --hash=sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522 +packaging==23.2 \ + --hash=sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5 \ + --hash=sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7 # via # gcp-releasetool # nox -pkginfo==1.8.3 \ - --hash=sha256:848865108ec99d4901b2f7e84058b6e7660aae8ae10164e015a6dcf5b242a594 \ - --hash=sha256:a84da4318dd86f870a9447a8c98340aa06216bfc6f2b7bdc4b8766984ae1867c +pkginfo==1.9.6 \ + --hash=sha256:4b7a555a6d5a22169fcc9cf7bfd78d296b0361adad412a346c1226849af5e546 \ + --hash=sha256:8fd5896e8718a4372f0ea9cc9d96f6417c9b986e23a4d116dda26b62cc29d046 # via twine -platformdirs==2.5.4 \ - --hash=sha256:1006647646d80f16130f052404c6b901e80ee4ed6bef6792e1f238a8969106f7 \ - --hash=sha256:af0276409f9a02373d540bf8480021a048711d572745aef4b7842dad245eba10 +platformdirs==3.11.0 \ + --hash=sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3 \ + --hash=sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e # via virtualenv protobuf==3.20.3 \ --hash=sha256:03038ac1cfbc41aa21f6afcbcd357281d7521b4157926f30ebecc8d4ea59dcb7 \ @@ -382,34 +410,31 @@ protobuf==3.20.3 \ # gcp-docuploader # gcp-releasetool # google-api-core -pyasn1==0.4.8 \ - --hash=sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d \ - --hash=sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba + # googleapis-common-protos +pyasn1==0.5.0 \ + --hash=sha256:87a2121042a1ac9358cabcaf1d07680ff97ee6404333bacca15f76aa8ad01a57 \ + --hash=sha256:97b7290ca68e62a832558ec3976f15cbf911bf5d7c7039d8b861c2a0ece69fde # via # pyasn1-modules # rsa -pyasn1-modules==0.2.8 \ - --hash=sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e \ - --hash=sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74 +pyasn1-modules==0.3.0 \ + --hash=sha256:5bd01446b736eb9d31512a30d46c1ac3395d676c6f3cafa4c03eb54b9925631c \ + --hash=sha256:d3ccd6ed470d9ffbc716be08bd90efbd44d0734bc9303818f7336070984a162d # via google-auth pycparser==2.21 \ --hash=sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 \ --hash=sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206 # via cffi -pygments==2.15.0 \ - --hash=sha256:77a3299119af881904cd5ecd1ac6a66214b6e9bed1f2db16993b54adede64094 \ - --hash=sha256:f7e36cffc4c517fbc252861b9a6e4644ca0e5abadf9a113c72d1358ad09b9500 +pygments==2.16.1 \ + --hash=sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692 \ + --hash=sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29 # via # readme-renderer # rich -pyjwt==2.6.0 \ - --hash=sha256:69285c7e31fc44f68a1feb309e948e0df53259d579295e6cfe2b1792329f05fd \ - --hash=sha256:d83c3d892a77bbb74d3e1a2cfa90afaadb60945205d1095d9221f04466f64c14 +pyjwt==2.8.0 \ + --hash=sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de \ + --hash=sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320 # via gcp-releasetool -pyparsing==3.0.9 \ - --hash=sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb \ - --hash=sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc - # via packaging pyperclip==1.8.2 \ --hash=sha256:105254a8b04934f0bc84e9c24eb360a591aaf6535c9def5f29d92af107a9bf57 # via gcp-releasetool @@ -417,9 +442,9 @@ python-dateutil==2.8.2 \ --hash=sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 \ --hash=sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9 # via gcp-releasetool -readme-renderer==37.3 \ - --hash=sha256:cd653186dfc73055656f090f227f5cb22a046d7f71a841dfa305f55c9a513273 \ - --hash=sha256:f67a16caedfa71eef48a31b39708637a6f4664c4394801a7b0d6432d13907343 +readme-renderer==42.0 \ + --hash=sha256:13d039515c1f24de668e2c93f2e877b9dbe6c6c32328b90a40a49d8b2b85f36d \ + --hash=sha256:2d55489f83be4992fe4454939d1a051c33edbab778e82761d060c9fc6b308cd1 # via twine requests==2.31.0 \ --hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \ @@ -430,17 +455,17 @@ requests==2.31.0 \ # google-cloud-storage # requests-toolbelt # twine -requests-toolbelt==0.10.1 \ - --hash=sha256:18565aa58116d9951ac39baa288d3adb5b3ff975c4f25eee78555d89e8f247f7 \ - --hash=sha256:62e09f7ff5ccbda92772a29f394a49c3ad6cb181d568b1337626b2abb628a63d +requests-toolbelt==1.0.0 \ + --hash=sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6 \ + --hash=sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06 # via twine rfc3986==2.0.0 \ --hash=sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd \ --hash=sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c # via twine -rich==12.6.0 \ - --hash=sha256:a4eb26484f2c82589bd9a17c73d32a010b1e29d89f1604cd9bf3a2097b81bb5e \ - --hash=sha256:ba3a3775974105c221d31141f2c116f4fd65c5ceb0698657a11e9f295ec93fd0 +rich==13.6.0 \ + --hash=sha256:2b38e2fe9ca72c9a00170a1a2d20c63c790d0e10ef1fe35eba76e1e7b1d7d245 \ + --hash=sha256:5c14d22737e6d5084ef4771b62d5d4363165b403455a30a1c8ca39dc7b644bef # via twine rsa==4.9 \ --hash=sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7 \ @@ -454,43 +479,37 @@ six==1.16.0 \ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 # via - # bleach # gcp-docuploader - # google-auth # python-dateutil -twine==4.0.1 \ - --hash=sha256:42026c18e394eac3e06693ee52010baa5313e4811d5a11050e7d48436cf41b9e \ - --hash=sha256:96b1cf12f7ae611a4a40b6ae8e9570215daff0611828f5fe1f37a16255ab24a0 +twine==4.0.2 \ + --hash=sha256:929bc3c280033347a00f847236564d1c52a3e61b1ac2516c97c48f3ceab756d8 \ + --hash=sha256:9e102ef5fdd5a20661eb88fad46338806c3bd32cf1db729603fe3697b1bc83c8 # via -r requirements.in -typing-extensions==4.4.0 \ - --hash=sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa \ - --hash=sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e +typing-extensions==4.8.0 \ + --hash=sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0 \ + --hash=sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef # via -r requirements.in -urllib3==1.26.12 \ - --hash=sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e \ - --hash=sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997 +urllib3==2.0.7 \ + --hash=sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84 \ + --hash=sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e # via # requests # twine -virtualenv==20.16.7 \ - --hash=sha256:8691e3ff9387f743e00f6bb20f70121f5e4f596cae754531f2b3b3a1b1ac696e \ - --hash=sha256:efd66b00386fdb7dbe4822d172303f40cd05e50e01740b19ea42425cbe653e29 +virtualenv==20.24.6 \ + --hash=sha256:02ece4f56fbf939dbbc33c0715159951d6bf14aaf5457b092e4548e1382455af \ + --hash=sha256:520d056652454c5098a00c0f073611ccbea4c79089331f60bf9d7ba247bb7381 # via nox -webencodings==0.5.1 \ - --hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \ - --hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923 - # via bleach -wheel==0.38.4 \ - --hash=sha256:965f5259b566725405b05e7cf774052044b1ed30119b5d586b2703aafe8719ac \ - --hash=sha256:b60533f3f5d530e971d6737ca6d58681ee434818fab630c83a734bb10c083ce8 +wheel==0.41.3 \ + --hash=sha256:488609bc63a29322326e05560731bf7bfea8e48ad646e1f5e40d366607de0942 \ + --hash=sha256:4d4987ce51a49370ea65c0bfd2234e8ce80a12780820d9dc462597a6e60d0841 # via -r requirements.in -zipp==3.10.0 \ - --hash=sha256:4fcb6f278987a6605757302a6e40e896257570d11c51628968ccb2a47e80c6c1 \ - --hash=sha256:7a7262fd930bd3e36c50b9a64897aec3fafff3dfdeec9623ae22b40e93f99bb8 +zipp==3.17.0 \ + --hash=sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31 \ + --hash=sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0 # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: -setuptools==65.5.1 \ - --hash=sha256:d0b9a8433464d5800cbe05094acf5c6d52a91bfac9b52bcfc4d41382be5d5d31 \ - --hash=sha256:e197a19aa8ec9722928f2206f8de752def0e4c9fc6953527360d1c36d94ddb2f +setuptools==68.2.2 \ + --hash=sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87 \ + --hash=sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a # via -r requirements.in From 293c19dfb130e75b951450de7c3ff56e30d49e9b Mon Sep 17 00:00:00 2001 From: Carl Lundin <108372512+clundin25@users.noreply.github.com> Date: Wed, 15 Nov 2023 15:46:14 -0800 Subject: [PATCH 775/966] fix: Add missing before request to async oauth2 credentials. (#1420) This resolves https://togithub.com/googleapis/google-auth-library-python/issues/1417. Down the road we may want to consider modifying the inheritance pattern of the async classes, instead of having this duplication. --- .../google/oauth2/_credentials_async.py | 6 +++++ .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../oauth2/test_credentials_async.py | 23 ++++++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/packages/google-auth/google/oauth2/_credentials_async.py b/packages/google-auth/google/oauth2/_credentials_async.py index e7b9637c82f1..b5561aae0229 100644 --- a/packages/google-auth/google/oauth2/_credentials_async.py +++ b/packages/google-auth/google/oauth2/_credentials_async.py @@ -96,6 +96,12 @@ async def refresh(self, request): ) ) + @_helpers.copy_docstring(credentials.Credentials) + async def before_request(self, request, method, url, headers): + if not self.valid: + await self.refresh(request) + self.apply(headers) + class UserAccessTokenCredentials(oauth2_credentials.UserAccessTokenCredentials): """Access token credentials for user account. diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 966e9f8b2718d4c5576cceff88d79cfd088e2fd5..a8ffdb89f44ad2b2f4bed194926f940b718a4f2e 100644 GIT binary patch literal 10324 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 diff --git a/packages/google-auth/tests_async/oauth2/test_credentials_async.py b/packages/google-auth/tests_async/oauth2/test_credentials_async.py index a328cc3cba6d..f6c640ad63c2 100644 --- a/packages/google-auth/tests_async/oauth2/test_credentials_async.py +++ b/packages/google-auth/tests_async/oauth2/test_credentials_async.py @@ -463,6 +463,29 @@ def test_unpickle_old_credentials_pickle(self): credentials = pickle.load(f) assert credentials.quota_project_id is None + @mock.patch("google.oauth2._credentials_async.Credentials.apply", autospec=True) + @mock.patch("google.oauth2._credentials_async.Credentials.refresh", autospec=True) + @pytest.mark.asyncio + async def test_before_request(self, refresh, apply): + cred = self.make_credentials() + assert not cred.valid + await cred.before_request(mock.Mock(), "GET", "https://example.com", {}) + refresh.assert_called() + apply.assert_called() + + @mock.patch("google.oauth2._credentials_async.Credentials.apply", autospec=True) + @mock.patch("google.oauth2._credentials_async.Credentials.refresh", autospec=True) + @pytest.mark.asyncio + async def test_before_request_no_refresh(self, refresh, apply): + cred = self.make_credentials() + cred.token = refresh + cred.expiry = None + + assert cred.valid + await cred.before_request(mock.Mock(), "GET", "https://example.com", {}) + refresh.assert_not_called() + apply.assert_called() + class TestUserAccessTokenCredentials(object): def test_instance(self): From ed7fa00e1547e66749d4d6d5418bce6034fa8697 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Sat, 25 Nov 2023 23:39:40 -0800 Subject: [PATCH 776/966] feat: add universe domain support for VM cred (#1409) * feat: add universe domain support for VM cred * chore: refresh sys test cred --- .../google/auth/compute_engine/_metadata.py | 53 +++++++++++++--- .../google/auth/compute_engine/credentials.py | 9 +++ .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../tests/compute_engine/test__metadata.py | 59 ++++++++++++++++++ .../tests/compute_engine/test_credentials.py | 20 ++++++ 5 files changed, 132 insertions(+), 9 deletions(-) diff --git a/packages/google-auth/google/auth/compute_engine/_metadata.py b/packages/google-auth/google/auth/compute_engine/_metadata.py index 1b2f5161a9bb..1c884c3c43e3 100644 --- a/packages/google-auth/google/auth/compute_engine/_metadata.py +++ b/packages/google-auth/google/auth/compute_engine/_metadata.py @@ -156,6 +156,7 @@ def get( recursive=False, retry_count=5, headers=None, + return_none_for_not_found_error=False, ): """Fetch a resource from the metadata server. @@ -173,6 +174,8 @@ def get( retry_count (int): How many times to attempt connecting to metadata server using above timeout. headers (Optional[Mapping[str, str]]): Headers for the request. + return_none_for_not_found_error (Optional[bool]): If True, returns None + for 404 error instead of throwing an exception. Returns: Union[Mapping, str]: If the metadata server returns JSON, a mapping of @@ -216,8 +219,17 @@ def get( "metadata service. Compute Engine Metadata server unavailable".format(url) ) + content = _helpers.from_bytes(response.data) + + if response.status == http_client.NOT_FOUND and return_none_for_not_found_error: + _LOGGER.info( + "Compute Engine Metadata server call to %s returned 404, reason: %s", + path, + content, + ) + return None + if response.status == http_client.OK: - content = _helpers.from_bytes(response.data) if ( _helpers.parse_content_type(response.headers["content-type"]) == "application/json" @@ -232,14 +244,14 @@ def get( raise new_exc from caught_exc else: return content - else: - raise exceptions.TransportError( - "Failed to retrieve {} from the Google Compute Engine " - "metadata service. Status: {} Response:\n{}".format( - url, response.status, response.data - ), - response, - ) + + raise exceptions.TransportError( + "Failed to retrieve {} from the Google Compute Engine " + "metadata service. Status: {} Response:\n{}".format( + url, response.status, response.data + ), + response, + ) def get_project_id(request): @@ -259,6 +271,29 @@ def get_project_id(request): return get(request, "project/project-id") +def get_universe_domain(request): + """Get the universe domain value from the metadata server. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + + Returns: + str: The universe domain value. If the universe domain endpoint is not + not found, return the default value, which is googleapis.com + + Raises: + google.auth.exceptions.TransportError: if an error other than + 404 occurs while retrieving metadata. + """ + universe_domain = get( + request, "universe/universe_domain", return_none_for_not_found_error=True + ) + if not universe_domain: + return "googleapis.com" + return universe_domain + + def get_service_account_info(request, service_account="default"): """Get information about a service account from the metadata server. diff --git a/packages/google-auth/google/auth/compute_engine/credentials.py b/packages/google-auth/google/auth/compute_engine/credentials.py index 7ae673880f94..fa30aa44afe3 100644 --- a/packages/google-auth/google/auth/compute_engine/credentials.py +++ b/packages/google-auth/google/auth/compute_engine/credentials.py @@ -73,6 +73,7 @@ def __init__( self._quota_project_id = quota_project_id self._scopes = scopes self._default_scopes = default_scopes + self._universe_domain_cached = False def _retrieve_info(self, request): """Retrieve information about the service account. @@ -131,6 +132,14 @@ def service_account_email(self): def requires_scopes(self): return not self._scopes + @property + def universe_domain(self): + if self._universe_domain_cached: + return self._universe_domain + self._universe_domain = _metadata.get_universe_domain() + self._universe_domain_cached = True + return self._universe_domain + @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) def with_quota_project(self, quota_project_id): return self.__class__( diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index a8ffdb89f44ad2b2f4bed194926f940b718a4f2e..06696ab2ab4464b5cd1c5246fa09304e189166b2 100644 GIT binary patch literal 10324 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 mwMK1j`24|K(om4a)5Or3Y_);fr`-7S^+)8Ln# Date: Tue, 28 Nov 2023 19:23:29 -0500 Subject: [PATCH 777/966] feat: Add support for Python 3.12 (#1421) * feat: Add support for Python 3.12 * Add samples lint session * Add constraints file for python 3.12 * chore: Refresh system test creds. * only run specified nox session for samples --------- Co-authored-by: Carl Lundin --- .../.github/sync-repo-settings.yaml | 2 + .../google-auth/.kokoro/samples-test-setup.sh | 4 +- .../.kokoro/samples/lint/common.cfg | 7 +++ .../.kokoro/samples/python3.10/common.cfg | 6 +++ .../.kokoro/samples/python3.11/common.cfg | 37 ++++++++++++++ .../.kokoro/samples/python3.11/continuous.cfg | 6 +++ .../samples/python3.11/periodic-head.cfg | 11 +++++ .../.kokoro/samples/python3.11/periodic.cfg | 6 +++ .../.kokoro/samples/python3.11/presubmit.cfg | 6 +++ .../.kokoro/samples/python3.12/common.cfg | 37 ++++++++++++++ .../.kokoro/samples/python3.12/continuous.cfg | 6 +++ .../samples/python3.12/periodic-head.cfg | 11 +++++ .../.kokoro/samples/python3.12/periodic.cfg | 6 +++ .../.kokoro/samples/python3.12/presubmit.cfg | 6 +++ .../.kokoro/samples/python3.7/common.cfg | 7 +++ .../.kokoro/samples/python3.8/common.cfg | 6 +++ .../.kokoro/samples/python3.9/common.cfg | 6 +++ packages/google-auth/CONTRIBUTING.rst | 2 +- packages/google-auth/noxfile.py | 2 +- .../authenticate_implicit_with_adc.py | 2 +- .../samples/cloud-client/snippets/noxfile.py | 45 ++++++++++++++++-- packages/google-auth/setup.py | 1 + .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../google-auth/testing/constraints-2.7.txt | 1 - .../google-auth/testing/constraints-3.12.txt | 1 + 25 files changed, 214 insertions(+), 10 deletions(-) create mode 100644 packages/google-auth/.kokoro/samples/python3.11/common.cfg create mode 100644 packages/google-auth/.kokoro/samples/python3.11/continuous.cfg create mode 100644 packages/google-auth/.kokoro/samples/python3.11/periodic-head.cfg create mode 100644 packages/google-auth/.kokoro/samples/python3.11/periodic.cfg create mode 100644 packages/google-auth/.kokoro/samples/python3.11/presubmit.cfg create mode 100644 packages/google-auth/.kokoro/samples/python3.12/common.cfg create mode 100644 packages/google-auth/.kokoro/samples/python3.12/continuous.cfg create mode 100644 packages/google-auth/.kokoro/samples/python3.12/periodic-head.cfg create mode 100644 packages/google-auth/.kokoro/samples/python3.12/periodic.cfg create mode 100644 packages/google-auth/.kokoro/samples/python3.12/presubmit.cfg delete mode 100644 packages/google-auth/testing/constraints-2.7.txt create mode 100644 packages/google-auth/testing/constraints-3.12.txt diff --git a/packages/google-auth/.github/sync-repo-settings.yaml b/packages/google-auth/.github/sync-repo-settings.yaml index ab8bb03fa44f..5ebb96e6a219 100644 --- a/packages/google-auth/.github/sync-repo-settings.yaml +++ b/packages/google-auth/.github/sync-repo-settings.yaml @@ -15,6 +15,8 @@ branchProtectionRules: - 'Samples - Python 3.8' - 'Samples - Python 3.9' - 'Samples - Python 3.10' + - 'Samples - Python 3.11' + - 'Samples - Python 3.12' permissionRules: - team: actools-python permission: admin diff --git a/packages/google-auth/.kokoro/samples-test-setup.sh b/packages/google-auth/.kokoro/samples-test-setup.sh index 938402a92202..5be9713ef7a0 100755 --- a/packages/google-auth/.kokoro/samples-test-setup.sh +++ b/packages/google-auth/.kokoro/samples-test-setup.sh @@ -44,5 +44,5 @@ gcloud config set project ${PROJECT_ID} # Decrypt system test secrets ./scripts/decrypt-secrets.sh -# Run system tests which use a different noxfile -python3 -m nox -f samples/cloud-client/snippets/noxfile.py +# Run samples tests which use a different noxfile +python3 -m nox -f samples/cloud-client/snippets/noxfile.py -s "$RUN_TESTS_SESSION" diff --git a/packages/google-auth/.kokoro/samples/lint/common.cfg b/packages/google-auth/.kokoro/samples/lint/common.cfg index da47830eb659..e38920a08be4 100644 --- a/packages/google-auth/.kokoro/samples/lint/common.cfg +++ b/packages/google-auth/.kokoro/samples/lint/common.cfg @@ -7,6 +7,13 @@ action { } } +# Specify which tests to run +env_vars: { + key: "RUN_TESTS_SESSION" + value: "lint" +} + + # Download trampoline resources. gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" diff --git a/packages/google-auth/.kokoro/samples/python3.10/common.cfg b/packages/google-auth/.kokoro/samples/python3.10/common.cfg index da47830eb659..022eaafda85c 100644 --- a/packages/google-auth/.kokoro/samples/python3.10/common.cfg +++ b/packages/google-auth/.kokoro/samples/python3.10/common.cfg @@ -7,6 +7,12 @@ action { } } +# Specify which tests to run +env_vars: { + key: "RUN_TESTS_SESSION" + value: "unit-3.10" +} + # Download trampoline resources. gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" diff --git a/packages/google-auth/.kokoro/samples/python3.11/common.cfg b/packages/google-auth/.kokoro/samples/python3.11/common.cfg new file mode 100644 index 000000000000..25be29326bc2 --- /dev/null +++ b/packages/google-auth/.kokoro/samples/python3.11/common.cfg @@ -0,0 +1,37 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Build logs will be here +action { + define_artifacts { + regex: "**/*sponge_log.xml" + } +} + +# Specify which tests to run +env_vars: { + key: "RUN_TESTS_SESSION" + value: "unit-3.11" +} + +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" + +# Download resources for system tests (service account key, etc.) +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-auth-library-python" + +# Use the trampoline script to run in docker. +build_file: "google-auth-library-python/.kokoro/trampoline.sh" + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/python-multi" +} +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/google-auth-library-python/.kokoro/build.sh" +} +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/google-auth-library-python/.kokoro/samples-test-setup.sh" +} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.11/continuous.cfg b/packages/google-auth/.kokoro/samples/python3.11/continuous.cfg new file mode 100644 index 000000000000..a1c8d9759c88 --- /dev/null +++ b/packages/google-auth/.kokoro/samples/python3.11/continuous.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.11/periodic-head.cfg b/packages/google-auth/.kokoro/samples/python3.11/periodic-head.cfg new file mode 100644 index 000000000000..83eace873ccb --- /dev/null +++ b/packages/google-auth/.kokoro/samples/python3.11/periodic-head.cfg @@ -0,0 +1,11 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/google-auth-library-python/.kokoro/test-samples-against-head.sh" +} diff --git a/packages/google-auth/.kokoro/samples/python3.11/periodic.cfg b/packages/google-auth/.kokoro/samples/python3.11/periodic.cfg new file mode 100644 index 000000000000..71cd1e597e38 --- /dev/null +++ b/packages/google-auth/.kokoro/samples/python3.11/periodic.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "False" +} diff --git a/packages/google-auth/.kokoro/samples/python3.11/presubmit.cfg b/packages/google-auth/.kokoro/samples/python3.11/presubmit.cfg new file mode 100644 index 000000000000..a1c8d9759c88 --- /dev/null +++ b/packages/google-auth/.kokoro/samples/python3.11/presubmit.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.12/common.cfg b/packages/google-auth/.kokoro/samples/python3.12/common.cfg new file mode 100644 index 000000000000..25d2ea1deccc --- /dev/null +++ b/packages/google-auth/.kokoro/samples/python3.12/common.cfg @@ -0,0 +1,37 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Build logs will be here +action { + define_artifacts { + regex: "**/*sponge_log.xml" + } +} + +# Specify which tests to run +env_vars: { + key: "RUN_TESTS_SESSION" + value: "unit-3.12" +} + +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" + +# Download resources for system tests (service account key, etc.) +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-auth-library-python" + +# Use the trampoline script to run in docker. +build_file: "google-auth-library-python/.kokoro/trampoline.sh" + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/python-multi" +} +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/google-auth-library-python/.kokoro/build.sh" +} +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/google-auth-library-python/.kokoro/samples-test-setup.sh" +} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.12/continuous.cfg b/packages/google-auth/.kokoro/samples/python3.12/continuous.cfg new file mode 100644 index 000000000000..a1c8d9759c88 --- /dev/null +++ b/packages/google-auth/.kokoro/samples/python3.12/continuous.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.12/periodic-head.cfg b/packages/google-auth/.kokoro/samples/python3.12/periodic-head.cfg new file mode 100644 index 000000000000..83eace873ccb --- /dev/null +++ b/packages/google-auth/.kokoro/samples/python3.12/periodic-head.cfg @@ -0,0 +1,11 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/google-auth-library-python/.kokoro/test-samples-against-head.sh" +} diff --git a/packages/google-auth/.kokoro/samples/python3.12/periodic.cfg b/packages/google-auth/.kokoro/samples/python3.12/periodic.cfg new file mode 100644 index 000000000000..71cd1e597e38 --- /dev/null +++ b/packages/google-auth/.kokoro/samples/python3.12/periodic.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "False" +} diff --git a/packages/google-auth/.kokoro/samples/python3.12/presubmit.cfg b/packages/google-auth/.kokoro/samples/python3.12/presubmit.cfg new file mode 100644 index 000000000000..a1c8d9759c88 --- /dev/null +++ b/packages/google-auth/.kokoro/samples/python3.12/presubmit.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} \ No newline at end of file diff --git a/packages/google-auth/.kokoro/samples/python3.7/common.cfg b/packages/google-auth/.kokoro/samples/python3.7/common.cfg index da47830eb659..123c3e122324 100644 --- a/packages/google-auth/.kokoro/samples/python3.7/common.cfg +++ b/packages/google-auth/.kokoro/samples/python3.7/common.cfg @@ -7,6 +7,13 @@ action { } } + +# Specify which tests to run +env_vars: { + key: "RUN_TESTS_SESSION" + value: "unit-3.7" +} + # Download trampoline resources. gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" diff --git a/packages/google-auth/.kokoro/samples/python3.8/common.cfg b/packages/google-auth/.kokoro/samples/python3.8/common.cfg index da47830eb659..62108f6be0ab 100644 --- a/packages/google-auth/.kokoro/samples/python3.8/common.cfg +++ b/packages/google-auth/.kokoro/samples/python3.8/common.cfg @@ -7,6 +7,12 @@ action { } } +# Specify which tests to run +env_vars: { + key: "RUN_TESTS_SESSION" + value: "unit-3.8" +} + # Download trampoline resources. gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" diff --git a/packages/google-auth/.kokoro/samples/python3.9/common.cfg b/packages/google-auth/.kokoro/samples/python3.9/common.cfg index da47830eb659..2649c6c6eeed 100644 --- a/packages/google-auth/.kokoro/samples/python3.9/common.cfg +++ b/packages/google-auth/.kokoro/samples/python3.9/common.cfg @@ -7,6 +7,12 @@ action { } } +# Specify which tests to run +env_vars: { + key: "RUN_TESTS_SESSION" + value: "unit-3.9" +} + # Download trampoline resources. gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" diff --git a/packages/google-auth/CONTRIBUTING.rst b/packages/google-auth/CONTRIBUTING.rst index 3d07261ec8ad..34a91ec7addf 100644 --- a/packages/google-auth/CONTRIBUTING.rst +++ b/packages/google-auth/CONTRIBUTING.rst @@ -19,7 +19,7 @@ A few notes on making changes to ``google-auth-library-python``. using ``nox -s docgen``. - The change must work fully on the following CPython versions: - 3.7, 3.8, 3.9, 3.10 across macOS, Linux, and Windows. + 3.7, 3.8, 3.9, 3.10, 3.11 and 3.12 across macOS, Linux, and Windows. - The codebase *must* have 100% test statement coverage after each commit. You can test coverage via ``nox -e cover``. diff --git a/packages/google-auth/noxfile.py b/packages/google-auth/noxfile.py index 4ec313407987..07cef9bcce3d 100644 --- a/packages/google-auth/noxfile.py +++ b/packages/google-auth/noxfile.py @@ -84,7 +84,7 @@ def mypy(session): session.run("mypy", "-p", "google", "-p", "tests", "-p", "tests_async") -@nox.session(python=["3.7", "3.8", "3.9", "3.10", "3.11"]) +@nox.session(python=["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"]) def unit(session): constraints_path = str( CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt" diff --git a/packages/google-auth/samples/cloud-client/snippets/authenticate_implicit_with_adc.py b/packages/google-auth/samples/cloud-client/snippets/authenticate_implicit_with_adc.py index 006e75bf69ec..2c1a0414d39a 100644 --- a/packages/google-auth/samples/cloud-client/snippets/authenticate_implicit_with_adc.py +++ b/packages/google-auth/samples/cloud-client/snippets/authenticate_implicit_with_adc.py @@ -43,4 +43,4 @@ def authenticate_implicit_with_adc(project_id="your-google-cloud-project-id"): print(bucket.name) print("Listed all storage buckets.") -# [END auth_cloud_implicit_adc] \ No newline at end of file +# [END auth_cloud_implicit_adc] diff --git a/packages/google-auth/samples/cloud-client/snippets/noxfile.py b/packages/google-auth/samples/cloud-client/snippets/noxfile.py index e129dca59254..f5d827941a8f 100644 --- a/packages/google-auth/samples/cloud-client/snippets/noxfile.py +++ b/packages/google-auth/samples/cloud-client/snippets/noxfile.py @@ -12,9 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os import pathlib -import shutil import nox @@ -33,7 +31,36 @@ "docs/conf.py", ] -@nox.session(python=["3.7", "3.8", "3.9", "3.10"]) + +# Error if a python version is missing +nox.options.error_on_missing_interpreters = True + +# +# Style Checks +# + + +# Linting with flake8. +# +# We ignore the following rules: +# E203: whitespace before ‘:’ +# E266: too many leading ‘#’ for block comment +# E501: line too long +# I202: Additional newline in a section of imports +# +# We also need to specify the rules which are ignored by default: +# ['E226', 'W504', 'E126', 'E123', 'W503', 'E24', 'E704', 'E121'] +FLAKE8_COMMON_ARGS = [ + "--show-source", + "--builtin=gettext", + "--max-complexity=20", + "--exclude=.nox,.cache,env,lib,generated_pb2,*_pb2.py,*_pb2_grpc.py", + "--ignore=E121,E123,E126,E203,E226,E24,E266,E501,E704,W503,W504,I202", + "--max-line-length=88", +] + + +@nox.session(python=["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"]) def unit(session): # constraints_path = str( # CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt" @@ -45,4 +72,14 @@ def unit(session): f"--junitxml=unit_{session.python}_sponge_log.xml", "snippets_test.py", # "tests_async", - ) \ No newline at end of file + ) + + +@nox.session +def lint(session: nox.sessions.Session) -> None: + session.install("flake8") + + args = FLAKE8_COMMON_ARGS + [ + ".", + ] + session.run("flake8", *args) diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 047818ca0612..71a3655fad56 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -70,6 +70,7 @@ "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 06696ab2ab4464b5cd1c5246fa09304e189166b2..937b6f065e8e90a37d6aaba424e4b73010ef785b 100644 GIT binary patch literal 10324 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 literal 10324 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 maDa>Rh^@{#K}!?W}skBc_JS4|12.0.0 \ No newline at end of file From 27db87ad387ba4dc70e5e729f8598eb139be3a83 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 30 Nov 2023 09:43:15 -0800 Subject: [PATCH 778/966] chore(main): release 2.24.0 (#1416) --- packages/google-auth/CHANGELOG.md | 21 +++++++++++++++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index ab1ea28e78d4..b1a96d7b679d 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,27 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.24.0](https://github.com/googleapis/google-auth-library-python/compare/v2.23.4...v2.24.0) (2023-11-29) + + +### Features + +* Add support for Python 3.12 ([#1421](https://github.com/googleapis/google-auth-library-python/issues/1421)) ([307916c](https://github.com/googleapis/google-auth-library-python/commit/307916cdd99fc99bb4de567848f0809746db0562)) +* Add universe domain support for VM cred ([#1409](https://github.com/googleapis/google-auth-library-python/issues/1409)) ([7ab0fce](https://github.com/googleapis/google-auth-library-python/commit/7ab0fced0008ad9283c955b7dd7e2b7db55ae5e7)) +* Modify the token refresh window ([#1419](https://github.com/googleapis/google-auth-library-python/issues/1419)) ([c6af1d6](https://github.com/googleapis/google-auth-library-python/commit/c6af1d692b43833baca978948376739547cf685a)) + + +### Bug Fixes + +* Add missing before request to async oauth2 credentials. ([#1420](https://github.com/googleapis/google-auth-library-python/issues/1420)) ([8eaa878](https://github.com/googleapis/google-auth-library-python/commit/8eaa878618f648b1dac35b9f420651f22e357445)) +* Auto create self signed jwt cred ([#1418](https://github.com/googleapis/google-auth-library-python/issues/1418)) ([6c610a5](https://github.com/googleapis/google-auth-library-python/commit/6c610a564e833545837991f473b98139740b8866)) +* Migrate datetime.utcnow for python 3.12 ([#1413](https://github.com/googleapis/google-auth-library-python/issues/1413)) ([e4d9c27](https://github.com/googleapis/google-auth-library-python/commit/e4d9c278375647e6c21754f42b81819827beb5e4)) + + +### Documentation + +* Update user cred doc ([#1414](https://github.com/googleapis/google-auth-library-python/issues/1414)) ([3f426bc](https://github.com/googleapis/google-auth-library-python/commit/3f426bc05c5620e612a54e21fb6562e965ba24bd)) + ## [2.23.4](https://github.com/googleapis/google-auth-library-python/compare/v2.23.3...v2.23.4) (2023-10-31) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 960d9e9511bf..e16f2d08a2c1 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.23.4" +__version__ = "2.24.0" From e6cf354c1c1ab12aab9b3970c9e5db414558729a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20N=C3=BC=C3=9Flein?= Date: Thu, 30 Nov 2023 18:57:56 +0000 Subject: [PATCH 779/966] chore: fix link in README (#1424) * docs: fix link to Setup Guide * chore: Refresh system test creds. --------- Co-authored-by: Carl Lundin Co-authored-by: Anthonios Partheniou --- packages/google-auth/README.rst | 2 +- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/README.rst b/packages/google-auth/README.rst index dfc1596f0f67..e058f24713b8 100644 --- a/packages/google-auth/README.rst +++ b/packages/google-auth/README.rst @@ -20,7 +20,7 @@ You can install using `pip`_:: For more information on setting up your Python development environment, please refer to `Python Development Environment Setup Guide`_ for Google Cloud Platform. -.. _`Python Development Environment Setup Guide`: https://cloud.google.com/python/setup +.. _`Python Development Environment Setup Guide`: https://cloud.google.com/python/docs/setup Extras ------ diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 937b6f065e8e90a37d6aaba424e4b73010ef785b..2f0c09c0e149d17b2496417e823620f23c1637bb 100644 GIT binary patch literal 10324 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 literal 10324 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 From 21ddb14da770eb73a3468659c6014f59a7d50879 Mon Sep 17 00:00:00 2001 From: Carl Lundin <108372512+clundin25@users.noreply.github.com> Date: Thu, 30 Nov 2023 13:09:53 -0800 Subject: [PATCH 780/966] feat: Add custom tls signer for ECP Provider. (#1402) feat: Add custom tls signer for ECP Provider. --- .../auth/transport/_custom_tls_signer.py | 79 +++++++++---- .../google/auth/transport/requests.py | 1 - .../data/enterprise_cert_valid_provider.json | 6 + .../transport/test__custom_tls_signer.py | 108 +++++++++++------- .../tests/transport/test_requests.py | 5 - 5 files changed, 131 insertions(+), 68 deletions(-) create mode 100644 packages/google-auth/tests/data/enterprise_cert_valid_provider.json diff --git a/packages/google-auth/google/auth/transport/_custom_tls_signer.py b/packages/google-auth/google/auth/transport/_custom_tls_signer.py index 07f14df02de1..57a563d03bd5 100644 --- a/packages/google-auth/google/auth/transport/_custom_tls_signer.py +++ b/packages/google-auth/google/auth/transport/_custom_tls_signer.py @@ -107,6 +107,22 @@ def load_signer_lib(signer_lib_path): return lib +def load_provider_lib(provider_lib_path): + _LOGGER.debug("loading provider library from %s", provider_lib_path) + + # winmode parameter is only available for python 3.8+. + lib = ( + ctypes.CDLL(provider_lib_path, winmode=0) + if sys.version_info >= (3, 8) and os.name == "nt" + else ctypes.CDLL(provider_lib_path) + ) + + lib.ECP_attach_to_ctx.argtypes = [ctypes.c_void_p, ctypes.c_char_p] + lib.ECP_attach_to_ctx.restype = ctypes.c_int + + return lib + + # Computes SHA256 hash. def _compute_sha256_digest(to_be_signed, to_be_signed_len): from cryptography.hazmat.primitives import hashes @@ -199,21 +215,31 @@ def __init__(self, enterprise_cert_file_path): self._enterprise_cert_file_path = enterprise_cert_file_path self._cert = None self._sign_callback = None + self._provider_lib = None def load_libraries(self): - try: - with open(self._enterprise_cert_file_path, "r") as f: - enterprise_cert_json = json.load(f) - libs = enterprise_cert_json["libs"] - signer_library = libs["ecp_client"] - offload_library = libs["tls_offload"] - except (KeyError, ValueError) as caught_exc: - new_exc = exceptions.MutualTLSChannelError( - "enterprise cert file is invalid", caught_exc - ) - raise new_exc from caught_exc - self._offload_lib = load_offload_lib(offload_library) - self._signer_lib = load_signer_lib(signer_library) + with open(self._enterprise_cert_file_path, "r") as f: + enterprise_cert_json = json.load(f) + libs = enterprise_cert_json.get("libs", {}) + + signer_library = libs.get("ecp_client", None) + offload_library = libs.get("tls_offload", None) + provider_library = libs.get("ecp_provider", None) + + # Using newer provider implementation. This is mutually exclusive to the + # offload implementation. + if provider_library: + self._provider_lib = load_provider_lib(provider_library) + return + + # Using old offload implementation + if offload_library and signer_library: + self._offload_lib = load_offload_lib(offload_library) + self._signer_lib = load_signer_lib(signer_library) + self.set_up_custom_key() + return + + raise exceptions.MutualTLSChannelError("enterprise cert file is invalid") def set_up_custom_key(self): # We need to keep a reference of the cert and sign callback so it won't @@ -224,11 +250,22 @@ def set_up_custom_key(self): ) def attach_to_ssl_context(self, ctx): - # In the TLS handshake, the signing operation will be done by the - # sign_callback. - if not self._offload_lib.ConfigureSslContext( - self._sign_callback, - ctypes.c_char_p(self._cert), - _cast_ssl_ctx_to_void_p(ctx._ctx._context), - ): - raise exceptions.MutualTLSChannelError("failed to configure SSL context") + if self._provider_lib: + if not self._provider_lib.ECP_attach_to_ctx( + _cast_ssl_ctx_to_void_p(ctx._ctx._context), + self._enterprise_cert_file_path.encode("ascii"), + ): + raise exceptions.MutualTLSChannelError( + "failed to configure ECP Provider SSL context" + ) + elif self._offload_lib and self._signer_lib: + if not self._offload_lib.ConfigureSslContext( + self._sign_callback, + ctypes.c_char_p(self._cert), + _cast_ssl_ctx_to_void_p(ctx._ctx._context), + ): + raise exceptions.MutualTLSChannelError( + "failed to configure ECP Offload SSL context" + ) + else: + raise exceptions.MutualTLSChannelError("Invalid ECP configuration.") diff --git a/packages/google-auth/google/auth/transport/requests.py b/packages/google-auth/google/auth/transport/requests.py index b9bcad359f05..aa1611322629 100644 --- a/packages/google-auth/google/auth/transport/requests.py +++ b/packages/google-auth/google/auth/transport/requests.py @@ -274,7 +274,6 @@ def __init__(self, enterprise_cert_file_path): self.signer = _custom_tls_signer.CustomTlsSigner(enterprise_cert_file_path) self.signer.load_libraries() - self.signer.set_up_custom_key() poolmanager = create_urllib3_context() poolmanager.load_verify_locations(cafile=certifi.where()) diff --git a/packages/google-auth/tests/data/enterprise_cert_valid_provider.json b/packages/google-auth/tests/data/enterprise_cert_valid_provider.json new file mode 100644 index 000000000000..9b7adf8bc30d --- /dev/null +++ b/packages/google-auth/tests/data/enterprise_cert_valid_provider.json @@ -0,0 +1,6 @@ +{ + "libs": { + "ecp_client": "/path/to/signer/lib", + "ecp_provider": "/path/to/provider/lib" + } +} diff --git a/packages/google-auth/tests/transport/test__custom_tls_signer.py b/packages/google-auth/tests/transport/test__custom_tls_signer.py index 5836b325addf..d2907bad2973 100644 --- a/packages/google-auth/tests/transport/test__custom_tls_signer.py +++ b/packages/google-auth/tests/transport/test__custom_tls_signer.py @@ -11,7 +11,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - import base64 import ctypes import os @@ -30,11 +29,19 @@ ENTERPRISE_CERT_FILE = os.path.join( os.path.dirname(__file__), "../data/enterprise_cert_valid.json" ) +ENTERPRISE_CERT_FILE_PROVIDER = os.path.join( + os.path.dirname(__file__), "../data/enterprise_cert_valid_provider.json" +) INVALID_ENTERPRISE_CERT_FILE = os.path.join( os.path.dirname(__file__), "../data/enterprise_cert_invalid.json" ) +def test_load_provider_lib(): + with mock.patch("ctypes.CDLL", return_value=mock.MagicMock()): + _custom_tls_signer.load_provider_lib("/path/to/provider/lib") + + def test_load_offload_lib(): with mock.patch("ctypes.CDLL", return_value=mock.MagicMock()): lib = _custom_tls_signer.load_offload_lib("/path/to/offload/lib") @@ -173,62 +180,81 @@ def test_custom_tls_signer(): ) as load_offload_lib: load_offload_lib.return_value = offload_lib load_signer_lib.return_value = signer_lib - signer_object = _custom_tls_signer.CustomTlsSigner(ENTERPRISE_CERT_FILE) - signer_object.load_libraries() - assert signer_object._cert is None + with mock.patch( + "google.auth.transport._custom_tls_signer.get_cert" + ) as get_cert: + with mock.patch( + "google.auth.transport._custom_tls_signer.get_sign_callback" + ) as get_sign_callback: + get_cert.return_value = b"mock_cert" + signer_object = _custom_tls_signer.CustomTlsSigner( + ENTERPRISE_CERT_FILE + ) + signer_object.load_libraries() + signer_object.attach_to_ssl_context(create_urllib3_context()) + get_cert.assert_called_once() + get_sign_callback.assert_called_once() + offload_lib.ConfigureSslContext.assert_called_once() assert signer_object._enterprise_cert_file_path == ENTERPRISE_CERT_FILE assert signer_object._offload_lib == offload_lib assert signer_object._signer_lib == signer_lib load_signer_lib.assert_called_with("/path/to/signer/lib") load_offload_lib.assert_called_with("/path/to/offload/lib") - # Test set_up_custom_key and set_up_ssl_context methods - with mock.patch("google.auth.transport._custom_tls_signer.get_cert") as get_cert: - with mock.patch( - "google.auth.transport._custom_tls_signer.get_sign_callback" - ) as get_sign_callback: - get_cert.return_value = b"mock_cert" - signer_object.set_up_custom_key() - signer_object.attach_to_ssl_context(create_urllib3_context()) - get_cert.assert_called_once() - get_sign_callback.assert_called_once() - offload_lib.ConfigureSslContext.assert_called_once() +def test_custom_tls_signer_provider(): + provider_lib = mock.MagicMock() -def test_custom_tls_signer_failed_to_load_libraries(): # Test load_libraries method + with mock.patch( + "google.auth.transport._custom_tls_signer.load_provider_lib" + ) as load_provider_lib: + load_provider_lib.return_value = provider_lib + signer_object = _custom_tls_signer.CustomTlsSigner( + ENTERPRISE_CERT_FILE_PROVIDER + ) + signer_object.load_libraries() + signer_object.attach_to_ssl_context(mock.MagicMock()) + + assert signer_object._enterprise_cert_file_path == ENTERPRISE_CERT_FILE_PROVIDER + assert signer_object._provider_lib == provider_lib + load_provider_lib.assert_called_with("/path/to/provider/lib") + + +def test_custom_tls_signer_failed_to_load_libraries(): with pytest.raises(exceptions.MutualTLSChannelError) as excinfo: signer_object = _custom_tls_signer.CustomTlsSigner(INVALID_ENTERPRISE_CERT_FILE) signer_object.load_libraries() assert excinfo.match("enterprise cert file is invalid") -def test_custom_tls_signer_fail_to_offload(): - offload_lib = mock.MagicMock() - signer_lib = mock.MagicMock() +def test_custom_tls_signer_failed_to_attach(): + with pytest.raises(exceptions.MutualTLSChannelError) as excinfo: + signer_object = _custom_tls_signer.CustomTlsSigner(ENTERPRISE_CERT_FILE) + signer_object._offload_lib = mock.MagicMock() + signer_object._signer_lib = mock.MagicMock() + signer_object._sign_callback = mock.MagicMock() + signer_object._cert = b"mock cert" + signer_object._offload_lib.ConfigureSslContext.return_value = False + signer_object.attach_to_ssl_context(mock.MagicMock()) + assert excinfo.match("failed to configure ECP Offload SSL context") - with mock.patch( - "google.auth.transport._custom_tls_signer.load_signer_lib" - ) as load_signer_lib: - with mock.patch( - "google.auth.transport._custom_tls_signer.load_offload_lib" - ) as load_offload_lib: - load_offload_lib.return_value = offload_lib - load_signer_lib.return_value = signer_lib - signer_object = _custom_tls_signer.CustomTlsSigner(ENTERPRISE_CERT_FILE) - signer_object.load_libraries() - # set the return value to be 0 which indicts offload fails - offload_lib.ConfigureSslContext.return_value = 0 +def test_custom_tls_signer_failed_to_attach_provider(): + with pytest.raises(exceptions.MutualTLSChannelError) as excinfo: + signer_object = _custom_tls_signer.CustomTlsSigner( + ENTERPRISE_CERT_FILE_PROVIDER + ) + signer_object._provider_lib = mock.MagicMock() + signer_object._provider_lib.ECP_attach_to_ctx.return_value = False + signer_object.attach_to_ssl_context(mock.MagicMock()) + assert excinfo.match("failed to configure ECP Provider SSL context") + +def test_custom_tls_signer_failed_to_attach_no_libs(): with pytest.raises(exceptions.MutualTLSChannelError) as excinfo: - with mock.patch( - "google.auth.transport._custom_tls_signer.get_cert" - ) as get_cert: - with mock.patch( - "google.auth.transport._custom_tls_signer.get_sign_callback" - ): - get_cert.return_value = b"mock_cert" - signer_object.set_up_custom_key() - signer_object.attach_to_ssl_context(create_urllib3_context()) - assert excinfo.match("failed to configure SSL context") + signer_object = _custom_tls_signer.CustomTlsSigner(ENTERPRISE_CERT_FILE) + signer_object._offload_lib = None + signer_object._signer_lib = None + signer_object.attach_to_ssl_context(mock.MagicMock()) + assert excinfo.match("Invalid ECP configuration.") diff --git a/packages/google-auth/tests/transport/test_requests.py b/packages/google-auth/tests/transport/test_requests.py index d9628143460c..aadc1ddbfd0b 100644 --- a/packages/google-auth/tests/transport/test_requests.py +++ b/packages/google-auth/tests/transport/test_requests.py @@ -544,9 +544,6 @@ class TestMutualTlsOffloadAdapter(object): @mock.patch.object( google.auth.transport._custom_tls_signer.CustomTlsSigner, "load_libraries" ) - @mock.patch.object( - google.auth.transport._custom_tls_signer.CustomTlsSigner, "set_up_custom_key" - ) @mock.patch.object( google.auth.transport._custom_tls_signer.CustomTlsSigner, "attach_to_ssl_context", @@ -554,7 +551,6 @@ class TestMutualTlsOffloadAdapter(object): def test_success( self, mock_attach_to_ssl_context, - mock_set_up_custom_key, mock_load_libraries, mock_proxy_manager_for, mock_init_poolmanager, @@ -565,7 +561,6 @@ def test_success( ) mock_load_libraries.assert_called_once() - mock_set_up_custom_key.assert_called_once() assert mock_attach_to_ssl_context.call_count == 2 adapter.init_poolmanager() From 9dadf7f253a923734f3cb80e2d1a2514687d516e Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Fri, 1 Dec 2023 17:09:32 -0800 Subject: [PATCH 781/966] fix: add with_universe_domain (#1408) * fix: add with_universe_domain to service account and external cred * update * update * chore: refresh sys test cred --- .../google/auth/external_account.py | 16 ++++++ .../google-auth/google/oauth2/credentials.py | 48 ++++++++++++++++++ .../google/oauth2/service_account.py | 21 ++++++-- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../tests/oauth2/test_credentials.py | 18 +++++++ .../tests/oauth2/test_service_account.py | 11 ++++ .../tests/test_external_account.py | 5 ++ 7 files changed, 115 insertions(+), 4 deletions(-) diff --git a/packages/google-auth/google/auth/external_account.py b/packages/google-auth/google/auth/external_account.py index 28b004c5fad6..e7fed8695ade 100644 --- a/packages/google-auth/google/auth/external_account.py +++ b/packages/google-auth/google/auth/external_account.py @@ -415,6 +415,22 @@ def with_token_uri(self, token_uri): new_cred._metrics_options = self._metrics_options return new_cred + def with_universe_domain(self, universe_domain): + """Create a copy of these credentials with the given universe domain. + + Args: + universe_domain (str): The universe domain value. + + Returns: + google.auth.external_account.Credentials: A new credentials + instance. + """ + kwargs = self._constructor_args() + kwargs.update(universe_domain=universe_domain) + new_cred = self.__class__(**kwargs) + new_cred._metrics_options = self._metrics_options + return new_cred + def _initialize_impersonated_credentials(self): """Generates an impersonated credentials. diff --git a/packages/google-auth/google/oauth2/credentials.py b/packages/google-auth/google/oauth2/credentials.py index ae204b45a887..7e2173ebed59 100644 --- a/packages/google-auth/google/oauth2/credentials.py +++ b/packages/google-auth/google/oauth2/credentials.py @@ -49,6 +49,7 @@ # The Google OAuth 2.0 token endpoint. Used for authorized user credentials. _GOOGLE_OAUTH2_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token" +_DEFAULT_UNIVERSE_DOMAIN = "googleapis.com" class Credentials(credentials.ReadOnlyScoped, credentials.CredentialsWithQuotaProject): @@ -85,6 +86,7 @@ def __init__( enable_reauth_refresh=False, granted_scopes=None, trust_boundary=None, + universe_domain=_DEFAULT_UNIVERSE_DOMAIN, ): """ Args: @@ -126,6 +128,9 @@ def __init__( granted_scopes (Optional[Sequence[str]]): The scopes that were consented/granted by the user. This could be different from the requested scopes and it could be empty if granted and requested scopes were same. + trust_boundary (str): String representation of trust boundary meta. + universe_domain (Optional[str]): The universe domain. The default + universe domain is googleapis.com. """ super(Credentials, self).__init__() self.token = token @@ -143,6 +148,7 @@ def __init__( self.refresh_handler = refresh_handler self._enable_reauth_refresh = enable_reauth_refresh self._trust_boundary = trust_boundary + self._universe_domain = universe_domain or _DEFAULT_UNIVERSE_DOMAIN def __getstate__(self): """A __getstate__ method must exist for the __setstate__ to be called @@ -273,6 +279,7 @@ def with_quota_project(self, quota_project_id): rapt_token=self.rapt_token, enable_reauth_refresh=self._enable_reauth_refresh, trust_boundary=self._trust_boundary, + universe_domain=self._universe_domain, ) @_helpers.copy_docstring(credentials.CredentialsWithTokenUri) @@ -292,6 +299,34 @@ def with_token_uri(self, token_uri): rapt_token=self.rapt_token, enable_reauth_refresh=self._enable_reauth_refresh, trust_boundary=self._trust_boundary, + universe_domain=self._universe_domain, + ) + + def with_universe_domain(self, universe_domain): + """Create a copy of the credential with the given universe domain. + + Args: + universe_domain (str): The universe domain value. + + Returns: + google.oauth2.credentials.Credentials: A new credentials instance. + """ + + return self.__class__( + self.token, + refresh_token=self.refresh_token, + id_token=self.id_token, + token_uri=self._token_uri, + client_id=self.client_id, + client_secret=self.client_secret, + scopes=self.scopes, + default_scopes=self.default_scopes, + granted_scopes=self.granted_scopes, + quota_project_id=self.quota_project_id, + rapt_token=self.rapt_token, + enable_reauth_refresh=self._enable_reauth_refresh, + trust_boundary=self._trust_boundary, + universe_domain=universe_domain, ) def _metric_header_for_usage(self): @@ -299,6 +334,17 @@ def _metric_header_for_usage(self): @_helpers.copy_docstring(credentials.Credentials) def refresh(self, request): + if self._universe_domain != _DEFAULT_UNIVERSE_DOMAIN: + raise exceptions.RefreshError( + "User credential refresh is only supported in the default " + "googleapis.com universe domain, but the current universe " + "domain is {}. If you created the credential with an access " + "token, it's likely that the provided token is expired now, " + "please update your code with a valid token.".format( + self._universe_domain + ) + ) + scopes = self._scopes if self._scopes is not None else self._default_scopes # Use refresh handler if available and no refresh token is # available. This is useful in general when tokens are obtained by calling @@ -428,6 +474,7 @@ def from_authorized_user_info(cls, info, scopes=None): expiry=expiry, rapt_token=info.get("rapt_token"), # may not exist trust_boundary=info.get("trust_boundary"), # may not exist + universe_domain=info.get("universe_domain"), # may not exist ) @classmethod @@ -471,6 +518,7 @@ def to_json(self, strip=None): "client_secret": self.client_secret, "scopes": self.scopes, "rapt_token": self.rapt_token, + "universe_domain": self._universe_domain, } if self.expiry: # flatten expiry timestamp prep["expiry"] = self.expiry.isoformat() + "Z" diff --git a/packages/google-auth/google/oauth2/service_account.py b/packages/google-auth/google/oauth2/service_account.py index 2b5e0d3901b9..68db41af409d 100644 --- a/packages/google-auth/google/oauth2/service_account.py +++ b/packages/google-auth/google/oauth2/service_account.py @@ -182,10 +182,7 @@ def __init__( self._quota_project_id = quota_project_id self._token_uri = token_uri self._always_use_jwt_access = always_use_jwt_access - if not universe_domain: - self._universe_domain = _DEFAULT_UNIVERSE_DOMAIN - else: - self._universe_domain = universe_domain + self._universe_domain = universe_domain or _DEFAULT_UNIVERSE_DOMAIN if universe_domain != _DEFAULT_UNIVERSE_DOMAIN: self._always_use_jwt_access = True @@ -328,6 +325,22 @@ def with_always_use_jwt_access(self, always_use_jwt_access): cred._always_use_jwt_access = always_use_jwt_access return cred + def with_universe_domain(self, universe_domain): + """Create a copy of these credentials with the given universe domain. + + Args: + universe_domain (str): The universe domain value. + + Returns: + google.auth.service_account.Credentials: A new credentials + instance. + """ + cred = self._make_copy() + cred._universe_domain = universe_domain + if universe_domain != _DEFAULT_UNIVERSE_DOMAIN: + cred._always_use_jwt_access = True + return cred + def with_subject(self, subject): """Create a copy of these credentials with the specified subject. diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 2f0c09c0e149d17b2496417e823620f23c1637bb..e4c71790e9d5a6e6960a4651ccd6baa6bebd41ba 100644 GIT binary patch literal 10324 zcmV-aD67{BB>?tKRTF&9%dNXRmz z-t^|~B-$;I1UhNfZFU-uG&OCe(+7HF)WeCvnv}w=t$?5Lraeq|eM9&)gYy1T@exXU zR+Y}acI;W&`IGHa3oz3`GfW1cS?zvK9eutz?4SJW z_d}g{=E8IZ-dORO(a+?@rG`;=HUa|EIoF~^iB^mJO4cPI#i1HIIj@H$dt`hFxct^O zQ3lV3@aty4tlTl2R}NM*bC(VAwi2qb)7x>le}hRh5dmycy@nD!=*(+nI{Bbx#@8HZ z4DiNFW{S688@upsaI0iA!bmOmJ8^iTZfy+LjE6hKM1u{|%>`$CjqS~V9Ytb)n{baU z1p*8VIR+y5wOGN`Jz03EGES@YNKXzBoNw`N?hmVMT@6@q#AYLx!jaC}MKCjmx4@&G z-oij($_#Kf&<(teIsJ8?ZdnC=R1k9?iE)Bz$rQ+21%)J(s76u5>gP3MoP%!^ao%N( zwJ_Jb8}{fSvau1t4>j2MLfe=0TF1<5hdvVTJorap3k9Ra;Mr_1yBW_@E|xReIM26i z@KSjwQ&kEF7xyDj=NGJg^4rTx&DH{APb7KO`3NeB(1_?qcZ7 zk5;8UTIUyiVMy}gj=tke2`;hY8i%H^Ug%-O&leBK22%t;{DH%WY@!b;K{)27 zHgZ%*S5EXiIHd!ACOf_RMR$7Rw{2g)Q_H{EKGR5R`G$;-ET}o`Rw9kq0nWxSob)S7 zOVT`cvN*8=y@;xCNrQ@-I*UX4zr&sQzY+g*u8vTxhZ~r^0u${fVy>!J>PLEp3P&%z z6+l2x5>@>DSyk$0g@5cOh>4>>yH6WsL9jm10B=uZ!^>bp960I?I2^44r%e^FLWGqJ zzaPxisGna>mNxDXo*JupF3Kp=Jn3JBB2D(M*dRDcR{(y!gO#c;I^zmsS@~= zsLLdlL+E9e*&Cj|cjBfpNy01�H;pjv=uHHb1I1yaIJ0N<$P|s`upb3z-=kaa8}^ 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 literal 10324 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 diff --git a/packages/google-auth/tests/oauth2/test_credentials.py b/packages/google-auth/tests/oauth2/test_credentials.py index d265d22ed230..054f7940553f 100644 --- a/packages/google-auth/tests/oauth2/test_credentials.py +++ b/packages/google-auth/tests/oauth2/test_credentials.py @@ -122,6 +122,17 @@ def test_invalid_refresh_handler(self): assert excinfo.match("The provided refresh_handler is not a callable or None.") + def test_refresh_with_non_default_universe_domain(self): + creds = credentials.Credentials( + token="token", universe_domain="dummy_universe.com" + ) + with pytest.raises(exceptions.RefreshError) as excinfo: + creds.refresh(mock.Mock()) + + assert excinfo.match( + "refresh is only supported in the default googleapis.com universe domain" + ) + @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True) @mock.patch( "google.auth._helpers.utcnow", @@ -774,6 +785,12 @@ def test_with_quota_project(self): creds.apply(headers) assert "x-goog-user-project" in headers + def test_with_universe_domain(self): + creds = credentials.Credentials(token="token") + assert creds.universe_domain == "googleapis.com" + new_creds = creds.with_universe_domain("dummy_universe.com") + assert new_creds.universe_domain == "dummy_universe.com" + def test_with_token_uri(self): info = AUTH_USER_INFO.copy() @@ -868,6 +885,7 @@ def test_to_json(self): assert json_asdict.get("scopes") == creds.scopes assert json_asdict.get("client_secret") == creds.client_secret assert json_asdict.get("expiry") == info["expiry"] + assert json_asdict.get("universe_domain") == creds.universe_domain # Test with a `strip` arg json_output = creds.to_json(strip=["client_secret"]) diff --git a/packages/google-auth/tests/oauth2/test_service_account.py b/packages/google-auth/tests/oauth2/test_service_account.py index f9e0c1186965..ebaab05fccfa 100644 --- a/packages/google-auth/tests/oauth2/test_service_account.py +++ b/packages/google-auth/tests/oauth2/test_service_account.py @@ -205,6 +205,17 @@ def test_with_token_uri(self): creds_with_new_token_uri = credentials.with_token_uri(new_token_uri) assert creds_with_new_token_uri._token_uri == new_token_uri + def test_with_universe_domain(self): + credentials = self.make_credentials() + + new_credentials = credentials.with_universe_domain("dummy_universe.com") + assert new_credentials.universe_domain == "dummy_universe.com" + assert new_credentials._always_use_jwt_access + + new_credentials = credentials.with_universe_domain("googleapis.com") + assert new_credentials.universe_domain == "googleapis.com" + assert not new_credentials._always_use_jwt_access + def test__with_always_use_jwt_access(self): credentials = self.make_credentials() assert not credentials._always_use_jwt_access diff --git a/packages/google-auth/tests/test_external_account.py b/packages/google-auth/tests/test_external_account.py index 6f6e18b2cf18..5225dcf34240 100644 --- a/packages/google-auth/tests/test_external_account.py +++ b/packages/google-auth/tests/test_external_account.py @@ -505,6 +505,11 @@ def test_universe_domain(self): credentials = self.make_credentials() assert credentials.universe_domain == external_account._DEFAULT_UNIVERSE_DOMAIN + def test_with_universe_domain(self): + credentials = self.make_credentials() + new_credentials = credentials.with_universe_domain("dummy_universe.com") + assert new_credentials.universe_domain == "dummy_universe.com" + def test_info_workforce_pool(self): credentials = self.make_workforce_pool_credentials( workforce_pool_user_project=self.WORKFORCE_POOL_USER_PROJECT From d09cb96e23252e3fb2d17e4db1128c2deafe11bb Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Sat, 2 Dec 2023 09:09:07 -0500 Subject: [PATCH 782/966] chore: bump cryptography from 41.0.5 to 41.0.6 in /synthtool/gcp/templates/python_library/.kokoro (#1423) Source-Link: https://github.com/googleapis/synthtool/commit/9367caadcbb30b5b2719f30eb00c44cc913550ed Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:2f155882785883336b4468d5218db737bb1d10c9cea7cb62219ad16fe248c03c Co-authored-by: Owl Bot --- .../google-auth/.github/.OwlBot.lock.yaml | 4 +- packages/google-auth/.kokoro/requirements.txt | 48 +++++++++---------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/packages/google-auth/.github/.OwlBot.lock.yaml b/packages/google-auth/.github/.OwlBot.lock.yaml index 453b540c1e58..773c1dfd2146 100644 --- a/packages/google-auth/.github/.OwlBot.lock.yaml +++ b/packages/google-auth/.github/.OwlBot.lock.yaml @@ -13,5 +13,5 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:caffe0a9277daeccc4d1de5c9b55ebba0901b57c2f713ec9c876b0d4ec064f61 -# created: 2023-11-08T19:46:45.022803742Z + digest: sha256:2f155882785883336b4468d5218db737bb1d10c9cea7cb62219ad16fe248c03c +# created: 2023-11-29T14:54:29.548172703Z diff --git a/packages/google-auth/.kokoro/requirements.txt b/packages/google-auth/.kokoro/requirements.txt index 8957e21104e2..e5c1ffca94b7 100644 --- a/packages/google-auth/.kokoro/requirements.txt +++ b/packages/google-auth/.kokoro/requirements.txt @@ -93,30 +93,30 @@ colorlog==6.7.0 \ # via # gcp-docuploader # nox -cryptography==41.0.5 \ - --hash=sha256:0c327cac00f082013c7c9fb6c46b7cc9fa3c288ca702c74773968173bda421bf \ - --hash=sha256:0d2a6a598847c46e3e321a7aef8af1436f11c27f1254933746304ff014664d84 \ - --hash=sha256:227ec057cd32a41c6651701abc0328135e472ed450f47c2766f23267b792a88e \ - --hash=sha256:22892cc830d8b2c89ea60148227631bb96a7da0c1b722f2aac8824b1b7c0b6b8 \ - --hash=sha256:392cb88b597247177172e02da6b7a63deeff1937fa6fec3bbf902ebd75d97ec7 \ - --hash=sha256:3be3ca726e1572517d2bef99a818378bbcf7d7799d5372a46c79c29eb8d166c1 \ - --hash=sha256:573eb7128cbca75f9157dcde974781209463ce56b5804983e11a1c462f0f4e88 \ - --hash=sha256:580afc7b7216deeb87a098ef0674d6ee34ab55993140838b14c9b83312b37b86 \ - --hash=sha256:5a70187954ba7292c7876734183e810b728b4f3965fbe571421cb2434d279179 \ - --hash=sha256:73801ac9736741f220e20435f84ecec75ed70eda90f781a148f1bad546963d81 \ - --hash=sha256:7d208c21e47940369accfc9e85f0de7693d9a5d843c2509b3846b2db170dfd20 \ - --hash=sha256:8254962e6ba1f4d2090c44daf50a547cd5f0bf446dc658a8e5f8156cae0d8548 \ - --hash=sha256:88417bff20162f635f24f849ab182b092697922088b477a7abd6664ddd82291d \ - --hash=sha256:a48e74dad1fb349f3dc1d449ed88e0017d792997a7ad2ec9587ed17405667e6d \ - --hash=sha256:b948e09fe5fb18517d99994184854ebd50b57248736fd4c720ad540560174ec5 \ - --hash=sha256:c707f7afd813478e2019ae32a7c49cd932dd60ab2d2a93e796f68236b7e1fbf1 \ - --hash=sha256:d38e6031e113b7421db1de0c1b1f7739564a88f1684c6b89234fbf6c11b75147 \ - --hash=sha256:d3977f0e276f6f5bf245c403156673db103283266601405376f075c849a0b936 \ - --hash=sha256:da6a0ff8f1016ccc7477e6339e1d50ce5f59b88905585f77193ebd5068f1e797 \ - --hash=sha256:e270c04f4d9b5671ebcc792b3ba5d4488bf7c42c3c241a3748e2599776f29696 \ - --hash=sha256:e886098619d3815e0ad5790c973afeee2c0e6e04b4da90b88e6bd06e2a0b1b72 \ - --hash=sha256:ec3b055ff8f1dce8e6ef28f626e0972981475173d7973d63f271b29c8a2897da \ - --hash=sha256:fba1e91467c65fe64a82c689dc6cf58151158993b13eb7a7f3f4b7f395636723 +cryptography==41.0.6 \ + --hash=sha256:068bc551698c234742c40049e46840843f3d98ad7ce265fd2bd4ec0d11306596 \ + --hash=sha256:0f27acb55a4e77b9be8d550d762b0513ef3fc658cd3eb15110ebbcbd626db12c \ + --hash=sha256:2132d5865eea673fe6712c2ed5fb4fa49dba10768bb4cc798345748380ee3660 \ + --hash=sha256:3288acccef021e3c3c10d58933f44e8602cf04dba96d9796d70d537bb2f4bbc4 \ + --hash=sha256:35f3f288e83c3f6f10752467c48919a7a94b7d88cc00b0668372a0d2ad4f8ead \ + --hash=sha256:398ae1fc711b5eb78e977daa3cbf47cec20f2c08c5da129b7a296055fbb22aed \ + --hash=sha256:422e3e31d63743855e43e5a6fcc8b4acab860f560f9321b0ee6269cc7ed70cc3 \ + --hash=sha256:48783b7e2bef51224020efb61b42704207dde583d7e371ef8fc2a5fb6c0aabc7 \ + --hash=sha256:4d03186af98b1c01a4eda396b137f29e4e3fb0173e30f885e27acec8823c1b09 \ + --hash=sha256:5daeb18e7886a358064a68dbcaf441c036cbdb7da52ae744e7b9207b04d3908c \ + --hash=sha256:60e746b11b937911dc70d164060d28d273e31853bb359e2b2033c9e93e6f3c43 \ + --hash=sha256:742ae5e9a2310e9dade7932f9576606836ed174da3c7d26bc3d3ab4bd49b9f65 \ + --hash=sha256:7e00fb556bda398b99b0da289ce7053639d33b572847181d6483ad89835115f6 \ + --hash=sha256:85abd057699b98fce40b41737afb234fef05c67e116f6f3650782c10862c43da \ + --hash=sha256:8efb2af8d4ba9dbc9c9dd8f04d19a7abb5b49eab1f3694e7b5a16a5fc2856f5c \ + --hash=sha256:ae236bb8760c1e55b7a39b6d4d32d2279bc6c7c8500b7d5a13b6fb9fc97be35b \ + --hash=sha256:afda76d84b053923c27ede5edc1ed7d53e3c9f475ebaf63c68e69f1403c405a8 \ + --hash=sha256:b27a7fd4229abef715e064269d98a7e2909ebf92eb6912a9603c7e14c181928c \ + --hash=sha256:b648fe2a45e426aaee684ddca2632f62ec4613ef362f4d681a9a6283d10e079d \ + --hash=sha256:c5a550dc7a3b50b116323e3d376241829fd326ac47bc195e04eb33a8170902a9 \ + --hash=sha256:da46e2b5df770070412c46f87bac0849b8d685c5f2679771de277a422c7d0b86 \ + --hash=sha256:f39812f70fc5c71a15aa3c97b2bbe213c3f2a460b79bd21c40d033bb34a9bf36 \ + --hash=sha256:ff369dd19e8fe0528b02e8df9f2aeb2479f89b1270d90f96a63500afe9af5cae # via # gcp-releasetool # secretstorage From 58609942ab0807377022c2be7273d0ff64c05567 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Sun, 3 Dec 2023 21:45:23 -0500 Subject: [PATCH 783/966] fix: fixes issue where Python37DeprecationWarning cannot be filtered (#1428) * fix: fixes issue where Python37DeprecationWarning cannot be filtered * chore: refresh sys test cred --------- Co-authored-by: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Co-authored-by: Sijun Liu --- packages/google-auth/google/auth/__init__.py | 3 --- .../google-auth/google/oauth2/__init__.py | 2 -- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 3 files changed, 5 deletions(-) diff --git a/packages/google-auth/google/auth/__init__.py b/packages/google-auth/google/auth/__init__.py index 2d6caec714d0..5f716442da48 100644 --- a/packages/google-auth/google/auth/__init__.py +++ b/packages/google-auth/google/auth/__init__.py @@ -49,9 +49,6 @@ class Python37DeprecationWarning(DeprecationWarning): # pragma: NO COVER "for Python 3.7. More details about Python 3.7 support " "can be found at https://cloud.google.com/python/docs/python37-sunset/" ) - - # Configure the Python37DeprecationWarning warning so that it is only emitted once. - warnings.simplefilter("once", Python37DeprecationWarning) warnings.warn(message, Python37DeprecationWarning) # Set default logging handler to avoid "No handler found" warnings. diff --git a/packages/google-auth/google/oauth2/__init__.py b/packages/google-auth/google/oauth2/__init__.py index 3ebf219fd78e..68524abcaad9 100644 --- a/packages/google-auth/google/oauth2/__init__.py +++ b/packages/google-auth/google/oauth2/__init__.py @@ -35,6 +35,4 @@ class Python37DeprecationWarning(DeprecationWarning): # pragma: NO COVER "for Python 3.7. More details about Python 3.7 support " "can be found at https://cloud.google.com/python/docs/python37-sunset/" ) - # Configure the Python37DeprecationWarning warning so that it is only emitted once. - warnings.simplefilter("once", Python37DeprecationWarning) warnings.warn(message, Python37DeprecationWarning) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index e4c71790e9d5a6e6960a4651ccd6baa6bebd41ba..f9e6946988441471924e666411571d9bec6b6678 100644 GIT binary patch literal 10324 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 literal 10324 zcmV-aD67{BB>?tKRTF&9%dNXRmz z-t^|~B-$;I1UhNfZFU-uG&OCe(+7HF)WeCvnv}w=t$?5Lraeq|eM9&)gYy1T@exXU zR+Y}acI;W&`IGHa3oz3`GfW1cS?zvK9eutz?4SJW z_d}g{=E8IZ-dORO(a+?@rG`;=HUa|EIoF~^iB^mJO4cPI#i1HIIj@H$dt`hFxct^O zQ3lV3@aty4tlTl2R}NM*bC(VAwi2qb)7x>le}hRh5dmycy@nD!=*(+nI{Bbx#@8HZ z4DiNFW{S688@upsaI0iA!bmOmJ8^iTZfy+LjE6hKM1u{|%>`$CjqS~V9Ytb)n{baU z1p*8VIR+y5wOGN`Jz03EGES@YNKXzBoNw`N?hmVMT@6@q#AYLx!jaC}MKCjmx4@&G z-oij($_#Kf&<(teIsJ8?ZdnC=R1k9?iE)Bz$rQ+21%)J(s76u5>gP3MoP%!^ao%N( zwJ_Jb8}{fSvau1t4>j2MLfe=0TF1<5hdvVTJorap3k9Ra;Mr_1yBW_@E|xReIM26i z@KSjwQ&kEF7xyDj=NGJg^4rTx&DH{APb7KO`3NeB(1_?qcZ7 zk5;8UTIUyiVMy}gj=tke2`;hY8i%H^Ug%-O&leBK22%t;{DH%WY@!b;K{)27 zHgZ%*S5EXiIHd!ACOf_RMR$7Rw{2g)Q_H{EKGR5R`G$;-ET}o`Rw9kq0nWxSob)S7 zOVT`cvN*8=y@;xCNrQ@-I*UX4zr&sQzY+g*u8vTxhZ~r^0u${fVy>!J>PLEp3P&%z z6+l2x5>@>DSyk$0g@5cOh>4>>yH6WsL9jm10B=uZ!^>bp960I?I2^44r%e^FLWGqJ zzaPxisGna>mNxDXo*JupF3Kp=Jn3JBB2D(M*dRDcR{(y!gO#c;I^zmsS@~= zsLLdlL+E9e*&Cj|cjBfpNy01�H;pjv=uHHb1I1yaIJ0N<$P|s`upb3z-=kaa8}^ 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 From 75843a9d921e08d77340e8421bbc1c4e4e4c9eb7 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Mon, 4 Dec 2023 05:40:16 -0500 Subject: [PATCH 784/966] fix: remove broken link in Python37DeprecationWarning (#1430) Co-authored-by: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> --- packages/google-auth/google/auth/__init__.py | 6 ++---- packages/google-auth/google/oauth2/__init__.py | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/google-auth/google/auth/__init__.py b/packages/google-auth/google/auth/__init__.py index 5f716442da48..765bbd70581a 100644 --- a/packages/google-auth/google/auth/__init__.py +++ b/packages/google-auth/google/auth/__init__.py @@ -35,8 +35,7 @@ class Python37DeprecationWarning(DeprecationWarning): # pragma: NO COVER """ Deprecation warning raised when Python 3.7 runtime is detected. - Python 3.7 support will be dropped after January 1, 2024. See - https://cloud.google.com/python/docs/python37-sunset/ for more information. + Python 3.7 support will be dropped after January 1, 2024. """ pass @@ -46,8 +45,7 @@ class Python37DeprecationWarning(DeprecationWarning): # pragma: NO COVER if sys.version_info.major == 3 and sys.version_info.minor == 7: # pragma: NO COVER message = ( "After January 1, 2024, new releases of this library will drop support " - "for Python 3.7. More details about Python 3.7 support " - "can be found at https://cloud.google.com/python/docs/python37-sunset/" + "for Python 3.7." ) warnings.warn(message, Python37DeprecationWarning) diff --git a/packages/google-auth/google/oauth2/__init__.py b/packages/google-auth/google/oauth2/__init__.py index 68524abcaad9..accae96579d8 100644 --- a/packages/google-auth/google/oauth2/__init__.py +++ b/packages/google-auth/google/oauth2/__init__.py @@ -21,8 +21,7 @@ class Python37DeprecationWarning(DeprecationWarning): # pragma: NO COVER """ Deprecation warning raised when Python 3.7 runtime is detected. - Python 3.7 support will be dropped after January 1, 2024. See - https://cloud.google.com/python/docs/python37-sunset/ for more information. + Python 3.7 support will be dropped after January 1, 2024. """ pass @@ -32,7 +31,6 @@ class Python37DeprecationWarning(DeprecationWarning): # pragma: NO COVER if sys.version_info.major == 3 and sys.version_info.minor == 7: # pragma: NO COVER message = ( "After January 1, 2024, new releases of this library will drop support " - "for Python 3.7. More details about Python 3.7 support " - "can be found at https://cloud.google.com/python/docs/python37-sunset/" + "for Python 3.7." ) warnings.warn(message, Python37DeprecationWarning) From 1a5ad1573c55b12eed98a48749abd983ce959636 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 5 Dec 2023 10:37:44 -0800 Subject: [PATCH 785/966] chore(main): release 2.25.0 (#1425) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 15 +++++++++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index b1a96d7b679d..825fd09b6919 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,21 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.25.0](https://github.com/googleapis/google-auth-library-python/compare/v2.24.0...v2.25.0) (2023-12-04) + + +### Features + +* Add custom tls signer for ECP Provider. ([39eb287](https://github.com/googleapis/google-auth-library-python/commit/39eb2877a45f0a6264442a059f7ffe8acbd7c29e)) +* Add custom tls signer for ECP Provider. ([#1402](https://github.com/googleapis/google-auth-library-python/issues/1402)) ([39eb287](https://github.com/googleapis/google-auth-library-python/commit/39eb2877a45f0a6264442a059f7ffe8acbd7c29e)) + + +### Bug Fixes + +* Add with_universe_domain ([#1408](https://github.com/googleapis/google-auth-library-python/issues/1408)) ([505910c](https://github.com/googleapis/google-auth-library-python/commit/505910c6a0bb2472284a52cd69db212aa8555119)) +* Fixes issue where Python37DeprecationWarning cannot be filtered ([#1428](https://github.com/googleapis/google-auth-library-python/issues/1428)) ([f22f767](https://github.com/googleapis/google-auth-library-python/commit/f22f767a22a3b567c3784c2fb5c7f5ae11d3e10f)) +* Remove broken link in Python37DeprecationWarning ([#1430](https://github.com/googleapis/google-auth-library-python/issues/1430)) ([e2db602](https://github.com/googleapis/google-auth-library-python/commit/e2db602a4fe8aee7f2eb8b59e2eaf23f1ced1b24)) + ## [2.24.0](https://github.com/googleapis/google-auth-library-python/compare/v2.23.4...v2.24.0) (2023-11-29) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index e16f2d08a2c1..4bbaabf05d35 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.24.0" +__version__ = "2.25.0" From 5b36d863296bd954db0ce0efb7b0876c5ad18d6f Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Tue, 5 Dec 2023 17:09:00 -0800 Subject: [PATCH 786/966] fix: fix vm universe_domain bug (#1433) --- .../google/auth/compute_engine/credentials.py | 6 +++++- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../tests/compute_engine/test_credentials.py | 8 ++++++-- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/google-auth/google/auth/compute_engine/credentials.py b/packages/google-auth/google/auth/compute_engine/credentials.py index fa30aa44afe3..a035c7697afe 100644 --- a/packages/google-auth/google/auth/compute_engine/credentials.py +++ b/packages/google-auth/google/auth/compute_engine/credentials.py @@ -28,6 +28,7 @@ from google.auth import jwt from google.auth import metrics from google.auth.compute_engine import _metadata +from google.auth.transport import requests as google_auth_requests from google.oauth2 import _client @@ -74,6 +75,7 @@ def __init__( self._scopes = scopes self._default_scopes = default_scopes self._universe_domain_cached = False + self._universe_domain_request = google_auth_requests.Request() def _retrieve_info(self, request): """Retrieve information about the service account. @@ -136,7 +138,9 @@ def requires_scopes(self): def universe_domain(self): if self._universe_domain_cached: return self._universe_domain - self._universe_domain = _metadata.get_universe_domain() + self._universe_domain = _metadata.get_universe_domain( + self._universe_domain_request + ) self._universe_domain_cached = True return self._universe_domain diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index f9e6946988441471924e666411571d9bec6b6678..3fcd3279e77cb5c431633a3a079d3659469b2ec5 100644 GIT binary patch literal 10324 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 diff --git a/packages/google-auth/tests/compute_engine/test_credentials.py b/packages/google-auth/tests/compute_engine/test_credentials.py index 95f9b0efbee2..5d6ccdcdec6c 100644 --- a/packages/google-auth/tests/compute_engine/test_credentials.py +++ b/packages/google-auth/tests/compute_engine/test_credentials.py @@ -221,12 +221,16 @@ def test_universe_domain(self, get_universe_domain): assert self.credentials.universe_domain == "fake_universe_domain" assert self.credentials._universe_domain == "fake_universe_domain" assert self.credentials._universe_domain_cached - get_universe_domain.assert_called_once() + get_universe_domain.assert_called_once_with( + self.credentials._universe_domain_request + ) # calling the universe_domain property the second time should use the # cached value instead of calling get_universe_domain assert self.credentials.universe_domain == "fake_universe_domain" - get_universe_domain.assert_called_once() + get_universe_domain.assert_called_once_with( + self.credentials._universe_domain_request + ) class TestIDTokenCredentials(object): From fa7954e2e811035a510bde80bed5d5e3d98a9771 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 5 Dec 2023 17:40:57 -0800 Subject: [PATCH 787/966] chore(main): release 2.25.1 (#1434) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 825fd09b6919..6559c9184874 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.25.1](https://github.com/googleapis/google-auth-library-python/compare/v2.25.0...v2.25.1) (2023-12-06) + + +### Bug Fixes + +* Fix vm universe_domain bug ([#1433](https://github.com/googleapis/google-auth-library-python/issues/1433)) ([8683520](https://github.com/googleapis/google-auth-library-python/commit/8683520af48e1b501076ef16013b6a27cc173af0)) + ## [2.25.0](https://github.com/googleapis/google-auth-library-python/compare/v2.24.0...v2.25.0) (2023-12-04) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 4bbaabf05d35..70505391f0ba 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.25.0" +__version__ = "2.25.1" From e83a0fa59c74262eed1cc61a444b9e580c9bc688 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Fri, 8 Dec 2023 11:10:34 -0800 Subject: [PATCH 788/966] fix: fix user cred universe domain issue (#1436) --- .../google-auth/google/oauth2/credentials.py | 2 +- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../tests/oauth2/test_credentials.py | 11 +++++++++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/google/oauth2/credentials.py b/packages/google-auth/google/oauth2/credentials.py index 7e2173ebed59..a5c93ecc2f85 100644 --- a/packages/google-auth/google/oauth2/credentials.py +++ b/packages/google-auth/google/oauth2/credentials.py @@ -180,7 +180,7 @@ def __setstate__(self, d): self._rapt_token = d.get("_rapt_token") self._enable_reauth_refresh = d.get("_enable_reauth_refresh") self._trust_boundary = d.get("_trust_boundary") - self._universe_domain = d.get("_universe_domain") + self._universe_domain = d.get("_universe_domain") or _DEFAULT_UNIVERSE_DOMAIN # The refresh_handler setter should be used to repopulate this. self._refresh_handler = None diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 3fcd3279e77cb5c431633a3a079d3659469b2ec5..06b7f380c156919b32d24057e9094de7067db6c5 100644 GIT binary patch literal 10324 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 Date: Fri, 8 Dec 2023 11:33:24 -0800 Subject: [PATCH 789/966] chore(main): release 2.25.2 (#1438) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 6559c9184874..cabc74bb52f3 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.25.2](https://github.com/googleapis/google-auth-library-python/compare/v2.25.1...v2.25.2) (2023-12-08) + + +### Bug Fixes + +* Fix user cred universe domain issue ([#1436](https://github.com/googleapis/google-auth-library-python/issues/1436)) ([ae314a8](https://github.com/googleapis/google-auth-library-python/commit/ae314a8f8c63ada4e31683464c24e28df0f5af7f)) + ## [2.25.1](https://github.com/googleapis/google-auth-library-python/compare/v2.25.0...v2.25.1) (2023-12-06) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 70505391f0ba..31cc30242a21 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.25.1" +__version__ = "2.25.2" From f902c2770370938312c801677d19ad2e52b9cc3c Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Fri, 15 Dec 2023 13:56:05 -0800 Subject: [PATCH 790/966] fix: external account user cred universe domain support (#1437) * fix: external account user cred universe domain support * refactor --------- Co-authored-by: Jin --- .../google-auth/google/auth/credentials.py | 21 +++++++++++-- .../google/auth/external_account.py | 10 +----- .../auth/external_account_authorized_user.py | 12 +++++++ .../google-auth/google/oauth2/credentials.py | 9 +----- .../google/oauth2/service_account.py | 10 +----- .../test_external_account_authorized_user.py | 31 +++++++++++++++++++ 6 files changed, 65 insertions(+), 28 deletions(-) diff --git a/packages/google-auth/google/auth/credentials.py b/packages/google-auth/google/auth/credentials.py index 800781c4089a..6e62a4b4ee78 100644 --- a/packages/google-auth/google/auth/credentials.py +++ b/packages/google-auth/google/auth/credentials.py @@ -188,7 +188,7 @@ def with_quota_project(self, quota_project_id): billing purposes Returns: - google.oauth2.credentials.Credentials: A new credentials instance. + google.auth.credentials.Credentials: A new credentials instance. """ raise NotImplementedError("This credential does not support quota project.") @@ -209,11 +209,28 @@ def with_token_uri(self, token_uri): token_uri (str): The uri to use for fetching/exchanging tokens Returns: - google.oauth2.credentials.Credentials: A new credentials instance. + google.auth.credentials.Credentials: A new credentials instance. """ raise NotImplementedError("This credential does not use token uri.") +class CredentialsWithUniverseDomain(Credentials): + """Abstract base for credentials supporting ``with_universe_domain`` factory""" + + def with_universe_domain(self, universe_domain): + """Returns a copy of these credentials with a modified universe domain. + + Args: + universe_domain (str): The universe domain to use + + Returns: + google.auth.credentials.Credentials: A new credentials instance. + """ + raise NotImplementedError( + "This credential does not support with_universe_domain." + ) + + class AnonymousCredentials(Credentials): """Credentials that do not provide any authentication information. diff --git a/packages/google-auth/google/auth/external_account.py b/packages/google-auth/google/auth/external_account.py index e7fed8695ade..c314ea799ea6 100644 --- a/packages/google-auth/google/auth/external_account.py +++ b/packages/google-auth/google/auth/external_account.py @@ -415,16 +415,8 @@ def with_token_uri(self, token_uri): new_cred._metrics_options = self._metrics_options return new_cred + @_helpers.copy_docstring(credentials.CredentialsWithUniverseDomain) def with_universe_domain(self, universe_domain): - """Create a copy of these credentials with the given universe domain. - - Args: - universe_domain (str): The universe domain value. - - Returns: - google.auth.external_account.Credentials: A new credentials - instance. - """ kwargs = self._constructor_args() kwargs.update(universe_domain=universe_domain) new_cred = self.__class__(**kwargs) diff --git a/packages/google-auth/google/auth/external_account_authorized_user.py b/packages/google-auth/google/auth/external_account_authorized_user.py index a2d4edf6ffd2..55230103f43e 100644 --- a/packages/google-auth/google/auth/external_account_authorized_user.py +++ b/packages/google-auth/google/auth/external_account_authorized_user.py @@ -43,6 +43,7 @@ from google.oauth2 import sts from google.oauth2 import utils +_DEFAULT_UNIVERSE_DOMAIN = "googleapis.com" _EXTERNAL_ACCOUNT_AUTHORIZED_USER_JSON_TYPE = "external_account_authorized_user" @@ -75,6 +76,7 @@ def __init__( revoke_url=None, scopes=None, quota_project_id=None, + universe_domain=_DEFAULT_UNIVERSE_DOMAIN, ): """Instantiates a external account authorized user credentials object. @@ -98,6 +100,8 @@ def __init__( quota_project_id (str): The optional project ID used for quota and billing. This project may be different from the project used to create the credentials. + universe_domain (Optional[str]): The universe domain. The default value + is googleapis.com. Returns: google.auth.external_account_authorized_user.Credentials: The @@ -116,6 +120,7 @@ def __init__( self._revoke_url = revoke_url self._quota_project_id = quota_project_id self._scopes = scopes + self._universe_domain = universe_domain or _DEFAULT_UNIVERSE_DOMAIN if not self.valid and not self.can_refresh: raise exceptions.InvalidOperation( @@ -162,6 +167,7 @@ def constructor_args(self): "revoke_url": self._revoke_url, "scopes": self._scopes, "quota_project_id": self._quota_project_id, + "universe_domain": self._universe_domain, } @property @@ -297,6 +303,12 @@ def with_token_uri(self, token_uri): kwargs.update(token_url=token_uri) return self.__class__(**kwargs) + @_helpers.copy_docstring(credentials.CredentialsWithUniverseDomain) + def with_universe_domain(self, universe_domain): + kwargs = self.constructor_args() + kwargs.update(universe_domain=universe_domain) + return self.__class__(**kwargs) + @classmethod def from_info(cls, info, **kwargs): """Creates a Credentials instance from parsed external account info. diff --git a/packages/google-auth/google/oauth2/credentials.py b/packages/google-auth/google/oauth2/credentials.py index a5c93ecc2f85..7d327c110931 100644 --- a/packages/google-auth/google/oauth2/credentials.py +++ b/packages/google-auth/google/oauth2/credentials.py @@ -302,15 +302,8 @@ def with_token_uri(self, token_uri): universe_domain=self._universe_domain, ) + @_helpers.copy_docstring(credentials.CredentialsWithUniverseDomain) def with_universe_domain(self, universe_domain): - """Create a copy of the credential with the given universe domain. - - Args: - universe_domain (str): The universe domain value. - - Returns: - google.oauth2.credentials.Credentials: A new credentials instance. - """ return self.__class__( self.token, diff --git a/packages/google-auth/google/oauth2/service_account.py b/packages/google-auth/google/oauth2/service_account.py index 68db41af409d..4502c6f68c60 100644 --- a/packages/google-auth/google/oauth2/service_account.py +++ b/packages/google-auth/google/oauth2/service_account.py @@ -325,16 +325,8 @@ def with_always_use_jwt_access(self, always_use_jwt_access): cred._always_use_jwt_access = always_use_jwt_access return cred + @_helpers.copy_docstring(credentials.CredentialsWithUniverseDomain) def with_universe_domain(self, universe_domain): - """Create a copy of these credentials with the given universe domain. - - Args: - universe_domain (str): The universe domain value. - - Returns: - google.auth.service_account.Credentials: A new credentials - instance. - """ cred = self._make_copy() cred._universe_domain = universe_domain if universe_domain != _DEFAULT_UNIVERSE_DOMAIN: diff --git a/packages/google-auth/tests/test_external_account_authorized_user.py b/packages/google-auth/tests/test_external_account_authorized_user.py index 7ffd5078c814..7213a23486c1 100644 --- a/packages/google-auth/tests/test_external_account_authorized_user.py +++ b/packages/google-auth/tests/test_external_account_authorized_user.py @@ -44,6 +44,8 @@ BASIC_AUTH_ENCODING = "dXNlcm5hbWU6cGFzc3dvcmQ=" SCOPES = ["email", "profile"] NOW = datetime.datetime(1990, 8, 27, 6, 54, 30) +FAKE_UNIVERSE_DOMAIN = "fake-universe-domain" +DEFAULT_UNIVERSE_DOMAIN = external_account_authorized_user._DEFAULT_UNIVERSE_DOMAIN class TestCredentials(object): @@ -98,6 +100,7 @@ def test_default_state(self): assert creds.refresh_token == REFRESH_TOKEN assert creds.audience == AUDIENCE assert creds.token_url == TOKEN_URL + assert creds.universe_domain == DEFAULT_UNIVERSE_DOMAIN def test_basic_create(self): creds = external_account_authorized_user.Credentials( @@ -105,6 +108,7 @@ def test_basic_create(self): expiry=datetime.datetime.max, scopes=SCOPES, revoke_url=REVOKE_URL, + universe_domain=FAKE_UNIVERSE_DOMAIN, ) assert creds.expiry == datetime.datetime.max @@ -115,6 +119,7 @@ def test_basic_create(self): assert creds.scopes == SCOPES assert creds.is_user assert creds.revoke_url == REVOKE_URL + assert creds.universe_domain == FAKE_UNIVERSE_DOMAIN def test_stunted_create_no_refresh_token(self): with pytest.raises(ValueError) as excinfo: @@ -339,6 +344,7 @@ def test_info(self): assert info["token_info_url"] == TOKEN_INFO_URL assert info["client_id"] == CLIENT_ID assert info["client_secret"] == CLIENT_SECRET + assert info["universe_domain"] == DEFAULT_UNIVERSE_DOMAIN assert "token" not in info assert "expiry" not in info assert "revoke_url" not in info @@ -350,6 +356,7 @@ def test_info_full(self): expiry=NOW, revoke_url=REVOKE_URL, quota_project_id=QUOTA_PROJECT_ID, + universe_domain=FAKE_UNIVERSE_DOMAIN, ) info = creds.info @@ -363,6 +370,7 @@ def test_info_full(self): assert info["expiry"] == NOW.isoformat() + "Z" assert info["revoke_url"] == REVOKE_URL assert info["quota_project_id"] == QUOTA_PROJECT_ID + assert info["universe_domain"] == FAKE_UNIVERSE_DOMAIN def test_to_json(self): creds = self.make_credentials() @@ -375,6 +383,7 @@ def test_to_json(self): assert info["token_info_url"] == TOKEN_INFO_URL assert info["client_id"] == CLIENT_ID assert info["client_secret"] == CLIENT_SECRET + assert info["universe_domain"] == DEFAULT_UNIVERSE_DOMAIN assert "token" not in info assert "expiry" not in info assert "revoke_url" not in info @@ -386,6 +395,7 @@ def test_to_json_full(self): expiry=NOW, revoke_url=REVOKE_URL, quota_project_id=QUOTA_PROJECT_ID, + universe_domain=FAKE_UNIVERSE_DOMAIN, ) json_info = creds.to_json() info = json.loads(json_info) @@ -400,6 +410,7 @@ def test_to_json_full(self): assert info["expiry"] == NOW.isoformat() + "Z" assert info["revoke_url"] == REVOKE_URL assert info["quota_project_id"] == QUOTA_PROJECT_ID + assert info["universe_domain"] == FAKE_UNIVERSE_DOMAIN def test_to_json_full_with_strip(self): creds = self.make_credentials( @@ -467,6 +478,26 @@ def test_with_token_uri(self): assert new_creds._revoke_url == creds._revoke_url assert new_creds._quota_project_id == creds._quota_project_id + def test_with_universe_domain(self): + creds = self.make_credentials( + token=ACCESS_TOKEN, + expiry=NOW, + revoke_url=REVOKE_URL, + quota_project_id=QUOTA_PROJECT_ID, + ) + new_creds = creds.with_universe_domain(FAKE_UNIVERSE_DOMAIN) + assert new_creds._audience == creds._audience + assert new_creds._refresh_token == creds._refresh_token + assert new_creds._token_url == creds._token_url + assert new_creds._token_info_url == creds._token_info_url + assert new_creds._client_id == creds._client_id + assert new_creds._client_secret == creds._client_secret + assert new_creds.token == creds.token + assert new_creds.expiry == creds.expiry + assert new_creds._revoke_url == creds._revoke_url + assert new_creds._quota_project_id == QUOTA_PROJECT_ID + assert new_creds.universe_domain == FAKE_UNIVERSE_DOMAIN + def test_from_file_required_options_only(self, tmpdir): from_creds = self.make_credentials() config_file = tmpdir.join("config.json") From 0cb62efcaa98337dc5e3d936a229e425c727ed34 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Fri, 15 Dec 2023 14:58:01 -0800 Subject: [PATCH 791/966] chore: refresh sys test cred (#1443) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 06b7f380c156919b32d24057e9094de7067db6c5..b6fddac9532eecb2731f31f040b062e52aa028e1 100644 GIT binary patch literal 10324 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 literal 10324 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 mzroy`_lUnFOJWKsw+=*vO8Gdh7~m+6 Date: Mon, 18 Dec 2023 14:12:00 -0800 Subject: [PATCH 792/966] feat: Add optional non blocking refresh for sync auth code (#1368) feat: Add optional non blocking refresh for sync auth code --- .../google/auth/_refresh_worker.py | 98 ++++++++++++ .../google-auth/google/auth/credentials.py | 76 ++++++++- .../google/auth/impersonated_credentials.py | 5 +- .../google-auth/google/oauth2/credentials.py | 4 + .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../tests/oauth2/test_credentials.py | 10 +- .../google-auth/tests/test__refresh_worker.py | 147 ++++++++++++++++++ .../google-auth/tests/test_credentials.py | 118 ++++++++++++++ packages/google-auth/tests/test_downscoped.py | 18 +++ .../tests/test_external_account.py | 10 ++ .../tests/test_impersonated_credentials.py | 2 +- .../oauth2/test_credentials_async.py | 5 +- 12 files changed, 485 insertions(+), 8 deletions(-) create mode 100644 packages/google-auth/google/auth/_refresh_worker.py create mode 100644 packages/google-auth/tests/test__refresh_worker.py diff --git a/packages/google-auth/google/auth/_refresh_worker.py b/packages/google-auth/google/auth/_refresh_worker.py new file mode 100644 index 000000000000..cb115b93999e --- /dev/null +++ b/packages/google-auth/google/auth/_refresh_worker.py @@ -0,0 +1,98 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import copy +import logging +import threading + +import google.auth.exceptions as e + +_LOGGER = logging.getLogger(__name__) + + +class RefreshThreadManager: + """ + Organizes exactly one background job that refresh a token. + """ + + def __init__(self): + """Initializes the manager.""" + + self._worker = None + self._lock = threading.Lock() # protects access to worker threads. + + def start_refresh(self, cred, request): + """Starts a refresh thread for the given credentials. + The credentials are refreshed using the request parameter. + request and cred MUST not be None + + Returns True if a background refresh was kicked off. False otherwise. + + Args: + cred: A credentials object. + request: A request object. + Returns: + bool + """ + if cred is None or request is None: + raise e.InvalidValue( + "Unable to start refresh. cred and request must be valid and instantiated objects." + ) + + with self._lock: + if self._worker is not None and self._worker._error_info is not None: + return False + + if self._worker is None or not self._worker.is_alive(): # pragma: NO COVER + self._worker = RefreshThread(cred=cred, request=copy.deepcopy(request)) + self._worker.start() + return True + + def clear_error(self): + """ + Removes any errors that were stored from previous background refreshes. + """ + with self._lock: + if self._worker: + self._worker._error_info = None + + +class RefreshThread(threading.Thread): + """ + Thread that refreshes credentials. + """ + + def __init__(self, cred, request, **kwargs): + """Initializes the thread. + + Args: + cred: A Credential object to refresh. + request: A Request object used to perform a credential refresh. + **kwargs: Additional keyword arguments. + """ + + super().__init__(**kwargs) + self._cred = cred + self._request = request + self._error_info = None + + def run(self): + """ + Perform the credential refresh. + """ + try: + self._cred.refresh(self._request) + except Exception as err: # pragma: NO COVER + _LOGGER.error(f"Background refresh failed due to: {err}") + self._error_info = err diff --git a/packages/google-auth/google/auth/credentials.py b/packages/google-auth/google/auth/credentials.py index 6e62a4b4ee78..a4fa1829c720 100644 --- a/packages/google-auth/google/auth/credentials.py +++ b/packages/google-auth/google/auth/credentials.py @@ -16,11 +16,13 @@ """Interfaces for credentials.""" import abc +from enum import Enum import os from google.auth import _helpers, environment_vars from google.auth import exceptions from google.auth import metrics +from google.auth._refresh_worker import RefreshThreadManager class Credentials(metaclass=abc.ABCMeta): @@ -59,6 +61,9 @@ def __init__(self): """Optional[str]: The universe domain value, default is googleapis.com """ + self._use_non_blocking_refresh = False + self._refresh_worker = RefreshThreadManager() + @property def expired(self): """Checks if the credentials are expired. @@ -66,10 +71,12 @@ def expired(self): Note that credentials can be invalid but not expired because Credentials with :attr:`expiry` set to None is considered to never expire. + + .. deprecated:: v2.24.0 + Prefer checking :attr:`token_state` instead. """ if not self.expiry: return False - # Remove some threshold from expiry to err on the side of reporting # expiration early so that we avoid the 401-refresh-retry loop. skewed_expiry = self.expiry - _helpers.REFRESH_THRESHOLD @@ -81,9 +88,34 @@ def valid(self): This is True if the credentials have a :attr:`token` and the token is not :attr:`expired`. + + .. deprecated:: v2.24.0 + Prefer checking :attr:`token_state` instead. """ return self.token is not None and not self.expired + @property + def token_state(self): + """ + See `:obj:`TokenState` + """ + if self.token is None: + return TokenState.INVALID + + # Credentials that can't expire are always treated as fresh. + if self.expiry is None: + return TokenState.FRESH + + expired = _helpers.utcnow() >= self.expiry + if expired: + return TokenState.INVALID + + is_stale = _helpers.utcnow() >= (self.expiry - _helpers.REFRESH_THRESHOLD) + if is_stale: + return TokenState.STALE + + return TokenState.FRESH + @property def quota_project_id(self): """Project to use for quota and billing purposes.""" @@ -154,6 +186,25 @@ def apply(self, headers, token=None): if self.quota_project_id: headers["x-goog-user-project"] = self.quota_project_id + def _blocking_refresh(self, request): + if not self.valid: + self.refresh(request) + + def _non_blocking_refresh(self, request): + use_blocking_refresh_fallback = False + + if self.token_state == TokenState.STALE: + use_blocking_refresh_fallback = not self._refresh_worker.start_refresh( + self, request + ) + + if self.token_state == TokenState.INVALID or use_blocking_refresh_fallback: + self.refresh(request) + # If the blocking refresh succeeds then we can clear the error info + # on the background refresh worker, and perform refreshes in a + # background thread. + self._refresh_worker.clear_error() + def before_request(self, request, method, url, headers): """Performs credential-specific before request logic. @@ -171,11 +222,17 @@ def before_request(self, request, method, url, headers): # pylint: disable=unused-argument # (Subclasses may use these arguments to ascertain information about # the http request.) - if not self.valid: - self.refresh(request) + if self._use_non_blocking_refresh: + self._non_blocking_refresh(request) + else: + self._blocking_refresh(request) + metrics.add_metric_header(headers, self._metric_header_for_usage()) self.apply(headers) + def with_non_blocking_refresh(self): + self._use_non_blocking_refresh = True + class CredentialsWithQuotaProject(Credentials): """Abstract base for credentials supporting ``with_quota_project`` factory""" @@ -439,3 +496,16 @@ def signer(self): # pylint: disable=missing-raises-doc # (pylint doesn't recognize that this is abstract) raise NotImplementedError("Signer must be implemented.") + + +class TokenState(Enum): + """ + Tracks the state of a token. + FRESH: The token is valid. It is not expired or close to expired, or the token has no expiry. + STALE: The token is close to expired, and should be refreshed. The token can be used normally. + INVALID: The token is expired or invalid. The token cannot be used for a normal operation. + """ + + FRESH = 1 + STALE = 2 + INVALID = 3 diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index c272a3ca28bd..d32e6eb69a58 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -259,7 +259,10 @@ def _update_token(self, request): """ # Refresh our source credentials if it is not valid. - if not self._source_credentials.valid: + if ( + self._source_credentials.token_state == credentials.TokenState.STALE + or self._source_credentials.token_state == credentials.TokenState.INVALID + ): self._source_credentials.refresh(request) body = { diff --git a/packages/google-auth/google/oauth2/credentials.py b/packages/google-auth/google/oauth2/credentials.py index 7d327c110931..4ad52db905f1 100644 --- a/packages/google-auth/google/oauth2/credentials.py +++ b/packages/google-auth/google/oauth2/credentials.py @@ -161,6 +161,8 @@ def __getstate__(self): # because they need to be importable. # Instead, the refresh_handler setter should be used to repopulate this. del state_dict["_refresh_handler"] + # Remove worker as it contains multiproccessing queue objects. + del state_dict["_refresh_worker"] return state_dict def __setstate__(self, d): @@ -183,6 +185,8 @@ def __setstate__(self, d): self._universe_domain = d.get("_universe_domain") or _DEFAULT_UNIVERSE_DOMAIN # The refresh_handler setter should be used to repopulate this. self._refresh_handler = None + self._refresh_worker = None + self._use_non_blocking_refresh = d.get("_use_non_blocking_refresh") @property def refresh_token(self): diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index b6fddac9532eecb2731f31f040b062e52aa028e1..493b2222e457e801bb11b38d86d0392be9be2ce0 100644 GIT binary patch literal 10324 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 literal 10324 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 diff --git a/packages/google-auth/tests/oauth2/test_credentials.py b/packages/google-auth/tests/oauth2/test_credentials.py index 7d4917739100..78711299ea78 100644 --- a/packages/google-auth/tests/oauth2/test_credentials.py +++ b/packages/google-auth/tests/oauth2/test_credentials.py @@ -24,6 +24,7 @@ from google.auth import _helpers from google.auth import exceptions from google.auth import transport +from google.auth.credentials import TokenState from google.oauth2 import credentials @@ -61,6 +62,7 @@ def test_default_state(self): assert not credentials.expired # Scopes aren't required for these credentials assert not credentials.requires_scopes + assert credentials.token_state == TokenState.INVALID # Test properties assert credentials.refresh_token == self.REFRESH_TOKEN assert credentials.token_uri == self.TOKEN_URI @@ -911,7 +913,11 @@ def test_pickle_and_unpickle(self): assert list(creds.__dict__).sort() == list(unpickled.__dict__).sort() for attr in list(creds.__dict__): - assert getattr(creds, attr) == getattr(unpickled, attr) + # Worker should always be None + if attr == "_refresh_worker": + assert getattr(unpickled, attr) is None + else: + assert getattr(creds, attr) == getattr(unpickled, attr) def test_pickle_and_unpickle_universe_domain(self): # old version of auth lib doesn't have _universe_domain, so the pickled @@ -945,7 +951,7 @@ def test_pickle_and_unpickle_with_refresh_handler(self): for attr in list(creds.__dict__): # For the _refresh_handler property, the unpickled creds should be # set to None. - if attr == "_refresh_handler": + if attr == "_refresh_handler" or attr == "_refresh_worker": assert getattr(unpickled, attr) is None else: assert getattr(creds, attr) == getattr(unpickled, attr) diff --git a/packages/google-auth/tests/test__refresh_worker.py b/packages/google-auth/tests/test__refresh_worker.py new file mode 100644 index 000000000000..1fbbf1625c8e --- /dev/null +++ b/packages/google-auth/tests/test__refresh_worker.py @@ -0,0 +1,147 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import random +import threading +import time + +import mock +import pytest # type: ignore + +from google.auth import _refresh_worker, credentials, exceptions + +MAIN_THREAD_SLEEP_MS = 100 / 1000 + + +class MockCredentialsImpl(credentials.Credentials): + def __init__(self, sleep_seconds=None): + self.refresh_count = 0 + self.token = None + self.sleep_seconds = sleep_seconds if sleep_seconds else None + + def refresh(self, request): + if self.sleep_seconds: + time.sleep(self.sleep_seconds) + self.token = request + self.refresh_count += 1 + + +@pytest.fixture +def test_thread_count(): + return 25 + + +def _cred_spinlock(cred): + while cred.token is None: # pragma: NO COVER + time.sleep(MAIN_THREAD_SLEEP_MS) + + +def test_invalid_start_refresh(): + w = _refresh_worker.RefreshThreadManager() + with pytest.raises(exceptions.InvalidValue): + w.start_refresh(None, None) + + +def test_start_refresh(): + w = _refresh_worker.RefreshThreadManager() + cred = MockCredentialsImpl() + request = mock.MagicMock() + assert w.start_refresh(cred, request) + + assert w._worker is not None + + _cred_spinlock(cred) + + assert cred.token == request + assert cred.refresh_count == 1 + + +def test_nonblocking_start_refresh(): + w = _refresh_worker.RefreshThreadManager() + cred = MockCredentialsImpl(sleep_seconds=1) + request = mock.MagicMock() + assert w.start_refresh(cred, request) + + assert w._worker is not None + assert not cred.token + assert cred.refresh_count == 0 + + +def test_multiple_refreshes_multiple_workers(test_thread_count): + w = _refresh_worker.RefreshThreadManager() + cred = MockCredentialsImpl() + request = mock.MagicMock() + + def _thread_refresh(): + time.sleep(random.randrange(0, 5)) + assert w.start_refresh(cred, request) + + threads = [ + threading.Thread(target=_thread_refresh) for _ in range(test_thread_count) + ] + for t in threads: + t.start() + + _cred_spinlock(cred) + + assert cred.token == request + # There is a chance only one thread has enough time to perform a refresh. + # Generally multiple threads will have time to perform a refresh + assert cred.refresh_count > 0 + + +def test_refresh_error(): + w = _refresh_worker.RefreshThreadManager() + cred = mock.MagicMock() + request = mock.MagicMock() + + cred.refresh.side_effect = exceptions.RefreshError("Failed to refresh") + + assert w.start_refresh(cred, request) + + while w._worker._error_info is None: # pragma: NO COVER + time.sleep(MAIN_THREAD_SLEEP_MS) + + assert w._worker is not None + assert isinstance(w._worker._error_info, exceptions.RefreshError) + + +def test_refresh_error_call_refresh_again(): + w = _refresh_worker.RefreshThreadManager() + cred = mock.MagicMock() + request = mock.MagicMock() + + cred.refresh.side_effect = exceptions.RefreshError("Failed to refresh") + + assert w.start_refresh(cred, request) + + while w._worker._error_info is None: # pragma: NO COVER + time.sleep(MAIN_THREAD_SLEEP_MS) + + assert not w.start_refresh(cred, request) + + +def test_refresh_dead_worker(): + cred = MockCredentialsImpl() + request = mock.MagicMock() + + w = _refresh_worker.RefreshThreadManager() + w._worker = None + + w.start_refresh(cred, request) + + _cred_spinlock(cred) + + assert cred.token == request + assert cred.refresh_count == 1 diff --git a/packages/google-auth/tests/test_credentials.py b/packages/google-auth/tests/test_credentials.py index d64f3abb5065..8e6bbc96330c 100644 --- a/packages/google-auth/tests/test_credentials.py +++ b/packages/google-auth/tests/test_credentials.py @@ -14,6 +14,7 @@ import datetime +import mock import pytest # type: ignore from google.auth import _helpers @@ -23,6 +24,11 @@ class CredentialsImpl(credentials.Credentials): def refresh(self, request): self.token = request + self.expiry = ( + datetime.datetime.utcnow() + + _helpers.REFRESH_THRESHOLD + + datetime.timedelta(seconds=5) + ) def with_quota_project(self, quota_project_id): raise NotImplementedError() @@ -43,6 +49,13 @@ def test_credentials_constructor(): assert not credentials.expired assert not credentials.valid assert credentials.universe_domain == "googleapis.com" + assert not credentials._use_non_blocking_refresh + + +def test_with_non_blocking_refresh(): + c = CredentialsImpl() + c.with_non_blocking_refresh() + assert c._use_non_blocking_refresh def test_expired_and_valid(): @@ -220,3 +233,108 @@ def test_create_scoped_if_required_not_scopes(): ) assert scoped_credentials is unscoped_credentials + + +def test_nonblocking_refresh_fresh_credentials(): + c = CredentialsImpl() + + c._refresh_worker = mock.MagicMock() + + request = "token" + + c.refresh(request) + assert c.token_state == credentials.TokenState.FRESH + + c.with_non_blocking_refresh() + c.before_request(request, "http://example.com", "GET", {}) + + +def test_nonblocking_refresh_invalid_credentials(): + c = CredentialsImpl() + c.with_non_blocking_refresh() + + request = "token" + headers = {} + + assert c.token_state == credentials.TokenState.INVALID + + c.before_request(request, "http://example.com", "GET", headers) + assert c.token_state == credentials.TokenState.FRESH + assert c.valid + assert c.token == "token" + assert headers["authorization"] == "Bearer token" + assert "x-identity-trust-boundary" not in headers + + +def test_nonblocking_refresh_stale_credentials(): + c = CredentialsImpl() + c.with_non_blocking_refresh() + + request = "token" + headers = {} + + # Invalid credentials MUST require a blocking refresh. + c.before_request(request, "http://example.com", "GET", headers) + assert c.token_state == credentials.TokenState.FRESH + assert not c._refresh_worker._worker + + c.expiry = ( + datetime.datetime.utcnow() + + _helpers.REFRESH_THRESHOLD + - datetime.timedelta(seconds=1) + ) + + # STALE credentials SHOULD spawn a non-blocking worker + assert c.token_state == credentials.TokenState.STALE + c.before_request(request, "http://example.com", "GET", headers) + assert c._refresh_worker._worker is not None + + assert c.token_state == credentials.TokenState.FRESH + assert c.valid + assert c.token == "token" + assert headers["authorization"] == "Bearer token" + assert "x-identity-trust-boundary" not in headers + + +def test_nonblocking_refresh_failed_credentials(): + c = CredentialsImpl() + c.with_non_blocking_refresh() + + request = "token" + headers = {} + + # Invalid credentials MUST require a blocking refresh. + c.before_request(request, "http://example.com", "GET", headers) + assert c.token_state == credentials.TokenState.FRESH + assert not c._refresh_worker._worker + + c.expiry = ( + datetime.datetime.utcnow() + + _helpers.REFRESH_THRESHOLD + - datetime.timedelta(seconds=1) + ) + + # STALE credentials SHOULD spawn a non-blocking worker + assert c.token_state == credentials.TokenState.STALE + c._refresh_worker._worker = mock.MagicMock() + c._refresh_worker._worker._error_info = "Some Error" + c.before_request(request, "http://example.com", "GET", headers) + assert c._refresh_worker._worker is not None + + assert c.token_state == credentials.TokenState.FRESH + assert c.valid + assert c.token == "token" + assert headers["authorization"] == "Bearer token" + assert "x-identity-trust-boundary" not in headers + + +def test_token_state_no_expiry(): + c = CredentialsImpl() + + request = "token" + c.refresh(request) + + c.expiry = None + assert c.token_state == credentials.TokenState.FRESH + + c.before_request(request, "http://example.com", "GET", {}) diff --git a/packages/google-auth/tests/test_downscoped.py b/packages/google-auth/tests/test_downscoped.py index b011380bdbb8..8cc2a30d1637 100644 --- a/packages/google-auth/tests/test_downscoped.py +++ b/packages/google-auth/tests/test_downscoped.py @@ -25,6 +25,7 @@ from google.auth import downscoped from google.auth import exceptions from google.auth import transport +from google.auth.credentials import TokenState EXPRESSION = ( @@ -676,6 +677,7 @@ def test_before_request_expired(self, utcnow): assert credentials.valid assert not credentials.expired + assert credentials.token_state == TokenState.FRESH credentials.before_request(request, "POST", "https://example.com/api", headers) @@ -687,8 +689,24 @@ def test_before_request_expired(self, utcnow): assert not credentials.valid assert credentials.expired + assert credentials.token_state == TokenState.STALE credentials.before_request(request, "POST", "https://example.com/api", headers) + assert credentials.token_state == TokenState.FRESH + + # New token should be retrieved. + assert headers == { + "authorization": "Bearer {}".format(SUCCESS_RESPONSE["access_token"]) + } + + utcnow.return_value = datetime.datetime.min + datetime.timedelta(seconds=6000) + + assert not credentials.valid + assert credentials.expired + assert credentials.token_state == TokenState.INVALID + + credentials.before_request(request, "POST", "https://example.com/api", headers) + assert credentials.token_state == TokenState.FRESH # New token should be retrieved. assert headers == { diff --git a/packages/google-auth/tests/test_external_account.py b/packages/google-auth/tests/test_external_account.py index 5225dcf34240..7f33b1dfa2a9 100644 --- a/packages/google-auth/tests/test_external_account.py +++ b/packages/google-auth/tests/test_external_account.py @@ -24,6 +24,7 @@ from google.auth import exceptions from google.auth import external_account from google.auth import transport +from google.auth.credentials import TokenState IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE = ( @@ -1494,6 +1495,7 @@ def test_before_request_expired(self, utcnow): assert credentials.valid assert not credentials.expired + assert credentials.token_state == TokenState.FRESH credentials.before_request(request, "POST", "https://example.com/api", headers) @@ -1508,8 +1510,10 @@ def test_before_request_expired(self, utcnow): assert not credentials.valid assert credentials.expired + assert credentials.token_state == TokenState.STALE credentials.before_request(request, "POST", "https://example.com/api", headers) + assert credentials.token_state == TokenState.FRESH # New token should be retrieved. assert headers == { @@ -1551,8 +1555,10 @@ def test_before_request_impersonation_expired(self, utcnow): assert credentials.valid assert not credentials.expired + assert credentials.token_state == TokenState.FRESH credentials.before_request(request, "POST", "https://example.com/api", headers) + assert credentials.token_state == TokenState.FRESH # Cached token should be used. assert headers == { @@ -1566,6 +1572,10 @@ def test_before_request_impersonation_expired(self, utcnow): assert not credentials.valid assert credentials.expired + assert credentials.token_state == TokenState.STALE + + credentials.before_request(request, "POST", "https://example.com/api", headers) + assert credentials.token_state == TokenState.FRESH credentials.before_request(request, "POST", "https://example.com/api", headers) diff --git a/packages/google-auth/tests/test_impersonated_credentials.py b/packages/google-auth/tests/test_impersonated_credentials.py index 9eb04b134059..a2bf31bf858e 100644 --- a/packages/google-auth/tests/test_impersonated_credentials.py +++ b/packages/google-auth/tests/test_impersonated_credentials.py @@ -242,7 +242,7 @@ def test_refresh_success_iam_endpoint_override( request_kwargs = request.call_args[1] assert request_kwargs["url"] == self.IAM_ENDPOINT_OVERRIDE - @pytest.mark.parametrize("time_skew", [100, -100]) + @pytest.mark.parametrize("time_skew", [150, -150]) def test_refresh_source_credentials(self, time_skew): credentials = self.make_credentials(lifetime=None) diff --git a/packages/google-auth/tests_async/oauth2/test_credentials_async.py b/packages/google-auth/tests_async/oauth2/test_credentials_async.py index f6c640ad63c2..fba0c3cf93c8 100644 --- a/packages/google-auth/tests_async/oauth2/test_credentials_async.py +++ b/packages/google-auth/tests_async/oauth2/test_credentials_async.py @@ -433,7 +433,10 @@ def test_pickle_and_unpickle(self): assert list(creds.__dict__).sort() == list(unpickled.__dict__).sort() for attr in list(creds.__dict__): - assert getattr(creds, attr) == getattr(unpickled, attr) + if attr == "_refresh_worker": + assert getattr(unpickled, attr) is None + else: + assert getattr(creds, attr) == getattr(unpickled, attr) def test_pickle_with_missing_attribute(self): creds = self.make_credentials() From 91f75b29196bea76d1e7fb69f9942a2129ac7835 Mon Sep 17 00:00:00 2001 From: Carl Lundin <108372512+clundin25@users.noreply.github.com> Date: Wed, 20 Dec 2023 10:34:14 -0800 Subject: [PATCH 793/966] fix: Guard delete statements. Add default fallback for _use_non_blocking_refresh. (#1445) --- .../google-auth/google/oauth2/credentials.py | 10 ++++++---- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../tests/oauth2/test_credentials.py | 2 ++ 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/google-auth/google/oauth2/credentials.py b/packages/google-auth/google/oauth2/credentials.py index 4ad52db905f1..41f4a05bb6e3 100644 --- a/packages/google-auth/google/oauth2/credentials.py +++ b/packages/google-auth/google/oauth2/credentials.py @@ -160,9 +160,11 @@ def __getstate__(self): # unpickling certain callables (lambda, functools.partial instances) # because they need to be importable. # Instead, the refresh_handler setter should be used to repopulate this. - del state_dict["_refresh_handler"] - # Remove worker as it contains multiproccessing queue objects. - del state_dict["_refresh_worker"] + if "_refresh_handler" in state_dict: + del state_dict["_refresh_handler"] + + if "_refresh_worker" in state_dict: + del state_dict["_refresh_worker"] return state_dict def __setstate__(self, d): @@ -186,7 +188,7 @@ def __setstate__(self, d): # The refresh_handler setter should be used to repopulate this. self._refresh_handler = None self._refresh_worker = None - self._use_non_blocking_refresh = d.get("_use_non_blocking_refresh") + self._use_non_blocking_refresh = d.get("_use_non_blocking_refresh", False) @property def refresh_token(self): diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 493b2222e457e801bb11b38d86d0392be9be2ce0..c5b080e8b702ae098ccb4fc6891308df69603438 100644 GIT binary patch literal 10324 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 diff --git a/packages/google-auth/tests/oauth2/test_credentials.py b/packages/google-auth/tests/oauth2/test_credentials.py index 78711299ea78..7516fe22e627 100644 --- a/packages/google-auth/tests/oauth2/test_credentials.py +++ b/packages/google-auth/tests/oauth2/test_credentials.py @@ -963,6 +963,8 @@ def test_pickle_with_missing_attribute(self): # this mimics a pickle created with a previous class definition with # fewer attributes del creds.__dict__["_quota_project_id"] + del creds.__dict__["_refresh_handler"] + del creds.__dict__["_refresh_worker"] unpickled = pickle.loads(pickle.dumps(creds)) From d50162b00e5ba71b7a5e7ec0ee4567ca7173e5f5 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Wed, 3 Jan 2024 00:20:27 -0800 Subject: [PATCH 794/966] chore(main): release 2.26.0 (#1442) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 14 ++++++++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index cabc74bb52f3..4446846ed9c4 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,20 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.26.0](https://github.com/googleapis/google-auth-library-python/compare/v2.25.2...v2.26.0) (2023-12-20) + + +### Features + +* Add optional non blocking refresh for sync auth code ([a6dc2c3](https://github.com/googleapis/google-auth-library-python/commit/a6dc2c336a5e76a478691e3bedd0418aae08b911)) +* Add optional non blocking refresh for sync auth code ([#1368](https://github.com/googleapis/google-auth-library-python/issues/1368)) ([a6dc2c3](https://github.com/googleapis/google-auth-library-python/commit/a6dc2c336a5e76a478691e3bedd0418aae08b911)) + + +### Bug Fixes + +* External account user cred universe domain support ([#1437](https://github.com/googleapis/google-auth-library-python/issues/1437)) ([75068f9](https://github.com/googleapis/google-auth-library-python/commit/75068f93453e6ea0b5c7be5561e7ba342c695e95)) +* Guard delete statements. Add default fallback for _use_non_blocking_refresh. ([#1445](https://github.com/googleapis/google-auth-library-python/issues/1445)) ([776d634](https://github.com/googleapis/google-auth-library-python/commit/776d634ac6d989b224f8dbfb11d166cb3025a342)) + ## [2.25.2](https://github.com/googleapis/google-auth-library-python/compare/v2.25.1...v2.25.2) (2023-12-08) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 31cc30242a21..8057b8a08753 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.25.2" +__version__ = "2.26.0" From 60dc1e15911ce739f1fc19bd6d7e08ae64825884 Mon Sep 17 00:00:00 2001 From: Carl Lundin <108372512+clundin25@users.noreply.github.com> Date: Wed, 3 Jan 2024 14:26:07 -0800 Subject: [PATCH 795/966] fix: Ensure that refresh worker is pickle-able. (#1447) * fix: Ensure that refresh worker is pickle-able. --- .../google/auth/_refresh_worker.py | 11 +++++++++++ .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../google-auth/tests/test__refresh_worker.py | 9 +++++++++ 3 files changed, 20 insertions(+) diff --git a/packages/google-auth/google/auth/_refresh_worker.py b/packages/google-auth/google/auth/_refresh_worker.py index cb115b93999e..9bb0ccc2c598 100644 --- a/packages/google-auth/google/auth/_refresh_worker.py +++ b/packages/google-auth/google/auth/_refresh_worker.py @@ -67,6 +67,17 @@ def clear_error(self): if self._worker: self._worker._error_info = None + def __getstate__(self): + """Pickle helper that serializes the _lock attribute.""" + state = self.__dict__.copy() + state["_lock"] = None + return state + + def __setstate__(self, state): + """Pickle helper that deserializes the _lock attribute.""" + state["_key"] = threading.Lock() + self.__dict__.update(state) + class RefreshThread(threading.Thread): """ diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index c5b080e8b702ae098ccb4fc6891308df69603438..871eb5cbb0c71af88457e6a5968bb58396416bfe 100644 GIT binary patch literal 10324 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 Date: Wed, 3 Jan 2024 22:48:15 +0000 Subject: [PATCH 796/966] chore(main): release 2.26.1 (#1448) :robot: I have created a release *beep* *boop* --- ## [2.26.1](https://togithub.com/googleapis/google-auth-library-python/compare/v2.26.0...v2.26.1) (2024-01-03) ### Bug Fixes * Ensure that refresh worker is pickle-able. ([#1447](https://togithub.com/googleapis/google-auth-library-python/issues/1447)) ([421c184](https://togithub.com/googleapis/google-auth-library-python/commit/421c184ff4654024afe3e64754318a6be0cc96fc)) --- This PR was generated with [Release Please](https://togithub.com/googleapis/release-please). See [documentation](https://togithub.com/googleapis/release-please#release-please). --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 4446846ed9c4..467275c9254f 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.26.1](https://github.com/googleapis/google-auth-library-python/compare/v2.26.0...v2.26.1) (2024-01-03) + + +### Bug Fixes + +* Ensure that refresh worker is pickle-able. ([#1447](https://github.com/googleapis/google-auth-library-python/issues/1447)) ([421c184](https://github.com/googleapis/google-auth-library-python/commit/421c184ff4654024afe3e64754318a6be0cc96fc)) + ## [2.26.0](https://github.com/googleapis/google-auth-library-python/compare/v2.25.2...v2.26.0) (2023-12-20) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 8057b8a08753..1c94c2f5f613 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.26.0" +__version__ = "2.26.1" From 72af03c0df136635becb7e46f6123fa1b2240d50 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Wed, 10 Jan 2024 16:16:21 -0800 Subject: [PATCH 797/966] fix: read universe_domain for external account authorized user (#1450) * chore: refresh sys test cred * fix: read universe_domain for external account authorized user * update test json file * update --- .../auth/external_account_authorized_user.py | 1 + .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes ...ernal_account_authorized_user_non_gdu.json | 10 ++++++++++ packages/google-auth/tests/test__default.py | 13 +++++++++++++ 4 files changed, 24 insertions(+) create mode 100644 packages/google-auth/tests/data/external_account_authorized_user_non_gdu.json diff --git a/packages/google-auth/google/auth/external_account_authorized_user.py b/packages/google-auth/google/auth/external_account_authorized_user.py index 55230103f43e..526588f7e85d 100644 --- a/packages/google-auth/google/auth/external_account_authorized_user.py +++ b/packages/google-auth/google/auth/external_account_authorized_user.py @@ -342,6 +342,7 @@ def from_info(cls, info, **kwargs): revoke_url=info.get("revoke_url"), quota_project_id=info.get("quota_project_id"), scopes=info.get("scopes"), + universe_domain=info.get("universe_domain", _DEFAULT_UNIVERSE_DOMAIN), **kwargs ) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 871eb5cbb0c71af88457e6a5968bb58396416bfe..bcb6dec2185c05006aa936c861c537a4ca83ca63 100644 GIT binary patch literal 10324 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 literal 10324 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 Date: Wed, 10 Jan 2024 22:02:52 -0800 Subject: [PATCH 798/966] chore(main): release 2.26.2 (#1451) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 467275c9254f..ad386157c4d6 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.26.2](https://github.com/googleapis/google-auth-library-python/compare/v2.26.1...v2.26.2) (2024-01-11) + + +### Bug Fixes + +* Read universe_domain for external account authorized user ([#1450](https://github.com/googleapis/google-auth-library-python/issues/1450)) ([1cc7df3](https://github.com/googleapis/google-auth-library-python/commit/1cc7df3505ad2083d85f3cb2476eeee250e7faf0)) + ## [2.26.1](https://github.com/googleapis/google-auth-library-python/compare/v2.26.0...v2.26.1) (2024-01-03) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 1c94c2f5f613..6d53c4c41187 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.26.1" +__version__ = "2.26.2" From 1dc959308f09bf5fc2de114993a6127ad04c248d Mon Sep 17 00:00:00 2001 From: Carl Lundin <108372512+clundin25@users.noreply.github.com> Date: Thu, 18 Jan 2024 16:44:17 -0800 Subject: [PATCH 799/966] fix: Conditionally import requests only if no request was passed by the caller. (#1456) This resolves https://togithub.com/googleapis/google-auth-library-python/issues/1455. The repo-wide pattern is to only import the requests module at runtime as a fallback. --- .../google-auth/google/oauth2/id_token.py | 3 ++- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/google/oauth2/id_token.py b/packages/google-auth/google/oauth2/id_token.py index 2b1abec2b42b..e5dda508da2e 100644 --- a/packages/google-auth/google/oauth2/id_token.py +++ b/packages/google-auth/google/oauth2/id_token.py @@ -62,7 +62,6 @@ from google.auth import environment_vars from google.auth import exceptions from google.auth import jwt -import google.auth.transport.requests # The URL that provides public certificates for verifying ID tokens issued @@ -282,6 +281,8 @@ def fetch_id_token_credentials(audience, request=None): # Create a request object if not provided. if not request: + import google.auth.transport.requests + request = google.auth.transport.requests.Request() if _metadata.ping(request): diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index bcb6dec2185c05006aa936c861c537a4ca83ca63..61417809cca72f2238d491efa0406c267f15816e 100644 GIT binary patch literal 10324 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 From 1a8fea790c7d5972b00410bea6f8a418ab4738c8 Mon Sep 17 00:00:00 2001 From: Carl Lundin <108372512+clundin25@users.noreply.github.com> Date: Tue, 23 Jan 2024 20:19:40 -0800 Subject: [PATCH 800/966] feat: Add optional account association for Authorized User credentials. (#1458) * feat: Add optional account association for Authorized User credentials. * chore: Refresh system test creds. * Fix two missed constructors. --- .../google-auth/google/oauth2/credentials.py | 42 ++++++++++++++++++ .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../tests/oauth2/test_credentials.py | 7 +++ 3 files changed, 49 insertions(+) diff --git a/packages/google-auth/google/oauth2/credentials.py b/packages/google-auth/google/oauth2/credentials.py index 41f4a05bb6e3..c239beed1347 100644 --- a/packages/google-auth/google/oauth2/credentials.py +++ b/packages/google-auth/google/oauth2/credentials.py @@ -87,6 +87,7 @@ def __init__( granted_scopes=None, trust_boundary=None, universe_domain=_DEFAULT_UNIVERSE_DOMAIN, + account=None, ): """ Args: @@ -131,6 +132,7 @@ def __init__( trust_boundary (str): String representation of trust boundary meta. universe_domain (Optional[str]): The universe domain. The default universe domain is googleapis.com. + account (Optional[str]): The account associated with the credential. """ super(Credentials, self).__init__() self.token = token @@ -149,6 +151,7 @@ def __init__( self._enable_reauth_refresh = enable_reauth_refresh self._trust_boundary = trust_boundary self._universe_domain = universe_domain or _DEFAULT_UNIVERSE_DOMAIN + self._account = account or "" def __getstate__(self): """A __getstate__ method must exist for the __setstate__ to be called @@ -189,6 +192,7 @@ def __setstate__(self, d): self._refresh_handler = None self._refresh_worker = None self._use_non_blocking_refresh = d.get("_use_non_blocking_refresh", False) + self._account = d.get("_account", "") @property def refresh_token(self): @@ -268,6 +272,11 @@ def refresh_handler(self, value): raise TypeError("The provided refresh_handler is not a callable or None.") self._refresh_handler = value + @property + def account(self): + """str: The user account associated with the credential. If the account is unknown an empty string is returned.""" + return self._account + @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) def with_quota_project(self, quota_project_id): @@ -286,6 +295,7 @@ def with_quota_project(self, quota_project_id): enable_reauth_refresh=self._enable_reauth_refresh, trust_boundary=self._trust_boundary, universe_domain=self._universe_domain, + account=self._account, ) @_helpers.copy_docstring(credentials.CredentialsWithTokenUri) @@ -306,6 +316,35 @@ def with_token_uri(self, token_uri): enable_reauth_refresh=self._enable_reauth_refresh, trust_boundary=self._trust_boundary, universe_domain=self._universe_domain, + account=self._account, + ) + + def with_account(self, account): + """Returns a copy of these credentials with a modified account. + + Args: + account (str): The account to set + + Returns: + google.oauth2.credentials.Credentials: A new credentials instance. + """ + + return self.__class__( + self.token, + refresh_token=self.refresh_token, + id_token=self.id_token, + token_uri=self._token_uri, + client_id=self.client_id, + client_secret=self.client_secret, + scopes=self.scopes, + default_scopes=self.default_scopes, + granted_scopes=self.granted_scopes, + quota_project_id=self.quota_project_id, + rapt_token=self.rapt_token, + enable_reauth_refresh=self._enable_reauth_refresh, + trust_boundary=self._trust_boundary, + universe_domain=self._universe_domain, + account=account, ) @_helpers.copy_docstring(credentials.CredentialsWithUniverseDomain) @@ -326,6 +365,7 @@ def with_universe_domain(self, universe_domain): enable_reauth_refresh=self._enable_reauth_refresh, trust_boundary=self._trust_boundary, universe_domain=universe_domain, + account=self._account, ) def _metric_header_for_usage(self): @@ -474,6 +514,7 @@ def from_authorized_user_info(cls, info, scopes=None): rapt_token=info.get("rapt_token"), # may not exist trust_boundary=info.get("trust_boundary"), # may not exist universe_domain=info.get("universe_domain"), # may not exist + account=info.get("account", ""), # may not exist ) @classmethod @@ -518,6 +559,7 @@ def to_json(self, strip=None): "scopes": self.scopes, "rapt_token": self.rapt_token, "universe_domain": self._universe_domain, + "account": self._account, } if self.expiry: # flatten expiry timestamp prep["expiry"] = self.expiry.isoformat() + "Z" diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 61417809cca72f2238d491efa0406c267f15816e..78e375c054480a8bffde4e716319d765c199b32c 100644 GIT binary patch literal 10324 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 m!q8?aDS!apDc5NquJ Date: Wed, 24 Jan 2024 10:03:22 -0800 Subject: [PATCH 801/966] fix: allow custom universe domain for gce creds (#1460) --- .../google/auth/compute_engine/credentials.py | 35 ++++++++++- .../tests/compute_engine/test_credentials.py | 61 ++++++++++++++++--- 2 files changed, 85 insertions(+), 11 deletions(-) diff --git a/packages/google-auth/google/auth/compute_engine/credentials.py b/packages/google-auth/google/auth/compute_engine/credentials.py index a035c7697afe..7541c1d8cfa9 100644 --- a/packages/google-auth/google/auth/compute_engine/credentials.py +++ b/packages/google-auth/google/auth/compute_engine/credentials.py @@ -32,7 +32,11 @@ from google.oauth2 import _client -class Credentials(credentials.Scoped, credentials.CredentialsWithQuotaProject): +class Credentials( + credentials.Scoped, + credentials.CredentialsWithQuotaProject, + credentials.CredentialsWithUniverseDomain, +): """Compute Engine Credentials. These credentials use the Google Compute Engine metadata server to obtain @@ -57,6 +61,7 @@ def __init__( quota_project_id=None, scopes=None, default_scopes=None, + universe_domain=None, ): """ Args: @@ -68,6 +73,10 @@ def __init__( scopes (Optional[Sequence[str]]): The list of scopes for the credentials. default_scopes (Optional[Sequence[str]]): Default scopes passed by a Google client library. Use 'scopes' for user-defined scopes. + universe_domain (Optional[str]): The universe domain. If not + provided or None, credential will attempt to fetch the value + from metadata server. If metadata server doesn't have universe + domain endpoint, then the default googleapis.com will be used. """ super(Credentials, self).__init__() self._service_account_email = service_account_email @@ -76,6 +85,9 @@ def __init__( self._default_scopes = default_scopes self._universe_domain_cached = False self._universe_domain_request = google_auth_requests.Request() + if universe_domain: + self._universe_domain = universe_domain + self._universe_domain_cached = True def _retrieve_info(self, request): """Retrieve information about the service account. @@ -146,23 +158,40 @@ def universe_domain(self): @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) def with_quota_project(self, quota_project_id): - return self.__class__( + creds = self.__class__( service_account_email=self._service_account_email, quota_project_id=quota_project_id, scopes=self._scopes, + default_scopes=self._default_scopes, ) + creds._universe_domain = self._universe_domain + creds._universe_domain_cached = self._universe_domain_cached + return creds @_helpers.copy_docstring(credentials.Scoped) def with_scopes(self, scopes, default_scopes=None): # Compute Engine credentials can not be scoped (the metadata service # ignores the scopes parameter). App Engine, Cloud Run and Flex support # requesting scopes. - return self.__class__( + creds = self.__class__( scopes=scopes, default_scopes=default_scopes, service_account_email=self._service_account_email, quota_project_id=self._quota_project_id, ) + creds._universe_domain = self._universe_domain + creds._universe_domain_cached = self._universe_domain_cached + return creds + + @_helpers.copy_docstring(credentials.CredentialsWithUniverseDomain) + def with_universe_domain(self, universe_domain): + return self.__class__( + scopes=self._scopes, + default_scopes=self._default_scopes, + service_account_email=self._service_account_email, + quota_project_id=self._quota_project_id, + universe_domain=universe_domain, + ) _DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds diff --git a/packages/google-auth/tests/compute_engine/test_credentials.py b/packages/google-auth/tests/compute_engine/test_credentials.py index 5d6ccdcdec6c..f04bb1304a81 100644 --- a/packages/google-auth/tests/compute_engine/test_credentials.py +++ b/packages/google-auth/tests/compute_engine/test_credentials.py @@ -50,13 +50,27 @@ "gl-python/3.7 auth/1.1 auth-request-type/it cred-type/mds" ) +FAKE_SERVICE_ACCOUNT_EMAIL = "foo@bar.com" +FAKE_QUOTA_PROJECT_ID = "fake-quota-project" +FAKE_SCOPES = ["scope1", "scope2"] +FAKE_DEFAULT_SCOPES = ["scope3", "scope4"] +FAKE_UNIVERSE_DOMAIN = "fake-universe-domain" + class TestCredentials(object): credentials = None + credentials_with_all_fields = None @pytest.fixture(autouse=True) def credentials_fixture(self): self.credentials = credentials.Credentials() + self.credentials_with_all_fields = credentials.Credentials( + service_account_email=FAKE_SERVICE_ACCOUNT_EMAIL, + quota_project_id=FAKE_QUOTA_PROJECT_ID, + scopes=FAKE_SCOPES, + default_scopes=FAKE_DEFAULT_SCOPES, + universe_domain=FAKE_UNIVERSE_DOMAIN, + ) def test_default_state(self): assert not self.credentials.valid @@ -68,6 +82,9 @@ def test_default_state(self): assert self.credentials.service_account_email == "default" # No quota project assert not self.credentials._quota_project_id + # Universe domain is the default and not cached + assert self.credentials._universe_domain == "googleapis.com" + assert not self.credentials._universe_domain_cached @mock.patch( "google.auth._helpers.utcnow", @@ -187,17 +204,35 @@ def test_before_request_refreshes(self, get): assert self.credentials.valid def test_with_quota_project(self): - quota_project_creds = self.credentials.with_quota_project("project-foo") + creds = self.credentials_with_all_fields.with_quota_project("project-foo") - assert quota_project_creds._quota_project_id == "project-foo" + assert creds._quota_project_id == "project-foo" + assert creds._service_account_email == FAKE_SERVICE_ACCOUNT_EMAIL + assert creds._scopes == FAKE_SCOPES + assert creds._default_scopes == FAKE_DEFAULT_SCOPES + assert creds.universe_domain == FAKE_UNIVERSE_DOMAIN + assert creds._universe_domain_cached def test_with_scopes(self): - assert self.credentials._scopes is None - scopes = ["one", "two"] - self.credentials = self.credentials.with_scopes(scopes) + creds = self.credentials_with_all_fields.with_scopes(scopes) - assert self.credentials._scopes == scopes + assert creds._scopes == scopes + assert creds._quota_project_id == FAKE_QUOTA_PROJECT_ID + assert creds._service_account_email == FAKE_SERVICE_ACCOUNT_EMAIL + assert creds._default_scopes is None + assert creds.universe_domain == FAKE_UNIVERSE_DOMAIN + assert creds._universe_domain_cached + + def test_with_universe_domain(self): + creds = self.credentials_with_all_fields.with_universe_domain("universe_domain") + + assert creds._scopes == FAKE_SCOPES + assert creds._quota_project_id == FAKE_QUOTA_PROJECT_ID + assert creds._service_account_email == FAKE_SERVICE_ACCOUNT_EMAIL + assert creds._default_scopes == FAKE_DEFAULT_SCOPES + assert creds.universe_domain == "universe_domain" + assert creds._universe_domain_cached def test_token_usage_metrics(self): self.credentials.token = "token" @@ -213,8 +248,9 @@ def test_token_usage_metrics(self): return_value="fake_universe_domain", ) def test_universe_domain(self, get_universe_domain): - self.credentials._universe_domain_cached = False - self.credentials._universe_domain = "googleapis.com" + # Check the default state + assert not self.credentials._universe_domain_cached + assert self.credentials._universe_domain == "googleapis.com" # calling the universe_domain property should trigger a call to # get_universe_domain to fetch the value. The value should be cached. @@ -232,6 +268,15 @@ def test_universe_domain(self, get_universe_domain): self.credentials._universe_domain_request ) + @mock.patch("google.auth.compute_engine._metadata.get_universe_domain") + def test_user_provided_universe_domain(self, get_universe_domain): + assert self.credentials_with_all_fields.universe_domain == FAKE_UNIVERSE_DOMAIN + assert self.credentials_with_all_fields._universe_domain_cached + + # Since user provided universe_domain, we will not call the universe + # domain endpoint. + get_universe_domain.assert_not_called() + class TestIDTokenCredentials(object): credentials = None From a6788a9640ac8303d00f5c1e9582da7e6cd9f12c Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Wed, 24 Jan 2024 10:39:33 -0800 Subject: [PATCH 802/966] chore(main): release 2.27.0 (#1457) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 13 +++++++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index ad386157c4d6..209bedf4c61a 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,19 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.27.0](https://github.com/googleapis/google-auth-library-python/compare/v2.26.2...v2.27.0) (2024-01-24) + + +### Features + +* Add optional account association for Authorized User credentials. ([#1458](https://github.com/googleapis/google-auth-library-python/issues/1458)) ([988153d](https://github.com/googleapis/google-auth-library-python/commit/988153dbc927de112969b952e3d0bb6edc27bb0a)) + + +### Bug Fixes + +* Allow custom universe domain for gce creds ([#1460](https://github.com/googleapis/google-auth-library-python/issues/1460)) ([7db5823](https://github.com/googleapis/google-auth-library-python/commit/7db5823b599150c597e67b0a350e04600ea6434e)) +* Conditionally import requests only if no request was passed by the caller. ([#1456](https://github.com/googleapis/google-auth-library-python/issues/1456)) ([9cd6742](https://github.com/googleapis/google-auth-library-python/commit/9cd67425e95faab15e57b258a70506b02bccb799)) + ## [2.26.2](https://github.com/googleapis/google-auth-library-python/compare/v2.26.1...v2.26.2) (2024-01-11) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 6d53c4c41187..e1fa722c8110 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.26.2" +__version__ = "2.27.0" From 1d8095c137e27d1964e25e69cb9c589cf7edbdd9 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Wed, 31 Jan 2024 13:30:20 -0800 Subject: [PATCH 803/966] test: add mds universe domain test for empty response (#1461) * chore: refresh sys test cred * test: add mds universe domain test for empty response --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../tests/compute_engine/test__metadata.py | 13 +++++++++++++ 2 files changed, 13 insertions(+) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 78e375c054480a8bffde4e716319d765c199b32c..73820b67234aefd1044ca48627b59c2732e9e935 100644 GIT binary patch literal 10324 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 literal 10324 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 Date: Mon, 5 Feb 2024 14:04:40 -0800 Subject: [PATCH 804/966] chore: update token (#1462) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10323 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 73820b67234aefd1044ca48627b59c2732e9e935..00c6dc0e0737349c81e6ea734fe210fe007a88b2 100644 GIT binary patch literal 10323 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 literal 10324 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 From 969808fa7fedfa1d6551d144f95bbeb89a0ea6fc Mon Sep 17 00:00:00 2001 From: Jin Date: Thu, 8 Feb 2024 14:26:16 -0800 Subject: [PATCH 805/966] Token update (#1470) * chore: update token * chore: token update --- .../google-auth/system_tests/secrets.tar.enc | Bin 10323 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 00c6dc0e0737349c81e6ea734fe210fe007a88b2..80f59b04f12543235ef198caf9252c5f01777a2a 100644 GIT binary patch literal 10324 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 From 91d3c659a7bc8c766537cc96bc303f8a1bc33889 Mon Sep 17 00:00:00 2001 From: Jin Date: Thu, 8 Feb 2024 15:46:11 -0800 Subject: [PATCH 806/966] feat: adding universe domain support for downscroped credentials (#1463) * feat: adding universe domain support for downscroped credentials * fix lint * address comments * Update tests/test_downscoped.py Co-authored-by: Leo <39062083+lsirac@users.noreply.github.com> --------- Co-authored-by: Leo <39062083+lsirac@users.noreply.github.com> --- .../google-auth/google/auth/credentials.py | 4 +- .../google-auth/google/auth/downscoped.py | 14 ++- packages/google-auth/tests/test_downscoped.py | 88 ++++++++++++++++++- 3 files changed, 98 insertions(+), 8 deletions(-) diff --git a/packages/google-auth/google/auth/credentials.py b/packages/google-auth/google/auth/credentials.py index a4fa1829c720..27abd443dc09 100644 --- a/packages/google-auth/google/auth/credentials.py +++ b/packages/google-auth/google/auth/credentials.py @@ -24,6 +24,8 @@ from google.auth import metrics from google.auth._refresh_worker import RefreshThreadManager +DEFAULT_UNIVERSE_DOMAIN = "googleapis.com" + class Credentials(metaclass=abc.ABCMeta): """Base class for all credentials. @@ -57,7 +59,7 @@ def __init__(self): """Optional[dict]: Cache of a trust boundary response which has a list of allowed regions and an encoded string representation of credentials trust boundary.""" - self._universe_domain = "googleapis.com" + self._universe_domain = DEFAULT_UNIVERSE_DOMAIN """Optional[str]: The universe domain value, default is googleapis.com """ diff --git a/packages/google-auth/google/auth/downscoped.py b/packages/google-auth/google/auth/downscoped.py index b4d9d386e504..ea75be90fe4e 100644 --- a/packages/google-auth/google/auth/downscoped.py +++ b/packages/google-auth/google/auth/downscoped.py @@ -63,7 +63,7 @@ # The token exchange requested_token_type. This is always an access_token. _STS_REQUESTED_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token" # The STS token URL used to exchanged a short lived access token for a downscoped one. -_STS_TOKEN_URL = "https://sts.googleapis.com/v1/token" +_STS_TOKEN_URL_PATTERN = "https://sts.{}/v1/token" # The subject token type to use when exchanging a short lived access token for a # downscoped token. _STS_SUBJECT_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token" @@ -437,7 +437,11 @@ class Credentials(credentials.CredentialsWithQuotaProject): """ def __init__( - self, source_credentials, credential_access_boundary, quota_project_id=None + self, + source_credentials, + credential_access_boundary, + quota_project_id=None, + universe_domain=credentials.DEFAULT_UNIVERSE_DOMAIN, ): """Instantiates a downscoped credentials object using the provided source credentials and credential access boundary rules. @@ -456,6 +460,7 @@ def __init__( the upper bound of the permissions that are available on that resource and an optional condition to further restrict permissions. quota_project_id (Optional[str]): The optional quota project ID. + universe_domain (Optional[str]): The universe domain value, default is googleapis.com Raises: google.auth.exceptions.RefreshError: If the source credentials return an error on token refresh. @@ -467,7 +472,10 @@ def __init__( self._source_credentials = source_credentials self._credential_access_boundary = credential_access_boundary self._quota_project_id = quota_project_id - self._sts_client = sts.Client(_STS_TOKEN_URL) + self._universe_domain = universe_domain or credentials.DEFAULT_UNIVERSE_DOMAIN + self._sts_client = sts.Client( + _STS_TOKEN_URL_PATTERN.format(self.universe_domain) + ) @_helpers.copy_docstring(credentials.Credentials) def refresh(self, request): diff --git a/packages/google-auth/tests/test_downscoped.py b/packages/google-auth/tests/test_downscoped.py index 8cc2a30d1637..fe6e291c7561 100644 --- a/packages/google-auth/tests/test_downscoped.py +++ b/packages/google-auth/tests/test_downscoped.py @@ -25,6 +25,7 @@ from google.auth import downscoped from google.auth import exceptions from google.auth import transport +from google.auth.credentials import DEFAULT_UNIVERSE_DOMAIN from google.auth.credentials import TokenState @@ -447,7 +448,11 @@ def test_to_json(self): class TestCredentials(object): @staticmethod - def make_credentials(source_credentials=SourceCredentials(), quota_project_id=None): + def make_credentials( + source_credentials=SourceCredentials(), + quota_project_id=None, + universe_domain=None, + ): availability_condition = make_availability_condition( EXPRESSION, TITLE, DESCRIPTION ) @@ -458,7 +463,10 @@ def make_credentials(source_credentials=SourceCredentials(), quota_project_id=No credential_access_boundary = make_credential_access_boundary(rules) return downscoped.Credentials( - source_credentials, credential_access_boundary, quota_project_id + source_credentials, + credential_access_boundary, + quota_project_id, + universe_domain, ) @staticmethod @@ -473,10 +481,12 @@ def make_mock_request(data, status=http_client.OK): return request @staticmethod - def assert_request_kwargs(request_kwargs, headers, request_data): + def assert_request_kwargs( + request_kwargs, headers, request_data, token_endpoint=TOKEN_EXCHANGE_ENDPOINT + ): """Asserts the request was called with the expected parameters. """ - assert request_kwargs["url"] == TOKEN_EXCHANGE_ENDPOINT + assert request_kwargs["url"] == token_endpoint assert request_kwargs["method"] == "POST" assert request_kwargs["headers"] == headers assert request_kwargs["body"] is not None @@ -496,6 +506,33 @@ def test_default_state(self): assert not credentials.expired # No quota project ID set. assert not credentials.quota_project_id + assert credentials.universe_domain == DEFAULT_UNIVERSE_DOMAIN + + def test_default_state_with_explicit_none_value(self): + credentials = self.make_credentials(universe_domain=None) + + # No token acquired yet. + assert not credentials.token + assert not credentials.valid + # Expiration hasn't been set yet. + assert not credentials.expiry + assert not credentials.expired + # No quota project ID set. + assert not credentials.quota_project_id + assert credentials.universe_domain == DEFAULT_UNIVERSE_DOMAIN + + def test_create_with_customized_universe_domain(self): + test_universe_domain = "foo.com" + credentials = self.make_credentials(universe_domain=test_universe_domain) + # No token acquired yet. + assert not credentials.token + assert not credentials.valid + # Expiration hasn't been set yet. + assert not credentials.expiry + assert not credentials.expired + # No quota project ID set. + assert not credentials.quota_project_id + assert credentials.universe_domain == test_universe_domain def test_with_quota_project(self): credentials = self.make_credentials() @@ -506,6 +543,49 @@ def test_with_quota_project(self): assert quota_project_creds.quota_project_id == "project-foo" + @mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) + def test_refresh_on_custom_universe(self, unused_utcnow): + test_universe_domain = "foo.com" + response = SUCCESS_RESPONSE.copy() + # Test custom expiration to confirm expiry is set correctly. + response["expires_in"] = 2800 + expected_expiry = datetime.datetime.min + datetime.timedelta( + seconds=response["expires_in"] + ) + headers = {"Content-Type": "application/x-www-form-urlencoded"} + request_data = { + "grant_type": GRANT_TYPE, + "subject_token": "ACCESS_TOKEN_1", + "subject_token_type": SUBJECT_TOKEN_TYPE, + "requested_token_type": REQUESTED_TOKEN_TYPE, + "options": urllib.parse.quote(json.dumps(CREDENTIAL_ACCESS_BOUNDARY_JSON)), + } + request = self.make_mock_request(status=http_client.OK, data=response) + source_credentials = SourceCredentials() + credentials = self.make_credentials( + source_credentials=source_credentials, universe_domain=test_universe_domain + ) + token_exchange_endpoint = downscoped._STS_TOKEN_URL_PATTERN.format( + test_universe_domain + ) + + # Spy on calls to source credentials refresh to confirm the expected request + # instance is used. + with mock.patch.object( + source_credentials, "refresh", wraps=source_credentials.refresh + ) as wrapped_souce_cred_refresh: + credentials.refresh(request) + + self.assert_request_kwargs( + request.call_args[1], headers, request_data, token_exchange_endpoint + ) + assert credentials.valid + assert credentials.expiry == expected_expiry + assert not credentials.expired + assert credentials.token == response["access_token"] + # Confirm source credentials called with the same request instance. + wrapped_souce_cred_refresh.assert_called_with(request) + @mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min) def test_refresh(self, unused_utcnow): response = SUCCESS_RESPONSE.copy() From 8d0fcad7b70dd7af1abb5760e46bb7a3ac0ed110 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Gryta?= Date: Fri, 9 Feb 2024 18:08:21 +0100 Subject: [PATCH 807/966] fix: Change log level to debug for return_none_for_not_found_error (#1473) --- packages/google-auth/google/auth/compute_engine/_metadata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/compute_engine/_metadata.py b/packages/google-auth/google/auth/compute_engine/_metadata.py index 1c884c3c43e3..108cbfe93296 100644 --- a/packages/google-auth/google/auth/compute_engine/_metadata.py +++ b/packages/google-auth/google/auth/compute_engine/_metadata.py @@ -222,7 +222,7 @@ def get( content = _helpers.from_bytes(response.data) if response.status == http_client.NOT_FOUND and return_none_for_not_found_error: - _LOGGER.info( + _LOGGER.debug( "Compute Engine Metadata server call to %s returned 404, reason: %s", path, content, From 84719ccca99c38cf010bad6fc954ac4ef6debbf1 Mon Sep 17 00:00:00 2001 From: Jin Date: Mon, 12 Feb 2024 11:33:27 -0800 Subject: [PATCH 808/966] chore: token update (#1474) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 80f59b04f12543235ef198caf9252c5f01777a2a..d9c77ce94600f156f58d530c3c4a6617bc4d64cd 100644 GIT binary patch literal 10324 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 z Date: Tue, 13 Feb 2024 11:24:51 -0800 Subject: [PATCH 809/966] chore: refactoring common used constant from the same source (#1472) --- .../google/auth/external_account.py | 10 +++--- .../auth/external_account_authorized_user.py | 9 +++--- .../google-auth/google/oauth2/credentials.py | 11 ++++--- .../google/oauth2/service_account.py | 31 ++++++++++++------- .../tests/oauth2/test_service_account.py | 11 ++++--- packages/google-auth/tests/test_aws.py | 3 +- .../tests/test_external_account.py | 14 ++++----- .../test_external_account_authorized_user.py | 2 +- .../google-auth/tests/test_identity_pool.py | 4 +-- packages/google-auth/tests/test_pluggable.py | 2 +- 10 files changed, 52 insertions(+), 45 deletions(-) diff --git a/packages/google-auth/google/auth/external_account.py b/packages/google-auth/google/auth/external_account.py index c314ea799ea6..0420883f86b4 100644 --- a/packages/google-auth/google/auth/external_account.py +++ b/packages/google-auth/google/auth/external_account.py @@ -51,8 +51,6 @@ # Cloud resource manager URL used to retrieve project information. _CLOUD_RESOURCE_MANAGER = "https://cloudresourcemanager.googleapis.com/v1/projects/" -_DEFAULT_UNIVERSE_DOMAIN = "googleapis.com" - class Credentials( credentials.Scoped, @@ -83,7 +81,7 @@ def __init__( scopes=None, default_scopes=None, workforce_pool_user_project=None, - universe_domain=_DEFAULT_UNIVERSE_DOMAIN, + universe_domain=credentials.DEFAULT_UNIVERSE_DOMAIN, trust_boundary=None, ): """Instantiates an external account credentials object. @@ -131,7 +129,7 @@ def __init__( self._scopes = scopes self._default_scopes = default_scopes self._workforce_pool_user_project = workforce_pool_user_project - self._universe_domain = universe_domain or _DEFAULT_UNIVERSE_DOMAIN + self._universe_domain = universe_domain or credentials.DEFAULT_UNIVERSE_DOMAIN self._trust_boundary = { "locations": [], "encoded_locations": "0x0", @@ -513,7 +511,9 @@ def from_info(cls, info, **kwargs): credential_source=info.get("credential_source"), quota_project_id=info.get("quota_project_id"), workforce_pool_user_project=info.get("workforce_pool_user_project"), - universe_domain=info.get("universe_domain", _DEFAULT_UNIVERSE_DOMAIN), + universe_domain=info.get( + "universe_domain", credentials.DEFAULT_UNIVERSE_DOMAIN + ), **kwargs ) diff --git a/packages/google-auth/google/auth/external_account_authorized_user.py b/packages/google-auth/google/auth/external_account_authorized_user.py index 526588f7e85d..f73387172c26 100644 --- a/packages/google-auth/google/auth/external_account_authorized_user.py +++ b/packages/google-auth/google/auth/external_account_authorized_user.py @@ -43,7 +43,6 @@ from google.oauth2 import sts from google.oauth2 import utils -_DEFAULT_UNIVERSE_DOMAIN = "googleapis.com" _EXTERNAL_ACCOUNT_AUTHORIZED_USER_JSON_TYPE = "external_account_authorized_user" @@ -76,7 +75,7 @@ def __init__( revoke_url=None, scopes=None, quota_project_id=None, - universe_domain=_DEFAULT_UNIVERSE_DOMAIN, + universe_domain=credentials.DEFAULT_UNIVERSE_DOMAIN, ): """Instantiates a external account authorized user credentials object. @@ -120,7 +119,7 @@ def __init__( self._revoke_url = revoke_url self._quota_project_id = quota_project_id self._scopes = scopes - self._universe_domain = universe_domain or _DEFAULT_UNIVERSE_DOMAIN + self._universe_domain = universe_domain or credentials.DEFAULT_UNIVERSE_DOMAIN if not self.valid and not self.can_refresh: raise exceptions.InvalidOperation( @@ -342,7 +341,9 @@ def from_info(cls, info, **kwargs): revoke_url=info.get("revoke_url"), quota_project_id=info.get("quota_project_id"), scopes=info.get("scopes"), - universe_domain=info.get("universe_domain", _DEFAULT_UNIVERSE_DOMAIN), + universe_domain=info.get( + "universe_domain", credentials.DEFAULT_UNIVERSE_DOMAIN + ), **kwargs ) diff --git a/packages/google-auth/google/oauth2/credentials.py b/packages/google-auth/google/oauth2/credentials.py index c239beed1347..5ca00d4c5a58 100644 --- a/packages/google-auth/google/oauth2/credentials.py +++ b/packages/google-auth/google/oauth2/credentials.py @@ -49,7 +49,6 @@ # The Google OAuth 2.0 token endpoint. Used for authorized user credentials. _GOOGLE_OAUTH2_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token" -_DEFAULT_UNIVERSE_DOMAIN = "googleapis.com" class Credentials(credentials.ReadOnlyScoped, credentials.CredentialsWithQuotaProject): @@ -86,7 +85,7 @@ def __init__( enable_reauth_refresh=False, granted_scopes=None, trust_boundary=None, - universe_domain=_DEFAULT_UNIVERSE_DOMAIN, + universe_domain=credentials.DEFAULT_UNIVERSE_DOMAIN, account=None, ): """ @@ -150,7 +149,7 @@ def __init__( self.refresh_handler = refresh_handler self._enable_reauth_refresh = enable_reauth_refresh self._trust_boundary = trust_boundary - self._universe_domain = universe_domain or _DEFAULT_UNIVERSE_DOMAIN + self._universe_domain = universe_domain or credentials.DEFAULT_UNIVERSE_DOMAIN self._account = account or "" def __getstate__(self): @@ -187,7 +186,9 @@ def __setstate__(self, d): self._rapt_token = d.get("_rapt_token") self._enable_reauth_refresh = d.get("_enable_reauth_refresh") self._trust_boundary = d.get("_trust_boundary") - self._universe_domain = d.get("_universe_domain") or _DEFAULT_UNIVERSE_DOMAIN + self._universe_domain = ( + d.get("_universe_domain") or credentials.DEFAULT_UNIVERSE_DOMAIN + ) # The refresh_handler setter should be used to repopulate this. self._refresh_handler = None self._refresh_worker = None @@ -373,7 +374,7 @@ def _metric_header_for_usage(self): @_helpers.copy_docstring(credentials.Credentials) def refresh(self, request): - if self._universe_domain != _DEFAULT_UNIVERSE_DOMAIN: + if self._universe_domain != credentials.DEFAULT_UNIVERSE_DOMAIN: raise exceptions.RefreshError( "User credential refresh is only supported in the default " "googleapis.com universe domain, but the current universe " diff --git a/packages/google-auth/google/oauth2/service_account.py b/packages/google-auth/google/oauth2/service_account.py index 4502c6f68c60..04fd7797ada3 100644 --- a/packages/google-auth/google/oauth2/service_account.py +++ b/packages/google-auth/google/oauth2/service_account.py @@ -82,7 +82,6 @@ from google.oauth2 import _client _DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds -_DEFAULT_UNIVERSE_DOMAIN = "googleapis.com" _GOOGLE_OAUTH2_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token" @@ -139,7 +138,7 @@ def __init__( quota_project_id=None, additional_claims=None, always_use_jwt_access=False, - universe_domain=_DEFAULT_UNIVERSE_DOMAIN, + universe_domain=credentials.DEFAULT_UNIVERSE_DOMAIN, trust_boundary=None, ): """ @@ -182,9 +181,9 @@ def __init__( self._quota_project_id = quota_project_id self._token_uri = token_uri self._always_use_jwt_access = always_use_jwt_access - self._universe_domain = universe_domain or _DEFAULT_UNIVERSE_DOMAIN + self._universe_domain = universe_domain or credentials.DEFAULT_UNIVERSE_DOMAIN - if universe_domain != _DEFAULT_UNIVERSE_DOMAIN: + if universe_domain != credentials.DEFAULT_UNIVERSE_DOMAIN: self._always_use_jwt_access = True self._jwt_credentials = None @@ -216,7 +215,9 @@ def _from_signer_and_info(cls, signer, info, **kwargs): service_account_email=info["client_email"], token_uri=info["token_uri"], project_id=info.get("project_id"), - universe_domain=info.get("universe_domain", _DEFAULT_UNIVERSE_DOMAIN), + universe_domain=info.get( + "universe_domain", credentials.DEFAULT_UNIVERSE_DOMAIN + ), trust_boundary=info.get("trust_boundary"), **kwargs ) @@ -316,7 +317,7 @@ def with_always_use_jwt_access(self, always_use_jwt_access): """ cred = self._make_copy() if ( - cred._universe_domain != _DEFAULT_UNIVERSE_DOMAIN + cred._universe_domain != credentials.DEFAULT_UNIVERSE_DOMAIN and not always_use_jwt_access ): raise exceptions.InvalidValue( @@ -329,7 +330,7 @@ def with_always_use_jwt_access(self, always_use_jwt_access): def with_universe_domain(self, universe_domain): cred = self._make_copy() cred._universe_domain = universe_domain - if universe_domain != _DEFAULT_UNIVERSE_DOMAIN: + if universe_domain != credentials.DEFAULT_UNIVERSE_DOMAIN: cred._always_use_jwt_access = True return cred @@ -427,7 +428,10 @@ def refresh(self, request): # created, try to create one with scopes self._create_self_signed_jwt(None) - if self._universe_domain != _DEFAULT_UNIVERSE_DOMAIN and self._subject: + if ( + self._universe_domain != credentials.DEFAULT_UNIVERSE_DOMAIN + and self._subject + ): raise exceptions.RefreshError( "domain wide delegation is not supported for non-default universe domain" ) @@ -556,7 +560,7 @@ def __init__( target_audience, additional_claims=None, quota_project_id=None, - universe_domain=_DEFAULT_UNIVERSE_DOMAIN, + universe_domain=credentials.DEFAULT_UNIVERSE_DOMAIN, ): """ Args: @@ -588,11 +592,11 @@ def __init__( self._use_iam_endpoint = False if not universe_domain: - self._universe_domain = _DEFAULT_UNIVERSE_DOMAIN + self._universe_domain = credentials.DEFAULT_UNIVERSE_DOMAIN else: self._universe_domain = universe_domain - if universe_domain != _DEFAULT_UNIVERSE_DOMAIN: + if universe_domain != credentials.DEFAULT_UNIVERSE_DOMAIN: self._use_iam_endpoint = True if additional_claims is not None: @@ -708,7 +712,10 @@ def _with_use_iam_endpoint(self, use_iam_endpoint): default and use_iam_endpoint is False. """ cred = self._make_copy() - if cred._universe_domain != _DEFAULT_UNIVERSE_DOMAIN and not use_iam_endpoint: + if ( + cred._universe_domain != credentials.DEFAULT_UNIVERSE_DOMAIN + and not use_iam_endpoint + ): raise exceptions.InvalidValue( "use_iam_endpoint should be True for non-default universe domain" ) diff --git a/packages/google-auth/tests/oauth2/test_service_account.py b/packages/google-auth/tests/oauth2/test_service_account.py index ebaab05fccfa..b0adf8d0014d 100644 --- a/packages/google-auth/tests/oauth2/test_service_account.py +++ b/packages/google-auth/tests/oauth2/test_service_account.py @@ -24,6 +24,7 @@ from google.auth import exceptions from google.auth import jwt from google.auth import transport +from google.auth.credentials import DEFAULT_UNIVERSE_DOMAIN from google.oauth2 import service_account @@ -58,7 +59,7 @@ class TestCredentials(object): TOKEN_URI = "https://example.com/oauth2/token" @classmethod - def make_credentials(cls, universe_domain=service_account._DEFAULT_UNIVERSE_DOMAIN): + def make_credentials(cls, universe_domain=DEFAULT_UNIVERSE_DOMAIN): return service_account.Credentials( SIGNER, cls.SERVICE_ACCOUNT_EMAIL, @@ -70,7 +71,7 @@ def test_constructor_no_universe_domain(self): credentials = service_account.Credentials( SIGNER, self.SERVICE_ACCOUNT_EMAIL, self.TOKEN_URI, universe_domain=None ) - assert credentials.universe_domain == service_account._DEFAULT_UNIVERSE_DOMAIN + assert credentials.universe_domain == DEFAULT_UNIVERSE_DOMAIN def test_from_service_account_info(self): credentials = service_account.Credentials.from_service_account_info( @@ -80,7 +81,7 @@ def test_from_service_account_info(self): assert credentials._signer.key_id == SERVICE_ACCOUNT_INFO["private_key_id"] assert credentials.service_account_email == SERVICE_ACCOUNT_INFO["client_email"] assert credentials._token_uri == SERVICE_ACCOUNT_INFO["token_uri"] - assert credentials._universe_domain == service_account._DEFAULT_UNIVERSE_DOMAIN + assert credentials._universe_domain == DEFAULT_UNIVERSE_DOMAIN assert not credentials._always_use_jwt_access def test_from_service_account_info_non_gdu(self): @@ -595,7 +596,7 @@ class TestIDTokenCredentials(object): TARGET_AUDIENCE = "https://example.com" @classmethod - def make_credentials(cls, universe_domain=service_account._DEFAULT_UNIVERSE_DOMAIN): + def make_credentials(cls, universe_domain=DEFAULT_UNIVERSE_DOMAIN): return service_account.IDTokenCredentials( SIGNER, cls.SERVICE_ACCOUNT_EMAIL, @@ -612,7 +613,7 @@ def test_constructor_no_universe_domain(self): self.TARGET_AUDIENCE, universe_domain=None, ) - assert credentials._universe_domain == service_account._DEFAULT_UNIVERSE_DOMAIN + assert credentials._universe_domain == DEFAULT_UNIVERSE_DOMAIN def test_from_service_account_info(self): credentials = service_account.IDTokenCredentials.from_service_account_info( diff --git a/packages/google-auth/tests/test_aws.py b/packages/google-auth/tests/test_aws.py index db2e984100f6..3f358d52b097 100644 --- a/packages/google-auth/tests/test_aws.py +++ b/packages/google-auth/tests/test_aws.py @@ -26,7 +26,7 @@ from google.auth import environment_vars from google.auth import exceptions from google.auth import transport - +from google.auth.credentials import DEFAULT_UNIVERSE_DOMAIN IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE = ( "gl-python/3.7 auth/1.1 auth-request-type/at cred-type/imp" @@ -75,7 +75,6 @@ # Each tuple contains the following entries: # region, time, credentials, original_request, signed_request -DEFAULT_UNIVERSE_DOMAIN = "googleapis.com" VALID_TOKEN_URLS = [ "https://sts.googleapis.com", "https://us-east-1.sts.googleapis.com", diff --git a/packages/google-auth/tests/test_external_account.py b/packages/google-auth/tests/test_external_account.py index 7f33b1dfa2a9..03a5014ce5b5 100644 --- a/packages/google-auth/tests/test_external_account.py +++ b/packages/google-auth/tests/test_external_account.py @@ -24,9 +24,9 @@ from google.auth import exceptions from google.auth import external_account from google.auth import transport +from google.auth.credentials import DEFAULT_UNIVERSE_DOMAIN from google.auth.credentials import TokenState - IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE = ( "gl-python/3.7 auth/1.1 auth-request-type/at cred-type/imp" ) @@ -150,7 +150,7 @@ def make_credentials( default_scopes=None, service_account_impersonation_url=None, service_account_impersonation_options={}, - universe_domain=external_account._DEFAULT_UNIVERSE_DOMAIN, + universe_domain=DEFAULT_UNIVERSE_DOMAIN, ): return CredentialsImpl( audience=cls.AUDIENCE, @@ -386,7 +386,7 @@ def test_with_scopes_full_options_propagated(self): quota_project_id=self.QUOTA_PROJECT_ID, scopes=["email"], default_scopes=["default2"], - universe_domain=external_account._DEFAULT_UNIVERSE_DOMAIN, + universe_domain=DEFAULT_UNIVERSE_DOMAIN, ) def test_with_token_uri(self): @@ -474,7 +474,7 @@ def test_with_quota_project_full_options_propagated(self): quota_project_id="project-foo", scopes=self.SCOPES, default_scopes=["default1"], - universe_domain=external_account._DEFAULT_UNIVERSE_DOMAIN, + universe_domain=DEFAULT_UNIVERSE_DOMAIN, ) def test_with_invalid_impersonation_target_principal(self): @@ -504,7 +504,7 @@ def test_universe_domain(self): assert credentials.universe_domain == "dummy_universe.com" credentials = self.make_credentials() - assert credentials.universe_domain == external_account._DEFAULT_UNIVERSE_DOMAIN + assert credentials.universe_domain == DEFAULT_UNIVERSE_DOMAIN def test_with_universe_domain(self): credentials = self.make_credentials() @@ -523,7 +523,7 @@ def test_info_workforce_pool(self): "token_url": self.TOKEN_URL, "credential_source": self.CREDENTIAL_SOURCE.copy(), "workforce_pool_user_project": self.WORKFORCE_POOL_USER_PROJECT, - "universe_domain": external_account._DEFAULT_UNIVERSE_DOMAIN, + "universe_domain": DEFAULT_UNIVERSE_DOMAIN, } def test_info_with_full_options(self): @@ -548,7 +548,7 @@ def test_info_with_full_options(self): "quota_project_id": self.QUOTA_PROJECT_ID, "client_id": CLIENT_ID, "client_secret": CLIENT_SECRET, - "universe_domain": external_account._DEFAULT_UNIVERSE_DOMAIN, + "universe_domain": DEFAULT_UNIVERSE_DOMAIN, } def test_service_account_email_without_impersonation(self): diff --git a/packages/google-auth/tests/test_external_account_authorized_user.py b/packages/google-auth/tests/test_external_account_authorized_user.py index 7213a23486c1..743ee9c848d7 100644 --- a/packages/google-auth/tests/test_external_account_authorized_user.py +++ b/packages/google-auth/tests/test_external_account_authorized_user.py @@ -22,6 +22,7 @@ from google.auth import exceptions from google.auth import external_account_authorized_user from google.auth import transport +from google.auth.credentials import DEFAULT_UNIVERSE_DOMAIN TOKEN_URL = "https://sts.googleapis.com/v1/token" TOKEN_INFO_URL = "https://sts.googleapis.com/v1/introspect" @@ -45,7 +46,6 @@ SCOPES = ["email", "profile"] NOW = datetime.datetime(1990, 8, 27, 6, 54, 30) FAKE_UNIVERSE_DOMAIN = "fake-universe-domain" -DEFAULT_UNIVERSE_DOMAIN = external_account_authorized_user._DEFAULT_UNIVERSE_DOMAIN class TestCredentials(object): diff --git a/packages/google-auth/tests/test_identity_pool.py b/packages/google-auth/tests/test_identity_pool.py index 8821df088d6f..be30c4e9b410 100644 --- a/packages/google-auth/tests/test_identity_pool.py +++ b/packages/google-auth/tests/test_identity_pool.py @@ -26,7 +26,7 @@ from google.auth import identity_pool from google.auth import metrics from google.auth import transport - +from google.auth.credentials import DEFAULT_UNIVERSE_DOMAIN CLIENT_ID = "username" CLIENT_SECRET = "password" @@ -67,8 +67,6 @@ WORKFORCE_SUBJECT_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:id_token" WORKFORCE_POOL_USER_PROJECT = "WORKFORCE_POOL_USER_PROJECT_NUMBER" -DEFAULT_UNIVERSE_DOMAIN = "googleapis.com" - VALID_TOKEN_URLS = [ "https://sts.googleapis.com", "https://us-east-1.sts.googleapis.com", diff --git a/packages/google-auth/tests/test_pluggable.py b/packages/google-auth/tests/test_pluggable.py index 1773b40d409f..6bee054c5b1d 100644 --- a/packages/google-auth/tests/test_pluggable.py +++ b/packages/google-auth/tests/test_pluggable.py @@ -21,6 +21,7 @@ from google.auth import exceptions from google.auth import pluggable +from google.auth.credentials import DEFAULT_UNIVERSE_DOMAIN from tests.test__default import WORKFORCE_AUDIENCE CLIENT_ID = "username" @@ -45,7 +46,6 @@ TOKEN_INFO_URL = "https://sts.googleapis.com/v1/introspect" SUBJECT_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:jwt" AUDIENCE = "//iam.googleapis.com/projects/123456/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID" -DEFAULT_UNIVERSE_DOMAIN = "googleapis.com" VALID_TOKEN_URLS = [ "https://sts.googleapis.com", From 342647092d858b4108f09c3273111862b882d668 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Wed, 14 Feb 2024 23:43:03 -0800 Subject: [PATCH 810/966] chore: refresh sys test cred (#1477) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index d9c77ce94600f156f58d530c3c4a6617bc4d64cd..506ef1415a8cf6abaa9ba998175cbb69f240dacc 100644 GIT binary patch literal 10324 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 literal 10324 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 m_Ru_u7ipSY7`YS?-6#X6<+K`eg+0XQAvsASTV8 Date: Thu, 15 Feb 2024 10:17:38 -0800 Subject: [PATCH 811/966] fix: make requests import conditional for gce universe domain (#1476) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: make requests import conditional for gce universe domain * remove if for creating request object * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --------- Co-authored-by: Owl Bot --- .../google-auth/google/auth/compute_engine/credentials.py | 7 ++++--- .../google-auth/tests/compute_engine/test_credentials.py | 8 ++------ 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/packages/google-auth/google/auth/compute_engine/credentials.py b/packages/google-auth/google/auth/compute_engine/credentials.py index 7541c1d8cfa9..008b991bb957 100644 --- a/packages/google-auth/google/auth/compute_engine/credentials.py +++ b/packages/google-auth/google/auth/compute_engine/credentials.py @@ -28,7 +28,6 @@ from google.auth import jwt from google.auth import metrics from google.auth.compute_engine import _metadata -from google.auth.transport import requests as google_auth_requests from google.oauth2 import _client @@ -84,7 +83,6 @@ def __init__( self._scopes = scopes self._default_scopes = default_scopes self._universe_domain_cached = False - self._universe_domain_request = google_auth_requests.Request() if universe_domain: self._universe_domain = universe_domain self._universe_domain_cached = True @@ -150,8 +148,11 @@ def requires_scopes(self): def universe_domain(self): if self._universe_domain_cached: return self._universe_domain + + from google.auth.transport import requests as google_auth_requests + self._universe_domain = _metadata.get_universe_domain( - self._universe_domain_request + google_auth_requests.Request() ) self._universe_domain_cached = True return self._universe_domain diff --git a/packages/google-auth/tests/compute_engine/test_credentials.py b/packages/google-auth/tests/compute_engine/test_credentials.py index f04bb1304a81..9cca317924e4 100644 --- a/packages/google-auth/tests/compute_engine/test_credentials.py +++ b/packages/google-auth/tests/compute_engine/test_credentials.py @@ -257,16 +257,12 @@ def test_universe_domain(self, get_universe_domain): assert self.credentials.universe_domain == "fake_universe_domain" assert self.credentials._universe_domain == "fake_universe_domain" assert self.credentials._universe_domain_cached - get_universe_domain.assert_called_once_with( - self.credentials._universe_domain_request - ) + get_universe_domain.assert_called_once() # calling the universe_domain property the second time should use the # cached value instead of calling get_universe_domain assert self.credentials.universe_domain == "fake_universe_domain" - get_universe_domain.assert_called_once_with( - self.credentials._universe_domain_request - ) + get_universe_domain.assert_called_once() @mock.patch("google.auth.compute_engine._metadata.get_universe_domain") def test_user_provided_universe_domain(self, get_universe_domain): From 55ee4fc0a5c959cb6ce49b9b4c451894f57f3399 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 15 Feb 2024 19:18:34 +0000 Subject: [PATCH 812/966] chore(main): release 2.28.0 (#1471) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 13 +++++++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 209bedf4c61a..98658da9cecc 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,19 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.28.0](https://github.com/googleapis/google-auth-library-python/compare/v2.27.0...v2.28.0) (2024-02-15) + + +### Features + +* Adding universe domain support for downscroped credentials ([#1463](https://github.com/googleapis/google-auth-library-python/issues/1463)) ([fa8b7b2](https://github.com/googleapis/google-auth-library-python/commit/fa8b7b24ec32712aafff98c2d6c6a6cc5fd20ada)) + + +### Bug Fixes + +* Change log level to debug for return_none_for_not_found_error ([#1473](https://github.com/googleapis/google-auth-library-python/issues/1473)) ([a036b47](https://github.com/googleapis/google-auth-library-python/commit/a036b4797471227f9ce87d71868404c9ebcde7c7)) +* Make requests import conditional for gce universe domain ([#1476](https://github.com/googleapis/google-auth-library-python/issues/1476)) ([9bb64c8](https://github.com/googleapis/google-auth-library-python/commit/9bb64c81d2745541efcc3dfeec31808ec682557e)) + ## [2.27.0](https://github.com/googleapis/google-auth-library-python/compare/v2.26.2...v2.27.0) (2024-01-24) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index e1fa722c8110..9672a6c4127c 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.27.0" +__version__ = "2.28.0" From 194f7b41b181788b274490ef11cb94448d83c319 Mon Sep 17 00:00:00 2001 From: Carl Lundin <108372512+clundin25@users.noreply.github.com> Date: Wed, 21 Feb 2024 08:40:26 -0800 Subject: [PATCH 813/966] fix: Typo when setting the state for the pickle deserializer. (#1479) * fix: Typo when setting the state for the pickle deserializer. --- .../google/auth/_refresh_worker.py | 2 +- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10323 bytes .../google-auth/tests/test__refresh_worker.py | 3 +++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/_refresh_worker.py b/packages/google-auth/google/auth/_refresh_worker.py index 9bb0ccc2c598..674032d8496b 100644 --- a/packages/google-auth/google/auth/_refresh_worker.py +++ b/packages/google-auth/google/auth/_refresh_worker.py @@ -75,7 +75,7 @@ def __getstate__(self): def __setstate__(self, state): """Pickle helper that deserializes the _lock attribute.""" - state["_key"] = threading.Lock() + state["_lock"] = threading.Lock() self.__dict__.update(state) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 506ef1415a8cf6abaa9ba998175cbb69f240dacc..79c9bb6b63ad562170bfc49b004a41ae57669d47 100644 GIT binary patch literal 10323 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 diff --git a/packages/google-auth/tests/test__refresh_worker.py b/packages/google-auth/tests/test__refresh_worker.py index f842b02cac49..c25965d10b93 100644 --- a/packages/google-auth/tests/test__refresh_worker.py +++ b/packages/google-auth/tests/test__refresh_worker.py @@ -150,7 +150,10 @@ def test_refresh_dead_worker(): def test_pickle(): w = _refresh_worker.RefreshThreadManager() + # For some reason isinstance cannot interpret threading.Lock as a type. + assert w._lock is not None pickled_manager = pickle.dumps(w) manager = pickle.loads(pickled_manager) assert isinstance(manager, _refresh_worker.RefreshThreadManager) + assert manager._lock is not None From 76b2780b1dfcbf1b0d0181eabf4c320b9a3a465c Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Wed, 21 Feb 2024 10:27:17 -0800 Subject: [PATCH 814/966] chore(main): release 2.28.1 (#1482) * chore(main): release 2.28.1 --------- Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> Co-authored-by: Carl Lundin --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/google/auth/version.py | 2 +- .../google-auth/system_tests/secrets.tar.enc | Bin 10323 -> 10324 bytes 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 98658da9cecc..7d49aa78355c 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.28.1](https://github.com/googleapis/google-auth-library-python/compare/v2.28.0...v2.28.1) (2024-02-21) + + +### Bug Fixes + +* Typo when setting the state for the pickle deserializer. ([#1479](https://github.com/googleapis/google-auth-library-python/issues/1479)) ([08b5cc3](https://github.com/googleapis/google-auth-library-python/commit/08b5cc38d09d388a5a7c11942b7992870ee012b7)) + ## [2.28.0](https://github.com/googleapis/google-auth-library-python/compare/v2.27.0...v2.28.0) (2024-02-15) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 9672a6c4127c..7580efbee524 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.28.0" +__version__ = "2.28.1" diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 79c9bb6b63ad562170bfc49b004a41ae57669d47..78432137847421468ef13d92df902bfd831e38b5 100644 GIT binary patch literal 10324 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 literal 10323 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 Date: Wed, 6 Mar 2024 10:53:39 -0500 Subject: [PATCH 815/966] build(deps): bump cryptography from 41.0.6 to 42.0.0 in /synthtool/gcp/templates/python_library/.kokoro (#1466) Source-Link: https://github.com/googleapis/synthtool/commit/e13b22b1f660c80e4c3e735a9177d2f16c4b8bdc Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:97b671488ad548ef783a452a9e1276ac10f144d5ae56d98cc4bf77ba504082b4 Co-authored-by: Owl Bot --- .../google-auth/.github/.OwlBot.lock.yaml | 6 +- packages/google-auth/.kokoro/requirements.txt | 63 +++++++++++-------- 2 files changed, 39 insertions(+), 30 deletions(-) diff --git a/packages/google-auth/.github/.OwlBot.lock.yaml b/packages/google-auth/.github/.OwlBot.lock.yaml index 773c1dfd2146..2aefd0e91175 100644 --- a/packages/google-auth/.github/.OwlBot.lock.yaml +++ b/packages/google-auth/.github/.OwlBot.lock.yaml @@ -1,4 +1,4 @@ -# Copyright 2023 Google LLC +# Copyright 2024 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,5 +13,5 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:2f155882785883336b4468d5218db737bb1d10c9cea7cb62219ad16fe248c03c -# created: 2023-11-29T14:54:29.548172703Z + digest: sha256:97b671488ad548ef783a452a9e1276ac10f144d5ae56d98cc4bf77ba504082b4 +# created: 2024-02-06T03:20:16.660474034Z diff --git a/packages/google-auth/.kokoro/requirements.txt b/packages/google-auth/.kokoro/requirements.txt index e5c1ffca94b7..8c11c9f3e9b6 100644 --- a/packages/google-auth/.kokoro/requirements.txt +++ b/packages/google-auth/.kokoro/requirements.txt @@ -93,30 +93,39 @@ colorlog==6.7.0 \ # via # gcp-docuploader # nox -cryptography==41.0.6 \ - --hash=sha256:068bc551698c234742c40049e46840843f3d98ad7ce265fd2bd4ec0d11306596 \ - --hash=sha256:0f27acb55a4e77b9be8d550d762b0513ef3fc658cd3eb15110ebbcbd626db12c \ - --hash=sha256:2132d5865eea673fe6712c2ed5fb4fa49dba10768bb4cc798345748380ee3660 \ - --hash=sha256:3288acccef021e3c3c10d58933f44e8602cf04dba96d9796d70d537bb2f4bbc4 \ - --hash=sha256:35f3f288e83c3f6f10752467c48919a7a94b7d88cc00b0668372a0d2ad4f8ead \ - --hash=sha256:398ae1fc711b5eb78e977daa3cbf47cec20f2c08c5da129b7a296055fbb22aed \ - --hash=sha256:422e3e31d63743855e43e5a6fcc8b4acab860f560f9321b0ee6269cc7ed70cc3 \ - --hash=sha256:48783b7e2bef51224020efb61b42704207dde583d7e371ef8fc2a5fb6c0aabc7 \ - --hash=sha256:4d03186af98b1c01a4eda396b137f29e4e3fb0173e30f885e27acec8823c1b09 \ - --hash=sha256:5daeb18e7886a358064a68dbcaf441c036cbdb7da52ae744e7b9207b04d3908c \ - --hash=sha256:60e746b11b937911dc70d164060d28d273e31853bb359e2b2033c9e93e6f3c43 \ - --hash=sha256:742ae5e9a2310e9dade7932f9576606836ed174da3c7d26bc3d3ab4bd49b9f65 \ - --hash=sha256:7e00fb556bda398b99b0da289ce7053639d33b572847181d6483ad89835115f6 \ - --hash=sha256:85abd057699b98fce40b41737afb234fef05c67e116f6f3650782c10862c43da \ - --hash=sha256:8efb2af8d4ba9dbc9c9dd8f04d19a7abb5b49eab1f3694e7b5a16a5fc2856f5c \ - --hash=sha256:ae236bb8760c1e55b7a39b6d4d32d2279bc6c7c8500b7d5a13b6fb9fc97be35b \ - --hash=sha256:afda76d84b053923c27ede5edc1ed7d53e3c9f475ebaf63c68e69f1403c405a8 \ - --hash=sha256:b27a7fd4229abef715e064269d98a7e2909ebf92eb6912a9603c7e14c181928c \ - --hash=sha256:b648fe2a45e426aaee684ddca2632f62ec4613ef362f4d681a9a6283d10e079d \ - --hash=sha256:c5a550dc7a3b50b116323e3d376241829fd326ac47bc195e04eb33a8170902a9 \ - --hash=sha256:da46e2b5df770070412c46f87bac0849b8d685c5f2679771de277a422c7d0b86 \ - --hash=sha256:f39812f70fc5c71a15aa3c97b2bbe213c3f2a460b79bd21c40d033bb34a9bf36 \ - --hash=sha256:ff369dd19e8fe0528b02e8df9f2aeb2479f89b1270d90f96a63500afe9af5cae +cryptography==42.0.0 \ + --hash=sha256:0a68bfcf57a6887818307600c3c0ebc3f62fbb6ccad2240aa21887cda1f8df1b \ + --hash=sha256:146e971e92a6dd042214b537a726c9750496128453146ab0ee8971a0299dc9bd \ + --hash=sha256:14e4b909373bc5bf1095311fa0f7fcabf2d1a160ca13f1e9e467be1ac4cbdf94 \ + --hash=sha256:206aaf42e031b93f86ad60f9f5d9da1b09164f25488238ac1dc488334eb5e221 \ + --hash=sha256:3005166a39b70c8b94455fdbe78d87a444da31ff70de3331cdec2c568cf25b7e \ + --hash=sha256:324721d93b998cb7367f1e6897370644751e5580ff9b370c0a50dc60a2003513 \ + --hash=sha256:33588310b5c886dfb87dba5f013b8d27df7ffd31dc753775342a1e5ab139e59d \ + --hash=sha256:35cf6ed4c38f054478a9df14f03c1169bb14bd98f0b1705751079b25e1cb58bc \ + --hash=sha256:3ca482ea80626048975360c8e62be3ceb0f11803180b73163acd24bf014133a0 \ + --hash=sha256:56ce0c106d5c3fec1038c3cca3d55ac320a5be1b44bf15116732d0bc716979a2 \ + --hash=sha256:5a217bca51f3b91971400890905a9323ad805838ca3fa1e202a01844f485ee87 \ + --hash=sha256:678cfa0d1e72ef41d48993a7be75a76b0725d29b820ff3cfd606a5b2b33fda01 \ + --hash=sha256:69fd009a325cad6fbfd5b04c711a4da563c6c4854fc4c9544bff3088387c77c0 \ + --hash=sha256:6cf9b76d6e93c62114bd19485e5cb003115c134cf9ce91f8ac924c44f8c8c3f4 \ + --hash=sha256:74f18a4c8ca04134d2052a140322002fef535c99cdbc2a6afc18a8024d5c9d5b \ + --hash=sha256:85f759ed59ffd1d0baad296e72780aa62ff8a71f94dc1ab340386a1207d0ea81 \ + --hash=sha256:87086eae86a700307b544625e3ba11cc600c3c0ef8ab97b0fda0705d6db3d4e3 \ + --hash=sha256:8814722cffcfd1fbd91edd9f3451b88a8f26a5fd41b28c1c9193949d1c689dc4 \ + --hash=sha256:8fedec73d590fd30c4e3f0d0f4bc961aeca8390c72f3eaa1a0874d180e868ddf \ + --hash=sha256:9515ea7f596c8092fdc9902627e51b23a75daa2c7815ed5aa8cf4f07469212ec \ + --hash=sha256:988b738f56c665366b1e4bfd9045c3efae89ee366ca3839cd5af53eaa1401bce \ + --hash=sha256:a2a8d873667e4fd2f34aedab02ba500b824692c6542e017075a2efc38f60a4c0 \ + --hash=sha256:bd7cf7a8d9f34cc67220f1195884151426ce616fdc8285df9054bfa10135925f \ + --hash=sha256:bdce70e562c69bb089523e75ef1d9625b7417c6297a76ac27b1b8b1eb51b7d0f \ + --hash=sha256:be14b31eb3a293fc6e6aa2807c8a3224c71426f7c4e3639ccf1a2f3ffd6df8c3 \ + --hash=sha256:be41b0c7366e5549265adf2145135dca107718fa44b6e418dc7499cfff6b4689 \ + --hash=sha256:c310767268d88803b653fffe6d6f2f17bb9d49ffceb8d70aed50ad45ea49ab08 \ + --hash=sha256:c58115384bdcfe9c7f644c72f10f6f42bed7cf59f7b52fe1bf7ae0a622b3a139 \ + --hash=sha256:c640b0ef54138fde761ec99a6c7dc4ce05e80420262c20fa239e694ca371d434 \ + --hash=sha256:ca20550bb590db16223eb9ccc5852335b48b8f597e2f6f0878bbfd9e7314eb17 \ + --hash=sha256:d97aae66b7de41cdf5b12087b5509e4e9805ed6f562406dfcf60e8481a9a28f8 \ + --hash=sha256:e9326ca78111e4c645f7e49cbce4ed2f3f85e17b61a563328c85a5208cf34440 # via # gcp-releasetool # secretstorage @@ -263,9 +272,9 @@ jeepney==0.8.0 \ # via # keyring # secretstorage -jinja2==3.1.2 \ - --hash=sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852 \ - --hash=sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61 +jinja2==3.1.3 \ + --hash=sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa \ + --hash=sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90 # via gcp-releasetool keyring==24.2.0 \ --hash=sha256:4901caaf597bfd3bbd78c9a0c7c4c29fcd8310dab2cffefe749e916b6527acd6 \ From 7c1dcb7bb5516680ae4822db5b130da6ed9fb736 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Fri, 8 Mar 2024 10:25:10 -0800 Subject: [PATCH 816/966] fix: remove gce log for expected 404 (#1491) --- .../google/auth/compute_engine/_metadata.py | 5 ----- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 2 files changed, 5 deletions(-) diff --git a/packages/google-auth/google/auth/compute_engine/_metadata.py b/packages/google-auth/google/auth/compute_engine/_metadata.py index 108cbfe93296..e597365851c9 100644 --- a/packages/google-auth/google/auth/compute_engine/_metadata.py +++ b/packages/google-auth/google/auth/compute_engine/_metadata.py @@ -222,11 +222,6 @@ def get( content = _helpers.from_bytes(response.data) if response.status == http_client.NOT_FOUND and return_none_for_not_found_error: - _LOGGER.debug( - "Compute Engine Metadata server call to %s returned 404, reason: %s", - path, - content, - ) return None if response.status == http_client.OK: diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 78432137847421468ef13d92df902bfd831e38b5..bcdb96cea16696ca47618058b29731a3cc64e32c 100644 GIT binary patch literal 10324 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 literal 10324 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 From caf2fa0ea4bdc94ae2687144ce9783ae68f57462 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Fri, 8 Mar 2024 11:15:12 -0800 Subject: [PATCH 817/966] chore(main): release 2.28.2 (#1492) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- packages/google-auth/CHANGELOG.md | 7 +++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 7d49aa78355c..0f64fdf342ae 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.28.2](https://github.com/googleapis/google-auth-library-python/compare/v2.28.1...v2.28.2) (2024-03-08) + + +### Bug Fixes + +* Remove gce log for expected 404 ([#1491](https://github.com/googleapis/google-auth-library-python/issues/1491)) ([cb04e49](https://github.com/googleapis/google-auth-library-python/commit/cb04e49efa17004bff3aaa9ed974396d10f839b5)) + ## [2.28.1](https://github.com/googleapis/google-auth-library-python/compare/v2.28.0...v2.28.1) (2024-02-21) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 7580efbee524..0959c754195e 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.28.1" +__version__ = "2.28.2" From 3895a3214c52258f224d97b4ac4dd44c816a9a0e Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Mon, 11 Mar 2024 15:54:40 +0000 Subject: [PATCH 818/966] build(deps): bump cryptography from 42.0.2 to 42.0.4 in .kokoro (#1483) Source-Link: https://togithub.com/googleapis/synthtool/commit/d895aec3679ad22aa120481f746bf9f2f325f26f Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:98f3afd11308259de6e828e37376d18867fd321aba07826e29e4f8d9cab56bad --- .../google-auth/.github/.OwlBot.lock.yaml | 4 +- packages/google-auth/.kokoro/requirements.txt | 66 +++++++++--------- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 3 files changed, 35 insertions(+), 35 deletions(-) diff --git a/packages/google-auth/.github/.OwlBot.lock.yaml b/packages/google-auth/.github/.OwlBot.lock.yaml index 2aefd0e91175..e4e943e0259a 100644 --- a/packages/google-auth/.github/.OwlBot.lock.yaml +++ b/packages/google-auth/.github/.OwlBot.lock.yaml @@ -13,5 +13,5 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:97b671488ad548ef783a452a9e1276ac10f144d5ae56d98cc4bf77ba504082b4 -# created: 2024-02-06T03:20:16.660474034Z + digest: sha256:98f3afd11308259de6e828e37376d18867fd321aba07826e29e4f8d9cab56bad +# created: 2024-02-27T15:56:18.442440378Z diff --git a/packages/google-auth/.kokoro/requirements.txt b/packages/google-auth/.kokoro/requirements.txt index 8c11c9f3e9b6..bda8e38c4f31 100644 --- a/packages/google-auth/.kokoro/requirements.txt +++ b/packages/google-auth/.kokoro/requirements.txt @@ -93,39 +93,39 @@ colorlog==6.7.0 \ # via # gcp-docuploader # nox -cryptography==42.0.0 \ - --hash=sha256:0a68bfcf57a6887818307600c3c0ebc3f62fbb6ccad2240aa21887cda1f8df1b \ - --hash=sha256:146e971e92a6dd042214b537a726c9750496128453146ab0ee8971a0299dc9bd \ - --hash=sha256:14e4b909373bc5bf1095311fa0f7fcabf2d1a160ca13f1e9e467be1ac4cbdf94 \ - --hash=sha256:206aaf42e031b93f86ad60f9f5d9da1b09164f25488238ac1dc488334eb5e221 \ - --hash=sha256:3005166a39b70c8b94455fdbe78d87a444da31ff70de3331cdec2c568cf25b7e \ - --hash=sha256:324721d93b998cb7367f1e6897370644751e5580ff9b370c0a50dc60a2003513 \ - --hash=sha256:33588310b5c886dfb87dba5f013b8d27df7ffd31dc753775342a1e5ab139e59d \ - --hash=sha256:35cf6ed4c38f054478a9df14f03c1169bb14bd98f0b1705751079b25e1cb58bc \ - --hash=sha256:3ca482ea80626048975360c8e62be3ceb0f11803180b73163acd24bf014133a0 \ - --hash=sha256:56ce0c106d5c3fec1038c3cca3d55ac320a5be1b44bf15116732d0bc716979a2 \ - --hash=sha256:5a217bca51f3b91971400890905a9323ad805838ca3fa1e202a01844f485ee87 \ - --hash=sha256:678cfa0d1e72ef41d48993a7be75a76b0725d29b820ff3cfd606a5b2b33fda01 \ - --hash=sha256:69fd009a325cad6fbfd5b04c711a4da563c6c4854fc4c9544bff3088387c77c0 \ - --hash=sha256:6cf9b76d6e93c62114bd19485e5cb003115c134cf9ce91f8ac924c44f8c8c3f4 \ - --hash=sha256:74f18a4c8ca04134d2052a140322002fef535c99cdbc2a6afc18a8024d5c9d5b \ - --hash=sha256:85f759ed59ffd1d0baad296e72780aa62ff8a71f94dc1ab340386a1207d0ea81 \ - --hash=sha256:87086eae86a700307b544625e3ba11cc600c3c0ef8ab97b0fda0705d6db3d4e3 \ - --hash=sha256:8814722cffcfd1fbd91edd9f3451b88a8f26a5fd41b28c1c9193949d1c689dc4 \ - --hash=sha256:8fedec73d590fd30c4e3f0d0f4bc961aeca8390c72f3eaa1a0874d180e868ddf \ - --hash=sha256:9515ea7f596c8092fdc9902627e51b23a75daa2c7815ed5aa8cf4f07469212ec \ - --hash=sha256:988b738f56c665366b1e4bfd9045c3efae89ee366ca3839cd5af53eaa1401bce \ - --hash=sha256:a2a8d873667e4fd2f34aedab02ba500b824692c6542e017075a2efc38f60a4c0 \ - --hash=sha256:bd7cf7a8d9f34cc67220f1195884151426ce616fdc8285df9054bfa10135925f \ - --hash=sha256:bdce70e562c69bb089523e75ef1d9625b7417c6297a76ac27b1b8b1eb51b7d0f \ - --hash=sha256:be14b31eb3a293fc6e6aa2807c8a3224c71426f7c4e3639ccf1a2f3ffd6df8c3 \ - --hash=sha256:be41b0c7366e5549265adf2145135dca107718fa44b6e418dc7499cfff6b4689 \ - --hash=sha256:c310767268d88803b653fffe6d6f2f17bb9d49ffceb8d70aed50ad45ea49ab08 \ - --hash=sha256:c58115384bdcfe9c7f644c72f10f6f42bed7cf59f7b52fe1bf7ae0a622b3a139 \ - --hash=sha256:c640b0ef54138fde761ec99a6c7dc4ce05e80420262c20fa239e694ca371d434 \ - --hash=sha256:ca20550bb590db16223eb9ccc5852335b48b8f597e2f6f0878bbfd9e7314eb17 \ - --hash=sha256:d97aae66b7de41cdf5b12087b5509e4e9805ed6f562406dfcf60e8481a9a28f8 \ - --hash=sha256:e9326ca78111e4c645f7e49cbce4ed2f3f85e17b61a563328c85a5208cf34440 +cryptography==42.0.4 \ + --hash=sha256:01911714117642a3f1792c7f376db572aadadbafcd8d75bb527166009c9f1d1b \ + --hash=sha256:0e89f7b84f421c56e7ff69f11c441ebda73b8a8e6488d322ef71746224c20fce \ + --hash=sha256:12d341bd42cdb7d4937b0cabbdf2a94f949413ac4504904d0cdbdce4a22cbf88 \ + --hash=sha256:15a1fb843c48b4a604663fa30af60818cd28f895572386e5f9b8a665874c26e7 \ + --hash=sha256:1cdcdbd117681c88d717437ada72bdd5be9de117f96e3f4d50dab3f59fd9ab20 \ + --hash=sha256:1df6fcbf60560d2113b5ed90f072dc0b108d64750d4cbd46a21ec882c7aefce9 \ + --hash=sha256:3c6048f217533d89f2f8f4f0fe3044bf0b2090453b7b73d0b77db47b80af8dff \ + --hash=sha256:3e970a2119507d0b104f0a8e281521ad28fc26f2820687b3436b8c9a5fcf20d1 \ + --hash=sha256:44a64043f743485925d3bcac548d05df0f9bb445c5fcca6681889c7c3ab12764 \ + --hash=sha256:4e36685cb634af55e0677d435d425043967ac2f3790ec652b2b88ad03b85c27b \ + --hash=sha256:5f8907fcf57392cd917892ae83708761c6ff3c37a8e835d7246ff0ad251d9298 \ + --hash=sha256:69b22ab6506a3fe483d67d1ed878e1602bdd5912a134e6202c1ec672233241c1 \ + --hash=sha256:6bfadd884e7280df24d26f2186e4e07556a05d37393b0f220a840b083dc6a824 \ + --hash=sha256:6d0fbe73728c44ca3a241eff9aefe6496ab2656d6e7a4ea2459865f2e8613257 \ + --hash=sha256:6ffb03d419edcab93b4b19c22ee80c007fb2d708429cecebf1dd3258956a563a \ + --hash=sha256:810bcf151caefc03e51a3d61e53335cd5c7316c0a105cc695f0959f2c638b129 \ + --hash=sha256:831a4b37accef30cccd34fcb916a5d7b5be3cbbe27268a02832c3e450aea39cb \ + --hash=sha256:887623fe0d70f48ab3f5e4dbf234986b1329a64c066d719432d0698522749929 \ + --hash=sha256:a0298bdc6e98ca21382afe914c642620370ce0470a01e1bef6dd9b5354c36854 \ + --hash=sha256:a1327f280c824ff7885bdeef8578f74690e9079267c1c8bd7dc5cc5aa065ae52 \ + --hash=sha256:c1f25b252d2c87088abc8bbc4f1ecbf7c919e05508a7e8628e6875c40bc70923 \ + --hash=sha256:c3a5cbc620e1e17009f30dd34cb0d85c987afd21c41a74352d1719be33380885 \ + --hash=sha256:ce8613beaffc7c14f091497346ef117c1798c202b01153a8cc7b8e2ebaaf41c0 \ + --hash=sha256:d2a27aca5597c8a71abbe10209184e1a8e91c1fd470b5070a2ea60cafec35bcd \ + --hash=sha256:dad9c385ba8ee025bb0d856714f71d7840020fe176ae0229de618f14dae7a6e2 \ + --hash=sha256:db4b65b02f59035037fde0998974d84244a64c3265bdef32a827ab9b63d61b18 \ + --hash=sha256:e09469a2cec88fb7b078e16d4adec594414397e8879a4341c6ace96013463d5b \ + --hash=sha256:e53dc41cda40b248ebc40b83b31516487f7db95ab8ceac1f042626bc43a2f992 \ + --hash=sha256:f1e85a178384bf19e36779d91ff35c7617c885da487d689b05c1366f9933ad74 \ + --hash=sha256:f47be41843200f7faec0683ad751e5ef11b9a56a220d57f300376cd8aba81660 \ + --hash=sha256:fb0cef872d8193e487fc6bdb08559c3aa41b659a7d9be48b2e10747f47863925 \ + --hash=sha256:ffc73996c4fca3d2b6c1c8c12bfd3ad00def8621da24f547626bf06441400449 # via # gcp-releasetool # secretstorage diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index bcdb96cea16696ca47618058b29731a3cc64e32c..f18f973152ac7e7b52a712f9f3e8d24f32396fec 100644 GIT binary patch literal 10324 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 From 9ab72a8d59d70732a45d82933a0dae5fb6e1ae6c Mon Sep 17 00:00:00 2001 From: Jin Date: Thu, 14 Mar 2024 09:52:15 -0700 Subject: [PATCH 819/966] chore: update token (#1497) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index f18f973152ac7e7b52a712f9f3e8d24f32396fec..b5f624b67ec419206a4941e688edc6394f067ab5 100644 GIT binary patch literal 10324 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 Date: Fri, 15 Mar 2024 09:18:01 -0700 Subject: [PATCH 820/966] feat: Adds support for custom suppliers in AWS and Identity Pool credentials (#1496) * feat: refactor AWS and identity pool credentials to use suppliers (#1484) * feat: refactor aws and identity pool credentials to use supplier framework * Linting * changing class types * linting * remove unused import * Fix typing * add docstring and fix casing * feat: Adds support for custom suppliers in AWS and Identity Pool credential instantiation (#1494) * feat: refactor aws and identity pool credentials to use supplier framework * Linting * changing class types * linting * remove unused import * Fix typing * add docstring and fix casing * feat: adds support for passing suppliers to credentials. * fixes merge issues and adds _has_custom_supplier method * adds _has_custom_supplier function to identity_pool * Update google/auth/external_account.py Co-authored-by: Carl Lundin <108372512+clundin25@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Carl Lundin <108372512+clundin25@users.noreply.github.com> * Respond to comments and fix docs --------- Co-authored-by: Carl Lundin <108372512+clundin25@users.noreply.github.com> * docs: add documentation for suppliers (#1495) * docs: update docs for programmatic * add space * update user guide * update docs * Apply suggestions from code review Co-authored-by: Leo <39062083+lsirac@users.noreply.github.com> * Update docs * Add docs about context and request --------- Co-authored-by: Carl Lundin <108372512+clundin25@users.noreply.github.com> Co-authored-by: Leo <39062083+lsirac@users.noreply.github.com> --- packages/google-auth/docs/user-guide.rst | 177 ++++- packages/google-auth/google/auth/aws.py | 610 ++++++++++-------- .../google/auth/external_account.py | 51 +- .../google-auth/google/auth/identity_pool.py | 274 +++++--- packages/google-auth/tests/test_aws.py | 498 +++++++++++--- .../tests/test_external_account.py | 32 +- .../google-auth/tests/test_identity_pool.py | 165 ++++- 7 files changed, 1338 insertions(+), 469 deletions(-) diff --git a/packages/google-auth/docs/user-guide.rst b/packages/google-auth/docs/user-guide.rst index 0cb119127c32..05516393eb04 100644 --- a/packages/google-auth/docs/user-guide.rst +++ b/packages/google-auth/docs/user-guide.rst @@ -212,7 +212,7 @@ There is a separate library, `google-auth-oauthlib`_, that has some helpers for integrating with `requests-oauthlib`_ to provide support for obtaining user credentials. You can use :func:`google_auth_oauthlib.helpers.credentials_from_session` to obtain -:class:`google.oauth2.credentials.Credentials` from a +:class:`google.oauth2.credentials.Credentials` from a :class:`requests_oauthlib.OAuth2Session` as above:: from google_auth_oauthlib.helpers import credentials_from_session @@ -459,9 +459,9 @@ Error responses must include both the ``code`` and ``message`` fields. The library will populate the following environment variables when the executable is run: ``GOOGLE_EXTERNAL_ACCOUNT_AUDIENCE``: The audience -field from the credential configuration. Always present. +field from the credential configuration. Always present. ``GOOGLE_EXTERNAL_ACCOUNT_IMPERSONATED_EMAIL``: The service account -email. Only present when service account impersonation is used. +email. Only present when service account impersonation is used. ``GOOGLE_EXTERNAL_ACCOUNT_OUTPUT_FILE``: The output file location from the credential configuration. Only present when specified in the credential configuration. @@ -486,6 +486,117 @@ they do not meet your specific requirements. You can now `use the Auth library <#using-external-identities>`__ to call Google Cloud resources from an OIDC or SAML provider. + +Accessing resources using a custom supplier with OIDC or SAML +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This library also allows for a custom implementation of :class:`google.auth.identity_pool.SubjectTokenSupplier` +to be specificed when creating a :class:`google.auth.identity_pool.Credential`. The supplier must +return a valid OIDC or SAML2.0 subject token, which will then be exchanged for a +Google Cloud access token. If an error occurs during token retrieval, the supplier +should return a :class:`google.auth.exceptions.RefreshError` and indicate via the error +whether the subject token retrieval is retryable. +Any call to the supplier from the Identity Pool credential will send a :class:`google.auth.external_account.SupplierContext` +object, which contains the requested audience and subject type. Additionally, the credential will +send the :class:`google.auth.transport.requests.Request` passed in the credential refresh call which +can be used to make HTTP requests.:: + + from google.auth import exceptions + from google.auth import identity_pool + + class CustomSubjectTokenSupplier(identity_pool.SubjectTokenSupplier): + + def get_subject_token(self, context, request): + audience = context.audience + subject_token_type = context.subject_token_type + try: + # Attempt to return the valid subject token of the requested type for the requested audience. + except Exception as e: + # If token retrieval fails, raise a refresh error, setting retryable to true if the client should + # attempt to retrieve the subject token again. + raise exceptions.RefreshError(e, retryable=True) + + supplier = CustomSubjectTokenSupplier() + + credentials = identity_pool.Credentials( + AUDIENCE, # Set GCP Audience. + "urn:ietf:params:aws:token-type:jwt", # Set subject token type. + subject_token_supplier=supplier, # Set supplier. + scopes=SCOPES # Set desired scopes. + ) + +Where the `audience`_ is: ``///iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID`` +Where the following variables need to be substituted: + +* ``$PROJECT_NUMBER``: The project number. +* ``$POOL_ID``: The workload pool ID. +* ``$PROVIDER_ID``: The provider ID. + +The values for audience, service account impersonation URL, and any other builder field can also be found +by generating a `credential configuration file with the gcloud CLI`_. + +.. _audience: + https://cloud.google.com/iam/docs/best-practices-for-using-workload-identity-federation#provider-audience +.. _credential configuration file with the gcloud CLI: + https://cloud.google.com/sdk/gcloud/reference/iam/workload-identity-pools/create-cred-config + +Accessing resources using a custom supplier with AWS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This library also allows for a custom implementation of :class:`google.auth.aws.AwsSecurityCredentialsSupplier` +to be specificed when creating a :class:`google.auth.aws.Credential`. The supplier must +return valid AWS security credentials, which will then be exchanged for a +Google Cloud access token. If an error occurs during credential retrieval, the supplier +should return a :class:`google.auth.exceptions.RefreshError` and indicate via the error +whether the credential retrieval is retryable. +Any call to the supplier from the Identity Pool credential will send a :class:`google.auth.external_account.SupplierContext` +object, which contains the requested audience and subject type. Additionally, the credential will +send the :class:`google.auth.transport.requests.Request` passed in the credential refresh call which +can be used to make HTTP requests.:: + + from google.auth import aws + from google.auth import exceptions + + class CustomAwsSecurityCredentialsSupplier(aws.AwsSecurityCredentialsSupplier): + + def get_aws_security_credentials(self, context, request): + audience = context.audience + try: + # Return valid AWS security credentials. These credentials are not cached by + # the google credential, so caching should be implemented in the supplier. + return aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, SESSION_TOKEN) + except Exception as e: + # If credentials retrieval fails, raise a refresh error, setting retryable to true if the client should + # attempt to retrieve the subject token again. + raise exceptions.RefreshError(e, retryable=True) + + def get_aws_region(self, context, request): + # Return active AWS region. + + supplier = CustomAwsSecurityCredentialsSupplier() + + credentials = aws.Credentials( + AUDIENCE, # Set GCP Audience. + "urn:ietf:params:aws:token-type:aws4_request", # Set AWS subject token type. + aws_security_token_supplier=supplier, # Set supplier. + scopes=SCOPES # Set desired scopes. + ) + +Where the `audience`_ is: ``///iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID`` +Where the following variables need to be substituted: + +* ``$PROJECT_NUMBER``: The project number. +* ``$POOL_ID``: The workload pool ID. +* ``$PROVIDER_ID``: The provider ID. + +The values for audience, service account impersonation URL, and any other builder field can also be found +by generating a `credential configuration file with the gcloud CLI`_. + +.. _audience: + https://cloud.google.com/iam/docs/best-practices-for-using-workload-identity-federation#provider-audience +.. _credential configuration file with the gcloud CLI: + https://cloud.google.com/sdk/gcloud/reference/iam/workload-identity-pools/create-cred-config + Using External Identities ~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -774,6 +885,62 @@ Refer to the `using executable-sourced credentials with Workload Identity Federation `__ above for the executable response specification. +Accessing resources using a custom supplier with OIDC or SAML +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This library also allows for a custom implementation of :class:`google.auth.identity_pool.SubjectTokenSupplier` +to be specificed when creating a :class:`google.auth.identity_pool.Credential`. The supplier must +return a valid OIDC or SAML2.0 subject token, which will then be exchanged for a +Google Cloud access token. If an error occurs during token retrieval, the supplier +should return a :class:`google.auth.exceptions.RefreshError` and indicate via the error +whether the subject token retrieval is retryable. +Any call to the supplier from the Identity Pool credential will send a :class:`google.auth.external_account.SupplierContext` +object, which contains the requested audience and subject type. Additionally, the credential will +send the :class:`google.auth.transport.requests.Request` passed in the credential refresh call which +can be used to make HTTP requests.:: + + from google.auth import exceptions + from google.auth import identity_pool + + class CustomSubjectTokenSupplier(identity_pool.SubjectTokenSupplier): + + def get_subject_token(self, context, request): + audience = context.audience + subject_token_type = context.subject_token_type + try: + # Attempt to return the valid subject token of the requested type for the requested audience. + except Exception as e: + # If token retrieval fails, raise a refresh error, setting retryable to true if the client should + # attempt to retrieve the subject token again. + raise exceptions.RefreshError(e, retryable=True) + + + supplier = CustomSubjectTokenSupplier() + + credentials = identity_pool.Credentials( + AUDIENCE, # Set GCP Audience. + "urn:ietf:params:aws:token-type:jwt", # Set subject token type. + subject_token_supplier=supplier, # Set supplier. + scopes=SCOPES, # Set desired scopes. + workforce_pool_user_project=USER_PROJECT # Set workforce pool user project. + ) + +Where the audience is: ``//iam.googleapis.com/locations/global/workforcePools/$WORKFORCE_POOL_ID/providers/$PROVIDER_ID`` +Where the following variables need to be substituted: + +* ``$WORKFORCE_POOL_ID``: The workforce pool ID. +* ``$PROVIDER_ID``: The provider ID. + +and the workforce pool user project is the project number associated with the `workforce pools user project`_. + +The values for audience, service account impersonation URL, and any other builder field can also be found +by generating a `credential configuration file`_ with the gcloud CLI. + +.. _workforce pools user project: + https://cloud.google.com/iam/docs/workforce-identity-federation#workforce-pools-user-project +.. _credential configuration file: + https://cloud.google.com/iam/docs/workforce-obtaining-short-lived-credentials#use_configuration_files_for_sign-in + Security considerations ~~~~~~~~~~~~~~~~~~~~~~~ @@ -814,7 +981,7 @@ Impersonated credentials ++++++++++++++++++++++++ Impersonated Credentials allows one set of credentials issued to a user or service account -to impersonate another. The source credentials must be granted +to impersonate another. The source credentials must be granted the "Service Account Token Creator" IAM role. :: from google.auth import impersonated_credentials @@ -884,7 +1051,7 @@ Token broker :: credential_access_boundary = downscoped.CredentialAccessBoundary( rules=[rule]) - # Retrieve the source credentials via ADC. + # Retrieve the source credentials via ADC. source_credentials, _ = google.auth.default() # Create the downscoped credentials. diff --git a/packages/google-auth/google/auth/aws.py b/packages/google-auth/google/auth/aws.py index 6e0e4e864f1e..14ac8fc9ab6e 100644 --- a/packages/google-auth/google/auth/aws.py +++ b/packages/google-auth/google/auth/aws.py @@ -21,10 +21,11 @@ AWS Credentials are initialized using external_account arguments which are typically loaded from the external credentials JSON file. -Unlike other Credentials that can be initialized with a list of explicit -arguments, secrets or credentials, external account clients use the -environment and hints/guidelines provided by the external_account JSON -file to retrieve credentials and exchange them for Google access tokens. + +This module also provides a definition for an abstract AWS security credentials supplier. +This supplier can be implemented to return valid AWS security credentials and an AWS region +and used to create AWS credentials. The credentials will then call the +supplier instead of using pre-defined methods such as calling the EC2 metadata endpoints. This module also provides a basic implementation of the `AWS Signature Version 4`_ request signing algorithm. @@ -37,6 +38,8 @@ .. _AWS STS GetCallerIdentity: https://docs.aws.amazon.com/STS/latest/APIReference/API_GetCallerIdentity.html """ +import abc +from dataclasses import dataclass import hashlib import hmac import http.client as http_client @@ -44,6 +47,7 @@ import os import posixpath import re +from typing import Optional import urllib from urllib.parse import urljoin @@ -61,6 +65,10 @@ _AWS_SECURITY_TOKEN_HEADER = "x-amz-security-token" # The AWS authorization header name for the auto-generated date. _AWS_DATE_HEADER = "x-amz-date" +# The default AWS regional credential verification URL. +_DEFAULT_AWS_REGIONAL_CREDENTIAL_VERIFICATION_URL = ( + "https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15" +) class RequestSigner(object): @@ -92,8 +100,7 @@ def get_request_options( https://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html Args: - aws_security_credentials (Mapping[str, str]): A dictionary containing - the AWS security credentials. + aws_security_credentials (AWSSecurityCredentials): The AWS security credentials. url (str): The AWS service URL containing the canonical URI and query string. method (str): The HTTP method used to call this API. @@ -105,10 +112,6 @@ def get_request_options( Returns: Mapping[str, str]: The AWS signed request dictionary object. """ - # Get AWS credentials. - access_key = aws_security_credentials.get("access_key_id") - secret_key = aws_security_credentials.get("secret_access_key") - security_token = aws_security_credentials.get("security_token") additional_headers = additional_headers or {} @@ -129,9 +132,7 @@ def get_request_options( canonical_querystring=_get_canonical_querystring(uri.query), method=method, region=self._region_name, - access_key=access_key, - secret_key=secret_key, - security_token=security_token, + aws_security_credentials=aws_security_credentials, request_payload=request_payload, additional_headers=additional_headers, ) @@ -147,8 +148,8 @@ def get_request_options( headers[key] = additional_headers[key] # Add session token if available. - if security_token is not None: - headers[_AWS_SECURITY_TOKEN_HEADER] = security_token + if aws_security_credentials.session_token is not None: + headers[_AWS_SECURITY_TOKEN_HEADER] = aws_security_credentials.session_token signed_request = {"url": url, "method": method, "headers": headers} if request_payload: @@ -233,9 +234,7 @@ def _generate_authentication_header_map( canonical_querystring, method, region, - access_key, - secret_key, - security_token, + aws_security_credentials, request_payload="", additional_headers={}, ): @@ -248,10 +247,7 @@ def _generate_authentication_header_map( canonical_querystring (str): The AWS service URL query string. method (str): The HTTP method used to call this API. region (str): The AWS region. - access_key (str): The AWS access key ID. - secret_key (str): The AWS secret access key. - security_token (Optional[str]): The AWS security session token. This is - available for temporary sessions. + aws_security_credentials (AWSSecurityCredentials): The AWS security credentials. request_payload (Optional[str]): The optional request payload if available. additional_headers (Optional[Mapping[str, str]]): The optional @@ -274,8 +270,10 @@ def _generate_authentication_header_map( for key in additional_headers: full_headers[key.lower()] = additional_headers[key] # Add AWS session token if available. - if security_token is not None: - full_headers[_AWS_SECURITY_TOKEN_HEADER] = security_token + if aws_security_credentials.session_token is not None: + full_headers[ + _AWS_SECURITY_TOKEN_HEADER + ] = aws_security_credentials.session_token # Required headers full_headers["host"] = host @@ -321,14 +319,20 @@ def _generate_authentication_header_map( ) # https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html - signing_key = _get_signing_key(secret_key, date_stamp, region, service_name) + signing_key = _get_signing_key( + aws_security_credentials.secret_access_key, date_stamp, region, service_name + ) signature = hmac.new( signing_key, string_to_sign.encode("utf-8"), hashlib.sha256 ).hexdigest() # https://docs.aws.amazon.com/general/latest/gr/sigv4-add-signature-to-request.html authorization_header = "{} Credential={}/{}, SignedHeaders={}, Signature={}".format( - _AWS_ALGORITHM, access_key, credential_scope, signed_headers, signature + _AWS_ALGORITHM, + aws_security_credentials.access_key_id, + credential_scope, + signed_headers, + signature, ) authentication_header = {"authorization_header": authorization_header} @@ -338,211 +342,112 @@ def _generate_authentication_header_map( return authentication_header -class Credentials(external_account.Credentials): - """AWS external account credentials. - This is used to exchange serialized AWS signature v4 signed requests to - AWS STS GetCallerIdentity service for Google access tokens. - """ - - def __init__( - self, - audience, - subject_token_type, - token_url, - credential_source=None, - *args, - **kwargs - ): - """Instantiates an AWS workload external account credentials object. - - Args: - audience (str): The STS audience field. - subject_token_type (str): The subject token type. - token_url (str): The STS endpoint URL. - credential_source (Mapping): The credential source dictionary used - to provide instructions on how to retrieve external credential - to be exchanged for Google access tokens. - args (List): Optional positional arguments passed into the underlying :meth:`~external_account.Credentials.__init__` method. - kwargs (Mapping): Optional keyword arguments passed into the underlying :meth:`~external_account.Credentials.__init__` method. - - Raises: - google.auth.exceptions.RefreshError: If an error is encountered during - access token retrieval logic. - ValueError: For invalid parameters. +@dataclass +class AwsSecurityCredentials: + """A class that models AWS security credentials with an optional session token. - .. note:: Typically one of the helper constructors - :meth:`from_file` or - :meth:`from_info` are used instead of calling the constructor directly. - """ - super(Credentials, self).__init__( - audience=audience, - subject_token_type=subject_token_type, - token_url=token_url, - credential_source=credential_source, - *args, - **kwargs - ) - credential_source = credential_source or {} - self._environment_id = credential_source.get("environment_id") or "" - self._region_url = credential_source.get("region_url") - self._security_credentials_url = credential_source.get("url") - self._cred_verification_url = credential_source.get( - "regional_cred_verification_url" - ) - self._imdsv2_session_token_url = credential_source.get( - "imdsv2_session_token_url" - ) - self._region = None - self._request_signer = None - self._target_resource = audience + Attributes: + access_key_id (str): The AWS security credentials access key id. + secret_access_key (str): The AWS security credentials secret access key. + session_token (Optional[str]): The optional AWS security credentials session token. This should be set when using temporary credentials. + """ - # Get the environment ID. Currently, only one version supported (v1). - matches = re.match(r"^(aws)([\d]+)$", self._environment_id) - if matches: - env_id, env_version = matches.groups() - else: - env_id, env_version = (None, None) + access_key_id: str + secret_access_key: str + session_token: Optional[str] = None - if env_id != "aws" or self._cred_verification_url is None: - raise exceptions.InvalidResource( - "No valid AWS 'credential_source' provided" - ) - elif int(env_version or "") != 1: - raise exceptions.InvalidValue( - "aws version '{}' is not supported in the current build.".format( - env_version - ) - ) - def retrieve_subject_token(self, request): - """Retrieves the subject token using the credential_source object. - The subject token is a serialized `AWS GetCallerIdentity signed request`_. +class AwsSecurityCredentialsSupplier(metaclass=abc.ABCMeta): + """Base class for AWS security credential suppliers. This can be implemented with custom logic to retrieve + AWS security credentials to exchange for a Google Cloud access token. The AWS external account credential does + not cache the AWS security credentials, so caching logic should be added in the implementation. + """ - The logic is summarized as: + @abc.abstractmethod + def get_aws_security_credentials(self, context, request): + """Returns the AWS security credentials for the requested context. - Retrieve the AWS region from the AWS_REGION or AWS_DEFAULT_REGION - environment variable or from the AWS metadata server availability-zone - if not found in the environment variable. + .. warning: This is not cached by the calling Google credential, so caching logic should be implemented in the supplier. - Check AWS credentials in environment variables. If not found, retrieve - from the AWS metadata server security-credentials endpoint. - - When retrieving AWS credentials from the metadata server - security-credentials endpoint, the AWS role needs to be determined by - calling the security-credentials endpoint without any argument. Then the - credentials can be retrieved via: security-credentials/role_name + Args: + context (google.auth.externalaccount.SupplierContext): The context object + containing information about the requested audience and subject token type. + request (google.auth.transport.Request): The object used to make + HTTP requests. - Generate the signed request to AWS STS GetCallerIdentity action. + Raises: + google.auth.exceptions.RefreshError: If an error is encountered during + security credential retrieval logic. - Inject x-goog-cloud-target-resource into header and serialize the - signed request. This will be the subject-token to pass to GCP STS. + Returns: + AwsSecurityCredentials: The requested AWS security credentials. + """ + raise NotImplementedError("") - .. _AWS GetCallerIdentity signed request: - https://cloud.google.com/iam/docs/access-resources-aws#exchange-token + @abc.abstractmethod + def get_aws_region(self, context, request): + """Returns the AWS region for the requested context. Args: - request (google.auth.transport.Request): A callable used to make + context (google.auth.externalaccount.SupplierContext): The context object + containing information about the requested audience and subject token type. + request (google.auth.transport.Request): The object used to make HTTP requests. - Returns: - str: The retrieved subject token. - """ - # Fetch the session token required to make meta data endpoint calls to aws. - if ( - request is not None - and self._imdsv2_session_token_url is not None - and self._should_use_metadata_server() - ): - headers = {"X-aws-ec2-metadata-token-ttl-seconds": "300"} - imdsv2_session_token_response = request( - url=self._imdsv2_session_token_url, method="PUT", headers=headers - ) + Raises: + google.auth.exceptions.RefreshError: If an error is encountered during + region retrieval logic. - if imdsv2_session_token_response.status != 200: - raise exceptions.RefreshError( - "Unable to retrieve AWS Session Token", - imdsv2_session_token_response.data, - ) + Returns: + str: The AWS region. + """ + raise NotImplementedError("") - imdsv2_session_token = imdsv2_session_token_response.data - else: - imdsv2_session_token = None - # Initialize the request signer if not yet initialized after determining - # the current AWS region. - if self._request_signer is None: - self._region = self._get_region( - request, self._region_url, imdsv2_session_token - ) - self._request_signer = RequestSigner(self._region) +class _DefaultAwsSecurityCredentialsSupplier(AwsSecurityCredentialsSupplier): + """Default implementation of AWS security credentials supplier. Supports retrieving + credentials and region via EC2 metadata endpoints and environment variables. + """ - # Retrieve the AWS security credentials needed to generate the signed - # request. - aws_security_credentials = self._get_security_credentials( - request, imdsv2_session_token - ) - # Generate the signed request to AWS STS GetCallerIdentity API. - # Use the required regional endpoint. Otherwise, the request will fail. - request_options = self._request_signer.get_request_options( - aws_security_credentials, - self._cred_verification_url.replace("{region}", self._region), - "POST", + def __init__(self, credential_source): + self._region_url = credential_source.get("region_url") + self._security_credentials_url = credential_source.get("url") + self._imdsv2_session_token_url = credential_source.get( + "imdsv2_session_token_url" ) - # The GCP STS endpoint expects the headers to be formatted as: - # [ - # {key: 'x-amz-date', value: '...'}, - # {key: 'Authorization', value: '...'}, - # ... - # ] - # And then serialized as: - # quote(json.dumps({ - # url: '...', - # method: 'POST', - # headers: [{key: 'x-amz-date', value: '...'}, ...] - # })) - request_headers = request_options.get("headers") - # The full, canonical resource name of the workload identity pool - # provider, with or without the HTTPS prefix. - # Including this header as part of the signature is recommended to - # ensure data integrity. - request_headers["x-goog-cloud-target-resource"] = self._target_resource - # Serialize AWS signed request. - # Keeping inner keys in sorted order makes testing easier for Python - # versions <=3.5 as the stringified JSON string would have a predictable - # key order. - aws_signed_req = {} - aws_signed_req["url"] = request_options.get("url") - aws_signed_req["method"] = request_options.get("method") - aws_signed_req["headers"] = [] - # Reformat header to GCP STS expected format. - for key in sorted(request_headers.keys()): - aws_signed_req["headers"].append( - {"key": key, "value": request_headers[key]} - ) + @_helpers.copy_docstring(AwsSecurityCredentialsSupplier) + def get_aws_security_credentials(self, context, request): - return urllib.parse.quote( - json.dumps(aws_signed_req, separators=(",", ":"), sort_keys=True) + # Check environment variables for permanent credentials first. + # https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html + env_aws_access_key_id = os.environ.get(environment_vars.AWS_ACCESS_KEY_ID) + env_aws_secret_access_key = os.environ.get( + environment_vars.AWS_SECRET_ACCESS_KEY ) + # This is normally not available for permanent credentials. + env_aws_session_token = os.environ.get(environment_vars.AWS_SESSION_TOKEN) + if env_aws_access_key_id and env_aws_secret_access_key: + return AwsSecurityCredentials( + env_aws_access_key_id, env_aws_secret_access_key, env_aws_session_token + ) - def _get_region(self, request, url, imdsv2_session_token): - """Retrieves the current AWS region from either the AWS_REGION or - AWS_DEFAULT_REGION environment variable or from the AWS metadata server. + imdsv2_session_token = self._get_imdsv2_session_token(request) + role_name = self._get_metadata_role_name(request, imdsv2_session_token) - Args: - request (google.auth.transport.Request): A callable used to make - HTTP requests. - url (str): The AWS metadata server region URL. - imdsv2_session_token (str): The AWS IMDSv2 session token to be added as a - header in the requests to AWS metadata endpoint. + # Get security credentials. + credentials = self._get_metadata_security_credentials( + request, role_name, imdsv2_session_token + ) - Returns: - str: The current AWS region. + return AwsSecurityCredentials( + credentials.get("AccessKeyId"), + credentials.get("SecretAccessKey"), + credentials.get("Token"), + ) - Raises: - google.auth.exceptions.RefreshError: If an error occurs while - retrieving the AWS region. - """ + @_helpers.copy_docstring(AwsSecurityCredentialsSupplier) + def get_aws_region(self, context, request): # The AWS metadata server is not available in some AWS environments # such as AWS lambda. Instead, it is available via environment # variable. @@ -558,6 +463,7 @@ def _get_region(self, request, url, imdsv2_session_token): raise exceptions.RefreshError("Unable to determine AWS region") headers = None + imdsv2_session_token = self._get_imdsv2_session_token(request) if imdsv2_session_token is not None: headers = {"X-aws-ec2-metadata-token": imdsv2_session_token} @@ -579,53 +485,23 @@ def _get_region(self, request, url, imdsv2_session_token): # Only the us-east-2 part should be used. return response_body[:-1] - def _get_security_credentials(self, request, imdsv2_session_token): - """Retrieves the AWS security credentials required for signing AWS - requests from either the AWS security credentials environment variables - or from the AWS metadata server. - - Args: - request (google.auth.transport.Request): A callable used to make - HTTP requests. - imdsv2_session_token (str): The AWS IMDSv2 session token to be added as a - header in the requests to AWS metadata endpoint. - - Returns: - Mapping[str, str]: The AWS security credentials dictionary object. - - Raises: - google.auth.exceptions.RefreshError: If an error occurs while - retrieving the AWS security credentials. - """ - - # Check environment variables for permanent credentials first. - # https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html - env_aws_access_key_id = os.environ.get(environment_vars.AWS_ACCESS_KEY_ID) - env_aws_secret_access_key = os.environ.get( - environment_vars.AWS_SECRET_ACCESS_KEY - ) - # This is normally not available for permanent credentials. - env_aws_session_token = os.environ.get(environment_vars.AWS_SESSION_TOKEN) - if env_aws_access_key_id and env_aws_secret_access_key: - return { - "access_key_id": env_aws_access_key_id, - "secret_access_key": env_aws_secret_access_key, - "security_token": env_aws_session_token, - } + def _get_imdsv2_session_token(self, request): + if request is not None and self._imdsv2_session_token_url is not None: + headers = {"X-aws-ec2-metadata-token-ttl-seconds": "300"} - # Get role name. - role_name = self._get_metadata_role_name(request, imdsv2_session_token) + imdsv2_session_token_response = request( + url=self._imdsv2_session_token_url, method="PUT", headers=headers + ) - # Get security credentials. - credentials = self._get_metadata_security_credentials( - request, role_name, imdsv2_session_token - ) + if imdsv2_session_token_response.status != 200: + raise exceptions.RefreshError( + "Unable to retrieve AWS Session Token", + imdsv2_session_token_response.data, + ) - return { - "access_key_id": credentials.get("AccessKeyId"), - "secret_access_key": credentials.get("SecretAccessKey"), - "security_token": credentials.get("Token"), - } + return imdsv2_session_token_response.data + else: + return None def _get_metadata_security_credentials( self, request, role_name, imdsv2_session_token @@ -722,30 +598,230 @@ def _get_metadata_role_name(self, request, imdsv2_session_token): return response_body - def _should_use_metadata_server(self): - # The AWS region can be provided through AWS_REGION or AWS_DEFAULT_REGION. - # The metadata server should be used if it cannot be retrieved from one of - # these environment variables. - if not os.environ.get(environment_vars.AWS_REGION) and not os.environ.get( - environment_vars.AWS_DEFAULT_REGION - ): - return True - # AWS security credentials can be retrieved from the AWS_ACCESS_KEY_ID - # and AWS_SECRET_ACCESS_KEY environment variables. The metadata server - # should be used if either of these are not available. - if not os.environ.get(environment_vars.AWS_ACCESS_KEY_ID) or not os.environ.get( - environment_vars.AWS_SECRET_ACCESS_KEY +class Credentials(external_account.Credentials): + """AWS external account credentials. + This is used to exchange serialized AWS signature v4 signed requests to + AWS STS GetCallerIdentity service for Google access tokens. + """ + + def __init__( + self, + audience, + subject_token_type, + token_url=external_account._DEFAULT_TOKEN_URL, + credential_source=None, + aws_security_credentials_supplier=None, + *args, + **kwargs + ): + """Instantiates an AWS workload external account credentials object. + + Args: + audience (str): The STS audience field. + subject_token_type (str): The subject token type based on the Oauth2.0 token exchange spec. + Expected values include:: + + “urn:ietf:params:aws:token-type:aws4_request” + + token_url (Optional [str]): The STS endpoint URL. If not provided, will default to "https://sts.googleapis.com/v1/token". + credential_source (Optional [Mapping]): The credential source dictionary used + to provide instructions on how to retrieve external credential to be exchanged for Google access tokens. + Either a credential source or an AWS security credentials supplier must be provided. + + Example credential_source for AWS credential:: + + { + "environment_id": "aws1", + "regional_cred_verification_url": "https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15", + "region_url": "http://169.254.169.254/latest/meta-data/placement/availability-zone", + "url": "http://169.254.169.254/latest/meta-data/iam/security-credentials", + imdsv2_session_token_url": "http://169.254.169.254/latest/api/token" + } + + aws_security_credentials_supplier (Optional [AwsSecurityCredentialsSupplier]): Optional AWS security credentials supplier. + This will be called to supply valid AWS security credentails which will then + be exchanged for Google access tokens. Either an AWS security credentials supplier + or a credential source must be provided. + args (List): Optional positional arguments passed into the underlying :meth:`~external_account.Credentials.__init__` method. + kwargs (Mapping): Optional keyword arguments passed into the underlying :meth:`~external_account.Credentials.__init__` method. + + Raises: + google.auth.exceptions.RefreshError: If an error is encountered during + access token retrieval logic. + ValueError: For invalid parameters. + + .. note:: Typically one of the helper constructors + :meth:`from_file` or + :meth:`from_info` are used instead of calling the constructor directly. + """ + super(Credentials, self).__init__( + audience=audience, + subject_token_type=subject_token_type, + token_url=token_url, + credential_source=credential_source, + *args, + **kwargs + ) + if credential_source is None and aws_security_credentials_supplier is None: + raise exceptions.InvalidValue( + "A valid credential source or AWS security credentials supplier must be provided." + ) + if ( + credential_source is not None + and aws_security_credentials_supplier is not None ): - return True + raise exceptions.InvalidValue( + "AWS credential cannot have both a credential source and an AWS security credentials supplier." + ) + + if aws_security_credentials_supplier: + self._aws_security_credentials_supplier = aws_security_credentials_supplier + # The regional cred verification URL would normally be provided through the credential source. So set it to the default one here. + self._cred_verification_url = ( + _DEFAULT_AWS_REGIONAL_CREDENTIAL_VERIFICATION_URL + ) + else: + environment_id = credential_source.get("environment_id") or "" + self._aws_security_credentials_supplier = _DefaultAwsSecurityCredentialsSupplier( + credential_source + ) + self._cred_verification_url = credential_source.get( + "regional_cred_verification_url" + ) + + # Get the environment ID. Currently, only one version supported (v1). + matches = re.match(r"^(aws)([\d]+)$", environment_id) + if matches: + env_id, env_version = matches.groups() + else: + env_id, env_version = (None, None) + + if env_id != "aws" or self._cred_verification_url is None: + raise exceptions.InvalidResource( + "No valid AWS 'credential_source' provided" + ) + elif int(env_version or "") != 1: + raise exceptions.InvalidValue( + "aws version '{}' is not supported in the current build.".format( + env_version + ) + ) + + self._target_resource = audience + self._request_signer = None + + def retrieve_subject_token(self, request): + """Retrieves the subject token using the credential_source object. + The subject token is a serialized `AWS GetCallerIdentity signed request`_. + + The logic is summarized as: + + Retrieve the AWS region from the AWS_REGION or AWS_DEFAULT_REGION + environment variable or from the AWS metadata server availability-zone + if not found in the environment variable. + + Check AWS credentials in environment variables. If not found, retrieve + from the AWS metadata server security-credentials endpoint. + + When retrieving AWS credentials from the metadata server + security-credentials endpoint, the AWS role needs to be determined by + calling the security-credentials endpoint without any argument. Then the + credentials can be retrieved via: security-credentials/role_name + + Generate the signed request to AWS STS GetCallerIdentity action. + + Inject x-goog-cloud-target-resource into header and serialize the + signed request. This will be the subject-token to pass to GCP STS. + + .. _AWS GetCallerIdentity signed request: + https://cloud.google.com/iam/docs/access-resources-aws#exchange-token - return False + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + Returns: + str: The retrieved subject token. + """ + + # Initialize the request signer if not yet initialized after determining + # the current AWS region. + if self._request_signer is None: + self._region = self._aws_security_credentials_supplier.get_aws_region( + self._supplier_context, request + ) + self._request_signer = RequestSigner(self._region) + + # Retrieve the AWS security credentials needed to generate the signed + # request. + aws_security_credentials = self._aws_security_credentials_supplier.get_aws_security_credentials( + self._supplier_context, request + ) + # Generate the signed request to AWS STS GetCallerIdentity API. + # Use the required regional endpoint. Otherwise, the request will fail. + request_options = self._request_signer.get_request_options( + aws_security_credentials, + self._cred_verification_url.replace("{region}", self._region), + "POST", + ) + # The GCP STS endpoint expects the headers to be formatted as: + # [ + # {key: 'x-amz-date', value: '...'}, + # {key: 'Authorization', value: '...'}, + # ... + # ] + # And then serialized as: + # quote(json.dumps({ + # url: '...', + # method: 'POST', + # headers: [{key: 'x-amz-date', value: '...'}, ...] + # })) + request_headers = request_options.get("headers") + # The full, canonical resource name of the workload identity pool + # provider, with or without the HTTPS prefix. + # Including this header as part of the signature is recommended to + # ensure data integrity. + request_headers["x-goog-cloud-target-resource"] = self._target_resource + + # Serialize AWS signed request. + # Keeping inner keys in sorted order makes testing easier for Python + # versions <=3.5 as the stringified JSON string would have a predictable + # key order. + aws_signed_req = {} + aws_signed_req["url"] = request_options.get("url") + aws_signed_req["method"] = request_options.get("method") + aws_signed_req["headers"] = [] + # Reformat header to GCP STS expected format. + for key in sorted(request_headers.keys()): + aws_signed_req["headers"].append( + {"key": key, "value": request_headers[key]} + ) + + return urllib.parse.quote( + json.dumps(aws_signed_req, separators=(",", ":"), sort_keys=True) + ) def _create_default_metrics_options(self): metrics_options = super(Credentials, self)._create_default_metrics_options() metrics_options["source"] = "aws" + if self._has_custom_supplier(): + metrics_options["source"] = "programmatic" return metrics_options + def _has_custom_supplier(self): + return self._credential_source is None + + def _constructor_args(self): + args = super(Credentials, self)._constructor_args() + # If a custom supplier was used, append it to the args dict. + if self._has_custom_supplier(): + args.update( + { + "aws_security_credentials_supplier": self._aws_security_credentials_supplier + } + ) + return args + @classmethod def from_info(cls, info, **kwargs): """Creates an AWS Credentials instance from parsed external account info. @@ -761,6 +837,12 @@ def from_info(cls, info, **kwargs): Raises: ValueError: For invalid parameters. """ + aws_security_credentials_supplier = info.get( + "aws_security_credentials_supplier" + ) + kwargs.update( + {"aws_security_credentials_supplier": aws_security_credentials_supplier} + ) return super(Credentials, cls).from_info(info, **kwargs) @classmethod diff --git a/packages/google-auth/google/auth/external_account.py b/packages/google-auth/google/auth/external_account.py index 0420883f86b4..c14001bc2b14 100644 --- a/packages/google-auth/google/auth/external_account.py +++ b/packages/google-auth/google/auth/external_account.py @@ -29,6 +29,7 @@ import abc import copy +from dataclasses import dataclass import datetime import io import json @@ -50,6 +51,29 @@ _STS_REQUESTED_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token" # Cloud resource manager URL used to retrieve project information. _CLOUD_RESOURCE_MANAGER = "https://cloudresourcemanager.googleapis.com/v1/projects/" +# Default Google sts token url. +_DEFAULT_TOKEN_URL = "https://sts.googleapis.com/v1/token" + + +@dataclass +class SupplierContext: + """A context class that contains information about the requested third party credential that is passed + to AWS security credential and subject token suppliers. + + Attributes: + subject_token_type (str): The requested subject token type based on the Oauth2.0 token exchange spec. + Expected values include:: + + “urn:ietf:params:oauth:token-type:jwt” + “urn:ietf:params:oauth:token-type:id-token” + “urn:ietf:params:oauth:token-type:saml2” + “urn:ietf:params:aws:token-type:aws4_request” + + audience (str): The requested audience for the subject token. + """ + + subject_token_type: str + audience: str class Credentials( @@ -88,7 +112,14 @@ def __init__( Args: audience (str): The STS audience field. - subject_token_type (str): The subject token type. + subject_token_type (str): The subject token type based on the Oauth2.0 token exchange spec. + Expected values include:: + + “urn:ietf:params:oauth:token-type:jwt” + “urn:ietf:params:oauth:token-type:id-token” + “urn:ietf:params:oauth:token-type:saml2” + “urn:ietf:params:aws:token-type:aws4_request” + token_url (str): The STS endpoint URL. credential_source (Mapping): The credential source dictionary. service_account_impersonation_url (Optional[str]): The optional service account @@ -145,11 +176,11 @@ def __init__( self._metrics_options = self._create_default_metrics_options() - if self._service_account_impersonation_url: - self._impersonated_credentials = self._initialize_impersonated_credentials() - else: - self._impersonated_credentials = None + self._impersonated_credentials = None self._project_id = None + self._supplier_context = SupplierContext( + self._subject_token_type, self._audience + ) if not self.is_workforce_pool and self._workforce_pool_user_project: # Workload identity pools do not support workforce pool user projects. @@ -358,6 +389,10 @@ def get_project_id(self, request): @_helpers.copy_docstring(credentials.Credentials) def refresh(self, request): scopes = self._scopes if self._scopes is not None else self._default_scopes + + if self._should_initialize_impersonated_credentials(): + self._impersonated_credentials = self._initialize_impersonated_credentials() + if self._impersonated_credentials: self._impersonated_credentials.refresh(request) self.token = self._impersonated_credentials.token @@ -421,6 +456,12 @@ def with_universe_domain(self, universe_domain): new_cred._metrics_options = self._metrics_options return new_cred + def _should_initialize_impersonated_credentials(self): + return ( + self._service_account_impersonation_url is not None + and self._impersonated_credentials is None + ) + def _initialize_impersonated_credentials(self): """Generates an impersonated credentials. diff --git a/packages/google-auth/google/auth/identity_pool.py b/packages/google-auth/google/auth/identity_pool.py index a515353c376e..5526e775cfb3 100644 --- a/packages/google-auth/google/auth/identity_pool.py +++ b/packages/google-auth/google/auth/identity_pool.py @@ -26,11 +26,13 @@ Identity Pool Credentials are initialized using external_account arguments which are typically loaded from an external credentials file or -an external credentials URL. Unlike other Credentials that can be initialized -with a list of explicit arguments, secrets or credentials, external account -clients use the environment and hints/guidelines provided by the -external_account JSON file to retrieve credentials and exchange them for Google -access tokens. +an external credentials URL. + +This module also provides a definition for an abstract subject token supplier. +This supplier can be implemented to return a valid OIDC or SAML2.0 subject token +and used to create Identity Pool credentials. The credentials will then call the +supplier instead of using pre-defined methods such as reading a local file or +calling a URL. """ try: @@ -38,15 +40,130 @@ # Python 2.7 compatibility except ImportError: # pragma: NO COVER from collections import Mapping +import abc import io import json import os +from typing import NamedTuple from google.auth import _helpers from google.auth import exceptions from google.auth import external_account +class SubjectTokenSupplier(metaclass=abc.ABCMeta): + """Base class for subject token suppliers. This can be implemented with custom logic to retrieve + a subject token to exchange for a Google Cloud access token when using Workload or + Workforce Identity Federation. The identity pool credential does not cache the subject token, + so caching logic should be added in the implementation. + """ + + @abc.abstractmethod + def get_subject_token(self, context, request): + """Returns the requested subject token. The subject token must be valid. + + .. warning: This is not cached by the calling Google credential, so caching logic should be implemented in the supplier. + + Args: + context (google.auth.externalaccount.SupplierContext): The context object + containing information about the requested audience and subject token type. + request (google.auth.transport.Request): The object used to make + HTTP requests. + + Raises: + google.auth.exceptions.RefreshError: If an error is encountered during + subject token retrieval logic. + + Returns: + str: The requested subject token string. + """ + raise NotImplementedError("") + + +class _TokenContent(NamedTuple): + """Models the token content response from file and url internal suppliers. + Attributes: + content (str): The string content of the file or URL response. + location (str): The location the content was retrieved from. This will either be a file location or a URL. + """ + + content: str + location: str + + +class _FileSupplier(SubjectTokenSupplier): + """ Internal implementation of subject token supplier which supports reading a subject token from a file.""" + + def __init__(self, path, format_type, subject_token_field_name): + self._path = path + self._format_type = format_type + self._subject_token_field_name = subject_token_field_name + + @_helpers.copy_docstring(SubjectTokenSupplier) + def get_subject_token(self, context, request): + if not os.path.exists(self._path): + raise exceptions.RefreshError("File '{}' was not found.".format(self._path)) + + with io.open(self._path, "r", encoding="utf-8") as file_obj: + token_content = _TokenContent(file_obj.read(), self._path) + + return _parse_token_data( + token_content, self._format_type, self._subject_token_field_name + ) + + +class _UrlSupplier(SubjectTokenSupplier): + """ Internal implementation of subject token supplier which supports retrieving a subject token by calling a URL endpoint.""" + + def __init__(self, url, format_type, subject_token_field_name, headers): + self._url = url + self._format_type = format_type + self._subject_token_field_name = subject_token_field_name + self._headers = headers + + @_helpers.copy_docstring(SubjectTokenSupplier) + def get_subject_token(self, context, request): + response = request(url=self._url, method="GET", headers=self._headers) + + # support both string and bytes type response.data + response_body = ( + response.data.decode("utf-8") + if hasattr(response.data, "decode") + else response.data + ) + + if response.status != 200: + raise exceptions.RefreshError( + "Unable to retrieve Identity Pool subject token", response_body + ) + token_content = _TokenContent(response_body, self._url) + return _parse_token_data( + token_content, self._format_type, self._subject_token_field_name + ) + + +def _parse_token_data(token_content, format_type="text", subject_token_field_name=None): + if format_type == "text": + token = token_content.content + else: + try: + # Parse file content as JSON. + response_data = json.loads(token_content.content) + # Get the subject_token. + token = response_data[subject_token_field_name] + except (KeyError, ValueError): + raise exceptions.RefreshError( + "Unable to parse subject_token from JSON file '{}' using key '{}'".format( + token_content.location, subject_token_field_name + ) + ) + if not token: + raise exceptions.RefreshError( + "Missing subject_token in the credential_source file" + ) + return token + + class Credentials(external_account.Credentials): """External account credentials sourced from files and URLs.""" @@ -54,8 +171,9 @@ def __init__( self, audience, subject_token_type, - token_url, - credential_source, + token_url=external_account._DEFAULT_TOKEN_URL, + credential_source=None, + subject_token_supplier=None, *args, **kwargs ): @@ -63,11 +181,18 @@ def __init__( Args: audience (str): The STS audience field. - subject_token_type (str): The subject token type. - token_url (str): The STS endpoint URL. - credential_source (Mapping): The credential source dictionary used to + subject_token_type (str): The subject token type based on the Oauth2.0 token exchange spec. + Expected values include:: + + “urn:ietf:params:oauth:token-type:jwt” + “urn:ietf:params:oauth:token-type:id-token” + “urn:ietf:params:oauth:token-type:saml2” + + token_url (Optional [str]): The STS endpoint URL. If not provided, will default to "https://sts.googleapis.com/v1/token". + credential_source (Optional [Mapping]): The credential source dictionary used to provide instructions on how to retrieve external credential to be - exchanged for Google access tokens. + exchanged for Google access tokens. Either a credential source or + a subject token supplier must be provided. Example credential_source for url-sourced credential:: @@ -85,6 +210,10 @@ def __init__( { "file": "/path/to/token/file.txt" } + subject_token_supplier (Optional [SubjectTokenSupplier]): Optional subject token supplier. + This will be called to supply a valid subject token which will then + be exchanged for Google access tokens. Either a subject token supplier + or a credential source must be provided. args (List): Optional positional arguments passed into the underlying :meth:`~external_account.Credentials.__init__` method. kwargs (Mapping): Optional keyword arguments passed into the underlying :meth:`~external_account.Credentials.__init__` method. @@ -106,10 +235,25 @@ def __init__( *args, **kwargs ) - if not isinstance(credential_source, Mapping): + if credential_source is None and subject_token_supplier is None: + raise exceptions.InvalidValue( + "A valid credential source or a subject token supplier must be provided." + ) + if credential_source is not None and subject_token_supplier is not None: + raise exceptions.InvalidValue( + "Identity pool credential cannot have both a credential source and a subject token supplier." + ) + + if subject_token_supplier is not None: + self._subject_token_supplier = subject_token_supplier self._credential_source_file = None self._credential_source_url = None else: + if not isinstance(credential_source, Mapping): + self._credential_source_executable = None + raise exceptions.MalformedError( + "Invalid credential_source. The credential_source is not a dict." + ) self._credential_source_file = credential_source.get("file") self._credential_source_url = credential_source.get("url") self._credential_source_headers = credential_source.get("headers") @@ -143,79 +287,35 @@ def __init__( else: self._credential_source_field_name = None - if self._credential_source_file and self._credential_source_url: - raise exceptions.MalformedError( - "Ambiguous credential_source. 'file' is mutually exclusive with 'url'." - ) - if not self._credential_source_file and not self._credential_source_url: - raise exceptions.MalformedError( - "Missing credential_source. A 'file' or 'url' must be provided." - ) + if self._credential_source_file and self._credential_source_url: + raise exceptions.MalformedError( + "Ambiguous credential_source. 'file' is mutually exclusive with 'url'." + ) + if not self._credential_source_file and not self._credential_source_url: + raise exceptions.MalformedError( + "Missing credential_source. A 'file' or 'url' must be provided." + ) + + if self._credential_source_file: + self._subject_token_supplier = _FileSupplier( + self._credential_source_file, + self._credential_source_format_type, + self._credential_source_field_name, + ) + else: + self._subject_token_supplier = _UrlSupplier( + self._credential_source_url, + self._credential_source_format_type, + self._credential_source_field_name, + self._credential_source_headers, + ) @_helpers.copy_docstring(external_account.Credentials) def retrieve_subject_token(self, request): - return self._parse_token_data( - self._get_token_data(request), - self._credential_source_format_type, - self._credential_source_field_name, - ) - - def _get_token_data(self, request): - if self._credential_source_file: - return self._get_file_data(self._credential_source_file) - else: - return self._get_url_data( - request, self._credential_source_url, self._credential_source_headers - ) - - def _get_file_data(self, filename): - if not os.path.exists(filename): - raise exceptions.RefreshError("File '{}' was not found.".format(filename)) - - with io.open(filename, "r", encoding="utf-8") as file_obj: - return file_obj.read(), filename - - def _get_url_data(self, request, url, headers): - response = request(url=url, method="GET", headers=headers) - - # support both string and bytes type response.data - response_body = ( - response.data.decode("utf-8") - if hasattr(response.data, "decode") - else response.data + return self._subject_token_supplier.get_subject_token( + self._supplier_context, request ) - if response.status != 200: - raise exceptions.RefreshError( - "Unable to retrieve Identity Pool subject token", response_body - ) - - return response_body, url - - def _parse_token_data( - self, token_content, format_type="text", subject_token_field_name=None - ): - content, filename = token_content - if format_type == "text": - token = content - else: - try: - # Parse file content as JSON. - response_data = json.loads(content) - # Get the subject_token. - token = response_data[subject_token_field_name] - except (KeyError, ValueError): - raise exceptions.RefreshError( - "Unable to parse subject_token from JSON file '{}' using key '{}'".format( - filename, subject_token_field_name - ) - ) - if not token: - raise exceptions.RefreshError( - "Missing subject_token in the credential_source file" - ) - return token - def _create_default_metrics_options(self): metrics_options = super(Credentials, self)._create_default_metrics_options() # Check that credential source is a dict before checking for file vs url. This check needs to be done @@ -226,8 +326,20 @@ def _create_default_metrics_options(self): metrics_options["source"] = "file" else: metrics_options["source"] = "url" + else: + metrics_options["source"] = "programmatic" return metrics_options + def _has_custom_supplier(self): + return self._credential_source is None + + def _constructor_args(self): + args = super(Credentials, self)._constructor_args() + # If a custom supplier was used, append it to the args dict. + if self._has_custom_supplier(): + args.update({"subject_token_supplier": self._subject_token_supplier}) + return args + @classmethod def from_info(cls, info, **kwargs): """Creates an Identity Pool Credentials instance from parsed external account info. @@ -244,6 +356,8 @@ def from_info(cls, info, **kwargs): Raises: ValueError: For invalid parameters. """ + subject_token_supplier = info.get("subject_token_supplier") + kwargs.update({"subject_token_supplier": subject_token_supplier}) return super(Credentials, cls).from_info(info, **kwargs) @classmethod diff --git a/packages/google-auth/tests/test_aws.py b/packages/google-auth/tests/test_aws.py index 3f358d52b097..56148203128f 100644 --- a/packages/google-auth/tests/test_aws.py +++ b/packages/google-auth/tests/test_aws.py @@ -21,7 +21,7 @@ import mock import pytest # type: ignore -from google.auth import _helpers +from google.auth import _helpers, external_account from google.auth import aws from google.auth import environment_vars from google.auth import exceptions @@ -616,8 +616,13 @@ def test_get_request_options( ): utcnow.return_value = datetime.datetime.strptime(time, "%Y-%m-%dT%H:%M:%SZ") request_signer = aws.RequestSigner(region) + credentials_object = aws.AwsSecurityCredentials( + credentials.get("access_key_id"), + credentials.get("secret_access_key"), + credentials.get("security_token"), + ) actual_signed_request = request_signer.get_request_options( - credentials, + credentials_object, original_request.get("url"), original_request.get("method"), original_request.get("data"), @@ -631,10 +636,7 @@ def test_get_request_options_with_missing_scheme_url(self): with pytest.raises(ValueError) as excinfo: request_signer.get_request_options( - { - "access_key_id": ACCESS_KEY_ID, - "secret_access_key": SECRET_ACCESS_KEY, - }, + aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY), "invalid", "POST", ) @@ -646,10 +648,7 @@ def test_get_request_options_with_invalid_scheme_url(self): with pytest.raises(ValueError) as excinfo: request_signer.get_request_options( - { - "access_key_id": ACCESS_KEY_ID, - "secret_access_key": SECRET_ACCESS_KEY, - }, + aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY), "http://invalid", "POST", ) @@ -661,10 +660,7 @@ def test_get_request_options_with_missing_hostname_url(self): with pytest.raises(ValueError) as excinfo: request_signer.get_request_options( - { - "access_key_id": ACCESS_KEY_ID, - "secret_access_key": SECRET_ACCESS_KEY, - }, + aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY), "https://", "POST", ) @@ -672,6 +668,36 @@ def test_get_request_options_with_missing_hostname_url(self): assert excinfo.match(r"Invalid AWS service URL") +class TestAwsSecurityCredentialsSupplier(aws.AwsSecurityCredentialsSupplier): + def __init__( + self, + security_credentials=None, + region=None, + credentials_exception=None, + region_exception=None, + expected_context=None, + ): + self._security_credentials = security_credentials + self._region = region + self._credentials_exception = credentials_exception + self._region_exception = region_exception + self._expected_context = expected_context + + def get_aws_security_credentials(self, context, request): + if self._expected_context is not None: + assert self._expected_context == context + if self._credentials_exception is not None: + raise self._credentials_exception + return self._security_credentials + + def get_aws_region(self, context, request): + if self._expected_context is not None: + assert self._expected_context == context + if self._region_exception is not None: + raise self._region_exception + return self._region + + class TestCredentials(object): AWS_REGION = "us-east-2" AWS_ROLE = "gcp-aws-role" @@ -734,7 +760,7 @@ def make_serialized_aws_signed_request( ], } # Include security token if available. - if "security_token" in aws_security_credentials: + if aws_security_credentials.session_token is not None: reformatted_signed_request.get("headers").append( { "key": "x-amz-security-token", @@ -773,16 +799,17 @@ def make_mock_request( in an AWS environment. """ responses = [] - if imdsv2_session_token_status: - # AWS session token request - imdsv2_session_response = mock.create_autospec( - transport.Response, instance=True - ) - imdsv2_session_response.status = imdsv2_session_token_status - imdsv2_session_response.data = imdsv2_session_token_data - responses.append(imdsv2_session_response) if region_status: + if imdsv2_session_token_status: + # AWS session token request + imdsv2_session_response = mock.create_autospec( + transport.Response, instance=True + ) + imdsv2_session_response.status = imdsv2_session_token_status + imdsv2_session_response.data = imdsv2_session_token_data + responses.append(imdsv2_session_response) + # AWS region request. region_response = mock.create_autospec(transport.Response, instance=True) region_response.status = region_status @@ -790,6 +817,15 @@ def make_mock_request( region_response.data = "{}b".format(region_name).encode("utf-8") responses.append(region_response) + if imdsv2_session_token_status: + # AWS session token request + imdsv2_session_response = mock.create_autospec( + transport.Response, instance=True + ) + imdsv2_session_response.status = imdsv2_session_token_status + imdsv2_session_response.data = imdsv2_session_token_data + responses.append(imdsv2_session_response) + if role_status: # AWS role name request. role_response = mock.create_autospec(transport.Response, instance=True) @@ -834,7 +870,8 @@ def make_mock_request( @classmethod def make_credentials( cls, - credential_source, + credential_source=None, + aws_security_credentials_supplier=None, token_url=TOKEN_URL, token_info_url=TOKEN_INFO_URL, client_id=None, @@ -851,6 +888,7 @@ def make_credentials( token_info_url=token_info_url, service_account_impersonation_url=service_account_impersonation_url, credential_source=credential_source, + aws_security_credentials_supplier=aws_security_credentials_supplier, client_id=client_id, client_secret=client_secret, quota_project_id=quota_project_id, @@ -929,6 +967,7 @@ def test_from_info_full_options(self, mock_init): client_id=CLIENT_ID, client_secret=CLIENT_SECRET, credential_source=self.CREDENTIAL_SOURCE, + aws_security_credentials_supplier=None, quota_project_id=QUOTA_PROJECT_ID, workforce_pool_user_project=None, universe_domain=DEFAULT_UNIVERSE_DOMAIN, @@ -957,6 +996,38 @@ def test_from_info_required_options_only(self, mock_init): client_id=None, client_secret=None, credential_source=self.CREDENTIAL_SOURCE, + aws_security_credentials_supplier=None, + quota_project_id=None, + workforce_pool_user_project=None, + universe_domain=DEFAULT_UNIVERSE_DOMAIN, + ) + + @mock.patch.object(aws.Credentials, "__init__", return_value=None) + def test_from_info_supplier(self, mock_init): + supplier = TestAwsSecurityCredentialsSupplier() + + credentials = aws.Credentials.from_info( + { + "audience": AUDIENCE, + "subject_token_type": SUBJECT_TOKEN_TYPE, + "token_url": TOKEN_URL, + "aws_security_credentials_supplier": supplier, + } + ) + + # Confirm aws.Credentials instance initialized with the expected parameters. + assert isinstance(credentials, aws.Credentials) + mock_init.assert_called_once_with( + audience=AUDIENCE, + subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + token_info_url=None, + service_account_impersonation_url=None, + service_account_impersonation_options={}, + client_id=None, + client_secret=None, + credential_source=None, + aws_security_credentials_supplier=supplier, quota_project_id=None, workforce_pool_user_project=None, universe_domain=DEFAULT_UNIVERSE_DOMAIN, @@ -993,6 +1064,7 @@ def test_from_file_full_options(self, mock_init, tmpdir): client_id=CLIENT_ID, client_secret=CLIENT_SECRET, credential_source=self.CREDENTIAL_SOURCE, + aws_security_credentials_supplier=None, quota_project_id=QUOTA_PROJECT_ID, workforce_pool_user_project=None, universe_domain=DEFAULT_UNIVERSE_DOMAIN, @@ -1022,6 +1094,7 @@ def test_from_file_required_options_only(self, mock_init, tmpdir): client_id=None, client_secret=None, credential_source=self.CREDENTIAL_SOURCE, + aws_security_credentials_supplier=None, quota_project_id=None, workforce_pool_user_project=None, universe_domain=DEFAULT_UNIVERSE_DOMAIN, @@ -1036,6 +1109,27 @@ def test_constructor_invalid_credential_source(self): assert excinfo.match(r"No valid AWS 'credential_source' provided") + def test_constructor_invalid_credential_source_and_supplier(self): + # Provide both a credential source and supplier. + with pytest.raises(ValueError) as excinfo: + self.make_credentials( + credential_source=self.CREDENTIAL_SOURCE, + aws_security_credentials_supplier="test", + ) + + assert excinfo.match( + r"AWS credential cannot have both a credential source and an AWS security credentials supplier." + ) + + def test_constructor_invalid_no_credential_source_or_supplier(self): + # Provide no credential source or supplier. + with pytest.raises(ValueError) as excinfo: + self.make_credentials() + + assert excinfo.match( + r"A valid credential source or AWS security credentials supplier must be provided." + ) + def test_constructor_invalid_environment_id(self): # Provide invalid environment_id. credential_source = self.CREDENTIAL_SOURCE.copy() @@ -1158,11 +1252,7 @@ def test_retrieve_subject_token_success_temp_creds_no_environment_vars( subject_token = credentials.retrieve_subject_token(request) assert subject_token == self.make_serialized_aws_signed_request( - { - "access_key_id": ACCESS_KEY_ID, - "secret_access_key": SECRET_ACCESS_KEY, - "security_token": TOKEN, - } + aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN) ) # Assert region request. self.assert_aws_metadata_request_kwargs( @@ -1231,11 +1321,7 @@ def test_retrieve_subject_token_success_temp_creds_no_environment_vars_idmsv2( subject_token = credentials.retrieve_subject_token(request) assert subject_token == self.make_serialized_aws_signed_request( - { - "access_key_id": ACCESS_KEY_ID, - "secret_access_key": SECRET_ACCESS_KEY, - "security_token": TOKEN, - } + aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN) ) # Assert session token request self.assert_aws_metadata_request_kwargs( @@ -1250,15 +1336,22 @@ def test_retrieve_subject_token_success_temp_creds_no_environment_vars_idmsv2( REGION_URL, {"X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN}, ) - # Assert role request. + # Assert session token request self.assert_aws_metadata_request_kwargs( request.call_args_list[2][1], + IMDSV2_SESSION_TOKEN_URL, + {"X-aws-ec2-metadata-token-ttl-seconds": "300"}, + "PUT", + ) + # Assert role request. + self.assert_aws_metadata_request_kwargs( + request.call_args_list[3][1], SECURITY_CREDS_URL, {"X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN}, ) # Assert security credentials request. self.assert_aws_metadata_request_kwargs( - request.call_args_list[3][1], + request.call_args_list[4][1], "{}/{}".format(SECURITY_CREDS_URL, self.AWS_ROLE), { "Content-Type": "application/json", @@ -1335,11 +1428,7 @@ def test_retrieve_subject_token_success_temp_creds_environment_vars_missing_secr subject_token = credentials.retrieve_subject_token(request) assert subject_token == self.make_serialized_aws_signed_request( - { - "access_key_id": ACCESS_KEY_ID, - "secret_access_key": SECRET_ACCESS_KEY, - "security_token": TOKEN, - } + aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN) ) # Assert session token request. self.assert_aws_metadata_request_kwargs( @@ -1396,11 +1485,7 @@ def test_retrieve_subject_token_success_temp_creds_environment_vars_missing_acce subject_token = credentials.retrieve_subject_token(request) assert subject_token == self.make_serialized_aws_signed_request( - { - "access_key_id": ACCESS_KEY_ID, - "secret_access_key": SECRET_ACCESS_KEY, - "security_token": TOKEN, - } + aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN) ) # Assert session token request. self.assert_aws_metadata_request_kwargs( @@ -1451,11 +1536,7 @@ def test_retrieve_subject_token_success_temp_creds_environment_vars_missing_cred subject_token = credentials.retrieve_subject_token(request) assert subject_token == self.make_serialized_aws_signed_request( - { - "access_key_id": ACCESS_KEY_ID, - "secret_access_key": SECRET_ACCESS_KEY, - "security_token": TOKEN, - } + aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN) ) # Assert session token request. self.assert_aws_metadata_request_kwargs( @@ -1530,11 +1611,7 @@ def test_retrieve_subject_token_success_ipv6(self, utcnow): subject_token = credentials.retrieve_subject_token(request) assert subject_token == self.make_serialized_aws_signed_request( - { - "access_key_id": ACCESS_KEY_ID, - "secret_access_key": SECRET_ACCESS_KEY, - "security_token": TOKEN, - } + aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN) ) # Assert session token request. self.assert_aws_metadata_request_kwargs( @@ -1549,15 +1626,22 @@ def test_retrieve_subject_token_success_ipv6(self, utcnow): REGION_URL_IPV6, {"X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN}, ) - # Assert role request. + # Assert session token request. self.assert_aws_metadata_request_kwargs( request.call_args_list[2][1], + IMDSV2_SESSION_TOKEN_URL_IPV6, + {"X-aws-ec2-metadata-token-ttl-seconds": "300"}, + "PUT", + ) + # Assert role request. + self.assert_aws_metadata_request_kwargs( + request.call_args_list[3][1], SECURITY_CREDS_URL_IPV6, {"X-aws-ec2-metadata-token": self.AWS_IMDSV2_SESSION_TOKEN}, ) # Assert security credentials request. self.assert_aws_metadata_request_kwargs( - request.call_args_list[3][1], + request.call_args_list[4][1], "{}/{}".format(SECURITY_CREDS_URL_IPV6, self.AWS_ROLE), { "Content-Type": "application/json", @@ -1619,7 +1703,7 @@ def test_retrieve_subject_token_success_permanent_creds_no_environment_vars( subject_token = credentials.retrieve_subject_token(request) assert subject_token == self.make_serialized_aws_signed_request( - {"access_key_id": ACCESS_KEY_ID, "secret_access_key": SECRET_ACCESS_KEY} + aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY) ) @mock.patch("google.auth._helpers.utcnow") @@ -1636,11 +1720,7 @@ def test_retrieve_subject_token_success_environment_vars(self, utcnow, monkeypat subject_token = credentials.retrieve_subject_token(None) assert subject_token == self.make_serialized_aws_signed_request( - { - "access_key_id": ACCESS_KEY_ID, - "secret_access_key": SECRET_ACCESS_KEY, - "security_token": TOKEN, - } + aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN) ) @mock.patch("google.auth._helpers.utcnow") @@ -1659,11 +1739,7 @@ def test_retrieve_subject_token_success_environment_vars_with_default_region( subject_token = credentials.retrieve_subject_token(None) assert subject_token == self.make_serialized_aws_signed_request( - { - "access_key_id": ACCESS_KEY_ID, - "secret_access_key": SECRET_ACCESS_KEY, - "security_token": TOKEN, - } + aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN) ) @mock.patch("google.auth._helpers.utcnow") @@ -1686,11 +1762,7 @@ def test_retrieve_subject_token_success_environment_vars_with_both_regions_set( subject_token = credentials.retrieve_subject_token(None) assert subject_token == self.make_serialized_aws_signed_request( - { - "access_key_id": ACCESS_KEY_ID, - "secret_access_key": SECRET_ACCESS_KEY, - "security_token": TOKEN, - } + aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN) ) @mock.patch("google.auth._helpers.utcnow") @@ -1708,7 +1780,7 @@ def test_retrieve_subject_token_success_environment_vars_no_session_token( subject_token = credentials.retrieve_subject_token(None) assert subject_token == self.make_serialized_aws_signed_request( - {"access_key_id": ACCESS_KEY_ID, "secret_access_key": SECRET_ACCESS_KEY} + aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY) ) @mock.patch("google.auth._helpers.utcnow") @@ -1730,11 +1802,7 @@ def test_retrieve_subject_token_success_environment_vars_except_region( subject_token = credentials.retrieve_subject_token(request) assert subject_token == self.make_serialized_aws_signed_request( - { - "access_key_id": ACCESS_KEY_ID, - "secret_access_key": SECRET_ACCESS_KEY, - "security_token": TOKEN, - } + aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN) ) def test_retrieve_subject_token_error_determining_aws_region(self): @@ -1806,11 +1874,7 @@ def test_refresh_success_without_impersonation_ignore_default_scopes( self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ" ) expected_subject_token = self.make_serialized_aws_signed_request( - { - "access_key_id": ACCESS_KEY_ID, - "secret_access_key": SECRET_ACCESS_KEY, - "security_token": TOKEN, - } + aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN) ) token_headers = { "Content-Type": "application/x-www-form-urlencoded", @@ -1869,11 +1933,7 @@ def test_refresh_success_without_impersonation_use_default_scopes( self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ" ) expected_subject_token = self.make_serialized_aws_signed_request( - { - "access_key_id": ACCESS_KEY_ID, - "secret_access_key": SECRET_ACCESS_KEY, - "security_token": TOKEN, - } + aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN) ) token_headers = { "Content-Type": "application/x-www-form-urlencoded", @@ -1939,11 +1999,7 @@ def test_refresh_success_with_impersonation_ignore_default_scopes( _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=3600) ).isoformat("T") + "Z" expected_subject_token = self.make_serialized_aws_signed_request( - { - "access_key_id": ACCESS_KEY_ID, - "secret_access_key": SECRET_ACCESS_KEY, - "security_token": TOKEN, - } + aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN) ) token_headers = { "Content-Type": "application/x-www-form-urlencoded", @@ -2036,11 +2092,7 @@ def test_refresh_success_with_impersonation_use_default_scopes( _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=3600) ).isoformat("T") + "Z" expected_subject_token = self.make_serialized_aws_signed_request( - { - "access_key_id": ACCESS_KEY_ID, - "secret_access_key": SECRET_ACCESS_KEY, - "security_token": TOKEN, - } + aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN) ) token_headers = { "Content-Type": "application/x-www-form-urlencoded", @@ -2122,3 +2174,249 @@ def test_refresh_with_retrieve_subject_token_error(self): credentials.refresh(request) assert excinfo.match(r"Unable to retrieve AWS region") + + @mock.patch("google.auth._helpers.utcnow") + def test_retrieve_subject_token_success_with_supplier(self, utcnow): + utcnow.return_value = datetime.datetime.strptime( + self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ" + ) + request = self.make_mock_request() + + security_credentials = aws.AwsSecurityCredentials( + ACCESS_KEY_ID, SECRET_ACCESS_KEY + ) + supplier = TestAwsSecurityCredentialsSupplier( + security_credentials=security_credentials, region=self.AWS_REGION + ) + + credentials = self.make_credentials(aws_security_credentials_supplier=supplier) + + subject_token = credentials.retrieve_subject_token(request) + assert subject_token == self.make_serialized_aws_signed_request( + aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY) + ) + + @mock.patch("google.auth._helpers.utcnow") + def test_retrieve_subject_token_success_with_supplier_session_token(self, utcnow): + utcnow.return_value = datetime.datetime.strptime( + self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ" + ) + request = self.make_mock_request() + + security_credentials = aws.AwsSecurityCredentials( + ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN + ) + supplier = TestAwsSecurityCredentialsSupplier( + security_credentials=security_credentials, region=self.AWS_REGION + ) + + credentials = self.make_credentials(aws_security_credentials_supplier=supplier) + + subject_token = credentials.retrieve_subject_token(request) + assert subject_token == self.make_serialized_aws_signed_request( + aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN) + ) + + @mock.patch("google.auth._helpers.utcnow") + def test_retrieve_subject_token_success_with_supplier_correct_context(self, utcnow): + utcnow.return_value = datetime.datetime.strptime( + self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ" + ) + request = self.make_mock_request() + expected_context = external_account.SupplierContext( + SUBJECT_TOKEN_TYPE, AUDIENCE + ) + + security_credentials = aws.AwsSecurityCredentials( + ACCESS_KEY_ID, SECRET_ACCESS_KEY + ) + supplier = TestAwsSecurityCredentialsSupplier( + security_credentials=security_credentials, + region=self.AWS_REGION, + expected_context=expected_context, + ) + + credentials = self.make_credentials(aws_security_credentials_supplier=supplier) + + credentials.retrieve_subject_token(request) + + def test_retrieve_subject_token_error_with_supplier(self): + request = self.make_mock_request() + expected_exception = exceptions.RefreshError("Test error") + supplier = TestAwsSecurityCredentialsSupplier( + region=self.AWS_REGION, credentials_exception=expected_exception + ) + + credentials = self.make_credentials(aws_security_credentials_supplier=supplier) + + with pytest.raises(exceptions.RefreshError) as excinfo: + credentials.refresh(request) + + assert excinfo.match(r"Test error") + + def test_retrieve_subject_token_error_with_supplier_region(self): + request = self.make_mock_request() + expected_exception = exceptions.RefreshError("Test error") + security_credentials = aws.AwsSecurityCredentials( + ACCESS_KEY_ID, SECRET_ACCESS_KEY + ) + supplier = TestAwsSecurityCredentialsSupplier( + security_credentials=security_credentials, + region_exception=expected_exception, + ) + + credentials = self.make_credentials(aws_security_credentials_supplier=supplier) + + with pytest.raises(exceptions.RefreshError) as excinfo: + credentials.refresh(request) + + assert excinfo.match(r"Test error") + + @mock.patch( + "google.auth.metrics.python_and_auth_lib_version", + return_value=LANG_LIBRARY_METRICS_HEADER_VALUE, + ) + @mock.patch("google.auth._helpers.utcnow") + def test_refresh_success_with_supplier_with_impersonation( + self, utcnow, mock_auth_lib_value + ): + utcnow.return_value = datetime.datetime.strptime( + self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ" + ) + expire_time = ( + _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=3600) + ).isoformat("T") + "Z" + expected_subject_token = self.make_serialized_aws_signed_request( + aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN) + ) + token_headers = { + "Content-Type": "application/x-www-form-urlencoded", + "Authorization": "Basic " + BASIC_AUTH_ENCODING, + "x-goog-api-client": "gl-python/3.7 auth/1.1 google-byoid-sdk sa-impersonation/true config-lifetime/false source/programmatic", + } + token_request_data = { + "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange", + "audience": AUDIENCE, + "requested_token_type": "urn:ietf:params:oauth:token-type:access_token", + "scope": "https://www.googleapis.com/auth/iam", + "subject_token": expected_subject_token, + "subject_token_type": SUBJECT_TOKEN_TYPE, + } + # Service account impersonation request/response. + impersonation_response = { + "accessToken": "SA_ACCESS_TOKEN", + "expireTime": expire_time, + } + impersonation_headers = { + "Content-Type": "application/json", + "authorization": "Bearer {}".format(self.SUCCESS_RESPONSE["access_token"]), + "x-goog-user-project": QUOTA_PROJECT_ID, + "x-goog-api-client": IMPERSONATE_ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, + "x-allowed-locations": "0x0", + } + impersonation_request_data = { + "delegates": None, + "scope": SCOPES, + "lifetime": "3600s", + } + request = self.make_mock_request( + token_status=http_client.OK, + token_data=self.SUCCESS_RESPONSE, + impersonation_status=http_client.OK, + impersonation_data=impersonation_response, + ) + + supplier = TestAwsSecurityCredentialsSupplier( + security_credentials=aws.AwsSecurityCredentials( + ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN + ), + region=self.AWS_REGION, + ) + + credentials = self.make_credentials( + client_id=CLIENT_ID, + client_secret=CLIENT_SECRET, + aws_security_credentials_supplier=supplier, + service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + quota_project_id=QUOTA_PROJECT_ID, + scopes=SCOPES, + # Default scopes should be ignored. + default_scopes=["ignored"], + ) + + credentials.refresh(request) + + assert len(request.call_args_list) == 2 + # First request should be sent to GCP STS endpoint. + self.assert_token_request_kwargs( + request.call_args_list[0][1], token_headers, token_request_data + ) + # Second request should be sent to iamcredentials endpoint for service + # account impersonation. + self.assert_impersonation_request_kwargs( + request.call_args_list[1][1], + impersonation_headers, + impersonation_request_data, + ) + assert credentials.token == impersonation_response["accessToken"] + assert credentials.quota_project_id == QUOTA_PROJECT_ID + assert credentials.scopes == SCOPES + assert credentials.default_scopes == ["ignored"] + + @mock.patch( + "google.auth.metrics.python_and_auth_lib_version", + return_value=LANG_LIBRARY_METRICS_HEADER_VALUE, + ) + @mock.patch("google.auth._helpers.utcnow") + def test_refresh_success_with_supplier(self, utcnow, mock_auth_lib_value): + utcnow.return_value = datetime.datetime.strptime( + self.AWS_SIGNATURE_TIME, "%Y-%m-%dT%H:%M:%SZ" + ) + expected_subject_token = self.make_serialized_aws_signed_request( + aws.AwsSecurityCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN) + ) + token_headers = { + "Content-Type": "application/x-www-form-urlencoded", + "Authorization": "Basic " + BASIC_AUTH_ENCODING, + "x-goog-api-client": "gl-python/3.7 auth/1.1 google-byoid-sdk sa-impersonation/false config-lifetime/false source/programmatic", + } + token_request_data = { + "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange", + "audience": AUDIENCE, + "requested_token_type": "urn:ietf:params:oauth:token-type:access_token", + "scope": " ".join(SCOPES), + "subject_token": expected_subject_token, + "subject_token_type": SUBJECT_TOKEN_TYPE, + } + request = self.make_mock_request( + token_status=http_client.OK, token_data=self.SUCCESS_RESPONSE + ) + + supplier = TestAwsSecurityCredentialsSupplier( + security_credentials=aws.AwsSecurityCredentials( + ACCESS_KEY_ID, SECRET_ACCESS_KEY, TOKEN + ), + region=self.AWS_REGION, + ) + + credentials = self.make_credentials( + client_id=CLIENT_ID, + client_secret=CLIENT_SECRET, + aws_security_credentials_supplier=supplier, + quota_project_id=QUOTA_PROJECT_ID, + scopes=SCOPES, + # Default scopes should be ignored. + default_scopes=["ignored"], + ) + + credentials.refresh(request) + + assert len(request.call_args_list) == 1 + # First request should be sent to GCP STS endpoint. + self.assert_token_request_kwargs( + request.call_args_list[0][1], token_headers, token_request_data + ) + assert credentials.token == self.SUCCESS_RESPONSE["access_token"] + assert credentials.quota_project_id == QUOTA_PROJECT_ID + assert credentials.scopes == SCOPES + assert credentials.default_scopes == ["ignored"] diff --git a/packages/google-auth/tests/test_external_account.py b/packages/google-auth/tests/test_external_account.py index 03a5014ce5b5..c458b21b6438 100644 --- a/packages/google-auth/tests/test_external_account.py +++ b/packages/google-auth/tests/test_external_account.py @@ -477,16 +477,6 @@ def test_with_quota_project_full_options_propagated(self): universe_domain=DEFAULT_UNIVERSE_DOMAIN, ) - def test_with_invalid_impersonation_target_principal(self): - invalid_url = "https://iamcredentials.googleapis.com/v1/invalid" - - with pytest.raises(exceptions.RefreshError) as excinfo: - self.make_credentials(service_account_impersonation_url=invalid_url) - - assert excinfo.match( - r"Unable to determine target principal from service account impersonation URL." - ) - def test_info(self): credentials = self.make_credentials(universe_domain="dummy_universe.com") @@ -1069,6 +1059,21 @@ def test_refresh_impersonation_without_client_auth_error(self): assert not credentials.expired assert credentials.token is None + def test_refresh_impersonation_invalid_impersonated_url_error(self): + credentials = self.make_credentials( + service_account_impersonation_url="https://iamcredentials.googleapis.com/v1/invalid", + scopes=self.SCOPES, + ) + + with pytest.raises(exceptions.RefreshError) as excinfo: + credentials.refresh(None) + + assert excinfo.match( + r"Unable to determine target principal from service account impersonation URL." + ) + assert not credentials.expired + assert credentials.token is None + @mock.patch( "google.auth.metrics.python_and_auth_lib_version", return_value=LANG_LIBRARY_METRICS_HEADER_VALUE, @@ -1913,3 +1918,10 @@ def test_get_project_id_cloud_resource_manager_error(self): assert project_id is None # Only 2 requests to STS and cloud resource manager should be sent. assert len(request.call_args_list) == 2 + + +def test_supplier_context(): + context = external_account.SupplierContext("TestTokenType", "TestAudience") + + assert context.subject_token_type == "TestTokenType" + assert context.audience == "TestAudience" diff --git a/packages/google-auth/tests/test_identity_pool.py b/packages/google-auth/tests/test_identity_pool.py index be30c4e9b410..ac1d9a0bb1c8 100644 --- a/packages/google-auth/tests/test_identity_pool.py +++ b/packages/google-auth/tests/test_identity_pool.py @@ -21,7 +21,7 @@ import mock import pytest # type: ignore -from google.auth import _helpers +from google.auth import _helpers, external_account from google.auth import exceptions from google.auth import identity_pool from google.auth import metrics @@ -151,6 +151,22 @@ ] +class TestSubjectTokenSupplier(identity_pool.SubjectTokenSupplier): + def __init__( + self, subject_token=None, subject_token_exception=None, expected_context=None + ): + self._subject_token = subject_token + self._subject_token_exception = subject_token_exception + self._expected_context = expected_context + + def get_subject_token(self, context, request): + if self._expected_context is not None: + assert self._expected_context == context + if self._subject_token_exception is not None: + raise self._subject_token_exception + return self._subject_token + + class TestCredentials(object): CREDENTIAL_SOURCE_TEXT = {"file": SUBJECT_TOKEN_TEXT_FILE} CREDENTIAL_SOURCE_JSON = { @@ -273,10 +289,13 @@ def assert_underlying_credentials_refresh( else: metrics_options["sa-impersonation"] = "false" metrics_options["config-lifetime"] = "false" - if credentials._credential_source_file: - metrics_options["source"] = "file" + if credentials._credential_source: + if credentials._credential_source_file: + metrics_options["source"] = "file" + else: + metrics_options["source"] = "url" else: - metrics_options["source"] = "url" + metrics_options["source"] = "programmatic" token_headers["x-goog-api-client"] = metrics.byoid_metrics_header( metrics_options @@ -386,6 +405,7 @@ def make_credentials( default_scopes=None, service_account_impersonation_url=None, credential_source=None, + subject_token_supplier=None, workforce_pool_user_project=None, ): return identity_pool.Credentials( @@ -395,6 +415,7 @@ def make_credentials( token_info_url=token_info_url, service_account_impersonation_url=service_account_impersonation_url, credential_source=credential_source, + subject_token_supplier=subject_token_supplier, client_id=client_id, client_secret=client_secret, quota_project_id=quota_project_id, @@ -432,6 +453,7 @@ def test_from_info_full_options(self, mock_init): client_id=CLIENT_ID, client_secret=CLIENT_SECRET, credential_source=self.CREDENTIAL_SOURCE_TEXT, + subject_token_supplier=None, quota_project_id=QUOTA_PROJECT_ID, workforce_pool_user_project=None, universe_domain=DEFAULT_UNIVERSE_DOMAIN, @@ -460,6 +482,38 @@ def test_from_info_required_options_only(self, mock_init): client_id=None, client_secret=None, credential_source=self.CREDENTIAL_SOURCE_TEXT, + subject_token_supplier=None, + quota_project_id=None, + workforce_pool_user_project=None, + universe_domain=DEFAULT_UNIVERSE_DOMAIN, + ) + + @mock.patch.object(identity_pool.Credentials, "__init__", return_value=None) + def test_from_info_supplier(self, mock_init): + supplier = TestSubjectTokenSupplier() + + credentials = identity_pool.Credentials.from_info( + { + "audience": AUDIENCE, + "subject_token_type": SUBJECT_TOKEN_TYPE, + "token_url": TOKEN_URL, + "subject_token_supplier": supplier, + } + ) + + # Confirm identity_pool.Credentials instantiated with expected attributes. + assert isinstance(credentials, identity_pool.Credentials) + mock_init.assert_called_once_with( + audience=AUDIENCE, + subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + token_info_url=None, + service_account_impersonation_url=None, + service_account_impersonation_options={}, + client_id=None, + client_secret=None, + credential_source=None, + subject_token_supplier=supplier, quota_project_id=None, workforce_pool_user_project=None, universe_domain=DEFAULT_UNIVERSE_DOMAIN, @@ -489,6 +543,7 @@ def test_from_info_workforce_pool(self, mock_init): client_id=None, client_secret=None, credential_source=self.CREDENTIAL_SOURCE_TEXT, + subject_token_supplier=None, quota_project_id=None, workforce_pool_user_project=WORKFORCE_POOL_USER_PROJECT, universe_domain=DEFAULT_UNIVERSE_DOMAIN, @@ -524,6 +579,7 @@ def test_from_file_full_options(self, mock_init, tmpdir): client_id=CLIENT_ID, client_secret=CLIENT_SECRET, credential_source=self.CREDENTIAL_SOURCE_TEXT, + subject_token_supplier=None, quota_project_id=QUOTA_PROJECT_ID, workforce_pool_user_project=None, universe_domain=DEFAULT_UNIVERSE_DOMAIN, @@ -553,6 +609,7 @@ def test_from_file_required_options_only(self, mock_init, tmpdir): client_id=None, client_secret=None, credential_source=self.CREDENTIAL_SOURCE_TEXT, + subject_token_supplier=None, quota_project_id=None, workforce_pool_user_project=None, universe_domain=DEFAULT_UNIVERSE_DOMAIN, @@ -583,6 +640,7 @@ def test_from_file_workforce_pool(self, mock_init, tmpdir): client_id=None, client_secret=None, credential_source=self.CREDENTIAL_SOURCE_TEXT, + subject_token_supplier=None, quota_project_id=None, workforce_pool_user_project=WORKFORCE_POOL_USER_PROJECT, universe_domain=DEFAULT_UNIVERSE_DOMAIN, @@ -633,7 +691,29 @@ def test_constructor_invalid_credential_source(self): with pytest.raises(ValueError) as excinfo: self.make_credentials(credential_source="non-dict") - assert excinfo.match(r"Missing credential_source") + assert excinfo.match( + r"Invalid credential_source. The credential_source is not a dict." + ) + + def test_constructor_invalid_no_credential_source_or_supplier(self): + with pytest.raises(ValueError) as excinfo: + self.make_credentials() + + assert excinfo.match( + r"A valid credential source or a subject token supplier must be provided." + ) + + def test_constructor_invalid_both_credential_source_and_supplier(self): + supplier = TestSubjectTokenSupplier() + with pytest.raises(ValueError) as excinfo: + self.make_credentials( + credential_source=self.CREDENTIAL_SOURCE_TEXT, + subject_token_supplier=supplier, + ) + + assert excinfo.match( + r"Identity pool credential cannot have both a credential source and a subject token supplier." + ) def test_constructor_invalid_credential_source_format_type(self): credential_source = {"format": {"type": "xml"}} @@ -1297,3 +1377,78 @@ def test_refresh_with_retrieve_subject_token_error_url(self): self.CREDENTIAL_URL, "not_found" ) ) + + def test_retrieve_subject_token_supplier(self): + supplier = TestSubjectTokenSupplier(subject_token=JSON_FILE_SUBJECT_TOKEN) + + credentials = self.make_credentials(subject_token_supplier=supplier) + + subject_token = credentials.retrieve_subject_token(None) + + assert subject_token == JSON_FILE_SUBJECT_TOKEN + + def test_retrieve_subject_token_supplier_correct_context(self): + supplier = TestSubjectTokenSupplier( + subject_token=JSON_FILE_SUBJECT_TOKEN, + expected_context=external_account.SupplierContext( + SUBJECT_TOKEN_TYPE, AUDIENCE + ), + ) + + credentials = self.make_credentials(subject_token_supplier=supplier) + + credentials.retrieve_subject_token(None) + + def test_retrieve_subject_token_supplier_error(self): + expected_exception = exceptions.RefreshError("test error") + supplier = TestSubjectTokenSupplier(subject_token_exception=expected_exception) + + credentials = self.make_credentials(subject_token_supplier=supplier) + + with pytest.raises(exceptions.RefreshError) as excinfo: + credentials.refresh(self.make_mock_request(token_data=JSON_FILE_CONTENT)) + + assert excinfo.match("test error") + + def test_refresh_success_supplier_with_impersonation_url(self): + # Initialize credentials with service account impersonation and a supplier. + supplier = TestSubjectTokenSupplier(subject_token=JSON_FILE_SUBJECT_TOKEN) + credentials = self.make_credentials( + subject_token_supplier=supplier, + service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + scopes=SCOPES, + ) + + self.assert_underlying_credentials_refresh( + credentials=credentials, + audience=AUDIENCE, + subject_token=TEXT_FILE_SUBJECT_TOKEN, + subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + service_account_impersonation_url=SERVICE_ACCOUNT_IMPERSONATION_URL, + basic_auth_encoding=None, + quota_project_id=None, + used_scopes=SCOPES, + scopes=SCOPES, + default_scopes=None, + ) + + def test_refresh_success_supplier_without_impersonation_url(self): + # Initialize supplier credentials without service account impersonation. + supplier = TestSubjectTokenSupplier(subject_token=JSON_FILE_SUBJECT_TOKEN) + credentials = self.make_credentials( + subject_token_supplier=supplier, scopes=SCOPES + ) + + self.assert_underlying_credentials_refresh( + credentials=credentials, + audience=AUDIENCE, + subject_token=TEXT_FILE_SUBJECT_TOKEN, + subject_token_type=SUBJECT_TOKEN_TYPE, + token_url=TOKEN_URL, + basic_auth_encoding=None, + quota_project_id=None, + used_scopes=SCOPES, + scopes=SCOPES, + default_scopes=None, + ) From 51d84fe6df20833f4c7f2f53629327ff20c7915e Mon Sep 17 00:00:00 2001 From: Jin Date: Mon, 18 Mar 2024 15:02:04 -0700 Subject: [PATCH 821/966] chore: update token (#1502) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index b5f624b67ec419206a4941e688edc6394f067ab5..1c64ee479d0a6b1a6654651d2a011ef4a180bd02 100644 GIT binary patch literal 10324 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 m)li*;5|?{;0PHG_Kta+Cdmy0g4CJm9!bdn literal 10324 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 mSX`<+ClDs Date: Mon, 18 Mar 2024 15:25:24 -0700 Subject: [PATCH 822/966] fix: refactor tech debt in aws and identity pool credentials (#1501) * fix: implement fixes suggested in suppliers PR * Add back elif * update const name to include unit --- packages/google-auth/google/auth/aws.py | 30 ++++++++++--------- .../google-auth/google/auth/identity_pool.py | 3 +- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/packages/google-auth/google/auth/aws.py b/packages/google-auth/google/auth/aws.py index 14ac8fc9ab6e..28c065d3c751 100644 --- a/packages/google-auth/google/auth/aws.py +++ b/packages/google-auth/google/auth/aws.py @@ -69,6 +69,8 @@ _DEFAULT_AWS_REGIONAL_CREDENTIAL_VERIFICATION_URL = ( "https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15" ) +# IMDSV2 session token lifetime. This is set to a low value because the session token is used immediately. +_IMDSV2_SESSION_TOKEN_TTL_SECONDS = "300" class RequestSigner(object): @@ -476,9 +478,9 @@ def get_aws_region(self, context, request): else response.data ) - if response.status != 200: + if response.status != http_client.OK: raise exceptions.RefreshError( - "Unable to retrieve AWS region", response_body + "Unable to retrieve AWS region: {}".format(response_body) ) # This endpoint will return the region in format: us-east-2b. @@ -487,16 +489,19 @@ def get_aws_region(self, context, request): def _get_imdsv2_session_token(self, request): if request is not None and self._imdsv2_session_token_url is not None: - headers = {"X-aws-ec2-metadata-token-ttl-seconds": "300"} + headers = { + "X-aws-ec2-metadata-token-ttl-seconds": _IMDSV2_SESSION_TOKEN_TTL_SECONDS + } imdsv2_session_token_response = request( url=self._imdsv2_session_token_url, method="PUT", headers=headers ) - if imdsv2_session_token_response.status != 200: + if imdsv2_session_token_response.status != http_client.OK: raise exceptions.RefreshError( - "Unable to retrieve AWS Session Token", - imdsv2_session_token_response.data, + "Unable to retrieve AWS Session Token: {}".format( + imdsv2_session_token_response.data + ) ) return imdsv2_session_token_response.data @@ -545,7 +550,7 @@ def _get_metadata_security_credentials( if response.status != http_client.OK: raise exceptions.RefreshError( - "Unable to retrieve AWS security credentials", response_body + "Unable to retrieve AWS security credentials: {}".format(response_body) ) credentials_response = json.loads(response_body) @@ -593,7 +598,7 @@ def _get_metadata_role_name(self, request, imdsv2_session_token): if response.status != http_client.OK: raise exceptions.RefreshError( - "Unable to retrieve AWS role name", response_body + "Unable to retrieve AWS role name {}".format(response_body) ) return response_body @@ -690,7 +695,7 @@ def __init__( "regional_cred_verification_url" ) - # Get the environment ID. Currently, only one version supported (v1). + # Get the environment ID, i.e. "aws1". Currently, only one version supported (1). matches = re.match(r"^(aws)([\d]+)$", environment_id) if matches: env_id, env_version = matches.groups() @@ -701,7 +706,7 @@ def __init__( raise exceptions.InvalidResource( "No valid AWS 'credential_source' provided" ) - elif int(env_version or "") != 1: + elif env_version is None or int(env_version) != 1: raise exceptions.InvalidValue( "aws version '{}' is not supported in the current build.".format( env_version @@ -784,15 +789,12 @@ def retrieve_subject_token(self, request): request_headers["x-goog-cloud-target-resource"] = self._target_resource # Serialize AWS signed request. - # Keeping inner keys in sorted order makes testing easier for Python - # versions <=3.5 as the stringified JSON string would have a predictable - # key order. aws_signed_req = {} aws_signed_req["url"] = request_options.get("url") aws_signed_req["method"] = request_options.get("method") aws_signed_req["headers"] = [] # Reformat header to GCP STS expected format. - for key in sorted(request_headers.keys()): + for key in request_headers.keys(): aws_signed_req["headers"].append( {"key": key, "value": request_headers[key]} ) diff --git a/packages/google-auth/google/auth/identity_pool.py b/packages/google-auth/google/auth/identity_pool.py index 5526e775cfb3..a9ec57733461 100644 --- a/packages/google-auth/google/auth/identity_pool.py +++ b/packages/google-auth/google/auth/identity_pool.py @@ -41,7 +41,6 @@ except ImportError: # pragma: NO COVER from collections import Mapping import abc -import io import json import os from typing import NamedTuple @@ -104,7 +103,7 @@ def get_subject_token(self, context, request): if not os.path.exists(self._path): raise exceptions.RefreshError("File '{}' was not found.".format(self._path)) - with io.open(self._path, "r", encoding="utf-8") as file_obj: + with open(self._path, "r", encoding="utf-8") as file_obj: token_content = _TokenContent(file_obj.read(), self._path) return _parse_token_data( From 310e26643ffab5447f18326eb2a5f623e7edea02 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Wed, 20 Mar 2024 10:20:38 -0700 Subject: [PATCH 823/966] chore(main): release 2.29.0 (#1498) --- packages/google-auth/CHANGELOG.md | 12 ++++++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 0f64fdf342ae..69663529d36c 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,18 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.29.0](https://github.com/googleapis/google-auth-library-python/compare/v2.28.2...v2.29.0) (2024-03-18) + + +### Features + +* Adds support for custom suppliers in AWS and Identity Pool credentials ([#1496](https://github.com/googleapis/google-auth-library-python/issues/1496)) ([3af1768](https://github.com/googleapis/google-auth-library-python/commit/3af176879fc356f785b9f5b449ce4270f437db7e)) + + +### Bug Fixes + +* Refactor tech debt in aws and identity pool credentials ([#1501](https://github.com/googleapis/google-auth-library-python/issues/1501)) ([ce435b0](https://github.com/googleapis/google-auth-library-python/commit/ce435b0b13f43ef9258c8693f6c3beac06da64e2)) + ## [2.28.2](https://github.com/googleapis/google-auth-library-python/compare/v2.28.1...v2.28.2) (2024-03-08) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 0959c754195e..f0dd919dca6d 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.28.2" +__version__ = "2.29.0" From 16e9ab290afe71415381b3bfb9b1874c73483966 Mon Sep 17 00:00:00 2001 From: Jin Date: Thu, 21 Mar 2024 14:42:14 -0700 Subject: [PATCH 824/966] chore: update token (#1504) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 1c64ee479d0a6b1a6654651d2a011ef4a180bd02..b24ceb7fb61495587f7c6a24d886b160e339f4df 100644 GIT binary patch literal 10324 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 literal 10324 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 m)li*;5|?{;0PHG_Kta+Cdmy0g4CJm9!bdn From b8633dc6613619ae67c37040e5fb46eb9b4a19aa Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Thu, 28 Mar 2024 11:26:40 -0700 Subject: [PATCH 825/966] fix: fix id_token iam endpoint for non-gdu service credentials (#1506) * fix: fix id_token iam endpoint for non-gdu service credentials * chore: address comments --- packages/google-auth/google/auth/iam.py | 21 +++++++++++-- .../google/auth/impersonated_credentials.py | 29 +++++------------- packages/google-auth/google/oauth2/_client.py | 11 +++---- .../google/oauth2/service_account.py | 7 ++++- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../tests/compute_engine/test_credentials.py | 4 +-- .../google-auth/tests/oauth2/test__client.py | 13 ++++++-- .../tests/oauth2/test_service_account.py | 29 +++++++++++++++++- 8 files changed, 77 insertions(+), 37 deletions(-) diff --git a/packages/google-auth/google/auth/iam.py b/packages/google-auth/google/auth/iam.py index e9df84417816..bba1624c1645 100644 --- a/packages/google-auth/google/auth/iam.py +++ b/packages/google-auth/google/auth/iam.py @@ -27,8 +27,23 @@ from google.auth import crypt from google.auth import exceptions -_IAM_API_ROOT_URI = "https://iamcredentials.googleapis.com/v1" -_SIGN_BLOB_URI = _IAM_API_ROOT_URI + "/projects/-/serviceAccounts/{}:signBlob?alt=json" + +_IAM_SCOPE = ["https://www.googleapis.com/auth/iam"] + +_IAM_ENDPOINT = ( + "https://iamcredentials.googleapis.com/v1/projects/-" + + "/serviceAccounts/{}:generateAccessToken" +) + +_IAM_SIGN_ENDPOINT = ( + "https://iamcredentials.googleapis.com/v1/projects/-" + + "/serviceAccounts/{}:signBlob" +) + +_IAM_IDTOKEN_ENDPOINT = ( + "https://iamcredentials.googleapis.com/v1/" + + "projects/-/serviceAccounts/{}:generateIdToken" +) class Signer(crypt.Signer): @@ -67,7 +82,7 @@ def _make_signing_request(self, message): message = _helpers.to_bytes(message) method = "POST" - url = _SIGN_BLOB_URI.format(self._service_account_email) + url = _IAM_SIGN_ENDPOINT.format(self._service_account_email) headers = {"Content-Type": "application/json"} body = json.dumps( {"payload": base64.b64encode(message).decode("utf-8")} diff --git a/packages/google-auth/google/auth/impersonated_credentials.py b/packages/google-auth/google/auth/impersonated_credentials.py index d32e6eb69a58..3c6f8712a9b8 100644 --- a/packages/google-auth/google/auth/impersonated_credentials.py +++ b/packages/google-auth/google/auth/impersonated_credentials.py @@ -34,32 +34,15 @@ from google.auth import _helpers from google.auth import credentials from google.auth import exceptions +from google.auth import iam from google.auth import jwt from google.auth import metrics -_IAM_SCOPE = ["https://www.googleapis.com/auth/iam"] - -_IAM_ENDPOINT = ( - "https://iamcredentials.googleapis.com/v1/projects/-" - + "/serviceAccounts/{}:generateAccessToken" -) - -_IAM_SIGN_ENDPOINT = ( - "https://iamcredentials.googleapis.com/v1/projects/-" - + "/serviceAccounts/{}:signBlob" -) - -_IAM_IDTOKEN_ENDPOINT = ( - "https://iamcredentials.googleapis.com/v1/" - + "projects/-/serviceAccounts/{}:generateIdToken" -) _REFRESH_ERROR = "Unable to acquire impersonated credentials" _DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds -_DEFAULT_TOKEN_URI = "https://oauth2.googleapis.com/token" - def _make_iam_token_request( request, principal, headers, body, iam_endpoint_override=None @@ -83,7 +66,7 @@ def _make_iam_token_request( `iamcredentials.googleapis.com` is not enabled or the `Service Account Token Creator` is not assigned """ - iam_endpoint = iam_endpoint_override or _IAM_ENDPOINT.format(principal) + iam_endpoint = iam_endpoint_override or iam._IAM_ENDPOINT.format(principal) body = json.dumps(body).encode("utf-8") @@ -225,7 +208,9 @@ def __init__( # added to refresh correctly. User credentials cannot have # their original scopes modified. if isinstance(self._source_credentials, credentials.Scoped): - self._source_credentials = self._source_credentials.with_scopes(_IAM_SCOPE) + self._source_credentials = self._source_credentials.with_scopes( + iam._IAM_SCOPE + ) # If the source credential is service account and self signed jwt # is needed, we need to create a jwt credential inside it if ( @@ -290,7 +275,7 @@ def _update_token(self, request): def sign_bytes(self, message): from google.auth.transport.requests import AuthorizedSession - iam_sign_endpoint = _IAM_SIGN_ENDPOINT.format(self._target_principal) + iam_sign_endpoint = iam._IAM_SIGN_ENDPOINT.format(self._target_principal) body = { "payload": base64.b64encode(message).decode("utf-8"), @@ -425,7 +410,7 @@ def with_quota_project(self, quota_project_id): def refresh(self, request): from google.auth.transport.requests import AuthorizedSession - iam_sign_endpoint = _IAM_IDTOKEN_ENDPOINT.format( + iam_sign_endpoint = iam._IAM_IDTOKEN_ENDPOINT.format( self._target_credentials.signer_email ) diff --git a/packages/google-auth/google/oauth2/_client.py b/packages/google-auth/google/oauth2/_client.py index d2af6c8aa85b..bce797b88bb0 100644 --- a/packages/google-auth/google/oauth2/_client.py +++ b/packages/google-auth/google/oauth2/_client.py @@ -39,10 +39,6 @@ _JSON_CONTENT_TYPE = "application/json" _JWT_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer" _REFRESH_GRANT_TYPE = "refresh_token" -_IAM_IDTOKEN_ENDPOINT = ( - "https://iamcredentials.googleapis.com/v1/" - + "projects/-/serviceAccounts/{}:generateIdToken" -) def _handle_error_response(response_data, retryable_error): @@ -328,12 +324,15 @@ def jwt_grant(request, token_uri, assertion, can_retry=True): return access_token, expiry, response_data -def call_iam_generate_id_token_endpoint(request, signer_email, audience, access_token): +def call_iam_generate_id_token_endpoint( + request, iam_id_token_endpoint, signer_email, audience, access_token +): """Call iam.generateIdToken endpoint to get ID token. Args: request (google.auth.transport.Request): A callable used to make HTTP requests. + iam_id_token_endpoint (str): The IAM ID token endpoint to use. signer_email (str): The signer email used to form the IAM generateIdToken endpoint. audience (str): The audience for the ID token. @@ -346,7 +345,7 @@ def call_iam_generate_id_token_endpoint(request, signer_email, audience, access_ response_data = _token_endpoint_request( request, - _IAM_IDTOKEN_ENDPOINT.format(signer_email), + iam_id_token_endpoint.format(signer_email), body, access_token=access_token, use_json=True, diff --git a/packages/google-auth/google/oauth2/service_account.py b/packages/google-auth/google/oauth2/service_account.py index 04fd7797ada3..0e12868f14b5 100644 --- a/packages/google-auth/google/oauth2/service_account.py +++ b/packages/google-auth/google/oauth2/service_account.py @@ -77,6 +77,7 @@ from google.auth import _service_account_info from google.auth import credentials from google.auth import exceptions +from google.auth import iam from google.auth import jwt from google.auth import metrics from google.oauth2 import _client @@ -595,8 +596,11 @@ def __init__( self._universe_domain = credentials.DEFAULT_UNIVERSE_DOMAIN else: self._universe_domain = universe_domain + self._iam_id_token_endpoint = iam._IAM_IDTOKEN_ENDPOINT.replace( + "googleapis.com", self._universe_domain + ) - if universe_domain != credentials.DEFAULT_UNIVERSE_DOMAIN: + if self._universe_domain != credentials.DEFAULT_UNIVERSE_DOMAIN: self._use_iam_endpoint = True if additional_claims is not None: @@ -792,6 +796,7 @@ def _refresh_with_iam_endpoint(self, request): jwt_credentials.refresh(request) self.token, self.expiry = _client.call_iam_generate_id_token_endpoint( request, + self._iam_id_token_endpoint, self.signer_email, self._target_audience, jwt_credentials.token.decode(), diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index b24ceb7fb61495587f7c6a24d886b160e339f4df..316c960e08935d476cb13cd0a1b4017b33f1eed1 100644 GIT binary patch literal 10324 zcmV-aD67{BB>?tKRTFZ(Q~Y9CRsVm&)MHv^Cy%%(_1~;3qq)kEUEIJZcTp0mPyjE~ zv){XH0NtRu(L;-><^7v!kyS@M!cHX@<_z)0(Q}Mm0ki~R_k_naLloqPhRu5#yaBn! z)Nvq8xw5ed26FX{)awN{p!2y$F^ ztXRlO&@Q)aP;n}so_VEZ(F|BE5?+%A?5`45wmAE+x~*j3X;r8xN~%sA_y;&_fGt^wOc1&=+|Ymj>2wpDy|GpI_<#0Zo5lqjt=XK~ z1pt>;2ygD)4Lmj2MR|fIkE;=>t5brgvc%-qhFZ>c%1o1$=?UGlQRwBq=+@7nt{k1R zHrX9PM2+H7D3}LSRF9I55l6gn(zbvOJoR_xdjUeLS9aTQT4&pl?eIpNcp{1c01D1j zH_4YMm6|I(%Non1%b%r|=UE}+si{Z=BezX9K4F8o>O9o0)O$LSpk+yyd*fJ189 zAzEY+NoLwbx;~q>F zavR~1DXYu@4F(a=p|C#a=m~K=Ia%^hq7t=vZ+QMRHQ-GkZbY#};IyuYC4|x3AOM?^ zD;Ogfx>}Jqy-ze3>ve(6zy)B-{K>FZfr{J+DwTG)+??92JoPCHEDYs#u_rn$RQN${ z(|0WvJJ0mC-xeW%sGq{dmVxb2TK5ZriU@aoY^qY0J)0NoPKY3stWqALapy0czHPk@ zUWs*-H+-%{StAYFkxnLtijx_eHeIC9@$~> z&j^hezEc`~Ur$;Ra{_*%khLUh9#g|%Ljo?evYon~vKviS5m9@v)5Vi6`6O}UU^y(Q z?8bkV@r`?vEwY|fr^ZfU1Z%PGH-V=r=NlSq(Evmgkyq8;;L9A#pisXN@ z{wV*N!PEZZ%eq!j(Bo{)LGyzfR(WAw^(BZ-FpMa=cf$v1N$E=UqL74cbeZ|+8YtB} zv_#P4wL5#|^!5=*%`h4Lwv(zel}!J`AEqUFC@uP9VR(gJ@FF zYmG!`k;{5dEEMkJTtYKec=&fIFEsV5jLDBEe%4wqX3UQ@)6HyV#K#itX|1w2h?F^dFZ2Y~I{0Ko(aThb@mI*5zOr?M z5vG?N6@#SNQG?EUXo~hY!esA2@T4hIrI9ey>i>ThZo;$TS6OTvEBU!W@}e7w&f{S? z9B{{Dyz}8vN^sNV5H$>m<3Q(EKw0J3OS4Z7`+{e1(&k$4%D>iKu1~|N=56Ow2h1JA zdLtec9Q74wc7K^90gmwLW0c5vayG;?d|_63vdOL;K|ZRYpCt1Y7&~IXMXb`brIMQ0 z7A9mYTS1Gu!LThu2I>n1tj)-5y5T>VS)=&K3_QQEF;WLi@N?22Zyn@y1ts3CqUu_x zGCt%ZYOZZlqtb*?VGquuWBC4>@m8!Vzul^e(kw9C_M*}N^T0%PP> zE#a`Bcg^0_mi8coezRGomO6=B{N8!c33cTwVO}37jo$N~7)T>PHV-2dhpLQxkO4T) zm9(UvQ`mY>Q^V3xEiw)PDh(kE>_SBqzO>BK69dy?r2#OT&qYsKR;=eKN}dwURuR$! z^kKe4r=oFcL$_VpEIcdXQsZ-m0ge3Rn|SKW>`wdz(1aWhyZr^;zeGbmq6_5Y(Fwf; zIq6*EIR^sGWG?0P8U%)kBu|q0?3+1s_Ya$wnFxP_BSz7ARRyw{VkU;^m@Kk{}u~9Xw3iw(la8(_}I-tPyfee zKWd1h`|>)=v+F>~}9P(g#NRO8o zbMk`wZX}`$Qe9QLIPDF4tbHgXj$;Jwu^O4724dnYbvUU!I`ihDKa@pOl71l->WS zRqi{Gio+^mqO`mi-xH1maEf!A={PrOpQQvrT3~%W5zgc9sZ4*SAFtF<@yfdfEbCT;q-M7u}h;FC81Vhop`eDu3DY21{;TwXn18 z(Rj-ggmwiAc>pP(rUp9?+x-)j*uA_<qq>^~n-eQWQ{$6=$C^(sZ>0a0;7Gmer$?sv zpA&?{H#zT_iAB>+k2)$OBOsb0DJ7v*m|o!lN6aK3RZ3rwYCy-R)5%g;gV-4Atks~; zbYnsWUi>h}OxDx8qWFYRxqgmCqT;b58MS^x(+%hSA&}DoXR#$a$eKN1m{(Tl1+%&A zW^4_W<&?Q;*{$f{xr@9uTRx%j1B`&S!PafGlj0O9!$!$9}k00xLlcDYFo#iGjyUYNzT+pT#j+7C>Me-*NK^=6xU%jyklsfu7bBpJZIUXYpJR1 zK5(=>v=-aC9eQuV29`}nC!H$j<2a7FyYzcZ#9^uhb5oa=3^=Me)0L|Ym+6Q|aB;0X zi3u<9kEW8ZM<=6G_ouAj&eZ{Vg%5V6Hb-QGds7W?1Kj*VEG3vPB{t>6y*0H2QL_P1 zAMDy$lByaVJ)XVKR}t4FT)3GZw-PRE$N@mS!s(Tea5fb2X~d2FOc7JW@I`};T;yu}x{SY2USe-;ocDG6ZUj6Q5)*)Y0W3^&33`zT z>~-2saT=pWm2a9@r%uNcI@^$pMtG35a>>~K>rqY??hqSI#}>o!jn(p#E~V#OdxeT8 z1Jl=}P>OEK>PJ1(9*f~+Rra33v_I#}7(HXB7-#-aR@!ILqrHA`(Yf*yDN}zvV8nCN zU-~%wsc!|tO^e{)rRyQuK2#R+7%jLG9achrO8(hUfhXy==8=JZK`h?ZyV(JA^JaCd z(^>JBhGJsn2#xzyh;}Q1$^`kJ!?fM0i{mXCs6K7_uyL6tMN2Dw60_e2h~;3CE*=i< z@uJih`QvBm;23vK26g!rPuQ;oVq=5Vap;R_)PhN-jm`;mY#X*4enzYgXdQ)&So_b4 z&Bcc!FpI-vkj{+koCikF6D1M!mO$3y;#H%I2Rox#;DOS4M}`+}fO5ARLwc0XGXdn~BU?Y45?Yb`&?`z=aV3JhyU9Q_7x5Xk&r3%5;cvRQEKq6<_nMkDz>W*R%HbMg4qLn04mi?XKjL^}68Fa{T zdiuDir)5yn;ilkUM;XAj^Sg~&ZkFs`iX-Fz_D2M^?J2e4j@kpmsf5@1kR1Y!YMq~O^pxRez21kVRR+Tj!p)HL@=v&hnUOc33!z; zt~lr}IBHG;pwfX}rAG(@Z}U|z3BOaEo~C`QJ|DG+@$4vY+XFdO-8Ke5D2o^ z83QXwR#1``8LGdegX*A~dVx_SHckaX0rS;a{bo5ou^5344kj&&9ouW88k^z=ed-(z z8MLY4t5??Yee)_Mlq&k#9Hfn@!Z$J7gr`L|V308za;=d`LmRbTnEF!On5~5m8Ldys z?43Ary;jVpKb+;!h+7C#*O;-ZQ=od7>aC)1tveoLV3s7}%*QC$a+$M}JM0>!D{#YU zePZDgH6JN0cUzX};zW@j;2B0%oXI*>WN#(uUd2&A9PEKbn^raA!V0Jpq4mVcOmt}n z9lVtTq%aEv!ooB?gtAK0cmRokD@7B%Yz!y5P^Ohw)Pc6);U#VpR0nIjRruHmOGgpJ zFmUs^{*_Eg{xS8W++i~2-fRp=)2iq+>_6)?{cQ`FdT5yaOL_wU(lXfv0~P)`?zTEr zF$4j0vkEaM&XHXOY2B;x##8SYMa&9@KCjDJvVlizt{0rI-<38}xfpm!-k`%$y;&Qo z!i*cSb+#X(N7iO?dI4%4JBT`D6Mo#Pj`ja3^@LtkwF9xM$3O<)yE|}E@CoyhE2qB> zl3^{k5&8oTR|eLng;E(k=_4FgKiW6m-^e7qCg`IK=Y8#x1jGqRCgdL_MT6HtKTLYK zc_mJgx~fs!lXt;VHB8-;wjv~**F2-SICzW&q2AMK9m{5gK?^+yXs>qmH;fB+0SZxT zO@AK2Sw`MJ5TKei_{px%i9QZ`&BoC_eD&N*Vr%46Ap!-oxp*~$dr}pfIYu;w?|Ctc z8Ysn)05P&PI?CguBSw#g7LZ*f`2a<%4(l~$Wuw77KFV>*PCXpwg5%g1OTFK~ETSKU zJWzoxhCz zZ57*ZlR8KVmQH4?vyE5(FDk7J2m{ssM%p$t)(hcaAiM)yONZt`+e4#_ zFRisqd02G5Q@Ab9#y3`1CBJM)tEN6YRmj&Qy0k$W7j~wB3D8B!7=g&r71l;$yseW` zMgJQnDb&WB+#IO50fz!nn0yOgV_}v4;~d8=W{wk#yxUnZu73dRMGYgd_(I!+z2!m(M}_m zK&cpXZ=kwQPGDMkHr6QXzYM%XvH17?*zwb*tS)-4rsdVrApGmB0G+KL@m8{McEr}u z?m$^#Lw1}lS1et~rFs4oOK)MV3|tx&tc)NR9M;FA9}Qx5kGF*9MfGN(DD?c<%BO!Y`8dXCWL% zh#m{=PfZ5RgL?G56dX1LV&ML>U$T>hrYf=r1O!46m80CFOD~@$Y~?=Kp$lhpTSY_9 ztI~=EZCmm95;ouqOsj))8wiDR$C#YMpmieK=bCZq1iW7+So{Ni7&;=PLKY%&kH${4 z%T6o!=8l?;YtC2%!<^Zj|NAd|x0$t8hbYYm78QjsIJ0+yEgvu^Ob`%lN)$@~3pbrC zE+00_tVSU&OFRLNSg~?2OjT`^kV1?(>Oc>-!VkY#95z2j`wGlZbJgIFOR_eiaE_)* z-=y&g0JC3`NpqY(YROkHVzW78N6b|MbQ^;;GN;_G*cxjBp<#;s%@cDihE+90$K<44 zJCU09T=T!9S?xC~;mvUsGPyj4!Ye0WM&0KGB}%$lZCK_6cVA1X+=VUyTThxoc z!6c-_ksq6FD?T)DY+9(Yaep?N{A2~DHnT{7IGohEL8C?97(ic|iV%CEATF69tIJB1 zjKG4!^Hdw=d~3Tkszw%eLs06N^9dQJE-o*lzLC##oxW>PyNX?VH!u!?alW2(sGNP+ zN1F0Ob9K9jy1ckMLf&4<0>>S|C?guOMG>i_4-van)ifk8F-3^tpMiJm{W&yFFZ4MQ zEx>f@qPXjw4FpYp{c?qe5gvHehj$j|n4k$xypAwHZ#b)KzFPpZEUgd;j@-)d!d`)4 z|J#9%5`WUGvh?9Z2N~8Bl!0*LfyIRR?GGi9uTl!^CNrl_XmuvG-S(#2>=HFH=*Z69 z9;gawiDbAONvodLRKtZPhGu^f_9{o7CNe~3a4MyxylPo)d$XwM8>u~kRlEWtOQ`pfmANx=m>zXVei)*qpLoig=0nhU9a)g}maiD6)vd0Mc|u zXmg}E&hVu6#h7D#LYP_ItmUvorg7eJb=YjXb!=vR^1LtXS&d`fL$yj8#%W0~K*B}E z!T}kom>?2EPrl_w)8A_)b&A+rv6j?=b>`m!nH~VS@ZYG^`khH5uoSffppyE`1Pn2wefn?3`wu$C@C=5n;WNKz3Ch@F zmGC4i&{As++DE}#8hg#iNl@@tvsEmr69KOD;)?djiX%HzDMBL?EFR;p!`m#f256JR zU11|)sx^8=rFA^Sl4egQ@N8f>{BfvKY$Rew(Eoj5YLY0td?+GrgkhePe7QR9GH%1$Y` zP#;R57GN|}RtavFG6Ze-QegUEhl=A!Z^31c)xz{QGUlZ~xx6~t(I;AQI!PO+ zHpdgm-Q;NIn$sYl6;$spir{$|fv(17@1{$~)J7on3*SpBD|@bZqa@`>Y_-5>r);8$ zEh%WiSZS5&Qeipi4vvK}A*MWm3YLX9no?*v&Q9tMh5U#f?@1v~gWm09uUN6lFhQf# zPs*wA$Ea{tLij~O!M8*^bu)LhJizh;LM{k!!+9jZE_cB7tuF03te{XhEZCtP?7c+S z{WymoFRa$dDS^7Hdt5077A-kV90;fw_N4}1rN%Z|oB_`6m+R5+4 zlj05a!btCXb*^IAFD^Stq@XcNU7jj70CdiZ>wHxYgO7G^B^Dg^09t%c#<{IdnGY*s z`~lAvxK&YrC^Y*>1z|)cV3(1Olk4@>ZQy@eNBIYXD+)adaVLf)R9xrXkClh>vn>X> zvH5$>n1tL>k#fgw+YZItdN%nx1=Zk9O4zC#8RG}1D5J+x< zkMDUrO54nTMYq8Xy>96ySOQ`|`n!+KT~2{2Ov5s4Dad8E^%Do>#gRkT23r69N+Smh zZtAr4D6+$Kqx{Ic10?4zPDh;s$Q*YB2~6j=?Z#4@iL#$N*IxF1G~x;u(q z0oGNDkxC|C*r-h|VZI{`n{6uja!6sot(&x%VYuNqs)JG;f|!)}Hcd!Xw*PfOvgd$V;Zk!H<5p3F&@k|%CbCB)+tPbv4bB{xa&{W%>B%2(0 z+V25TCOa=X>QZ$X0Kq$DAM;kAZvVUfmWrD!6w}n-tM+z!w7Z?soU6P^!6@5d45*sF z+gRIMCbgbyQMC_s_ulNUtVydMH_fIA!~57clw$Z66Jajgt>`RlZ@Q zT|1YrVp`80Yeyf;5y(S1OV_%5SGjCQTf`1EC461LHW;lq^>SDjoZcg}aD3LvpgT(! zl#3fw&H(^@+q&aaEDdB%=8Hl8E_u*H;$5C~(Zjk$M6<^!2ti={+!-w-MAbX!vOr!E zoW`K;;!?xgsc@HP##WD&kFhMBN#(VsNALLQP*Ura#YOKVV?Nl|`ENY9Y)eNf(szF| zBrNjeQh+TLer*tZTaKqWGiX1M{wpn{f8FzkWWE(%vh3Tv|7CvEkV>xh*dGC#f^G-4dDk?%arYSwpoa%=DXvCxrtRcEO5 z>TN*I<{3$1>b1<$DQ|Hm1433m>1;W6UL>hhudlz^w1lzvDFZS=pZ|**frfRcZ>FV) z1sA6u)y*KoDGL-4+RD^FgLwu%PW5AId9L?5jTOhWdDPA6)e2cxE?CDo*Qf)7wDA`2 zlm18ZrmRq>P&mtzNmbVe>ZVuCQoOb@XmnVCeAh~^t2l-%x~^&sv0`KDq;T{6g1b=~ zEU}^|m7e^~__?rt21mhPz-cY(wopVv#aF_gJ&3aepUVgY%o-><2y*z}CQ=t|_w-Db z+|2>)&8=X%kPUs`rTUau6(#t8ahw)1yQ~ohn1IAT))#^tx>?xbb7Xlexs)c`_(qD1*rK%r1&pKpR3|vD$J< zZH7sEQU=R)-Cq9vCCfyh3j*pxLc4hJ@pLV$xG{nEUBpJMM#<2PjVsC@Q6tNn0;lq# z2k2;gMH)mw3Nc({bCq~;r)*~&-%+$+^XL&u#=wPL_%iaNLKhvts{#sc^6xtTR%+W) z?#J6GE!cS8$M1VXZ`#%Z9h~sT6rCf=S)%Ln0L2nlxa)r|V&h;isn&A6w=kaFfL1k- z2IG?{>*HZhQRXxqJ@ZrkWCP{VvSBVagzKESoxl8M z1D@^3-uD@hv6j?txa&41;%mM^>nlMBBgH_s^0w6xT6>1}y(LrETHbBHM_BFVh}A0$ z+rFb##&+W$LR9WHhW+(FidXmXcT4wa&g=1*QTt(N&^thZ;*MC->d~@3^>BGJlx+8l z)yqe45tw@-WD6vupy+%QsOXbuO;x3DdKYtfwaff9Sp4$oFI8G>LRR#3?)jy=iqrT8I=nQ6wN9Kcc*UD@Z0R5 zEMi7W9n2BUN_7{RUzodOPtpiorMme%yyh~>`NgEiz{q;?1YzI}u_%*e3l%WaR(zA# zqF4XQTl&k<$?xF(#2as$I4$HP*U(3MNv6+O+27Oz(%(ae zhUEdi_Mqpw=$=B9*jhV{gswxGLgzDj|MdEwiVb~zLwY?*y@!4>sD+F;S$|h48r7*! z$}t()NM*C=f9xl+u(eJGNGEdfIgD3EcuNw=+)I-#OZ55m@Dnw8A~zj@R{&XvcVg`g zxCTFOxbSodgsP{$eU<#&NKP2%OgsVvpH(Ndc@7tz28>l!lALV+-}1ST!@?>gioU^L zX0JTo9SK6C6X;t>V`ws}#%EDzOXim}wq8R9QOG>5qh2O~r!*5Da|~}+ne?3^w#+Dj zug-BR2R_@Hms7r6oAHX$!NchyQ+hZ$oMviMju(sRBV>MfDk|1@T((P?S;Ly0&fj*} z?eY~rF2`}eFHSh+l|AFoRZSygU{+_nvv8nyui_P zJV{$FJiF?8e~i@iv^_bhY!$)M@Y}v{b-V)$HorSc%Nyg(9W1EO9yd}n*;jSd0&XN_ zMUHCwQoshN*4h!6@xK*9SU_tT-t zF60uWCa(D+*=Bz*fb|*T){5K4yi!}ZqhA*pq@f{LsxO7wU?qwyip_L41Z6HN1Cl==ln z*G5+i$+@Mdk6x_E*H5m!0e<3vA!AKe{*am`E9C@|CxC4o;fIe_@I%IYhbgJ92j_j` z%>^83*_G{J>pgQ@0a9oLBE6@uYYZFJ<_)KggM9lGgwLV_nZunB+vhORcJ>h}&gP&TSHM30ms1G_C-`cy}OUOd)CD;GV{Y4X`sJlR=by?tKRTIieWtHs^drWP{Q8Es+UyQv#jk*df^Rt^8+cGO*cg_;3PyjE~ zv)@hlhz^@PYfu@deDg}-qZ}IdwY%h%oVnA{RWMhZIZBWouOvtLwo6_S0g&XU=D#?G zuHidI^P4Z(toW^$FrSlHk)q1-1lb_p8uxDI|=jCtuHFm1i1-2x?E%8itEG^rkj^(ga#Wf zgmp+g(Mr`)F8uOi7V0HOP3NmS>LBUvJv*tnL5U0CzjN0RF|H4wmF~%y3w!2E&8d## zHHqab*?FU+ ze3TA>K$$?xC`*<3!1*SqziRQrKyk8jpZqP8N;{oy%ien`oSrG+MF(YN206-DAn+GQm$aN!h^tRJ+TxG~JB8SA*}dn5lQs>#o@0QJ2s$-Q6vbaZKfXoe?{{(JIDR&l8M5l^ za@_EEG*@Qk8LC7K?z**nXc0=5SsIP!8CSlm^AtW1wN;#I|4dPWX+%9^!U_!o#5pXV zapj%To@15Yl*qznO8yt>I@B;4`m1`Egc_jom`mD1ss-^2hkk@V$Zh#I*szN-4`H_R z(qs6~Srg|1NC1iIcw%BsP&2n28^u*yE0e`TbDQErFoM3}fCA4WffG+sdT>df?@bn-K5+h<1KcX zXJ~&*S8R&3&G#f%vD4!DdS`por>82%D=BT&abl{+lRSmE|1MdQQc}d6 zfECdl?WdJ>I2;Ml?qqkgBu0|T1okD1fFwu@$H_Bb4gHg-!#u92L+F6n{suiNCT*d2 z*J-VGgwzbZsxFWF!v&83!dr`SPMl2D~(B7o?N@Xdm8)PYLw_~lLj_E ziZ{khIyqRxN0gq(Bgc+gx%G$uZ17m9j;IzZuza%B?lNVm2Czubb`zR<6ZFI+%X>A7!))J-07BG zX$pa{%$O!*8j>hOCZvJrPa7qDZ3)MvFmyR?aY6Rm1z?26wJi}Ldz zDKl2m>k=cm4A6o@&=y~?uMpB|(7cr!lClQM%YBAK6pp6>&B<%0UXP2G!LLF!uP=Z{ z)c^cQ?@{J&u}EQxOxje}o5{Yq{&$KmOVEZZY5Kwhj3dnQ8{6!?AeXJo(Z($aReoYND7#eE{y?zt-|n$i*Uo!6>p zhkE^)qQBO^HynUuPYz)C+>0H1^i~b|P%4J}WS&MW?28S6F4 zhN(N3IFxd1lRCzZ{M%%VzEZNX!rvP#XzmP!uZ@g`WqHozbW6)WlwVX8g2@Ez`!7HS zblhaKPciqJSnov}7=p-MQ0(gZ`MyKrLnvW`rwu<}4qrrLHko_CEU}>3$tm6?tf+VK zg?AMtnxK}X8GoyB(%#Z;*ahlNcnE9=<_A_^s5wb!0iw%XwbQHbxeCoLwpt+dtyt zN@^KrLs6k?zLpZ;r6pck>qazs>NL|kHVdz@rKmlqh$J3W+SiyI`yJSeP8fiJ*t zw`>CUS_zVRyQKKwpxCU&SwC`F`TBbm&>JRm`hlwvJQ?9A^r5U?Y@!Z4t&uLifm#@0 zslL;9nh{embYl%@=FH1B=&-R|`+iGXzJO*9Hc5VgW&J_mqG`&SsvCd3X3O_iFRK+P z-r8$DwAq|U*3VmURfw%;do*}K!sDE~`CjNu9wx`nrobt{SKu-ZIY;d4$Uag+mr*(4 z>8S<1-sxg#oLBcn$u7$3k-SQG>ex&O|J?w32sjditkQx9m*Xgtgd{Usc4v-jF4y4l zy6iaFgC)jkc&Jj=m}gn<_c(%?Q6f8kugwf1;WG$}TbBC>b->@x3Khjm{0H72PL z8xdLyx6j^Rlw=V2#JFDEnX=%7Hb?2x0M;iy0CAV22O>pJPj}vi(aFbYMFQyvj~Nz4W@h6bilX zc-&*0#kf46MKbs&poMIhkW39ob+17~Taxj??*q!Jgc0`25HHy)qvZ$?pz+3JRb&0R z*dj0s4n3jI7h15A9S5@3E+LhOwqJUR$9*qvT9GE#==KZbyb_|N0}YP_mk|jz@9}`f zbKxB<352#8C!NnV%$YJrXY1B9Bf0MkwTd~C|1sY^Z4Z$EaAF6#AxcX*wt_fh*ePvn zgfR1o+xLDg{#A?WEUy!fmv|=w0qR6GZBqnGdm{#HSR}nDQ*~;__^mI=9qn9c zVfppFw;?Uqn*Q<-*PwH4fb38!r3667FTr<(I|{x0QObM(ov;OnGU!yVBreGx?R~Hq zGEne<>^m>P10yCDQS?^rh6RZO@;opV4*3C(;9OAF7N~hv>WS6!V?m0Z)BGWOAQkR* z(T7o@J=?anVmuuRu&vVcGcvv%5DGfI&*vN-RyXL8w7v4X>iB5I1UCFBsiiVC%?J96 zj_Zhq-f~R`J&r&ox^fL!!V==$ec|YA0eoCn)`p~ZANtxi&u3Ei^yVXGZgdDf-dWC7 z7=0>O*y!zX^pYBxAXAAgdOMKL<5TXU^&JjpcA~bPiy%ATk84WqpCHtK7~*{W>Aq0p z{~ON%IauadoO#3xqkqV|0<^GT!B^6pIO(E9qv36918gJ$<|!rT?FU)u*SiBC{&MAk z<3h&{;(29^NEK@2rAc-W($Z*`cw!v^v4j6ADd?eUygDaRx4JEqIqLHIyM8W50!<&P zc+H*?$}_jj;YQ9*dlehoT?jU-d-A)pkD%RQ(qy1#0v1T^aWf*n9ECmy+)B1$hUgBoRQoM+0U|S^F$Up@Ai3s(3>)jy0r@LbPE7h z+}4xc1R#)f`=3(Q(#TuMo`5zFKFA~m1{qWeTp|4K>loku&*CM6S%BDpV~r8U5~G3Zse?1OhO9Bk|P_G_t#Y~@Pf?? zgkBLay{v*{&vS#08t&LUT#-3`<2K1_&B0a4H-^E^P+b@glY&u#vHurA_YM(s*QDbd zWnMpfs=A96yZ)j+sg*M`@Rbh|m2#nsh(Q86k>4!d)g@%H7?VEe>N-w6J0=h)QX%`E zcj#A1qc8TDPfbH_fV7m3qCyMt&AXPV68?ql#Nn50?{3uI@0qcHsE2tMxgs44hgaY# z363l>Qp#+#k?r_+<=`VPZnR-m_MU8~AK$$f2WWt|$b4a9yN|kx5}T=v@-l0u<$q%1 zkqAWA1F#blkQwQxxFm4zuVC*B1$O~aa}Fw6N4s2n%Qn?kXE>Qg^Yl% zu>nke80tLZmFHb6O6{xGXYrX7*LO@o)#>iCQ_Jua1E4wYF@uspItRzcTk6oWmC~g< zJUR8Vivy6w(aH(uX|#ui6n@M(V=>6M6jT+y{`-p4i*)_-Cqm{zU0jHFjVa#`vzq8L zD7cKx3tk(w`;gSmoycK_p|`}R{N&uq(t*v!fcTaQPZTsvTK{V!td&=_lt;e3YKesb zX_0}SH>71;e%R{AZG;`q0U$6t6e2r`XEA+x#8P7=kOC$2Dl^>Q0kN-I2ha`(Ul3n| z;Ltn82Vu$tjV#^}^qwBSnyYJ2$XDlN5}7B2jK{kI!l;CybGNU#`?}b&l2|C%)P*%Z z{xfJPZnL6@`d^zR&w?ZtgvhKJFt6xVtP!sA zW6XXD1~)zis`ibjihX-fA>*+`dsrq^KxDKTbS_cyCX&JC6!jf;L<4+EWwRsIKnKaPo4^F5p+ zpGOReKZABZWv!?2O@ixm5sDCNXo1?udCcAg6@}6>FD0T8P(i?>29!+60R-2L;X_L6 zdfTUlj!bxUj>lS+Ka+yW87iYq1YMGKZJk$`B!-%rW0mN$B@%D^9++dSdVHM4$4|R@ z;IkWbJa0!j&m>Sz4=>(+zm}LDPHqzp_}B*d3LTk_cmy#A@%tom%AA4%xss!5O3}<# zkJ>JaZ%u|37vI$;n#vQd)4MOm2)Ky*1gT#()xpHjk=PHCBO2bv1Bbd>R(qKO}3Vo6%P|BTF&tWIfOH#@QtB95PZerM=X3=_q9h zwdU}z=ToylU6^T^#GJg(HNLgZ!#_OEf>>R6!Ps`mm%1znv&mP#xYSaUk(Bv{V&Y1a zHC@pp;P)hP8EZU+x2mKCQfN$8oiv4@)E3=l`>P6bKEn|NstwIlEu`4I8KV_|OAo-| zwCz1yO6_MN%jSa(LpH^)k${w+g0TS0ssDX9$O5@0g&8n88S?`UbdF@1%YWp8PCr&_(Q zO3zMEpKOZ^RANN@9%aDk_AU;^9_05R-RMp+lqEoLQW;A(rd?Y{5F=&#o1R%iD>!QM zS*qo4T}E%OzBuQ%|0KZ<5hYatjQn72F@c;E&S5l>76GnaaImZjwLJ^ES&!dV&a?0B z1tZ6yGuiv&gK5O{KjTIcrF3z=IpDA?dqG?yQwRn@bEFk;=!BAuQK&obHNbm3c3{F) z&W(BOHat>&#i;2|7~%q2_Ya8a>nUBjWE#T6C=S8mmG4A zl_qi1FpX(xVmCvob{1qhv>|_;=PyL5D?kqw0ks-p!Ft0#ouCd;LGT&HnJUw(bSJqQ zKf`wHUr0ngoxH02oW_O@<(#;=9!7W@Joc+NS3H&SL>aimzt*8!$U@s7S(??qrzkU# z2(IhC-RWFIF!WOS(YsEGid!ERR2uEpLY%!66oa8IF%*fHC~*#_;tn>f!q}6#X9V&d z#1!axN0AwL-ow=N$W+B-gx(33UwjH-2)JaaR?rw7eb35a@Z!I0uA0c{*k^#q zGXcc&n>zR(+v96s3CSKB%f%$exLBRd{VwB!qNo5y7>I%mif$}J=s-AQIU5;Mv#UTm zES|t#@&=e&_;j~yo}OkpZ1vL#LDcnR*R(k88Uhq3SUvQYH`7gdW5kv0+>#J;#}8mL z#;wycr3A;OKo&hE51jdFw2WL=&K}8V(4mC>%!BW0I-RWe7_mzle%Yq^POU`V`+ZPU zLMj<^a}tubIu-g1N@_xga5H*{HPIG2g>B=h;@=Ef9sJ@*j`jaKm47@iBv0e`66s1C zhl&?Ozc zi>$?mC0KIRadKS&w<)yfAPc z)!L|)u~dDuD<=e(ZlD4e;Im@!`5esAC^=4Od`8)826ZVv${iJ-b{{ppM1xnTX4P5S z)9|Z-`Wy&Kj{3AiYJi+lxy9yK$c~VsXBr~&ccODq`(PR(}S6xXW7h$G%y~Qz2ts04@B|ApfY|>zw>eE&~G6DuNpAje_!KUv>osD9@x$e`N zzJ=ozlVy=~-u8k3xU0&7evP*lBxQ9@5QFN(aJFC<7hi->wd2 z4^Q>+QAXh2l6xd9Tkm%=X=imw^+3~%PSU-+_@$UG#5)-CEUlOG!PTRfz8Lwx6XXPO zc#h$Tee3T%7|Kt*-CD_s8J2?04h3op{6uzd(p5k4Rqz!>F$9|-d3g3>#`w_s#0LJ)XD7gL(Qng&M_giHl`bz7Uh7&bi0jlGUo8a!O=|dPD)3r&5`+1 zth(`$9sW!Jhu7ATf5`=F+Jy(eZ)F>vBSak~a~R`#xTz;1i9t*H)Y;T`rG& zM3yzzaEKqCJ0wGVhX%WWellJWS+&E0+Z-jWi(%w8_qUmW|M|Du~L5 z2W@ssIRlSF4$rl>6uzZQ+KlQ~tq|Hh=s+UVX{tp51kC)ysPFSykh`!@EvQM?fzCay zE6%#iN-Q6YxZaqq19Z8iA>+!EZ3dr{>@`?a!Lp4R)VyjUsDm04$G_csVbmv^vbl>9 zYnpj1*Ya0!eF=oZ{oB#igtr+2c5!qOGnWwxfw5r>zE^BbKJ6~iy?nga?X>p`tiea@ zWpqz=k}Dif%klC#1v7p|Gg+HT7(>}rs8v}l_qZ|UA>NU*Rt;n>!7ozxh3{=3ySNQ~ z-|lUxE?m!PvDy5gvrI<)1@-&)IMiXCTHDqLLnWZF41HY43?C}R*2wG8^i;8-en4r?bwi(U6}M9q2it(s&w%SR-RsnJ=3 zqtC`!Z3jBe2d(T*0vyWod8`X?AWlA;vihNR#WpV(V*LSb6?$w$-?GjcKis&i)%AVC zRKli)rQhYgsbgDsPM;&jX?y3%4E$NE7UHKW0Z5p`L{TB<5^F0l;+d{o5GC93UB1Hh zTDHW$6b%#qkP4prlbn%WAShlI3mM*~{Y2SI+6+6_DVhj9uqB(yZ{ zp)7Zi0q!WP637r0@JkUlEH|xYnJ&y)O~90n`V$4=&D7&j3~vo;m>vhai$?^5CU@xcyybmiz|#vXT9SYf$h z5X^w>J$A6u5l=S-R37i?j$w;<&~6114oyYhelnOV-!d~+6#^+^Nggg?BGOwg^vbF* zONbDbyxP;O)ZZJH)n+!~(Fv!$2=<<+zNH8)MBPQuH&70>Lr_1 z662UF(&K2Q7*-cKFY)&aCG`|C=;N2JN(0A(TnB`8S-WV*Y^p6nfRk-6GrqC)%!xw4 zdD~#CliO)qP=Rj{3n6zZ4%<{v!WJ-kL<(>}kf<>a*#EOf!{KQX>!Yx2o?>9&0Ja@wm z(O*0<`i#KAq>OpC=xC(Rw_JY5-8c58_?TPA56U2g{*ZUG^07R`bn7)_K>A?~B$&QY zC}!lRO=m!z)=?4oMA0F8MUtZ1(dogF`l8|_fylPGNsZimwl;xcrhOy_X2jmSEOTi6 zQBxpPKv)Dvo7(zV^`XI2CtmhV#h=7vquElAdDXSa2ywH6u^0$N*}k`~hBI-MY8I!k znl>OD>SX#IEP6`Mg?c>MieQjHK>?94qU5XhmU@GD@>g-t2-9*&JC#+So|oc^M#sf3z<5Z#Aa zk5meO{RtY+w3&A7Fn=u>NYdS}1ftaJ`=i|AK}ps_u)lcc+2Ys-5KTm*Tkr_NFkc0p zJ3LPN0mUVBit}JJ_kG@&OuXDK&r&y?AO8p@oN$UgPUi@vJ^Nd#)_Pp-JVF69%^OMv-L`k#Ck9GfV)E?kHYaNP=_RePhA$Iy?z;tQ@~8N+~yr?$YH*+z8?AJ6b@ zU0yj}dkFD~eRlDL=9huJ%m(&w9n&-EPXGg;MR8@oN2TFPc+HL~gfsn};-BI9aV05? zh*c5yH)*J;_8LG3_8Lp;4Aq`0C3@Ei-ZaV46MO<(MT&#j)35O@7rBW2PoWbq$80Op;Lu(FQ6!mT_jPoj$KPYr zbd=Oom{SUQ=fd;t5qC#hQ=}A@-m}{J+IS$P_!Q{U)w7C3EkTgXCwr~!;|*Zb^@P5n z;fwyG5)P>59i3@gvS0 zq&2?nP%uyt!&05(lB9+ZRzSDK(GODbMlfn9?lPi}*GnO{T%cJ5unj#a0k>)8!(G#s zHy&!)blZTg20_K;d3^ntEQR!QJP>2v8Nq*iAj7`#WnDa>?7>r&)qxA+tMagtj1`z_ z$Wg3*ZPQ7ou*b*;iX4iKqJi-3Y}n``ypixNgJE; zL~(eXDY;P`D&oV@V6vST&U~sZ1jr2O_5Bs#i@f5(8ZJYn%Y=pu<5E^X zPg8Y+5i@v?UQ0U!BrBGE#tbD}mF6JNa=0%&48M`aN6D3<$z>a$W?m3)3`yH3+$vE# zX{Q?E?IxZO{QW`>a&pOVFkt{%jmak+j0X*uInLFS!BMgh6cl`;6iH?vS;4;_@>9T1 ztg||*<$!i1xGF`!bE?~7>`E7$TKF9XfU9sM_EI_TKHd;o?A>Xwr}|D||DXLG=)pQu zaVaICs)?s|0FI08N!zNL z&`Qrmsx4lsFAo*_(V`#j^*sqg+G$z63exHhM$B$tKt zy@k%5Qu|Q=zP|kw)IH?G`+`~11J;{`nVcHNg<3z2gJoF6{c;TT_nkpM76V!L+NMC_ z5PF^^a^$BO>tC4nc3**0V?R2l(``2lb6Z7%eiH{U_s5h%B5X8Zz%Iim>|a*6-lo*P zbQVd`2>xZVlaJM^l0}-=BVN8jH`EVR`@-py8X~{;<-Jj4`H50-NUUGBhG;q#%bq;} zIm+OUP3F)6{5HgUQ$F;KiR`nctX^5eZ?*!6`dsx27QRp&3|{sb9C=*X#6V zj@|P#s39aBjhqz*R7#ZsexP9-mpD8BMoEtZh{lwaFp@ZYZMT4bgb{GqLQ*_8Hl+N& zLmir4w9eA*Hzv2GKd@7dejFZ`Ml$C3DI3=yCk%yC2aH@Pa;Wj!YLvuCX>!GOpPsHB zgP=rGEv^%j#P}%eXJ(Wm?x9#5bHbg(0Z5d_J{+unG2BSL3=xQmKhN`U)j&EXsb=_u zH)qT)zMfd-pp4J2d>)Vz*JYATgB}bA*4N#`>|wO6^F>0{Pbd$8_CDw%)6v_#rhIPW z>U=)%sH!m7Jt9kMfpSSm^cjuMV%E%ZqD}4pT~2t}44+A#wzb*8@^`p5x`5UgO{OKa zmC*&Dr+S*^n*3=?PLZ(RuBWaf&WMeq^shu}3re4oTJclbm zCloS|VG}>3N$IZLHI>T~7~DSO>%B7EDr3lKG4{lbXv>SsE3)K(BYu@54Xc{7Mm!4~ z1Q1Qm~ z7~GMjT9^T5N*TR7WQ%LeV~>wjw5D5xj2UdW3KGftrGawl{rnWMeT2l?J(}_C5}%K+5<}rgpr>j~+Y# diff --git a/packages/google-auth/tests/compute_engine/test_credentials.py b/packages/google-auth/tests/compute_engine/test_credentials.py index 9cca317924e4..bb29f8c6e2b6 100644 --- a/packages/google-auth/tests/compute_engine/test_credentials.py +++ b/packages/google-auth/tests/compute_engine/test_credentials.py @@ -499,7 +499,7 @@ def test_with_target_audience_integration(self): responses.add( responses.POST, "https://iamcredentials.googleapis.com/v1/projects/-/" - "serviceAccounts/service-account@example.com:signBlob?alt=json", + "serviceAccounts/service-account@example.com:signBlob", status=200, content_type="application/json", json={"keyId": "some-key-id", "signedBlob": signature}, @@ -657,7 +657,7 @@ def test_with_quota_project_integration(self): responses.add( responses.POST, "https://iamcredentials.googleapis.com/v1/projects/-/" - "serviceAccounts/service-account@example.com:signBlob?alt=json", + "serviceAccounts/service-account@example.com:signBlob", status=200, content_type="application/json", json={"keyId": "some-key-id", "signedBlob": signature}, diff --git a/packages/google-auth/tests/oauth2/test__client.py b/packages/google-auth/tests/oauth2/test__client.py index 4cbd3a8adcd5..ab079ac5bc78 100644 --- a/packages/google-auth/tests/oauth2/test__client.py +++ b/packages/google-auth/tests/oauth2/test__client.py @@ -24,6 +24,7 @@ from google.auth import _helpers from google.auth import crypt from google.auth import exceptions +from google.auth import iam from google.auth import jwt from google.auth import transport from google.oauth2 import _client @@ -318,7 +319,11 @@ def test_call_iam_generate_id_token_endpoint(): request = make_request({"token": id_token}) token, expiry = _client.call_iam_generate_id_token_endpoint( - request, "fake_email", "fake_audience", "fake_access_token" + request, + iam._IAM_IDTOKEN_ENDPOINT, + "fake_email", + "fake_audience", + "fake_access_token", ) assert ( @@ -351,7 +356,11 @@ def test_call_iam_generate_id_token_endpoint_no_id_token(): with pytest.raises(exceptions.RefreshError) as excinfo: _client.call_iam_generate_id_token_endpoint( - request, "fake_email", "fake_audience", "fake_access_token" + request, + iam._IAM_IDTOKEN_ENDPOINT, + "fake_email", + "fake_audience", + "fake_access_token", ) assert excinfo.match("No ID token in response") diff --git a/packages/google-auth/tests/oauth2/test_service_account.py b/packages/google-auth/tests/oauth2/test_service_account.py index b0adf8d0014d..f16a43fb9ee0 100644 --- a/packages/google-auth/tests/oauth2/test_service_account.py +++ b/packages/google-auth/tests/oauth2/test_service_account.py @@ -22,6 +22,7 @@ from google.auth import _helpers from google.auth import crypt from google.auth import exceptions +from google.auth import iam from google.auth import jwt from google.auth import transport from google.auth.credentials import DEFAULT_UNIVERSE_DOMAIN @@ -771,10 +772,36 @@ def test_refresh_iam_flow(self, call_iam_generate_id_token_endpoint): ) request = mock.Mock() credentials.refresh(request) - req, signer_email, target_audience, access_token = call_iam_generate_id_token_endpoint.call_args[ + req, iam_endpoint, signer_email, target_audience, access_token = call_iam_generate_id_token_endpoint.call_args[ 0 ] assert req == request + assert iam_endpoint == iam._IAM_IDTOKEN_ENDPOINT + assert signer_email == "service-account@example.com" + assert target_audience == "https://example.com" + decoded_access_token = jwt.decode(access_token, verify=False) + assert decoded_access_token["scope"] == "https://www.googleapis.com/auth/iam" + + @mock.patch( + "google.oauth2._client.call_iam_generate_id_token_endpoint", autospec=True + ) + def test_refresh_iam_flow_non_gdu(self, call_iam_generate_id_token_endpoint): + credentials = self.make_credentials(universe_domain="fake-universe") + token = "id_token" + call_iam_generate_id_token_endpoint.return_value = ( + token, + _helpers.utcnow() + datetime.timedelta(seconds=500), + ) + request = mock.Mock() + credentials.refresh(request) + req, iam_endpoint, signer_email, target_audience, access_token = call_iam_generate_id_token_endpoint.call_args[ + 0 + ] + assert req == request + assert ( + iam_endpoint + == "https://iamcredentials.fake-universe/v1/projects/-/serviceAccounts/{}:generateIdToken" + ) assert signer_email == "service-account@example.com" assert target_audience == "https://example.com" decoded_access_token = jwt.decode(access_token, verify=False) From 1a12e675dfd64a8cdaf796d1e75a831977dd483f Mon Sep 17 00:00:00 2001 From: Yujian Zhao Date: Thu, 28 Mar 2024 18:26:55 -0700 Subject: [PATCH 826/966] feat: Add WebAuthn plugin component to handle WebAuthn get assertion request (#1464) --- .../google-auth/google/auth/identity_pool.py | 2 +- packages/google-auth/google/auth/pluggable.py | 2 +- .../google/oauth2/webauthn_handler.py | 82 ++++++ .../google/oauth2/webauthn_types.py | 156 ++++++++++++ .../tests/oauth2/test_webauthn_handler.py | 148 +++++++++++ .../tests/oauth2/test_webauthn_types.py | 237 ++++++++++++++++++ 6 files changed, 625 insertions(+), 2 deletions(-) create mode 100644 packages/google-auth/google/oauth2/webauthn_handler.py create mode 100644 packages/google-auth/google/oauth2/webauthn_types.py create mode 100644 packages/google-auth/tests/oauth2/test_webauthn_handler.py create mode 100644 packages/google-auth/tests/oauth2/test_webauthn_types.py diff --git a/packages/google-auth/google/auth/identity_pool.py b/packages/google-auth/google/auth/identity_pool.py index a9ec57733461..1c97885a4ab3 100644 --- a/packages/google-auth/google/auth/identity_pool.py +++ b/packages/google-auth/google/auth/identity_pool.py @@ -39,7 +39,7 @@ from collections.abc import Mapping # Python 2.7 compatibility except ImportError: # pragma: NO COVER - from collections import Mapping + from collections import Mapping # type: ignore import abc import json import os diff --git a/packages/google-auth/google/auth/pluggable.py b/packages/google-auth/google/auth/pluggable.py index 53b4eac5b4c2..d725188f87a9 100644 --- a/packages/google-auth/google/auth/pluggable.py +++ b/packages/google-auth/google/auth/pluggable.py @@ -34,7 +34,7 @@ from collections.abc import Mapping # Python 2.7 compatibility except ImportError: # pragma: NO COVER - from collections import Mapping + from collections import Mapping # type: ignore import json import os import subprocess diff --git a/packages/google-auth/google/oauth2/webauthn_handler.py b/packages/google-auth/google/oauth2/webauthn_handler.py new file mode 100644 index 000000000000..e27c7e099005 --- /dev/null +++ b/packages/google-auth/google/oauth2/webauthn_handler.py @@ -0,0 +1,82 @@ +import abc +import os +import struct +import subprocess + +from google.auth import exceptions +from google.oauth2.webauthn_types import GetRequest, GetResponse + + +class WebAuthnHandler(abc.ABC): + @abc.abstractmethod + def is_available(self) -> bool: + """Check whether this WebAuthn handler is available""" + raise NotImplementedError("is_available method must be implemented") + + @abc.abstractmethod + def get(self, get_request: GetRequest) -> GetResponse: + """WebAuthn get (assertion)""" + raise NotImplementedError("get method must be implemented") + + +class PluginHandler(WebAuthnHandler): + """Offloads WebAuthn get reqeust to a pluggable command-line tool. + + Offloads WebAuthn get to a plugin which takes the form of a + command-line tool. The command-line tool is configurable via the + PluginHandler._ENV_VAR environment variable. + + The WebAuthn plugin should implement the following interface: + + Communication occurs over stdin/stdout, and messages are both sent and + received in the form: + + [4 bytes - payload size (little-endian)][variable bytes - json payload] + """ + + _ENV_VAR = "GOOGLE_AUTH_WEBAUTHN_PLUGIN" + + def is_available(self) -> bool: + try: + self._find_plugin() + except Exception: + return False + else: + return True + + def get(self, get_request: GetRequest) -> GetResponse: + request_json = get_request.to_json() + cmd = self._find_plugin() + response_json = self._call_plugin(cmd, request_json) + return GetResponse.from_json(response_json) + + def _call_plugin(self, cmd: str, input_json: str) -> str: + # Calculate length of input + input_length = len(input_json) + length_bytes_le = struct.pack(" str: + plugin_cmd = os.environ.get(PluginHandler._ENV_VAR) + if plugin_cmd is None: + raise exceptions.InvalidResource( + "{} env var is not set".format(PluginHandler._ENV_VAR) + ) + return plugin_cmd diff --git a/packages/google-auth/google/oauth2/webauthn_types.py b/packages/google-auth/google/oauth2/webauthn_types.py new file mode 100644 index 000000000000..7784e83d0b98 --- /dev/null +++ b/packages/google-auth/google/oauth2/webauthn_types.py @@ -0,0 +1,156 @@ +from dataclasses import dataclass +import json +from typing import Any, Dict, List, Optional + +from google.auth import exceptions + + +@dataclass(frozen=True) +class PublicKeyCredentialDescriptor: + """Descriptor for a security key based credential. + + https://www.w3.org/TR/webauthn-3/#dictionary-credential-descriptor + + Args: + id: credential id (key handle). + transports: <'usb'|'nfc'|'ble'|'internal'> List of supported transports. + """ + + id: str + transports: Optional[List[str]] = None + + def to_dict(self): + cred = {"type": "public-key", "id": self.id} + if self.transports: + cred["transports"] = self.transports + return cred + + +@dataclass +class AuthenticationExtensionsClientInputs: + """Client extensions inputs for WebAuthn extensions. + + Args: + appid: app id that can be asserted with in addition to rpid. + https://www.w3.org/TR/webauthn-3/#sctn-appid-extension + """ + + appid: Optional[str] = None + + def to_dict(self): + extensions = {} + if self.appid: + extensions["appid"] = self.appid + return extensions + + +@dataclass +class GetRequest: + """WebAuthn get request + + Args: + origin: Origin where the WebAuthn get assertion takes place. + rpid: Relying Party ID. + challenge: raw challenge. + timeout_ms: Timeout number in millisecond. + allow_credentials: List of allowed credentials. + user_verification: <'required'|'preferred'|'discouraged'> User verification requirement. + extensions: WebAuthn authentication extensions inputs. + """ + + origin: str + rpid: str + challenge: str + timeout_ms: Optional[int] = None + allow_credentials: Optional[List[PublicKeyCredentialDescriptor]] = None + user_verification: Optional[str] = None + extensions: Optional[AuthenticationExtensionsClientInputs] = None + + def to_json(self) -> str: + req_options: Dict[str, Any] = {"rpid": self.rpid, "challenge": self.challenge} + if self.timeout_ms: + req_options["timeout"] = self.timeout_ms + if self.allow_credentials: + req_options["allowCredentials"] = [ + c.to_dict() for c in self.allow_credentials + ] + if self.user_verification: + req_options["userVerification"] = self.user_verification + if self.extensions: + req_options["extensions"] = self.extensions.to_dict() + return json.dumps( + {"type": "get", "origin": self.origin, "requestData": req_options} + ) + + +@dataclass(frozen=True) +class AuthenticatorAssertionResponse: + """Authenticator response to a WebAuthn get (assertion) request. + + https://www.w3.org/TR/webauthn-3/#authenticatorassertionresponse + + Args: + client_data_json: client data JSON. + authenticator_data: authenticator data. + signature: signature. + user_handle: user handle. + """ + + client_data_json: str + authenticator_data: str + signature: str + user_handle: Optional[str] + + +@dataclass(frozen=True) +class GetResponse: + """WebAuthn get (assertion) response. + + Args: + id: credential id (key handle). + response: The authenticator assertion response. + authenticator_attachment: <'cross-platform'|'platform'> The attachment status of the authenticator. + client_extension_results: WebAuthn authentication extensions output results in a dictionary. + """ + + id: str + response: AuthenticatorAssertionResponse + authenticator_attachment: Optional[str] + client_extension_results: Optional[Dict] + + @staticmethod + def from_json(json_str: str): + """Verify and construct GetResponse from a JSON string.""" + try: + resp_json = json.loads(json_str) + except ValueError: + raise exceptions.MalformedError("Invalid Get JSON response") + if resp_json.get("type") != "getResponse": + raise exceptions.MalformedError( + "Invalid Get response type: {}".format(resp_json.get("type")) + ) + pk_cred = resp_json.get("responseData") + if pk_cred is None: + if resp_json.get("error"): + raise exceptions.ReauthFailError( + "WebAuthn.get failure: {}".format(resp_json["error"]) + ) + else: + raise exceptions.MalformedError("Get response is empty") + if pk_cred.get("type") != "public-key": + raise exceptions.MalformedError( + "Invalid credential type: {}".format(pk_cred.get("type")) + ) + assertion_json = pk_cred["response"] + assertion_resp = AuthenticatorAssertionResponse( + client_data_json=assertion_json["clientDataJSON"], + authenticator_data=assertion_json["authenticatorData"], + signature=assertion_json["signature"], + user_handle=assertion_json.get("userHandle"), + ) + return GetResponse( + id=pk_cred["id"], + response=assertion_resp, + authenticator_attachment=pk_cred.get("authenticatorAttachment"), + client_extension_results=pk_cred.get("clientExtensionResults"), + ) diff --git a/packages/google-auth/tests/oauth2/test_webauthn_handler.py b/packages/google-auth/tests/oauth2/test_webauthn_handler.py new file mode 100644 index 000000000000..454e97cb61d8 --- /dev/null +++ b/packages/google-auth/tests/oauth2/test_webauthn_handler.py @@ -0,0 +1,148 @@ +import json +import struct + +import mock +import pytest # type: ignore + +from google.auth import exceptions +from google.oauth2 import webauthn_handler +from google.oauth2 import webauthn_types + + +@pytest.fixture +def os_get_stub(): + with mock.patch.object( + webauthn_handler.os.environ, + "get", + return_value="gcloud_webauthn_plugin", + name="fake os.environ.get", + ) as mock_os_environ_get: + yield mock_os_environ_get + + +@pytest.fixture +def subprocess_run_stub(): + with mock.patch.object( + webauthn_handler.subprocess, "run", name="fake subprocess.run" + ) as mock_subprocess_run: + yield mock_subprocess_run + + +def test_PluginHandler_is_available(os_get_stub): + test_handler = webauthn_handler.PluginHandler() + + assert test_handler.is_available() is True + + os_get_stub.return_value = None + assert test_handler.is_available() is False + + +GET_ASSERTION_REQUEST = webauthn_types.GetRequest( + origin="fake_origin", + rpid="fake_rpid", + challenge="fake_challenge", + allow_credentials=[webauthn_types.PublicKeyCredentialDescriptor(id="fake_id_1")], +) + + +def test_malformated_get_assertion_response(os_get_stub, subprocess_run_stub): + response_len = struct.pack(" Date: Fri, 12 Apr 2024 10:13:49 -0700 Subject: [PATCH 827/966] chore: update token (#1515) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 316c960e08935d476cb13cd0a1b4017b33f1eed1..2d02787aeb58162bfe561b43796211ccb50ed1ac 100644 GIT binary patch literal 10324 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 literal 10324 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 Date: Fri, 12 Apr 2024 11:17:16 -0700 Subject: [PATCH 828/966] fix: makes default token_url universe aware (#1514) * fix: makes default token_url universe aware * fix defaulting --- .../google/auth/external_account.py | 8 +++-- packages/google-auth/tests/test_aws.py | 33 +++++++++++++++++++ .../google-auth/tests/test_identity_pool.py | 33 +++++++++++++++++++ 3 files changed, 72 insertions(+), 2 deletions(-) diff --git a/packages/google-auth/google/auth/external_account.py b/packages/google-auth/google/auth/external_account.py index c14001bc2b14..3943de2a3427 100644 --- a/packages/google-auth/google/auth/external_account.py +++ b/packages/google-auth/google/auth/external_account.py @@ -52,7 +52,7 @@ # Cloud resource manager URL used to retrieve project information. _CLOUD_RESOURCE_MANAGER = "https://cloudresourcemanager.googleapis.com/v1/projects/" # Default Google sts token url. -_DEFAULT_TOKEN_URL = "https://sts.googleapis.com/v1/token" +_DEFAULT_TOKEN_URL = "https://sts.{universe_domain}/v1/token" @dataclass @@ -147,7 +147,12 @@ def __init__( super(Credentials, self).__init__() self._audience = audience self._subject_token_type = subject_token_type + self._universe_domain = universe_domain self._token_url = token_url + if self._token_url == _DEFAULT_TOKEN_URL: + self._token_url = self._token_url.replace( + "{universe_domain}", self._universe_domain + ) self._token_info_url = token_info_url self._credential_source = credential_source self._service_account_impersonation_url = service_account_impersonation_url @@ -160,7 +165,6 @@ def __init__( self._scopes = scopes self._default_scopes = default_scopes self._workforce_pool_user_project = workforce_pool_user_project - self._universe_domain = universe_domain or credentials.DEFAULT_UNIVERSE_DOMAIN self._trust_boundary = { "locations": [], "encoded_locations": "0x0", diff --git a/packages/google-auth/tests/test_aws.py b/packages/google-auth/tests/test_aws.py index 56148203128f..df1f02e7d702 100644 --- a/packages/google-auth/tests/test_aws.py +++ b/packages/google-auth/tests/test_aws.py @@ -1220,6 +1220,39 @@ def test_service_account_impersonation_url_custom(self): url + SERVICE_ACCOUNT_IMPERSONATION_URL_ROUTE ) + def test_info_with_default_token_url(self): + credentials = aws.Credentials( + audience=AUDIENCE, + subject_token_type=SUBJECT_TOKEN_TYPE, + credential_source=self.CREDENTIAL_SOURCE.copy(), + ) + + assert credentials.info == { + "type": "external_account", + "audience": AUDIENCE, + "subject_token_type": SUBJECT_TOKEN_TYPE, + "token_url": TOKEN_URL, + "credential_source": self.CREDENTIAL_SOURCE.copy(), + "universe_domain": DEFAULT_UNIVERSE_DOMAIN, + } + + def test_info_with_default_token_url_with_universe_domain(self): + credentials = aws.Credentials( + audience=AUDIENCE, + subject_token_type=SUBJECT_TOKEN_TYPE, + credential_source=self.CREDENTIAL_SOURCE.copy(), + universe_domain="testdomain.org", + ) + + assert credentials.info == { + "type": "external_account", + "audience": AUDIENCE, + "subject_token_type": SUBJECT_TOKEN_TYPE, + "token_url": "https://sts.testdomain.org/v1/token", + "credential_source": self.CREDENTIAL_SOURCE.copy(), + "universe_domain": "testdomain.org", + } + def test_retrieve_subject_token_missing_region_url(self): # When AWS_REGION envvar is not available, region_url is required for # determining the current AWS region. diff --git a/packages/google-auth/tests/test_identity_pool.py b/packages/google-auth/tests/test_identity_pool.py index ac1d9a0bb1c8..a11b1e70fe9b 100644 --- a/packages/google-auth/tests/test_identity_pool.py +++ b/packages/google-auth/tests/test_identity_pool.py @@ -782,6 +782,39 @@ def test_info_with_url_credential_source(self): "universe_domain": DEFAULT_UNIVERSE_DOMAIN, } + def test_info_with_default_token_url(self): + credentials = identity_pool.Credentials( + audience=AUDIENCE, + subject_token_type=SUBJECT_TOKEN_TYPE, + credential_source=self.CREDENTIAL_SOURCE_TEXT_URL.copy(), + ) + + assert credentials.info == { + "type": "external_account", + "audience": AUDIENCE, + "subject_token_type": SUBJECT_TOKEN_TYPE, + "token_url": TOKEN_URL, + "credential_source": self.CREDENTIAL_SOURCE_TEXT_URL, + "universe_domain": DEFAULT_UNIVERSE_DOMAIN, + } + + def test_info_with_default_token_url_with_universe_domain(self): + credentials = identity_pool.Credentials( + audience=AUDIENCE, + subject_token_type=SUBJECT_TOKEN_TYPE, + credential_source=self.CREDENTIAL_SOURCE_TEXT_URL.copy(), + universe_domain="testdomain.org", + ) + + assert credentials.info == { + "type": "external_account", + "audience": AUDIENCE, + "subject_token_type": SUBJECT_TOKEN_TYPE, + "token_url": "https://sts.testdomain.org/v1/token", + "credential_source": self.CREDENTIAL_SOURCE_TEXT_URL, + "universe_domain": "testdomain.org", + } + def test_retrieve_subject_token_missing_subject_token(self, tmpdir): # Provide empty text file. empty_file = tmpdir.join("empty.txt") From 2ebd12cf0078bb9bef12e13d72d3580974af3a4a Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Fri, 12 Apr 2024 14:33:45 -0400 Subject: [PATCH 829/966] chore(python): update dependencies in /.kokoro (#1499) Source-Link: https://github.com/googleapis/synthtool/commit/db94845da69ccdfefd7ce55c84e6cfa74829747e Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:a8a80fc6456e433df53fc2a0d72ca0345db0ddefb409f1b75b118dfd1babd952 Co-authored-by: Owl Bot Co-authored-by: Anthonios Partheniou --- .../google-auth/.github/.OwlBot.lock.yaml | 4 +- .../.kokoro/docker/docs/Dockerfile | 4 + .../.kokoro/docker/docs/requirements.in | 1 + .../.kokoro/docker/docs/requirements.txt | 38 ++++++ packages/google-auth/.kokoro/requirements.in | 3 +- packages/google-auth/.kokoro/requirements.txt | 114 ++++++++---------- 6 files changed, 99 insertions(+), 65 deletions(-) create mode 100644 packages/google-auth/.kokoro/docker/docs/requirements.in create mode 100644 packages/google-auth/.kokoro/docker/docs/requirements.txt diff --git a/packages/google-auth/.github/.OwlBot.lock.yaml b/packages/google-auth/.github/.OwlBot.lock.yaml index e4e943e0259a..4bdeef3904e2 100644 --- a/packages/google-auth/.github/.OwlBot.lock.yaml +++ b/packages/google-auth/.github/.OwlBot.lock.yaml @@ -13,5 +13,5 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:98f3afd11308259de6e828e37376d18867fd321aba07826e29e4f8d9cab56bad -# created: 2024-02-27T15:56:18.442440378Z + digest: sha256:a8a80fc6456e433df53fc2a0d72ca0345db0ddefb409f1b75b118dfd1babd952 +# created: 2024-03-15T16:25:47.905264637Z diff --git a/packages/google-auth/.kokoro/docker/docs/Dockerfile b/packages/google-auth/.kokoro/docker/docs/Dockerfile index 8e39a2cc438d..bdaf39fe22d0 100644 --- a/packages/google-auth/.kokoro/docker/docs/Dockerfile +++ b/packages/google-auth/.kokoro/docker/docs/Dockerfile @@ -80,4 +80,8 @@ RUN wget -O /tmp/get-pip.py 'https://bootstrap.pypa.io/get-pip.py' \ # Test pip RUN python3 -m pip +# Install build requirements +COPY requirements.txt /requirements.txt +RUN python3 -m pip install --require-hashes -r requirements.txt + CMD ["python3.8"] diff --git a/packages/google-auth/.kokoro/docker/docs/requirements.in b/packages/google-auth/.kokoro/docker/docs/requirements.in new file mode 100644 index 000000000000..816817c672a1 --- /dev/null +++ b/packages/google-auth/.kokoro/docker/docs/requirements.in @@ -0,0 +1 @@ +nox diff --git a/packages/google-auth/.kokoro/docker/docs/requirements.txt b/packages/google-auth/.kokoro/docker/docs/requirements.txt new file mode 100644 index 000000000000..0e5d70f20f83 --- /dev/null +++ b/packages/google-auth/.kokoro/docker/docs/requirements.txt @@ -0,0 +1,38 @@ +# +# This file is autogenerated by pip-compile with Python 3.9 +# by the following command: +# +# pip-compile --allow-unsafe --generate-hashes requirements.in +# +argcomplete==3.2.3 \ + --hash=sha256:bf7900329262e481be5a15f56f19736b376df6f82ed27576fa893652c5de6c23 \ + --hash=sha256:c12355e0494c76a2a7b73e3a59b09024ca0ba1e279fb9ed6c1b82d5b74b6a70c + # via nox +colorlog==6.8.2 \ + --hash=sha256:3e3e079a41feb5a1b64f978b5ea4f46040a94f11f0e8bbb8261e3dbbeca64d44 \ + --hash=sha256:4dcbb62368e2800cb3c5abd348da7e53f6c362dda502ec27c560b2e58a66bd33 + # via nox +distlib==0.3.8 \ + --hash=sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784 \ + --hash=sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64 + # via virtualenv +filelock==3.13.1 \ + --hash=sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e \ + --hash=sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c + # via virtualenv +nox==2024.3.2 \ + --hash=sha256:e53514173ac0b98dd47585096a55572fe504fecede58ced708979184d05440be \ + --hash=sha256:f521ae08a15adbf5e11f16cb34e8d0e6ea521e0b92868f684e91677deb974553 + # via -r requirements.in +packaging==24.0 \ + --hash=sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5 \ + --hash=sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9 + # via nox +platformdirs==4.2.0 \ + --hash=sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068 \ + --hash=sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768 + # via virtualenv +virtualenv==20.25.1 \ + --hash=sha256:961c026ac520bac5f69acb8ea063e8a4f071bcc9457b9c1f28f6b085c511583a \ + --hash=sha256:e08e13ecdca7a0bd53798f356d5831434afa5b07b93f0abdf0797b7a06ffe197 + # via nox diff --git a/packages/google-auth/.kokoro/requirements.in b/packages/google-auth/.kokoro/requirements.in index ec867d9fd65a..fff4d9ce0d0a 100644 --- a/packages/google-auth/.kokoro/requirements.in +++ b/packages/google-auth/.kokoro/requirements.in @@ -1,5 +1,5 @@ gcp-docuploader -gcp-releasetool>=1.10.5 # required for compatibility with cryptography>=39.x +gcp-releasetool>=2 # required for compatibility with cryptography>=42.x importlib-metadata typing-extensions twine @@ -8,3 +8,4 @@ setuptools nox>=2022.11.21 # required to remove dependency on py charset-normalizer<3 click<8.1.0 +cryptography>=42.0.5 diff --git a/packages/google-auth/.kokoro/requirements.txt b/packages/google-auth/.kokoro/requirements.txt index bda8e38c4f31..dd61f5f32018 100644 --- a/packages/google-auth/.kokoro/requirements.txt +++ b/packages/google-auth/.kokoro/requirements.txt @@ -93,40 +93,41 @@ colorlog==6.7.0 \ # via # gcp-docuploader # nox -cryptography==42.0.4 \ - --hash=sha256:01911714117642a3f1792c7f376db572aadadbafcd8d75bb527166009c9f1d1b \ - --hash=sha256:0e89f7b84f421c56e7ff69f11c441ebda73b8a8e6488d322ef71746224c20fce \ - --hash=sha256:12d341bd42cdb7d4937b0cabbdf2a94f949413ac4504904d0cdbdce4a22cbf88 \ - --hash=sha256:15a1fb843c48b4a604663fa30af60818cd28f895572386e5f9b8a665874c26e7 \ - --hash=sha256:1cdcdbd117681c88d717437ada72bdd5be9de117f96e3f4d50dab3f59fd9ab20 \ - --hash=sha256:1df6fcbf60560d2113b5ed90f072dc0b108d64750d4cbd46a21ec882c7aefce9 \ - --hash=sha256:3c6048f217533d89f2f8f4f0fe3044bf0b2090453b7b73d0b77db47b80af8dff \ - --hash=sha256:3e970a2119507d0b104f0a8e281521ad28fc26f2820687b3436b8c9a5fcf20d1 \ - --hash=sha256:44a64043f743485925d3bcac548d05df0f9bb445c5fcca6681889c7c3ab12764 \ - --hash=sha256:4e36685cb634af55e0677d435d425043967ac2f3790ec652b2b88ad03b85c27b \ - --hash=sha256:5f8907fcf57392cd917892ae83708761c6ff3c37a8e835d7246ff0ad251d9298 \ - --hash=sha256:69b22ab6506a3fe483d67d1ed878e1602bdd5912a134e6202c1ec672233241c1 \ - --hash=sha256:6bfadd884e7280df24d26f2186e4e07556a05d37393b0f220a840b083dc6a824 \ - --hash=sha256:6d0fbe73728c44ca3a241eff9aefe6496ab2656d6e7a4ea2459865f2e8613257 \ - --hash=sha256:6ffb03d419edcab93b4b19c22ee80c007fb2d708429cecebf1dd3258956a563a \ - --hash=sha256:810bcf151caefc03e51a3d61e53335cd5c7316c0a105cc695f0959f2c638b129 \ - --hash=sha256:831a4b37accef30cccd34fcb916a5d7b5be3cbbe27268a02832c3e450aea39cb \ - --hash=sha256:887623fe0d70f48ab3f5e4dbf234986b1329a64c066d719432d0698522749929 \ - --hash=sha256:a0298bdc6e98ca21382afe914c642620370ce0470a01e1bef6dd9b5354c36854 \ - --hash=sha256:a1327f280c824ff7885bdeef8578f74690e9079267c1c8bd7dc5cc5aa065ae52 \ - --hash=sha256:c1f25b252d2c87088abc8bbc4f1ecbf7c919e05508a7e8628e6875c40bc70923 \ - --hash=sha256:c3a5cbc620e1e17009f30dd34cb0d85c987afd21c41a74352d1719be33380885 \ - --hash=sha256:ce8613beaffc7c14f091497346ef117c1798c202b01153a8cc7b8e2ebaaf41c0 \ - --hash=sha256:d2a27aca5597c8a71abbe10209184e1a8e91c1fd470b5070a2ea60cafec35bcd \ - --hash=sha256:dad9c385ba8ee025bb0d856714f71d7840020fe176ae0229de618f14dae7a6e2 \ - --hash=sha256:db4b65b02f59035037fde0998974d84244a64c3265bdef32a827ab9b63d61b18 \ - --hash=sha256:e09469a2cec88fb7b078e16d4adec594414397e8879a4341c6ace96013463d5b \ - --hash=sha256:e53dc41cda40b248ebc40b83b31516487f7db95ab8ceac1f042626bc43a2f992 \ - --hash=sha256:f1e85a178384bf19e36779d91ff35c7617c885da487d689b05c1366f9933ad74 \ - --hash=sha256:f47be41843200f7faec0683ad751e5ef11b9a56a220d57f300376cd8aba81660 \ - --hash=sha256:fb0cef872d8193e487fc6bdb08559c3aa41b659a7d9be48b2e10747f47863925 \ - --hash=sha256:ffc73996c4fca3d2b6c1c8c12bfd3ad00def8621da24f547626bf06441400449 +cryptography==42.0.5 \ + --hash=sha256:0270572b8bd2c833c3981724b8ee9747b3ec96f699a9665470018594301439ee \ + --hash=sha256:111a0d8553afcf8eb02a4fea6ca4f59d48ddb34497aa8706a6cf536f1a5ec576 \ + --hash=sha256:16a48c23a62a2f4a285699dba2e4ff2d1cff3115b9df052cdd976a18856d8e3d \ + --hash=sha256:1b95b98b0d2af784078fa69f637135e3c317091b615cd0905f8b8a087e86fa30 \ + --hash=sha256:1f71c10d1e88467126f0efd484bd44bca5e14c664ec2ede64c32f20875c0d413 \ + --hash=sha256:2424ff4c4ac7f6b8177b53c17ed5d8fa74ae5955656867f5a8affaca36a27abb \ + --hash=sha256:2bce03af1ce5a5567ab89bd90d11e7bbdff56b8af3acbbec1faded8f44cb06da \ + --hash=sha256:329906dcc7b20ff3cad13c069a78124ed8247adcac44b10bea1130e36caae0b4 \ + --hash=sha256:37dd623507659e08be98eec89323469e8c7b4c1407c85112634ae3dbdb926fdd \ + --hash=sha256:3eaafe47ec0d0ffcc9349e1708be2aaea4c6dd4978d76bf6eb0cb2c13636c6fc \ + --hash=sha256:5e6275c09d2badf57aea3afa80d975444f4be8d3bc58f7f80d2a484c6f9485c8 \ + --hash=sha256:6fe07eec95dfd477eb9530aef5bead34fec819b3aaf6c5bd6d20565da607bfe1 \ + --hash=sha256:7367d7b2eca6513681127ebad53b2582911d1736dc2ffc19f2c3ae49997496bc \ + --hash=sha256:7cde5f38e614f55e28d831754e8a3bacf9ace5d1566235e39d91b35502d6936e \ + --hash=sha256:9481ffe3cf013b71b2428b905c4f7a9a4f76ec03065b05ff499bb5682a8d9ad8 \ + --hash=sha256:98d8dc6d012b82287f2c3d26ce1d2dd130ec200c8679b6213b3c73c08b2b7940 \ + --hash=sha256:a011a644f6d7d03736214d38832e030d8268bcff4a41f728e6030325fea3e400 \ + --hash=sha256:a2913c5375154b6ef2e91c10b5720ea6e21007412f6437504ffea2109b5a33d7 \ + --hash=sha256:a30596bae9403a342c978fb47d9b0ee277699fa53bbafad14706af51fe543d16 \ + --hash=sha256:b03c2ae5d2f0fc05f9a2c0c997e1bc18c8229f392234e8a0194f202169ccd278 \ + --hash=sha256:b6cd2203306b63e41acdf39aa93b86fb566049aeb6dc489b70e34bcd07adca74 \ + --hash=sha256:b7ffe927ee6531c78f81aa17e684e2ff617daeba7f189f911065b2ea2d526dec \ + --hash=sha256:b8cac287fafc4ad485b8a9b67d0ee80c66bf3574f655d3b97ef2e1082360faf1 \ + --hash=sha256:ba334e6e4b1d92442b75ddacc615c5476d4ad55cc29b15d590cc6b86efa487e2 \ + --hash=sha256:ba3e4a42397c25b7ff88cdec6e2a16c2be18720f317506ee25210f6d31925f9c \ + --hash=sha256:c41fb5e6a5fe9ebcd58ca3abfeb51dffb5d83d6775405305bfa8715b76521922 \ + --hash=sha256:cd2030f6650c089aeb304cf093f3244d34745ce0cfcc39f20c6fbfe030102e2a \ + --hash=sha256:cd65d75953847815962c84a4654a84850b2bb4aed3f26fadcc1c13892e1e29f6 \ + --hash=sha256:e4985a790f921508f36f81831817cbc03b102d643b5fcb81cd33df3fa291a1a1 \ + --hash=sha256:e807b3188f9eb0eaa7bbb579b462c5ace579f1cedb28107ce8b48a9f7ad3679e \ + --hash=sha256:f12764b8fffc7a123f641d7d049d382b73f96a34117e0b637b80643169cec8ac \ + --hash=sha256:f8837fe1d6ac4a8052a9a8ddab256bc006242696f03368a4009be7ee3075cdb7 # via + # -r requirements.in # gcp-releasetool # secretstorage distlib==0.3.7 \ @@ -145,9 +146,9 @@ gcp-docuploader==0.6.5 \ --hash=sha256:30221d4ac3e5a2b9c69aa52fdbef68cc3f27d0e6d0d90e220fc024584b8d2318 \ --hash=sha256:b7458ef93f605b9d46a4bf3a8dc1755dad1f31d030c8679edf304e343b347eea # via -r requirements.in -gcp-releasetool==1.16.0 \ - --hash=sha256:27bf19d2e87aaa884096ff941aa3c592c482be3d6a2bfe6f06afafa6af2353e3 \ - --hash=sha256:a316b197a543fd036209d0caba7a8eb4d236d8e65381c80cbc6d7efaa7606d63 +gcp-releasetool==2.0.0 \ + --hash=sha256:3d73480b50ba243f22d7c7ec08b115a30e1c7817c4899781840c26f9c55b8277 \ + --hash=sha256:7aa9fd935ec61e581eb8458ad00823786d91756c25e492f372b2b30962f3c28f # via -r requirements.in google-api-core==2.12.0 \ --hash=sha256:c22e01b1e3c4dcd90998494879612c38d0a3411d1f7b679eb89e2abe3ce1f553 \ @@ -392,29 +393,18 @@ platformdirs==3.11.0 \ --hash=sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3 \ --hash=sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e # via virtualenv -protobuf==3.20.3 \ - --hash=sha256:03038ac1cfbc41aa21f6afcbcd357281d7521b4157926f30ebecc8d4ea59dcb7 \ - --hash=sha256:28545383d61f55b57cf4df63eebd9827754fd2dc25f80c5253f9184235db242c \ - --hash=sha256:2e3427429c9cffebf259491be0af70189607f365c2f41c7c3764af6f337105f2 \ - --hash=sha256:398a9e0c3eaceb34ec1aee71894ca3299605fa8e761544934378bbc6c97de23b \ - --hash=sha256:44246bab5dd4b7fbd3c0c80b6f16686808fab0e4aca819ade6e8d294a29c7050 \ - --hash=sha256:447d43819997825d4e71bf5769d869b968ce96848b6479397e29fc24c4a5dfe9 \ - --hash=sha256:67a3598f0a2dcbc58d02dd1928544e7d88f764b47d4a286202913f0b2801c2e7 \ - --hash=sha256:74480f79a023f90dc6e18febbf7b8bac7508420f2006fabd512013c0c238f454 \ - --hash=sha256:819559cafa1a373b7096a482b504ae8a857c89593cf3a25af743ac9ecbd23480 \ - --hash=sha256:899dc660cd599d7352d6f10d83c95df430a38b410c1b66b407a6b29265d66469 \ - --hash=sha256:8c0c984a1b8fef4086329ff8dd19ac77576b384079247c770f29cc8ce3afa06c \ - --hash=sha256:9aae4406ea63d825636cc11ffb34ad3379335803216ee3a856787bcf5ccc751e \ - --hash=sha256:a7ca6d488aa8ff7f329d4c545b2dbad8ac31464f1d8b1c87ad1346717731e4db \ - --hash=sha256:b6cc7ba72a8850621bfec987cb72623e703b7fe2b9127a161ce61e61558ad905 \ - --hash=sha256:bf01b5720be110540be4286e791db73f84a2b721072a3711efff6c324cdf074b \ - --hash=sha256:c02ce36ec760252242a33967d51c289fd0e1c0e6e5cc9397e2279177716add86 \ - --hash=sha256:d9e4432ff660d67d775c66ac42a67cf2453c27cb4d738fc22cb53b5d84c135d4 \ - --hash=sha256:daa564862dd0d39c00f8086f88700fdbe8bc717e993a21e90711acfed02f2402 \ - --hash=sha256:de78575669dddf6099a8a0f46a27e82a1783c557ccc38ee620ed8cc96d3be7d7 \ - --hash=sha256:e64857f395505ebf3d2569935506ae0dfc4a15cb80dc25261176c784662cdcc4 \ - --hash=sha256:f4bd856d702e5b0d96a00ec6b307b0f51c1982c2bf9c0052cf9019e9a544ba99 \ - --hash=sha256:f4c42102bc82a51108e449cbb32b19b180022941c727bac0cfd50170341f16ee +protobuf==4.25.3 \ + --hash=sha256:19b270aeaa0099f16d3ca02628546b8baefe2955bbe23224aaf856134eccf1e4 \ + --hash=sha256:209ba4cc916bab46f64e56b85b090607a676f66b473e6b762e6f1d9d591eb2e8 \ + --hash=sha256:25b5d0b42fd000320bd7830b349e3b696435f3b329810427a6bcce6a5492cc5c \ + --hash=sha256:7c8daa26095f82482307bc717364e7c13f4f1c99659be82890dcfc215194554d \ + --hash=sha256:c053062984e61144385022e53678fbded7aea14ebb3e0305ae3592fb219ccfa4 \ + --hash=sha256:d4198877797a83cbfe9bffa3803602bbe1625dc30d8a097365dbc762e5790faa \ + --hash=sha256:e3c97a1555fd6388f857770ff8b9703083de6bf1f9274a002a332d65fbb56c8c \ + --hash=sha256:e7cb0ae90dd83727f0c0718634ed56837bfeeee29a5f82a7514c03ee1364c019 \ + --hash=sha256:f0700d54bcf45424477e46a9f0944155b46fb0639d69728739c0e47bab83f2b9 \ + --hash=sha256:f1279ab38ecbfae7e456a108c5c0681e4956d5b1090027c1de0f934dfdb4b35c \ + --hash=sha256:f4f118245c4a087776e0a8408be33cf09f6c547442c00395fbfb116fac2f8ac2 # via # gcp-docuploader # gcp-releasetool @@ -518,7 +508,7 @@ zipp==3.17.0 \ # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: -setuptools==68.2.2 \ - --hash=sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87 \ - --hash=sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a +setuptools==69.2.0 \ + --hash=sha256:0ff4183f8f42cd8fa3acea16c45205521a4ef28f73c6391d8a25e92893134f2e \ + --hash=sha256:c21c49fb1042386df081cb5d86759792ab89efca84cf114889191cd09aacc80c # via -r requirements.in From 9dbddb33d78e68dba14fa859e0467aaadb3520f7 Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Fri, 12 Apr 2024 16:42:27 -0400 Subject: [PATCH 830/966] chore(python): bump idna from 3.4 to 3.7 in .kokoro (#1517) Source-Link: https://github.com/googleapis/synthtool/commit/d50980e704793a2d3310bfb3664f3a82f24b5796 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:5a4c19d17e597b92d786e569be101e636c9c2817731f80a5adec56b2aa8fe070 Co-authored-by: Owl Bot Co-authored-by: Anthonios Partheniou --- packages/google-auth/.github/.OwlBot.lock.yaml | 4 ++-- packages/google-auth/.kokoro/requirements.txt | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/google-auth/.github/.OwlBot.lock.yaml b/packages/google-auth/.github/.OwlBot.lock.yaml index 4bdeef3904e2..81f87c56917d 100644 --- a/packages/google-auth/.github/.OwlBot.lock.yaml +++ b/packages/google-auth/.github/.OwlBot.lock.yaml @@ -13,5 +13,5 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:a8a80fc6456e433df53fc2a0d72ca0345db0ddefb409f1b75b118dfd1babd952 -# created: 2024-03-15T16:25:47.905264637Z + digest: sha256:5a4c19d17e597b92d786e569be101e636c9c2817731f80a5adec56b2aa8fe070 +# created: 2024-04-12T11:35:58.922854369Z diff --git a/packages/google-auth/.kokoro/requirements.txt b/packages/google-auth/.kokoro/requirements.txt index dd61f5f32018..51f92b8e12f1 100644 --- a/packages/google-auth/.kokoro/requirements.txt +++ b/packages/google-auth/.kokoro/requirements.txt @@ -252,9 +252,9 @@ googleapis-common-protos==1.61.0 \ --hash=sha256:22f1915393bb3245343f6efe87f6fe868532efc12aa26b391b15132e1279f1c0 \ --hash=sha256:8a64866a97f6304a7179873a465d6eee97b7a24ec6cfd78e0f575e96b821240b # via google-api-core -idna==3.4 \ - --hash=sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4 \ - --hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2 +idna==3.7 \ + --hash=sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc \ + --hash=sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0 # via requests importlib-metadata==6.8.0 \ --hash=sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb \ From 533a803ce129ce4f131b3e0fa89794291a653b3a Mon Sep 17 00:00:00 2001 From: Carl Lundin <108372512+clundin25@users.noreply.github.com> Date: Thu, 2 May 2024 10:24:13 -0700 Subject: [PATCH 831/966] chore: Add log when rapt token is acquired. (#1520) * chore: Add log when rapt token is acquired. * chore: Add log when rapt token is acquired. --- packages/google-auth/google/oauth2/reauth.py | 1 + .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 2 files changed, 1 insertion(+) diff --git a/packages/google-auth/google/oauth2/reauth.py b/packages/google-auth/google/oauth2/reauth.py index 5870347739b1..1e39e0bc7f41 100644 --- a/packages/google-auth/google/oauth2/reauth.py +++ b/packages/google-auth/google/oauth2/reauth.py @@ -274,6 +274,7 @@ def get_rapt_token( # Get rapt token from reauth API. rapt_token = _obtain_rapt(request, access_token, requested_scopes=scopes) + sys.stderr.write("Reauthentication successful.\n") return rapt_token diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 2d02787aeb58162bfe561b43796211ccb50ed1ac..883ab77496f8072ded5cf8e53a919214cea12ec9 100644 GIT binary patch literal 10324 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 literal 10324 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 From 52aec1336541913d8be9322688555288e85b3304 Mon Sep 17 00:00:00 2001 From: Carl Lundin <108372512+clundin25@users.noreply.github.com> Date: Tue, 21 May 2024 08:50:18 -0700 Subject: [PATCH 832/966] feat: ECP Provider drop cryptography requirement (#1524) The ECP provider can work with the Python std lib SSL library. This simplifies packaging, as cryptography can be omitted. This patch makes the code use the standard SSL library if a provider implementation is detected. --- .../auth/transport/_custom_tls_signer.py | 20 ++++++++-- .../google/auth/transport/requests.py | 13 +++---- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../transport/test__custom_tls_signer.py | 2 + .../tests/transport/test_requests.py | 35 ++++++++++++++++++ 5 files changed, 58 insertions(+), 12 deletions(-) diff --git a/packages/google-auth/google/auth/transport/_custom_tls_signer.py b/packages/google-auth/google/auth/transport/_custom_tls_signer.py index 57a563d03bd5..9279158d45c6 100644 --- a/packages/google-auth/google/auth/transport/_custom_tls_signer.py +++ b/packages/google-auth/google/auth/transport/_custom_tls_signer.py @@ -46,10 +46,17 @@ # Cast SSL_CTX* to void* -def _cast_ssl_ctx_to_void_p(ssl_ctx): +def _cast_ssl_ctx_to_void_p_pyopenssl(ssl_ctx): return ctypes.cast(int(cffi.FFI().cast("intptr_t", ssl_ctx)), ctypes.c_void_p) +# Cast SSL_CTX* to void* +def _cast_ssl_ctx_to_void_p_stdlib(context): + return ctypes.c_void_p.from_address( + id(context) + ctypes.sizeof(ctypes.c_void_p) * 2 + ) + + # Load offload library and set up the function types. def load_offload_lib(offload_lib_path): _LOGGER.debug("loading offload library from %s", offload_lib_path) @@ -249,10 +256,15 @@ def set_up_custom_key(self): self._signer_lib, self._enterprise_cert_file_path ) - def attach_to_ssl_context(self, ctx): + def should_use_provider(self): if self._provider_lib: + return True + return False + + def attach_to_ssl_context(self, ctx): + if self.should_use_provider(): if not self._provider_lib.ECP_attach_to_ctx( - _cast_ssl_ctx_to_void_p(ctx._ctx._context), + _cast_ssl_ctx_to_void_p_stdlib(ctx), self._enterprise_cert_file_path.encode("ascii"), ): raise exceptions.MutualTLSChannelError( @@ -262,7 +274,7 @@ def attach_to_ssl_context(self, ctx): if not self._offload_lib.ConfigureSslContext( self._sign_callback, ctypes.c_char_p(self._cert), - _cast_ssl_ctx_to_void_p(ctx._ctx._context), + _cast_ssl_ctx_to_void_p_pyopenssl(ctx._ctx._context), ): raise exceptions.MutualTLSChannelError( "failed to configure ECP Offload SSL context" diff --git a/packages/google-auth/google/auth/transport/requests.py b/packages/google-auth/google/auth/transport/requests.py index aa1611322629..63a2b4596c47 100644 --- a/packages/google-auth/google/auth/transport/requests.py +++ b/packages/google-auth/google/auth/transport/requests.py @@ -262,19 +262,16 @@ class _MutualTlsOffloadAdapter(requests.adapters.HTTPAdapter): def __init__(self, enterprise_cert_file_path): import certifi - import urllib3.contrib.pyopenssl - from google.auth.transport import _custom_tls_signer - # Call inject_into_urllib3 to activate certificate checking. See the - # following links for more info: - # (1) doc: https://github.com/urllib3/urllib3/blob/cb9ebf8aac5d75f64c8551820d760b72b619beff/src/urllib3/contrib/pyopenssl.py#L31-L32 - # (2) mTLS example: https://github.com/urllib3/urllib3/issues/474#issuecomment-253168415 - urllib3.contrib.pyopenssl.inject_into_urllib3() - self.signer = _custom_tls_signer.CustomTlsSigner(enterprise_cert_file_path) self.signer.load_libraries() + if not self.signer.should_use_provider(): + import urllib3.contrib.pyopenssl + + urllib3.contrib.pyopenssl.inject_into_urllib3() + poolmanager = create_urllib3_context() poolmanager.load_verify_locations(cafile=certifi.where()) self.signer.attach_to_ssl_context(poolmanager) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 883ab77496f8072ded5cf8e53a919214cea12ec9..104243c2c5af16beff9e04049342857ae34f0235 100644 GIT binary patch literal 10324 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 mRu;3~3PLW&?tKRTKJOPb=t?zz)gltAPyjE~ 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 diff --git a/packages/google-auth/tests/transport/test__custom_tls_signer.py b/packages/google-auth/tests/transport/test__custom_tls_signer.py index d2907bad2973..3a33c2c02154 100644 --- a/packages/google-auth/tests/transport/test__custom_tls_signer.py +++ b/packages/google-auth/tests/transport/test__custom_tls_signer.py @@ -195,6 +195,7 @@ def test_custom_tls_signer(): get_cert.assert_called_once() get_sign_callback.assert_called_once() offload_lib.ConfigureSslContext.assert_called_once() + assert not signer_object.should_use_provider() assert signer_object._enterprise_cert_file_path == ENTERPRISE_CERT_FILE assert signer_object._offload_lib == offload_lib assert signer_object._signer_lib == signer_lib @@ -216,6 +217,7 @@ def test_custom_tls_signer_provider(): signer_object.load_libraries() signer_object.attach_to_ssl_context(mock.MagicMock()) + assert signer_object.should_use_provider() assert signer_object._enterprise_cert_file_path == ENTERPRISE_CERT_FILE_PROVIDER assert signer_object._provider_lib == provider_lib load_provider_lib.assert_called_with("/path/to/provider/lib") diff --git a/packages/google-auth/tests/transport/test_requests.py b/packages/google-auth/tests/transport/test_requests.py index aadc1ddbfd0b..0da3e36d9a30 100644 --- a/packages/google-auth/tests/transport/test_requests.py +++ b/packages/google-auth/tests/transport/test_requests.py @@ -568,3 +568,38 @@ def test_success( adapter.proxy_manager_for() mock_proxy_manager_for.assert_called_with(ssl_context=adapter._ctx_proxymanager) + + @mock.patch.object(requests.adapters.HTTPAdapter, "init_poolmanager") + @mock.patch.object(requests.adapters.HTTPAdapter, "proxy_manager_for") + @mock.patch.object( + google.auth.transport._custom_tls_signer.CustomTlsSigner, "should_use_provider" + ) + @mock.patch.object( + google.auth.transport._custom_tls_signer.CustomTlsSigner, "load_libraries" + ) + @mock.patch.object( + google.auth.transport._custom_tls_signer.CustomTlsSigner, + "attach_to_ssl_context", + ) + def test_success_should_use_provider( + self, + mock_attach_to_ssl_context, + mock_load_libraries, + mock_should_use_provider, + mock_proxy_manager_for, + mock_init_poolmanager, + ): + enterprise_cert_file_path = "/path/to/enterprise/cert/json" + adapter = google.auth.transport.requests._MutualTlsOffloadAdapter( + enterprise_cert_file_path + ) + + mock_should_use_provider.side_effect = True + mock_load_libraries.assert_called_once() + assert mock_attach_to_ssl_context.call_count == 2 + + adapter.init_poolmanager() + mock_init_poolmanager.assert_called_with(ssl_context=adapter._ctx_poolmanager) + + adapter.proxy_manager_for() + mock_proxy_manager_for.assert_called_with(ssl_context=adapter._ctx_proxymanager) From 79411d9bfc2c58e44a6ed55ac0af4b048d58e39b Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Wed, 5 Jun 2024 18:43:32 -0700 Subject: [PATCH 833/966] chore: update sys test cred (#1531) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 104243c2c5af16beff9e04049342857ae34f0235..cb3e978270c4dc428bc7f610ab6e92cd0695102f 100644 GIT binary patch literal 10324 zcmV-aD67{BB>?tKRTI5AgCYdy9(jWVTEv-`CM#}wGL7)mXem^h^t+#@+7A+{PyjE~ zv)`Xv#WI=gPy?e*`!-w~iyQ@F(s36mWn*&EX{Zx9=2fE3ujWv_50T8V zS1OioeVwblPwYp0M7YX$2nUVhQ7<~KFR`L7Bib#4)<#E3$TPS)E!t`E4^ zH{ptqlmyh87&xH;ioik#L{&m987CQx&a&O?@Hk0a1#MY2)*(#6QYMNR7siY;oHbJf zNnlb>bjxI!#cHJC)>{_8yUxV9pO~?KIFc?(jFwaeH;i9CvxPeca<$-a9VVBx$H zIZT~7ztGs28}Fw!tLI4l(y0&zw)_&SNPxH+G^1HY9SfiZ@roE`35I~L8b_0|(}pVd3U=%$bo#Iq+L zts^}kv(5>sK-B7ISiIKCa~HqAm6dF}+Efc$9Svq>6SM&p-1JgX2h(6OfE_$ou*Qv= zVk0mETs38vN9O2i5I+FmdV!>di1mtc7=Z?_bLsy@gI-N}iuzRP(Ti}rQ#+Oo7+B(E zve1+U`DHkum2!9Rza}VHrj%JE7i~EJB`(W6M95!iyeaGb0Z+axdwe~ednWclzmDSK z?AXs5yAyg0Pv9|kg|i^K<<3JMx4K^|qO`n7eINFTumia$ylKzYXMoD&$ysoAqtL@4 z)Ew$TL2|IM$ct8S;BJOxmc7W3E0+&bwy^2~YD4k^&yEliGy_cWRoOxs-t~~PvW_qV zZmumEXZ=kpqbS^cQ^^3l65b4wGc0Pih`s6t89u4RQCN|?G(jdhRa72YRl~WImy+;M zPgf|qn?WFH{P|IXSV6uJg+Dk%_3!gdMnwDlYd{ud z{56#3468EKQ_nCeJFm)6Q{aTNmuvlM7GyU=6qr>h9G%|Y2-zdIjOrb1h#&dZA5Qlj z8`~%?;`5XOo9@PNt+C0(J?;}|=nVM+GBj*_+!a(%Cqvp%%7SbX9Oxfn0{J${tt=Q<2b?76w6lk3APlxvXhD{-?e(llq62jX z-Y{!#%!C+Rc9x+0C3Kc|JS$^F-Xm-+6uxvBE01&gOO{pT7t;HSL zFa*Oh1ywU|E*yC83zp6<0iW_i%IhqI*G=2tmm0=diXJDHHRW402!g@5t+!8R|B$T~`4lNgSu?^Rtwh5{j zh2U$;|(S79+<=CV`g#8T^UT9Q3g*|d&LGxHlR z&oqsS{L@0CAYLTHSZoE(4_57**v{5@P^IUP*!Yd8%W#G{+ePU~^# zx>9eNu5bW-_@*uUSU4wY1BnH@NXshtvXhx5J8*F8j)iI{8~N~H!M9r7PVG$z>^s79 zFLCb9zs*e%rZ7#NsH26)%5TjIhBnT>ckgEfba0()`+j&UuOqkcg8=zBn0Q}vlCSjA zWXnrizszjwGh2(!N3em`Ny@j{)8Znfr&J9IcIP0jFOUFGw(&FiG#gTV=3D=#2|001 zcCb7sE>itz=U6DH#CZRXs;$aH6s%SJ37)cb3)IZkmpF1yKaT zz4NZo2c7D{tCuAy#_0^b$X%)hL5Ohd{R=t*Q;|%l!nN}lhRgEmhF|DfH)b`s+(A!aIz)ET_lm*7#W3nL+hp}6aPuj2>Csv$IgT^1 zHDJ~zC@k)y?U7e_qe-p98j2nK=tP{(m~hnjGYefZ#OvG?@7Lf>soUY?F$f32Xl+*P z+DW*K&W>XF#?Fo<2Kc_?U(PPly3(sIALe8zq%~PUxPzN%RecTbVeN@N?&rJOD|%7> zOSelfIwZLo-&h_MhO=YIJq#i=1WDj&`+%7xm_pwrkOP zpf|R~L#S9uVueb$XFZz4j)w_uz~V5eDXhMoOD;>`i%ZAN^wgX12M1BFe4DJK*{WT* zL58%mAY}C20)D((&wjZpkWY9@l1L>c7`RLF$F*Mmrd1APhbn_lKzWA-x=hYZyDkG} z9M&`t)us<7*0NvaYoO+rE35E}+OckqYA=s?gd`qz3{G7J19H5R<0%!D0)Qh6Q%Gm% z3N||`>GkILc#AB8$p25a(OE%odOvOnDfphc@vs}fjv-Ndr{$InGOt^EFWEFij2{i) zzYeoFgrR=%y?R)V2E(jADCLuZ&>SY}r!RsN#rx9NQHJha(YWb!sAgKbw94I4cI_I? zY0mp(?dDHXfW=g*!>IMTs5z^u3yEPxn)1VJ4?5;E@e40ozY&>M>NXava@gI$&wo29 z+=4d$;_#12*|2a!*s9uc30?y4xeF4-j(KAg(y!DrFuVSti++RlBE%u5^A;-O)nHiK0e6D zoJW}*wS_k&Ae>qX4m#B;uY%yUvDE|kNdT*=l7ZAIL1v5MR7F^0O~6~23^6gzw37WOF1kzDh?wv`Tn5;@v! ztlW{?X1O_~$Zu_>%B;{+wZ4eghLSU73~2?oWz{uLnOeoDoC>S?tNxp*bjM3u3`*6Q zAlP*hH$88lk7L8_ppzj06m3=A<4dGjJ{VQ4@@<(4ZtjuIM{6YuGYa7 z4aBvc|BZfoG@E&VAnZSF(Y{P6e!sa;J15*hu zK?I65E%6eQn`Nh{lxZ{Vf#T&Bph`+@3c@h4*5H5}e7mKUvw^eiMWK7Tm2=YoJ3EjG z6ecxPw%yrerAqsFqTI!R!T%)G0S1&tL-Vpm|NL`Ed{c0%-)$zvt+K$R1v~e6l>7eD zBp3k_pE}Pj<*0NC^dZZ+^u>l!IHzS#A*Sm;4NZBxIw@;&pFq+IXwnI8&t7ac912%F zqgS*TCUS85N2W{K^)*xRaaZ0+Gn#JbI9o4EKh%!zIX(xGzMG!dM=)_%J<69(zF|x+P0YPOa7s2t;oA{E@hWk9#ZLsB?;7vac z4&vvJn-!OgF=lyj8DNgFXaqFMaFA8?O?#C8ap7P_rO)->rlKX&1R+J)ve0ba+21jpJdHSgq--d{H z`C=8%x&Mh>83d*zODHSA!LVbS_iB_E3!kJ}tR>t}1UaA733Q6hPT_Vgpp+MyQq{^h z3GMY_Q!Z@sRB(0R;3aOZ>U^!XBy-g4T0Y6EwM^8BJ5ww(->Ef{^4I-8%hl&^k83rZj_v-S7U{eRd< z9{Ljj0LEwWpOeDnqA<2vrMLNvnuKW>$$GWNt(N)Iz6&P_B{Md_TB2C?%<`HIv7*&W zz-Hu3w}MmF#v7EE%~f*78B@VKWHa>uQI-OeBWVhcCg-5}!KO0j$9E}=#7J)NcJUC)a`$It-t!zY2*dt@rZly3CNc*e42sh}h ztHjygib*=~C|snZVkfdU3&Bs9QOF9@>^UZI>?E)<;jP@V4hzk_cg$T?%T?nG=9?o67h5 z_YwpUT&(&NM9VQb&UF@m7BY4r=Ai-H98)xea|3_aCIT$BMJuTrP3ZXzO`>As7$9!#Q6iUJ2CHFxxaR9qraEOOo~1dq65U<8dv7=L>~(Dg)a!^#!q^M`v(N z=D~`Trl*84MLR{S#*6dqX%}lg8bQ&NJ5_bqlWf?_N7FET0-I7;K2nc+|97|*tHQ0A zLwH94rPG+b>Wpk(1CK-;_mp(RS_E-fqqXxB1AJOM<%g86eoBO0cE)7&Po$3|EU91- znQ!%Eolo=;f3UOPR7QY0t#SWruv&Wk_7FTt zk9fI4Cniw-D!oG51PG{~(7nkl%SkFEjpvjqfl;OJ)Z` zs1JbJi0Yy2e>a3n9&3T8!xVXgRWc;<_j9bFO=QE;k^<^{JNE!GCJZrQnvy4R`)kBOjic~<3+v!4Lx7teONx#v@%Bb8hok!IIEfv;a z!*DGvoB06kGZtXkn6n+>cv5MiBK!i4CTq{+0)F^3(55Ze!ZYps#>U*LEC>r$Q*H3E zTN>45)*m`RpMSlsxg622HQZt2zvu_9Tgm(S>crB-B<=hCvA5xamDS>jMSS;tf?MxE z{U`A25ZM!7YLQPyULh9Pa&o$e6C6=z2xOZg+LveBO-b(6p+dQUR@P$T^|{W`_|zl9 z!Jpo(3uU0xor}ZatUJH|tEF0Ihhh4P9t!T$WCyZ_^rr~!^rzvA^Wx zE;G-Kjmwfzl?(z^?IU10EPIY8bKLxWMrdM^q|K=}oFF%0L-Bwl$(E9?dgFJ$v zfgdr`@L(TQZE9gvWxkMv6lcp*;InMaoQh&3s~@*mV>w+FOzEZ)Mv2OC{ulYfQ9y^b zd#>}3ChBacE0@OGpfRA3?-;Z49vE~>sU^HCpgZ!AwR!vD zi=o&y&p3X7xFE#D^=e%>G&FivnaN@^2H1xz$tGC&MtxQDi<_3&;yQj^xdwID0f7o3 z#bk;ZsY3B%DN2&?S`HWiKa%YK%%4iOp{wrVm)Hm`qkj7b7tbS4eS6^ojC~g5;3`=G zY^znMl+IT!W$v1*v3Kyz%9LEN)e?g`skUu)C-Jv8IkW>N6|Uu2J^KLa8VBUeUDp+x z0~w%J92mn!qHV;H!1BBVs-kKetXCZ*6Jw;_IL2u)@A%7nzyM~!Z%KVyH@EVSB|MZ^)owg1G4cVT#dVrMY zcp0xS0%9Zur(fzrJ!dPBKUeV;(k8Y>YdG?fJksNBEo9$^w-fvgofzy@Bf zZrmk8z3=EFV@$BE>NKhG@VFz(M(O^z^4ZC#xGq)wcjxAK5q?+~84^k%Z^!XeeaFB* zdefwq26h{l6^ngyi7I|Eg`6=l;6*{Lf+m?Jd7eKN)myzD;Z)@(eZ&9E$0V>33B8p! z5m|PiH*|58AWq1`gje7E1OH;E>ss#1U=PJDEm<5w(7GDU4g82|5*K&ihF%_DWV{Y$$?o00NZWv? zpV3}QQV1-}Koag*;xn%N@os}qr4C^k5AKICa1=Tj^krccoudwBuHkcnUQU#sqRwLa z?x>#4Cni=OuNu$xk< zO=}wDX_%l)sGSei@x;n+@`BhxO4M>2OIVPT)CJMnIwK;X`SGT`UrL7wqq#IkM3lAr ziXr1H0giS6^sLH{OB=GENxx&adkz-Wofz9opt=cbT{_zG|OKm_t4CEw$)k#ul!WDM-g`fs2N#q58H^k87u9DX;$53@xaYive)8AHdc3bKUCY^j1mQ z3JZ$Fykz~3%J87vU56NrRKALbrautM855t2kHZMKeS_7_llk#kTTw}cAo~oGxH~wz z3acOdrjUEG@dg%L5a$RlE4j%y{H#=)8B9VCA@QvG68x_uNE*Q?4Xg0IteiEuWRCtx z!1}@x)p4%1I&RZzlYaJSUBiK~~dkCZo1`^x} zN8n4ubhu_8U{)W}DPXHqlcam=!uNfq`i=VCn0$C1@6QIS*E-6MwOh;Sm8ewPU%TE!AC!)oj0a3*V%F=mLi$%}} zy!;l-*m3F2zK48zy!7eF@T{U4;g&8X7!(s7V)!G(q*rY$@&TJ#@h0d=kh>K^wbMaG zzjOi{5E=2bYD_NZayxY1AxfK@ENL@AkKL%|Isr{+xSVFERj1r~Tb{~h1}0&DFSjb2 zQv})UPboMMFN6rOHthswN9lax!9WlAu4cfLs zI_Yq`An`ZnWC&Yg*NnoKB$lY2h2~j6P9_r-oGeo*f|o%l-Zs>tz>jjgwVR+w0}@v# zx;GanJI_k_2jepo_Pl_B`>A)%e}Vsrxf88bErv1}XiTnVGm4~WSS;wGwZwh` zW8J~woU!MqX+u%TsXYh|;4=0e<~MtKnu7ShIRWX$&WO>3-CUmxGODNbYx>tXK9YGX zwI+&|j;gxaPxVF5er3p~t`9ksJmxS9y~Vha;@$eqq!*J&d`BZxw|^LSkrzNsrDQDF z0DL*8dZ<&LhwX+AJEx9t43PAVB^CJj*lJNVhDF8}-tfa`)?bC8@bDo2!Z<2}2EG0Q zlM#)Nl()Kof7b7`CJE#fbs8Z4P^xRDTyMK zHamMFVkT&(uO@_x6#19W#?3ultWzW6^W5u61bbIF2$(wOyUsKwR&bPbWe9*?SM)g5 zgK3PMq{rxgXysHIX{tt>vGU8%FqKa8c?9y?6+?fmSm>)BHPl)Sr&Hft71wo$n$&Ex zeyJoq<`tM6;~d+^!^1=oiyA+efuA+!4p09stWW2^c0Y3~obFd`^y{$iQc=4Yunc1c zNhL;9EgfkpM3_|tD>ZPNwox6cdo(dB5)^##j~h~uV87Bxf4n@L5+>h^%T2G(YbLQ= z2Z)5<4BN@}CY(!9$RT!k95+q+EL_yB|K}FOY;Jw|O_3B<8!snKu#x;zzGa>=`~>p(@LUxD)|3m!^sOj8^pXQCdn^aAME?km3;Kp2I;xmK$#7QA(*_!Lf@B=Bdw#i)qwCh`ggk5?+>=XRmPOj|q z2MVPgha!vNfMs~oqV`S}9KW6if9Hy-~X1J_2*v zv8HWtbSq9a-QtDWWoG{%D*Xt1GVYfM!E*op-CldnmPxoTO4+eoE+ z%O2o$VJlBG!v1M+z}f2W&?j_gi@PFRCk5;5bzl;uVA^=q^E|XQseN7IF~-8Y`LR^` zIT>uZwN+-G$KTBq^$lOr(d4qR#)BR*lO(#eW;3|kC^S~)pXq;}VwOF8L|Bkc>7ERP>yayJWtygmT zbAN_3isS^$?m?A>>b{NmP3IiX{gJB>%l+nEVMUGnf=U&}+gZLHw~Ij2Y6NaZJ!on@ zYG2VeO_7e(Ahq9M5^wxHlV*$DO_IN&8xXKB^2PUl!q407?1b!comENaKs3MO=+1Yv zECxkJoNJxnCs?=-j2pp639q)FPKudttQW{e3E*J~Y;)^Ly27Osh7Xn?a7jx$5P}h} zr^+p-iYD5Z=1d>qQHosC#(AfarRcZanDatjRt4@e_jfuOwKI?+=I`Q$#G_PYz^*r+T-DZBb%Ta~liV@*ooRJq-DD*KXm zQ%XiJ?eu^_aRPMh~+}?#~X z%c|S{fL`VCso5tVk1Otgnq2(Fy#1a+T`ZUB8=I>a=}g`#-aJQwZ!sNOM6=ic7XRsL z47vP`jkqwhtF&*=fFgUNp7d0k&x`sht`-v0%n}pJ_`$*Spejp;t5$*w_C}3Rf}~~x zXcs7uV{^1S-X^NTR$Z%T&XVEV#uYEaF5lJOEX@tkeUUz%D=(Wv$xTj&^|p9?O_3+; zyA_U_xhwPn2qeqTr*UbrjCqdtxHBUNvMcFJ~_VCiW9Rv;TRwE2XSurW>3EksE=`=`YT%x??AO zg6=Bpx*o*0ihjODAlodRDx0XV)T2)2jC_b(g_`tAY3x=CO?0j)(%ytUXBfd5nRN&l zla-n~dM{-%Iuf!vFu{!ZpxkcEbw(E~2J(i51)E*Z4aa_=LIftrBf4);{(nF|bAW)Y~TKLARfUrLeyry++l55n|E$?E8on!IrX$BEv zL-FcpXLo8UuA6_@O1t@$5F((S#v`maMSlT`J9gbB-(JWjzKq{QTlMl`7n@n zl%#Ne3bAv$Yo3#5lq2IWCo(w21rtP=O7S9l!zPL1Wro1l1 z6cCNCAWx%>!`S*ac4xK?TTCVxWe`5?mUW)Sbv=H9Tl~342rAW)u!{mY05Mq(m(V-L zAr*1M^|brx;-(JfceKHZgPTPqfg_$R2~bk#9xXV~f6iN(7iJ7eZrtmQw1Ry62@tz3 zCD?S14jZ{*Or;HBf=I~>HmydXGXAI2pRijH!I?7DYU7c1u2f>6EuoDP1^;L7L zJ;@1>P4Eht9Smzf|4p=6AzoO)>XG^P7kASTM1#S#fL0h!cXe(<4NO0ywUBh=`!4Pr z-%dI)GcEx7>`T$bng7%ISQ$c}(2qc0VNoM$*dNewxy+EYzO%6xxy$~Yw>0`?Pq#cQ z!_6s@QF7!&GIEdJsgY9H@>-2^YLTOuLdFovwc>#*x~RR!3OeaW4qvsmuYvmvJohnq zGF!$_)VxDito?P!fhN(2md-?EaYzc7w~8yr6{{&Bu95{q-w#_&!)X;XDes=0cFjfsi&9x)p@ mol)4NRgg;i+_F0Ge|wyPGCtt~MV|>zZ2Gn!+{Ym!+}$s=LLW2$ literal 10324 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 mRu;3~3PLW& Date: Thu, 6 Jun 2024 14:58:50 -0700 Subject: [PATCH 834/966] feat: Enable webauthn plugin for security keys (#1528) * feat: Enable webauthn handling when plugin is installed. * Minor code cleanup. * feat: Enable webauthn plugin for security keys Move key press prompt and remove TODO question. * feat: Enable webauthn plugin for security keys Fix lint and mypy errors. * feat: Enable webauthn plugin for security keys Check dict accesses for None. Remove commented out line. * feat: Enable webauthn plugin for security keys Change _urlsafe_b64recode to _unpadded_urlsafe_b64recode for clarity. * feat: Enable webauthn plugin for security keys Fix broken test and add test clauses to bring coverage to 100%. --------- Co-authored-by: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> --- .../google-auth/google/oauth2/challenges.py | 78 +++++++++ .../google/oauth2/webauthn_handler_factory.py | 16 ++ .../tests/oauth2/test_challenges.py | 153 ++++++++++++++++++ .../oauth2/test_webauthn_handler_factory.py | 29 ++++ 4 files changed, 276 insertions(+) create mode 100644 packages/google-auth/google/oauth2/webauthn_handler_factory.py create mode 100644 packages/google-auth/tests/oauth2/test_webauthn_handler_factory.py diff --git a/packages/google-auth/google/oauth2/challenges.py b/packages/google-auth/google/oauth2/challenges.py index c55796323ba1..6468498bcb6d 100644 --- a/packages/google-auth/google/oauth2/challenges.py +++ b/packages/google-auth/google/oauth2/challenges.py @@ -22,12 +22,19 @@ from google.auth import _helpers from google.auth import exceptions +from google.oauth2 import webauthn_handler_factory +from google.oauth2.webauthn_types import ( + AuthenticationExtensionsClientInputs, + GetRequest, + PublicKeyCredentialDescriptor, +) REAUTH_ORIGIN = "https://accounts.google.com" SAML_CHALLENGE_MESSAGE = ( "Please run `gcloud auth login` to complete reauthentication with SAML." ) +WEBAUTHN_TIMEOUT_MS = 120000 # Two minute timeout def get_user_password(text): @@ -110,6 +117,17 @@ def is_locally_eligible(self): @_helpers.copy_docstring(ReauthChallenge) def obtain_challenge_input(self, metadata): + # Check if there is an available Webauthn Handler, if not use pyu2f + try: + factory = webauthn_handler_factory.WebauthnHandlerFactory() + webauthn_handler = factory.get_handler() + if webauthn_handler is not None: + sys.stderr.write("Please insert and touch your security key\n") + return self._obtain_challenge_input_webauthn(metadata, webauthn_handler) + except Exception: + # Attempt pyu2f if exception in webauthn flow + pass + try: import pyu2f.convenience.authenticator # type: ignore import pyu2f.errors # type: ignore @@ -173,6 +191,66 @@ def obtain_challenge_input(self, metadata): sys.stderr.write("No security key found.\n") return None + def _obtain_challenge_input_webauthn(self, metadata, webauthn_handler): + sk = metadata.get("securityKey") + if sk is None: + raise exceptions.InvalidValue("securityKey is None") + challenges = sk.get("challenges") + application_id = sk.get("applicationId") + relying_party_id = sk.get("relyingPartyId") + if challenges is None or len(challenges) < 1: + raise exceptions.InvalidValue("challenges is None or empty") + if application_id is None: + raise exceptions.InvalidValue("application_id is None") + if relying_party_id is None: + raise exceptions.InvalidValue("relying_party_id is None") + + allow_credentials = [] + for challenge in challenges: + kh = challenge.get("keyHandle") + if kh is None: + raise exceptions.InvalidValue("keyHandle is None") + key_handle = self._unpadded_urlsafe_b64recode(kh) + allow_credentials.append(PublicKeyCredentialDescriptor(id=key_handle)) + + extension = AuthenticationExtensionsClientInputs(appid=application_id) + + challenge = challenges[0].get("challenge") + if challenge is None: + raise exceptions.InvalidValue("challenge is None") + + get_request = GetRequest( + origin=REAUTH_ORIGIN, + rpid=relying_party_id, + challenge=self._unpadded_urlsafe_b64recode(challenge), + timeout_ms=WEBAUTHN_TIMEOUT_MS, + allow_credentials=allow_credentials, + user_verification="required", + extensions=extension, + ) + + try: + get_response = webauthn_handler.get(get_request) + except Exception as e: + sys.stderr.write("Webauthn Error: {}.\n".format(e)) + raise e + + response = { + "clientData": get_response.response.client_data_json, + "authenticatorData": get_response.response.authenticator_data, + "signatureData": get_response.response.signature, + "applicationId": application_id, + "keyHandle": get_response.id, + "securityKeyReplyType": 2, + } + return {"securityKey": response} + + def _unpadded_urlsafe_b64recode(self, s): + """Converts standard b64 encoded string to url safe b64 encoded string + with no padding.""" + b = base64.urlsafe_b64decode(s) + return base64.urlsafe_b64encode(b).decode().rstrip("=") + class SamlChallenge(ReauthChallenge): """Challenge that asks the users to browse to their ID Providers. diff --git a/packages/google-auth/google/oauth2/webauthn_handler_factory.py b/packages/google-auth/google/oauth2/webauthn_handler_factory.py new file mode 100644 index 000000000000..184329fed7e9 --- /dev/null +++ b/packages/google-auth/google/oauth2/webauthn_handler_factory.py @@ -0,0 +1,16 @@ +from typing import List, Optional + +from google.oauth2.webauthn_handler import PluginHandler, WebAuthnHandler + + +class WebauthnHandlerFactory: + handlers: List[WebAuthnHandler] + + def __init__(self): + self.handlers = [PluginHandler()] + + def get_handler(self) -> Optional[WebAuthnHandler]: + for handler in self.handlers: + if handler.is_available(): + return handler + return None diff --git a/packages/google-auth/tests/oauth2/test_challenges.py b/packages/google-auth/tests/oauth2/test_challenges.py index a06f5528377b..4116b913abd9 100644 --- a/packages/google-auth/tests/oauth2/test_challenges.py +++ b/packages/google-auth/tests/oauth2/test_challenges.py @@ -15,6 +15,7 @@ """Tests for the reauth module.""" import base64 +import os import sys import mock @@ -23,6 +24,13 @@ from google.auth import exceptions from google.oauth2 import challenges +from google.oauth2.webauthn_types import ( + AuthenticationExtensionsClientInputs, + AuthenticatorAssertionResponse, + GetRequest, + GetResponse, + PublicKeyCredentialDescriptor, +) def test_get_user_password(): @@ -54,6 +62,8 @@ def test_security_key(): # Test the case that security key challenge is passed with applicationId and # relyingPartyId the same. + os.environ.pop('"GOOGLE_AUTH_WEBAUTHN_PLUGIN"', None) + with mock.patch("pyu2f.model.RegisteredKey", return_value=mock_key): with mock.patch( "pyu2f.convenience.authenticator.CompositeAuthenticator.Authenticate" @@ -70,6 +80,19 @@ def test_security_key(): print_callback=sys.stderr.write, ) + # Test the case that webauthn plugin is available + os.environ["GOOGLE_AUTH_WEBAUTHN_PLUGIN"] = "plugin" + + with mock.patch( + "google.oauth2.challenges.SecurityKeyChallenge._obtain_challenge_input_webauthn", + return_value={"securityKey": "security key response"}, + ): + + assert challenge.obtain_challenge_input(metadata) == { + "securityKey": "security key response" + } + os.environ.pop('"GOOGLE_AUTH_WEBAUTHN_PLUGIN"', None) + # Test the case that security key challenge is passed with applicationId and # relyingPartyId different, first call works. metadata["securityKey"]["relyingPartyId"] = "security_key_relying_party_id" @@ -173,6 +196,136 @@ def test_security_key(): assert excinfo.match(r"pyu2f dependency is required") +def test_security_key_webauthn(): + metadata = { + "status": "READY", + "challengeId": 2, + "challengeType": "SECURITY_KEY", + "securityKey": { + "applicationId": "security_key_application_id", + "challenges": [ + { + "keyHandle": "some_key", + "challenge": base64.urlsafe_b64encode( + "some_challenge".encode("ascii") + ).decode("ascii"), + } + ], + "relyingPartyId": "security_key_application_id", + }, + } + + challenge = challenges.SecurityKeyChallenge() + + sk = metadata["securityKey"] + sk_challenges = sk["challenges"] + + application_id = sk["applicationId"] + + allow_credentials = [] + for sk_challenge in sk_challenges: + allow_credentials.append( + PublicKeyCredentialDescriptor(id=sk_challenge["keyHandle"]) + ) + + extension = AuthenticationExtensionsClientInputs(appid=application_id) + + get_request = GetRequest( + origin=challenges.REAUTH_ORIGIN, + rpid=application_id, + challenge=challenge._unpadded_urlsafe_b64recode(sk_challenge["challenge"]), + timeout_ms=challenges.WEBAUTHN_TIMEOUT_MS, + allow_credentials=allow_credentials, + user_verification="required", + extensions=extension, + ) + + assertion_resp = AuthenticatorAssertionResponse( + client_data_json="clientDataJSON", + authenticator_data="authenticatorData", + signature="signature", + user_handle="userHandle", + ) + get_response = GetResponse( + id="id", + response=assertion_resp, + authenticator_attachment="authenticatorAttachment", + client_extension_results="clientExtensionResults", + ) + response = { + "clientData": get_response.response.client_data_json, + "authenticatorData": get_response.response.authenticator_data, + "signatureData": get_response.response.signature, + "applicationId": "security_key_application_id", + "keyHandle": get_response.id, + "securityKeyReplyType": 2, + } + + mock_handler = mock.Mock() + mock_handler.get.return_value = get_response + + # Test success case + assert challenge._obtain_challenge_input_webauthn(metadata, mock_handler) == { + "securityKey": response + } + mock_handler.get.assert_called_with(get_request) + + # Test exceptions + + # Missing Values + sk = metadata["securityKey"] + metadata["securityKey"] = None + with pytest.raises(exceptions.InvalidValue): + challenge._obtain_challenge_input_webauthn(metadata, mock_handler) + metadata["securityKey"] = sk + + c = metadata["securityKey"]["challenges"] + metadata["securityKey"]["challenges"] = None + with pytest.raises(exceptions.InvalidValue): + challenge._obtain_challenge_input_webauthn(metadata, mock_handler) + metadata["securityKey"]["challenges"] = [] + with pytest.raises(exceptions.InvalidValue): + challenge._obtain_challenge_input_webauthn(metadata, mock_handler) + metadata["securityKey"]["challenges"] = c + + aid = metadata["securityKey"]["applicationId"] + metadata["securityKey"]["applicationId"] = None + with pytest.raises(exceptions.InvalidValue): + challenge._obtain_challenge_input_webauthn(metadata, mock_handler) + metadata["securityKey"]["applicationId"] = aid + + rpi = metadata["securityKey"]["relyingPartyId"] + metadata["securityKey"]["relyingPartyId"] = None + with pytest.raises(exceptions.InvalidValue): + challenge._obtain_challenge_input_webauthn(metadata, mock_handler) + metadata["securityKey"]["relyingPartyId"] = rpi + + kh = metadata["securityKey"]["challenges"][0]["keyHandle"] + metadata["securityKey"]["challenges"][0]["keyHandle"] = None + with pytest.raises(exceptions.InvalidValue): + challenge._obtain_challenge_input_webauthn(metadata, mock_handler) + metadata["securityKey"]["challenges"][0]["keyHandle"] = kh + + ch = metadata["securityKey"]["challenges"][0]["challenge"] + metadata["securityKey"]["challenges"][0]["challenge"] = None + with pytest.raises(exceptions.InvalidValue): + challenge._obtain_challenge_input_webauthn(metadata, mock_handler) + metadata["securityKey"]["challenges"][0]["challenge"] = ch + + # Handler Exceptions + mock_handler.get.side_effect = exceptions.MalformedError + with pytest.raises(exceptions.MalformedError): + challenge._obtain_challenge_input_webauthn(metadata, mock_handler) + + mock_handler.get.side_effect = exceptions.InvalidResource + with pytest.raises(exceptions.InvalidResource): + challenge._obtain_challenge_input_webauthn(metadata, mock_handler) + + mock_handler.get.side_effect = exceptions.ReauthFailError + with pytest.raises(exceptions.ReauthFailError): + challenge._obtain_challenge_input_webauthn(metadata, mock_handler) + + @mock.patch("getpass.getpass", return_value="foo") def test_password_challenge(getpass_mock): challenge = challenges.PasswordChallenge() diff --git a/packages/google-auth/tests/oauth2/test_webauthn_handler_factory.py b/packages/google-auth/tests/oauth2/test_webauthn_handler_factory.py new file mode 100644 index 000000000000..47890ce4b46c --- /dev/null +++ b/packages/google-auth/tests/oauth2/test_webauthn_handler_factory.py @@ -0,0 +1,29 @@ +import mock +import pytest # type: ignore + +from google.oauth2 import webauthn_handler +from google.oauth2 import webauthn_handler_factory + + +@pytest.fixture +def os_get_stub(): + with mock.patch.object( + webauthn_handler.os.environ, + "get", + return_value="gcloud_webauthn_plugin", + name="fake os.environ.get", + ) as mock_os_environ_get: + yield mock_os_environ_get + + +# Check that get_handler returns a value when env is set, +# that type is PluginHandler, and that no value is returned +# if env not set. +def test_WebauthHandlerFactory_get(os_get_stub): + factory = webauthn_handler_factory.WebauthnHandlerFactory() + assert factory.get_handler() is not None + + assert isinstance(factory.get_handler(), webauthn_handler.PluginHandler) + + os_get_stub.return_value = None + assert factory.get_handler() is None From 27c916de4aac2866206055aac3c30182d612619c Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Thu, 6 Jun 2024 15:40:43 -0700 Subject: [PATCH 835/966] chore: update sys test cred (#1532) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index cb3e978270c4dc428bc7f610ab6e92cd0695102f..65a811ee09cdeda230473f849494cac535efa03a 100644 GIT binary patch literal 10324 zcmV-aD67{BB>?tKRTJ94?D)eAVp%^Nql+o;>uiw~z<+pOuJ`~=77skk+}9GSPyjE~ zv)^Ug;J52C?Vt}HSgQAfYTbm`sfAPSIo^#8C`&q&Fad0hUL4Vg4{EE3`Dq~Ld+ecl z%DLL5pYWq|Z6nx&(ltvi9z5bns z_QKlroB$uz7JRzNRU~YrKMjicau}*dV1t0PpR5Bo4?!e^mWV;mJODv6bfA)3@<6=6w-7I&Fx(SmzL30@ETesxgHA6nAPWx)>fpWkc>7+9jWMtd zvin3@c>o7G=dT6ct3JvO$H`;ZY@sKt%r8=5g`G{2KFncB_tHO3tn>oS#L{E z2oqG5I$}bcQcZ9XHJLo;A76k9f2&|CF_I2Avjy8BogpjTWDzygkUUgzcU3=S^5mG- zxCVz-DcV=~l|NOKg8cQ|E<5*;ba{a4A9yj7zohKf2e8o(=quImjnf8k3Ho$>wtfGx zKLZ#C?0hlnciKBpW~2fU-Rv~qCVFON(@Xl4DM2||>5;2YLmR$n^y_3Yt>*%Sr-#Ukh_54ANP$RU^dLHbubEuh{R&cMoA|)E@FW?c3JEH^p+Zf z)sQWE)X3){NEAz`epGk7GUIrNZcV0(*}J@-NBP!>Kc4unocubr>@&rERS6635wUa@ zwvSZxJXOdQeWgiSEiV+EZ7S-d{l_Q+g#U1GR9C~wKfkSy8F$3c3hK4nxR=1pI7i4%Ejh zzng##pBi)sd=nN&O0*Hgp|0c&%S@{M-wW5qx-3F+;3x(o;~lNF&OM&Ct6+fpDL;#& zX@c3vP*G7i@6QV$3Uo*U%E3 zgUOg#qA}0!Ra(|R<_vy->8Vnh#Dy^EMsBq>c~}TCMt(Ux1v6 zlAZ%CsDsB7eE%8&91{`-iU~o@#(O>n3`EB)tp5&Iy~rAj>a6L~nrgW>e-La;C(#(q zO0FqsPBk)d`R#C!b(1|diF0jO!5-OK_s(Aq*p>Bb6bmL{=LD@5GXoNKU-#&NXRMHB zJC}bhhA+Zt^wc8)0fej;(E1G_FN3APM$aUXIO{Nc^iy=Nbu^0Ct)|wztHj^}z|9`> z<;Jl{aekyPs3KKWfC5L|ao1VNEE*2)7lT^i;ln4e@3|#dd~wu)(JkW#S=%8s2@7tJ z(hbEWd)bmQC%?`PWrX}8*+E>nV#F11^}&CT?iolbm=B%!F-3nxyfXAKkt$uo04>q~ zYj{!WZEuiDUPl2dAm+noi+qAnu-*%>RhPPtJxb^yIW4)RnaH6*5afYm1ko-_Q=M+W z=hiYhnDcSLjTDqJd97Q~M%qXFoA4p50(4a0nQ!@RmxNHvty%)2xpKV!gz*x+MLt55 zu_%x@dKziR{i)}U~5hl@SYA#L1yBKl!$fWfV#9 zu2Se#9o`2wX%X7GFiOJzR-)1u=XrqPRWZybtzp?;>DX*qCbjl|(^Vkj{eb zwk?qB5LS8mf)MUOeLB_lryH-9ZosJOE~rV-n-`PdCfMH-PtL&nq~KadD4ca`ymXG5 zQ52VTURx6c;*tWq*PIS;XDHum z+4bq3PfYLm(k)1rIE|;EM+0X*H&=d>o`=az-hUI0WO^Cpty1AV6xI970fc3fE+#l$ z1hzNvF+5F!*kjp3F-A^B;WmHFPQK>f7S{B#tZuS9b`G)58T!;zaoAF-_-j5E6FL1< z#?_juI8w`(!ciV#0w{gd!x5`1nv+IPldhdwdBjPSp=s8a2q?9|ekpc1Ug`EQFqanQ zC+0y>k0;Tl=JIlimUc|_`mfv{FWA7}B~<)WcnbdoAP^T6#?Ej(Kh;N>hHH~$<_ORB zJFz;(yI1sPuKkHlznFZGdkb$?hO+#h0Rsa=!DWO?9GtCm9R1w1>UBdWwVq$_QeGB6 zy9@`O`=M!1Vb$%N5_h4@Cvf72@lFg`QWY);{ z7qG?5&^y^?OnRt$sv~@f)zFI7ShTu3?OiXjR+@FNO^Hq<`rK_whZCS z(6uDRTR)NS?%|<0ELAAt!(F2}t1<_fb#PW_!r+0oWG{Y0iihJuGVRUW#vJv53(XK< zEhw|_Funm!8#0bN&EwJ#Ey;qa0O_ju^CBJK;3ri3_ zSkqI*7MKHOKrM5o*AoNT?9f@a-?^#EpfZKm_AxF8ByO8qjVhrLK`031!GO`HG--=O zDc3%;+L9D3BNI)tEI_3kULNG2`DySk7$z*{Ao-9{pZS*GWq3d9&A!wiEj3ib=~-1k zIC=J*kVjDMKpqJobwxy9ady+8GI8>I1Z-^|6D?hL%HTS3O+|6G0rlW!vi(iU+9ry~ zZ9n+_ww^FP*~;z)Kt|N^pDh{jH`WYFhV76Zrw`5VH0{9H^!y zO@^^Ek8?p2qrB=+DESAg{V?uAqIZ2hiQ5OpMJyg=>Z?r`gfoe6Hxoz#X>l)Epus9R zU`qkm;A<~}O+dWT=ACXpY^rrfB_N$-D(~cx%etF5$p({X$Mp@?2a3`!Hq0qAz}AB~ z^;z>iv-K>hX`_^=4x|B_TzSBqBvVzD?Ha>N@-!D)Yh_oOj6h=DHo#XE6g#hsli7k5 z$h{>LQH1&Yq)k|?H0zW7OFY5-iz47~;0Cf{yHgU?gn0poV~3FTxsSoZ!V|a2^4am6 zX7fpny7ZoZV7Gt_j+uJE4-67kT>#w()o;h17Ganx%bv2b?MDwkxUSq;0Y%c_TL9&JUtQ=9d40W7Rja-+R?NZ8F>Mes6kC4?`^j#;VQFq7JTXg2- zRqBC$Jvv4i(>uTt&r#a!G;L9q?L%wl9c1oHdXEBMZR!g8W9Qci4Ix^NI-gy>_9}o^ zlD->c0NhkF>Da{Tvs(*&oXRD7dUT+$s{OD1!GN`|5gEY_^7Dk#fEkr$F)|tYnz{Sh zW{=}_FAM^&39>ngdB3++tMZNrU^O(u^CG~bJa_`OXn!9l6SSJ0f5*vh_?g?N_NirI zK~wr0QXojJ_SRbHNhsvSz|J<-iw~I`mqYP3BDk5uEmMfHVwx$#iY|O4qmXyqWy=p_;==vYBZkhH>36T2Pzfa40eJ9rF`D-5>6h8%r{38EM}$t z46}rRw6%a}j$!vsnl>2J&jA;W?jwzXG6^uQhUC! zPvKd4YrcF4;d8|3aeQfSTLi@$emxR<7!6*(^QAqL^?w9W44-}DtPO4Qbm0_h1lx(a z_4>63q0C;lcaZ|THpNC>B6B`X^IH2hPS2bHvZ@R>(0S+z_XLK&vBYq{8xag%jjMyz zpx>N6hZmqa?AC}yu#4t&czB$L5$AgPAVh3+cxqN33~jFxn#|n4;1BO)>=K8 z!|<;^J(upTVg&w_0odCR(+a!i_zpsLE+_i@Q3N^ELgT4`60p-i@@5Eqf|YMNvtgad zT=0WA5bq99=sviR13+A^R3LUHb8EiW=pS~)!BVs4HttEUPIL4s(Kkk&-~+g@f5JH2 z)VNu-csPL;t5@^RaIqJ>rEYny&{KO?s{kzMZmWWi{6`5R`kBntZAPKM zfz*|{NhM{SF_{Yok=0Lq?JDC{n(JJbsHl^%dx1msYRML`MW@LQGjcZZw`wd*jH~qI zA4^SKcnOCmSM@p@(=wz`2A*dRk%q*gF6$><^?O^7HiV74l(3HQo^~5%m!o^+c8Yo_ z$-!~cP!z)bRGogwSC+DF{FN)iu^1{wQ_#Mjf{S)D^=FH;uq%S;^It3%;H-bv{zI=|Ds*nqbmO)ycpnC;9jZ7{- zZA%fJ#JW_Dy=ch1p-JLrih8YnVb6@l4i(kV1>@8tWJm zl?93KZ9GIzBD8@|A^?=e%1j{pw$DJ34VUMDVZD?s6>O-1g*m5heCWpkkh!pZ4UC*{Tf;WZX(*;L0LkYrrU- z;)z*$_4pSlvxkhS2aj=O=eay^i5|WnX?~{KMmEZbs$xJRLU>y>R31FBK~{3dHBv8j z%Hd?N=XaKTFm-nkj>w^G=k|~V!xNy`b- zEv@HhAF2Y?g($IUi1Z6_(_beDkd-fFmHhp?<5gF=wxoXpylk?NoK#&IO@fYy-jb@C z`H%c0BhjM8`W^i-vJB`BLPW5cw$@J`7CU0Rr&_XT{`3w^3+<28IA#@W5Q&UpU}R!& zQ&cXp)pNiWQ;7(2gVTRw=l4Z@LWh`kSfD+G^n-Xk*ai1;TK2ttUhKuTtwr8XZFGWg z+L7C+rkLafMrz3GIE`w0tsoe#_814}a7YVleng}$S@}PqZG)2wog#6;k-Hj(Cj5dy z1b)6yX@(KOD}<3;j&9Q0{-}7qI6(1w`mBepVqdvfG>sD3==#EKmjR>vD6<(Xh;+*) z(Vlig{PNi7W|;GGp5Cl%8%C!ih@>l-gt;M&H^_DvV;L-o#uwqi_Ptl+Pz@wuwr&|= z4}K2F+_UEV1`lH$WZ)c4o8d1TCeKX`|IXXBQhsBSbKkNyiP75L#p-9qD4d0iNUOjl zbNJ?Fs?GL*Px^BN=P z3&p4+fSZN>xste5sLG87=qXA0QsKwSQ&?1mgL4zOmpSC&x@V`FJ&qP;%IHILt(7u8 zsd;jRnwXX`9UszpcQ__6Uh2u1x4&V_R|laN43!?<-e5bW=JMJ?jtSH#d=-j~9`vUq zz;1suYm3bDNCaV*4S>~OlI3H`S=Xf!I8KY(QV81plrhgqr@;B7!hz%iaTb>`7Npw} z@u;)Da>Z}s!M7Gd)@aE@jp3Z$38?|nSs2&!AL{)rqiN+dw+4|>`$|U$xRkn7rUwhw zYS75%*(wdW;brIeE3f@1*+alrBDZIG^v%}~E zPCW&kR#e(3;hx;qruoUryEOY1Pr&$oN_vWNr13QSQyF9F=X0o<^hf1s%`4F$@eizE zaH0wa!XCACI&uK>3ZRO7;mP&pN4h55DX8;Z4CR{IR%}VQC}{ZiX-L_B+p%1CHN`1J z(J2!4lk?Hp-$jJvHH6x7s@j!)9TSz-l41|@=%f&?|0RE((yEFfK`zQHoFCZ%|JRCj zo^xgB7CM!`q~sdzblv3=o)YiRjxd^M!+M9nKuT_}kQj0_oQbhALo+x_0+MDMpErzS z|Go3X7uwJHpHr0hEL}KA@A|+|v5pbrcadi3sTJt^{6C&{*_xRix@OpNgS5Y&$C1_Wkm&9J$$OQ2DG%`u-IZ=IR z>YEOdoz=Fach=Kqm@YcBB<^V;k<-99bjL-+Nr%czmLRZEGb!z?9RD6DfLy};ANU28 zQ&}(_*VnWR(mvc4nXg<|N4^LSLdB%X=0^K$IFS??hr>(acmTL>xbc!X)SX3JjB&0F#K@azkx7 z+c}tQ2aeJk8rU!bYX$3d>LB2fU{?mktP*%_kV5ZI14!X+*^`y-<1}5FaLR!9_)j9H zMvHtoQm-n_pGk;_WyNTgg&GOkfT8eC#)8mC0vFD6X&^M4(W>f63uM)?ia6mnfw(Y4 zM&9>Bda-EaFXuaS_1Gz*Mmk6`A-N@#pN@q9#p9DEla?BX9RV*l`;r1c@d<5hDfH>O zraDPuSF_DA@igy5tuZ;ZW?rtq3UP5x{<)G)pFn&MIO5$t@m*E037k*`n3p8Kl0Oi6bX?57AfLOs2w)Nn=_r2P-R+jwA#>i^k{5oQe+#B zcCqA^!Heg?_X=3gvGI;73BjPgcs6Fn5B=X}xtlwq+TulEcIZX7i04(!^}V(_WpJC; z*I>)-1ik~(w?6@(-poCHfO|EegMgGerfbQ6*Qb;KDIivNB|M@l%>xr6ndW>XY{P81 z=Y8OF=OM=oVWbVpRzkJu3RyvdN0B~ZlWZ9Dd>Pp~j7(_G zAW#c&&4(M#O}`65rLF5_L4ZoCC^>1~1Q;BmH0| zURxiTQx3^#{UiNCP{lE&^PB>v0u;F*!Fq0Fl76q%O0rd2B^#R%W!QwAT3b7c$ zYC+km=Py8=Q^XihQ$Z)lrSDGCojczwA%+jNX({T;JKk{-Fb&^I_ooi|3K!=e{4Mj= zw{%6I$A<$zCfz#p!@yldiTR^o-)uXY@E!bO0Rmo%V63iCbfgrb&5C8{JR5cogWKf} z)qDZ(l*cM51(#nJBv5!IhN%V7u4DPR=OmL5EL#(%dj#{cSr08&e?evpn8Zk!-kC36 zL1%Ss0-Dr?R?pXpE*p~>`+}ZF-0ICGQ~k#@Uz^pIX;8ds$*??hs?AM*xU7n(fJp4Z z%fMr>xC(g!4(zSg^-Q`~0}G#aVtH5Sn}sVN15hY47A|_p(bf1s8=KeA_P=3FX@`y2 zRMXKn8OWQ5mJ6qt>_R_Q9eg0SR*(DdvecW>CPUer?-wR1Wmiky;%;zZnD$lzSxh)3 z2+=&te)#eYkpjnhpoC>WA7Yfs&j2*hP_zl`CDZ_}u+0miPNr!}gZP`YlLy$$eU-1$ zVS{N8if0i*(;2tdCvo2#B3Q|r(}imWE!nwIpapVNf%fI8nIAekB5lN_0KIolhi9;C z3s06huKUiGDs=@q#_zspB;~sAyu0qQ(=zEKv_ZgfuTN$E*M!5sg{6R4byT0hiV4st zlkLw)R5?wBWQyhaGx`)*GFQ|Sx5#YQ4t2rNDy%%&6@f?G!q#1MZ>?%J7-pM?yz7%y za;!Kj*w3K!AqYFWD-=MQGkrIDvpGZtVOrh!4jwQgFZon3@b>(69D0k_YG5jVIlMl+Y1$_nXtc=d;;SbKj~>@;=C zz~@JukfifE0%33};FhDm_t9_likoFd!KB^r zop7wFG_o3UUmP%u&UT>0NLyj~xc43_dAa%jRHBER&P{cj#Nqqgf~V-L zI|HtJ-X|IKS{VR{u2&d5_;htNoi?3g)w!?X2a1F>#1E^7>R|(YGf<2K%?nmB`#k5u zO5-DVKuJ&*B=h*!q(+oocieFZ<$x0);vePKEK4VVPQe#&>heLRjlxDLKS%qF@NOj?%5 zhqrJLT^~;xV8wD)Ezee}F4!KNGI78_*u#wQm19F<~U|A;RJ=e=I2gso!!m)g z2|x@g*}Wx%Ql8B{3&%+Yy!?lw1moRKsG1c7VMuL5*(;9%b&YI}n=u2|^3Usnfebiv zjpk>CQQca(qHg6NI*dDBV!mi9g?}WBMdHdC#j5Mf;@FrNQi+1t^2(L-=tA}PY_y^& z7coLp!-1q6KO60()M7Kp%6lMh#k9NRg4wrx3+dgnJ|+2F&NA2mDfSH_QxJ;^VwuXn`5`(|?J&f;i1m9$x;(zIbB(EQQ9=M!C=^SUG{siVD2GlHCVK z4=7qdH+(bKSYYoZ z)t=qVl}#!HaMKS6MHo;IJCMa^u-yVf<9L=ka% z?zDh2eEr$(WVdQVCWRCcIf-DB;JY-c-F!7quHNUcNT`JjjyNE`6vSJ_FOtM`xbDTh zN7q}N=(x6?nO#Se`{p4yt8*CBbi7Zx9!`ky7EviNc02@ife(-d-!=^mvHqrG8*C4g ze9`ui=g6u`Zlgy_7pR!;y^S;$@MiO9yBIU3qtnQQ?)_oP%02ueDq#0z{G${bQGR2tnst^JgYd^)2A`?-at6g5#_y^L>? z{ub>H;z3Iw8wDmOa>t48Mami*ITu9HB>=1d)jTiG{i~WL8fvm!cntB)rm@&>Ci_KS z+j#>P_f2|w2jUGU5;Ms}cUv$tDo1rj_2vnUADe5u$m_^cPa>^+XA_gwAL);^(`lFB zpwIk{X6#8sqRyfuhBbm_2XX##qd3R$d|Po2?vc@0-nEGBe{gT4J>ua5_3$r@7Fodi zFOB?ZWEioW|2P1h%x-gi)l#FjDV&DB zrDcrM+UB)vpSeE#rTQnNywbEL+MyrI*wQ})4h)wVd^ClV z!|{uJ3GjW3P1k4v-xzVe?)!JMSe-aLu}%;?2NU*U!1qBfLF!oAK(NuvawrvA#hxkm z{^WnHGJOrO6}aGtkcN^laaStVLep-5?$PRVsyKOw$v`e}HQ=YN)w|hFSrFTi0}UdH zMRScdJGU&0?||@mrE&do;$FQA{+m(LvbmK|H)R66(5%in3KFr05sq2SCkB#uWXUCI z0qqwYu4g*NOnK#{Zn{g6?pPfBNsiHQb<#RRjv&<{=9P;G9`~ zP;3J*V0h5;TouxbhpiCyT_B5M|6{6uhODXjb7&zLC^w@s(1k4R#mYqXf9a@D+1 z9YOlt!|mMt$t3D)J~w-WzZ>VKXA|JSv;xx6)Slf;2k1oOFS9STlhjKI2j`-|Znm?j zOw--}Q1=NNwwFqZ6#uPz*Q)R~dIhUTJ@2ozB=^o}xgcYhpN{nJPc)JGqy4%4EeUWU zX9XhL3Lg$;*2hMuA%ICEtv3sTU$S=IJGc*~By83f+>nt~X&IT!yM1ua{rL!oK5MNR zfg*J`;ym{Jd9TwPaL@_gm1AkF)dH1Wq%v~y5Yd}4JdwtdgCU$cv~fveTx0g(6!jNM zskhFoY_M@hZ^)q@M;aZ(DE_9T0`7l;H=~($zt)P)(XnvAXs9-*0|Y-sHT!|f!&Czn z`?}dozIc-_%?pWonP1{n8beDzMtax)!1rPNIp1+IZs4i1wS@1~ATch$HS(8<>6@8c mN2pxNX)3<`E0=jF2OSrA4YN6Afvn}JlZ+lak=`5ix4>q-XXX0< literal 10324 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 From 11b1f4e0504498f6b9cbce0c5bdb229c2b940a44 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 6 Jun 2024 23:16:19 +0000 Subject: [PATCH 836/966] chore(main): release 2.30.0 (#1507) :robot: I have created a release *beep* *boop* --- ## [2.30.0](https://togithub.com/googleapis/google-auth-library-python/compare/v2.29.0...v2.30.0) (2024-06-06) ### Features * Add WebAuthn plugin component to handle WebAuthn get assertion request ([#1464](https://togithub.com/googleapis/google-auth-library-python/issues/1464)) ([e25f336](https://togithub.com/googleapis/google-auth-library-python/commit/e25f336ab49c2018a222458a95ebe083e8a4eb2a)) * ECP Provider drop cryptography requirement ([#1524](https://togithub.com/googleapis/google-auth-library-python/issues/1524)) ([a821d71](https://togithub.com/googleapis/google-auth-library-python/commit/a821d719e2fc7bcdc21737fdf175d6f06aa9a56a)) * Enable webauthn plugin for security keys ([#1528](https://togithub.com/googleapis/google-auth-library-python/issues/1528)) ([e2d5e63](https://togithub.com/googleapis/google-auth-library-python/commit/e2d5e635da2cb2caf8240fb9e07fc381442a9d0c)) ### Bug Fixes * Fix id_token iam endpoint for non-gdu service credentials ([#1506](https://togithub.com/googleapis/google-auth-library-python/issues/1506)) ([93d681e](https://togithub.com/googleapis/google-auth-library-python/commit/93d681e6cfb15eb4a3efada623be8ba73b302257)) * Makes default token_url universe aware ([#1514](https://togithub.com/googleapis/google-auth-library-python/issues/1514)) ([045776e](https://togithub.com/googleapis/google-auth-library-python/commit/045776e5dfa3fb172ffaeb59bfe5c637778a5d34)) --- This PR was generated with [Release Please](https://togithub.com/googleapis/release-please). See [documentation](https://togithub.com/googleapis/release-please#release-please). --- packages/google-auth/CHANGELOG.md | 15 +++++++++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 69663529d36c..9696198e94df 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,21 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.30.0](https://github.com/googleapis/google-auth-library-python/compare/v2.29.0...v2.30.0) (2024-06-06) + + +### Features + +* Add WebAuthn plugin component to handle WebAuthn get assertion request ([#1464](https://github.com/googleapis/google-auth-library-python/issues/1464)) ([e25f336](https://github.com/googleapis/google-auth-library-python/commit/e25f336ab49c2018a222458a95ebe083e8a4eb2a)) +* ECP Provider drop cryptography requirement ([#1524](https://github.com/googleapis/google-auth-library-python/issues/1524)) ([a821d71](https://github.com/googleapis/google-auth-library-python/commit/a821d719e2fc7bcdc21737fdf175d6f06aa9a56a)) +* Enable webauthn plugin for security keys ([#1528](https://github.com/googleapis/google-auth-library-python/issues/1528)) ([e2d5e63](https://github.com/googleapis/google-auth-library-python/commit/e2d5e635da2cb2caf8240fb9e07fc381442a9d0c)) + + +### Bug Fixes + +* Fix id_token iam endpoint for non-gdu service credentials ([#1506](https://github.com/googleapis/google-auth-library-python/issues/1506)) ([93d681e](https://github.com/googleapis/google-auth-library-python/commit/93d681e6cfb15eb4a3efada623be8ba73b302257)) +* Makes default token_url universe aware ([#1514](https://github.com/googleapis/google-auth-library-python/issues/1514)) ([045776e](https://github.com/googleapis/google-auth-library-python/commit/045776e5dfa3fb172ffaeb59bfe5c637778a5d34)) + ## [2.29.0](https://github.com/googleapis/google-auth-library-python/compare/v2.28.2...v2.29.0) (2024-03-18) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index f0dd919dca6d..0800489978c3 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.29.0" +__version__ = "2.30.0" From 5c1a03691fd43a927e2b40b38ea1709f7e882a0b Mon Sep 17 00:00:00 2001 From: Jin Date: Mon, 10 Jun 2024 11:21:48 -0700 Subject: [PATCH 837/966] chore: update token (#1534) --- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 65a811ee09cdeda230473f849494cac535efa03a..36a234dc732032dff3ab3ab66045f75e02414035 100644 GIT binary patch literal 10324 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 From 1931988dc63a70ca2bb641b1535f2731f38abb18 Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Mon, 10 Jun 2024 12:06:10 -0700 Subject: [PATCH 838/966] fix: pass trust_env kwarg to ClientSession (#1533) fixes #1530 --- .../google-auth/google/auth/transport/_aiohttp_requests.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/transport/_aiohttp_requests.py b/packages/google-auth/google/auth/transport/_aiohttp_requests.py index 3a8da917a1dc..bc4d9dc69afe 100644 --- a/packages/google-auth/google/auth/transport/_aiohttp_requests.py +++ b/packages/google-auth/google/auth/transport/_aiohttp_requests.py @@ -307,7 +307,8 @@ async def request( headers[key] = headers[key].decode("utf-8") async with aiohttp.ClientSession( - auto_decompress=self._auto_decompress + auto_decompress=self._auto_decompress, + trust_env=kwargs.get("trust_env", False), ) as self._auth_request_session: auth_request = Request(self._auth_request_session) self._auth_request = auth_request From 346eb1cddd3b8ed11bcd000064201d74730b1a9f Mon Sep 17 00:00:00 2001 From: aeitzman <12433791+aeitzman@users.noreply.github.com> Date: Mon, 10 Jun 2024 13:25:01 -0700 Subject: [PATCH 839/966] feat: adds X509 workload cert logic (#1527) * feat: adds X509 workload cert logic * add JSON checking, and edits comments * Adds comment with more explanation * fix test coverage --------- Co-authored-by: Leo <39062083+lsirac@users.noreply.github.com> --- .../google/auth/transport/_mtls_helper.py | 146 +++++++++++- .../tests/transport/test__mtls_helper.py | 219 +++++++++++++++--- .../google-auth/tests/transport/test_grpc.py | 36 ++- 3 files changed, 339 insertions(+), 62 deletions(-) diff --git a/packages/google-auth/google/auth/transport/_mtls_helper.py b/packages/google-auth/google/auth/transport/_mtls_helper.py index 1b9b9c285c6c..e95b953a10ec 100644 --- a/packages/google-auth/google/auth/transport/_mtls_helper.py +++ b/packages/google-auth/google/auth/transport/_mtls_helper.py @@ -16,13 +16,15 @@ import json import logging -from os import path +from os import environ, path import re import subprocess from google.auth import exceptions CONTEXT_AWARE_METADATA_PATH = "~/.secureConnect/context_aware_metadata.json" +_CERTIFICATE_CONFIGURATION_DEFAULT_PATH = "~/.config/gcloud/certificate_config.json" +_CERTIFICATE_CONFIGURATION_ENV = "GOOGLE_API_CERTIFICATE_CONFIG" _CERT_PROVIDER_COMMAND = "cert_provider_command" _CERT_REGEX = re.compile( b"-----BEGIN CERTIFICATE-----.+-----END CERTIFICATE-----\r?\n?", re.DOTALL @@ -63,26 +65,150 @@ def _check_dca_metadata_path(metadata_path): return metadata_path -def _read_dca_metadata_file(metadata_path): - """Loads context aware metadata from the given path. +def _load_json_file(path): + """Reads and loads JSON from the given path. Used to read both X509 workload certificate and + secure connect configurations. Args: - metadata_path (str): context aware metadata path. + path (str): the path to read from. Returns: - Dict[str, str]: The metadata. + Dict[str, str]: The JSON stored at the file. Raises: - google.auth.exceptions.ClientCertError: If failed to parse metadata as JSON. + google.auth.exceptions.ClientCertError: If failed to parse the file as JSON. """ try: - with open(metadata_path) as f: - metadata = json.load(f) + with open(path) as f: + json_data = json.load(f) except ValueError as caught_exc: new_exc = exceptions.ClientCertError(caught_exc) raise new_exc from caught_exc - return metadata + return json_data + + +def _get_workload_cert_and_key(certificate_config_path=None): + """Read the workload identity cert and key files specified in the certificate config provided. + If no config path is provided, check the environment variable: "GOOGLE_API_CERTIFICATE_CONFIG" + first, then the well known gcloud location: "~/.config/gcloud/certificate_config.json". + + Args: + certificate_config_path (string): The certificate config path. If no path is provided, + the environment variable will be checked first, then the well known gcloud location. + + Returns: + Tuple[Optional[bytes], Optional[bytes]]: client certificate bytes in PEM format and key + bytes in PEM format. + + Raises: + google.auth.exceptions.ClientCertError: if problems occurs when retrieving + the certificate or key information. + """ + absolute_path = _get_cert_config_path(certificate_config_path) + if absolute_path is None: + return None, None + data = _load_json_file(absolute_path) + + if "cert_configs" not in data: + raise exceptions.ClientCertError( + 'Certificate config file {} is in an invalid format, a "cert configs" object is expected'.format( + absolute_path + ) + ) + cert_configs = data["cert_configs"] + + if "workload" not in cert_configs: + raise exceptions.ClientCertError( + 'Certificate config file {} is in an invalid format, a "workload" cert config is expected'.format( + absolute_path + ) + ) + workload = cert_configs["workload"] + + if "cert_path" not in workload: + raise exceptions.ClientCertError( + 'Certificate config file {} is in an invalid format, a "cert_path" is expected in the workload cert config'.format( + absolute_path + ) + ) + cert_path = workload["cert_path"] + + if "key_path" not in workload: + raise exceptions.ClientCertError( + 'Certificate config file {} is in an invalid format, a "key_path" is expected in the workload cert config'.format( + absolute_path + ) + ) + key_path = workload["key_path"] + + return _read_cert_and_key_files(cert_path, key_path) + + +def _get_cert_config_path(certificate_config_path=None): + """Gets the certificate configuration full path using the following order of precedence: + + 1: Explicit override, if set + 2: Environment variable, if set + 3: Well-known location + + Returns "None" if the selected config file does not exist. + + Args: + certificate_config_path (string): The certificate config path. If provided, the well known + location and environment variable will be ignored. + + Returns: + The absolute path of the certificate config file, and None if the file does not exist. + """ + + if certificate_config_path is None: + env_path = environ.get(_CERTIFICATE_CONFIGURATION_ENV, None) + if env_path is not None and env_path != "": + certificate_config_path = env_path + else: + certificate_config_path = _CERTIFICATE_CONFIGURATION_DEFAULT_PATH + + certificate_config_path = path.expanduser(certificate_config_path) + if not path.exists(certificate_config_path): + return None + return certificate_config_path + + +def _read_cert_and_key_files(cert_path, key_path): + cert_data = _read_cert_file(cert_path) + key_data = _read_key_file(key_path) + + return cert_data, key_data + + +def _read_cert_file(cert_path): + with open(cert_path, "rb") as cert_file: + cert_data = cert_file.read() + + cert_match = re.findall(_CERT_REGEX, cert_data) + if len(cert_match) != 1: + raise exceptions.ClientCertError( + "Certificate file {} is in an invalid format, a single PEM formatted certificate is expected".format( + cert_path + ) + ) + return cert_match[0] + + +def _read_key_file(key_path): + with open(key_path, "rb") as key_file: + key_data = key_file.read() + + key_match = re.findall(_KEY_REGEX, key_data) + if len(key_match) != 1: + raise exceptions.ClientCertError( + "Private key file {} is in an invalid format, a single PEM formatted private key is expected".format( + key_path + ) + ) + + return key_match[0] def _run_cert_provider_command(command, expect_encrypted_key=False): @@ -163,7 +289,7 @@ def get_client_ssl_credentials( metadata_path = _check_dca_metadata_path(context_aware_metadata_path) if metadata_path: - metadata_json = _read_dca_metadata_file(metadata_path) + metadata_json = _load_json_file(metadata_path) if _CERT_PROVIDER_COMMAND not in metadata_json: raise exceptions.ClientCertError("Cert provider command is not found") diff --git a/packages/google-auth/tests/transport/test__mtls_helper.py b/packages/google-auth/tests/transport/test__mtls_helper.py index 1621a0530210..b195616dd577 100644 --- a/packages/google-auth/tests/transport/test__mtls_helper.py +++ b/packages/google-auth/tests/transport/test__mtls_helper.py @@ -126,7 +126,7 @@ def test_failure(self): class TestReadMetadataFile(object): def test_success(self): metadata_path = os.path.join(pytest.data_dir, "context_aware_metadata.json") - metadata = _mtls_helper._read_dca_metadata_file(metadata_path) + metadata = _mtls_helper._load_json_file(metadata_path) assert "cert_provider_command" in metadata @@ -134,7 +134,7 @@ def test_file_not_json(self): # read a file which is not json format. metadata_path = os.path.join(pytest.data_dir, "privatekey.pem") with pytest.raises(exceptions.ClientCertError): - _mtls_helper._read_dca_metadata_file(metadata_path) + _mtls_helper._load_json_file(metadata_path) class TestRunCertProviderCommand(object): @@ -277,22 +277,18 @@ class TestGetClientSslCredentials(object): @mock.patch( "google.auth.transport._mtls_helper._run_cert_provider_command", autospec=True ) - @mock.patch( - "google.auth.transport._mtls_helper._read_dca_metadata_file", autospec=True - ) + @mock.patch("google.auth.transport._mtls_helper._load_json_file", autospec=True) @mock.patch( "google.auth.transport._mtls_helper._check_dca_metadata_path", autospec=True ) def test_success( self, mock_check_dca_metadata_path, - mock_read_dca_metadata_file, + mock_load_json_file, mock_run_cert_provider_command, ): mock_check_dca_metadata_path.return_value = True - mock_read_dca_metadata_file.return_value = { - "cert_provider_command": ["command"] - } + mock_load_json_file.return_value = {"cert_provider_command": ["command"]} mock_run_cert_provider_command.return_value = (b"cert", b"key", None) has_cert, cert, key, passphrase = _mtls_helper.get_client_ssl_credentials() assert has_cert @@ -314,22 +310,18 @@ def test_success_without_metadata(self, mock_check_dca_metadata_path): @mock.patch( "google.auth.transport._mtls_helper._run_cert_provider_command", autospec=True ) - @mock.patch( - "google.auth.transport._mtls_helper._read_dca_metadata_file", autospec=True - ) + @mock.patch("google.auth.transport._mtls_helper._load_json_file", autospec=True) @mock.patch( "google.auth.transport._mtls_helper._check_dca_metadata_path", autospec=True ) def test_success_with_encrypted_key( self, mock_check_dca_metadata_path, - mock_read_dca_metadata_file, + mock_load_json_file, mock_run_cert_provider_command, ): mock_check_dca_metadata_path.return_value = True - mock_read_dca_metadata_file.return_value = { - "cert_provider_command": ["command"] - } + mock_load_json_file.return_value = {"cert_provider_command": ["command"]} mock_run_cert_provider_command.return_value = (b"cert", b"key", b"passphrase") has_cert, cert, key, passphrase = _mtls_helper.get_client_ssl_credentials( generate_encrypted_key=True @@ -342,40 +334,34 @@ def test_success_with_encrypted_key( ["command", "--with_passphrase"], expect_encrypted_key=True ) - @mock.patch( - "google.auth.transport._mtls_helper._read_dca_metadata_file", autospec=True - ) + @mock.patch("google.auth.transport._mtls_helper._load_json_file", autospec=True) @mock.patch( "google.auth.transport._mtls_helper._check_dca_metadata_path", autospec=True ) def test_missing_cert_command( - self, mock_check_dca_metadata_path, mock_read_dca_metadata_file + self, mock_check_dca_metadata_path, mock_load_json_file ): mock_check_dca_metadata_path.return_value = True - mock_read_dca_metadata_file.return_value = {} + mock_load_json_file.return_value = {} with pytest.raises(exceptions.ClientCertError): _mtls_helper.get_client_ssl_credentials() @mock.patch( "google.auth.transport._mtls_helper._run_cert_provider_command", autospec=True ) - @mock.patch( - "google.auth.transport._mtls_helper._read_dca_metadata_file", autospec=True - ) + @mock.patch("google.auth.transport._mtls_helper._load_json_file", autospec=True) @mock.patch( "google.auth.transport._mtls_helper._check_dca_metadata_path", autospec=True ) def test_customize_context_aware_metadata_path( self, mock_check_dca_metadata_path, - mock_read_dca_metadata_file, + mock_load_json_file, mock_run_cert_provider_command, ): context_aware_metadata_path = "/path/to/metata/data" mock_check_dca_metadata_path.return_value = context_aware_metadata_path - mock_read_dca_metadata_file.return_value = { - "cert_provider_command": ["command"] - } + mock_load_json_file.return_value = {"cert_provider_command": ["command"]} mock_run_cert_provider_command.return_value = (b"cert", b"key", None) has_cert, cert, key, passphrase = _mtls_helper.get_client_ssl_credentials( @@ -387,7 +373,182 @@ def test_customize_context_aware_metadata_path( assert key == b"key" assert passphrase is None mock_check_dca_metadata_path.assert_called_with(context_aware_metadata_path) - mock_read_dca_metadata_file.assert_called_with(context_aware_metadata_path) + mock_load_json_file.assert_called_with(context_aware_metadata_path) + + +class TestGetWorkloadCertAndKey(object): + @mock.patch("google.auth.transport._mtls_helper._load_json_file", autospec=True) + @mock.patch( + "google.auth.transport._mtls_helper._get_cert_config_path", autospec=True + ) + @mock.patch( + "google.auth.transport._mtls_helper._read_cert_and_key_files", autospec=True + ) + def test_success( + self, + mock_read_cert_and_key_files, + mock_get_cert_config_path, + mock_load_json_file, + ): + cert_config_path = "/path/to/cert" + mock_get_cert_config_path.return_value = "/path/to/cert" + mock_load_json_file.return_value = { + "cert_configs": { + "workload": {"cert_path": "cert/path", "key_path": "key/path"} + } + } + mock_read_cert_and_key_files.return_value = ( + pytest.public_cert_bytes, + pytest.private_key_bytes, + ) + + actual_cert, actual_key = _mtls_helper._get_workload_cert_and_key( + cert_config_path + ) + assert actual_cert == pytest.public_cert_bytes + assert actual_key == pytest.private_key_bytes + + @mock.patch( + "google.auth.transport._mtls_helper._get_cert_config_path", autospec=True + ) + def test_file_not_found_returns_none(self, mock_get_cert_config_path): + mock_get_cert_config_path.return_value = None + + actual_cert, actual_key = _mtls_helper._get_workload_cert_and_key() + assert actual_cert is None + assert actual_key is None + + @mock.patch("google.auth.transport._mtls_helper._load_json_file", autospec=True) + @mock.patch( + "google.auth.transport._mtls_helper._get_cert_config_path", autospec=True + ) + def test_no_cert_configs(self, mock_get_cert_config_path, mock_load_json_file): + mock_get_cert_config_path.return_value = "/path/to/cert" + mock_load_json_file.return_value = {} + + with pytest.raises(exceptions.ClientCertError): + _mtls_helper._get_workload_cert_and_key("") + + @mock.patch("google.auth.transport._mtls_helper._load_json_file", autospec=True) + @mock.patch( + "google.auth.transport._mtls_helper._get_cert_config_path", autospec=True + ) + def test_no_workload(self, mock_get_cert_config_path, mock_load_json_file): + mock_get_cert_config_path.return_value = "/path/to/cert" + mock_load_json_file.return_value = {"cert_configs": {}} + + with pytest.raises(exceptions.ClientCertError): + _mtls_helper._get_workload_cert_and_key("") + + @mock.patch("google.auth.transport._mtls_helper._load_json_file", autospec=True) + @mock.patch( + "google.auth.transport._mtls_helper._get_cert_config_path", autospec=True + ) + def test_no_cert_file(self, mock_get_cert_config_path, mock_load_json_file): + mock_get_cert_config_path.return_value = "/path/to/cert" + mock_load_json_file.return_value = { + "cert_configs": {"workload": {"key_path": "path/to/key"}} + } + + with pytest.raises(exceptions.ClientCertError): + _mtls_helper._get_workload_cert_and_key("") + + @mock.patch("google.auth.transport._mtls_helper._load_json_file", autospec=True) + @mock.patch( + "google.auth.transport._mtls_helper._get_cert_config_path", autospec=True + ) + def test_no_key_file(self, mock_get_cert_config_path, mock_load_json_file): + mock_get_cert_config_path.return_value = "/path/to/cert" + mock_load_json_file.return_value = { + "cert_configs": {"workload": {"cert_path": "path/to/key"}} + } + + with pytest.raises(exceptions.ClientCertError): + _mtls_helper._get_workload_cert_and_key("") + + +class TestReadCertAndKeyFile(object): + def test_success(self): + cert_path = os.path.join(pytest.data_dir, "public_cert.pem") + key_path = os.path.join(pytest.data_dir, "privatekey.pem") + + actual_cert, actual_key = _mtls_helper._read_cert_and_key_files( + cert_path, key_path + ) + assert actual_cert == pytest.public_cert_bytes + assert actual_key == pytest.private_key_bytes + + def test_no_cert_file(self): + cert_path = "fake/file/path" + key_path = os.path.join(pytest.data_dir, "privatekey.pem") + with pytest.raises(FileNotFoundError): + _mtls_helper._read_cert_and_key_files(cert_path, key_path) + + def test_no_key_file(self): + cert_path = os.path.join(pytest.data_dir, "public_cert.pem") + key_path = "fake/file/path" + with pytest.raises(FileNotFoundError): + _mtls_helper._read_cert_and_key_files(cert_path, key_path) + + def test_invalid_cert_file(self): + cert_path = os.path.join(pytest.data_dir, "service_account.json") + key_path = os.path.join(pytest.data_dir, "privatekey.pem") + with pytest.raises(exceptions.ClientCertError): + _mtls_helper._read_cert_and_key_files(cert_path, key_path) + + def test_invalid_key_file(self): + cert_path = os.path.join(pytest.data_dir, "public_cert.pem") + key_path = os.path.join(pytest.data_dir, "public_cert.pem") + with pytest.raises(exceptions.ClientCertError): + _mtls_helper._read_cert_and_key_files(cert_path, key_path) + + +class TestGetCertConfigPath(object): + def test_success_with_override(self): + config_path = os.path.join(pytest.data_dir, "service_account.json") + returned_path = _mtls_helper._get_cert_config_path(config_path) + assert returned_path == config_path + + def test_override_does_not_exist(self): + config_path = "fake/file/path" + returned_path = _mtls_helper._get_cert_config_path(config_path) + assert returned_path is None + + @mock.patch.dict(os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": ""}) + @mock.patch("os.path.exists", autospec=True) + def test_default(self, mock_path_exists): + mock_path_exists.return_value = True + returned_path = _mtls_helper._get_cert_config_path() + expected_path = os.path.expanduser( + _mtls_helper._CERTIFICATE_CONFIGURATION_DEFAULT_PATH + ) + assert returned_path == expected_path + + @mock.patch.dict( + os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": "path/to/config/file"} + ) + @mock.patch("os.path.exists", autospec=True) + def test_env_variable(self, mock_path_exists): + mock_path_exists.return_value = True + returned_path = _mtls_helper._get_cert_config_path() + expected_path = "path/to/config/file" + assert returned_path == expected_path + + @mock.patch.dict(os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": ""}) + @mock.patch("os.path.exists", autospec=True) + def test_env_variable_file_does_not_exist(self, mock_path_exists): + mock_path_exists.return_value = False + returned_path = _mtls_helper._get_cert_config_path() + assert returned_path is None + + @mock.patch.dict( + os.environ, {"GOOGLE_API_CERTIFICATE_CONFIG": "path/to/config/file"} + ) + @mock.patch("os.path.exists", autospec=True) + def test_default_file_does_not_exist(self, mock_path_exists): + mock_path_exists.return_value = False + returned_path = _mtls_helper._get_cert_config_path() + assert returned_path is None class TestGetClientCertAndKey(object): diff --git a/packages/google-auth/tests/transport/test_grpc.py b/packages/google-auth/tests/transport/test_grpc.py index f62ab0eae760..433cc6855226 100644 --- a/packages/google-auth/tests/transport/test_grpc.py +++ b/packages/google-auth/tests/transport/test_grpc.py @@ -141,16 +141,14 @@ def test__get_authorization_headers_with_service_account_and_default_host(self): @mock.patch("grpc.ssl_channel_credentials", autospec=True) @mock.patch("grpc.secure_channel", autospec=True) class TestSecureAuthorizedChannel(object): - @mock.patch( - "google.auth.transport._mtls_helper._read_dca_metadata_file", autospec=True - ) + @mock.patch("google.auth.transport._mtls_helper._load_json_file", autospec=True) @mock.patch( "google.auth.transport._mtls_helper._check_dca_metadata_path", autospec=True ) def test_secure_authorized_channel_adc( self, check_dca_metadata_path, - read_dca_metadata_file, + load_json_file, secure_channel, ssl_channel_credentials, metadata_call_credentials, @@ -164,9 +162,7 @@ def test_secure_authorized_channel_adc( # Mock the context aware metadata and client cert/key so mTLS SSL channel # will be used. check_dca_metadata_path.return_value = METADATA_PATH - read_dca_metadata_file.return_value = { - "cert_provider_command": ["some command"] - } + load_json_file.return_value = {"cert_provider_command": ["some command"]} get_client_ssl_credentials.return_value = ( True, PUBLIC_CERT_BYTES, @@ -334,16 +330,14 @@ def test_secure_authorized_channel_with_client_cert_callback_success( ssl_channel_credentials.return_value, metadata_call_credentials.return_value ) - @mock.patch( - "google.auth.transport._mtls_helper._read_dca_metadata_file", autospec=True - ) + @mock.patch("google.auth.transport._mtls_helper._load_json_file", autospec=True) @mock.patch( "google.auth.transport._mtls_helper._check_dca_metadata_path", autospec=True ) def test_secure_authorized_channel_with_client_cert_callback_failure( self, check_dca_metadata_path, - read_dca_metadata_file, + load_json_file, secure_channel, ssl_channel_credentials, metadata_call_credentials, @@ -405,7 +399,7 @@ def test_secure_authorized_channel_cert_callback_without_client_cert_env( @mock.patch( "google.auth.transport._mtls_helper.get_client_ssl_credentials", autospec=True ) -@mock.patch("google.auth.transport._mtls_helper._read_dca_metadata_file", autospec=True) +@mock.patch("google.auth.transport._mtls_helper._load_json_file", autospec=True) @mock.patch( "google.auth.transport._mtls_helper._check_dca_metadata_path", autospec=True ) @@ -413,7 +407,7 @@ class TestSslCredentials(object): def test_no_context_aware_metadata( self, mock_check_dca_metadata_path, - mock_read_dca_metadata_file, + mock_load_json_file, mock_get_client_ssl_credentials, mock_ssl_channel_credentials, ): @@ -436,14 +430,12 @@ def test_no_context_aware_metadata( def test_get_client_ssl_credentials_failure( self, mock_check_dca_metadata_path, - mock_read_dca_metadata_file, + mock_load_json_file, mock_get_client_ssl_credentials, mock_ssl_channel_credentials, ): mock_check_dca_metadata_path.return_value = METADATA_PATH - mock_read_dca_metadata_file.return_value = { - "cert_provider_command": ["some command"] - } + mock_load_json_file.return_value = {"cert_provider_command": ["some command"]} # Mock that client cert and key are not loaded and exception is raised. mock_get_client_ssl_credentials.side_effect = exceptions.ClientCertError() @@ -457,14 +449,12 @@ def test_get_client_ssl_credentials_failure( def test_get_client_ssl_credentials_success( self, mock_check_dca_metadata_path, - mock_read_dca_metadata_file, + mock_load_json_file, mock_get_client_ssl_credentials, mock_ssl_channel_credentials, ): mock_check_dca_metadata_path.return_value = METADATA_PATH - mock_read_dca_metadata_file.return_value = { - "cert_provider_command": ["some command"] - } + mock_load_json_file.return_value = {"cert_provider_command": ["some command"]} mock_get_client_ssl_credentials.return_value = ( True, PUBLIC_CERT_BYTES, @@ -487,7 +477,7 @@ def test_get_client_ssl_credentials_success( def test_get_client_ssl_credentials_without_client_cert_env( self, mock_check_dca_metadata_path, - mock_read_dca_metadata_file, + mock_load_json_file, mock_get_client_ssl_credentials, mock_ssl_channel_credentials, ): @@ -497,6 +487,6 @@ def test_get_client_ssl_credentials_without_client_cert_env( assert ssl_credentials.ssl_credentials is not None assert not ssl_credentials.is_mtls mock_check_dca_metadata_path.assert_not_called() - mock_read_dca_metadata_file.assert_not_called() + mock_load_json_file.assert_not_called() mock_get_client_ssl_credentials.assert_not_called() mock_ssl_channel_credentials.assert_called_once() From fbf18747ccc60205e06e96370861ac2a273cbc57 Mon Sep 17 00:00:00 2001 From: Carl Lundin <108372512+clundin25@users.noreply.github.com> Date: Mon, 17 Jun 2024 11:15:17 -0700 Subject: [PATCH 840/966] chore: Use cryptography for the Provider as well. This is to have an easier time pinning the correct OpenSSL version. (#1537) * chore: Use cryptography for the Provider as well. This is to have an easier time pinning the correct OpenSSL version. --- .../google/auth/transport/requests.py | 5 ++--- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/google-auth/google/auth/transport/requests.py b/packages/google-auth/google/auth/transport/requests.py index 63a2b4596c47..23a69783dc33 100644 --- a/packages/google-auth/google/auth/transport/requests.py +++ b/packages/google-auth/google/auth/transport/requests.py @@ -267,10 +267,9 @@ def __init__(self, enterprise_cert_file_path): self.signer = _custom_tls_signer.CustomTlsSigner(enterprise_cert_file_path) self.signer.load_libraries() - if not self.signer.should_use_provider(): - import urllib3.contrib.pyopenssl + import urllib3.contrib.pyopenssl - urllib3.contrib.pyopenssl.inject_into_urllib3() + urllib3.contrib.pyopenssl.inject_into_urllib3() poolmanager = create_urllib3_context() poolmanager.load_verify_locations(cafile=certifi.where()) diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 36a234dc732032dff3ab3ab66045f75e02414035..44f0be23b2153efe08f0543058b3eebf1c86152f 100644 GIT binary patch literal 10324 zcmV-aD67{BB>?tKRTIybJ$hH@=1u|gf?U>T$1vX`5spi&&4y#IAay~uH&PO+Pylv+ ziViY$4dFH@B*ka<$zG>%IBe=Aki%az3Ki9FvnWp4^E32m7fH+KAio(fmOw#B{IlDN zQ^xox%*2HbcmDY=Oj9l}K?S^XSw^HE!h8J`^6wLcf59 zllxe{r}6$}zn^X8cp|Ak)cP|;kJ9R62C4SO0YA^|)lCd+2fGgCtkAeH4z`Ou>>5uR z>tzC@xsXi0!zzL{f%~&(-PDGTi3DqQ8>5-yY~hGTJG&JBY44a;vb^SDv~oVtQ#{&Z zT{PBUAQr$(s(5lLqu)PQCwM)B9x|L+#nF1=wUEph8eYDRws3~1y{~4Vqf?hUrX6(e zA3E`pNAjDtlJ}(uvj2Fc3{Wqo3ZM6a>eTk}MI%jAdPkJ6h|w|+GcrwM5fVyBgK*G2 z6b&1^7xUZs`vX(hi0O2H%$tLp1CF($0+!qZaz{ej%P;MtgY)N|M9O@M$g{=wVeca{`g@ zK~)dyR1I43urnR;(+$;c)x4r(G6cs9a*|fQU#Q8xu)&Dk@9H>JSfW|YF@C#lQYZ4Z zYw$y+u_5L)IQdax8m_S~GY@BJLoO%!3hK=897ifDU@HxxB!ak6IM%x(A2EU+lTBHj zSxmMuEYq@V(`8(AdBcfn_X(m=bRR>cOtS4{*z^2_6EqoPJ(CCbj&7oX_V9$DCK>^6 z;#JaLODu)#CI(@V=y={kOaVBr235wo^8Me*kE~u?f^;G|L*6(d|K)C*C+?9RS z0Hum`C?)|;Me-VvH2Hds;t)4d^;6067+VN9zlIUccM&xJ@?ZAdBhVmGdl ze3?8gJrqjhlZlCt~=v}HvAXAwgF$;>&k(L`D@8i ze}s2UP+n5$|9Hnjx(;W9b>TbtG|xWAy9xS!Ef_w z+2ddLgtO+FEqMz;;J^{d(?{SPVaLLaa3x}t??Hjt&N+c+4O*kTG7>Bm9dPIKAStKEY&=g>ff{H`6~3hg#KcWVU6k&WWU0&+&DrRvCzx3{!!ICqJ%JqaW=fy# zT%JME-pGRv>aBF>Qzu{eR4jl-%+nx#e&a_o?C3OtB;hEfoZYUI9|INn7jMEAU2n?T zLqnj^SLassynM9!|G0Q)a4Ks6q-dK1fw;K7DR#PQGM6`tk}`0V=hNtal6(@i zxfe1Rx{LK8*DyJgp5_<%@O^G$a;gaE{a{CenAJ5mO7Z@vD+(W9itJUHDE<^MVUJV2 zMi6p*V$4%5+pxfYkmbfdhftD3S&O;nU|wSR3kGE@v!XjC;h%JPt~ z&d@>RW{I(oyu=x!=^H3CLmM`8c6*TOZ5?2Jm&3V6cNiZ%4QKFm9>6w~7J(4K$Didb z(;fJJ(caZ9YN^r(LN~b-%t2>RGGik9m>`*`w2mDOLYxG`Y8Pj)CLA)P*-_HtYdLHW zJ_A(V9n+l&f|3%m3&=%Cen_kQsXs)x$H?=HTW8i(u+Y&z5g z5|;5R#dVMeav%!arrwjGDK|<3j?AmB*@uBX_h6t==k#c#< zBFd(VXKeUd@~s>_l@d09#YH#{ZGR^K=I`PHrnoO>)98Yk_}SB% zp`O(B#M#8Y}h5B$Cn9HiQoL#aDqBFL{>s5HX^Md4Mfk_=wf zfUfBSr`dMY>5Gkz_a(wT4VP-h?zJ9JoUsdIfTnht=Mu8odd_&BBwEg;=7lFL{Gq3V z+2EQkBESTaLp7jZahQysyns1?yfzHPOoYtiWe`Lt(Zw-Fr3%y0Q`58$bY8gmxp=Cc z#KgWE07By)4uw)rLdOBDkPX#uh)ITEhA5>bNHo%T{+~fFOMPDRX>O)2Kae>y52TVR zu=2E{0a@5xLdZRd)&6v1nT1V`xqYbkf)-OtRPp?znAM;Txa&?@@KRJ}iCtCqPu1Uu z$(c<~-`P7%6L7j+Zo(bz_Z~@dq0hIF*(W+6&AXVe{=QxQMAqG>Fws+woz{cw)Pg;T zC^BkpxznYDwk2AP!K0sHaIE~Z!t1ojJxj`PKQI%+{{VbSIpV{NY#rMN2pI7^d;ZA(L`U0I)fK-Ht%J`id;Vf~Pn95!x zWccm9lr4vn08WJ}ReDz`xCDr-5wk6b1FrT&`ewe#l|ho$)H6d%jZ)5py5AAmyUuTM zB#4HZ_GYpoR$S++YBpNO1`<+=Anlf|DLmoI{9qwJR%94h z@73=OM^+%5U|jcWDPPN3fMoZ5FNDoYE;%bAq=$#)TMtefwyM^^_x1owyD7bMZjh{4 zFCSppk?$4I(Tw~7NDpM?m02~w)1dkw^Mre}?e@{t4^X6rT;fO6t0piL4;(c*Wfv&R z91Eb`?8zV%S)#|^KVP8AqHTr8Ew|Q2kSN_un6oB}IF1105y#L#N`}o_jU)rbZe^Cn z{-Jh3DCn#a#Tnd*4CD;EH{96AY}03kDoWhlL*WN^#X{=v;-`mO6ng=|P##^l36d`2 zTsAG)! z>2Ss5ml<{fL%GcNZ|JssUagw4ORAUMiJfD#kRC(@(rs(8=b73w#P_*%K}06u5M2%E z-BIg^nOLFls!&Bp1?k-^yOQE$|AT65$Zyt|LN0V^Wcg-l|L&Ec3!5zTyRb*??24$y zln|o*_}Bd~E4~2m1=K~ZS~>y1l`?G&eOHo3r(Z;0w{<>f7%$a)4QQ;hUz|#>e!--&l9mfHY_lOWel}Mp*8}#ZyyyPk> z%N&6&B}{yuO;Rl!hKkM-7z#0Mq}lQHBg$Umz;B$=_ffESK&o}G@x%ibU1}&MFJl&@ z?MSoI^(yxC6VYVHzu0zkd`%~YCby}Z0-zM3CI!3E|LZ=#Qn@C(WdNtLVf9yFr)yPh!!_o39V%D@59pt{qXz|{wlW)xism$@2~?NdCB650n)Y|}`h^%+%y4E|fGhs*L{ z5u5P{=acL#aW2=E@JbnI6=PwnY?rSJ+D6zg%T*?{4NWaOOqJKdJmZWV==wDD(?@1-jV0isT7fq!qV@^F$Y{ zlNPbv7o#QAXzP{1Bdn1F-4mxwtjSsR(Pri*U5QJiO*j=CC4fVstypRpZa4izK3QYY z$O0=2cE)oSy`Ej*!wXJUIeECx41wPD?XB{=0><1Q;dom>8)3wUYx`24r>V}wa3zrP zTGnojL;JhF-Ns+g#~z(v=7+}@G#_^Yykweu`J=Wmy?QSx>yBe;`dx8xiX)^%2M;t= zYCP4S53_p^1c?#!=FRT-s{r>fdO~!>`_vo<`U(I9>ToF#bQ%@$2o4xB@#TnF6|-^L zX;9IMkRHdW;CeqTDIMU+R0tgwtkk1Xr1WwA@VF_#dY2i1Wx`Je_c+@9)?vnBN^F8k zpp;WC1ve`@8d3Si$-{ZsHKeSI88Q^u19Ysi>O96pA^iC0fjS`AEF0j*rMLZT3 zw%DtVlp6h*&t$}_zl`!Bd1!dY{iK>14XYZMinz!e(4J zp&^wHbrLn%>SIj&&-BC7P@WR{crebHs{JOJr~_-rzyxuhzs^}~0u(oK>Qu;YdtVNn5F6A-s^iGvrRpL17)M{|K1-9nGb4ma;HA7m3 zq0k!)1dJ2?@{Y5`EzG$z;d<}TIQMY)b+Ba3*5pf5P}+n8U~KMwf_8Q$@CeHI=N&lz za9_{_2b&$@40jdt@r2b=h)YH*k%{`Wt2ZR;CrMpE79cysi)_Zf?88WFBbIEO7iz&A z5u{E>l?FamrWeaYZ^y%?8-BCr&~jgq{#rod9XE|ZUfO%usG300jf^U>4gQc&4%D8% zZo;aLj{tB=rgo;~fbmx2gj*_YZ;7lLMoE#@g!(Urb|stDO>PIlh$Y?4q#x4fCYc=x zD1V2!Y}ZP1AsK?(j=#F2VbbJ?tJ7@D-2Nm%*& zffuwlg@F~`wVv$N?cO%F!JKpN9`Ew|B0|Y6o<_MW+yZnmgRfu}WF_X0XMOyoDcsTn z$9$IVYuK#rfS-xbyBj$DtAMY-RrLly;%I{_lLnTk_P3cntD%jAdU~t8coctl>}doW z_J5`+MsW>8TURL%#F5P(#ZaMeo$7G)u=(T>0~w;KtYNv%GJjacZ!M0|~T_z#g4s}zWmWlLvwz&DNHL<^n4a9G&J1>ieQVdfFwKSbHx+MX; zWur^q?plD>+~He<-00x*53;MzST$igb?ROnWp(bo48W!QY#9Tv#La{?x1Z5MJ8pk6 zn<;(%mPL(gLNRh+$7%YpR(()Yo;i}+vB;gce?_DCWw!=co?v4O2dlA4nE_6EC*@pG zj#=)lq%mNrCq46bbe|Sw)~J|GPp#Tn>-lv1wQ2MIB3M)G5t~OlhnLzB*9@FD)EsM% zee@-@`dL1u+0#yC>+<0K1yrkkT_plKO==fT@~3QJ$d;C1zI!g3LD9D%;vLe_^PZRz z_Fg21nzC^MlPGQRQozhLS@#@=8&hy6+(cOng_ZXYkoifwbkTg5E~Lpn)^r_`wdT3~ zdSNC;y)gnKectX#QHt3CD)5#@$zOfn-Snn_>cjK2CM&Ug# zP&A;#eKt zrex&Vd?cEGi0^fVh+c-?xa2K*Lh=gCi+Xcaq?2+XuR9h7`3KVwa+)>fwsy^%YWJ8pVlfh zrxRN!a3$jooW{D+QN8?q8l2jB#ZD_JV@ zHH0=C^wd2~5Z7mZDv`&SmQ#x*217*Dd(>ya;t`5Z@k~bN2l2kjRzh#cpb^%dFz1I6>Qt1NEj#LF=L<9bRO zCjlkP!gmY7P~?ofrQo0j6tdHa6W=$e?wpQM`hNM= zhwxe4cEDzA9^**P1Eszkf3Z^yTHR8rOngiHktvSu#BKlCX#0I?_C}1TRd4H9sMAxKigGj;|7DWKO+MHWg^>M z(*i$5!ZT11g>ZMKin8%&7(gY}7pgVXO5!cgJ)HO>n zjH~kek2AyhyqX$rX5-m5?zgkvO6g|7t&c_AFhLhh-ZI`KW54}61w~=SI0_PuL--C6 zdP>=+el9iCOE6|c34Li&#{DiU z6QbNO)+MnGCRvMDj<~R0+92lYJ#N@YFa6v+VNPR8g>(zGwki3|@4ZXZBn%Wc~ zLS66aZ$B{1`vxE&(OH{u$EhEZYJl|MoIO-zrgk{sR^1u`=S!+T1VJJY<84ZLUi7Cc zy6LNM58?MvXz>9UJk}+vALwEVb8w9<=5N>6!+b`nERzh5uiJ!2aeB*JA41d=(+rN| z1^uRN0C`DHRc}D$0bf}>)-(6V_MW@e$(ygUa7okCA$)7w#C66T<>@5yO`+!NX*%NW zj3aSUNY<^$I?mXgr*zKl8k!~v;ur){p#bE?{FP){sg#hcBSu}~L@8bI$R>$9D1}00 z9LyKSOg4$c-65Y2Cd)KcY>m{Xa_{xn_aAh3;&=68hw)qxfF+cs^C?~_eZ`ONn^G7h7CcS8zv*N#}$#sF3 zUUHZ`J0_e~cn_CyZNQfkE<9&ul57>4@(Y33vU|W?OFPu>ev`CYzc&i;RvP^f`j8WM zK9YPk5Qm0U_2b?Qt%#i_6cL55cX|8nZtzKRj=|9z4Zq7G%rS&Bi-(k0JU*)Wn&R)b zv_^;>;UV}cF)x*smp8)=c?<6pE5nuch{V~g`qrvEVwt-XfQ2GXOFY@;$^$9q1B(-t z9|mtCv7_O3MgBj)xjyKKPQ<4gO z;#8Q4;$!UpQ&Sn009G{AB4jyD6r^j{WwyeQuWTdTb|~1}sDOHj>+;e-?ba!kTvOw$ zq1Av3t?uk0o~DBUuGbpNeNm5ES?1r=_jxg9rzd?{WSxuVC%-qyiG~mj*zL?H`BHT5 zp7TN?91b_q-z7(pB8nE$7Q~SEkCnUQxh99*@URa+#wc}`XXRhrh;>apLO}iu*T=bZ zqF(vA*>C-;UILP9glQUI+ZEG4IrH3CSlU95N;pQ@F@F7Gce8(Pcg}v!!M|-%sZ03u z5HEE$C0q8zK|tSLL_yJ(rZP^qXp=6G&jVo4ut=eDug$e-A{R&4{|<9!X5I4EAsh^& za(B@EHj1Q@w#wIM^;G!JOmTd=Doy(ZgQG*iQHBK2@0BN&WT2KWXF1DSajJ04Qo^aE zPVt;XlZ@$e+&7gP$CAfu)62HZjyO>{=4I))HZ^1~qt??oT?BOMpBV0A})1^ZIVN3M($yQvgaLqm%F6oF1h3!Mia z)~x@#X^Z3ey_<%2NXTvc!`d0oRL;7Ms#O9JvijwyW*K@H$V6LDF71af1K1!vwX^C$+y+jS~*nCPT2IAgR-&`w2GyuGOF9bL*O=Y1)* zKv}}gvXf<-4r_m?+{?0g!{J2*Vop;7>~&?e#xlf;gM_Q~$}}MbnNijPwnIYkfjnPc zZijjCgU#bYV%H>~_Oh%K@k*iXF4i$9Rv9oBGj+$4WzuF}xaF&-uK7pgf6XjWt6{_| zvb(kthPq-{MAeeufs!KwjLGTBZPnxt!e2*I!A(@8iG!D%zO22E%8i9bZz(^Vsc#S>#9&{>sGCrwqc2H?d zp!dMeXa2r*SF4>q!fLTd8bGepD4+0guog01&$UTG4JoEHXx&ds=tA%Kv}5hi0q4wm zV#n-1BW7@UYV(ayNDiyr%O*19RQXPIpS2P70%x`8ih^ z0jy|VG!HOpaT@8;ZXc6Mb$C3)l}~5&QMahletZ5UfQKb4he(sw)>*fXy@BuSaSNC# zehGtthQVtz8Jxu|lGtWBl?Iy}z1A8pXr>tquNC-wsGB=rIMf=)irbGl{qlm7LOD)% zbH2VYqyI9$G6NY$FuEL1^!!2S6<^f7h$YwiHuZ4W>rVRjzemXpeYuE%XZmTE*hI5P z>oc&nS22dPWn*wIsasLN?dgIY!M_5Oz?htU*jWNNERh@( zlbze}WmSQBmTUR8ODle3ompu{fr|}kAVms29L2I;Ez*)Wu3w0V+`uwzs@{=Z$Is_1 zayxBt1H8(KvpYt|m480(Ndn;BYpQdK^pYT(*n=RICIx7w*H{ywkY?)0@Wzx*hN(~6 zvI|uATt#i>^wYw>!6(PMCO$lGeI3+1RuX;Xz0IVKQ^p*ZthVh{VXz)Ek$TmWyub+m zsNOt?_n5{c3lR*E+8Z$=bgo-Gpgk%54U%g9Qy1qM+)1XygKr2*72*adj#n0On(np| z+vMkMw_2DYNgu9!)=?d? z9Hitz(J9hwMTHL^r*$p&M?%W;X@C`9AW~WiF5X@1te9@0{Sm_{<6FCgc#)~I6~C~p zt*p2=DaWjj9-nDb;d zs*&h_@LBc7P!Q`?T_H6|Jf&+S)xJWDuy*Y`p3$@W27352tNJjgkLg*J?_&VB9H6pI zvWCVru~LbE`w;Y`h>OLWRPGT{Fu83w-nl?ffLi!z;OL|2p&l!PD1Eu~4yC#K!e28) zI`*eU+KP1$n>)PVL>|EDOO*Pm2~r(*gP<98&_7u?R3z%o)9k6<&r?FFysD}3GwW>d zahULZZJy`PApuKa>gX&&T9SfD*neYAykd=+zjU|wd~oQ3E6>+ani`us8nkQ*`$tu0#7ab8k^ z8!AobkwFf;Pb>2R875pS=+5D{q>R3=k)~SkrURw~QJUGBcoCP3nPqnLYR5h#r>lwv z%LFY!Dhc(VelCQo;)7y|%^Gf-Qtwfr!-@Gx)O&tNA7SaiZ)pOcFd7C7mjR@7b5;=>1h4n|p(DQHLZJ`BHZTHyHAdDl=naJHU z_Vu=QtL5Rg=vhy|n>;N93^ZeMb=RX$Z6|mmm$B@vVdoPxntuT$S^1i!A{vV8O3m$x zTXn<9!*e1yAEnApvh$#P|jRk!=hTz!-ySKm$Nt?^?ZoOI0DO@rr+|8H+LCdT$mrToRTlApL z+iJqP{dteKtYZH=hx3pAy^1Aw0ZKiQzGMo?)6(ji+aaTq?uGO442BDRbjHu>%E;y} zkiD_=%tfAUd7y6n3>vUg?I`hkYj{YIq$OKQb0u37^PVtSh%)91X?}7OiPxHszic&= zMU42)5lBebv)#Kc?%A~18Iwe!=h{i95)@AT<21lf<#~it>{@QDk{E7wAA2OTBy39| z=f6Q!{axXlT$TKbeD5xw<1xShRFmeC6_nw@Nc@;AgIwwah)dvOSZjBsgoP zKIqnr%MQd<$bl{q*%luVxrG_G8m`HP1F=kPj*bFA6=+?LAMkJCJw38*$H)E;DK|RJ zIIfCCtd-*At@E7=3~9aV#9D97*sUGlhcbI$Ir_lzCu3pJfSSKXsXxDWLvugXMB*E& zI5btpx!dpEp8Hz!fEM6`L2)VC-5&XYme)BAk!sr^7Go5J`8kxfZCD?>TdtK0O9fm) z(g`X)f*h_w=4>UZs;soj+spfC=j;^~;Vc|i=7~vhYxDEv#wIy6*de4sxG{NxPOK~j zdcz7D?G1DmvBLz1J6A;o=*8kcUi_rM~*6INB;q z%MBpRN>aO24&a!J;UDYuWfgKqB*tm#53vvhJio&c*cRx74-)EiVk^mL9D{b%z!5jn zOy!&~X@7|WK*kd9{URg?mdx);*#X4hz`2uiaqMKB%AZgu#8c@?4^A)}Q`pCJz zcp!cpX{1uugLf>rYQ%>VX}WWW$7mG9xee|ViP?Y4U^+fWi=q_M7Nw5{7V+1-&*n)o zs2-;9_h`CDQXwt(7=ri7Zec+4&9pB*>!lCMIJKSX%= zE&z_qhkUb>(Kosh*$>2xE#seKqur!cp;}7?iht%@6F%U(YZaN9KJ{B&vUgJ2*E&zm z3`$)DTS}Zr`77tv;qIj8rA+ij);sMV$5XN>dujS-27_9M8QYE-9Q=|prFW$SBq8J4 zlSd^>BwTFB9JV~7N+U;3O&i%NF^#52gk?&Q!Kr8jlt722@fylazDV!_(mRqE_U z6spU#TDq|IvR;5F1)KULA=!2t&WSF0?4bEtbWqj6ne`YBgzZ0l1G=!~%e0Wab-m`Q zJSd@qu{(P&GV_JIYa3}aUnGYY}IJZ6@(=!MMJrO(A|v=2jGVlXJRO%L(gkrr_xr_tgK5(TE2HD mqT|(8TJ3-l=UV@NSySr{#f+K%UGhBbFAL_1DA?D-wxTLk#$2WV literal 10324 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 mHjMy`7Jj)S5_6WGfz6scn|*# Date: Thu, 27 Jun 2024 11:29:29 -0700 Subject: [PATCH 841/966] fix: Added py.typed to MANIFEST.in (#1526) * Added py.typed to MANIFEST.in * Moved from MANIFEST.in to py.typed per PR suggestion * chore: Refresh system test creds. * Add py.typed to MANIFEST.in --------- Co-authored-by: Carl Lundin --- packages/google-auth/MANIFEST.in | 1 + packages/google-auth/setup.py | 1 + .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes 3 files changed, 2 insertions(+) diff --git a/packages/google-auth/MANIFEST.in b/packages/google-auth/MANIFEST.in index 2c28207f18c0..e72c449313a0 100644 --- a/packages/google-auth/MANIFEST.in +++ b/packages/google-auth/MANIFEST.in @@ -1,3 +1,4 @@ include README.rst LICENSE CHANGELOG.rst recursive-include tests * global-exclude *.pyc __pycache__ +global-include *.typed diff --git a/packages/google-auth/setup.py b/packages/google-auth/setup.py index 71a3655fad56..4f697a704d8a 100644 --- a/packages/google-auth/setup.py +++ b/packages/google-auth/setup.py @@ -58,6 +58,7 @@ packages=find_namespace_packages( exclude=("tests*", "system_tests*", "docs*", "samples*") ), + package_data={"google.auth": ["py.typed"], "google.oauth2": ["py.typed"]}, install_requires=DEPENDENCIES, extras_require=extras, python_requires=">=3.7", diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 44f0be23b2153efe08f0543058b3eebf1c86152f..254396d3374ff9908e67d7e32443b212482d6bd0 100644 GIT binary patch literal 10324 zcmV-aD67{BB>?tKRTDDSuHDuHL2?{$wa}}zT)Fd^yRR0>kaFl=TRLO4>h}_=Pylv+ ziVpE={!$xFHHh$M>y5NAlVk=3zn$>bIctd(gv*i75xnRvaB zF_>502@x?3jR4|CAFMm_&l+*x07m#Nz5(e^<{2GPDXEU*Gh++ler6;HNaGcN@VwiM zJ}5;|cliL#Gq3STi^$?VP#CL??GhMj$P^Z^byjSzlZ#0E2CkdhxudhRdx8mX4L&z= z@c5`Md!M(Zi=+9-jo=FZ&5NAz*s88AmtT4sqZyO1=?{NJbuO zT}}`Rx3f!&r-ejF^^q!rYkp+rN!@p5q}|gZsHEMSNkM?R$bl#@V`}B?;(8&TU*|q! zaZ4v-hpDF$VcSZ0^M9?c@vMEz>8`L~4lr6Cthw|^f=i*LoP-_E$DX{`_rA@?0ZV~C zJDT&1v-oX*$^wv-oo7;Kd6Is8?`57yU51xT66#VXWk;&^tIP_%VX6U0IK=3vfqeN` ze7|@i(vcw-t_}Q1=++b03x^ym^vzlQO-$XV*~TZbE}KgWNe+}raJwNvj*(q38(~b> z6}34Vl;=C@#X10#OX5B4yoxCJI*jcJ(Cv3h2@gYRLM(Spo5p<1k(0${z4j{e?=1>EtKer{<|`G~k%W_wi9PgI0GU{tfa6NlZ&tSKjf z;9X*n^6vv21Ko1GR$Lg_B&$Y04~W}#z7={CZk&2GynCn@q-x!Rpg<*?A}+?7clhNI z=~zaXlA$a?FQ<#XJcPsA8tc*{owfD+O3H&|OJ>Uylg>xxY#QA?{xtD>fkOUQO3FPw znlpQN17hZl(|Flsi;b58!L-u)bU#d^k7R_X0VJJeE;`6&v#qUu?lAWUNh$wBF?c$Y z#I3IV={6KP6^_%sJsBt}X=(YG42W8BD`E8Fq8YVq@1KHm;gH}U&-N*9WTC|zO0cL+ ztb$oJ{=MQ6&ZEvMIkbNS?NGypHpm;33Vg&{chzyMlvGe^va6T7Bg#qTE~P7SyIB|vHjtU>L7F14@Ee0K1+o;{cRwUm z?v5G0P9w)GO&7Tp_~iOYLU@&kk}=BENPvf1_xyM?{7uwtSBFH9_U(ac5e5Zwag9P5aoXlyygkGteqy#&u%C)Q7Ny4#{T^1h&*xDZFBz-1g{ zMfEIu2*Ea();006ny2b~4wLF)fam!Y- zd5FtMKFdS&Z)I@MxZWo{_Fo|vXmv6EZ-Z$!CJKE4gj|t_{^(yF)|3*!YgpX9yv^C9 z%Hr_#ySS*S`7||-uDawTVgg$MIsTQ|u%`IBpI24E11{yYOK7YT2*|dC;F>)Er32`M z#EBrbdq9^jf*;w}3ij&>Jd$L4pKi(1)bC~(MD1Vt6h!^GLid(iz6$$y?8#=^$&KF& zhmSJBOCicS4yoP6RLBf#afj1Q)8jg=eU4ZvPta1l^9GRE4NbTvQM3OzB{zXJ1hnvHYtPNht^ z>*|pn;4W{DEhw;^Bh5?Xm(3FuAn2#Rv3Ny z;-9taRYfnHQimV>1U0YdH#rnmPQNv+7YUCAzhy;wt3WB)9jw%Ti$$(c6FXJ}9WV1| z?}T@D0@u{K=&>8(~8a)D-EyX8@ts2DETpItoqaJx`O92vSqP^5Ko64uvbKrOgjkTp{Y*$rxvAqKB9&0G_<~xFw$N;r zcrB)}?o3g4Xb}>UMMNCqU8Ptz^$GeTv-BUlr$4=FdfMa>*(_BGXBS>yQ{vQ1@l3e7 z^=s=C9_cX~^`4mYVURfkc37x_qLyhrDINFgv!;2+mNy~=q?<3RVkPgc>`q{iJ zK;ri4WU^o&Xfr`W3*Urt#_9`iuxVlbk6@^U${6_6NB-L*_HJMxoed_bE z#I4$hmDJ?3zd38s1cuSHa#GiPPO?R4$IY^!)?kWa4Wpt6NB&$tTrqlXYULmPKSdK* zOUL^E(HUP6SqzsKLRBgXJ}&hsrl+XJ4O6#x{#wz6v&(cCT_hcZMsa7---KMP#XW>I zfw`Jonce{c|7&w8E7#=!Y^1MD_RGSwtzyK(}nK#{pM3CxG0;~uN6721!9D()66VZodh+&RUp=rssbd7BL9MN?S_?Yd zaZyCYqL+bZZ3W-o*Gp+{<)dUIQ`35*F~=EPp_EDE<0~WvULCZl&SNDG>ojUdwaIbI zMLs%qM-+rz6RzG`4H1=-tCj51WBdcH*7>)}o#JOVe^w&ikRjqvpBYk+0IUC+&B6^g z=Ci^}|HD#}OMO8#k`GUAjlAG9oRlGoKJArE2g#qS9*?H1v{0t!7r6qII;)dYkU5ne z=lT3SC3j+;GT`D5MD0nUiHQ`9E@`p#v!%#!oDe{=An5KIqKsOE-FrDJiy1jYa&$&F zt8oC?aTPE86WkAHSGHaQ%!*1Ybf<2iwj}cH2ESY-j&379`P~og33&X$sk2pQoy)1N z2;T4x&iKoLKwP}a_Rv^+CS?k_D=N+XrF7yv>WiauRK*9wsF3GD0FCm$*)z$*S+#{u zdL{yq*@asUjB6A;P+7gV{i$O}@!;X@crq}(y+i3-TLq)T=yGm7qnRq-GIJ*C(cJ>E zl5tenOBBOoemN^PNzP84wbcj%pph$q6xZGQBv{}*!y8A({!D1OWM16jh@tL-W{DTH z2ufs3@*lwqjr=e6Tg+}F!`R;Iwc>HWO9#m-A%t~fmQ3v?ZIXyh9st~HsK%MA-__3n zl>3YlvLko<9KL%rk~6qk0KzV){vZeLCYp0#iJN;?4u3x_-(ZNS;WI_`E45@LjarY7 z;KjeNUx&f!j5C|Wp?$J~3SPhN;S@>MAzUoXHIl+pC@}C2q8KZ%dv}9zQPhBn^;-QI z_xKRBZj|kmn`j{9=P~03%C{Sdf7_I>>Fryl7m|RIUjZh%+pcQVwZ&$X>9Uk&5&npY z(Hvas{P~}W_S~3`EBFSc6>=Mz8;G=QcMRLoPMpR{xqKh(&L?Ytl{Zy3Yer6E9dk@c zaf;u{ndl4QYTntt!Ta$EkcSmZzdAl6%`TcC>Q?2=5ki26l?HtmfPFgDaa`wK%;fr! zH>M`1!gs9aI^dhskC}}1bm`cGGZ{ySArQUz1kCoT zusx^NaXWM-7G{(@A)+p8?(J0&wGF8kY zwH{NFMi%RrkQ@JIG3j;Wq=<=gJ?`ggg`d{o!}ig=1H7$q zfB=LS;rf!~oRsVwwn%tis~k^hWN4+dvqeOOT*b5UyZDiXyM+^IUByeL8;#bfWqCJs zv3NLU7LLt4)xF``+&pu(Q6GhRLFAH7z*JXk8r$=RqAmpY4b^GOkE{K0I@z%&y(<`R z_)Nmn(w?Oj&?^}zeF`Tt0 zr0;NIb;fmC0|^_gs{cYsb|E5n&5)V{ zX}3;$^>RYv$?X@p|Fml@6;mA9S6NsIxwgtiLY(D@;4!&{Pdaq*2NLm2c+h8QqB~dN zDSle{D@Qj9Fq?UBI%}b_PW}xOXMa=-N)zT;lqvc`3O>Vt&Z^>b+Gh${?h}xWdRfCP znZuy_MB(hLXyxJkK7&Z@P)(`sp%qcRgBZTd)882*X+KG{{kD)t zvavc_iUX0AaX=Kk1baZgYZvT*Q2@AYk^tz7M6D4KEH3c|S$Pw)%SL#tpht zV6%vy6+4P?^stdi==lb=e`B721(bXX22Loj(36fl02Uotaj#Uv5soXBXF{>nBr~-h zHT#zQEO!nTF|w-AX$Kx!T0qE;|5iCagL4w0GsYn;*)I&8)$HZZ( z0)F9LspN%8i{3IG5Uot%6w6Vqi<$1QlT^-h{|SR3q4jQ6{v=#9MoE-Vn8u&Ep+8>E zPt8JCV<=K{9z>a;XIQ5q1Gu~pd!1{A{XvU5-jD@n(Rfg55&?Revfe+gd1{&nuF$H$M%MP)Y|?8(-m3#Tka#UbdLVGkHX0) zAjg;4*@zg5NLj&f{OSI*k2np_-}oJqouJc$c7%oHI;gT9Kff_+*?iBWls#+!z>~Qe z*d%IO+wWXL3)apeo*{9Qp<$$qR_j;IISF5J3Y<62zPWDqvNDBrnk2=)MC8r#Dj-!p%fL3gZm#~8 zzBBF7(nvhZS~*Z@cj*R$WOhHOSOEF3rPu@U*VD;t?!Q&XAy2siqAcmx=W4uBn0x7h z3nt9?Eux!zHTdaF{pBmjTLg)&^P-YhL^h(`_}o-lgt^(22=8^aJ%L!i(XXE#!So#l zRub>8&L^?~p5^)k784E`f_((rG<}$n5u@G4@s_)hHDNr{BRVV6yH-0kX;@EmX z@%;$yL7DF*@!d0x05qqe*z%ziFnB$n3IA2Hg8G8#`%tD}j91gy_MO?m1)uGy2Wg=N zZt^u?z)EQ~iT;jam`)EWhtDsI1G)1|c*@1w=DWfX0fw>pzrJUuEKs&u)B;R!&WxRD z*~37|w4CFAfe=#v1$beWxj+kPI03NrRewnET{t_W?`%ad z(x^@5Ek!$-Y}zkmdcH?cH;q??>N3ghm)&v`A$hhlW;Et@(q<|dMlo~+mPXLTztYlH zgP5KL)OC^)pT(jU6}n@n7~#$U;l)z5Q2@6&@nECWS&0+6sbKdt0JCR8pJ6}(K4nyM zom4a-T#UZK!;$wL4)^z^%%Qc_avB{kZ#~6eE#m*`2S>)uwLEZI=2D?K-Bx(cj(#VJ zG(B8PP5GQ4C-rD|U53V|bWq_5YDf73pbhPR z6HYXa3QEvYXb&@dP8VvQSboq@4sd>-Ok~^9@Qpot@0{S=ac{-i!ph;FDyo&4T4QR| zua+1n2rqL?z)BWMusqHI?_Td(bi)l1=0znVTnU8T_)YK8eX_ia$sNpo9kQ=o3^5Bt zYV)8+50(*^$hvQns|OM!ogv%#v-1%ie~cyGDKx3O|L+(!@(PUf)^7HQ!yZOU(RbPl zyv6U~o+kejZfalN5sfZRY$o#Na^nDEFV#I)QrL8AZ6WfoKlV7X)&bq$!ho}o+ztcQ zhxgZj*;zgXbS(3eEGFyy<5p*1#PjdRZ`1aT1Z1M;BE|a{wAQN0T^>?x?4qnt1Qak2 zyr9tjZ?%($i!uMG)DzMYVx)qC_ykFNp@Urg+oD_aFV}KcSWGTWa7>2EPUJu z5a*YP80Dv{Bdq%tS(KU0P0P}Pl+W@n3Wd80{Q8rnr*}1aMi!)@3-bm=;b?JcY5<-& zmcf}2TZnVY!AJ?6VFYHufP0Nsp@OP~B>XR)U-vK}lg;VQzu%qa!n*2CE}}Tb|Kjka zr%M&K6YD75P)is(2Y}=!B9W*p-*5|GmuWmEvi4r47N`+bT2=R01Z36w1x$XZf@%Wm zY-)e z>^hINTJD2KGYHIvkx5Y*)V@6yW^%fBDs>fRr_ZsoTVjP~aC8JI*uE_}{^`&N>A zKPHqG&dSpi#<_pQ{=>QomPd)KdZ2q$%OBbgQARa-4KJ@dsO%68Nm1vEb$)ibRU#e7 z`NmW<`bgk#JH;-s6(`btFC@GN?dQn~J*$$cVpLk(hNd*b!aI!=-&jlWeIeH)+s(`_ z8ytJ74X3PC3H!=RY(i=q43OUJ9k%#~O}{00WzncY5?>Sl&L@DF0e#(=E|ytyD`AN^ z(6sVE^r)=N>eS2KLxJsn*rYupo_wX90~SQs$prg#@nf^gw#cY9mn)HD-NTPa!t0PWcGKb@X-8bQZ`;22@Q_L2^l(f6%KqjW$5ieu;>-7!olBhw)V!lsgQmJkS)-$;EHW9ih&?P1~ z(l5ysU$20DOwary4Ds9BiCbyWV6HG^@qXq%N7ey1Uy!((mSRERV-GtL)558O z51%nmiaGY!6XxUO%*x#_VcKKP|CxTH!XKR^;PqHY6K9`?rMJ|;TTUiChK6$MRtRBC z{Y|9%+Wn>3q(X6C;rQ2fgahs;zh*u`4yxOZ%5?_5msHCa5ZsA9+Q1+)HBg>_$`-4LP6)h17y=DEk7(mXiLGrHK73 z;JF?heOF@&O9!ukKYLsMkB2Y4KN+P`i2_|;M`JgxD^4lF3=CPb3aZ=nKv#%4x25k! zeN$Gb2Z!7f)vO;~Ud58{7D}Fc5R%B9bco5$Zo>*Jwkiqe~o(dGOvFc>qn(0RNriu!b8T(&4!zk)cNjh=%t@@g}1MM{vgWs~Pc%#4u?_ zm#-2H<-Qp9!Ye@h)`GWM&GRk%^6Xm2qGacL`Z+F~SOK|~7qqn>2VAVGqGG2#vPQx! z2whziGACbaZ$N(gdx_7mC8?RN)Z})_x3Hat(SKR#K{5^OfbtVx}~&Nyt?z%f z0F`EBN9PXPyTqlM!*k%@O&!7H;-;wvqsrxPT0)>9eXn-Zt|=6{aB~E-!oo~3)b$Xi z20H@k)1vG+*Ah5<9e!poC-2|nQWVf)iLy>1%+v_wc#?1Iy;;&6Rr4G<$gqcWf1a?{ ziR4-f!60Hdv)|PtzJkS<$&ghV|1eMru=86dVEGsd?GF+}IhhwpJ}r0Mdi@~KE>+{> zuhubX!|WsN`O?h+>Qw5`wmI{2E_4&pOOq%D1p>>8#We5(y;G>su}y7f+GPo6aEOd; z^8EUrQ;9|)m#7c0JJv#3pWoDgbYB&>f19-PlkjC8GXiW908igE!}vvU!a&pfKZ8** zAKc1U%I4R`>dtc;tE9FxFCkgVjmC~y>p3DBU;`*%8qdD&LLp^k8HW=>rkQJ{GR>Tq zNs$vL&?AF;x*R{r7uN;0a@(II=E1tySf2yEHFMju{I$uC!kwt2r2lJ+ZU~F5;Nkx_O*uDXU>o@F=dpF&4n6l)0V%dnztR}{|2()Y_wRHJ&WB{o^|)9Sq7 z{e)Mtf2lM%a{Tu{+cD9?g%PE8$L!5*Y@@agH`JThIQ>6q{zUBp*l>L}qef}HpX+n_ ze*WX0bwx5?q*<0-x1rqK?h0Hl4V6U7MmvAMydENgIH$heYj^8jMe)<|Uc=5R`EbjV z+=Ms(SQ1ZZ7@}Rwxj2EEA2`@Vw3&^j@_>Znt*$n3pq)p(B4)q+s?wtA;q(!L@ZY(-3+GxMkOzf?;>a*oFHx_q zeTp$!ANW1PrIx<<4z-oAq`np`yU_32v{6=5-rO2ffu*);rzuB9Yoouh_dKU0vN^5q2A z`b98j`8~`U3KhlFEO=%9UUjw?V_GCs)^Y=Le1#oKzdR|Q#9&lXmvTnSW0qwrlFC)W zf)EAOQ{e8EndQ{@3F{4mv_lUDYg|p@Knv39iaHOg6cTPc)iEX=wkl~IO%RSlaI)=c zhoIY$`apY`(y@-4G1sN{kDsw5evitc$+jVG#jjn7Vxz+ceM2)`7AEujVI) z0+d_B4#k<$WGNv7m82DZMmtfDGV~TR0dWeot zKaxY?Kn^d*QYHJGGOjOqK+-gqK5HCi#y?fe!y*OZx(csqM?ct>n(FSr-DKSVPI7WAKVlIOq17?q5=n)<)SBC5T_vo*` z^j!TqMNu*I!08D-Fw6^)q?k0^DxM{%MNgsx%tiX&Xu%CmkN2nho>?^5t4Q(wPrB`d z`%iZimUzU=v_IeyY+McQub-Ly)G(pbB^#4_3QdQFs4BQdej$C1$$q*F8F8SUTX1Rd z9n|=eT_OATeE9`b`!tSU{#VtVc=k?N=ChPHaaLuq3H`_?NXTHm>AMx7lhMC>Y-NWQ zk&Q+*{s-%I?`lgu9GS_86A*gWpnp76ZJM8<2*JsV565!^O_S<9QbRnDBaS$d3T3lJ z0)6yrig(q~xnv%N(Qi%jugkxFzwPeIo8W~_O6`) zATE2FLJV-Bs?^<1^F49{b5UjC{7YMJe%_9#OPjsNBMyX##4~2<*ZZA_rM7LefpPe5 zWtT8rgqgRjy7cYnhpIFkvS->aqQ~PBk88p4M_$m-eo|(mU4vF|v6)j1z%@@E)V6d^ zu`9%Ff@NTDOKR{HYCoy6gD+BLWHJ&IHvC~~#=C^a7?~30836Q9hs*$>^E83iF1nBB|aU9%+ zZXwb4&?;68L!%Seye7s+CY7|jBmf~5z%WnwGmih zF@87^aW-QS!Qcx+I*VoNuvT0n48BS(?kiy${TnfEFgfE?1B-SJ>1*~Dq(y$ zYe^|2G?m3rPu`(x;?(U&qa3Hh%S}qAfdaV_K8&6WmwT+fM+Zv>W(aHz@3~5~!+vS{ zJF+ESn(aSbbGjXWsiJC>X|evS(8G9wSQc%gJa;l%{e94>HvU|=RK3B3qypp#Bq)bv zU3(-5N)y#S5W$Z}wx!n3ugI~N`KGi1rm*s^BSezhF1>0{*nrVuSpe^{I_Rs%lU5Fj zv(}&G^9&PUR9fyjM8DJ$kF^H$p}Lf&qty&bLGrw9HacZ>e&ysCxT^rmg9uB85CfBEVEXn28c>+{-m4?~38(G>KeiPq5gZad;x0R@^y|v65782mB$= zL7JQMR~>0EmSoK2@#f72t$)@?u?NY?gH8>QI!>T0f?3CcXrPn~$dCWz3=$uZ+-Gdr mc^Vrq8C!)2yZ|o+B(X@9A63$DlgJmFOma>u+c+XS`U~|SsXC$n literal 10324 zcmV-aD67{BB>?tKRTIybJ$hH@=1u|gf?U>T$1vX`5spi&&4y#IAay~uH&PO+Pylv+ ziViY$4dFH@B*ka<$zG>%IBe=Aki%az3Ki9FvnWp4^E32m7fH+KAio(fmOw#B{IlDN zQ^xox%*2HbcmDY=Oj9l}K?S^XSw^HE!h8J`^6wLcf59 zllxe{r}6$}zn^X8cp|Ak)cP|;kJ9R62C4SO0YA^|)lCd+2fGgCtkAeH4z`Ou>>5uR z>tzC@xsXi0!zzL{f%~&(-PDGTi3DqQ8>5-yY~hGTJG&JBY44a;vb^SDv~oVtQ#{&Z zT{PBUAQr$(s(5lLqu)PQCwM)B9x|L+#nF1=wUEph8eYDRws3~1y{~4Vqf?hUrX6(e zA3E`pNAjDtlJ}(uvj2Fc3{Wqo3ZM6a>eTk}MI%jAdPkJ6h|w|+GcrwM5fVyBgK*G2 z6b&1^7xUZs`vX(hi0O2H%$tLp1CF($0+!qZaz{ej%P;MtgY)N|M9O@M$g{=wVeca{`g@ zK~)dyR1I43urnR;(+$;c)x4r(G6cs9a*|fQU#Q8xu)&Dk@9H>JSfW|YF@C#lQYZ4Z zYw$y+u_5L)IQdax8m_S~GY@BJLoO%!3hK=897ifDU@HxxB!ak6IM%x(A2EU+lTBHj zSxmMuEYq@V(`8(AdBcfn_X(m=bRR>cOtS4{*z^2_6EqoPJ(CCbj&7oX_V9$DCK>^6 z;#JaLODu)#CI(@V=y={kOaVBr235wo^8Me*kE~u?f^;G|L*6(d|K)C*C+?9RS z0Hum`C?)|;Me-VvH2Hds;t)4d^;6067+VN9zlIUccM&xJ@?ZAdBhVmGdl ze3?8gJrqjhlZlCt~=v}HvAXAwgF$;>&k(L`D@8i ze}s2UP+n5$|9Hnjx(;W9b>TbtG|xWAy9xS!Ef_w z+2ddLgtO+FEqMz;;J^{d(?{SPVaLLaa3x}t??Hjt&N+c+4O*kTG7>Bm9dPIKAStKEY&=g>ff{H`6~3hg#KcWVU6k&WWU0&+&DrRvCzx3{!!ICqJ%JqaW=fy# zT%JME-pGRv>aBF>Qzu{eR4jl-%+nx#e&a_o?C3OtB;hEfoZYUI9|INn7jMEAU2n?T zLqnj^SLassynM9!|G0Q)a4Ks6q-dK1fw;K7DR#PQGM6`tk}`0V=hNtal6(@i zxfe1Rx{LK8*DyJgp5_<%@O^G$a;gaE{a{CenAJ5mO7Z@vD+(W9itJUHDE<^MVUJV2 zMi6p*V$4%5+pxfYkmbfdhftD3S&O;nU|wSR3kGE@v!XjC;h%JPt~ z&d@>RW{I(oyu=x!=^H3CLmM`8c6*TOZ5?2Jm&3V6cNiZ%4QKFm9>6w~7J(4K$Didb z(;fJJ(caZ9YN^r(LN~b-%t2>RGGik9m>`*`w2mDOLYxG`Y8Pj)CLA)P*-_HtYdLHW zJ_A(V9n+l&f|3%m3&=%Cen_kQsXs)x$H?=HTW8i(u+Y&z5g z5|;5R#dVMeav%!arrwjGDK|<3j?AmB*@uBX_h6t==k#c#< zBFd(VXKeUd@~s>_l@d09#YH#{ZGR^K=I`PHrnoO>)98Yk_}SB% zp`O(B#M#8Y}h5B$Cn9HiQoL#aDqBFL{>s5HX^Md4Mfk_=wf zfUfBSr`dMY>5Gkz_a(wT4VP-h?zJ9JoUsdIfTnht=Mu8odd_&BBwEg;=7lFL{Gq3V z+2EQkBESTaLp7jZahQysyns1?yfzHPOoYtiWe`Lt(Zw-Fr3%y0Q`58$bY8gmxp=Cc z#KgWE07By)4uw)rLdOBDkPX#uh)ITEhA5>bNHo%T{+~fFOMPDRX>O)2Kae>y52TVR zu=2E{0a@5xLdZRd)&6v1nT1V`xqYbkf)-OtRPp?znAM;Txa&?@@KRJ}iCtCqPu1Uu z$(c<~-`P7%6L7j+Zo(bz_Z~@dq0hIF*(W+6&AXVe{=QxQMAqG>Fws+woz{cw)Pg;T zC^BkpxznYDwk2AP!K0sHaIE~Z!t1ojJxj`PKQI%+{{VbSIpV{NY#rMN2pI7^d;ZA(L`U0I)fK-Ht%J`id;Vf~Pn95!x zWccm9lr4vn08WJ}ReDz`xCDr-5wk6b1FrT&`ewe#l|ho$)H6d%jZ)5py5AAmyUuTM zB#4HZ_GYpoR$S++YBpNO1`<+=Anlf|DLmoI{9qwJR%94h z@73=OM^+%5U|jcWDPPN3fMoZ5FNDoYE;%bAq=$#)TMtefwyM^^_x1owyD7bMZjh{4 zFCSppk?$4I(Tw~7NDpM?m02~w)1dkw^Mre}?e@{t4^X6rT;fO6t0piL4;(c*Wfv&R z91Eb`?8zV%S)#|^KVP8AqHTr8Ew|Q2kSN_un6oB}IF1105y#L#N`}o_jU)rbZe^Cn z{-Jh3DCn#a#Tnd*4CD;EH{96AY}03kDoWhlL*WN^#X{=v;-`mO6ng=|P##^l36d`2 zTsAG)! z>2Ss5ml<{fL%GcNZ|JssUagw4ORAUMiJfD#kRC(@(rs(8=b73w#P_*%K}06u5M2%E z-BIg^nOLFls!&Bp1?k-^yOQE$|AT65$Zyt|LN0V^Wcg-l|L&Ec3!5zTyRb*??24$y zln|o*_}Bd~E4~2m1=K~ZS~>y1l`?G&eOHo3r(Z;0w{<>f7%$a)4QQ;hUz|#>e!--&l9mfHY_lOWel}Mp*8}#ZyyyPk> z%N&6&B}{yuO;Rl!hKkM-7z#0Mq}lQHBg$Umz;B$=_ffESK&o}G@x%ibU1}&MFJl&@ z?MSoI^(yxC6VYVHzu0zkd`%~YCby}Z0-zM3CI!3E|LZ=#Qn@C(WdNtLVf9yFr)yPh!!_o39V%D@59pt{qXz|{wlW)xism$@2~?NdCB650n)Y|}`h^%+%y4E|fGhs*L{ z5u5P{=acL#aW2=E@JbnI6=PwnY?rSJ+D6zg%T*?{4NWaOOqJKdJmZWV==wDD(?@1-jV0isT7fq!qV@^F$Y{ zlNPbv7o#QAXzP{1Bdn1F-4mxwtjSsR(Pri*U5QJiO*j=CC4fVstypRpZa4izK3QYY z$O0=2cE)oSy`Ej*!wXJUIeECx41wPD?XB{=0><1Q;dom>8)3wUYx`24r>V}wa3zrP zTGnojL;JhF-Ns+g#~z(v=7+}@G#_^Yykweu`J=Wmy?QSx>yBe;`dx8xiX)^%2M;t= zYCP4S53_p^1c?#!=FRT-s{r>fdO~!>`_vo<`U(I9>ToF#bQ%@$2o4xB@#TnF6|-^L zX;9IMkRHdW;CeqTDIMU+R0tgwtkk1Xr1WwA@VF_#dY2i1Wx`Je_c+@9)?vnBN^F8k zpp;WC1ve`@8d3Si$-{ZsHKeSI88Q^u19Ysi>O96pA^iC0fjS`AEF0j*rMLZT3 zw%DtVlp6h*&t$}_zl`!Bd1!dY{iK>14XYZMinz!e(4J zp&^wHbrLn%>SIj&&-BC7P@WR{crebHs{JOJr~_-rzyxuhzs^}~0u(oK>Qu;YdtVNn5F6A-s^iGvrRpL17)M{|K1-9nGb4ma;HA7m3 zq0k!)1dJ2?@{Y5`EzG$z;d<}TIQMY)b+Ba3*5pf5P}+n8U~KMwf_8Q$@CeHI=N&lz za9_{_2b&$@40jdt@r2b=h)YH*k%{`Wt2ZR;CrMpE79cysi)_Zf?88WFBbIEO7iz&A z5u{E>l?FamrWeaYZ^y%?8-BCr&~jgq{#rod9XE|ZUfO%usG300jf^U>4gQc&4%D8% zZo;aLj{tB=rgo;~fbmx2gj*_YZ;7lLMoE#@g!(Urb|stDO>PIlh$Y?4q#x4fCYc=x zD1V2!Y}ZP1AsK?(j=#F2VbbJ?tJ7@D-2Nm%*& zffuwlg@F~`wVv$N?cO%F!JKpN9`Ew|B0|Y6o<_MW+yZnmgRfu}WF_X0XMOyoDcsTn z$9$IVYuK#rfS-xbyBj$DtAMY-RrLly;%I{_lLnTk_P3cntD%jAdU~t8coctl>}doW z_J5`+MsW>8TURL%#F5P(#ZaMeo$7G)u=(T>0~w;KtYNv%GJjacZ!M0|~T_z#g4s}zWmWlLvwz&DNHL<^n4a9G&J1>ieQVdfFwKSbHx+MX; zWur^q?plD>+~He<-00x*53;MzST$igb?ROnWp(bo48W!QY#9Tv#La{?x1Z5MJ8pk6 zn<;(%mPL(gLNRh+$7%YpR(()Yo;i}+vB;gce?_DCWw!=co?v4O2dlA4nE_6EC*@pG zj#=)lq%mNrCq46bbe|Sw)~J|GPp#Tn>-lv1wQ2MIB3M)G5t~OlhnLzB*9@FD)EsM% zee@-@`dL1u+0#yC>+<0K1yrkkT_plKO==fT@~3QJ$d;C1zI!g3LD9D%;vLe_^PZRz z_Fg21nzC^MlPGQRQozhLS@#@=8&hy6+(cOng_ZXYkoifwbkTg5E~Lpn)^r_`wdT3~ zdSNC;y)gnKectX#QHt3CD)5#@$zOfn-Snn_>cjK2CM&Ug# zP&A;#eKt zrex&Vd?cEGi0^fVh+c-?xa2K*Lh=gCi+Xcaq?2+XuR9h7`3KVwa+)>fwsy^%YWJ8pVlfh zrxRN!a3$jooW{D+QN8?q8l2jB#ZD_JV@ zHH0=C^wd2~5Z7mZDv`&SmQ#x*217*Dd(>ya;t`5Z@k~bN2l2kjRzh#cpb^%dFz1I6>Qt1NEj#LF=L<9bRO zCjlkP!gmY7P~?ofrQo0j6tdHa6W=$e?wpQM`hNM= zhwxe4cEDzA9^**P1Eszkf3Z^yTHR8rOngiHktvSu#BKlCX#0I?_C}1TRd4H9sMAxKigGj;|7DWKO+MHWg^>M z(*i$5!ZT11g>ZMKin8%&7(gY}7pgVXO5!cgJ)HO>n zjH~kek2AyhyqX$rX5-m5?zgkvO6g|7t&c_AFhLhh-ZI`KW54}61w~=SI0_PuL--C6 zdP>=+el9iCOE6|c34Li&#{DiU z6QbNO)+MnGCRvMDj<~R0+92lYJ#N@YFa6v+VNPR8g>(zGwki3|@4ZXZBn%Wc~ zLS66aZ$B{1`vxE&(OH{u$EhEZYJl|MoIO-zrgk{sR^1u`=S!+T1VJJY<84ZLUi7Cc zy6LNM58?MvXz>9UJk}+vALwEVb8w9<=5N>6!+b`nERzh5uiJ!2aeB*JA41d=(+rN| z1^uRN0C`DHRc}D$0bf}>)-(6V_MW@e$(ygUa7okCA$)7w#C66T<>@5yO`+!NX*%NW zj3aSUNY<^$I?mXgr*zKl8k!~v;ur){p#bE?{FP){sg#hcBSu}~L@8bI$R>$9D1}00 z9LyKSOg4$c-65Y2Cd)KcY>m{Xa_{xn_aAh3;&=68hw)qxfF+cs^C?~_eZ`ONn^G7h7CcS8zv*N#}$#sF3 zUUHZ`J0_e~cn_CyZNQfkE<9&ul57>4@(Y33vU|W?OFPu>ev`CYzc&i;RvP^f`j8WM zK9YPk5Qm0U_2b?Qt%#i_6cL55cX|8nZtzKRj=|9z4Zq7G%rS&Bi-(k0JU*)Wn&R)b zv_^;>;UV}cF)x*smp8)=c?<6pE5nuch{V~g`qrvEVwt-XfQ2GXOFY@;$^$9q1B(-t z9|mtCv7_O3MgBj)xjyKKPQ<4gO z;#8Q4;$!UpQ&Sn009G{AB4jyD6r^j{WwyeQuWTdTb|~1}sDOHj>+;e-?ba!kTvOw$ zq1Av3t?uk0o~DBUuGbpNeNm5ES?1r=_jxg9rzd?{WSxuVC%-qyiG~mj*zL?H`BHT5 zp7TN?91b_q-z7(pB8nE$7Q~SEkCnUQxh99*@URa+#wc}`XXRhrh;>apLO}iu*T=bZ zqF(vA*>C-;UILP9glQUI+ZEG4IrH3CSlU95N;pQ@F@F7Gce8(Pcg}v!!M|-%sZ03u z5HEE$C0q8zK|tSLL_yJ(rZP^qXp=6G&jVo4ut=eDug$e-A{R&4{|<9!X5I4EAsh^& za(B@EHj1Q@w#wIM^;G!JOmTd=Doy(ZgQG*iQHBK2@0BN&WT2KWXF1DSajJ04Qo^aE zPVt;XlZ@$e+&7gP$CAfu)62HZjyO>{=4I))HZ^1~qt??oT?BOMpBV0A})1^ZIVN3M($yQvgaLqm%F6oF1h3!Mia z)~x@#X^Z3ey_<%2NXTvc!`d0oRL;7Ms#O9JvijwyW*K@H$V6LDF71af1K1!vwX^C$+y+jS~*nCPT2IAgR-&`w2GyuGOF9bL*O=Y1)* zKv}}gvXf<-4r_m?+{?0g!{J2*Vop;7>~&?e#xlf;gM_Q~$}}MbnNijPwnIYkfjnPc zZijjCgU#bYV%H>~_Oh%K@k*iXF4i$9Rv9oBGj+$4WzuF}xaF&-uK7pgf6XjWt6{_| zvb(kthPq-{MAeeufs!KwjLGTBZPnxt!e2*I!A(@8iG!D%zO22E%8i9bZz(^Vsc#S>#9&{>sGCrwqc2H?d zp!dMeXa2r*SF4>q!fLTd8bGepD4+0guog01&$UTG4JoEHXx&ds=tA%Kv}5hi0q4wm zV#n-1BW7@UYV(ayNDiyr%O*19RQXPIpS2P70%x`8ih^ z0jy|VG!HOpaT@8;ZXc6Mb$C3)l}~5&QMahletZ5UfQKb4he(sw)>*fXy@BuSaSNC# zehGtthQVtz8Jxu|lGtWBl?Iy}z1A8pXr>tquNC-wsGB=rIMf=)irbGl{qlm7LOD)% zbH2VYqyI9$G6NY$FuEL1^!!2S6<^f7h$YwiHuZ4W>rVRjzemXpeYuE%XZmTE*hI5P z>oc&nS22dPWn*wIsasLN?dgIY!M_5Oz?htU*jWNNERh@( zlbze}WmSQBmTUR8ODle3ompu{fr|}kAVms29L2I;Ez*)Wu3w0V+`uwzs@{=Z$Is_1 zayxBt1H8(KvpYt|m480(Ndn;BYpQdK^pYT(*n=RICIx7w*H{ywkY?)0@Wzx*hN(~6 zvI|uATt#i>^wYw>!6(PMCO$lGeI3+1RuX;Xz0IVKQ^p*ZthVh{VXz)Ek$TmWyub+m zsNOt?_n5{c3lR*E+8Z$=bgo-Gpgk%54U%g9Qy1qM+)1XygKr2*72*adj#n0On(np| z+vMkMw_2DYNgu9!)=?d? z9Hitz(J9hwMTHL^r*$p&M?%W;X@C`9AW~WiF5X@1te9@0{Sm_{<6FCgc#)~I6~C~p zt*p2=DaWjj9-nDb;d zs*&h_@LBc7P!Q`?T_H6|Jf&+S)xJWDuy*Y`p3$@W27352tNJjgkLg*J?_&VB9H6pI zvWCVru~LbE`w;Y`h>OLWRPGT{Fu83w-nl?ffLi!z;OL|2p&l!PD1Eu~4yC#K!e28) zI`*eU+KP1$n>)PVL>|EDOO*Pm2~r(*gP<98&_7u?R3z%o)9k6<&r?FFysD}3GwW>d zahULZZJy`PApuKa>gX&&T9SfD*neYAykd=+zjU|wd~oQ3E6>+ani`us8nkQ*`$tu0#7ab8k^ z8!AobkwFf;Pb>2R875pS=+5D{q>R3=k)~SkrURw~QJUGBcoCP3nPqnLYR5h#r>lwv z%LFY!Dhc(VelCQo;)7y|%^Gf-Qtwfr!-@Gx)O&tNA7SaiZ)pOcFd7C7mjR@7b5;=>1h4n|p(DQHLZJ`BHZTHyHAdDl=naJHU z_Vu=QtL5Rg=vhy|n>;N93^ZeMb=RX$Z6|mmm$B@vVdoPxntuT$S^1i!A{vV8O3m$x zTXn<9!*e1yAEnApvh$#P|jRk!=hTz!-ySKm$Nt?^?ZoOI0DO@rr+|8H+LCdT$mrToRTlApL z+iJqP{dteKtYZH=hx3pAy^1Aw0ZKiQzGMo?)6(ji+aaTq?uGO442BDRbjHu>%E;y} zkiD_=%tfAUd7y6n3>vUg?I`hkYj{YIq$OKQb0u37^PVtSh%)91X?}7OiPxHszic&= zMU42)5lBebv)#Kc?%A~18Iwe!=h{i95)@AT<21lf<#~it>{@QDk{E7wAA2OTBy39| z=f6Q!{axXlT$TKbeD5xw<1xShRFmeC6_nw@Nc@;AgIwwah)dvOSZjBsgoP zKIqnr%MQd<$bl{q*%luVxrG_G8m`HP1F=kPj*bFA6=+?LAMkJCJw38*$H)E;DK|RJ zIIfCCtd-*At@E7=3~9aV#9D97*sUGlhcbI$Ir_lzCu3pJfSSKXsXxDWLvugXMB*E& zI5btpx!dpEp8Hz!fEM6`L2)VC-5&XYme)BAk!sr^7Go5J`8kxfZCD?>TdtK0O9fm) z(g`X)f*h_w=4>UZs;soj+spfC=j;^~;Vc|i=7~vhYxDEv#wIy6*de4sxG{NxPOK~j zdcz7D?G1DmvBLz1J6A;o=*8kcUi_rM~*6INB;q z%MBpRN>aO24&a!J;UDYuWfgKqB*tm#53vvhJio&c*cRx74-)EiVk^mL9D{b%z!5jn zOy!&~X@7|WK*kd9{URg?mdx);*#X4hz`2uiaqMKB%AZgu#8c@?4^A)}Q`pCJz zcp!cpX{1uugLf>rYQ%>VX}WWW$7mG9xee|ViP?Y4U^+fWi=q_M7Nw5{7V+1-&*n)o zs2-;9_h`CDQXwt(7=ri7Zec+4&9pB*>!lCMIJKSX%= zE&z_qhkUb>(Kosh*$>2xE#seKqur!cp;}7?iht%@6F%U(YZaN9KJ{B&vUgJ2*E&zm z3`$)DTS}Zr`77tv;qIj8rA+ij);sMV$5XN>dujS-27_9M8QYE-9Q=|prFW$SBq8J4 zlSd^>BwTFB9JV~7N+U;3O&i%NF^#52gk?&Q!Kr8jlt722@fylazDV!_(mRqE_U z6spU#TDq|IvR;5F1)KULA=!2t&WSF0?4bEtbWqj6ne`YBgzZ0l1G=!~%e0Wab-m`Q zJSd@qu{(P&GV_JIYa3}aUnGYY}IJZ6@(=!MMJrO(A|v=2jGVlXJRO%L(gkrr_xr_tgK5(TE2HD mqT|(8TJ3-l=UV@NSySr{#f+K%UGhBbFAL_1DA?D-wxTLk#$2WV From cd255e9973acfcbd71cfc572049d97bbfe1c7553 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 09:13:33 -0700 Subject: [PATCH 842/966] chore(main): release 2.31.0 (#1535) --- packages/google-auth/CHANGELOG.md | 13 +++++++++++++ packages/google-auth/google/auth/version.py | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/CHANGELOG.md b/packages/google-auth/CHANGELOG.md index 9696198e94df..ffc55ac9c66e 100644 --- a/packages/google-auth/CHANGELOG.md +++ b/packages/google-auth/CHANGELOG.md @@ -4,6 +4,19 @@ [1]: https://pypi.org/project/google-auth/#history +## [2.31.0](https://github.com/googleapis/google-auth-library-python/compare/v2.30.0...v2.31.0) (2024-06-27) + + +### Features + +* Adds X509 workload cert logic ([#1527](https://github.com/googleapis/google-auth-library-python/issues/1527)) ([05220e0](https://github.com/googleapis/google-auth-library-python/commit/05220e06a23b3d4c398628918dab113abcce52db)) + + +### Bug Fixes + +* Added py.typed to MANIFEST.in ([#1526](https://github.com/googleapis/google-auth-library-python/issues/1526)) ([1829a3b](https://github.com/googleapis/google-auth-library-python/commit/1829a3be18aea5eca8b7272111d75eb61a6077b1)) +* Pass trust_env kwarg to ClientSession ([#1533](https://github.com/googleapis/google-auth-library-python/issues/1533)) ([6c15c9a](https://github.com/googleapis/google-auth-library-python/commit/6c15c9ad0ff90cf9bac044b38e72956aa3039cf6)), closes [#1530](https://github.com/googleapis/google-auth-library-python/issues/1530) + ## [2.30.0](https://github.com/googleapis/google-auth-library-python/compare/v2.29.0...v2.30.0) (2024-06-06) diff --git a/packages/google-auth/google/auth/version.py b/packages/google-auth/google/auth/version.py index 0800489978c3..b9313c667da0 100644 --- a/packages/google-auth/google/auth/version.py +++ b/packages/google-auth/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.30.0" +__version__ = "2.31.0" From c345077b66be4e75d73752001c10ad976e3395fb Mon Sep 17 00:00:00 2001 From: aeitzman <12433791+aeitzman@users.noreply.github.com> Date: Tue, 2 Jul 2024 13:22:01 -0700 Subject: [PATCH 843/966] feat: adds support for X509 workload credential type (#1541) * feat: adds support for X509 workload credential type * fix: PR comments * Apply suggestions from code review Co-authored-by: Leo <39062083+lsirac@users.noreply.github.com> * fix: responding to PR comments * Apply suggestions from code review Co-authored-by: Carl Lundin <108372512+clundin25@users.noreply.github.com> * renaming functions, adding comments, and removing auth_request temp var * Apply suggestions from code review Co-authored-by: Carl Lundin <108372512+clundin25@users.noreply.github.com> * chore: Update test credentials. * linting --------- Co-authored-by: Leo <39062083+lsirac@users.noreply.github.com> Co-authored-by: Carl Lundin <108372512+clundin25@users.noreply.github.com> Co-authored-by: Carl Lundin --- .../google/auth/external_account.py | 34 ++++ .../google-auth/google/auth/identity_pool.py | 135 +++++++++++---- .../google/auth/transport/_mtls_helper.py | 75 ++++---- .../google-auth/system_tests/secrets.tar.enc | Bin 10324 -> 10324 bytes .../tests/test_external_account.py | 161 +++++++++++++++++- .../google-auth/tests/test_identity_pool.py | 129 +++++++++++++- 6 files changed, 463 insertions(+), 71 deletions(-) diff --git a/packages/google-auth/google/auth/external_account.py b/packages/google-auth/google/auth/external_account.py index 3943de2a3427..df0511f255e1 100644 --- a/packages/google-auth/google/auth/external_account.py +++ b/packages/google-auth/google/auth/external_account.py @@ -31,6 +31,7 @@ import copy from dataclasses import dataclass import datetime +import functools import io import json import re @@ -394,6 +395,12 @@ def get_project_id(self, request): def refresh(self, request): scopes = self._scopes if self._scopes is not None else self._default_scopes + # Inject client certificate into request. + if self._mtls_required(): + request = functools.partial( + request, cert=self._get_mtls_cert_and_key_paths() + ) + if self._should_initialize_impersonated_credentials(): self._impersonated_credentials = self._initialize_impersonated_credentials() @@ -523,6 +530,33 @@ def _create_default_metrics_options(self): return metrics_options + def _mtls_required(self): + """Returns a boolean representing whether the current credential is configured + for mTLS and should add a certificate to the outgoing calls to the sts and service + account impersonation endpoint. + + Returns: + bool: True if the credential is configured for mTLS, False if it is not. + """ + return False + + def _get_mtls_cert_and_key_paths(self): + """Gets the file locations for a certificate and private key file + to be used for configuring mTLS for the sts and service account + impersonation calls. Currently only expected to return a value when using + X509 workload identity federation. + + Returns: + Tuple[str, str]: The cert and key file locations as strings in a tuple. + + Raises: + NotImplementedError: When the current credential is not configured for + mTLS. + """ + raise NotImplementedError( + "_get_mtls_cert_and_key_location must be implemented." + ) + @classmethod def from_info(cls, info, **kwargs): """Creates a Credentials instance from parsed external account info. diff --git a/packages/google-auth/google/auth/identity_pool.py b/packages/google-auth/google/auth/identity_pool.py index 1c97885a4ab3..47f9a55715ce 100644 --- a/packages/google-auth/google/auth/identity_pool.py +++ b/packages/google-auth/google/auth/identity_pool.py @@ -48,6 +48,7 @@ from google.auth import _helpers from google.auth import exceptions from google.auth import external_account +from google.auth.transport import _mtls_helper class SubjectTokenSupplier(metaclass=abc.ABCMeta): @@ -141,6 +142,14 @@ def get_subject_token(self, context, request): ) +class _X509Supplier(SubjectTokenSupplier): + """Internal supplier for X509 workload credentials. This class is used internally and always returns an empty string as the subject token.""" + + @_helpers.copy_docstring(SubjectTokenSupplier) + def get_subject_token(self, context, request): + return "" + + def _parse_token_data(token_content, format_type="text", subject_token_field_name=None): if format_type == "text": token = token_content.content @@ -247,6 +256,7 @@ def __init__( self._subject_token_supplier = subject_token_supplier self._credential_source_file = None self._credential_source_url = None + self._credential_source_certificate = None else: if not isinstance(credential_source, Mapping): self._credential_source_executable = None @@ -255,45 +265,22 @@ def __init__( ) self._credential_source_file = credential_source.get("file") self._credential_source_url = credential_source.get("url") - self._credential_source_headers = credential_source.get("headers") - credential_source_format = credential_source.get("format", {}) - # Get credential_source format type. When not provided, this - # defaults to text. - self._credential_source_format_type = ( - credential_source_format.get("type") or "text" - ) + self._credential_source_certificate = credential_source.get("certificate") + # environment_id is only supported in AWS or dedicated future external # account credentials. if "environment_id" in credential_source: raise exceptions.MalformedError( "Invalid Identity Pool credential_source field 'environment_id'" ) - if self._credential_source_format_type not in ["text", "json"]: - raise exceptions.MalformedError( - "Invalid credential_source format '{}'".format( - self._credential_source_format_type - ) - ) - # For JSON types, get the required subject_token field name. - if self._credential_source_format_type == "json": - self._credential_source_field_name = credential_source_format.get( - "subject_token_field_name" - ) - if self._credential_source_field_name is None: - raise exceptions.MalformedError( - "Missing subject_token_field_name for JSON credential_source format" - ) - else: - self._credential_source_field_name = None - if self._credential_source_file and self._credential_source_url: - raise exceptions.MalformedError( - "Ambiguous credential_source. 'file' is mutually exclusive with 'url'." - ) - if not self._credential_source_file and not self._credential_source_url: - raise exceptions.MalformedError( - "Missing credential_source. A 'file' or 'url' must be provided." - ) + # check that only one of file, url, or certificate are provided. + self._validate_single_source() + + if self._credential_source_certificate: + self._validate_certificate_config() + else: + self._validate_file_or_url_config(credential_source) if self._credential_source_file: self._subject_token_supplier = _FileSupplier( @@ -301,13 +288,15 @@ def __init__( self._credential_source_format_type, self._credential_source_field_name, ) - else: + elif self._credential_source_url: self._subject_token_supplier = _UrlSupplier( self._credential_source_url, self._credential_source_format_type, self._credential_source_field_name, self._credential_source_headers, ) + else: # self._credential_source_certificate + self._subject_token_supplier = _X509Supplier() @_helpers.copy_docstring(external_account.Credentials) def retrieve_subject_token(self, request): @@ -315,16 +304,31 @@ def retrieve_subject_token(self, request): self._supplier_context, request ) + def _get_mtls_cert_and_key_paths(self): + if self._credential_source_certificate is None: + raise exceptions.RefreshError( + 'The credential is not configured to use mtls requests. The credential should include a "certificate" section in the credential source.' + ) + else: + return _mtls_helper._get_workload_cert_and_key_paths( + self._certificate_config_location + ) + + def _mtls_required(self): + return self._credential_source_certificate is not None + def _create_default_metrics_options(self): metrics_options = super(Credentials, self)._create_default_metrics_options() - # Check that credential source is a dict before checking for file vs url. This check needs to be done + # Check that credential source is a dict before checking for credential type. This check needs to be done # here because the external_account credential constructor needs to pass the metrics options to the # impersonated credential object before the identity_pool credentials are validated. if isinstance(self._credential_source, Mapping): if self._credential_source.get("file"): metrics_options["source"] = "file" - else: + elif self._credential_source.get("url"): metrics_options["source"] = "url" + else: + metrics_options["source"] = "x509" else: metrics_options["source"] = "programmatic" return metrics_options @@ -339,6 +343,67 @@ def _constructor_args(self): args.update({"subject_token_supplier": self._subject_token_supplier}) return args + def _validate_certificate_config(self): + self._certificate_config_location = self._credential_source_certificate.get( + "certificate_config_location" + ) + use_default = self._credential_source_certificate.get( + "use_default_certificate_config" + ) + if self._certificate_config_location and use_default: + raise exceptions.MalformedError( + "Invalid certificate configuration, certificate_config_location cannot be specified when use_default_certificate_config = true." + ) + if not self._certificate_config_location and not use_default: + raise exceptions.MalformedError( + "Invalid certificate configuration, use_default_certificate_config should be true if no certificate_config_location is provided." + ) + + def _validate_file_or_url_config(self, credential_source): + self._credential_source_headers = credential_source.get("headers") + credential_source_format = credential_source.get("format", {}) + # Get credential_source format type. When not provided, this + # defaults to text. + self._credential_source_format_type = ( + credential_source_format.get("type") or "text" + ) + if self._credential_source_format_type not in ["text", "json"]: + raise exceptions.MalformedError( + "Invalid credential_source format '{}'".format( + self._credential_source_format_type + ) + ) + # For JSON types, get the required subject_token field name. + if self._credential_source_format_type == "json": + self._credential_source_field_name = credential_source_format.get( + "subject_token_field_name" + ) + if self._credential_source_field_name is None: + raise exceptions.MalformedError( + "Missing subject_token_field_name for JSON credential_source format" + ) + else: + self._credential_source_field_name = None + + def _validate_single_source(self): + credential_sources = [ + self._credential_source_file, + self._credential_source_url, + self._credential_source_certificate, + ] + valid_credential_sources = list( + filter(lambda source: source is not None, credential_sources) + ) + + if len(valid_credential_sources) > 1: + raise exceptions.MalformedError( + "Ambiguous credential_source. 'file', 'url', and 'certificate' are mutually exclusive.." + ) + if len(valid_credential_sources) != 1: + raise exceptions.MalformedError( + "Missing credential_source. A 'file', 'url', or 'certificate' must be provided." + ) + @classmethod def from_info(cls, info, **kwargs): """Creates an Identity Pool Credentials instance from parsed external account info. diff --git a/packages/google-auth/google/auth/transport/_mtls_helper.py b/packages/google-auth/google/auth/transport/_mtls_helper.py index e95b953a10ec..6299e2bdeaaa 100644 --- a/packages/google-auth/google/auth/transport/_mtls_helper.py +++ b/packages/google-auth/google/auth/transport/_mtls_helper.py @@ -105,9 +105,50 @@ def _get_workload_cert_and_key(certificate_config_path=None): google.auth.exceptions.ClientCertError: if problems occurs when retrieving the certificate or key information. """ - absolute_path = _get_cert_config_path(certificate_config_path) + + cert_path, key_path = _get_workload_cert_and_key_paths(certificate_config_path) + + if cert_path is None and key_path is None: + return None, None + + return _read_cert_and_key_files(cert_path, key_path) + + +def _get_cert_config_path(certificate_config_path=None): + """Get the certificate configuration path based on the following order: + + 1: Explicit override, if set + 2: Environment variable, if set + 3: Well-known location + + Returns "None" if the selected config file does not exist. + + Args: + certificate_config_path (string): The certificate config path. If provided, the well known + location and environment variable will be ignored. + + Returns: + The absolute path of the certificate config file, and None if the file does not exist. + """ + + if certificate_config_path is None: + env_path = environ.get(_CERTIFICATE_CONFIGURATION_ENV, None) + if env_path is not None and env_path != "": + certificate_config_path = env_path + else: + certificate_config_path = _CERTIFICATE_CONFIGURATION_DEFAULT_PATH + + certificate_config_path = path.expanduser(certificate_config_path) + if not path.exists(certificate_config_path): + return None + return certificate_config_path + + +def _get_workload_cert_and_key_paths(config_path): + absolute_path = _get_cert_config_path(config_path) if absolute_path is None: return None, None + data = _load_json_file(absolute_path) if "cert_configs" not in data: @@ -142,37 +183,7 @@ def _get_workload_cert_and_key(certificate_config_path=None): ) key_path = workload["key_path"] - return _read_cert_and_key_files(cert_path, key_path) - - -def _get_cert_config_path(certificate_config_path=None): - """Gets the certificate configuration full path using the following order of precedence: - - 1: Explicit override, if set - 2: Environment variable, if set - 3: Well-known location - - Returns "None" if the selected config file does not exist. - - Args: - certificate_config_path (string): The certificate config path. If provided, the well known - location and environment variable will be ignored. - - Returns: - The absolute path of the certificate config file, and None if the file does not exist. - """ - - if certificate_config_path is None: - env_path = environ.get(_CERTIFICATE_CONFIGURATION_ENV, None) - if env_path is not None and env_path != "": - certificate_config_path = env_path - else: - certificate_config_path = _CERTIFICATE_CONFIGURATION_DEFAULT_PATH - - certificate_config_path = path.expanduser(certificate_config_path) - if not path.exists(certificate_config_path): - return None - return certificate_config_path + return cert_path, key_path def _read_cert_and_key_files(cert_path, key_path): diff --git a/packages/google-auth/system_tests/secrets.tar.enc b/packages/google-auth/system_tests/secrets.tar.enc index 254396d3374ff9908e67d7e32443b212482d6bd0..94db780f16017777e75274af736892801f45a760 100644 GIT binary patch literal 10324 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