diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index 2ee9e0d..0000000 --- a/.coveragerc +++ /dev/null @@ -1,7 +0,0 @@ -[run] -branch = True -source = keystoneauth1 -omit = keystoneauth1/tests/* - -[report] -ignore_errors = True diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 5395e5c..0000000 --- a/.gitignore +++ /dev/null @@ -1,25 +0,0 @@ -.coverage -.testrepository -subunit.log -.venv -*,cover -cover -*.pyc -.idea -*.sw? -*.egg -*~ -.tox -AUTHORS -ChangeLog -build -dist -keystoneauth1.egg-info -doc/source/api -# Development environment files -.project -.pydevproject -# Temporary files created during test, but not removed -examples/pki/certs/tmp* -# Files created by releasenotes build -releasenotes/build diff --git a/.gitreview b/.gitreview deleted file mode 100644 index 96e3cc0..0000000 --- a/.gitreview +++ /dev/null @@ -1,4 +0,0 @@ -[gerrit] -host=review.openstack.org -port=29418 -project=openstack/keystoneauth.git diff --git a/.mailmap b/.mailmap deleted file mode 100644 index c804401..0000000 --- a/.mailmap +++ /dev/null @@ -1,6 +0,0 @@ -# Format is: -# -# - - - diff --git a/.testr.conf b/.testr.conf deleted file mode 100644 index 6595c1a..0000000 --- a/.testr.conf +++ /dev/null @@ -1,4 +0,0 @@ -[DEFAULT] -test_command=${PYTHON:-python} -m subunit.run discover -t ./ ${OS_TEST_PATH:-./keystoneauth1/tests/unit} $LISTOPT $IDOPTION -test_id_option=--load-list $IDFILE -test_list_option=--list diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst deleted file mode 100644 index 798f7eb..0000000 --- a/CONTRIBUTING.rst +++ /dev/null @@ -1,18 +0,0 @@ -If you would like to contribute to the development of OpenStack, -you must follow the steps documented at: - - https://docs.openstack.org/infra/manual/developers.html - -If you already have a good understanding of how the system works -and your OpenStack accounts are set up, you can skip to the -development workflow section of this documentation to learn how -changes to OpenStack should be submitted for review via the -Gerrit tool: - - https://docs.openstack.org/infra/manual/developers.html#development-workflow - -Pull requests submitted through GitHub will be ignored. - -Bugs should be filed on Launchpad, not GitHub: - - https://bugs.launchpad.net/keystoneauth diff --git a/HACKING.rst b/HACKING.rst deleted file mode 100644 index e56cfff..0000000 --- a/HACKING.rst +++ /dev/null @@ -1,24 +0,0 @@ -Keystone Style Commandments -=========================== - -- Step 1: Read the OpenStack Style Commandments - https://docs.openstack.org/hacking/latest/ -- Step 2: Read on - -Exceptions ----------- - -When dealing with exceptions from underlying libraries, translate those -exceptions to an instance or subclass of ClientException. - -======= -Testing -======= - -keystoneauth uses testtools and testr for its unittest suite -and its test runner. Basic workflow around our use of tox and testr can -be found at https://wiki.openstack.org/testr. If you'd like to learn more -in depth: - - https://testtools.readthedocs.org/ - https://testrepository.readthedocs.org/ diff --git a/LICENSE b/LICENSE deleted file mode 100644 index af613b1..0000000 --- a/LICENSE +++ /dev/null @@ -1,209 +0,0 @@ -Copyright (c) 2009 Jacob Kaplan-Moss - initial codebase (< v2.1) -Copyright (c) 2011 Rackspace - OpenStack extensions (>= v2.1) -Copyright (c) 2011 Nebula, Inc - Keystone refactor (>= v2.7) -All rights reserved. - - - 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. - ---- License for keystoneauth versions prior to 2.1 --- - -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. Neither the name of this project nor the names of its contributors may - be used to endorse or promote products derived from this software without - specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README b/README new file mode 100644 index 0000000..8fcd2b2 --- /dev/null +++ b/README @@ -0,0 +1,14 @@ +This project is no longer maintained. + +The contents of this repository are still available in the Git +source code management system. To see the contents of this +repository before it reached its end of life, please check out the +previous commit with "git checkout HEAD^1". + +For ongoing work on maintaining OpenStack packages in the Debian +distribution, please see the Debian OpenStack packaging team at +https://wiki.debian.org/OpenStack/. + +For any further questions, please email +openstack-dev@lists.openstack.org or join #openstack-dev on +Freenode. diff --git a/README.rst b/README.rst deleted file mode 100644 index 0bb1d4a..0000000 --- a/README.rst +++ /dev/null @@ -1,35 +0,0 @@ -======================== -Team and repository tags -======================== - -.. image:: https://governance.openstack.org/badges/keystoneauth.svg - :target: https://governance.openstack.org/reference/tags/index.html - -.. Change things from this point on - -============ -keystoneauth -============ - -.. image:: https://img.shields.io/pypi/v/keystoneauth1.svg - :target: https://pypi.python.org/pypi/keystoneauth1/ - :alt: Latest Version - -.. image:: https://img.shields.io/pypi/dm/keystoneauth1.svg - :target: https://pypi.python.org/pypi/keystoneauth1/ - :alt: Downloads - -This package contains tools for authenticating to an OpenStack-based cloud. -These tools include: - -* Authentication plugins (password, token, and federation based) -* Discovery mechanisms to determine API version support -* A session that is used to maintain client settings across requests (based on - the requests Python library) - -Further information: - -* Free software: Apache license -* Documentation: https://docs.openstack.org/keystoneauth/latest/ -* Source: https://git.openstack.org/cgit/openstack/keystoneauth -* Bugs: https://bugs.launchpad.net/keystoneauth diff --git a/bindep.txt b/bindep.txt deleted file mode 100644 index 2a783e6..0000000 --- a/bindep.txt +++ /dev/null @@ -1,8 +0,0 @@ -# This is a cross-platform list tracking distribution packages needed for install and tests; -# see https://docs.openstack.org/infra/bindep/ for additional information. - -build-essential [platform:dpkg test] -python-dev [platform:dpkg test] -python-devel [platform:rpm test] -libkrb5-dev [platform:dpkg test] -krb5-devel [platform:rpm test] diff --git a/doc/.gitignore b/doc/.gitignore deleted file mode 100644 index 567609b..0000000 --- a/doc/.gitignore +++ /dev/null @@ -1 +0,0 @@ -build/ diff --git a/doc/Makefile b/doc/Makefile deleted file mode 100644 index 31ab3ab..0000000 --- a/doc/Makefile +++ /dev/null @@ -1,90 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -SPHINXSOURCE = source -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) $(SPHINXSOURCE) - -.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest - -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 " 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 " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - -clean: - -rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -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." - -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/keystoneauth.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/keystoneauth.qhc" - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ - "run these through (pdf)latex." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -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." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." diff --git a/doc/ext/__init__.py b/doc/ext/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/doc/ext/list_plugins.py b/doc/ext/list_plugins.py deleted file mode 100644 index 9626880..0000000 --- a/doc/ext/list_plugins.py +++ /dev/null @@ -1,93 +0,0 @@ -# Licensed under the Apache License, Version 2.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 inspect - -from docutils import nodes -from docutils.parsers import rst -from docutils.parsers.rst import directives -from docutils.statemachine import ViewList -from sphinx.util.nodes import nested_parse_with_titles - -from stevedore import extension - - -class ListAuthPluginsDirective(rst.Directive): - """Present a simple list of the plugins in a namespace.""" - - option_spec = { - 'class': directives.class_option, - 'overline-style': directives.single_char_or_unicode, - 'underline-style': directives.single_char_or_unicode, - } - - has_content = True - - @property - def app(self): - return self.state.document.settings.env.app - - def report_load_failure(mgr, ep, err): - self.app.warn(u'Failed to load %s: %s' % (ep.module_name, err)) - - def display_plugin(self, ext): - overline_style = self.options.get('overline-style', '') - underline_style = self.options.get('underline-style', '=') - - if overline_style: - yield overline_style * len(ext.name) - - yield ext.name - - if underline_style: - yield underline_style * len(ext.name) - - yield "\n" - - doc = inspect.getdoc(ext.obj) - if doc: - yield doc - yield "\n" - yield "------" - - yield "\n" - - for opt in ext.obj.get_options(): - yield ":%s: %s" % (opt.name, opt.help) - - yield "\n" - - def run(self): - mgr = extension.ExtensionManager( - 'keystoneauth1.plugin', - on_load_failure_callback=self.report_load_failure, - invoke_on_load=True, - ) - - result = ViewList() - - for name in sorted(mgr.names()): - for line in self.display_plugin(mgr[name]): - for l in line.splitlines(): - result.append(l, mgr[name].entry_point.module_name) - - # Parse what we have into a new section. - node = nodes.section() - node.document = self.state.document - nested_parse_with_titles(self.state, result, node) - - return node.children - - -def setup(app): - app.info('loading keystoneauth1 plugins') - app.add_directive('list-auth-plugins', ListAuthPluginsDirective) diff --git a/doc/source/authentication-plugins.rst b/doc/source/authentication-plugins.rst deleted file mode 100644 index cba778f..0000000 --- a/doc/source/authentication-plugins.rst +++ /dev/null @@ -1,303 +0,0 @@ -====================== -Authentication Plugins -====================== - -Introduction -============ - -Authentication plugins provide a generic means by which to extend the -authentication mechanisms known to OpenStack clients. - -In the vast majority of cases the authentication plugins used will be those -written for use with the OpenStack Identity Service (Keystone), however this is -not the only possible case, and the mechanisms by which authentication plugins -are used and implemented should be generic enough to cover completely -customized authentication solutions. - -The subset of authentication plugins intended for use with an OpenStack -Identity server (such as Keystone) are called Identity Plugins. - - -Available Plugins -================= - -Keystoneauth ships with a number of plugins and particularly Identity -Plugins. - -V2 Identity Plugins -------------------- - -Standard V2 identity plugins are defined in the module: -:py:mod:`keystoneauth1.identity.v2` - -They include: - -- :py:class:`~keystoneauth1.identity.v2.Password`: Authenticate against - a V2 identity service using a username and password. -- :py:class:`~keystoneauth1.identity.v2.Token`: Authenticate against a - V2 identity service using an existing token. - -V2 identity plugins must use an `auth_url` that points to the root of a V2 -identity server URL, i.e.: ``http://hostname:5000/v2.0``. - -V3 Identity Plugins -------------------- - -Standard V3 identity plugins are defined in the module -:py:mod:`keystoneauth1.identity.v3`. - -V3 Identity plugins are slightly different from their V2 counterparts as a V3 -authentication request can contain multiple authentication methods. To handle -this V3 defines a number of different -:py:class:`~keystoneauth1.identity.v3.AuthMethod` classes: - -- :py:class:`~keystoneauth1.identity.v3.PasswordMethod`: Authenticate - against a V3 identity service using a username and password. -- :py:class:`~keystoneauth1.identity.v3.TokenMethod`: Authenticate against - a V3 identity service using an existing token. -- :py:class:`~keystoneauth1.identity.v3.TOTPMethod`: Authenticate against - a V3 identity service using Time-Based One-Time Password (TOTP). -- :py:class:`~keystoneauth1.identity.v3.TokenlessAuth`: Authenticate against - a V3 identity service using tokenless authentication. -- :py:class:`~keystoneauth1.extras.kerberos.KerberosMethod`: Authenticate - against a V3 identity service using Kerberos. - -The :py:class:`~keystoneauth1.identity.v3.AuthMethod` objects are then -passed to the :py:class:`~keystoneauth1.identity.v3.Auth` plugin:: - - >>> from keystoneauth1 import session - >>> from keystoneauth1.identity import v3 - >>> password = v3.PasswordMethod(username='user', - ... password='password', - ... user_domain_name='default') - >>> auth = v3.Auth(auth_url='http://my.keystone.com:5000/v3', - ... auth_methods=[password], - ... project_id='projectid') - >>> sess = session.Session(auth=auth) - -As in the majority of cases you will only want to use one -:py:class:`~keystoneauth1.identity.v3.AuthMethod` there are also helper -authentication plugins for the various -:py:class:`~keystoneauth1.identity.v3.AuthMethod` which can be used more -like the V2 plugins: - -- :py:class:`~keystoneauth1.identity.v3.Password`: Authenticate using - only a :py:class:`~keystoneauth1.identity.v3.PasswordMethod`. -- :py:class:`~keystoneauth1.identity.v3.Token`: Authenticate using only a - :py:class:`~keystoneauth1.identity.v3.TokenMethod`. -- :py:class:`~keystoneauth1.identity.v3.TOTP`: Authenticate using - only a :py:class:`~keystoneauth1.identity.v3.TOTPMethod`. -- :py:class:`~keystoneauth1.extras.kerberos.Kerberos`: Authenticate using - only a :py:class:`~keystoneauth1.extras.kerberos.KerberosMethod`. - -:: - - >>> auth = v3.Password(auth_url='http://my.keystone.com:5000/v3', - ... username='username', - ... password='password', - ... project_id='projectid', - ... user_domain_name='default') - >>> sess = session.Session(auth=auth) - -This will have exactly the same effect as using the single -:py:class:`~keystoneauth1.identity.v3.PasswordMethod` above. - -V3 identity plugins must use an `auth_url` that points to the root of a V3 -identity server URL, i.e.: ``http://hostname:5000/v3``. - -Federation -========== - -The following V3 plugins are provided to support federation: - -- :py:class:`~keystoneauth1.extras.kerberos.MappedKerberos`: Federated (mapped) - Kerberos. -- :py:class:`~keystoneauth1.extras._saml2.v3.Password`: SAML2 password - authentication. -- :py:class:`~keystoneauth1.identity.v3.Keystone2Keystone`: Keystone to - Keystone Federation. -- :py:class:`~keystoneauth1.identity.v3:OpenIDConnectAccessToken`: Plugin to - reuse an existing OpenID Connect access token. -- :py:class:`~keystoneauth1.identity.v3:OpenIDConnectAuthorizationCode`: OpenID - Connect Authorization Code grant type. -- :py:class:`~keystoneauth1.identity.v3:OpenIDConnectClientCredentials`: OpenID - Connect Client Credentials grant type. -- :py:class:`~keystoneauth1.identity.v3:OpenIDConnectPassword`: OpenID Connect - Resource Owner Password Credentials grant type. - - -Version Independent Identity Plugins ------------------------------------- - -Standard version independent identity plugins are defined in the module -:py:mod:`keystoneauth1.identity.generic`. - -For the cases of plugins that exist under both the identity V2 and V3 APIs -there is an abstraction to allow the plugin to determine which of the V2 and V3 -APIs are supported by the server and use the most appropriate API. - -These plugins are: - -- :py:class:`~keystoneauth1.identity.generic.Password`: Authenticate - using a user/password against either v2 or v3 API. -- :py:class:`~keystoneauth1.identity.generic.Token`: Authenticate using - an existing token against either v2 or v3 API. - -These plugins work by first querying the identity server to determine available -versions and so the `auth_url` used with the plugins should point to the base -URL of the identity server to use. If the `auth_url` points to either a V2 or -V3 endpoint it will restrict the plugin to only working with that version of -the API. - -Simple Plugins --------------- - -In addition to the Identity plugins a simple plugin that will always use the -same provided token and endpoint is available. This is useful in situations -where you have an token or in testing when you specifically know the endpoint -you want to communicate with. - -It can be found at :py:class:`keystoneauth1.token_endpoint.Token`. - - -V3 OAuth 1.0a Plugins ---------------------- - -There also exists a plugin for OAuth 1.0a authentication. We provide a helper -authentication plugin at: -:py:class:`~keystoneauth1.extras.oauth1.V3OAuth1`. -The plugin requires the OAuth consumer's key and secret, as well as the OAuth -access token's key and secret. For example:: - - >>> from keystoneauth1.extras import oauth1 - >>> from keystoneauth1 import session - >>> a = oauth1.V3OAuth1('http://my.keystone.com:5000/v3', - ... consumer_key=consumer_id, - ... consumer_secret=consumer_secret, - ... access_key=access_token_key, - ... access_secret=access_token_secret) - >>> s = session.Session(auth=a) - - -Tokenless Auth -============== - -A plugin for tokenless authentication also exists. It provides a means to -authorize client operations within the Identity server by using an X.509 -TLS client certificate without having to issue a token. We provide a -tokenless authentication plugin at: - -- :class:`~keystoneauth1.identity.v3.TokenlessAuth` - -It is mostly used by service clients for token validation and here is -an example of how this plugin would be used in practice:: - - >>> from keystoneauth1 import session - >>> from keystoneauth1.identity import v3 - >>> auth = v3.TokenlessAuth(auth_url='https://keystone:5000/v3', - ... domain_name='my_service_domain') - >>> sess = session.Session( - ... auth=auth, - ... cert=('/opt/service_client.crt', - ... '/opt/service_client.key'), - ... verify='/opt/ca.crt') - - -Loading Plugins by Name -======================= - -In auth_token middleware and for some service to service communication it is -possible to specify a plugin to load via name. The authentication options that -are available are then specific to the plugin that you specified. Currently the -authentication plugins that are available in `keystoneauth` are: - -- password: :py:class:`keystoneauth1.identity.generic.Password` -- token: :py:class:`keystoneauth1.identity.generic.Token` -- v2password: :py:class:`keystoneauth1.identity.v2.Password` -- v2token: :py:class:`keystoneauth1.identity.v2.Token` -- v3password: :py:class:`keystoneauth1.identity.v3.Password` -- v3token: :py:class:`keystoneauth1.identity.v3.Token` -- v3fedkerb: :py:class:`keystoneauth1.extras.kerberos.MappedKerberos` -- v3kerberos: :py:class:`keystoneauth1.extras.kerberos.Kerberos` -- v3oauth1: :py:class:`keystoneauth1.extras.oauth1.v3.OAuth1` -- v3oidcaccesstoken: :py:class:`keystoneauth1.identity.v3:OpenIDConnectAccessToken` -- v3oidcauthcode: :py:class:`keystoneauth1.identity.v3:OpenIDConnectAuthorizationCode` -- v3oidcclientcredentials: :py:class:`keystoneauth1.identity.v3:OpenIDConnectClientCredentials` -- v3oidcpassword: :py:class:`keystoneauth1.identity.v3:OpenIDConnectPassword` -- v3samlpassword: :py:class:`keystoneauth1.extras._saml2.v3.Password` -- v3tokenlessauth: :py:class:`keystoneauth1.identity.v3.TokenlessAuth` -- v3totp: :py:class:`keystoneauth1.identity.v3.TOTP` - - -Creating Authentication Plugins -=============================== - -Creating an Identity Plugin ---------------------------- - -If you have implemented a new authentication mechanism into the Identity -service then you will be able to reuse a lot of the infrastructure available -for the existing Identity mechanisms. As the V2 identity API is essentially -frozen, it is expected that new plugins are for the V3 API. - -To implement a new V3 plugin that can be combined with others you should -implement the base :py:class:`keystoneauth1.identity.v3.AuthMethod` class -and implement the -:py:meth:`~keystoneauth1.identity.v3.AuthMethod.get_auth_data` function. -If your Plugin cannot be used in conjunction with existing -:py:class:`keystoneauth1.identity.v3.AuthMethod` then you should just -override :py:class:`keystoneauth1.identity.v3.Auth` directly. - -The new :py:class:`~keystoneauth1.identity.v3.AuthMethod` should take all -the required parameters via -:py:meth:`~keystoneauth1.identity.v3.AuthMethod.__init__` and return from -:py:meth:`~keystoneauth1.identity.v3.AuthMethod.get_auth_data` a tuple -with the unique identifier of this plugin (e.g. *password*) and a dictionary -containing the payload of values to send to the authentication server. The -session, calling auth object and request headers are also passed to this -function so that the plugin may use or manipulate them. - -You should also provide a class that inherits from -:py:class:`keystoneauth1.identity.v3.Auth` with an instance of your new -:py:class:`~keystoneauth1.identity.v3.AuthMethod` as the `auth_methods` -parameter to :py:class:`keystoneauth1.identity.v3.Auth`. - -By convention (and like above) these are named `PluginType` and -`PluginTypeMethod` (for example -:py:class:`~keystoneauth1.identity.v3.Password` and -:py:class:`~keystoneauth1.identity.v3.PasswordMethod`). - - -Creating a Custom Plugin ------------------------- - -To implement an entirely new plugin you should implement the base class -:py:class:`keystoneauth1.plugin.BaseAuthPlugin` and provide the -:py:meth:`~keystoneauth1.plugin.BaseAuthPlugin.get_endpoint`, -:py:meth:`~keystoneauth1.plugin.BaseAuthPlugin.get_token` and -:py:meth:`~keystoneauth1.plugin.BaseAuthPlugin.invalidate` methods. - -:py:meth:`~keystoneauth1.plugin.BaseAuthPlugin.get_token` is called to retrieve -the string token from a plugin. It is intended that a plugin will cache a -received token and so if the token is still valid then it should be re-used -rather than fetching a new one. A session object is provided with which the -plugin can contact it's server. (Note: use `authenticated=False` when making -those requests or it will end up being called recursively). The return value -should be the token as a string. - -:py:meth:`~keystoneauth1.plugin.BaseAuthPlugin.get_endpoint` is called to -determine a base URL for a particular service's requests. The keyword arguments -provided to the function are those that are given by the `endpoint_filter` -variable in :py:meth:`keystoneauth1.session.Session.request`. A session object -is also provided so that the plugin may contact an external source to determine -the endpoint. Again this will be generally be called once per request and so -it is up to the plugin to cache these responses if appropriate. The return -value should be the base URL to communicate with. - -:py:meth:`~keystoneauth1.plugin.BaseAuthPlugin.invalidate` should also be -implemented to clear the current user credentials so that on the next -:py:meth:`~keystoneauth1.plugin.BaseAuthPlugin.get_token` call a new token can -be retrieved. - -The most simple example of a plugin is the -:py:class:`keystoneauth1.token_endpoint.Token` plugin. diff --git a/doc/source/conf.py b/doc/source/conf.py deleted file mode 100644 index 2a41a1a..0000000 --- a/doc/source/conf.py +++ /dev/null @@ -1,237 +0,0 @@ -# -*- coding: utf-8 -*- -# -# keystoneauth1 documentation build configuration file, created by -# sphinx-quickstart on Sun Dec 6 14:19:25 2009. -# -# 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. - -from __future__ import unicode_literals - -import os -import sys - -import pbr.version - - -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), - '..', '..'))) -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), - '..'))) - -# 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. -#sys.path.append(os.path.abspath('.')) - -# -- General configuration ---------------------------------------------------- - -# 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.todo', - 'sphinx.ext.coverage', - 'sphinx.ext.intersphinx', - 'openstackdocstheme', - 'ext.list_plugins', - ] - -todo_include_todos = True - -# Add any paths that contain templates here, relative to this directory. -# templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = 'keystoneauth1' -copyright = 'OpenStack Contributors' - -# 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. -version_info = pbr.version.VersionInfo('keystoneauth1') -# The short X.Y version. -version = version_info.version_string() -# The full version, including alpha/beta/rc tags. -release = version_info.release_string() - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -#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 documents that shouldn't be included in the build. -#unused_docs = [] - -# List of directories, relative to source directory, that shouldn't be searched -# for source files. -exclude_trees = [] - -# 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 = ['keystoneauth1.'] - -# Grouping the document tree for man pages. -# List of tuples 'sourcefile', 'target', 'title', 'Authors name', 'manual' -#man_pages = [] - -# -- Options for HTML output -------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. Major themes that come with -# Sphinx are currently 'default' and 'sphinxdoc'. -#html_theme_path = ["."] -#html_theme = '_theme' -html_theme = 'openstackdocs' - -# 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. If None, it defaults to -# " v documentation". -#html_title = None - -# 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 (within the static path) to use as 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'] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -html_last_updated_fmt = '%Y-%m-%d %H:%M' - -# 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_use_modindex = 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, 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 = '' - -# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = '' - -# Output file base name for HTML help builder. -htmlhelp_basename = 'keystoneauthdoc' - - -# -- Options for LaTeX output ------------------------------------------------- - -# The paper size ('letter' or 'a4'). -#latex_paper_size = 'letter' - -# The font size ('10pt', '11pt' or '12pt'). -#latex_font_size = '10pt' - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]) -# . -latex_documents = [ - ('index', 'keystoneauth1.tex', - 'keystoneauth1 Documentation', - 'Nebula Inc, based on work by Rackspace and Jacob Kaplan-Moss', - '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 - -# Additional stuff for the LaTeX preamble. -#latex_preamble = '' - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_use_modindex = True - -ksc = 'https://docs.openstack.org/python-keystoneclient/latest/' - -intersphinx_mapping = { - 'python': ('http://docs.python.org/', None), - 'osloconfig': ('https://docs.openstack.org/oslo.config/latest/', None), - 'keystoneclient': (ksc, None), -} - -# -- Options for openstackdocstheme ------------------------------------------- -repository_name = 'openstack/keystoneauth' -bug_project = 'keystoneauth' -bug_tag = 'doc' diff --git a/doc/source/extras.rst b/doc/source/extras.rst deleted file mode 100644 index 49f542d..0000000 --- a/doc/source/extras.rst +++ /dev/null @@ -1,73 +0,0 @@ -====== -Extras -====== - -The extensibility of keystoneauth plugins is purposefully designed to allow a -range of different authentication mechanisms that don't have to reside in the -upstream packages. There are however a number of plugins that upstream supports -that involve additional dependencies that the keystoneauth package cannot -depend upon directly. - -To get around this we utilize setuptools `extras dependencies `_ for additional -plugins. To use a plugin like the kerberos plugin that has additional -dependencies you must install the additional dependencies like:: - - pip install keystoneauth1[kerberos] - -By convention (not a requirement) extra plugins have a module located in the -keystoneauth1.extras module with the same name as the dependency. eg:: - - from keystoneauth1.extras import kerberos - -There is no keystoneauth specific check that the correct dependencies are -installed for accessing a module. You would expect to see standard python -ImportError when the required dependencies are not found. - -Examples -======== - -All extras plugins follow the pattern: - -1. import plugin module -2. instantiate the plugin -3. call get_token method of the plugin passing it a session object - to get a token - -Kerberos --------- - -Get domain-scoped token using -:py:class:`~keystoneauth1.extras.kerberos.Kerberos`:: - - from keystoneauth1.extras import kerberos - from keystoneauth1 import session - - plugin = kerberos.Kerberos('http://example.com:5000/v3') - sess = session.Session(plugin) - token = plugin.get_token(sess) - -Get unscoped federated token:: - - from keystoneauth1.extras import kerberos - from keystoneauth1 import session - - plugin = kerberos.MappedKerberos( - auth_url='http://example.com:5000/v3', protocol='example_protocol', - identity_provider='example_identity_provider') - - sess = session.Session() - token = plugin.get_token(sess) - -Get project scoped federated token:: - - from keystoneauth1.extras import kerberos - from keystoneauth1 import session - - plugin = kerberos.MappedKerberos( - auth_url='http://example.com:5000/v3', protocol='example_protocol', - identity_provider='example_identity_provider', - project_id='example_project_id') - - sess = session.Session() - token = plugin.get_token(sess) - project_id = plugin.get_project_id(sess) diff --git a/doc/source/history.rst b/doc/source/history.rst deleted file mode 100644 index 69ed4fe..0000000 --- a/doc/source/history.rst +++ /dev/null @@ -1 +0,0 @@ -.. include:: ../../ChangeLog diff --git a/doc/source/images/graphs_authComp.svg b/doc/source/images/graphs_authComp.svg deleted file mode 100644 index 6be629c..0000000 --- a/doc/source/images/graphs_authComp.svg +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - -AuthComp - - -AuthComp - -Auth -Component - - - -AuthComp->Reject - - -Reject -Unauthenticated -Requests - - -Service - -OpenStack -Service - - -AuthComp->Service - - -Forward -Authenticated -Requests - - - -Start->AuthComp - - - - - diff --git a/doc/source/images/graphs_authCompDelegate.svg b/doc/source/images/graphs_authCompDelegate.svg deleted file mode 100644 index 4788829..0000000 --- a/doc/source/images/graphs_authCompDelegate.svg +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - -AuthCompDelegate - - -AuthComp - -Auth -Component - - - -AuthComp->Reject - - -Reject Requests -Indicated by the Service - - -Service - -OpenStack -Service - - -AuthComp->Service - - -Forward Requests -with Identiy Status - - -Service->AuthComp - - -Send Response OR -Reject Message - - - -Start->AuthComp - - - - - diff --git a/doc/source/index.rst b/doc/source/index.rst deleted file mode 100644 index 579d7c5..0000000 --- a/doc/source/index.rst +++ /dev/null @@ -1,49 +0,0 @@ -Common Authentication Library for OpenStack Clients -=================================================== - -Keystoneauth provides a standard way to do authentication and service requests -within the OpenStack ecosystem. It is designed for use in conjunction with the -existing OpenStack clients and for simplifying the process of writing new -clients. - -Contents: - -.. toctree:: - :maxdepth: 1 - - using-sessions - authentication-plugins - plugin-options - - extras - migrating - - api/modules - -Release Notes -============= - -.. toctree:: - :maxdepth: 1 - - history - -Contributing -============ - -Code is hosted `on GitHub`_. Submit bugs to the Keystone project on -`Launchpad`_. Submit code to the ``openstack/keystoneauth`` project -using `Gerrit`_. - -.. _on GitHub: https://github.com/openstack/keystoneauth -.. _Launchpad: https://launchpad.net/keystoneauth -.. _Gerrit: https://docs.openstack.org/infra/manual/developers.html#development-workflow - -Run tests with ``tox``. - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/doc/source/migrating.rst b/doc/source/migrating.rst deleted file mode 100644 index a787358..0000000 --- a/doc/source/migrating.rst +++ /dev/null @@ -1,102 +0,0 @@ -============================= -Migrating from keystoneclient -============================= - -When keystoneauth was extracted from keystoneclient the basic usage of the -session, adapter and auth plugins purposefully did not change. If you are using -them in a supported fashion from keystoneclient then the transition should be -fairly simple. - -Authentication Plugins -====================== - -The authentication plugins themselves changed very little however there were -changes to the way plugins are loaded and some of the supporting classes. - -Plugin Loading --------------- - -In keystoneclient auth plugin loading is managed by the class itself. This -method proved useful in allowing the plugin to control the way it was loaded -however it linked the authentication logic with the config and CLI loading. - -In keystoneauth this has been severed and the auth plugin is handled separately -from the mechanism that loads it. - -Authentication plugins still implement the base authentication class -:py:class:`~keystoneauth1.plugin.BaseAuthPlugin`. To make the plugins capable -of being loaded from CLI or CONF file you should implement the base -:py:class:`~keystoneauth1.loading.BaseLoader` class which is loaded when -`--os-auth-type` is used. This class handles the options that are -presented, and then constructs the authentication plugin for use by the -application. - -Largely the options that are returned will be the same as what was used in -keystoneclient however in keystoneclient the options used -:py:class:`oslo_config.cfg.Opt` objects. Due to trying to keep minimal -dependencies there is no direct dependency from keystoneauth on oslo.config and -instead options should be specified as :py:class:`~keystoneauth1.loading.Opt` -objects. - -To ensure distinction between the plugins, the setuptools entrypoints that -plugins register at has been updated to reflect keystoneauth1 and should now -be: keystoneauth1.plugin - -AccessInfo Objects ------------------- - -AccessInfo objects are a representation of the information stored within a -token. In keystoneclient these objects were dictionaries of the token data with -property accessors. In keystoneauth the dictionary interface has been removed -and just the property accessors are available. - -The creation function has also changed. The -:py:meth:`keystoneclient.access.AccessInfo.factory` method has been removed -and replaced with the :py:func:`keystoneauth1.access.create`. - -Step-by-step migration example ------------------------------- - -Add ``keystoneauth1`` to requirements.txt - -In the code do the following change:: - - -from keystoneclient import auth - +from keystoneauth1 import plugin - -consequently:: - - -auth.BaseAuthPlugin - +plugin.BaseAuthPlugin - -To import service catalog:: - - -from keystoneclient import service_catalog - +from keystoneauth1.access import service_catalog - -To get url using service catalog *endpoint_type* parameter was changed to -*interface*:: - - -service_catalog.ServiceCatalogV2(sc).service_catalog.url_for(..., endpoint_type=interface) - +service_catalog.ServiceCatalogV2(sc).service_catalog.url_for(..., interface=interface) - -Obtaining the session:: - - -from keystoneclient import session - +from keystoneauth1 import loading as ks_loading - - -_SESSION = session.Session.load_from_conf_options( - -auth_plugin = auth.load_from_conf_options(conf, NEUTRON_GROUP) - +_SESSION = ks_loading.load_session_from_conf_options( - +auth_plugin = ks_loading.load_auth_from_conf_options(conf, NEUTRON_GROUP) - -Mocking session for test purposes:: - - -@mock.patch('keystoneclient.session.Session') - +@mock.patch('keystoneauth1.session.Session') - -Token fixture imports haven't change much:: - - -from keystoneclient.fixture import V2Token - +from keystoneauth1.fixture import V2Token - diff --git a/doc/source/plugin-options.rst b/doc/source/plugin-options.rst deleted file mode 100644 index 4417f81..0000000 --- a/doc/source/plugin-options.rst +++ /dev/null @@ -1,92 +0,0 @@ -============== -Plugin Options -============== - -Using plugins via config file ------------------------------ - -When using the plugins via config file you define the plugin name as -``auth_type``. The options of the plugin are then specified while replacing -``-`` with ``_`` to be valid in configuration. - -For example to use the password_ plugin in a config file you would specify: - -.. code-block:: ini - - [section] - auth_url = http://keystone.example.com:5000/ - auth_type = password - username = myuser - password = mypassword - project_name = myproject - default_domain_name = mydomain - - -Using plugins via CLI ---------------------- - -When using auth plugins via CLI via ``os-client-config`` or ``shade`` you can -specify parameters via environment configuration by using the pattern ``OS_`` -followed by the uppercase parameter name replacing ``-`` with ``_``. - -For example to use the password_ plugin via environment variable you specify: - -.. code-block:: bash - - export OS_AUTH_TYPE=password - export OS_AUTH_URL=http://keystone.example.com:5000/ - export OS_USERNAME=myuser - export OS_PASSWORD=mypassword - export OS_PROJECT_NAME=myproject - export OS_DEFAULT_DOMAIN_NAME=mydomain - -Specifying operations via CLI parameter will override the environment -parameter. These are specified with the pattern ``--os-`` and the parameter -name. Using the password_ example again: - -.. code-block:: bash - - openstack --os-auth-type password \ - --os-auth-url http://keystone.example.com:5000/ \ - --os-username myuser \ - --os-password mypassword \ - --os-project-name myproject \ - --os-default-domain-name mydomain \ - operation - -Additional loaders ------------------- - -The configuration and CLI loaders are quite commonly used however similar -concepts are found in other situations such as ``os-client-config`` in which -you specify authentication and other cloud parameters in a ``clouds.yaml`` -file. - -Loaders such as these use the same plugin options listed below, but via their -own mechanism. In ``os-client-config`` the password_ plugin looks like: - -.. code-block:: yaml - - clouds: - mycloud: - auth_type: password - auth: - auth_url: http://keystone.example.com:5000/ - auth_type: password - username: myuser - password: mypassword - project_name: myproject - default_domain_name: mydomain - -However different services may implement loaders in their own way and you -should consult their relevant documentation. The same auth options will be -available. - - -Available Plugins ------------------ - -This is a listing of all included plugins and the options that they accept. -Plugins are listed alphabetically and not in any order of priority. - -.. list-auth-plugins:: diff --git a/doc/source/using-sessions.rst b/doc/source/using-sessions.rst deleted file mode 100644 index ffd140f..0000000 --- a/doc/source/using-sessions.rst +++ /dev/null @@ -1,440 +0,0 @@ -============== -Using Sessions -============== - -Introduction -============ - -The :py:class:`keystoneauth1.session.Session` class was introduced into -keystoneauth1 as an attempt to bring a unified interface to the various -OpenStack clients that share common authentication and request parameters -between a variety of services. - -The model for using a Session and auth plugin as well as the general terms used -have been heavily inspired by the `requests `_ -library. However neither the Session class nor any of the authentication -plugins rely directly on those concepts from the requests library so you should -not expect a direct translation. - -Features --------- - -- Common client authentication - - Authentication is handled by one of a variety of authentication plugins and - then this authentication information is shared between all the services that - use the same Session object. - -- Security maintenance - - Security code is maintained in a single place and reused between all - clients such that in the event of problems it can be fixed in a single - location. - -- Standard service and version discovery - - Clients are not expected to have any knowledge of an identity token or any - other form of identification credential. Service, endpoint, major version - discovery and microversion support discovery are handled by the Session and - plugins. Discovery information is automatically cached in memory, so the user - need not worry about excessive use of discovery metadata. - - -Sessions for Users -================== - -The Session object is the contact point to your OpenStack cloud services. It -stores the authentication credentials and connection information required to -communicate with OpenStack such that it can be reused to communicate with many -services. When creating services this Session object is passed to the client -so that it may use this information. - -A Session will authenticate on demand. When a request that requires -authentication passes through the Session the authentication plugin will be -asked for a valid token. If a valid token is available it will be used -otherwise the authentication plugin may attempt to contact the authentication -service and fetch a new one. - -An example using keystoneclient to wrap a Session:: - - >>> from keystoneauth1.identity import v3 - >>> from keystoneauth1 import session - >>> from keystoneclient.v3 import client - - >>> auth = v3.Password(auth_url='https://my.keystone.com:5000/v3', - ... username='myuser', - ... password='mypassword', - ... project_name='proj', - ... user_domain_id='default', - ... project_domain_id='default') - >>> sess = session.Session(auth=auth, - ... verify='/path/to/ca.cert') - >>> ks = client.Client(session=sess) - >>> users = ks.users.list() - -As other OpenStack client libraries adopt this means of operating they will be -created in a similar fashion by passing the Session object to the client's -constructor. - - -Sharing Authentication Plugins ------------------------------- - -A Session can only contain one authentication plugin. However, there is -nothing that specifically binds the authentication plugin to that Session - a -new Session can be created that reuses the existing authentication plugin:: - - >>> new_sess = session.Session(auth=sess.auth, - verify='/path/to/different-cas.cert') - -In this case we cannot know which Session object will be used when the plugin -performs the authentication call so the command must be able to succeed with -either. - -Authentication plugins can also be provided on a per-request basis. This will -be beneficial in a situation where a single Session is juggling multiple -authentication credentials:: - - >>> sess.get('https://my.keystone.com:5000/v3', - auth=my_auth_plugin) - -If an auth plugin is provided via parameter then it will override any auth -plugin on the Session. - -Sessions for Client Developers -============================== - -Sessions are intended to take away much of the hassle of dealing with -authentication data and token formats. Clients should be able to specify filter -parameters for selecting the endpoint and have the parsing of the catalog -managed for them. - -Major Version Discovery and Microversion Support ------------------------------------------------- - -In OpenStack the root URLs of available services are distributed to the user -in an object called the Service Catalog, which is part of the token they -receive. Clients are expected to use the URLs from the Service Catalog rather -than have them provided. The root URL of a given service is referred to as the -`endpoint` of the service. The URL of a specific version of a service is -referred to as a `versioned endpoint`. REST requests for a service are made -against a given `versioned endpoint`. - -The topic of Major API versions and microversions can be confusing. As -`keystoneauth` provides facilities for discovery of versioned endpoints -associated with a Major API Version and for fetching information about -the microversions that versioned endpoint supports, it is important to be aware -of the distinction between the two. - -Conceptually the most important thing to understand is that a Major API Version -describes the URL of a discrete versioned endpoint, while a given versioned -endpoint might have properties that express that it supports a range of -microversions. - -When a user wants to make a REST request against a service, the user expresses -the Major API version and the type of service so that the appropriate versioned -endpoint can be found and used. For example, a user might request -version 2 of the compute service from cloud.example.com and end up with a -versioned endpoint of ``https://compute.example.com/v2``. - -Each service provides a discovery document at the root of each versioned -endpoint that contains information about that versioned endpoint. Each service -also provides a document at the root of the unversioned endpoint that contains -a list of the discovery documents for all of the available versioned endpoints. -By examining these documents, it is possible to find the versioned endpoint -that corresponds with the user's desired Major API version. - -Each of those documents may also indicate that the given versioned endpoint -supports microversions by listing a minimum and maximum microversion that it -understands. As a result of having found the versioned endpoint for the -requested Major API version, the user will also know which microversions, -if any, may be used in requests to that versioned endpoint. - -When a client makes REST requests to the Major API version's endpoint, the -client can, optionally, on a request-by-request basis, include a header -specifying that the individual request use the behavior defined by the given -microversion. If a client does not request a microversion, the service will -behave as if the minimum supported microversion was specified. - -.. note: The changes that each microversion reflects are documented elsewhere - and are not information provided by the discovery process. - -The overall transaction then has three parts: - -* What is the endpoint for a given Major API version of a given service? -* What are the minimum and maximum microversions supported at that endpoint? -* Which one of that range of microversions, if any, does the user want to use - for a given request? - -`keystoneauth` provides facilities for discovering the endpoint for a given -Major API of a given service, as well as reporting the available microversion -ranges that endpoint supports, if any. - -More information is available in the `API-WG Specs`_ on the topics of -`Microversions`_ and `Consuming the Catalog`_. - -Authentication --------------- - -When making a request with a Session object you can simply pass the keyword -parameter ``authenticated`` to indicate whether the argument should contain a -token, by default a token is included if an authentication plugin is available:: - - >>> # In keystone this route is unprotected by default - >>> resp = sess.get('https://my.keystone.com:5000/v3', - authenticated=False) - - -Service Discovery ------------------ - - -In general a client does not need to know the full URL for the server that they -are communicating with, simply that it should send a request to a path -belonging to the correct service. - -This is controlled by the ``endpoint_filter`` parameter to a request which -contains all the information an authentication plugin requires to determine the -correct URL to which to send a request. When using this mode only the path for -the request needs to be specified:: - - >>> resp = session.get('/users', - endpoint_filter={'service_type': 'identity', - 'interface': 'admin', - 'region_name': 'myregion', - 'min_version': '2.0', - 'max_version': '3.4', - 'discover_versions': False}) - -.. note:: The min_version and max_version arguments in this example indicate - acceptable range for finding the endpoint for the given Major API - versions. They are in the endpoint_filter, they are not requesting - the call to ``/users`` be made at a specific microversion. - -`endpoint_filter` accepts a number of arguments with which it can determine an -endpoint url: - -service_type - the type of service. For example ``identity``, ``compute``, ``volume`` or - many other predefined identifiers. - -interface - the network exposure the interface has. Can also be a list, in which case the - first matching interface will be used. Valid values are: - - - ``public``: An endpoint that is available to the wider internet or network. - - ``internal``: An endpoint that is only accessible within the private - network. - - ``admin``: An endpoint to be used for administrative tasks. - -region_name - the name of the region where the endpoint resides. - -version - the minimum version, restricted to a given Major API. For instance, a - `version` of ``2.2`` will match ``2.2`` and ``2.3`` but not ``2.1`` or - ``3.0``. Mutually exclusive with `min_version` and `max_version`. - -min_version - the minimum version of a given API, intended to be used as the lower bound of - a range with `max_version`. See `max_version` for examples. Mutually - exclusive with `version`. - -max_version - the maximum version of a given API, intended to be used as the upper bound of - a range with `min_version`. For example:: - - 'min_version': '2.2', - 'max_version': '3.3' - - will match ``2.2``, ``2.10``, ``3.0``, and ``3.3``, but not ``1.42``, - ``2.1``, or ``3.20``. Mutually exclusive with `version`. - -.. note:: version, min_version and max_version are all used to help determine - the endpoint for a given Major API version of a service. - -discover_versions - whether or not version discovery should be run, even if not strictly - necessary. It is often possible to fulfill an endpoint request purely - from the catalog, meaning the version discovery API is a potentially - wasted additional call. However, it's possible that running discovery - instead of inference is desired. Defaults to ``True``. - -All version arguments (`version`, `min_version` and `max_version`) can -be given as: - -* string: ``'2.0'`` -* int: ``2`` -* float: ``2.0`` -* tuple of ints: ``(2, 0)`` - -`version` and `max_version` can also be given the string ``latest``, which -indicates that the highest available version should be used. - -The endpoint filter is a simple key-value filter and can be provided with any -number of arguments. It is then up to the auth plugin to correctly use the -parameters it understands. - -If you want to further limit your service discovery by allowing experimental -APIs or disallowing deprecated APIs, you can use the ``allow`` parameter:: - - >>> resp = session.get('//volumes', - endpoint_filter={'service_type': 'volume', - 'interface': 'public', - 'version': 1}, - allow={'allow_deprecated': False}) - -The discoverable types of endpoints that `allow` can recognize are: - -- `allow_deprecated`: Allow deprecated version endpoints. - -- `allow_experimental`: Allow experimental version endpoints. - -- `allow_unknown`: Allow endpoints with an unrecognised status. - -The Session object creates a valid request by determining the URL matching the -filters and appending it to the provided path. If multiple URL matches are -found then any one may be chosen. - -While authentication plugins will endeavour to maintain a consistent set of -arguments for an ``endpoint_filter`` the concept of an authentication plugin is -purposefully generic. A specific mechanism may not know how to interpret -certain arguments in which case it may ignore them. For example the -:class:`keystoneauth1.token_endpoint.Token` plugin (which is used when you want -to always use a specific endpoint and token combination) will always return the -same endpoint regardless of the parameters to ``endpoint_filter`` or a custom -OpenStack authentication mechanism may not have the concept of multiple -``interface`` options and choose to ignore that parameter. - -There is some expectation on the user that they understand the limitations of -the authentication system they are using. - -Using Adapters --------------- - -If the developer would prefer not to provide `endpoint_filter` with every API -call, a :class:`keystoneauth1.adapter.Adapter` can be created. The `Adapter` -constructor takes the same arguments as `endpoint_filter`, as well as a -`Session`. An `Adapter` behaves much like a `Session`, with the same REST -methods, but is "mounted" on the endpoint that would be found by -`endpoint_filter`. - -.. code-block:: python - - adapter = keystoneauth1.adapter.Adapter( - session=session, - service_type='volume', - interface='public', - version=1) - response = adapter.get('/volumes') - -As with ``endpoint_filter`` on a Session, the ``version``, ``min_version`` -and ``max_version`` parameters exist to help determine the appropriate -endpoint for a Major API of a service. - -Endpoint Metadata ------------------ - -Both :class:`keystoneauth1.adapter.Adapter` and -:class:`keystoneauth1.session.Session` have a method for getting metadata about -the endpoint found for a given service: ``get_endpoint_data``. - -On the :class:`keystoneauth1.session.Session` it takes the same arguments as -`endpoint_filter`. - -On the :class:`keystoneauth1.adapter.Adapter` it does not take arguments, as -it returns the information for the Endpoint the Adapter is mounted on. - -``get_endpoint_data`` returns an :class:`keystoneauth1.discovery.EndpointData` -object. This object can be used to find information about the Endpoint, -including which major `api_version` was found, or which `interface` in case -of ranges, lists of input values or ``latest`` version. - -It can also be used to determine the `min_microversion` and `max_microversion` -supported by the API. If an API does not support microversions, the values for -both will be ``None``. It will also contain values for `next_min_version` and -`not_before` if they exist for the endpoint, or ``None`` if they do not. The -:class:`keystoneauth1.discovery.EndpointData` object will always contain -microversion related attributes regardless of whether the REST document does -or not. - -``get_endpoint_data`` makes use of the same cache as the rest of the discovery -process, so calling it should incur no undue expense. By default it will make -at least one version discovery call so that it can fetch microversion metadata. -If the user knows a service does not support microversions and is merely -curious as to which major version was discovered, `discover_versions` can be -set to `False` to prevent fetching microversion metadata. - -Requesting a Microversion -------------------------- - -A user who wants to specify a microversion for a given request can pass it to -the ``microversion`` parameter of the `request` method on the -:class:`keystoneauth1.session.Session` object, or the -:class:`keystoneauth1.adapter.Adapter` object. This will cause `keystoneauth` -to pass the appropriate header to the service informing the service of the -microversion the user wants. - -.. code-block:: python - - resp = session.get('/volumes', - microversion='3.15', - endpoint_filter={'service_type': 'volume', - 'interface': 'public', - 'min_version': '3', - 'max_version': 'latest'}) - -If the user is using a :class:`keystoneauth1.adapter.Adapter`, the -`service_type`, which is a part of the data sent in the microversion header, -will be taken from the Adapter's `service_type`. - -.. code-block:: python - - adapter = keystoneauth1.adapter.Adapter( - session=session, - service_type='compute', - interface='public', - min_version='2.1') - response = adapter.get('/servers', microversion='2.38') - -The user can also provide a ``default_microversion`` parameter to the Adapter -constructor which will be used on all requests where an explicit microversion -is not requested. - -.. code-block:: python - - adapter = keystoneauth1.adapter.Adapter( - session=session, - service_type='compute', - interface='public', - min_version='2.1', - default_microversion='2.38') - response = adapter.get('/servers') - -If the user is using a :class:`keystoneauth1.session.Session`, the -`service_type` will be taken from the `service_type` in `endpoint_filter`. - -If the `service_type` is the incorrect value to use for the microversion header -for the service in question, the parameter `microversion_service_type` can be -given. For instance, although keystoneauth already knows about Cinder, the -`service_type` for Cinder is ``block-storage`` but the microversion header -expects ``volume``. - -.. code-block:: python - - # Interactions with cinder do not need to explicitly override the - # microversion_service_type - it is only being used as an example for the - # use of the parameter. - resp = session.get('/volumes', - microversion='3.15', - microversion_service_type='volume', - endpoint_filter={'service_type': 'block-storage', - 'interface': 'public', - 'min_version': '3', - 'max_version': 'latest'}) - - -.. _API-WG Specs: http://specs.openstack.org/openstack/api-wg/ -.. _Consuming the Catalog: http://specs.openstack.org/openstack/api-wg/guidelines/consuming-catalog.html -.. _Microversions: http://specs.openstack.org/openstack/api-wg/guidelines/microversion_specification.html#version-discovery - diff --git a/keystoneauth1/__init__.py b/keystoneauth1/__init__.py deleted file mode 100644 index 312aa58..0000000 --- a/keystoneauth1/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -# Licensed under the Apache License, Version 2.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 pbr.version - - -__version__ = pbr.version.VersionInfo('keystoneauth1').version_string() diff --git a/keystoneauth1/_utils.py b/keystoneauth1/_utils.py deleted file mode 100644 index 2d6f0ae..0000000 --- a/keystoneauth1/_utils.py +++ /dev/null @@ -1,83 +0,0 @@ -# Licensed under the Apache License, Version 2.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 logging - -import iso8601 -import six - - -def get_logger(name): - name = name.replace(__name__.split('.')[0], 'keystoneauth') - return logging.getLogger(name) - - -logger = get_logger(__name__) - - -def normalize_time(timestamp): - """Normalize time in arbitrary timezone to UTC naive object.""" - offset = timestamp.utcoffset() - if offset is None: - return timestamp - return timestamp.replace(tzinfo=None) - offset - - -def parse_isotime(timestr): - """Parse time from ISO 8601 format.""" - try: - return iso8601.parse_date(timestr) - except iso8601.ParseError as e: - raise ValueError(six.text_type(e)) - except TypeError as e: - raise ValueError(six.text_type(e)) - - -def from_utcnow(**timedelta_kwargs): - r"""Calculate the time in the future from utcnow. - - :param \*\*timedelta_kwargs: - Passed directly to :class:`datetime.timedelta` to add to the current - time in UTC. - :returns: - The time in the future based on ``timedelta_kwargs``. - :rtype: - datetime.datetime - """ - now = datetime.datetime.utcnow() - delta = datetime.timedelta(**timedelta_kwargs) - return now + delta - - -def before_utcnow(**timedelta_kwargs): - r"""Calculate the time in the past from utcnow. - - :param \*\*timedelta_kwargs: - Passed directly to :class:`datetime.timedelta` to subtract from the - current time in UTC. - :returns: - The time in the past based on ``timedelta_kwargs``. - :rtype: - datetime.datetime - """ - now = datetime.datetime.utcnow() - delta = datetime.timedelta(**timedelta_kwargs) - return now - delta - - -# Detect if running on the Windows Subsystem for Linux -try: - with open('/proc/version', 'r') as f: - is_windows_linux_subsystem = 'microsoft' in f.read().lower() -except IOError: - is_windows_linux_subsystem = False diff --git a/keystoneauth1/access/__init__.py b/keystoneauth1/access/__init__.py deleted file mode 100644 index 273d9cd..0000000 --- a/keystoneauth1/access/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# Licensed under the Apache License, Version 2.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 keystoneauth1.access.access import * # noqa - - -__all__ = ('AccessInfo', - 'AccessInfoV2', - 'AccessInfoV3', - 'create') diff --git a/keystoneauth1/access/access.py b/keystoneauth1/access/access.py deleted file mode 100644 index 69ca1b5..0000000 --- a/keystoneauth1/access/access.py +++ /dev/null @@ -1,755 +0,0 @@ -# Copyright 2012 Nebula, 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. - -import functools - -from positional import positional - -from keystoneauth1 import _utils as utils -from keystoneauth1.access import service_catalog -from keystoneauth1.access import service_providers - - -# gap, in seconds, to determine whether the given token is about to expire -STALE_TOKEN_DURATION = 30 - - -__all__ = ('AccessInfo', - 'AccessInfoV2', - 'AccessInfoV3', - 'create') - - -@positional() -def create(resp=None, body=None, auth_token=None): - if resp and not body: - body = resp.json() - - if 'token' in body: - if resp and not auth_token: - auth_token = resp.headers.get('X-Subject-Token') - - return AccessInfoV3(body, auth_token) - elif 'access' in body: - return AccessInfoV2(body, auth_token) - - raise ValueError('Unrecognized auth response') - - -def _missingproperty(f): - - @functools.wraps(f) - def inner(self): - try: - return f(self) - except KeyError: - return None - - return property(inner) - - -class AccessInfo(object): - """Encapsulates a raw authentication token from keystone. - - Provides helper methods for extracting useful values from that token. - - """ - - _service_catalog_class = None - - def __init__(self, body, auth_token=None): - self._data = body - self._auth_token = auth_token - self._service_catalog = None - self._service_providers = None - - @property - def service_catalog(self): - if not self._service_catalog: - self._service_catalog = self._service_catalog_class.from_token( - self._data) - - return self._service_catalog - - def will_expire_soon(self, stale_duration=STALE_TOKEN_DURATION): - """Determine if expiration is about to occur. - - :returns: true if expiration is within the given duration - :rtype: boolean - - """ - norm_expires = utils.normalize_time(self.expires) - # (gyee) should we move auth_token.will_expire_soon() to timeutils - # instead of duplicating code here? - soon = utils.from_utcnow(seconds=stale_duration) - return norm_expires < soon - - def has_service_catalog(self): - """Return true if the auth token has a service catalog. - - :returns: boolean - """ - raise NotImplementedError() - - @property - def auth_token(self): - """Return the token_id associated with the auth request. - - To be used in headers for authenticating OpenStack API requests. - - :returns: str - """ - return self._auth_token - - @property - def expires(self): - """Return the token expiration (as datetime object). - - :returns: datetime - """ - raise NotImplementedError() - - @property - def issued(self): - """Return the token issue time (as datetime object). - - :returns: datetime - """ - raise NotImplementedError() - - @property - def username(self): - """Return the username associated with the auth request. - - Follows the pattern defined in the V2 API of first looking for 'name', - returning that if available, and falling back to 'username' if name - is unavailable. - - :returns: str - """ - raise NotImplementedError() - - @property - def user_id(self): - """Return the user id associated with the auth request. - - :returns: str - """ - raise NotImplementedError() - - @property - def user_domain_id(self): - """Return the user's domain id associated with the auth request. - - :returns: str - """ - raise NotImplementedError() - - @property - def user_domain_name(self): - """Return the user's domain name associated with the auth request. - - :returns: str - """ - raise NotImplementedError() - - @property - def role_ids(self): - """Return a list of user's role ids associated with the auth request. - - :returns: a list of strings of role ids - """ - raise NotImplementedError() - - @property - def role_names(self): - """Return a list of user's role names associated with the auth request. - - :returns: a list of strings of role names - """ - raise NotImplementedError() - - @property - def domain_name(self): - """Return the domain name associated with the auth request. - - :returns: str or None (if no domain associated with the token) - """ - raise NotImplementedError() - - @property - def domain_id(self): - """Return the domain id associated with the auth request. - - :returns: str or None (if no domain associated with the token) - """ - raise NotImplementedError() - - @property - def project_name(self): - """Return the project name associated with the auth request. - - :returns: str or None (if no project associated with the token) - """ - raise NotImplementedError() - - @property - def tenant_name(self): - """Synonym for project_name.""" - return self.project_name - - @property - def scoped(self): - """Return true if the auth token was scoped. - - Returns true if scoped to a tenant(project) or domain, - and contains a populated service catalog. - - This is deprecated, use project_scoped instead. - - :returns: bool - """ - return self.project_scoped or self.domain_scoped - - @property - def project_scoped(self): - """Return true if the auth token was scoped to a tenant (project). - - :returns: bool - """ - return bool(self.project_id) - - @property - def domain_scoped(self): - """Return true if the auth token was scoped to a domain. - - :returns: bool - """ - raise NotImplementedError() - - @property - def trust_id(self): - """Return the trust id associated with the auth request. - - :returns: str or None (if no trust associated with the token) - """ - raise NotImplementedError() - - @property - def trust_scoped(self): - """Return true if the auth token was scoped from a delegated trust. - - The trust delegation is via the OS-TRUST v3 extension. - - :returns: bool - """ - raise NotImplementedError() - - @property - def trustee_user_id(self): - """Return the trustee user id associated with a trust. - - :returns: str or None (if no trust associated with the token) - """ - raise NotImplementedError() - - @property - def trustor_user_id(self): - """Return the trustor user id associated with a trust. - - :returns: str or None (if no trust associated with the token) - """ - raise NotImplementedError() - - @property - def project_id(self): - """Return the project ID associated with the auth request. - - This returns None if the auth token wasn't scoped to a project. - - :returns: str or None (if no project associated with the token) - """ - raise NotImplementedError() - - @property - def tenant_id(self): - """Synonym for project_id.""" - return self.project_id - - @property - def project_domain_id(self): - """Return the project's domain id associated with the auth request. - - :returns: str - """ - raise NotImplementedError() - - @property - def project_domain_name(self): - """Return the project's domain name associated with the auth request. - - :returns: str - """ - raise NotImplementedError() - - @property - def oauth_access_token_id(self): - """Return the access token ID if OAuth authentication used. - - :returns: str or None. - """ - raise NotImplementedError() - - @property - def oauth_consumer_id(self): - """Return the consumer ID if OAuth authentication used. - - :returns: str or None. - """ - raise NotImplementedError() - - @property - def is_federated(self): - """Return true if federation was used to get the token. - - :returns: boolean - """ - raise NotImplementedError() - - @property - def is_admin_project(self): - """Return true if the current project scope is the admin project. - - For backwards compatibility purposes if there is nothing specified in - the token we always assume we are in the admin project, so this will - default to True. - - :returns boolean - """ - raise NotImplementedError() - - @property - def audit_id(self): - """Return the audit ID if present. - - :returns: str or None. - """ - raise NotImplementedError() - - @property - def audit_chain_id(self): - """Return the audit chain ID if present. - - In the event that a token was rescoped then this ID will be the - :py:attr:`audit_id` of the initial token. Returns None if no value - present. - - :returns: str or None. - """ - raise NotImplementedError() - - @property - def initial_audit_id(self): - """The audit ID of the initially requested token. - - This is the :py:attr:`audit_chain_id` if present or the - :py:attr:`audit_id`. - """ - return self.audit_chain_id or self.audit_id - - @property - def service_providers(self): - """Return an object representing the list of trusted service providers. - - Used for Keystone2Keystone federating-out. - - :returns: :py:class:`keystoneauth1.service_providers.ServiceProviders` - or None - """ - raise NotImplementedError() - - @property - def bind(self): - """Information about external mechanisms the token is bound to. - - If a token is bound to an external authentication mechanism it can only - be used in conjunction with that mechanism. For example if bound to a - kerberos principal it may only be accepted if there is also kerberos - authentication performed on the request. - - :returns: A dictionary or None. The key will be the bind type the value - is a dictionary that is specific to the format of the bind - type. Returns None if there is no bind information in the - token. - """ - raise NotImplementedError() - - @property - def project_is_domain(self): - """Return if a project act as a domain. - - :returns: bool - """ - raise NotImplementedError() - - -class AccessInfoV2(AccessInfo): - """An object for encapsulating raw v2 auth token from identity service.""" - - version = 'v2.0' - _service_catalog_class = service_catalog.ServiceCatalogV2 - - def has_service_catalog(self): - return 'serviceCatalog' in self._data.get('access', {}) - - @_missingproperty - def auth_token(self): - set_token = super(AccessInfoV2, self).auth_token - return set_token or self._data['access']['token']['id'] - - @property - def _token(self): - return self._data['access']['token'] - - @_missingproperty - def expires(self): - return utils.parse_isotime(self._token.get('expires')) - - @_missingproperty - def issued(self): - return utils.parse_isotime(self._token['issued_at']) - - @property - def _user(self): - return self._data['access']['user'] - - @_missingproperty - def username(self): - return self._user.get('name') or self._user.get('username') - - @_missingproperty - def user_id(self): - return self._user['id'] - - @property - def user_domain_id(self): - return None - - @property - def user_domain_name(self): - return None - - @_missingproperty - def role_ids(self): - metadata = self._data.get('access', {}).get('metadata', {}) - return metadata.get('roles', []) - - @_missingproperty - def role_names(self): - return [r['name'] for r in self._user.get('roles', [])] - - @property - def domain_name(self): - return None - - @property - def domain_id(self): - return None - - @property - def project_name(self): - try: - tenant_dict = self._token['tenant'] - except KeyError: - pass - else: - return tenant_dict.get('name') - - # pre grizzly - try: - return self._user['tenantName'] - except KeyError: - pass - - # pre diablo, keystone only provided a tenantId - try: - return self._token['tenantId'] - except KeyError: - pass - - @property - def domain_scoped(self): - return False - - @property - def _trust(self): - return self._data['access']['trust'] - - @_missingproperty - def trust_id(self): - return self._trust['id'] - - @_missingproperty - def trust_scoped(self): - return bool(self._trust) - - @_missingproperty - def trustee_user_id(self): - return self._trust['trustee_user_id'] - - @property - def trustor_user_id(self): - # this information is not available in the v2 token bug: #1331882 - return None - - @property - def project_id(self): - try: - tenant_dict = self._token['tenant'] - except KeyError: - pass - else: - return tenant_dict.get('id') - - # pre grizzly - try: - return self._user['tenantId'] - except KeyError: - pass - - # pre diablo - try: - return self._token['tenantId'] - except KeyError: - pass - - @property - def project_is_domain(self): - return False - - @property - def project_domain_id(self): - return None - - @property - def project_domain_name(self): - return None - - @property - def oauth_access_token_id(self): - return None - - @property - def oauth_consumer_id(self): - return None - - @property - def is_federated(self): - return False - - @property - def is_admin_project(self): - return True - - @property - def audit_id(self): - try: - return self._token.get('audit_ids', [])[0] - except IndexError: - return None - - @property - def audit_chain_id(self): - try: - return self._token.get('audit_ids', [])[1] - except IndexError: - return None - - @property - def service_providers(self): - return None - - @_missingproperty - def bind(self): - return self._token['bind'] - - -class AccessInfoV3(AccessInfo): - """An object encapsulating raw v3 auth token from identity service.""" - - version = 'v3' - _service_catalog_class = service_catalog.ServiceCatalogV3 - - def has_service_catalog(self): - return 'catalog' in self._data['token'] - - @property - def _user(self): - return self._data['token']['user'] - - @property - def is_federated(self): - return 'OS-FEDERATION' in self._user - - @property - def is_admin_project(self): - return self._data.get('token', {}).get('is_admin_project', True) - - @_missingproperty - def expires(self): - return utils.parse_isotime(self._data['token']['expires_at']) - - @_missingproperty - def issued(self): - return utils.parse_isotime(self._data['token']['issued_at']) - - @_missingproperty - def user_id(self): - return self._user['id'] - - @property - def user_domain_id(self): - try: - return self._user['domain']['id'] - except KeyError: - if self.is_federated: - return None - raise - - @property - def user_domain_name(self): - try: - return self._user['domain']['name'] - except KeyError: - if self.is_federated: - return None - raise - - @_missingproperty - def role_ids(self): - return [r['id'] for r in self._data['token'].get('roles', [])] - - @_missingproperty - def role_names(self): - return [r['name'] for r in self._data['token'].get('roles', [])] - - @_missingproperty - def username(self): - return self._user['name'] - - @property - def _domain(self): - return self._data['token']['domain'] - - @_missingproperty - def domain_name(self): - return self._domain['name'] - - @_missingproperty - def domain_id(self): - return self._domain['id'] - - @property - def _project(self): - return self._data['token']['project'] - - @_missingproperty - def project_id(self): - return self._project['id'] - - @_missingproperty - def project_is_domain(self): - return self._data['token']['is_domain'] - - @_missingproperty - def project_domain_id(self): - return self._project['domain']['id'] - - @_missingproperty - def project_domain_name(self): - return self._project['domain']['name'] - - @_missingproperty - def project_name(self): - return self._project['name'] - - @property - def domain_scoped(self): - try: - return bool(self._domain) - except KeyError: - return False - - @property - def _trust(self): - return self._data['token']['OS-TRUST:trust'] - - @_missingproperty - def trust_id(self): - return self._trust['id'] - - @property - def trust_scoped(self): - try: - return bool(self._trust) - except KeyError: - return False - - @_missingproperty - def trustee_user_id(self): - return self._trust['trustee_user']['id'] - - @_missingproperty - def trustor_user_id(self): - return self._trust['trustor_user']['id'] - - @property - def _oauth(self): - return self._data['token']['OS-OAUTH1'] - - @_missingproperty - def oauth_access_token_id(self): - return self._oauth['access_token_id'] - - @_missingproperty - def oauth_consumer_id(self): - return self._oauth['consumer_id'] - - @_missingproperty - def audit_id(self): - try: - return self._data['token']['audit_ids'][0] - except IndexError: - return None - - @_missingproperty - def audit_chain_id(self): - try: - return self._data['token']['audit_ids'][1] - except IndexError: - return None - - @property - def service_providers(self): - if not self._service_providers: - self._service_providers = ( - service_providers.ServiceProviders.from_token(self._data)) - - return self._service_providers - - @_missingproperty - def bind(self): - return self._data['token']['bind'] diff --git a/keystoneauth1/access/service_catalog.py b/keystoneauth1/access/service_catalog.py deleted file mode 100644 index dd51ded..0000000 --- a/keystoneauth1/access/service_catalog.py +++ /dev/null @@ -1,512 +0,0 @@ -# Copyright 2011 OpenStack Foundation -# Copyright 2011, Piston Cloud Computing, Inc. -# Copyright 2011 Nebula, 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. - -import abc -import copy - -from positional import positional -import six - -from keystoneauth1 import discover -from keystoneauth1 import exceptions - - -@six.add_metaclass(abc.ABCMeta) -class ServiceCatalog(object): - """Helper methods for dealing with a Keystone Service Catalog.""" - - def __init__(self, catalog): - self._catalog = catalog - - def _get_endpoint_region(self, endpoint): - return endpoint.get('region_id') or endpoint.get('region') - - @property - def catalog(self): - """Return the raw service catalog content, mostly useful for debugging. - - Applications should avoid this and use accessor methods instead. - However, there are times when inspecting the raw catalog can be useful - for analysis and other reasons. - """ - return self._catalog - - @abc.abstractmethod - def is_interface_match(self, endpoint, interface): - """Helper function to normalize endpoint matching across v2 and v3. - - :returns: True if the provided endpoint matches the required - interface otherwise False. - """ - - @staticmethod - def normalize_interface(self, interface): - """Handle differences in the way v2 and v3 catalogs specify endpoint. - - Both v2 and v3 must be able to handle the endpoint style of the other. - For example v2 must be able to handle a 'public' interface and - v3 must be able to handle a 'publicURL' interface. - - :returns: the endpoint string in the format appropriate for this - service catalog. - """ - return interface - - def _normalize_endpoints(self, endpoints): - """Translate endpoint description dicts into v3 form. - - Takes a raw endpoint description from the catalog and changes - it to be in v3 format. It also saves a copy of the data in - raw_endpoint so that it can be returned by methods that expect the - actual original data. - - :param list endpoints: List of endpoint description dicts - - :returns: List of endpoint description dicts in v3 format - """ - new_endpoints = [] - for endpoint in endpoints: - raw_endpoint = endpoint.copy() - new_endpoint = endpoint.copy() - new_endpoint['raw_endpoint'] = raw_endpoint - new_endpoints.append(new_endpoint) - return new_endpoints - - def _denormalize_endpoints(self, endpoints): - """Return original endpoint description dicts. - - Takes a list of EndpointData objects and returns the original - dict that was returned from the catalog. - - :param list endpoints: List of `keystoneauth1.discover.EndpointData` - - :returns: List of endpoint description dicts in original catalog format - """ - return [endpoint.raw_endpoint for endpoint in endpoints] - - def normalize_catalog(self): - """Return the catalog normalized into v3 format.""" - catalog = [] - for service in copy.deepcopy(self._catalog): - if 'type' not in service: - continue - - # NOTE(jamielennox): service_name is different. It is not available - # in API < v3.3. If it is in the catalog then we enforce it, if it - # is not then we don't because the name could be correct we just - # don't have that information to check against. Set to None so - # that checks will naturally work. - service.setdefault('name', None) - - # NOTE(jamielennox): there is no such thing as a service_id in v2 - # similarly to service_name. - service.setdefault('id', None) - - service['endpoints'] = self._normalize_endpoints( - service.get('endpoints', [])) - - for endpoint in service['endpoints']: - endpoint['region_name'] = self._get_endpoint_region(endpoint) - endpoint.setdefault('id', None) - catalog.append(service) - return catalog - - def _get_interface_list(self, interface): - if not interface: - return [] - if not isinstance(interface, list): - interface = [interface] - return [self.normalize_interface(i) for i in interface] - - @positional() - def get_endpoints_data(self, service_type=None, interface=None, - region_name=None, service_name=None, - service_id=None, endpoint_id=None): - """Fetch and filter endpoint data for the specified service(s). - - Returns endpoints for the specified service (or all) containing - the specified type (or all) and region (or all) and service name. - - If there is no name in the service catalog the service_name check will - be skipped. This allows compatibility with services that existed - before the name was available in the catalog. - - Valid interface types: `public` or `publicURL`, - `internal` or `internalURL`, - `admin` or 'adminURL` - - :param string service_type: Service type of the endpoint. - :param interface: Type of endpoint. Can be a single value or a list - of values. If it's a list of values, they will be - looked for in order of preference. - :param string region_name: Region of the endpoint. - :param string service_name: The assigned name of the service. - :param string service_id: The identifier of a service. - :param string endpoint_id: The identifier of an endpoint. - - :returns: a list of matching EndpointData objects - :rtype: list(`keystoneauth1.discover.EndpointData`) - - :returns: a dict, keyed by service_type, of lists of EndpointData - """ - interfaces = self._get_interface_list(interface) - - matching_endpoints = {} - - for service in self.normalize_catalog(): - - if service_type and service_type != service['type']: - continue - - if (service_name and service['name'] and - service_name != service['name']): - continue - - if (service_id and service['id'] and - service_id != service['id']): - continue - - matching_endpoints.setdefault(service['type'], []) - - for endpoint in service.get('endpoints', []): - if interfaces and endpoint['interface'] not in interfaces: - continue - if region_name and region_name != endpoint['region_name']: - continue - if endpoint_id and endpoint_id != endpoint['id']: - continue - if not endpoint['url']: - continue - - matching_endpoints[service['type']].append( - discover.EndpointData( - catalog_url=endpoint['url'], - service_type=service['type'], - service_name=service['name'], - service_id=service['id'], - interface=endpoint['interface'], - region_name=endpoint['region_name'], - endpoint_id=endpoint['id'], - raw_endpoint=endpoint['raw_endpoint'])) - - if not interfaces: - return matching_endpoints - - ret = {} - for service_type, endpoints in matching_endpoints.items(): - if not endpoints: - ret[service_type] = [] - continue - matches_by_interface = {} - for endpoint in endpoints: - matches_by_interface.setdefault(endpoint.interface, []) - matches_by_interface[endpoint.interface].append(endpoint) - best_interface = [i for i in interfaces - if i in matches_by_interface.keys()][0] - ret[service_type] = matches_by_interface[best_interface] - - return ret - - @positional() - def get_endpoints(self, service_type=None, interface=None, - region_name=None, service_name=None, - service_id=None, endpoint_id=None): - """Fetch and filter endpoint data for the specified service(s). - - Returns endpoints for the specified service (or all) containing - the specified type (or all) and region (or all) and service name. - - If there is no name in the service catalog the service_name check will - be skipped. This allows compatibility with services that existed - before the name was available in the catalog. - - Returns a dict keyed by service_type with a list of endpoint dicts - """ - endpoints_data = self.get_endpoints_data( - service_type=service_type, interface=interface, - region_name=region_name, service_name=service_name, - service_id=service_id, endpoint_id=endpoint_id) - endpoints = {} - for service_type, data in endpoints_data.items(): - endpoints[service_type] = self._denormalize_endpoints(data) - return endpoints - - @positional() - def get_endpoint_data_list(self, service_type=None, interface='public', - region_name=None, service_name=None, - service_id=None, endpoint_id=None): - """Fetch a flat list of matching EndpointData objects. - - Fetch the endpoints from the service catalog for a particular - endpoint attribute. If no attribute is given, return the first - endpoint of the specified type. - - Valid interface types: `public` or `publicURL`, - `internal` or `internalURL`, - `admin` or 'adminURL` - - :param string service_type: Service type of the endpoint. - :param interface: Type of endpoint. Can be a single value or a list - of values. If it's a list of values, they will be - looked for in order of preference. - :param string region_name: Region of the endpoint. - :param string service_name: The assigned name of the service. - :param string service_id: The identifier of a service. - :param string endpoint_id: The identifier of an endpoint. - - :returns: a list of matching EndpointData objects - :rtype: list(`keystoneauth1.discover.EndpointData`) - """ - endpoints = self.get_endpoints_data(service_type=service_type, - interface=interface, - region_name=region_name, - service_name=service_name, - service_id=service_id, - endpoint_id=endpoint_id) - return [endpoint for data in endpoints.values() for endpoint in data] - - @positional() - def get_urls(self, service_type=None, interface='public', - region_name=None, service_name=None, - service_id=None, endpoint_id=None): - """Fetch endpoint urls from the service catalog. - - Fetch the urls of endpoints from the service catalog for a particular - endpoint attribute. If no attribute is given, return the url of the - first endpoint of the specified type. - - Valid interface types: `public` or `publicURL`, - `internal` or `internalURL`, - `admin` or 'adminURL` - - :param string service_type: Service type of the endpoint. - :param interface: Type of endpoint. Can be a single value or a list - of values. If it's a list of values, they will be - looked for in order of preference. - :param string region_name: Region of the endpoint. - :param string service_name: The assigned name of the service. - :param string service_id: The identifier of a service. - :param string endpoint_id: The identifier of an endpoint. - - :returns: tuple of urls - """ - endpoints = self.get_endpoint_data_list(service_type=service_type, - interface=interface, - region_name=region_name, - service_name=service_name, - service_id=service_id, - endpoint_id=endpoint_id) - return tuple([endpoint.url for endpoint in endpoints]) - - @positional() - def url_for(self, service_type=None, interface='public', - region_name=None, service_name=None, - service_id=None, endpoint_id=None): - """Fetch an endpoint from the service catalog. - - Fetch the specified endpoint from the service catalog for - a particular endpoint attribute. If no attribute is given, return - the first endpoint of the specified type. - - Valid interface types: `public` or `publicURL`, - `internal` or `internalURL`, - `admin` or 'adminURL` - - :param string service_type: Service type of the endpoint. - :param interface: Type of endpoint. Can be a single value or a list - of values. If it's a list of values, they will be - looked for in order of preference. - :param string region_name: Region of the endpoint. - :param string service_name: The assigned name of the service. - :param string service_id: The identifier of a service. - :param string endpoint_id: The identifier of an endpoint. - """ - return self.endpoint_data_for(service_type=service_type, - interface=interface, - region_name=region_name, - service_name=service_name, - service_id=service_id, - endpoint_id=endpoint_id).url - - @positional() - def endpoint_data_for(self, service_type=None, interface='public', - region_name=None, service_name=None, - service_id=None, endpoint_id=None): - """Fetch endpoint data from the service catalog. - - Fetch the specified endpoint data from the service catalog for - a particular endpoint attribute. If no attribute is given, return - the first endpoint of the specified type. - - Valid interface types: `public` or `publicURL`, - `internal` or `internalURL`, - `admin` or 'adminURL` - - :param string service_type: Service type of the endpoint. - :param interface: Type of endpoint. Can be a single value or a list - of values. If it's a list of values, they will be - looked for in order of preference. - :param string region_name: Region of the endpoint. - :param string service_name: The assigned name of the service. - :param string service_id: The identifier of a service. - :param string endpoint_id: The identifier of an endpoint. - """ - if not self._catalog: - raise exceptions.EmptyCatalog('The service catalog is empty.') - - endpoint_data_list = self.get_endpoint_data_list( - service_type=service_type, - interface=interface, - region_name=region_name, - service_name=service_name, - service_id=service_id, - endpoint_id=endpoint_id) - - if endpoint_data_list: - return endpoint_data_list[0] - - if service_name and region_name: - msg = ('%(interface)s endpoint for %(service_type)s service ' - 'named %(service_name)s in %(region_name)s region not ' - 'found' % - {'interface': interface, - 'service_type': service_type, 'service_name': service_name, - 'region_name': region_name}) - elif service_name: - msg = ('%(interface)s endpoint for %(service_type)s service ' - 'named %(service_name)s not found' % - {'interface': interface, - 'service_type': service_type, - 'service_name': service_name}) - elif region_name: - msg = ('%(interface)s endpoint for %(service_type)s service ' - 'in %(region_name)s region not found' % - {'interface': interface, - 'service_type': service_type, 'region_name': region_name}) - else: - msg = ('%(interface)s endpoint for %(service_type)s service ' - 'not found' % - {'interface': interface, - 'service_type': service_type}) - - raise exceptions.EndpointNotFound(msg) - - -class ServiceCatalogV2(ServiceCatalog): - """An object for encapsulating the v2 service catalog. - - The object is created using raw v2 auth token from Keystone. - """ - - @classmethod - def from_token(cls, token): - if 'access' not in token: - raise ValueError('Invalid token format for fetching catalog') - - return cls(token['access'].get('serviceCatalog', {})) - - @staticmethod - def normalize_interface(interface): - if interface and 'URL' not in interface: - interface += 'URL' - - return interface - - def is_interface_match(self, endpoint, interface): - return interface in endpoint - - def _normalize_endpoints(self, endpoints): - """Translate endpoint description dicts into v3 form. - - Takes a raw endpoint description from the catalog and changes - it to be in v3 format. It also saves a copy of the data in - raw_endpoint so that it can be returned by methods that expect the - actual original data. - - :param list endpoints: List of endpoint description dicts - - :returns: List of endpoint description dicts in v3 format - """ - new_endpoints = [] - for endpoint in endpoints: - raw_endpoint = endpoint.copy() - interface_urls = {} - interface_keys = [key for key in endpoint.keys() - if key.endswith('URL')] - for key in interface_keys: - interface = self.normalize_interface(key) - interface_urls[interface] = endpoint.pop(key) - for interface, url in interface_urls.items(): - new_endpoint = endpoint.copy() - new_endpoint['interface'] = interface - new_endpoint['url'] = url - # Save the actual endpoint for ease of later reconstruction - new_endpoint['raw_endpoint'] = raw_endpoint - new_endpoints.append(new_endpoint) - return new_endpoints - - def _denormalize_endpoints(self, endpoints): - """Return original endpoint description dicts. - - Takes a list of EndpointData objects and returns the original - dict that was returned from the catalog. - - :param list endpoints: List of `keystoneauth1.discover.EndpointData` - - :returns: List of endpoint description dicts in original catalog format - """ - raw_endpoints = super(ServiceCatalogV2, self)._denormalize_endpoints( - endpoints) - # The same raw endpoint content will be in the list once for each - # v2 endpoint_type entry. We only need one of them in the resulting - # list. So keep a list of the string versions. - seen = {} - endpoints = [] - for endpoint in raw_endpoints: - if str(endpoint) in seen: - continue - seen[str(endpoint)] = True - endpoints.append(endpoint) - return endpoints - - -class ServiceCatalogV3(ServiceCatalog): - """An object for encapsulating the v3 service catalog. - - The object is created using raw v3 auth token from Keystone. - """ - - @classmethod - def from_token(cls, token): - if 'token' not in token: - raise ValueError('Invalid token format for fetching catalog') - - return cls(token['token'].get('catalog', {})) - - @staticmethod - def normalize_interface(interface): - if interface: - interface = interface.rstrip('URL') - - return interface - - def is_interface_match(self, endpoint, interface): - try: - return interface == endpoint['interface'] - except KeyError: - return False diff --git a/keystoneauth1/access/service_providers.py b/keystoneauth1/access/service_providers.py deleted file mode 100644 index 83a27cc..0000000 --- a/keystoneauth1/access/service_providers.py +++ /dev/null @@ -1,44 +0,0 @@ -# Licensed under the Apache License, Version 2.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 keystoneauth1 import exceptions - - -class ServiceProviders(object): - """Helper methods for dealing with Service Providers.""" - - @classmethod - def from_token(cls, token): - if 'token' not in token: - raise ValueError('Token format does not support service' - 'providers.') - - return cls(token['token'].get('service_providers', [])) - - def __init__(self, service_providers): - - def normalize(service_providers_list): - return dict((sp['id'], sp) for sp in service_providers_list - if 'id' in sp) - self._service_providers = normalize(service_providers) - - def _get_service_provider(self, sp_id): - try: - return self._service_providers[sp_id] - except KeyError: - raise exceptions.ServiceProviderNotFound(sp_id) - - def get_sp_url(self, sp_id): - return self._get_service_provider(sp_id).get('sp_url') - - def get_auth_url(self, sp_id): - return self._get_service_provider(sp_id).get('auth_url') diff --git a/keystoneauth1/adapter.py b/keystoneauth1/adapter.py deleted file mode 100644 index 1abc97f..0000000 --- a/keystoneauth1/adapter.py +++ /dev/null @@ -1,466 +0,0 @@ -# Licensed under the Apache License, Version 2.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 warnings - -from positional import positional - -from keystoneauth1 import session - - -class Adapter(object): - """An instance of a session with local variables. - - A session is a global object that is shared around amongst many clients. It - therefore contains state that is relevant to everyone. There is a lot of - state such as the service type and region_name that are only relevant to a - particular client that is using the session. An adapter provides a wrapper - of client local data around the global session object. - - version, min_version, max_version and default_microversion can all be - given either as a string or a tuple. - - :param session: The session object to wrap. - :type session: keystoneauth1.session.Session - :param str service_type: The default service_type for URL discovery. - :param str service_name: The default service_name for URL discovery. - :param str interface: The default interface for URL discovery. - :param str region_name: The default region_name for URL discovery. - :param str endpoint_override: Always use this endpoint URL for requests - for this client. - :param version: The minimum version restricted to a given Major API. - Mutually exclusive with min_version and max_version. - (optional) - :param auth: An auth plugin to use instead of the session one. - :type auth: keystoneauth1.plugin.BaseAuthPlugin - :param str user_agent: The User-Agent string to set. - :param int connect_retries: the maximum number of retries that should - be attempted for connection errors. - Default None - use session default which - is don't retry. - :param logger: A logging object to use for requests that pass through this - adapter. - :type logger: logging.Logger - :param dict allow: Extra filters to pass when discovering API versions. - (optional) - :param dict additional_headers: Additional headers that should be attached - to every request passing through the - adapter. Headers of the same name specified - per request will take priority. - :param str client_name: The name of the client that created the adapter. - This will be used to create the user_agent. - :param str client_version: The version of the client that created the - adapter. This will be used to create the - user_agent. - :param bool allow_version_hack: Allow keystoneauth to hack up catalog - URLS to support older schemes. - (optional, default True) - :param str global_request_id: A global_request_id (in the form of - ``req-$uuid``) that will be passed on all - requests. Enables cross project request id - tracking. - :param min_version: The minimum major version of a given API, intended to - be used as the lower bound of a range with - max_version. Mutually exclusive with version. - If min_version is given with no max_version it is as - if max version is 'latest'. (optional) - :param max_version: The maximum major version of a given API, intended to - be used as the upper bound of a range with min_version. - Mutually exclusive with version. (optional) - :param default_microversion: The default microversion value to send - with API requests. While microversions are - a per-request feature, a user may know they - want to default to sending a specific value. - (optional) - """ - - client_name = None - client_version = None - - @positional() - def __init__(self, session, service_type=None, service_name=None, - interface=None, region_name=None, endpoint_override=None, - version=None, auth=None, user_agent=None, - connect_retries=None, logger=None, allow={}, - additional_headers=None, client_name=None, - client_version=None, allow_version_hack=None, - global_request_id=None, - min_version=None, max_version=None, - default_microversion=None): - if version and (min_version or max_version): - raise TypeError( - "version is mutually exclusive with min_version and" - " max_version") - # NOTE(jamielennox): when adding new parameters to adapter please also - # add them to the adapter call in httpclient.HTTPClient.__init__ as - # well as to load_adapter_from_argparse below if the argument is - # intended to be something a user would reasonably expect to set on - # a command line - self.session = session - self.service_type = service_type - self.service_name = service_name - self.interface = interface - self.region_name = region_name - self.endpoint_override = endpoint_override - self.version = version - self.user_agent = user_agent - self.auth = auth - self.connect_retries = connect_retries - self.logger = logger - self.allow = allow - self.additional_headers = additional_headers or {} - self.allow_version_hack = allow_version_hack - self.min_version = min_version - self.max_version = max_version - self.default_microversion = default_microversion - - self.global_request_id = global_request_id - - if client_name: - self.client_name = client_name - if client_version: - self.client_version = client_version - - def _set_endpoint_filter_kwargs(self, kwargs): - if self.service_type: - kwargs.setdefault('service_type', self.service_type) - if self.service_name: - kwargs.setdefault('service_name', self.service_name) - if self.interface: - kwargs.setdefault('interface', self.interface) - if self.region_name: - kwargs.setdefault('region_name', self.region_name) - if self.version: - kwargs.setdefault('version', self.version) - if self.min_version: - kwargs.setdefault('min_version', self.min_version) - if self.max_version: - kwargs.setdefault('max_version', self.max_version) - if self.allow_version_hack is not None: - kwargs.setdefault('allow_version_hack', self.allow_version_hack) - - def request(self, url, method, **kwargs): - endpoint_filter = kwargs.setdefault('endpoint_filter', {}) - self._set_endpoint_filter_kwargs(endpoint_filter) - - if self.endpoint_override: - kwargs.setdefault('endpoint_override', self.endpoint_override) - - if self.auth: - kwargs.setdefault('auth', self.auth) - if self.user_agent: - kwargs.setdefault('user_agent', self.user_agent) - if self.connect_retries is not None: - kwargs.setdefault('connect_retries', self.connect_retries) - if self.logger: - kwargs.setdefault('logger', self.logger) - if self.allow: - kwargs.setdefault('allow', self.allow) - if self.default_microversion is not None: - kwargs.setdefault('microversion', self.default_microversion) - - if isinstance(self.session, (session.Session, Adapter)): - # these things are unsupported by keystoneclient's session so be - # careful with them until everyone has transitioned to ksa. - # Allowing adapter allows adapter nesting that auth_token does. - if self.client_name: - kwargs.setdefault('client_name', self.client_name) - if self.client_version: - kwargs.setdefault('client_version', self.client_version) - - else: - warnings.warn('Using keystoneclient sessions has been deprecated. ' - 'Please update your software to use keystoneauth1.') - - for k, v in self.additional_headers.items(): - kwargs.setdefault('headers', {}).setdefault(k, v) - - if self.global_request_id is not None: - kwargs.setdefault('headers', {}).setdefault( - "X-OpenStack-Request-ID", self.global_request_id) - - return self.session.request(url, method, **kwargs) - - def get_token(self, auth=None): - """Return a token as provided by the auth plugin. - - :param auth: The auth plugin to use for token. Overrides the plugin - on the session. (optional) - :type auth: keystoneauth1.plugin.BaseAuthPlugin - - :raises keystoneauth1.exceptions.auth.AuthorizationFailure: if a new - token fetch fails. - - :returns: A valid token. - :rtype: :class:`str` - """ - return self.session.get_token(auth or self.auth) - - def get_endpoint(self, auth=None, **kwargs): - """Get an endpoint as provided by the auth plugin. - - :param auth: The auth plugin to use for token. Overrides the plugin on - the session. (optional) - :type auth: keystoneauth1.plugin.BaseAuthPlugin - - :raises keystoneauth1.exceptions.auth_plugins.MissingAuthPlugin: if a - plugin is not available. - - :returns: An endpoint if available or None. - :rtype: :class:`str` - """ - if self.endpoint_override: - return self.endpoint_override - - self._set_endpoint_filter_kwargs(kwargs) - return self.session.get_endpoint(auth or self.auth, **kwargs) - - def get_endpoint_data(self, auth=None): - """Get the endpoint data for this Adapter's endpoint. - - :param auth: The auth plugin to use for token. Overrides the plugin on - the session. (optional) - :type auth: keystoneauth1.plugin.BaseAuthPlugin - - :raises keystoneauth1.exceptions.auth_plugins.MissingAuthPlugin: if a - plugin is not available. - :raises TypeError: If arguments are invalid - - :returns: Endpoint data if available or None. - :rtype: keystoneauth1.discover.EndpointData - """ - kwargs = {} - self._set_endpoint_filter_kwargs(kwargs) - if self.endpoint_override: - kwargs['endpoint_override'] = self.endpoint_override - - return self.session.get_endpoint_data(auth or self.auth, **kwargs) - - def invalidate(self, auth=None): - """Invalidate an authentication plugin.""" - return self.session.invalidate(auth or self.auth) - - def get_user_id(self, auth=None): - """Return the authenticated user_id as provided by the auth plugin. - - :param auth: The auth plugin to use for token. Overrides the plugin - on the session. (optional) - :type auth: keystoneauth1.plugin.BaseAuthPlugin - - :raises keystoneauth1.exceptions.auth.AuthorizationFailure: - if a new token fetch fails. - :raises keystoneauth1.exceptions.auth_plugins.MissingAuthPlugin: - if a plugin is not available. - - :returns: Current `user_id` or None if not supported by plugin. - :rtype: :class:`str` - """ - return self.session.get_user_id(auth or self.auth) - - def get_project_id(self, auth=None): - """Return the authenticated project_id as provided by the auth plugin. - - :param auth: The auth plugin to use for token. Overrides the plugin - on the session. (optional) - :type auth: keystoneauth1.plugin.BaseAuthPlugin - - :raises keystoneauth1.exceptions.auth.AuthorizationFailure: - if a new token fetch fails. - :raises keystoneauth1.exceptions.auth_plugins.MissingAuthPlugin: - if a plugin is not available. - - :returns: Current `project_id` or None if not supported by plugin. - :rtype: :class:`str` - """ - return self.session.get_project_id(auth or self.auth) - - def get(self, url, **kwargs): - return self.request(url, 'GET', **kwargs) - - def head(self, url, **kwargs): - return self.request(url, 'HEAD', **kwargs) - - def post(self, url, **kwargs): - return self.request(url, 'POST', **kwargs) - - def put(self, url, **kwargs): - return self.request(url, 'PUT', **kwargs) - - def patch(self, url, **kwargs): - return self.request(url, 'PATCH', **kwargs) - - def delete(self, url, **kwargs): - return self.request(url, 'DELETE', **kwargs) - - # TODO(efried): Move this to loading.adapter.Adapter - @classmethod - def register_argparse_arguments(cls, parser, service_type=None): - """Attach arguments to a given argparse Parser for Adapters. - - :param parser: The argparse parser to attach options to. - :type parser: argparse.ArgumentParser - :param str service_type: Default service_type value. (optional) - """ - adapter_group = parser.add_argument_group( - 'Service Options', - 'Options controlling the specialization of the API' - ' Connection from information found in the catalog') - - adapter_group.add_argument( - '--os-service-type', - metavar='', - default=os.environ.get('OS_SERVICE_TYPE', service_type), - help='Service type to request from the catalog') - - adapter_group.add_argument( - '--os-service-name', - metavar='', - default=os.environ.get('OS_SERVICE_NAME', None), - help='Service name to request from the catalog') - - adapter_group.add_argument( - '--os-interface', - metavar='', - default=os.environ.get('OS_INTERFACE', 'public'), - help='API Interface to use [public, internal, admin]') - - adapter_group.add_argument( - '--os-region-name', - metavar='', - default=os.environ.get('OS_REGION_NAME', None), - help='Region of the cloud to use') - - adapter_group.add_argument( - '--os-endpoint-override', - metavar='', - default=os.environ.get('OS_ENDPOINT_OVERRIDE', None), - help='Endpoint to use instead of the endpoint in the catalog') - - adapter_group.add_argument( - '--os-api-version', - metavar='', - default=os.environ.get('OS_API_VERSION', None), - help='Which version of the service API to use') - - # TODO(efried): Move this to loading.adapter.Adapter - @classmethod - def register_service_argparse_arguments(cls, parser, service_type): - """Attach arguments to a given argparse Parser for Adapters. - - :param parser: The argparse parser to attach options to. - :type parser: argparse.ArgumentParser - :param str service_type: Name of a service to generate additional - arguments for. - """ - service_env = service_type.upper().replace('-', '_') - adapter_group = parser.add_argument_group( - '{service_type} Service Options'.format( - service_type=service_type.title()), - 'Options controlling the specialization of the {service_type}' - ' API Connection from information found in the catalog'.format( - service_type=service_type.title())) - - adapter_group.add_argument( - '--os-{service_type}-service-type'.format( - service_type=service_type), - metavar='', - default=os.environ.get( - 'OS_{service_type}_SERVICE_TYPE'.format( - service_type=service_env), None), - help=('Service type to request from the catalog for the' - ' {service_type} service'.format( - service_type=service_type))) - - adapter_group.add_argument( - '--os-{service_type}-service-name'.format( - service_type=service_type), - metavar='', - default=os.environ.get( - 'OS_{service_type}_SERVICE_NAME'.format( - service_type=service_env), None), - help=('Service name to request from the catalog for the' - ' {service_type} service'.format( - service_type=service_type))) - - adapter_group.add_argument( - '--os-{service_type}-interface'.format( - service_type=service_type), - metavar='', - default=os.environ.get( - 'OS_{service_type}_INTERFACE'.format( - service_type=service_env), None), - help=('API Interface to use for the {service_type} service' - ' [public, internal, admin]'.format( - service_type=service_type))) - - adapter_group.add_argument( - '--os-{service_type}-api-version'.format( - service_type=service_type), - metavar='', - default=os.environ.get( - 'OS_{service_type}_API_VERSION'.format( - service_type=service_env), None), - help=('Which version of the service API to use for' - ' the {service_type} service'.format( - service_type=service_type))) - - adapter_group.add_argument( - '--os-{service_type}-endpoint-override'.format( - service_type=service_type), - metavar='', - default=os.environ.get( - 'OS_{service_type}_ENDPOINT_OVERRIDE'.format( - service_type=service_env), None), - help=('Endpoint to use for the {service_type} service' - ' instead of the endpoint in the catalog'.format( - service_type=service_type))) - - -class LegacyJsonAdapter(Adapter): - """Make something that looks like an old HTTPClient. - - A common case when using an adapter is that we want an interface similar to - the HTTPClients of old which returned the body as JSON as well. - - You probably don't want this if you are starting from scratch. - """ - - def request(self, *args, **kwargs): - headers = kwargs.setdefault('headers', {}) - headers.setdefault('Accept', 'application/json') - - try: - kwargs['json'] = kwargs.pop('body') - except KeyError: - pass - - resp = super(LegacyJsonAdapter, self).request(*args, **kwargs) - - try: - body = resp.json() - except ValueError: - body = None - - return resp, body - - -# TODO(efried): Deprecate this in favor of -# loading.adapter.register_argparse_arguments -def register_adapter_argparse_arguments(*args, **kwargs): - return Adapter.register_argparse_arguments(*args, **kwargs) - - -# TODO(efried): Deprecate this in favor of -# loading.adapter.register_service_argparse_arguments -def register_service_adapter_argparse_arguments(*args, **kwargs): - return Adapter.register_service_argparse_arguments(*args, **kwargs) diff --git a/keystoneauth1/discover.py b/keystoneauth1/discover.py deleted file mode 100644 index 0b2cba2..0000000 --- a/keystoneauth1/discover.py +++ /dev/null @@ -1,1195 +0,0 @@ -# Licensed under the Apache License, Version 2.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. - -"""The passive components to version discovery. - -The Discover object in discover.py contains functions that can create objects -on your behalf. These functions are not usable from within the keystoneauth1 -library because you will get dependency resolution issues. - -The Discover object in this file provides the querying components of Discovery. -This includes functions like url_for which allow you to retrieve URLs and the -raw data specified in version discovery responses. -""" - -import copy -import re - -from positional import positional -import six -from six.moves import urllib - -from keystoneauth1 import _utils as utils -from keystoneauth1 import exceptions - - -_LOGGER = utils.get_logger(__name__) - -LATEST = float('inf') - - -def _str_or_latest(val): - """Convert val to a string, handling LATEST => 'latest'. - - :param val: An int or the special value LATEST. - :return: A string representation of val. If val was LATEST, the return is - 'latest'. - """ - return 'latest' if val == LATEST else str(val) - - -def _int_or_latest(val): - """Convert val to an int or the special value LATEST. - - :param val: An int()-able, or the string 'latest', or the special value - LATEST. - :return: An int, or the special value LATEST - """ - return LATEST if val == 'latest' or val == LATEST else int(val) - - -@positional() -def get_version_data(session, url, authenticated=None): - """Retrieve raw version data from a url. - - The return is a list of dicts of the form:: - - [{ - 'status': 'STABLE', - 'id': 'v2.3', - 'links': [ - { - 'href': 'http://network.example.com/v2.3', - 'rel': 'self', - }, - { - 'href': 'http://network.example.com/', - 'rel': 'collection', - }, - ], - 'min_version': '2.0', - 'max_version': '2.7', - }, - ..., - ] - - Note: - The maximum microversion may be specified by `max_version` or `version`, - the former superseding the latter. - All `*version` keys are optional. - Other keys and 'links' entries are permitted, but ignored. - - :param session: A Session object that can be used for communication. - :type session: keystoneauth1.session.Session - :param string url: Endpoint or discovery URL from which to retrieve data. - :param bool authenticated: Include a token in the discovery call. - (optional) Defaults to None. - :return: A list of dicts containing version information. - :rtype: list(dict) - """ - headers = {'Accept': 'application/json'} - - resp = session.get(url, headers=headers, authenticated=authenticated) - - try: - body_resp = resp.json() - except ValueError: - pass - else: - # In the event of querying a root URL we will get back a list of - # available versions. - try: - return body_resp['versions']['values'] - except (KeyError, TypeError): - pass - - # Most servers don't have a 'values' element so accept a simple - # versions dict if available. - try: - return body_resp['versions'] - except KeyError: - pass - - # Otherwise if we query an endpoint like /v2.0 then we will get back - # just the one available version. - try: - return [body_resp['version']] - except KeyError: - pass - - err_text = resp.text[:50] + '...' if len(resp.text) > 50 else resp.text - raise exceptions.DiscoveryFailure('Invalid Response - Bad version data ' - 'returned: %s' % err_text) - - -def normalize_version_number(version): - """Turn a version representation into a tuple. - - Examples: - - The following all produce a return value of (1, 0):: - - 1, '1', 'v1', [1], (1,), ['1'], 1.0, '1.0', 'v1.0', (1, 0) - - The following all produce a return value of (1, 20, 3):: - - 'v1.20.3', '1.20.3', (1, 20, 3), ['1', '20', '3'] - - The following all produce a return value of (LATEST, LATEST):: - - 'latest', 'vlatest', ('latest', 'latest'), (LATEST, LATEST) - - The following all produce a return value of (2, LATEST):: - - '2.latest', 'v2.latest', (2, LATEST), ('2', 'latest') - - :param version: A version specifier in any of the following forms: - String, possibly prefixed with 'v', containing one or more numbers - *or* the string 'latest', separated by periods. Examples: 'v1', - 'v1.2', '1.2.3', '123', 'latest', '1.latest', 'v1.latest'. - Integer. This will be assumed to be the major version, with a minor - version of 0. - Float. The integer part is assumed to be the major version; the - decimal part the minor version. - Non-string iterable comprising integers, integer strings, the string - 'latest', or the special value LATEST. - Examples: (1,), [1, 2], ('12', '34', '56'), (LATEST,), (2, 'latest') - :return: A tuple of len >= 2 comprising integers and/or LATEST. - :raises TypeError: If the input version cannot be interpreted. - """ - # Copy the input var so the error presents the original value - ver = version - - # If it's a non-string iterable, turn it into a string for subsequent - # processing. This ensures at least 1 decimal point if e.g. [1] is given. - if not isinstance(ver, six.string_types): - try: - ver = '.'.join(map(_str_or_latest, ver)) - except TypeError: - # Not an iterable - pass - - # If it's a numeric or an integer as a string then normalize it to a - # float string. This ensures 1 decimal point. - # If it's a float as a string, don't do that, the split/map below will do - # what we want. (Otherwise, we wind up with 3.20 -> (3, 2)) - if isinstance(ver, six.string_types): - # trim the v from a 'v2.0' or similar - ver = ver.lstrip('v') - try: - # If version is a pure int, like '1' or '200' this will produce - # a stringified version with a .0 added. If it's any other number, - # such as '1.1' - int(version) raises an Exception - ver = str(float(int(ver))) - except ValueError: - pass - - # If it's an int or float, turn it into a float string - elif isinstance(ver, (int, float)): - ver = _str_or_latest(float(ver)) - - # At this point, we should either have a string that contains numbers with - # at least one decimal point, or something decidedly else. - - # if it's a string from above break it on . - try: - ver = ver.split('.') - except AttributeError: - # Not a string - pass - - # Handle special case variants of just 'latest' - if ver == 'latest' or tuple(ver) == ('latest',): - return LATEST, LATEST - - # It's either an interable, or something else that makes us sad. - try: - return tuple(map(_int_or_latest, ver)) - except (TypeError, ValueError): - pass - - raise TypeError('Invalid version specified: %s' % version) - - -def _normalize_version_args(version, min_version, max_version): - if version and (min_version or max_version): - raise ValueError( - "version is mutually exclusive with min_version and max_version") - - if version: - # Explode this into min_version and max_version - min_version = normalize_version_number(version) - max_version = (min_version[0], LATEST) - return min_version, max_version - - if min_version == 'latest': - if max_version not in (None, 'latest'): - raise ValueError( - "min_version is 'latest' and max_version is {max_version}" - " but is only allowed to be 'latest' or None".format( - max_version=max_version)) - max_version = 'latest' - - # Normalize e.g. empty string to None - min_version = min_version or None - max_version = max_version or None - - if min_version: - min_version = normalize_version_number(min_version) - # If min_version was specified but max_version was not, max is latest. - max_version = normalize_version_number(max_version or 'latest') - - # NOTE(efried): We should be doing this instead: - # max_version = normalize_version_number(max_version or 'latest') - # However, see first NOTE(jamielennox) in EndpointData._set_version_info. - if max_version: - max_version = normalize_version_number(max_version) - - if None not in (min_version, max_version) and max_version < min_version: - raise ValueError("min_version cannot be greater than max_version") - - return min_version, max_version - - -def version_to_string(version): - """Turn a version tuple into a string. - - :param tuple version: A version represented as a tuple of ints. As a - special case, a tuple member may be LATEST, which - translates to 'latest'. - :return: A version represented as a period-delimited string. - """ - # Special case - if all(ver == LATEST for ver in version): - return 'latest' - - return ".".join(map(_str_or_latest, version)) - - -def _version_between(min_version, max_version, candidate): - """Determine whether a candidate version is within a specified range. - - :param min_version: Normalized lower bound. May be None. May be - (LATEST, LATEST). - :param max_version: Normalized upper bound. May be None. May be - (LATEST, LATEST). - :param candidate: Normalized candidate version to test. May not be None. - :return: True if candidate is between min_version and max_version; False - otherwise. - :raises ValueError: If candidate is None or the input is not properly - normalized. - """ - def is_normalized(ver): - return normalize_version_number(ver) == ver - - # A version can't be between a range that doesn't exist - if not min_version and not max_version: - return False - - if candidate is None: - raise ValueError("candidate cannot be None.") - - if min_version is not None and not is_normalized(min_version): - raise ValueError("min_version is not normalized.") - if max_version is not None and not is_normalized(max_version): - raise ValueError("max_version is not normalized.") - if not is_normalized(candidate): - raise ValueError("candidate is not normalized.") - # This is only possible if args weren't run through _normalize_version_args - if max_version is None and min_version is not None: - raise ValueError("Can't use None as an upper bound.") - - # If the candidate is less than the min_version, it's - # not a match. None works here. - if min_version is not None and candidate < min_version: - return False - - if max_version is not None and candidate > max_version: - return False - - return True - - -def version_match(required, candidate): - """Test that an available version satisfies the required version. - - To be suitable a version must be of the same major version as required - and be at least a match in minor/patch level. - - eg. 3.3 is a match for a required 3.1 but 4.1 is not. - - :param tuple required: the version that must be met. - :param tuple candidate: the version to test against required. - - :returns: True if candidate is suitable False otherwise. - :rtype: bool - """ - # major versions must be the same (e.g. even though v2 is a lower - # version than v3 we can't use it if v2 was requested) - if candidate[0] != required[0]: - return False - - # prevent selecting a minor version less than what is required - if candidate < required: - return False - - return True - - -def _latest_soft_match(required, candidate): - if not required: - return False - - if LATEST not in required: - return False - - if all(part == LATEST for part in required): - return True - - if required[0] == candidate[0] and required[1] == LATEST: - return True - - # TODO(efried): Do we need to handle >2-part version numbers here? - - return False - - -def _combine_relative_url(discovery_url, version_url): - # NOTE(jamielennox): urllib.parse.urljoin allows the url to be relative - # or even protocol-less. The additional trailing '/' makes urljoin respect - # the current path as canonical even if the url doesn't include it. for - # example a "v2" path from http://host/admin should resolve as - # http://host/admin/v2 where it would otherwise be host/v2. This has no - # effect on absolute urls. - url = urllib.parse.urljoin(discovery_url.rstrip('/') + '/', version_url) - - # Parse and recombine the result to squish double //'s from the above - return urllib.parse.urlparse(url).geturl() - - -class Discover(object): - - CURRENT_STATUSES = ('stable', 'current', 'supported') - DEPRECATED_STATUSES = ('deprecated',) - EXPERIMENTAL_STATUSES = ('experimental',) - - @positional() - def __init__(self, session, url, authenticated=None): - self._url = url - self._data = get_version_data(session, url, - authenticated=authenticated) - - def raw_version_data(self, allow_experimental=False, - allow_deprecated=True, allow_unknown=False): - """Get raw version information from URL. - - Raw data indicates that only minimal validation processing is performed - on the data, so what is returned here will be the data in the same - format it was received from the endpoint. - - :param bool allow_experimental: Allow experimental version endpoints. - :param bool allow_deprecated: Allow deprecated version endpoints. - :param bool allow_unknown: Allow endpoints with an unrecognised status. - - :returns: The endpoints returned from the server that match the - criteria. - :rtype: list - """ - versions = [] - for v in self._data: - try: - status = v['status'] - except KeyError: - _LOGGER.warning('Skipping over invalid version data. ' - 'No stability status in version.') - continue - - status = status.lower() - - if status in self.CURRENT_STATUSES: - versions.append(v) - elif status in self.DEPRECATED_STATUSES: - if allow_deprecated: - versions.append(v) - elif status in self.EXPERIMENTAL_STATUSES: - if allow_experimental: - versions.append(v) - elif allow_unknown: - versions.append(v) - - return versions - - @positional() - def version_data(self, reverse=False, **kwargs): - """Get normalized version data. - - Return version data in a structured way. - - :param bool reverse: Reverse the list. reverse=true will mean the - returned list is sorted from newest to oldest - version. - :returns: A list of version data dictionaries sorted by version number. - Each data element in the returned list is a dictionary - consisting of: - - :version tuple: The normalized version of the endpoint. - :url str: The url for the endpoint. - :collection: The URL for the discovery document. May be None. - :min_microversion: The minimum microversion supported by the - endpoint. May be None. - :max_microversion: The maximum microversion supported by the - endpoint. May be None. - :raw_status str: The status as provided by the server - :rtype: list(dict) - """ - data = self.raw_version_data(**kwargs) - versions = [] - - for v in data: - try: - version_str = v['id'] - except KeyError: - _LOGGER.info('Skipping invalid version data. Missing ID.') - continue - - try: - links = v['links'] - except KeyError: - _LOGGER.info('Skipping invalid version data. Missing links') - continue - - version_number = normalize_version_number(version_str) - - # collect microversion information - # NOTE(efried): Some existing discovery documents (e.g. from nova - # v2.0 in the pike release) include *version keys with "" (empty - # string) values, expecting them to be treated the same as if the - # keys were absent. - min_microversion = v.get('min_version') or None - if min_microversion: - min_microversion = normalize_version_number(min_microversion) - max_microversion = v.get('max_version') - if not max_microversion: - max_microversion = v.get('version') or None - if max_microversion: - max_microversion = normalize_version_number(max_microversion) - next_min_version = v.get('next_min_version') or None - if next_min_version: - next_min_version = normalize_version_number(next_min_version) - not_before = v.get('not_before') or None - - self_url = None - collection_url = None - for link in links: - try: - rel = link['rel'] - url = _combine_relative_url(self._url, link['href']) - except (KeyError, TypeError): - _LOGGER.info('Skipping invalid version link. ' - 'Missing link URL or relationship.') - continue - - if rel.lower() == 'self': - self_url = url - elif rel.lower() == 'collection': - collection_url = url - if not self_url: - _LOGGER.info('Skipping invalid version data. ' - 'Missing link to endpoint.') - continue - - versions.append({'version': version_number, - 'url': self_url, - 'collection': collection_url, - 'min_microversion': min_microversion, - 'max_microversion': max_microversion, - 'next_min_version': next_min_version, - 'not_before': not_before, - 'raw_status': v['status']}) - - versions.sort(key=lambda v: v['version'], reverse=reverse) - return versions - - def data_for(self, version, **kwargs): - """Return endpoint data for a version. - - NOTE: This method raises a TypeError if version is None. It is - kept for backwards compatability. New code should use - versioned_data_for instead. - - :param tuple version: The version is always a minimum version in the - same major release as there should be no compatibility issues with - using a version newer than the one asked for. - - :returns: the endpoint data for a URL that matches the required version - (the format is described in version_data) or None if no - match. - :rtype: dict - """ - version = normalize_version_number(version) - - for data in self.version_data(reverse=True, **kwargs): - # Since the data is reversed, the latest version is first. If - # latest was requested, return it. - if _latest_soft_match(version, data['version']): - return data - if version_match(version, data['version']): - return data - - return None - - def url_for(self, version, **kwargs): - """Get the endpoint url for a version. - - NOTE: This method raises a TypeError if version is None. It is - kept for backwards compatability. New code should use - versioned_url_for instead. - - :param tuple version: The version is always a minimum version in the - same major release as there should be no compatibility issues with - using a version newer than the one asked for. - - :returns: The url for the specified version or None if no match. - :rtype: str - """ - data = self.data_for(version, **kwargs) - return data['url'] if data else None - - def versioned_data_for(self, url=None, - min_version=None, max_version=None, - **kwargs): - """Return endpoint data for the service at a url. - - min_version and max_version can be given either as strings or tuples. - - :param string url: If url is given, the data will be returned for the - endpoint data that has a self link matching the url. - :param min_version: The minimum endpoint version that is acceptable. If - min_version is given with no max_version it is as if max version is - 'latest'. If min_version is 'latest', max_version may only be - 'latest' or None. - :param max_version: The maximum endpoint version that is acceptable. If - min_version is given with no max_version it is as if max version is - 'latest'. If min_version is 'latest', max_version may only be - 'latest' or None. - - :returns: the endpoint data for a URL that matches the required version - (the format is described in version_data) or None if no - match. - :rtype: dict - """ - min_version, max_version = _normalize_version_args( - None, min_version, max_version) - no_version = not max_version and not min_version - - version_data = self.version_data(reverse=True, **kwargs) - - # If we don't have to check a min_version, we can short - # circuit anything else - if (max_version == (LATEST, LATEST) and - (not min_version or min_version == (LATEST, LATEST))): - # because we reverse we can just take the first entry - return version_data[0] - - if url: - url = url.rstrip('/') + '/' - - if no_version and not url: - # because we reverse we can just take the first entry - return version_data[0] - - # Version data is in order from highest to lowest, so we return - # the first matching entry - for data in version_data: - if url and data['url'] and data['url'].rstrip('/') + '/' == url: - return data - if _latest_soft_match(min_version, data['version']): - return data - if _version_between(min_version, max_version, data['version']): - return data - - # If there is no version requested and we could not find a matching - # url in the discovery doc, that means we've got an unversioned - # endpoint in the catalog and the user is requesting version data - # so that they know what version they got. We can return the first - # entry from version_data, because the user hasn't requested anything - # different. - if no_version and url: - return version_data[0] - - # We couldn't find a match. - return None - - def versioned_url_for(self, min_version=None, max_version=None, **kwargs): - """Get the endpoint url for a version. - - min_version and max_version can be given either as strings or tuples. - - :param min_version: The minimum version that is acceptable. If - min_version is given with no max_version it is as if max version - is 'latest'. - :param max_version: The maximum version that is acceptable. If - min_version is given with no max_version it is as if max version is - 'latest'. - - :returns: The url for the specified version or None if no match. - :rtype: str - """ - data = self.versioned_data_for(min_version=min_version, - max_version=max_version, **kwargs) - return data['url'] if data else None - - -class EndpointData(object): - """Normalized information about a discovered endpoint. - - Contains url, version, microversion, interface and region information. - This is essentially the data contained in the catalog and the version - discovery documents about an endpoint that is used to select the endpoint - desired by the user. It is returned so that a user can know which qualities - a discovered endpoint had, in case their request allowed for a range of - possibilities. - """ - - @positional() - def __init__(self, - catalog_url=None, - service_url=None, - service_type=None, - service_name=None, - service_id=None, - region_name=None, - interface=None, - endpoint_id=None, - raw_endpoint=None, - api_version=None, - major_version=None, - min_microversion=None, - max_microversion=None, - next_min_version=None, - not_before=None): - self.catalog_url = catalog_url - self.service_url = service_url - self.service_type = service_type - self.service_name = service_name - self.service_id = service_id - self.interface = interface - self.region_name = region_name - self.endpoint_id = endpoint_id - self.raw_endpoint = raw_endpoint - self.api_version = api_version - self.major_version = major_version - self.min_microversion = min_microversion - self.max_microversion = max_microversion - self.next_min_version = next_min_version - self.not_before = not_before - self._saved_project_id = None - self._catalog_matches_version = False - self._catalog_matches_exactly = False - self._disc = None - - def __copy__(self): - """Return a new EndpointData based on this one.""" - new_data = EndpointData( - catalog_url=self.catalog_url, - service_url=self.service_url, - service_type=self.service_type, - service_name=self.service_name, - service_id=self.service_id, - region_name=self.region_name, - interface=self.interface, - endpoint_id=self.endpoint_id, - raw_endpoint=self.raw_endpoint, - api_version=self.api_version, - major_version=self.major_version, - min_microversion=self.min_microversion, - max_microversion=self.max_microversion, - next_min_version=self.next_min_version, - not_before=self.not_before) - # Save cached discovery object - but we don't want to - # actually provide a constructor argument - new_data._disc = self._disc - new_data._saved_project_id = self._saved_project_id - return new_data - - @property - def url(self): - return self.service_url or self.catalog_url - - @positional(3) - def get_versioned_data(self, session, allow=None, cache=None, - allow_version_hack=True, project_id=None, - discover_versions=True, - min_version=None, max_version=None): - """Run version discovery for the service described. - - Performs Version Discovery and returns a new EndpointData object with - information found. - - min_version and max_version can be given either as strings or tuples. - - :param session: A session object that can be used for communication. - :type session: keystoneauth1.session.Session - :param dict allow: Extra filters to pass when discovering API - versions. (optional) - :param dict cache: A dict to be used for caching results in - addition to caching them on the Session. - (optional) - :param bool allow_version_hack: Allow keystoneauth to hack up catalog - URLS to support older schemes. - (optional, default True) - :param string project_id: ID of the currently scoped project. Used for - removing project_id components of URLs from - the catalog. (optional) - :param bool discover_versions: Whether to get version metadata from - the version discovery document even - if it's not neccessary to fulfill the - major version request. (optional, - defaults to True) - :param min_version: The minimum version that is acceptable. If - min_version is given with no max_version it is as - if max version is 'latest'. - :param max_version: The maximum version that is acceptable. If - min_version is given with no max_version it is as - if max version is 'latest'. - - :returns: A new EndpointData with the requested versioned data. - :rtype: :py:class:`keystoneauth1.discover.EndpointData` - :raises keystoneauth1.exceptions.discovery.DiscoveryFailure: If the - appropriate versioned data - could not be discovered. - """ - min_version, max_version = _normalize_version_args( - None, min_version, max_version) - - if not allow: - allow = {} - - # This method should always return a new EndpointData - new_data = copy.copy(self) - - new_data._set_version_info( - session=session, allow=allow, cache=cache, - allow_version_hack=allow_version_hack, project_id=project_id, - discover_versions=discover_versions, min_version=min_version, - max_version=max_version) - return new_data - - def _set_version_info(self, session, allow=None, cache=None, - allow_version_hack=True, project_id=None, - discover_versions=False, - min_version=None, max_version=None): - match_url = None - - no_version = not max_version and not min_version - if no_version and not discover_versions: - # NOTE(jamielennox): This may not be the best thing to default to - # but is here for backwards compatibility. It may be worth - # defaulting to the most recent version. - return - elif no_version and discover_versions: - # We want to run discovery, but we don't want to find different - # endpoints than what's in the catalog - allow_version_hack = False - match_url = self.url - - if project_id: - self.project_id = project_id - discovered_data = None - # Maybe we've run discovery in the past and have a document that can - # satisfy the request without further work - if self._disc: - discovered_data = self._disc.versioned_data_for( - min_version=min_version, max_version=max_version, - url=match_url, **allow) - if not discovered_data: - self._run_discovery( - session=session, cache=cache, - min_version=min_version, max_version=max_version, - project_id=project_id, allow_version_hack=allow_version_hack, - discover_versions=discover_versions) - if not self._disc: - return - discovered_data = self._disc.versioned_data_for( - min_version=min_version, max_version=max_version, - url=match_url, **allow) - - if not discovered_data: - if min_version and not max_version: - raise exceptions.DiscoveryFailure( - "Minimum version {min_version} was not found".format( - min_version=version_to_string(min_version))) - elif max_version and not min_version: - raise exceptions.DiscoveryFailure( - "Maximum version {max_version} was not found".format( - max_version=version_to_string(max_version))) - elif min_version and max_version: - raise exceptions.DiscoveryFailure( - "No version found between {min_version}" - " and {max_version}".format( - min_version=version_to_string(min_version), - max_version=version_to_string(max_version))) - - self.min_microversion = discovered_data['min_microversion'] - self.max_microversion = discovered_data['max_microversion'] - self.next_min_version = discovered_data['next_min_version'] - self.not_before = discovered_data['not_before'] - - # TODO(mordred): these next two things should be done by Discover - # in versioned_data_for. - discovered_url = discovered_data['url'] - - # NOTE(jamielennox): urljoin allows the url to be relative or even - # protocol-less. The additional trailing '/' make urljoin respect - # the current path as canonical even if the url doesn't include it. - # for example a "v2" path from http://host/admin should resolve as - # http://host/admin/v2 where it would otherwise be host/v2. - # This has no effect on absolute urls returned from url_for. - url = urllib.parse.urljoin(self._disc._url.rstrip('/') + '/', - discovered_url) - - # If we had to pop a project_id from the catalog_url, put it back on - if self._saved_project_id: - url = urllib.parse.urljoin(url.rstrip('/') + '/', - self._saved_project_id) - self.service_url = url - - @positional(1) - def _run_discovery(self, session, cache, min_version, max_version, - project_id, allow_version_hack, discover_versions): - tried = set() - - for vers_url in self._get_discovery_url_choices( - project_id=project_id, - allow_version_hack=allow_version_hack, - min_version=min_version, - max_version=max_version): - - if self._catalog_matches_exactly and not discover_versions: - # The version we started with is correct, and we don't want - # new data - return - - if vers_url in tried: - continue - tried.add(vers_url) - - try: - self._disc = get_discovery( - session, vers_url, - cache=cache, - authenticated=False) - break - except (exceptions.DiscoveryFailure, - exceptions.HttpError, - exceptions.ConnectionError): - continue - if not self._disc: - # We couldn't find a version discovery document anywhere. - if self._catalog_matches_version: - # But - the version in the catalog is fine. - self.service_url = self.catalog_url - return - - # NOTE(jamielennox): The logic here is required for backwards - # compatibility. By itself it is not ideal. - if allow_version_hack: - # NOTE(jamielennox): If we can't contact the server we - # fall back to just returning the URL from the catalog. This - # is backwards compatible behaviour and used when there is no - # other choice. Realistically if you have provided a version - # you should be able to rely on that version being returned or - # the request failing. - _LOGGER.warning( - 'Failed to contact the endpoint at %s for ' - 'discovery. Fallback to using that endpoint as ' - 'the base url.', self.url) - return - - else: - # NOTE(jamielennox): If you've said no to allow_version_hack - # and we can't determine the actual URL this is a failure - # because we are specifying that the deployment must be up to - # date enough to properly specify a version and keystoneauth - # can't deliver. - raise exceptions.DiscoveryFailure( - "Version requested but version discovery document was not" - " found and allow_version_hack was False") - - def _get_discovery_url_choices( - self, project_id=None, allow_version_hack=True, - min_version=None, max_version=None): - """Find potential locations for version discovery URLs. - - min_version and max_version are already normalized, so will either be - None or a tuple. - """ - url = urllib.parse.urlparse(self.url) - url_parts = url.path.split('/') - - # First, check to see if the catalog url ends with a project id - # We need to remove it and save it for later if it does - if project_id and url_parts[-1].endswith(project_id): - self._saved_project_id = url_parts.pop() - elif not project_id: - # Peek to see if -2 is a version. If so, -1 is a project_id, - # even if we don't know that at this point in the call stack - try: - normalize_version_number(url_parts[-2]) - self._saved_project_id = url_parts.pop() - except (IndexError, TypeError): - pass - - catalog_discovery = versioned_discovery = None - - # Next, check to see if the url indicates a version and if that - # version either matches our version request or is withing the - # range requested. If so, we can start by trying the given url - # as it has a high potential for success. - try: - url_version = normalize_version_number(url_parts[-1]) - versioned_discovery = urllib.parse.ParseResult( - url.scheme, - url.netloc, - '/'.join(url_parts), - url.params, - url.query, - url.fragment).geturl() - except TypeError: - pass - else: - is_between = _version_between(min_version, max_version, - url_version) - exact_match = (is_between and max_version and - max_version[0] == url_version[0]) - high_match = (is_between and max_version and - max_version[1] != LATEST and - version_match(max_version, url_version)) - if exact_match or is_between: - self._catalog_matches_version = True - self._catalog_matches_exactly = exact_match - # The endpoint from the catalog matches the version request - # We construct a URL minus any project_id, but we don't - # return it just yet. It's a good option, but unless we - # have an exact match or match the max requested, we want - # to try for an unversioned endpoint first. - catalog_discovery = urllib.parse.ParseResult( - url.scheme, - url.netloc, - '/'.join(url_parts), - url.params, - url.query, - url.fragment).geturl().rstrip('/') + '/' - - # If we found a viable catalog endpoint and it's - # an exact match or matches the max, go ahead and give - # it a go. - if catalog_discovery and (high_match or exact_match): - yield catalog_discovery - catalog_discovery = None - - url_parts.pop() - - if allow_version_hack: - # If there were projects or versions in the url they are now gone. - # That means we're left with what should be the unversioned url. - hacked_url = urllib.parse.ParseResult( - url.scheme, - url.netloc, - '/'.join(url_parts), - url.params, - url.query, - url.fragment).geturl() - # Since this is potentially us constructing a base URL from the - # versioned URL - we need to make sure it has a trailing /. But - # we only want to do that if we have built a new URL - not if - # we're using the one from the catalog - if hacked_url != self.catalog_url: - hacked_url = hacked_url.strip('/') + '/' - yield hacked_url - - # If we have a catalog discovery url, it either means we didn't - # return it earlier because it wasn't an exact enough match, or - # that we did and it failed. We don't double-request things when - # consuming this, so it's safe to return it here in case we didn't - # already return it. - if catalog_discovery: - yield catalog_discovery - - # NOTE(mordred): For backwards compatibility people might have - # added version hacks using the version hack system. The logic - # above should handle most cases, so by the time we get here it's - # most likely to be a no-op - yield self._get_catalog_discover_hack() - elif versioned_discovery and self._saved_project_id: - # We popped a project_id but are either avoiding version hacks - # or we didn't request a version. That means we still want to fetch - # the document from the "catalog url" - but the catalog url is has - # a project_id suffix so is likely not going to work for us. Try - # fetching from the project-less versioned endpoint. - yield versioned_discovery - - # As a final fallthrough case, return the actual unmodified url from - # the catalog. - yield self.catalog_url - - def _get_catalog_discover_hack(self): - """Apply the catalog hacks and figure out an unversioned endpoint. - - This function is internal to keystoneauth1. - - :returns: A url that has been transformed by the regex hacks that - match the service_type. - """ - return _VERSION_HACKS.get_discover_hack(self.service_type, self.url) - - -@positional() -def get_discovery(session, url, cache=None, authenticated=False): - """Return the discovery object for a URL. - - Check the session and the plugin cache to see if we have already - performed discovery on the URL and if so return it, otherwise create - a new discovery object, cache it and return it. - - NOTE: This function is expected to be used by keystoneauth and should not - be needed by users part of normal usage. A normal user should use - get_endpoint or get_endpoint_data on `keystoneauth.session.Session` or - endpoint_filters on `keystoneauth.session.Session` or - `keystoneauth.session.Session`. However, should the user need to perform - direct discovery for some reason, this function should be used so that - the discovery caching is used. - - :param session: A session object to discover with. - :type session: keystoneauth1.session.Session - :param str url: The url to lookup. - :param dict cache: - A dict to be used for caching results, in addition to caching them - on the Session. (optional) Defaults to None. - :param bool authenticated: - Include a token in the discovery call. (optional) Defaults to None, - which will use a token if an auth plugin is installed. - - :raises keystoneauth1.exceptions.discovery.DiscoveryFailure: - if for some reason the lookup fails. - :raises keystoneauth1.exceptions.http.HttpError: - An error from an invalid HTTP response. - - :returns: A discovery object with the results of looking up that URL. - :rtype: :py:class:`keystoneauth1.discover.Discovery` - """ - # There are between one and three different caches. The user may have - # passed one in. There is definitely one on the session, and there is - # one on the auth plugin if the Session has an auth plugin. - caches = [] - - # If a cache was passed in, check it first. - if cache is not None: - caches.append(cache) - - # If the session has a cache, check it second, since it could have been - # provided by the user at Session creation time. - if hasattr(session, '_discovery_cache'): - caches.append(session._discovery_cache) - - # Finally check the auth cache associated with the Session. - if session.auth and hasattr(session.auth, '_discovery_cache'): - caches.append(session.auth._discovery_cache) - - for cache in caches: - disc = cache.get(url) - - if disc: - break - else: - disc = Discover(session, url, authenticated=authenticated) - - # Whether we get one from fetching or from cache, set it in the - # caches. This assures that if we combine sessions and auth plugins - # that we don't make unnecesary calls. - if disc: - for cache in caches: - cache[url] = disc - - return disc - - -class _VersionHacks(object): - """A container to abstract the list of version hacks. - - This could be done as simply a dictionary but is abstracted like this to - make for easier testing. - """ - - def __init__(self): - self._discovery_data = {} - - def add_discover_hack(self, service_type, old, new=''): - """Add a new hack for a service type. - - :param str service_type: The service_type in the catalog. - :param re.RegexObject old: The pattern to use. - :param str new: What to replace the pattern with. - """ - hacks = self._discovery_data.setdefault(service_type, []) - hacks.append((old, new)) - - def get_discover_hack(self, service_type, url): - """Apply the catalog hacks and figure out an unversioned endpoint. - - :param str service_type: the service_type to look up. - :param str url: The original url that came from a service_catalog. - - :returns: Either the unversioned url or the one from the catalog - to try. - """ - for old, new in self._discovery_data.get(service_type, []): - new_string, number_of_subs_made = old.subn(new, url) - if number_of_subs_made > 0: - return new_string - - return url - - -_VERSION_HACKS = _VersionHacks() -_VERSION_HACKS.add_discover_hack('identity', re.compile('/v2.0/?$'), '/') - - -def add_catalog_discover_hack(service_type, old, new): - """Add a version removal rule for a particular service. - - Originally deployments of OpenStack would contain a versioned endpoint in - the catalog for different services. E.g. an identity service might look - like ``http://localhost:5000/v2.0``. This is a problem when we want to use - a different version like v3.0 as there is no way to tell where it is - located. We cannot simply change all service catalogs either so there must - be a way to handle the older style of catalog. - - This function adds a rule for a given service type that if part of the URL - matches a given regular expression in *old* then it will be replaced with - the *new* value. This will replace all instances of old with new. It should - therefore contain a regex anchor. - - For example the included rule states:: - - add_catalog_version_hack('identity', re.compile('/v2.0/?$'), '/') - - so if the catalog retrieves an *identity* URL that ends with /v2.0 or - /v2.0/ then it should replace it simply with / to fix the user's catalog. - - :param str service_type: The service type as defined in the catalog that - the rule will apply to. - :param re.RegexObject old: The regular expression to search for and replace - if found. - :param str new: The new string to replace the pattern with. - """ - _VERSION_HACKS.add_discover_hack(service_type, old, new) diff --git a/keystoneauth1/exceptions/__init__.py b/keystoneauth1/exceptions/__init__.py deleted file mode 100644 index e444926..0000000 --- a/keystoneauth1/exceptions/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -# Licensed under the Apache License, Version 2.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 keystoneauth1.exceptions.auth import * # noqa -from keystoneauth1.exceptions.auth_plugins import * # noqa -from keystoneauth1.exceptions.base import * # noqa -from keystoneauth1.exceptions.catalog import * # noqa -from keystoneauth1.exceptions.connection import * # noqa -from keystoneauth1.exceptions.discovery import * # noqa -from keystoneauth1.exceptions.http import * # noqa -from keystoneauth1.exceptions.oidc import * # noqa -from keystoneauth1.exceptions.response import * # noqa -from keystoneauth1.exceptions.service_providers import * # noqa diff --git a/keystoneauth1/exceptions/auth.py b/keystoneauth1/exceptions/auth.py deleted file mode 100644 index 99bea8d..0000000 --- a/keystoneauth1/exceptions/auth.py +++ /dev/null @@ -1,17 +0,0 @@ -# Licensed under the Apache License, Version 2.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 keystoneauth1.exceptions import base - - -class AuthorizationFailure(base.ClientException): - message = "Cannot authorize API client." diff --git a/keystoneauth1/exceptions/auth_plugins.py b/keystoneauth1/exceptions/auth_plugins.py deleted file mode 100644 index be3c8c1..0000000 --- a/keystoneauth1/exceptions/auth_plugins.py +++ /dev/null @@ -1,93 +0,0 @@ -# Licensed under the Apache License, Version 2.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 keystoneauth1.exceptions import base - - -__all__ = ('AuthPluginException', - 'MissingAuthPlugin', - 'NoMatchingPlugin', - 'UnsupportedParameters', - 'OptionError', - 'MissingRequiredOptions') - - -class AuthPluginException(base.ClientException): - message = "Unknown error with authentication plugins." - - -class MissingAuthPlugin(AuthPluginException): - message = "An authenticated request is required but no plugin available." - - -class NoMatchingPlugin(AuthPluginException): - """No auth plugins could be created from the parameters provided. - - :param str name: The name of the plugin that was attempted to load. - - .. py:attribute:: name - - The name of the plugin that was attempted to load. - """ - - def __init__(self, name): - self.name = name - msg = 'The plugin %s could not be found' % name - super(NoMatchingPlugin, self).__init__(msg) - - -class UnsupportedParameters(AuthPluginException): - """A parameter that was provided or returned is not supported. - - :param list(str) names: Names of the unsupported parameters. - - .. py:attribute:: names - - Names of the unsupported parameters. - """ - - def __init__(self, names): - self.names = names - - m = 'The following parameters were given that are unsupported: %s' - super(UnsupportedParameters, self).__init__(m % ', '.join(self.names)) - - -class OptionError(AuthPluginException): - """A requirement of this plugin loader was not met. - - This error can be raised by a specific plugin loader during the - load_from_options stage to indicate a parameter problem that can not be - handled by the generic options loader. - - The intention here is that a plugin can do checks like if a name parameter - is provided then a domain parameter must also be provided, but that Opt - checking doesn't handle. - """ - - -class MissingRequiredOptions(OptionError): - """One or more required options were not provided. - - :param list(keystoneauth1.loading.Opt) options: Missing options. - - .. py:attribute:: options - - List of the missing options. - """ - - def __init__(self, options): - self.options = options - - names = ", ".join(o.dest for o in options) - m = 'Auth plugin requires parameters which were not given: %s' - super(MissingRequiredOptions, self).__init__(m % names) diff --git a/keystoneauth1/exceptions/base.py b/keystoneauth1/exceptions/base.py deleted file mode 100644 index afb889b..0000000 --- a/keystoneauth1/exceptions/base.py +++ /dev/null @@ -1,24 +0,0 @@ -# Licensed under the Apache License, Version 2.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. - - -__all__ = ('ClientException',) - - -class ClientException(Exception): - """The base exception for everything to do with clients.""" - - message = "ClientException" - - def __init__(self, message=None): - self.message = message or self.message - super(ClientException, self).__init__(self.message) diff --git a/keystoneauth1/exceptions/catalog.py b/keystoneauth1/exceptions/catalog.py deleted file mode 100644 index 25b6005..0000000 --- a/keystoneauth1/exceptions/catalog.py +++ /dev/null @@ -1,30 +0,0 @@ -# Licensed under the Apache License, Version 2.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 keystoneauth1.exceptions import base - -__all__ = ('CatalogException', - 'EmptyCatalog', - 'EndpointNotFound') - - -class CatalogException(base.ClientException): - message = "Unknown error with service catalog." - - -class EndpointNotFound(CatalogException): - message = "Could not find requested endpoint in Service Catalog." - - -class EmptyCatalog(EndpointNotFound): - message = "The service catalog is empty." diff --git a/keystoneauth1/exceptions/connection.py b/keystoneauth1/exceptions/connection.py deleted file mode 100644 index e5de679..0000000 --- a/keystoneauth1/exceptions/connection.py +++ /dev/null @@ -1,51 +0,0 @@ -# Licensed under the Apache License, Version 2.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 keystoneauth1.exceptions import base - - -__all__ = ('ConnectionError', - 'ConnectTimeout', - 'ConnectFailure', - 'SSLError', - 'RetriableConnectionFailure', - 'UnknownConnectionError') - - -class RetriableConnectionFailure(Exception): - """A mixin class that implies you can retry the most recent request.""" - - pass - - -class ConnectionError(base.ClientException): - message = "Cannot connect to API service." - - -class ConnectTimeout(ConnectionError, RetriableConnectionFailure): - message = "Timed out connecting to service." - - -class ConnectFailure(ConnectionError, RetriableConnectionFailure): - message = "Connection failure that may be retried." - - -class SSLError(ConnectionError): - message = "An SSL error occurred." - - -class UnknownConnectionError(ConnectionError): - """An error was encountered but we don't know what it is.""" - - def __init__(self, msg, original): - super(UnknownConnectionError, self).__init__(msg) - self.original = original diff --git a/keystoneauth1/exceptions/discovery.py b/keystoneauth1/exceptions/discovery.py deleted file mode 100644 index 209009b..0000000 --- a/keystoneauth1/exceptions/discovery.py +++ /dev/null @@ -1,25 +0,0 @@ -# Licensed under the Apache License, Version 2.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 keystoneauth1.exceptions import base - - -__all__ = ('DiscoveryFailure', - 'VersionNotAvailable') - - -class DiscoveryFailure(base.ClientException): - message = "Discovery of client versions failed." - - -class VersionNotAvailable(DiscoveryFailure): - message = "Discovery failed. Requested version is not available." diff --git a/keystoneauth1/exceptions/http.py b/keystoneauth1/exceptions/http.py deleted file mode 100644 index f76afc2..0000000 --- a/keystoneauth1/exceptions/http.py +++ /dev/null @@ -1,428 +0,0 @@ -# Copyright 2010 Jacob Kaplan-Moss -# Copyright 2011 Nebula, Inc. -# Copyright 2013 Alessio Ababilov -# Copyright 2013 OpenStack Foundation -# 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. - -"""HTTP Exceptions used by keystoneauth1.""" - -import inspect -import sys - -from keystoneauth1.exceptions import base - - -__all__ = ('HttpError', - - 'HTTPClientError', - 'BadRequest', - 'Unauthorized', - 'PaymentRequired', - 'Forbidden', - 'NotFound', - 'MethodNotAllowed', - 'NotAcceptable', - 'ProxyAuthenticationRequired', - 'RequestTimeout', - 'Conflict', - 'Gone', - 'LengthRequired', - 'PreconditionFailed', - 'RequestEntityTooLarge', - 'RequestUriTooLong', - 'UnsupportedMediaType', - 'RequestedRangeNotSatisfiable', - 'ExpectationFailed', - 'UnprocessableEntity', - - 'HttpServerError', - 'InternalServerError', - 'HttpNotImplemented', - 'BadGateway', - 'ServiceUnavailable', - 'GatewayTimeout', - 'HttpVersionNotSupported', - - 'from_response') - - -class HttpError(base.ClientException): - """The base exception class for all HTTP exceptions.""" - - http_status = 0 - message = "HTTP Error" - - def __init__(self, message=None, details=None, - response=None, request_id=None, - url=None, method=None, http_status=None, - retry_after=0): - self.http_status = http_status or self.http_status - self.message = message or self.message - self.details = details - self.request_id = request_id - self.response = response - self.url = url - self.method = method - formatted_string = "%s (HTTP %s)" % (self.message, self.http_status) - self.retry_after = retry_after - if request_id: - formatted_string += " (Request-ID: %s)" % request_id - super(HttpError, self).__init__(formatted_string) - - -class HTTPClientError(HttpError): - """Client-side HTTP error. - - Exception for cases in which the client seems to have erred. - """ - - message = "HTTP Client Error" - - -class HttpServerError(HttpError): - """Server-side HTTP error. - - Exception for cases in which the server is aware that it has - erred or is incapable of performing the request. - """ - - message = "HTTP Server Error" - - -class BadRequest(HTTPClientError): - """HTTP 400 - Bad Request. - - The request cannot be fulfilled due to bad syntax. - """ - - http_status = 400 - message = "Bad Request" - - -class Unauthorized(HTTPClientError): - """HTTP 401 - Unauthorized. - - Similar to 403 Forbidden, but specifically for use when authentication - is required and has failed or has not yet been provided. - """ - - http_status = 401 - message = "Unauthorized" - - -class PaymentRequired(HTTPClientError): - """HTTP 402 - Payment Required. - - Reserved for future use. - """ - - http_status = 402 - message = "Payment Required" - - -class Forbidden(HTTPClientError): - """HTTP 403 - Forbidden. - - The request was a valid request, but the server is refusing to respond - to it. - """ - - http_status = 403 - message = "Forbidden" - - -class NotFound(HTTPClientError): - """HTTP 404 - Not Found. - - The requested resource could not be found but may be available again - in the future. - """ - - http_status = 404 - message = "Not Found" - - -class MethodNotAllowed(HTTPClientError): - """HTTP 405 - Method Not Allowed. - - A request was made of a resource using a request method not supported - by that resource. - """ - - http_status = 405 - message = "Method Not Allowed" - - -class NotAcceptable(HTTPClientError): - """HTTP 406 - Not Acceptable. - - The requested resource is only capable of generating content not - acceptable according to the Accept headers sent in the request. - """ - - http_status = 406 - message = "Not Acceptable" - - -class ProxyAuthenticationRequired(HTTPClientError): - """HTTP 407 - Proxy Authentication Required. - - The client must first authenticate itself with the proxy. - """ - - http_status = 407 - message = "Proxy Authentication Required" - - -class RequestTimeout(HTTPClientError): - """HTTP 408 - Request Timeout. - - The server timed out waiting for the request. - """ - - http_status = 408 - message = "Request Timeout" - - -class Conflict(HTTPClientError): - """HTTP 409 - Conflict. - - Indicates that the request could not be processed because of conflict - in the request, such as an edit conflict. - """ - - http_status = 409 - message = "Conflict" - - -class Gone(HTTPClientError): - """HTTP 410 - Gone. - - Indicates that the resource requested is no longer available and will - not be available again. - """ - - http_status = 410 - message = "Gone" - - -class LengthRequired(HTTPClientError): - """HTTP 411 - Length Required. - - The request did not specify the length of its content, which is - required by the requested resource. - """ - - http_status = 411 - message = "Length Required" - - -class PreconditionFailed(HTTPClientError): - """HTTP 412 - Precondition Failed. - - The server does not meet one of the preconditions that the requester - put on the request. - """ - - http_status = 412 - message = "Precondition Failed" - - -class RequestEntityTooLarge(HTTPClientError): - """HTTP 413 - Request Entity Too Large. - - The request is larger than the server is willing or able to process. - """ - - http_status = 413 - message = "Request Entity Too Large" - - def __init__(self, *args, **kwargs): - try: - self.retry_after = int(kwargs.pop('retry_after')) - except (KeyError, ValueError): - self.retry_after = 0 - - super(RequestEntityTooLarge, self).__init__(*args, **kwargs) - - -class RequestUriTooLong(HTTPClientError): - """HTTP 414 - Request-URI Too Long. - - The URI provided was too long for the server to process. - """ - - http_status = 414 - message = "Request-URI Too Long" - - -class UnsupportedMediaType(HTTPClientError): - """HTTP 415 - Unsupported Media Type. - - The request entity has a media type which the server or resource does - not support. - """ - - http_status = 415 - message = "Unsupported Media Type" - - -class RequestedRangeNotSatisfiable(HTTPClientError): - """HTTP 416 - Requested Range Not Satisfiable. - - The client has asked for a portion of the file, but the server cannot - supply that portion. - """ - - http_status = 416 - message = "Requested Range Not Satisfiable" - - -class ExpectationFailed(HTTPClientError): - """HTTP 417 - Expectation Failed. - - The server cannot meet the requirements of the Expect request-header field. - """ - - http_status = 417 - message = "Expectation Failed" - - -class UnprocessableEntity(HTTPClientError): - """HTTP 422 - Unprocessable Entity. - - The request was well-formed but was unable to be followed due to semantic - errors. - """ - - http_status = 422 - message = "Unprocessable Entity" - - -class InternalServerError(HttpServerError): - """HTTP 500 - Internal Server Error. - - A generic error message, given when no more specific message is suitable. - """ - - http_status = 500 - message = "Internal Server Error" - - -# NotImplemented is a python keyword. -class HttpNotImplemented(HttpServerError): - """HTTP 501 - Not Implemented. - - The server either does not recognize the request method, or it lacks - the ability to fulfill the request. - """ - - http_status = 501 - message = "Not Implemented" - - -class BadGateway(HttpServerError): - """HTTP 502 - Bad Gateway. - - The server was acting as a gateway or proxy and received an invalid - response from the upstream server. - """ - - http_status = 502 - message = "Bad Gateway" - - -class ServiceUnavailable(HttpServerError): - """HTTP 503 - Service Unavailable. - - The server is currently unavailable. - """ - - http_status = 503 - message = "Service Unavailable" - - -class GatewayTimeout(HttpServerError): - """HTTP 504 - Gateway Timeout. - - The server was acting as a gateway or proxy and did not receive a timely - response from the upstream server. - """ - - http_status = 504 - message = "Gateway Timeout" - - -class HttpVersionNotSupported(HttpServerError): - """HTTP 505 - HttpVersion Not Supported. - - The server does not support the HTTP protocol version used in the request. - """ - - http_status = 505 - message = "HTTP Version Not Supported" - - -# _code_map contains all the classes that have http_status attribute. -_code_map = dict( - (getattr(obj, 'http_status', None), obj) - for name, obj in vars(sys.modules[__name__]).items() - if inspect.isclass(obj) and getattr(obj, 'http_status', False) -) - - -def from_response(response, method, url): - """Return an instance of :class:`HttpError` or subclass based on response. - - :param response: instance of `requests.Response` class - :param method: HTTP method used for request - :param url: URL used for request - """ - req_id = response.headers.get("x-openstack-request-id") - - kwargs = { - "http_status": response.status_code, - "response": response, - "method": method, - "url": url, - "request_id": req_id, - } - if "retry-after" in response.headers: - kwargs["retry_after"] = response.headers["retry-after"] - - content_type = response.headers.get("Content-Type", "") - if content_type.startswith("application/json"): - try: - body = response.json() - except ValueError: - pass - else: - if isinstance(body, dict) and isinstance(body.get("error"), dict): - error = body["error"] - kwargs["message"] = error.get("message") - kwargs["details"] = error.get("details") - elif content_type.startswith("text/"): - kwargs["details"] = response.text - - try: - cls = _code_map[response.status_code] - except KeyError: - if 500 <= response.status_code < 600: - cls = HttpServerError - elif 400 <= response.status_code < 500: - cls = HTTPClientError - else: - cls = HttpError - return cls(**kwargs) diff --git a/keystoneauth1/exceptions/oidc.py b/keystoneauth1/exceptions/oidc.py deleted file mode 100644 index 2835017..0000000 --- a/keystoneauth1/exceptions/oidc.py +++ /dev/null @@ -1,44 +0,0 @@ -# Licensed under the Apache License, Version 2.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 keystoneauth1.exceptions import auth_plugins - -__all__ = ( - 'InvalidDiscoveryEndpoint', 'InvalidOidcDiscoveryDocument', - 'OidcAccessTokenEndpointNotFound', 'OidcAuthorizationEndpointNotFound', - 'OidcGrantTypeMissmatch', 'OidcPluginNotSupported', -) - - -class InvalidDiscoveryEndpoint(auth_plugins.AuthPluginException): - message = "OpenID Connect Discovery Document endpoint not set.""" - - -class InvalidOidcDiscoveryDocument(auth_plugins.AuthPluginException): - message = "OpenID Connect Discovery Document is not valid JSON.""" - - -class OidcAccessTokenEndpointNotFound(auth_plugins.AuthPluginException): - message = "OpenID Connect access token endpoint not provided." - - -class OidcAuthorizationEndpointNotFound(auth_plugins.AuthPluginException): - message = "OpenID Connect authorization endpoint not provided." - - -class OidcGrantTypeMissmatch(auth_plugins.AuthPluginException): - message = "Missmatch between OpenID Connect plugin and grant_type argument" - - -class OidcPluginNotSupported(auth_plugins.AuthPluginException): - message = "OpenID Connect grant type not supported by provider." diff --git a/keystoneauth1/exceptions/response.py b/keystoneauth1/exceptions/response.py deleted file mode 100644 index 0707eda..0000000 --- a/keystoneauth1/exceptions/response.py +++ /dev/null @@ -1,25 +0,0 @@ -# Licensed under the Apache License, Version 2.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 keystoneauth1.exceptions import base - - -__all__ = ('InvalidResponse',) - - -class InvalidResponse(base.ClientException): - message = "Invalid response from server." - - def __init__(self, response): - super(InvalidResponse, self).__init__() - self.response = response diff --git a/keystoneauth1/exceptions/service_providers.py b/keystoneauth1/exceptions/service_providers.py deleted file mode 100644 index 959d6eb..0000000 --- a/keystoneauth1/exceptions/service_providers.py +++ /dev/null @@ -1,24 +0,0 @@ -# Licensed under the Apache License, Version 2.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 keystoneauth1.exceptions import base - -__all__ = ('ServiceProviderNotFound',) - - -class ServiceProviderNotFound(base.ClientException): - """A Service Provider cannot be found.""" - - def __init__(self, sp_id): - self.sp_id = sp_id - msg = 'The Service Provider %(sp)s could not be found' % {'sp': sp_id} - super(ServiceProviderNotFound, self).__init__(msg) diff --git a/keystoneauth1/extras/__init__.py b/keystoneauth1/extras/__init__.py deleted file mode 100644 index fbcb352..0000000 --- a/keystoneauth1/extras/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -# Licensed under the Apache License, Version 2.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. - -# NOTE(jamielennox): This directory is designed to reflect the dependency -# extras in the setup.cfg file. If you create an additional dependency section -# like 'kerberos' in the setup.cfg it is expected that there be a kerberos -# package here that can be imported. -# -# e.g. from keystoneauth1.extras import kerberos - -pass diff --git a/keystoneauth1/extras/_saml2/__init__.py b/keystoneauth1/extras/_saml2/__init__.py deleted file mode 100644 index a15d1f9..0000000 --- a/keystoneauth1/extras/_saml2/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -# Licensed under the Apache License, Version 2.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 keystoneauth1.extras._saml2 import v3 - -_V3_SAML2_AVAILABLE = v3._SAML2_AVAILABLE -_V3_ADFS_AVAILABLE = v3._ADFS_AVAILABLE - -V3Saml2Password = v3.Saml2Password -V3ADFSPassword = v3.ADFSPassword - - -__all__ = ('V3Saml2Password', 'V3ADFSPassword') diff --git a/keystoneauth1/extras/_saml2/_loading.py b/keystoneauth1/extras/_saml2/_loading.py deleted file mode 100644 index 845eac0..0000000 --- a/keystoneauth1/extras/_saml2/_loading.py +++ /dev/null @@ -1,66 +0,0 @@ -# Licensed under the Apache License, Version 2.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 keystoneauth1.extras import _saml2 -from keystoneauth1 import loading - - -class Saml2Password(loading.BaseFederationLoader): - - @property - def plugin_class(self): - return _saml2.V3Saml2Password - - @property - def available(self): - return _saml2._V3_SAML2_AVAILABLE - - def get_options(self): - options = super(Saml2Password, self).get_options() - - options.extend([ - loading.Opt('identity-provider-url', - help=('An Identity Provider URL, where the SAML2 ' - 'authentication request will be sent.')), - loading.Opt('username', help='Username'), - loading.Opt('password', secret=True, help='Password') - ]) - - return options - - -class ADFSPassword(loading.BaseFederationLoader): - - @property - def plugin_class(self): - return _saml2.V3ADFSPassword - - @property - def available(self): - return _saml2._V3_ADFS_AVAILABLE - - def get_options(self): - options = super(ADFSPassword, self).get_options() - - options.extend([ - loading.Opt('identity-provider-url', - help=('An Identity Provider URL, where the SAML ' - 'authentication request will be sent.')), - loading.Opt('service-provider-endpoint', - help="Service Provider's Endpoint"), - loading.Opt('service-provider-entity-id', - help="Service Provider's SAML Entity ID"), - loading.Opt('username', help='Username'), - loading.Opt('password', secret=True, help='Password') - ]) - - return options diff --git a/keystoneauth1/extras/_saml2/v3/__init__.py b/keystoneauth1/extras/_saml2/v3/__init__.py deleted file mode 100644 index 4dbbeff..0000000 --- a/keystoneauth1/extras/_saml2/v3/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -# Licensed under the Apache License, Version 2.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 keystoneauth1.extras._saml2.v3 import adfs -from keystoneauth1.extras._saml2.v3 import base -from keystoneauth1.extras._saml2.v3 import saml2 - -_SAML2_AVAILABLE = base.etree is not None and saml2.etree is not None -_ADFS_AVAILABLE = base.etree is not None and adfs.etree is not None - -Saml2Password = saml2.Password -ADFSPassword = adfs.Password - -__all__ = ('Saml2Password', 'ADFSPassword') diff --git a/keystoneauth1/extras/_saml2/v3/adfs.py b/keystoneauth1/extras/_saml2/v3/adfs.py deleted file mode 100644 index 4d3b87b..0000000 --- a/keystoneauth1/extras/_saml2/v3/adfs.py +++ /dev/null @@ -1,431 +0,0 @@ -# Licensed under the Apache License, Version 2.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 uuid - -try: - from lxml import etree -except ImportError: - etree = None - -from six.moves import urllib - -from keystoneauth1 import access -from keystoneauth1 import exceptions -from keystoneauth1.extras._saml2.v3 import base - - -class Password(base.BaseSAMLPlugin): - """Authentication plugin for Microsoft ADFS2.0 IdPs.""" - - DEFAULT_ADFS_TOKEN_EXPIRATION = 120 - - HEADER_SOAP = {"Content-Type": "application/soap+xml; charset=utf-8"} - HEADER_X_FORM = {"Content-Type": "application/x-www-form-urlencoded"} - - NAMESPACES = { - 's': 'http://www.w3.org/2003/05/soap-envelope', - 'a': 'http://www.w3.org/2005/08/addressing', - 'u': ('http://docs.oasis-open.org/wss/2004/01/oasis-200401-' - 'wss-wssecurity-utility-1.0.xsd') - } - - ADFS_TOKEN_NAMESPACES = { - 's': 'http://www.w3.org/2003/05/soap-envelope', - 't': 'http://docs.oasis-open.org/ws-sx/ws-trust/200512' - } - ADFS_ASSERTION_XPATH = ('/s:Envelope/s:Body' - '/t:RequestSecurityTokenResponseCollection' - '/t:RequestSecurityTokenResponse') - - def __init__(self, auth_url, identity_provider, identity_provider_url, - service_provider_endpoint, username, password, - protocol, service_provider_entity_id=None, **kwargs): - """Constructor for ``ADFSPassword``. - - :param auth_url: URL of the Identity Service - :type auth_url: string - - :param identity_provider: name of the Identity Provider the client - will authenticate against. This parameter - will be used to build a dynamic URL used to - obtain unscoped OpenStack token. - :type identity_provider: string - - :param identity_provider_url: An Identity Provider URL, where the SAML2 - authentication request will be sent. - :type identity_provider_url: string - - :param service_provider_endpoint: Endpoint where an assertion is being - sent, for instance: ``https://host.domain/Shibboleth.sso/ADFS`` - :type service_provider_endpoint: string - :param service_provider_entity_id: Service Provider SAML Entity ID - :type service_provider_entity_id: string - - :param username: User's login - :type username: string - - :param password: User's password - :type password: string - - """ - super(Password, self).__init__( - auth_url=auth_url, identity_provider=identity_provider, - identity_provider_url=identity_provider_url, - username=username, password=password, protocol=protocol, **kwargs) - - self.service_provider_endpoint = service_provider_endpoint - self.service_provider_entity_id = service_provider_entity_id - - def _cookies(self, session): - """Check if cookie jar is not empty. - - keystoneauth1.session.Session object doesn't have a cookies attribute. - We should then try fetching cookies from the underlying - requests.Session object. If that fails too, there is something wrong - and let Python raise the AttributeError. - - :param session - :returns: True if cookie jar is nonempty, False otherwise - :raises AttributeError: in case cookies are not find anywhere - - """ - try: - return bool(session.cookies) - except AttributeError: - pass - - return bool(session.session.cookies) - - def _token_dates(self, fmt='%Y-%m-%dT%H:%M:%S.%fZ'): - """Calculate created and expires datetime objects. - - The method is going to be used for building ADFS Request Security - Token message. Time interval between ``created`` and ``expires`` - dates is now static and equals to 120 seconds. ADFS security tokens - should not be live too long, as currently ``keystoneauth1`` - doesn't have mechanisms for reusing such tokens (every time ADFS authn - method is called, keystoneauth1 will login with the ADFS instance). - - :param fmt: Datetime format for specifying string format of a date. - It should not be changed if the method is going to be used - for building the ADFS security token request. - :type fmt: string - - """ - date_created = datetime.datetime.utcnow() - date_expires = date_created + datetime.timedelta( - seconds=self.DEFAULT_ADFS_TOKEN_EXPIRATION) - return [_time.strftime(fmt) for _time in (date_created, date_expires)] - - def _prepare_adfs_request(self): - """Build the ADFS Request Security Token SOAP message. - - Some values like username or password are inserted in the request. - - """ - WSS_SECURITY_NAMESPACE = { - 'o': ('http://docs.oasis-open.org/wss/2004/01/oasis-200401-' - 'wss-wssecurity-secext-1.0.xsd') - } - - TRUST_NAMESPACE = { - 'trust': 'http://docs.oasis-open.org/ws-sx/ws-trust/200512' - } - - WSP_NAMESPACE = { - 'wsp': 'http://schemas.xmlsoap.org/ws/2004/09/policy' - } - - WSA_NAMESPACE = { - 'wsa': 'http://www.w3.org/2005/08/addressing' - } - - root = etree.Element( - '{http://www.w3.org/2003/05/soap-envelope}Envelope', - nsmap=self.NAMESPACES) - - header = etree.SubElement( - root, '{http://www.w3.org/2003/05/soap-envelope}Header') - action = etree.SubElement( - header, "{http://www.w3.org/2005/08/addressing}Action") - action.set( - "{http://www.w3.org/2003/05/soap-envelope}mustUnderstand", "1") - action.text = ('http://docs.oasis-open.org/ws-sx/ws-trust/200512' - '/RST/Issue') - - messageID = etree.SubElement( - header, '{http://www.w3.org/2005/08/addressing}MessageID') - messageID.text = 'urn:uuid:' + uuid.uuid4().hex - replyID = etree.SubElement( - header, '{http://www.w3.org/2005/08/addressing}ReplyTo') - address = etree.SubElement( - replyID, '{http://www.w3.org/2005/08/addressing}Address') - address.text = 'http://www.w3.org/2005/08/addressing/anonymous' - - to = etree.SubElement( - header, '{http://www.w3.org/2005/08/addressing}To') - to.set("{http://www.w3.org/2003/05/soap-envelope}mustUnderstand", "1") - - security = etree.SubElement( - header, '{http://docs.oasis-open.org/wss/2004/01/oasis-200401-' - 'wss-wssecurity-secext-1.0.xsd}Security', - nsmap=WSS_SECURITY_NAMESPACE) - - security.set( - "{http://www.w3.org/2003/05/soap-envelope}mustUnderstand", "1") - - timestamp = etree.SubElement( - security, ('{http://docs.oasis-open.org/wss/2004/01/oasis-200401-' - 'wss-wssecurity-utility-1.0.xsd}Timestamp')) - timestamp.set( - ('{http://docs.oasis-open.org/wss/2004/01/oasis-200401-' - 'wss-wssecurity-utility-1.0.xsd}Id'), '_0') - - created = etree.SubElement( - timestamp, ('{http://docs.oasis-open.org/wss/2004/01/oasis-200401-' - 'wss-wssecurity-utility-1.0.xsd}Created')) - - expires = etree.SubElement( - timestamp, ('{http://docs.oasis-open.org/wss/2004/01/oasis-200401-' - 'wss-wssecurity-utility-1.0.xsd}Expires')) - - created.text, expires.text = self._token_dates() - - usernametoken = etree.SubElement( - security, '{http://docs.oasis-open.org/wss/2004/01/oasis-200401-' - 'wss-wssecurity-secext-1.0.xsd}UsernameToken') - usernametoken.set( - ('{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-' - 'wssecurity-utility-1.0.xsd}u'), "uuid-%s-1" % uuid.uuid4().hex) - - username = etree.SubElement( - usernametoken, ('{http://docs.oasis-open.org/wss/2004/01/oasis-' - '200401-wss-wssecurity-secext-1.0.xsd}Username')) - password = etree.SubElement( - usernametoken, ('{http://docs.oasis-open.org/wss/2004/01/oasis-' - '200401-wss-wssecurity-secext-1.0.xsd}Password'), - Type=('http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-' - 'username-token-profile-1.0#PasswordText')) - - body = etree.SubElement( - root, "{http://www.w3.org/2003/05/soap-envelope}Body") - - request_security_token = etree.SubElement( - body, ('{http://docs.oasis-open.org/ws-sx/ws-trust/200512}' - 'RequestSecurityToken'), nsmap=TRUST_NAMESPACE) - - applies_to = etree.SubElement( - request_security_token, - '{http://schemas.xmlsoap.org/ws/2004/09/policy}AppliesTo', - nsmap=WSP_NAMESPACE) - - endpoint_reference = etree.SubElement( - applies_to, - '{http://www.w3.org/2005/08/addressing}EndpointReference', - nsmap=WSA_NAMESPACE) - - wsa_address = etree.SubElement( - endpoint_reference, - '{http://www.w3.org/2005/08/addressing}Address') - - keytype = etree.SubElement( - request_security_token, - '{http://docs.oasis-open.org/ws-sx/ws-trust/200512}KeyType') - keytype.text = ('http://docs.oasis-open.org/ws-sx/' - 'ws-trust/200512/Bearer') - - request_type = etree.SubElement( - request_security_token, - '{http://docs.oasis-open.org/ws-sx/ws-trust/200512}RequestType') - request_type.text = ('http://docs.oasis-open.org/ws-sx/' - 'ws-trust/200512/Issue') - token_type = etree.SubElement( - request_security_token, - '{http://docs.oasis-open.org/ws-sx/ws-trust/200512}TokenType') - token_type.text = 'urn:oasis:names:tc:SAML:1.0:assertion' - - # After constructing the request, let's plug in some values - username.text = self.username - password.text = self.password - to.text = self.identity_provider_url - wsa_address.text = (self.service_provider_entity_id or - self.service_provider_endpoint) - - self.prepared_request = root - - def _get_adfs_security_token(self, session): - """Send ADFS Security token to the ADFS server. - - Store the result in the instance attribute and raise an exception in - case the response is not valid XML data. - - If a user cannot authenticate due to providing bad credentials, the - ADFS2.0 server will return a HTTP 500 response and a XML Fault message. - If ``exceptions.InternalServerError`` is caught, the method tries to - parse the XML response. - If parsing is unsuccessful, an ``exceptions.AuthorizationFailure`` is - raised with a reason from the XML fault. Otherwise an original - ``exceptions.InternalServerError`` is re-raised. - - :param session : a session object to send out HTTP requests. - :type session: keystoneauth1.session.Session - - :raises keystoneauth1.exceptions.AuthorizationFailure: when HTTP - response from the ADFS server is not a valid XML ADFS security - token. - :raises keystoneauth1.exceptions.InternalServerError: If response - status code is HTTP 500 and the response XML cannot be - recognized. - - """ - def _get_failure(e): - xpath = '/s:Envelope/s:Body/s:Fault/s:Code/s:Subcode/s:Value' - content = e.response.content - try: - obj = self.str_to_xml(content).xpath( - xpath, namespaces=self.NAMESPACES) - obj = self._first(obj) - return obj.text - # NOTE(marek-denis): etree.Element.xpath() doesn't raise an - # exception, it just returns an empty list. In that case, _first() - # will raise IndexError and we should treat it as an indication XML - # is not valid. exceptions.AuthorizationFailure can be raised from - # str_to_xml(), however since server returned HTTP 500 we should - # re-raise exceptions.InternalServerError. - except (IndexError, exceptions.AuthorizationFailure): - raise e - - request_security_token = self.xml_to_str(self.prepared_request) - try: - response = session.post( - url=self.identity_provider_url, headers=self.HEADER_SOAP, - data=request_security_token, authenticated=False) - except exceptions.InternalServerError as e: - reason = _get_failure(e) - raise exceptions.AuthorizationFailure(reason) - msg = ('Error parsing XML returned from ' - 'the ADFS Identity Provider, reason: %s') - self.adfs_token = self.str_to_xml(response.content, msg) - - def _prepare_sp_request(self): - """Prepare ADFS Security Token to be sent to the Service Provider. - - The method works as follows: - * Extract SAML2 assertion from the ADFS Security Token. - * Replace namespaces - * urlencode assertion - * concatenate static string with the encoded assertion - - """ - assertion = self.adfs_token.xpath( - self.ADFS_ASSERTION_XPATH, namespaces=self.ADFS_TOKEN_NAMESPACES) - assertion = self._first(assertion) - assertion = self.xml_to_str(assertion) - # TODO(marek-denis): Ideally no string replacement should occur. - # Unfortunately lxml doesn't allow for namespaces changing in-place and - # probably the only solution good for now is to build the assertion - # from scratch and reuse values from the adfs security token. - assertion = assertion.replace( - b'http://docs.oasis-open.org/ws-sx/ws-trust/200512', - b'http://schemas.xmlsoap.org/ws/2005/02/trust') - - encoded_assertion = urllib.parse.quote(assertion) - self.encoded_assertion = 'wa=wsignin1.0&wresult=' + encoded_assertion - - def _send_assertion_to_service_provider(self, session): - """Send prepared assertion to a service provider. - - As the assertion doesn't contain a protected resource, the value from - the ``location`` header is not valid and we should not let the Session - object get redirected there. The aim of this call is to get a cookie in - the response which is required for entering a protected endpoint. - - :param session : a session object to send out HTTP requests. - :type session: keystoneauth1.session.Session - - :raises: Corresponding HTTP error exception - - """ - session.post( - url=self.service_provider_endpoint, data=self.encoded_assertion, - headers=self.HEADER_X_FORM, redirect=False, authenticated=False) - - def _access_service_provider(self, session): - """Access protected endpoint and fetch unscoped token. - - After federated authentication workflow a protected endpoint should be - accessible with the session object. The access is granted basing on the - cookies stored within the session object. If, for some reason no - cookies are present (quantity test) it means something went wrong and - user will not be able to fetch an unscoped token. In that case an - ``exceptions.AuthorizationFailure` exception is raised and no HTTP call - is even made. - - :param session : a session object to send out HTTP requests. - :type session: keystoneauth1.session.Session - - :raises keystoneauth1.exceptions.AuthorizationFailure: in case session - object has empty cookie jar. - - """ - if self._cookies(session) is False: - raise exceptions.AuthorizationFailure( - "Session object doesn't contain a cookie, therefore you are " - "not allowed to enter the Identity Provider's protected area.") - self.authenticated_response = session.get(self.federated_token_url, - authenticated=False) - - def get_unscoped_auth_ref(self, session, *kwargs): - """Retrieve unscoped token after authentcation with ADFS server. - - This is a multistep process: - - * Prepare ADFS Request Securty Token - - build an etree.XML object filling certain attributes with proper user - credentials, created/expires dates (ticket is be valid for 120 - seconds as currently we don't handle reusing ADFS issued security - tokens). - - * Send ADFS Security token to the ADFS server. Step handled by - - * Receive and parse security token, extract actual SAML assertion and - prepare a request addressed for the Service Provider endpoint. - This also includes changing namespaces in the XML document. Step - handled by ``ADFSPassword._prepare_sp_request()`` method. - - * Send prepared assertion to the Service Provider endpoint. Usually - the server will respond with HTTP 301 code which should be ignored as - the 'location' header doesn't contain protected area. The goal of - this operation is fetching the session cookie which later allows for - accessing protected URL endpoints. Step handed by - ``ADFSPassword._send_assertion_to_service_provider()`` method. - - * Once the session cookie is issued, the protected endpoint can be - accessed and an unscoped token can be retrieved. Step handled by - ``ADFSPassword._access_service_provider()`` method. - - :param session: a session object to send out HTTP requests. - :type session: keystoneauth1.session.Session - - :returns: AccessInfo - :rtype: :py:class:`keystoneauth1.access.AccessInfo` - - """ - self._prepare_adfs_request() - self._get_adfs_security_token(session) - self._prepare_sp_request() - self._send_assertion_to_service_provider(session) - self._access_service_provider(session) - - return access.create(resp=self.authenticated_response) diff --git a/keystoneauth1/extras/_saml2/v3/base.py b/keystoneauth1/extras/_saml2/v3/base.py deleted file mode 100644 index d221d15..0000000 --- a/keystoneauth1/extras/_saml2/v3/base.py +++ /dev/null @@ -1,98 +0,0 @@ -# Licensed under the Apache License, Version 2.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. - -try: - from lxml import etree -except ImportError: - etree = None - -from keystoneauth1 import exceptions -from keystoneauth1.identity import v3 - - -class _Saml2TokenAuthMethod(v3.AuthMethod): - _method_parameters = [] - - def get_auth_data(self, session, auth, headers, **kwargs): - raise exceptions.MethodNotImplemented('This method should never ' - 'be called') - - -class BaseSAMLPlugin(v3.FederationBaseAuth): - - HTTP_MOVED_TEMPORARILY = 302 - HTTP_SEE_OTHER = 303 - - _auth_method_class = _Saml2TokenAuthMethod - - def __init__(self, auth_url, - identity_provider, identity_provider_url, - username, password, protocol, - **kwargs): - """Class constructor accepting following parameters. - - :param auth_url: URL of the Identity Service - :type auth_url: string - - :param identity_provider: Name of the Identity Provider the client - will authenticate against. This parameter - will be used to build a dynamic URL used to - obtain unscoped OpenStack token. - :type identity_provider: string - - :param identity_provider_url: An Identity Provider URL, where the - SAML2 auhentication request will be - sent. - :type identity_provider_url: string - - :param username: User's login - :type username: string - - :param password: User's password - :type password: string - - :param protocol: Protocol to be used for the authentication. - The name must be equal to one configured at the - keystone sp side. This value is used for building - dynamic authentication URL. - Typical value would be: saml2 - :type protocol: string - - """ - super(BaseSAMLPlugin, self).__init__( - auth_url=auth_url, identity_provider=identity_provider, - protocol=protocol, - **kwargs) - self.identity_provider_url = identity_provider_url - self.username = username - self.password = password - - @staticmethod - def _first(_list): - if len(_list) != 1: - raise IndexError('Only single element list is acceptable') - return _list[0] - - @staticmethod - def str_to_xml(content, msg=None, include_exc=True): - try: - return etree.XML(content) - except etree.XMLSyntaxError as e: - if not msg: - msg = str(e) - else: - msg = msg % e if include_exc else msg - raise exceptions.AuthorizationFailure(msg) - - @staticmethod - def xml_to_str(content, **kwargs): - return etree.tostring(content, **kwargs) diff --git a/keystoneauth1/extras/_saml2/v3/saml2.py b/keystoneauth1/extras/_saml2/v3/saml2.py deleted file mode 100644 index 1cc5864..0000000 --- a/keystoneauth1/extras/_saml2/v3/saml2.py +++ /dev/null @@ -1,301 +0,0 @@ -# Licensed under the Apache License, Version 2.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 abc - -try: - from lxml import etree -except ImportError: - etree = None - -import requests -import requests.auth - -from keystoneauth1 import access -from keystoneauth1 import exceptions -from keystoneauth1.identity import v3 - -_PAOS_NAMESPACE = 'urn:liberty:paos:2003-08' -_ECP_NAMESPACE = 'urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp' -_PAOS_HEADER = 'application/vnd.paos+xml' - -_PAOS_VER = 'ver="%s";"%s"' % (_PAOS_NAMESPACE, _ECP_NAMESPACE) - -_XML_NAMESPACES = { - 'ecp': _ECP_NAMESPACE, - 'S': 'http://schemas.xmlsoap.org/soap/envelope/', - 'paos': _PAOS_NAMESPACE, -} - -_XBASE = '/S:Envelope/S:Header/' - -_XPATH_SP_RELAY_STATE = '//ecp:RelayState' -_XPATH_SP_CONSUMER_URL = _XBASE + 'paos:Request/@responseConsumerURL' -_XPATH_IDP_CONSUMER_URL = _XBASE + 'ecp:Response/@AssertionConsumerServiceURL' - -_SOAP_FAULT = """ - - - - S:Server - responseConsumerURL from SP and - assertionConsumerServiceURL from IdP do not match - - - - -""" - - -class SamlException(Exception): - """Base SAML plugin exception.""" - - -class InvalidResponse(SamlException): - """Invalid Response from SAML authentication.""" - - -class ConsumerMismatch(SamlException): - """The SP and IDP consumers do not match.""" - - -def _response_xml(response, name): - try: - return etree.XML(response.content) - except etree.XMLSyntaxError as e: - msg = 'SAML2: Error parsing XML returned from %s: %s' % (name, e) - raise InvalidResponse(msg) - - -def _str_from_xml(xml, path): - l = xml.xpath(path, namespaces=_XML_NAMESPACES) - if len(l) != 1: - raise IndexError('%s should provide a single element list' % path) - return l[0] - - -class _SamlAuth(requests.auth.AuthBase): - """A generic SAML ECP plugin for requests. - - This is a multi-step process including multiple HTTP requests. - Authentication consists of: - - * HTTP GET request to the Service Provider. - - It's crucial to include HTTP headers indicating we are expecting SOAP - message in return. Service Provider should respond with a SOAP - message. - - * HTTP POST request to the external Identity Provider service with - ECP extension enabled. The content sent is a header removed SOAP - message returned from the Service Provider. It's also worth noting - that ECP extension to the SAML2 doesn't define authentication method. - The most popular is HttpBasicAuth with just user and password. - Other possibilities could be X509 certificates or Kerberos. - Upon successful authentication the user should receive a SAML2 - assertion. - - * HTTP POST request again to the Service Provider. The body of the - request includes SAML2 assertion issued by a trusted Identity - Provider. The request should be sent to the Service Provider - consumer url specified in the SAML2 assertion. - Providing the authentication was successful and both Service Provider - and Identity Providers are trusted to each other, the Service - Provider will issue an unscoped token with a list of groups the - federated user is a member of. - """ - - def __init__(self, identity_provider_url, requests_auth): - super(_SamlAuth, self).__init__() - self.identity_provider_url = identity_provider_url - self.requests_auth = requests_auth - - def __call__(self, request): - try: - accept = request.headers['Accept'] - except KeyError: - request.headers['Accept'] = _PAOS_HEADER - else: - request.headers['Accept'] = ','.join([accept, _PAOS_HEADER]) - - request.headers['PAOS'] = _PAOS_VER - request.register_hook('response', self._handle_response) - return request - - def _handle_response(self, response, **kwargs): - if (response.status_code == 200 and - response.headers.get('Content-Type') == _PAOS_HEADER): - response = self._ecp_retry(response, **kwargs) - - return response - - def _ecp_retry(self, sp_response, **kwargs): - history = [sp_response] - - def send(*send_args, **send_kwargs): - req = requests.Request(*send_args, **send_kwargs) - return sp_response.connection.send(req.prepare(), **kwargs) - - authn_request = _response_xml(sp_response, 'Service Provider') - relay_state = _str_from_xml(authn_request, _XPATH_SP_RELAY_STATE) - sp_consumer_url = _str_from_xml(authn_request, _XPATH_SP_CONSUMER_URL) - - authn_request.remove(authn_request[0]) - - idp_response = send('POST', - self.identity_provider_url, - headers={'Content-type': 'text/xml'}, - data=etree.tostring(authn_request), - auth=self.requests_auth) - history.append(idp_response) - - authn_response = _response_xml(idp_response, 'Identity Provider') - idp_consumer_url = _str_from_xml(authn_response, - _XPATH_IDP_CONSUMER_URL) - - if sp_consumer_url != idp_consumer_url: - # send fault message to the SP, discard the response - send('POST', - sp_consumer_url, - data=_SOAP_FAULT, - headers={'Content-Type': _PAOS_HEADER}) - - # prepare error message and raise an exception. - msg = ('Consumer URLs from Service Provider %(service_provider)s ' - '%(sp_consumer_url)s and Identity Provider ' - '%(identity_provider)s %(idp_consumer_url)s are not equal') - msg = msg % { - 'service_provider': sp_response.request.url, - 'sp_consumer_url': sp_consumer_url, - 'identity_provider': self.identity_provider_url, - 'idp_consumer_url': idp_consumer_url - } - - raise ConsumerMismatch(msg) - - authn_response[0][0] = relay_state - - # idp_consumer_url is the URL on the SP that handles the ECP body - # returned and creates an authenticated session. - final_resp = send('POST', - idp_consumer_url, - headers={'Content-Type': _PAOS_HEADER}, - cookies=idp_response.cookies, - data=etree.tostring(authn_response)) - - history.append(final_resp) - - # the SP should then redirect us back to the original URL to retry the - # original request. - if final_resp.status_code in (requests.codes.found, - requests.codes.other): - - # Consume content and release the original connection - # to allow our new request to reuse the same one. - sp_response.content - sp_response.raw.release_conn() - - req = sp_response.request.copy() - req.url = final_resp.headers['location'] - req.prepare_cookies(final_resp.cookies) - - final_resp = sp_response.connection.send(req, **kwargs) - history.append(final_resp) - - final_resp.history.extend(history) - return final_resp - - -class _FederatedSaml(v3.FederationBaseAuth): - - def __init__(self, auth_url, identity_provider, protocol, - identity_provider_url, **kwargs): - super(_FederatedSaml, self).__init__(auth_url, - identity_provider, - protocol, - **kwargs) - self.identity_provider_url = identity_provider_url - - @abc.abstractmethod - def get_requests_auth(self): - raise NotImplementedError() - - def get_unscoped_auth_ref(self, session, **kwargs): - method = self.get_requests_auth() - auth = _SamlAuth(self.identity_provider_url, method) - - try: - resp = session.get(self.federated_token_url, - requests_auth=auth, - authenticated=False) - except SamlException as e: - raise exceptions.AuthorizationFailure(str(e)) - - return access.create(resp=resp) - - -class Password(_FederatedSaml): - r"""Implement authentication plugin for SAML2 protocol. - - ECP stands for `Enhanced Client or Proxy` and is a SAML2 extension - for federated authentication where a transportation layer consists of - HTTP protocol and XML SOAP messages. - - `Read for more information - `_ on ECP. - - Reference the `SAML2 ECP specification `_. - - Currently only HTTPBasicAuth mechanism is available for the IdP - authenication. - - :param auth_url: URL of the Identity Service - :type auth_url: string - - :param identity_provider: name of the Identity Provider the client will - authenticate against. This parameter will be used - to build a dynamic URL used to obtain unscoped - OpenStack token. - :type identity_provider: string - - :param identity_provider_url: An Identity Provider URL, where the SAML2 - authn request will be sent. - :type identity_provider_url: string - - :param username: User's login - :type username: string - - :param password: User's password - :type password: string - - :param protocol: Protocol to be used for the authentication. - The name must be equal to one configured at the - keystone sp side. This value is used for building - dynamic authentication URL. - Typical value would be: saml2 - :type protocol: string - - """ - - def __init__(self, auth_url, identity_provider, protocol, - identity_provider_url, username, password, **kwargs): - super(Password, self).__init__(auth_url, - identity_provider, - protocol, - identity_provider_url, - **kwargs) - self.username = username - self.password = password - - def get_requests_auth(self): - return requests.auth.HTTPBasicAuth(self.username, self.password) diff --git a/keystoneauth1/extras/kerberos/__init__.py b/keystoneauth1/extras/kerberos/__init__.py deleted file mode 100644 index 6ae3ebd..0000000 --- a/keystoneauth1/extras/kerberos/__init__.py +++ /dev/null @@ -1,89 +0,0 @@ -# Licensed under the Apache License, Version 2.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. - - -"""Kerberos authentication plugins. - -.. warning:: - This module requires installation of an extra package (`requests_kerberos`) - not installed by default. Without the extra package an import error will - occur. The extra package can be installed using:: - - $ pip install keystoneauth1[kerberos] -""" - -try: - import requests_kerberos -except ImportError: - requests_kerberos = None - -from keystoneauth1 import access -from keystoneauth1.identity import v3 -from keystoneauth1.identity.v3 import federation - - -def _requests_auth(): - # NOTE(jamielennox): request_kerberos.OPTIONAL allows the plugin to accept - # unencrypted error messages where we can't verify the origin of the error - # because we aren't authenticated. - return requests_kerberos.HTTPKerberosAuth( - mutual_authentication=requests_kerberos.OPTIONAL) - - -def _dependency_check(): - if requests_kerberos is None: - raise ImportError(""" -Using the kerberos authentication plugin requires installation of additional -packages. These can be installed with:: - - $ pip install keystoneauth1[kerberos] -""") - - -class KerberosMethod(v3.AuthMethod): - - _method_parameters = [] - - def __init__(self, *args, **kwargs): - _dependency_check() - super(KerberosMethod, self).__init__(*args, **kwargs) - - def get_auth_data(self, session, auth, headers, request_kwargs, **kwargs): - # NOTE(jamielennox): request_kwargs is passed as a kwarg however it is - # required and always present when called from keystoneclient. - request_kwargs['requests_auth'] = _requests_auth() - return 'kerberos', {} - - -class Kerberos(v3.AuthConstructor): - _auth_method_class = KerberosMethod - - -class MappedKerberos(federation.FederationBaseAuth): - """Authenticate using Kerberos via the keystone federation mechanisms. - - This uses the OS-FEDERATION extension to gain an unscoped token and then - use the standard keystone auth process to scope that to any given project. - """ - - def __init__(self, auth_url, identity_provider, protocol, **kwargs): - _dependency_check() - - super(MappedKerberos, self).__init__(auth_url, identity_provider, - protocol, **kwargs) - - def get_unscoped_auth_ref(self, session, **kwargs): - resp = session.get(self.federated_token_url, - requests_auth=_requests_auth(), - authenticated=False) - - return access.create(body=resp.json(), resp=resp) diff --git a/keystoneauth1/extras/kerberos/_loading.py b/keystoneauth1/extras/kerberos/_loading.py deleted file mode 100644 index 6925ad8..0000000 --- a/keystoneauth1/extras/kerberos/_loading.py +++ /dev/null @@ -1,36 +0,0 @@ -# Licensed under the Apache License, Version 2.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 keystoneauth1.extras import kerberos -from keystoneauth1 import loading - - -class Kerberos(loading.BaseV3Loader): - - @property - def plugin_class(self): - return kerberos.Kerberos - - @property - def available(self): - return kerberos.requests_kerberos is not None - - -class MappedKerberos(loading.BaseFederationLoader): - - @property - def plugin_class(self): - return kerberos.MappedKerberos - - @property - def available(self): - return kerberos.requests_kerberos is not None diff --git a/keystoneauth1/extras/oauth1/__init__.py b/keystoneauth1/extras/oauth1/__init__.py deleted file mode 100644 index e267446..0000000 --- a/keystoneauth1/extras/oauth1/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# Licensed under the Apache License, Version 2.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 keystoneauth1.extras.oauth1 import v3 - -__all__ = ('V3OAuth1Method', 'V3OAuth1') - - -V3OAuth1Method = v3.OAuth1Method -V3OAuth1 = v3.OAuth1 diff --git a/keystoneauth1/extras/oauth1/_loading.py b/keystoneauth1/extras/oauth1/_loading.py deleted file mode 100644 index fa43232..0000000 --- a/keystoneauth1/extras/oauth1/_loading.py +++ /dev/null @@ -1,47 +0,0 @@ -# Licensed under the Apache License, Version 2.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 keystoneauth1.extras.oauth1 import v3 -from keystoneauth1 import loading - - -# NOTE(jamielennox): This is not a BaseV3Loader because we don't want to -# include the scoping options like project-id in the option list -class V3OAuth1(loading.BaseIdentityLoader): - - @property - def plugin_class(self): - return v3.OAuth1 - - @property - def available(self): - return v3.oauth1 is not None - - def get_options(self): - options = super(V3OAuth1, self).get_options() - - options.extend([ - loading.Opt('consumer-key', - required=True, - help='OAuth Consumer ID/Key'), - loading.Opt('consumer-secret', - required=True, - help='OAuth Consumer Secret'), - loading.Opt('access-key', - required=True, - help='OAuth Access Key'), - loading.Opt('access-secret', - required=True, - help='OAuth Access Secret'), - ]) - - return options diff --git a/keystoneauth1/extras/oauth1/v3.py b/keystoneauth1/extras/oauth1/v3.py deleted file mode 100644 index 98c768d..0000000 --- a/keystoneauth1/extras/oauth1/v3.py +++ /dev/null @@ -1,80 +0,0 @@ -# Licensed under the Apache License, Version 2.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 authentication plugins. - -.. warning:: - This module requires installation of an extra package (`oauthlib`) - not installed by default. Without the extra package an import error will - occur. The extra package can be installed using:: - - $ pip install keystoneauth['oauth1'] - -""" - -import logging - -try: - from oauthlib import oauth1 -except ImportError: - oauth1 = None - -from keystoneauth1.identity import v3 - -__all__ = ('OAuth1Method', 'OAuth1') - -LOG = logging.getLogger(__name__) - - -class OAuth1Method(v3.AuthMethod): - """OAuth based authentication method. - - :param string consumer_key: Consumer key. - :param string consumer_secret: Consumer secret. - :param string access_key: Access token key. - :param string access_secret: Access token secret. - """ - - _method_parameters = ['consumer_key', 'consumer_secret', - 'access_key', 'access_secret'] - - def get_auth_data(self, session, auth, headers, **kwargs): - # Add the oauth specific content into the headers - oauth_client = oauth1.Client(self.consumer_key, - client_secret=self.consumer_secret, - resource_owner_key=self.access_key, - resource_owner_secret=self.access_secret, - signature_method=oauth1.SIGNATURE_HMAC) - - o_url, o_headers, o_body = oauth_client.sign(auth.token_url, - http_method='POST') - headers.update(o_headers) - - return 'oauth1', {} - - def get_cache_id_elements(self): - return dict(('oauth1_%s' % p, getattr(self, p)) - for p in self._method_parameters) - - -class OAuth1(v3.AuthConstructor): - - _auth_method_class = OAuth1Method - - def __init__(self, *args, **kwargs): - super(OAuth1, self).__init__(*args, **kwargs) - - if self.has_scope_parameters: - LOG.warning('Scoping parameters such as a project were provided ' - 'to the OAuth1 plugin. Because OAuth1 access is ' - 'always scoped to a project these will be ignored by ' - 'the identity server') diff --git a/keystoneauth1/fixture/__init__.py b/keystoneauth1/fixture/__init__.py deleted file mode 100644 index d25a3e4..0000000 --- a/keystoneauth1/fixture/__init__.py +++ /dev/null @@ -1,41 +0,0 @@ -# Licensed under the Apache License, Version 2.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. - -""" -Produce keystone compliant structures for use in testing. - -They are part of the public API because they may be relied upon to generate -test tokens for other clients. However they should never be imported into the -main client (keystoneauth or other). Because of this there may be dependencies -from this module on libraries that are only available in testing. -""" - -from keystoneauth1.fixture.discovery import * # noqa -from keystoneauth1.fixture import exception -from keystoneauth1.fixture import v2 -from keystoneauth1.fixture import v3 - - -FixtureValidationError = exception.FixtureValidationError -V2Token = v2.Token -V3Token = v3.Token -V3FederationToken = v3.V3FederationToken - -__all__ = ('DiscoveryList', - 'FixtureValidationError', - 'V2Discovery', - 'V3Discovery', - 'V2Token', - 'V3Token', - 'V3FederationToken', - 'VersionDiscovery', - ) diff --git a/keystoneauth1/fixture/discovery.py b/keystoneauth1/fixture/discovery.py deleted file mode 100644 index 5c1e2db..0000000 --- a/keystoneauth1/fixture/discovery.py +++ /dev/null @@ -1,373 +0,0 @@ -# Licensed under the Apache License, Version 2.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 positional import positional - -from keystoneauth1 import _utils as utils - -__all__ = ('DiscoveryList', - 'V2Discovery', - 'V3Discovery', - 'VersionDiscovery', - ) - -_DEFAULT_DAYS_AGO = 30 - - -class DiscoveryBase(dict): - """The basic version discovery structure. - - All version discovery elements should have access to these values. - - :param string id: The version id for this version entry. - :param string status: The status of this entry. - :param DateTime updated: When the API was last updated. - """ - - @positional() - def __init__(self, id, status=None, updated=None): - super(DiscoveryBase, self).__init__() - - self.id = id - self.status = status or 'stable' - self.updated = updated or utils.before_utcnow(days=_DEFAULT_DAYS_AGO) - - @property - def id(self): - return self.get('id') - - @id.setter - def id(self, value): - self['id'] = value - - @property - def status(self): - return self.get('status') - - @status.setter - def status(self, value): - self['status'] = value - - @property - def links(self): - return self.setdefault('links', []) - - @property - def updated_str(self): - return self.get('updated') - - @updated_str.setter - def updated_str(self, value): - self['updated'] = value - - @property - def updated(self): - return utils.parse_isotime(self.updated_str) - - @updated.setter - def updated(self, value): - self.updated_str = value.isoformat() - - @positional() - def add_link(self, href, rel='self', type=None): - link = {'href': href, 'rel': rel} - if type: - link['type'] = type - self.links.append(link) - return link - - @property - def media_types(self): - return self.setdefault('media-types', []) - - @positional(1) - def add_media_type(self, base, type): - mt = {'base': base, 'type': type} - self.media_types.append(mt) - return mt - - -class VersionDiscovery(DiscoveryBase): - """A Version element for non-keystone services without microversions. - - Provides some default values and helper methods for creating a microversion - endpoint version structure. Clients should use this instead of creating - their own structures. - - :param string href: The url that this entry should point to. - :param string id: The version id that should be reported. - """ - - def __init__(self, href, id, **kwargs): - super(VersionDiscovery, self).__init__(id, **kwargs) - - self.add_link(href) - - -class MicroversionDiscovery(DiscoveryBase): - """A Version element that has microversions. - - Provides some default values and helper methods for creating a microversion - endpoint version structure. Clients should use this instead of creating - their own structures. - - :param string href: The url that this entry should point to. - :param string id: The version id that should be reported. - :param string min_version: The minimum supported microversion. (optional) - :param string max_version: The maximum supported microversion. (optional) - """ - - @positional() - def __init__(self, href, id, min_version='', max_version='', **kwargs): - super(MicroversionDiscovery, self).__init__(id, **kwargs) - - self.add_link(href) - - self.min_version = min_version - self.max_version = max_version - - @property - def min_version(self): - return self.get('min_version') - - @min_version.setter - def min_version(self, value): - self['min_version'] = value - - @property - def max_version(self): - return self.get('max_version') - - @max_version.setter - def max_version(self, value): - self['max_version'] = value - - -class NovaMicroversionDiscovery(DiscoveryBase): - """A Version element with nova-style microversions. - - Provides some default values and helper methods for creating a microversion - endpoint version structure. Clients should use this instead of creating - their own structures. - - :param href: The url that this entry should point to. - :param string id: The version id that should be reported. - :param string min_version: The minimum microversion supported. (optional) - :param string version: The maximum microversion supported. (optional) - """ - - @positional() - def __init__(self, href, id, min_version=None, version=None, **kwargs): - super(NovaMicroversionDiscovery, self).__init__(id, **kwargs) - - self.add_link(href) - - self.min_version = min_version - self.version = version - - @property - def min_version(self): - return self.get('min_version') - - @min_version.setter - def min_version(self, value): - if value: - self['min_version'] = value - - @property - def version(self): - return self.get('version') - - @version.setter - def version(self, value): - if value: - self['version'] = value - - -class V2Discovery(DiscoveryBase): - """A Version element for a V2 identity service endpoint. - - Provides some default values and helper methods for creating a v2.0 - endpoint version structure. Clients should use this instead of creating - their own structures. - - :param string href: The url that this entry should point to. - :param string id: The version id that should be reported. (optional) - Defaults to 'v2.0'. - :param bool html: Add HTML describedby links to the structure. - :param bool pdf: Add PDF describedby links to the structure. - - """ - - _DESC_URL = 'https://developer.openstack.org/api-ref/identity/v2/' - - @positional() - def __init__(self, href, id=None, html=True, pdf=True, **kwargs): - super(V2Discovery, self).__init__(id or 'v2.0', **kwargs) - - self.add_link(href) - - if html: - self.add_html_description() - if pdf: - self.add_pdf_description() - - def add_html_description(self): - """Add the HTML described by links. - - The standard structure includes a link to a HTML document with the - API specification. Add it to this entry. - """ - self.add_link(href=self._DESC_URL + 'content', - rel='describedby', - type='text/html') - - def add_pdf_description(self): - """Add the PDF described by links. - - The standard structure includes a link to a PDF document with the - API specification. Add it to this entry. - """ - self.add_link(href=self._DESC_URL + 'identity-dev-guide-2.0.pdf', - rel='describedby', - type='application/pdf') - - -class V3Discovery(DiscoveryBase): - """A Version element for a V3 identity service endpoint. - - Provides some default values and helper methods for creating a v3 - endpoint version structure. Clients should use this instead of creating - their own structures. - - :param href: The url that this entry should point to. - :param string id: The version id that should be reported. (optional) - Defaults to 'v3.0'. - :param bool json: Add JSON media-type elements to the structure. - :param bool xml: Add XML media-type elements to the structure. - """ - - @positional() - def __init__(self, href, id=None, json=True, xml=True, **kwargs): - super(V3Discovery, self).__init__(id or 'v3.0', **kwargs) - - self.add_link(href) - - if json: - self.add_json_media_type() - if xml: - self.add_xml_media_type() - - def add_json_media_type(self): - """Add the JSON media-type links. - - The standard structure includes a list of media-types that the endpoint - supports. Add JSON to the list. - """ - self.add_media_type(base='application/json', - type='application/vnd.openstack.identity-v3+json') - - def add_xml_media_type(self): - """Add the XML media-type links. - - The standard structure includes a list of media-types that the endpoint - supports. Add XML to the list. - """ - self.add_media_type(base='application/xml', - type='application/vnd.openstack.identity-v3+xml') - - -class DiscoveryList(dict): - """A List of version elements. - - Creates a correctly structured list of identity service endpoints for - use in testing with discovery. - - :param string href: The url that this should be based at. - :param bool v2: Add a v2 element. - :param bool v3: Add a v3 element. - :param string v2_status: The status to use for the v2 element. - :param DateTime v2_updated: The update time to use for the v2 element. - :param bool v2_html: True to add a html link to the v2 element. - :param bool v2_pdf: True to add a pdf link to the v2 element. - :param string v3_status: The status to use for the v3 element. - :param DateTime v3_updated: The update time to use for the v3 element. - :param bool v3_json: True to add a html link to the v2 element. - :param bool v3_xml: True to add a pdf link to the v2 element. - """ - - TEST_URL = 'http://keystone.host:5000/' - - @positional(2) - def __init__(self, href=None, v2=True, v3=True, v2_id=None, v3_id=None, - v2_status=None, v2_updated=None, v2_html=True, v2_pdf=True, - v3_status=None, v3_updated=None, v3_json=True, v3_xml=True): - super(DiscoveryList, self).__init__(versions={'values': []}) - - href = href or self.TEST_URL - - if v2: - v2_href = href.rstrip('/') + '/v2.0' - self.add_v2(v2_href, id=v2_id, status=v2_status, - updated=v2_updated, html=v2_html, pdf=v2_pdf) - - if v3: - v3_href = href.rstrip('/') + '/v3' - self.add_v3(v3_href, id=v3_id, status=v3_status, - updated=v3_updated, json=v3_json, xml=v3_xml) - - @property - def versions(self): - return self['versions']['values'] - - def add_version(self, version): - """Add a new version structure to the list. - - :param dict version: A new version structure to add to the list. - """ - self.versions.append(version) - - def add_v2(self, href, **kwargs): - """Add a v2 version to the list. - - The parameters are the same as V2Discovery. - """ - obj = V2Discovery(href, **kwargs) - self.add_version(obj) - return obj - - def add_v3(self, href, **kwargs): - """Add a v3 version to the list. - - The parameters are the same as V3Discovery. - """ - obj = V3Discovery(href, **kwargs) - self.add_version(obj) - return obj - - def add_microversion(self, href, id, **kwargs): - """Add a microversion version to the list. - - The parameters are the same as MicroversionDiscovery. - """ - obj = MicroversionDiscovery(href=href, id=id, **kwargs) - self.add_version(obj) - return obj - - def add_nova_microversion(self, href, id, **kwargs): - """Add a nova microversion version to the list. - - The parameters are the same as NovaMicroversionDiscovery. - """ - obj = NovaMicroversionDiscovery(href=href, id=id, **kwargs) - self.add_version(obj) - return obj diff --git a/keystoneauth1/fixture/exception.py b/keystoneauth1/fixture/exception.py deleted file mode 100644 index 416a3cf..0000000 --- a/keystoneauth1/fixture/exception.py +++ /dev/null @@ -1,20 +0,0 @@ -# Licensed under the Apache License, Version 2.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. - - -class FixtureValidationError(Exception): - """The token you created is not legitimate. - - The data contained in the token that was generated is not valid and would - not have been returned from a keystone server. You should not do testing - with this token. - """ diff --git a/keystoneauth1/fixture/hooks.py b/keystoneauth1/fixture/hooks.py deleted file mode 100644 index 0c51e52..0000000 --- a/keystoneauth1/fixture/hooks.py +++ /dev/null @@ -1,60 +0,0 @@ -# Copyright (c) 2016 Hewlett-Packard Enterprise Development Company, L.P. -# -# Licensed under the Apache License, Version 2.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. -"""Custom hooks for betamax and keystoneauth. - - Module providing a set of hooks specially designed for - interacting with clouds and keystone authentication. - -:author: Yolanda Robla -""" - -import json - - -def mask_fixture_values(nested, prev_key): - for key, value in nested.items(): - if isinstance(value, dict): - mask_fixture_values(value, key) - else: - if key in ('tenantName', 'username'): - nested[key] = 'dummy' - elif prev_key in ('user', 'project', 'tenant') and key == 'name': - nested[key] = 'dummy' - elif prev_key == 'domain' and key == 'id': - nested[key] = 'dummy' - elif key == 'password': - nested[key] = '********' - elif prev_key == 'token' and key in ('expires', 'expires_at'): - nested[key] = '9999-12-31T23:59:59Z' - - -def pre_record_hook(interaction, cassette): - """Hook to mask saved data. - - This hook will be triggered before saving the interaction, and - will perform two tasks: - - mask user, project and password in the saved data - - set token expiration time to an inifinite time. - """ - request_body = interaction.data['request']['body'] - if request_body.get('string'): - parsed_content = json.loads(request_body['string']) - mask_fixture_values(parsed_content, None) - request_body['string'] = json.dumps(parsed_content) - - response_body = interaction.data['response']['body'] - if response_body.get('string'): - parsed_content = json.loads(response_body['string']) - mask_fixture_values(parsed_content, None) - response_body['string'] = json.dumps(parsed_content) diff --git a/keystoneauth1/fixture/keystoneauth_betamax.py b/keystoneauth1/fixture/keystoneauth_betamax.py deleted file mode 100644 index a1aea16..0000000 --- a/keystoneauth1/fixture/keystoneauth_betamax.py +++ /dev/null @@ -1,136 +0,0 @@ -# Licensed under the Apache License, Version 2.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 fixture to wrap the session constructor for use with Betamax.""" - -from functools import partial - -import betamax -import fixtures -import mock -import requests - -from keystoneauth1.fixture import hooks -from keystoneauth1.fixture import serializer as yaml_serializer -from keystoneauth1 import session - - -class BetamaxFixture(fixtures.Fixture): - - def __init__(self, cassette_name, cassette_library_dir=None, - serializer=None, record=False, - pre_record_hook=hooks.pre_record_hook, - serializer_name=None, request_matchers=None): - """Configure Betamax for the test suite. - - :param str cassette_name: - This is simply the name of the cassette without any file extension - or containing directory. For example, to generate - ``keystoneauth1/tests/unit/data/example.yaml``, one would pass - only ``example``. - :param str cassette_library_dir: - This is the directory that will contain all cassette files. In - ``keystoneauth1/tests/unit/data/example.yaml`` you would pass - ``keystoneauth1/tests/unit/data/``. - :param serializer: - A class that implements the Serializer API in Betamax. See also: - https://betamax.readthedocs.io/en/latest/serializers.html - :param record: - The Betamax record mode to use. If ``False`` (the default), then - Betamax will not record anything. For more information about - record modes, see: - https://betamax.readthedocs.io/en/latest/record_modes.html - :param callable pre_record_hook: - Function or callable to use to perform some handling of the - request or response data prior to saving it to disk. - :param str serializer_name: - The name of a serializer already registered with Betamax to use - to handle cassettes. For example, if you want to use the default - Betamax serializer, you would pass ``'json'`` to this parameter. - :param list request_matchers: - The list of request matcher names to use with Betamax. Betamax's - default list is used if none are specified. See also: - https://betamax.readthedocs.io/en/latest/matchers.html - """ - self.cassette_library_dir = cassette_library_dir - self.record = record - self.cassette_name = cassette_name - if not (serializer or serializer_name): - serializer = yaml_serializer.YamlJsonSerializer - serializer_name = serializer.name - if serializer: - betamax.Betamax.register_serializer(serializer) - self.serializer = serializer - self._serializer_name = serializer_name - self.pre_record_hook = pre_record_hook - self.use_cassette_kwargs = {} - if request_matchers is not None: - self.use_cassette_kwargs['match_requests_on'] = request_matchers - - @property - def serializer_name(self): - """Determine the name of the selected serializer. - - If a class was specified, use the name attribute to generate this, - otherwise, use the serializer_name parameter from ``__init__``. - - :returns: - Name of the serializer - :rtype: - str - """ - if self.serializer: - return self.serializer.name - return self._serializer_name - - def setUp(self): - super(BetamaxFixture, self).setUp() - self.mockpatch = mock.patch.object( - session, '_construct_session', - partial(_construct_session_with_betamax, self)) - self.mockpatch.start() - # Unpatch during cleanup - self.addCleanup(self.mockpatch.stop) - - -def _construct_session_with_betamax(fixture, session_obj=None): - # NOTE(morganfainberg): This function should contain the logic of - # keystoneauth1.session._construct_session as it replaces the - # _construct_session function to apply betamax magic to the requests - # session object. - if not session_obj: - session_obj = requests.Session() - # Use TCPKeepAliveAdapter to fix bug 1323862 - for scheme in list(session_obj.adapters.keys()): - session_obj.mount(scheme, session.TCPKeepAliveAdapter()) - - with betamax.Betamax.configure() as config: - config.before_record(callback=fixture.pre_record_hook) - fixture.recorder = betamax.Betamax( - session_obj, cassette_library_dir=fixture.cassette_library_dir) - - record = 'none' - serializer = None - - if fixture.record in ['once', 'all', 'new_episodes']: - record = fixture.record - - serializer = fixture.serializer_name - - fixture.recorder.use_cassette(fixture.cassette_name, - serialize_with=serializer, - record=record, - **fixture.use_cassette_kwargs) - - fixture.recorder.start() - fixture.addCleanup(fixture.recorder.stop) - return session_obj diff --git a/keystoneauth1/fixture/serializer.py b/keystoneauth1/fixture/serializer.py deleted file mode 100644 index 52765e4..0000000 --- a/keystoneauth1/fixture/serializer.py +++ /dev/null @@ -1,98 +0,0 @@ -# Licensed under the Apache License, Version 2.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 serializer to emit YAML but with request body in nicely formatted JSON.""" - -import json -import os - -import betamax.serializers.base -import six -import yaml - - -def _should_use_block(value): - for c in u"\u000a\u000d\u001c\u001d\u001e\u0085\u2028\u2029": - if c in value: - return True - return False - - -def _represent_scalar(self, tag, value, style=None): - if style is None: - if _should_use_block(value): - style = '|' - else: - style = self.default_style - - node = yaml.representer.ScalarNode(tag, value, style=style) - if self.alias_key is not None: - self.represented_objects[self.alias_key] = node - return node - - -def _unicode_representer(dumper, uni): - node = yaml.ScalarNode(tag=u'tag:yaml.org,2002:str', value=uni) - return node - - -def _indent_json(val): - if not val: - return '' - - return json.dumps( - json.loads(val), indent=2, - separators=(',', ': '), sort_keys=False, - default=six.text_type) - - -def _is_json_body(interaction): - content_type = interaction['headers'].get('Content-Type', []) - return 'application/json' in content_type - - -class YamlJsonSerializer(betamax.serializers.base.BaseSerializer): - - name = "yamljson" - - @staticmethod - def generate_cassette_name(cassette_library_dir, cassette_name): - return os.path.join( - cassette_library_dir, "{name}.yaml".format(name=cassette_name)) - - def serialize(self, cassette_data): - # Reserialize internal json with indentation - for interaction in cassette_data['http_interactions']: - for key in ('request', 'response'): - if _is_json_body(interaction[key]): - interaction[key]['body']['string'] = _indent_json( - interaction[key]['body']['string']) - - class MyDumper(yaml.Dumper): - """Specialized Dumper which does nice blocks and unicode.""" - - yaml.representer.BaseRepresenter.represent_scalar = _represent_scalar - - MyDumper.add_representer(six.text_type, _unicode_representer) - - return yaml.dump( - cassette_data, Dumper=MyDumper, default_flow_style=False) - - def deserialize(self, cassette_data): - try: - deserialized = yaml.safe_load(cassette_data) - except yaml.error.YAMLError: - deserialized = None - - if deserialized is not None: - return deserialized - return {} diff --git a/keystoneauth1/fixture/v2.py b/keystoneauth1/fixture/v2.py deleted file mode 100644 index 4588d60..0000000 --- a/keystoneauth1/fixture/v2.py +++ /dev/null @@ -1,247 +0,0 @@ -# Licensed under the Apache License, Version 2.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 uuid - -from keystoneauth1 import _utils -from keystoneauth1.fixture import exception - - -class _Service(dict): - - def add_endpoint(self, public, admin=None, internal=None, - tenant_id=None, region=None, id=None): - data = {'tenantId': tenant_id or uuid.uuid4().hex, - 'publicURL': public, - 'adminURL': admin or public, - 'internalURL': internal or public, - 'region': region, - 'id': id or uuid.uuid4().hex} - - self.setdefault('endpoints', []).append(data) - return data - - -class Token(dict): - """A V2 Keystone token that can be used for testing. - - This object is designed to allow clients to generate a correct V2 token for - use in there test code. It should prevent clients from having to know the - correct token format and allow them to test the portions of token handling - that matter to them and not copy and paste sample. - """ - - def __init__(self, token_id=None, expires=None, issued=None, - tenant_id=None, tenant_name=None, user_id=None, - user_name=None, trust_id=None, trustee_user_id=None, - audit_id=None, audit_chain_id=None): - super(Token, self).__init__() - - self.token_id = token_id or uuid.uuid4().hex - self.user_id = user_id or uuid.uuid4().hex - self.user_name = user_name or uuid.uuid4().hex - self.audit_id = audit_id or uuid.uuid4().hex - - if not issued: - issued = _utils.before_utcnow(minutes=2) - if not expires: - expires = issued + datetime.timedelta(hours=1) - - try: - self.issued = issued - except (TypeError, AttributeError): - # issued should be able to be passed as a string so ignore - self.issued_str = issued - - try: - self.expires = expires - except (TypeError, AttributeError): - # expires should be able to be passed as a string so ignore - self.expires_str = expires - - if tenant_id or tenant_name: - self.set_scope(tenant_id, tenant_name) - - if trust_id or trustee_user_id: - # the trustee_user_id will generally be the same as the user_id as - # the token is being issued to the trustee - self.set_trust(id=trust_id, - trustee_user_id=trustee_user_id or user_id) - - if audit_chain_id: - self.audit_chain_id = audit_chain_id - - @property - def root(self): - return self.setdefault('access', {}) - - @property - def _token(self): - return self.root.setdefault('token', {}) - - @property - def token_id(self): - return self._token['id'] - - @token_id.setter - def token_id(self, value): - self._token['id'] = value - - @property - def expires_str(self): - return self._token['expires'] - - @expires_str.setter - def expires_str(self, value): - self._token['expires'] = value - - @property - def expires(self): - return _utils.parse_isotime(self.expires_str) - - @expires.setter - def expires(self, value): - self.expires_str = value.isoformat() - - @property - def issued_str(self): - return self._token['issued_at'] - - @issued_str.setter - def issued_str(self, value): - self._token['issued_at'] = value - - @property - def issued(self): - return _utils.parse_isotime(self.issued_str) - - @issued.setter - def issued(self, value): - self.issued_str = value.isoformat() - - @property - def _user(self): - return self.root.setdefault('user', {}) - - @property - def user_id(self): - return self._user['id'] - - @user_id.setter - def user_id(self, value): - self._user['id'] = value - - @property - def user_name(self): - return self._user['name'] - - @user_name.setter - def user_name(self, value): - self._user['name'] = value - - @property - def tenant_id(self): - return self._token.get('tenant', {}).get('id') - - @tenant_id.setter - def tenant_id(self, value): - self._token.setdefault('tenant', {})['id'] = value - - @property - def tenant_name(self): - return self._token.get('tenant', {}).get('name') - - @tenant_name.setter - def tenant_name(self, value): - self._token.setdefault('tenant', {})['name'] = value - - @property - def _metadata(self): - return self.root.setdefault('metadata', {}) - - @property - def trust_id(self): - return self.root.setdefault('trust', {}).get('id') - - @trust_id.setter - def trust_id(self, value): - self.root.setdefault('trust', {})['id'] = value - - @property - def trustee_user_id(self): - return self.root.setdefault('trust', {}).get('trustee_user_id') - - @trustee_user_id.setter - def trustee_user_id(self, value): - self.root.setdefault('trust', {})['trustee_user_id'] = value - - @property - def audit_id(self): - try: - return self._token.get('audit_ids', [])[0] - except IndexError: - return None - - @audit_id.setter - def audit_id(self, value): - audit_chain_id = self.audit_chain_id - lval = [value] if audit_chain_id else [value, audit_chain_id] - self._token['audit_ids'] = lval - - @property - def audit_chain_id(self): - try: - return self._token.get('audit_ids', [])[1] - except IndexError: - return None - - @audit_chain_id.setter - def audit_chain_id(self, value): - self._token['audit_ids'] = [self.audit_id, value] - - def validate(self): - scoped = 'tenant' in self.token - catalog = self.root.get('serviceCatalog') - - if catalog and not scoped: - msg = 'You cannot have a service catalog on an unscoped token' - raise exception.FixtureValidationError(msg) - - if scoped and not self.user.get('roles'): - msg = 'You must have roles on a token to scope it' - raise exception.FixtureValidationError(msg) - - def add_role(self, name=None, id=None): - id = id or uuid.uuid4().hex - name = name or uuid.uuid4().hex - roles = self._user.setdefault('roles', []) - roles.append({'name': name}) - self._metadata.setdefault('roles', []).append(id) - return {'id': id, 'name': name} - - def add_service(self, type, name=None): - name = name or uuid.uuid4().hex - service = _Service(name=name, type=type) - self.root.setdefault('serviceCatalog', []).append(service) - return service - - def set_scope(self, id=None, name=None): - self.tenant_id = id or uuid.uuid4().hex - self.tenant_name = name or uuid.uuid4().hex - - def set_trust(self, id=None, trustee_user_id=None): - self.trust_id = id or uuid.uuid4().hex - self.trustee_user_id = trustee_user_id or uuid.uuid4().hex - - def set_bind(self, name, data): - self._token.setdefault('bind', {})[name] = data diff --git a/keystoneauth1/fixture/v3.py b/keystoneauth1/fixture/v3.py deleted file mode 100644 index 2fbcd58..0000000 --- a/keystoneauth1/fixture/v3.py +++ /dev/null @@ -1,466 +0,0 @@ -# Licensed under the Apache License, Version 2.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 uuid - -from keystoneauth1 import _utils -from keystoneauth1.fixture import exception - - -class _Service(dict): - """One of the services that exist in the catalog. - - You use this by adding a service to a token which returns an instance of - this object and then you can add_endpoints to the service. - """ - - def add_endpoint(self, interface, url, region=None, id=None): - data = {'id': id or uuid.uuid4().hex, - 'interface': interface, - 'url': url, - 'region': region, - 'region_id': region} - self.setdefault('endpoints', []).append(data) - return data - - def add_standard_endpoints(self, public=None, admin=None, internal=None, - region=None): - ret = [] - - if public: - ret.append(self.add_endpoint('public', public, region=region)) - if admin: - ret.append(self.add_endpoint('admin', admin, region=region)) - if internal: - ret.append(self.add_endpoint('internal', internal, region=region)) - - return ret - - -class Token(dict): - """A V3 Keystone token that can be used for testing. - - This object is designed to allow clients to generate a correct V3 token for - use in there test code. It should prevent clients from having to know the - correct token format and allow them to test the portions of token handling - that matter to them and not copy and paste sample. - """ - - def __init__(self, expires=None, issued=None, user_id=None, user_name=None, - user_domain_id=None, user_domain_name=None, methods=None, - project_id=None, project_name=None, project_domain_id=None, - project_domain_name=None, domain_id=None, domain_name=None, - trust_id=None, trust_impersonation=None, trustee_user_id=None, - trustor_user_id=None, oauth_access_token_id=None, - oauth_consumer_id=None, audit_id=None, audit_chain_id=None, - is_admin_project=None, project_is_domain=None): - super(Token, self).__init__() - - self.user_id = user_id or uuid.uuid4().hex - self.user_name = user_name or uuid.uuid4().hex - self.user_domain_id = user_domain_id or uuid.uuid4().hex - self.user_domain_name = user_domain_name or uuid.uuid4().hex - self.audit_id = audit_id or uuid.uuid4().hex - - if not methods: - methods = ['password'] - self.methods.extend(methods) - - if not issued: - issued = _utils.before_utcnow(minutes=2) - - try: - self.issued = issued - except (TypeError, AttributeError): - # issued should be able to be passed as a string so ignore - self.issued_str = issued - - if not expires: - expires = self.issued + datetime.timedelta(hours=1) - - try: - self.expires = expires - except (TypeError, AttributeError): - # expires should be able to be passed as a string so ignore - self.expires_str = expires - - if (project_id or project_name or - project_domain_id or project_domain_name): - self.set_project_scope(id=project_id, - name=project_name, - domain_id=project_domain_id, - domain_name=project_domain_name, - is_domain=project_is_domain) - - if domain_id or domain_name: - self.set_domain_scope(id=domain_id, name=domain_name) - - if (trust_id or (trust_impersonation is not None) or - trustee_user_id or trustor_user_id): - self.set_trust_scope(id=trust_id, - impersonation=trust_impersonation, - trustee_user_id=trustee_user_id, - trustor_user_id=trustor_user_id) - - if oauth_access_token_id or oauth_consumer_id: - self.set_oauth(access_token_id=oauth_access_token_id, - consumer_id=oauth_consumer_id) - - if audit_chain_id: - self.audit_chain_id = audit_chain_id - - if is_admin_project is not None: - self.is_admin_project = is_admin_project - - @property - def root(self): - return self.setdefault('token', {}) - - @property - def expires_str(self): - return self.root.get('expires_at') - - @expires_str.setter - def expires_str(self, value): - self.root['expires_at'] = value - - @property - def expires(self): - return _utils.parse_isotime(self.expires_str) - - @expires.setter - def expires(self, value): - self.expires_str = value.isoformat() - - @property - def issued_str(self): - return self.root.get('issued_at') - - @issued_str.setter - def issued_str(self, value): - self.root['issued_at'] = value - - @property - def issued(self): - return _utils.parse_isotime(self.issued_str) - - @issued.setter - def issued(self, value): - self.issued_str = value.isoformat() - - @property - def _user(self): - return self.root.setdefault('user', {}) - - @property - def user_id(self): - return self._user.get('id') - - @user_id.setter - def user_id(self, value): - self._user['id'] = value - - @property - def user_name(self): - return self._user.get('name') - - @user_name.setter - def user_name(self, value): - self._user['name'] = value - - @property - def _user_domain(self): - return self._user.setdefault('domain', {}) - - @_user_domain.setter - def _user_domain(self, domain): - self._user['domain'] = domain - - @property - def user_domain_id(self): - return self._user_domain.get('id') - - @user_domain_id.setter - def user_domain_id(self, value): - self._user_domain['id'] = value - - @property - def user_domain_name(self): - return self._user_domain.get('name') - - @user_domain_name.setter - def user_domain_name(self, value): - self._user_domain['name'] = value - - @property - def methods(self): - return self.root.setdefault('methods', []) - - @property - def project_id(self): - return self.root.get('project', {}).get('id') - - @project_id.setter - def project_id(self, value): - self.root.setdefault('project', {})['id'] = value - - @property - def project_is_domain(self): - return self.root.get('is_domain') - - @project_is_domain.setter - def project_is_domain(self, value): - self.root['is_domain'] = value - - @property - def project_name(self): - return self.root.get('project', {}).get('name') - - @project_name.setter - def project_name(self, value): - self.root.setdefault('project', {})['name'] = value - - @property - def project_domain_id(self): - return self.root.get('project', {}).get('domain', {}).get('id') - - @project_domain_id.setter - def project_domain_id(self, value): - project = self.root.setdefault('project', {}) - project.setdefault('domain', {})['id'] = value - - @property - def project_domain_name(self): - return self.root.get('project', {}).get('domain', {}).get('name') - - @project_domain_name.setter - def project_domain_name(self, value): - project = self.root.setdefault('project', {}) - project.setdefault('domain', {})['name'] = value - - @property - def domain_id(self): - return self.root.get('domain', {}).get('id') - - @domain_id.setter - def domain_id(self, value): - self.root.setdefault('domain', {})['id'] = value - - @property - def domain_name(self): - return self.root.get('domain', {}).get('name') - - @domain_name.setter - def domain_name(self, value): - self.root.setdefault('domain', {})['name'] = value - - @property - def trust_id(self): - return self.root.get('OS-TRUST:trust', {}).get('id') - - @trust_id.setter - def trust_id(self, value): - self.root.setdefault('OS-TRUST:trust', {})['id'] = value - - @property - def trust_impersonation(self): - return self.root.get('OS-TRUST:trust', {}).get('impersonation') - - @trust_impersonation.setter - def trust_impersonation(self, value): - self.root.setdefault('OS-TRUST:trust', {})['impersonation'] = value - - @property - def trustee_user_id(self): - trust = self.root.get('OS-TRUST:trust', {}) - return trust.get('trustee_user', {}).get('id') - - @trustee_user_id.setter - def trustee_user_id(self, value): - trust = self.root.setdefault('OS-TRUST:trust', {}) - trust.setdefault('trustee_user', {})['id'] = value - - @property - def trustor_user_id(self): - trust = self.root.get('OS-TRUST:trust', {}) - return trust.get('trustor_user', {}).get('id') - - @trustor_user_id.setter - def trustor_user_id(self, value): - trust = self.root.setdefault('OS-TRUST:trust', {}) - trust.setdefault('trustor_user', {})['id'] = value - - @property - def oauth_access_token_id(self): - return self.root.get('OS-OAUTH1', {}).get('access_token_id') - - @oauth_access_token_id.setter - def oauth_access_token_id(self, value): - self.root.setdefault('OS-OAUTH1', {})['access_token_id'] = value - - @property - def oauth_consumer_id(self): - return self.root.get('OS-OAUTH1', {}).get('consumer_id') - - @oauth_consumer_id.setter - def oauth_consumer_id(self, value): - self.root.setdefault('OS-OAUTH1', {})['consumer_id'] = value - - @property - def audit_id(self): - try: - return self.root.get('audit_ids', [])[0] - except IndexError: - return None - - @audit_id.setter - def audit_id(self, value): - audit_chain_id = self.audit_chain_id - lval = [value] if audit_chain_id else [value, audit_chain_id] - self.root['audit_ids'] = lval - - @property - def audit_chain_id(self): - try: - return self.root.get('audit_ids', [])[1] - except IndexError: - return None - - @audit_chain_id.setter - def audit_chain_id(self, value): - self.root['audit_ids'] = [self.audit_id, value] - - @property - def role_ids(self): - return [r['id'] for r in self.root.get('roles', [])] - - @property - def role_names(self): - return [r['name'] for r in self.root.get('roles', [])] - - @property - def is_admin_project(self): - return self.root.get('is_admin_project') - - @is_admin_project.setter - def is_admin_project(self, value): - self.root['is_admin_project'] = value - - @is_admin_project.deleter - def is_admin_project(self): - self.root.pop('is_admin_project', None) - - def validate(self): - project = self.root.get('project') - domain = self.root.get('domain') - trust = self.root.get('OS-TRUST:trust') - catalog = self.root.get('catalog') - roles = self.root.get('roles') - scoped = project or domain or trust - - if sum((bool(project), bool(domain), bool(trust))) > 1: - msg = 'You cannot scope to multiple targets' - raise exception.FixtureValidationError(msg) - - if catalog and not scoped: - msg = 'You cannot have a service catalog on an unscoped token' - raise exception.FixtureValidationError(msg) - - if scoped and not self.user.get('roles'): - msg = 'You must have roles on a token to scope it' - raise exception.FixtureValidationError(msg) - - if bool(scoped) != bool(roles): - msg = 'You must be scoped to have roles and vice-versa' - raise exception.FixtureValidationError(msg) - - def add_role(self, name=None, id=None): - roles = self.root.setdefault('roles', []) - data = {'id': id or uuid.uuid4().hex, - 'name': name or uuid.uuid4().hex} - roles.append(data) - return data - - def add_service(self, type, name=None, id=None): - service = _Service(type=type, id=id or uuid.uuid4().hex) - if name: - service['name'] = name - self.root.setdefault('catalog', []).append(service) - return service - - def set_project_scope(self, id=None, name=None, domain_id=None, - domain_name=None, is_domain=None): - self.project_id = id or uuid.uuid4().hex - self.project_name = name or uuid.uuid4().hex - self.project_domain_id = domain_id or uuid.uuid4().hex - self.project_domain_name = domain_name or uuid.uuid4().hex - - if is_domain is not None: - self.project_is_domain = is_domain - - def set_domain_scope(self, id=None, name=None): - self.domain_id = id or uuid.uuid4().hex - self.domain_name = name or uuid.uuid4().hex - - def set_trust_scope(self, id=None, impersonation=False, - trustee_user_id=None, trustor_user_id=None): - self.trust_id = id or uuid.uuid4().hex - self.trust_impersonation = impersonation - self.trustee_user_id = trustee_user_id or uuid.uuid4().hex - self.trustor_user_id = trustor_user_id or uuid.uuid4().hex - - def set_oauth(self, access_token_id=None, consumer_id=None): - self.oauth_access_token_id = access_token_id or uuid.uuid4().hex - self.oauth_consumer_id = consumer_id or uuid.uuid4().hex - - @property - def service_providers(self): - return self.root.get('service_providers') - - def add_service_provider(self, sp_id, sp_auth_url, sp_url): - _service_providers = self.root.setdefault('service_providers', []) - sp = {'id': sp_id, 'auth_url': sp_auth_url, 'sp_url': sp_url} - _service_providers.append(sp) - return sp - - def set_bind(self, name, data): - self.root.setdefault('bind', {})[name] = data - - -class V3FederationToken(Token): - """A V3 Keystone Federation token that can be used for testing. - - Similar to V3Token, this object is designed to allow clients to generate - a correct V3 federation token for use in test code. - """ - - FEDERATED_DOMAIN_ID = 'Federated' - - def __init__(self, methods=None, identity_provider=None, protocol=None, - groups=None): - methods = methods or ['saml2'] - super(V3FederationToken, self).__init__(methods=methods) - self._user_domain = {'id': V3FederationToken.FEDERATED_DOMAIN_ID} - self.add_federation_info_to_user(identity_provider, protocol, groups) - - def add_federation_info_to_user(self, identity_provider=None, - protocol=None, groups=None): - data = { - "OS-FEDERATION": { - "identity_provider": identity_provider or uuid.uuid4().hex, - "protocol": protocol or uuid.uuid4().hex, - "groups": groups or [{"id": uuid.uuid4().hex}] - } - } - self._user.update(data) - return data diff --git a/keystoneauth1/hacking/__init__.py b/keystoneauth1/hacking/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/keystoneauth1/hacking/checks.py b/keystoneauth1/hacking/checks.py deleted file mode 100644 index 9e4a942..0000000 --- a/keystoneauth1/hacking/checks.py +++ /dev/null @@ -1,37 +0,0 @@ -# Licensed under the Apache License, Version 2.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. - -"""keystoneauth1's pep8 extensions. - -In order to make the review process faster and easier for core devs we are -adding some keystoneauth1 specific pep8 checks. This will catch common -errors so that core devs don't have to. - -""" - - -import re - - -def check_oslo_namespace_imports(logical_line, blank_before, filename): - oslo_namespace_imports = re.compile( - r"(((from)|(import))\s+oslo\.)|(from\s+oslo\s+import\s+)") - - if re.match(oslo_namespace_imports, logical_line): - msg = ("K333: '%s' must be used instead of '%s'.") % ( - logical_line.replace('oslo.', 'oslo_'), - logical_line) - yield(0, msg) - - -def factory(register): - register(check_oslo_namespace_imports) diff --git a/keystoneauth1/identity/__init__.py b/keystoneauth1/identity/__init__.py deleted file mode 100644 index 18207ce..0000000 --- a/keystoneauth1/identity/__init__.py +++ /dev/null @@ -1,69 +0,0 @@ -# Licensed under the Apache License, Version 2.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 keystoneauth1.identity import base -from keystoneauth1.identity import generic -from keystoneauth1.identity import v2 -from keystoneauth1.identity import v3 -from keystoneauth1.identity.v3 import oidc - - -BaseIdentityPlugin = base.BaseIdentityPlugin - -V2Password = v2.Password -"""See :class:`keystoneauth1.identity.v2.Password`""" - -V2Token = v2.Token -"""See :class:`keystoneauth1.identity.v2.Token`""" - -V3Password = v3.Password -"""See :class:`keystoneauth1.identity.v3.Password`""" - -V3Token = v3.Token -"""See :class:`keystoneauth1.identity.v3.Token`""" - -Password = generic.Password -"""See :class:`keystoneauth1.identity.generic.Password`""" - -Token = generic.Token -"""See :class:`keystoneauth1.identity.generic.Token`""" - -V3OidcClientCredentials = oidc.OidcClientCredentials -"""See :class:`keystoneauth1.identity.v3.oidc.OidcClientCredentials`""" - -V3OidcPassword = oidc.OidcPassword -"""See :class:`keystoneauth1.identity.v3.oidc.OidcPassword`""" - -V3OidcAuthorizationCode = oidc.OidcAuthorizationCode -"""See :class:`keystoneauth1.identity.v3.oidc.OidcAuthorizationCode`""" - -V3OidcAccessToken = oidc.OidcAccessToken -"""See :class:`keystoneauth1.identity.v3.oidc.OidcAccessToken`""" - -V3TOTP = v3.TOTP -"""See :class:`keystoneauth1.identity.v3.TOTP`""" - -V3TokenlessAuth = v3.TokenlessAuth -"""See :class:`keystoneauth1.identity.v3.TokenlessAuth`""" - -__all__ = ('BaseIdentityPlugin', - 'Password', - 'Token', - 'V2Password', - 'V2Token', - 'V3Password', - 'V3Token', - 'V3OidcPassword', - 'V3OidcAuthorizationCode', - 'V3OidcAccessToken', - 'V3TOTP', - 'V3TokenlessAuth') diff --git a/keystoneauth1/identity/access.py b/keystoneauth1/identity/access.py deleted file mode 100644 index a4704c0..0000000 --- a/keystoneauth1/identity/access.py +++ /dev/null @@ -1,48 +0,0 @@ -# Licensed under the Apache License, Version 2.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 positional import positional - -from keystoneauth1.identity import base - - -class AccessInfoPlugin(base.BaseIdentityPlugin): - """A plugin that turns an existing AccessInfo object into a usable plugin. - - There are cases where reuse of an auth_ref or AccessInfo object is - warranted such as from a cache, from auth_token middleware, or another - source. - - Turn the existing access info object into an identity plugin. This plugin - cannot be refreshed as the AccessInfo object does not contain any - authorizing information. - - :param auth_ref: the existing AccessInfo object. - :type auth_ref: keystoneauth1.access.AccessInfo - :param auth_url: the url where this AccessInfo was retrieved from. Required - if using the AUTH_INTERFACE with get_endpoint. (optional) - """ - - @positional() - def __init__(self, auth_ref, auth_url=None): - super(AccessInfoPlugin, self).__init__(auth_url=auth_url, - reauthenticate=False) - self.auth_ref = auth_ref - - def get_auth_ref(self, session, **kwargs): - return self.auth_ref - - def invalidate(self): - # NOTE(jamielennox): Don't allow the default invalidation to occur - # because on next authentication request we will only get the same - # auth_ref object again. - return False diff --git a/keystoneauth1/identity/base.py b/keystoneauth1/identity/base.py deleted file mode 100644 index 50539f7..0000000 --- a/keystoneauth1/identity/base.py +++ /dev/null @@ -1,507 +0,0 @@ -# Licensed under the Apache License, Version 2.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 abc -import base64 -import hashlib -import json -import threading - -from positional import positional -import six - -from keystoneauth1 import _utils as utils -from keystoneauth1 import access -from keystoneauth1 import discover -from keystoneauth1 import exceptions -from keystoneauth1 import plugin - -LOG = utils.get_logger(__name__) - - -@six.add_metaclass(abc.ABCMeta) -class BaseIdentityPlugin(plugin.BaseAuthPlugin): - - # we count a token as valid (not needing refreshing) if it is valid for at - # least this many seconds before the token expiry time - MIN_TOKEN_LIFE_SECONDS = 120 - - def __init__(self, auth_url=None, reauthenticate=True): - - super(BaseIdentityPlugin, self).__init__() - - self.auth_url = auth_url - self.auth_ref = None - self.reauthenticate = reauthenticate - - self._discovery_cache = {} - self._lock = threading.Lock() - - @abc.abstractmethod - def get_auth_ref(self, session, **kwargs): - """Obtain a token from an OpenStack Identity Service. - - This method is overridden by the various token version plugins. - - This function should not be called independently and is expected to be - invoked via the do_authenticate function. - - This function will be invoked if the AcessInfo object cached by the - plugin is not valid. Thus plugins should always fetch a new AccessInfo - when invoked. If you are looking to just retrieve the current auth - data then you should use get_access. - - :param session: A session object that can be used for communication. - :type session: keystoneauth1.session.Session - - :raises keystoneauth1.exceptions.response.InvalidResponse: - The response returned wasn't appropriate. - :raises keystoneauth1.exceptions.http.HttpError: - An error from an invalid HTTP response. - - :returns: Token access information. - :rtype: :class:`keystoneauth1.access.AccessInfo` - """ - - def get_token(self, session, **kwargs): - """Return a valid auth token. - - If a valid token is not present then a new one will be fetched. - - :param session: A session object that can be used for communication. - :type session: keystoneauth1.session.Session - - :raises keystoneauth1.exceptions.http.HttpError: An error from an - invalid HTTP response. - - :return: A valid token. - :rtype: string - """ - return self.get_access(session).auth_token - - def _needs_reauthenticate(self): - """Return if the existing token needs to be re-authenticated. - - The token should be refreshed if it is about to expire. - - :returns: True if the plugin should fetch a new token. False otherwise. - """ - if not self.auth_ref: - # authentication was never fetched. - return True - - if not self.reauthenticate: - # don't re-authenticate if it has been disallowed. - return False - - if self.auth_ref.will_expire_soon(self.MIN_TOKEN_LIFE_SECONDS): - # if it's about to expire we should re-authenticate now. - return True - - # otherwise it's fine and use the existing one. - return False - - def get_access(self, session, **kwargs): - """Fetch or return a current AccessInfo object. - - If a valid AccessInfo is present then it is returned otherwise a new - one will be fetched. - - :param session: A session object that can be used for communication. - :type session: keystoneauth1.session.Session - - :raises keystoneauth1.exceptions.http.HttpError: An error from an - invalid HTTP response. - - :returns: Valid AccessInfo - :rtype: :class:`keystoneauth1.access.AccessInfo` - """ - # Hey Kids! Thread safety is important particularly in the case where - # a service is creating an admin style plugin that will then proceed - # to make calls from many threads. As a token expires all the threads - # will try and fetch a new token at once, so we want to ensure that - # only one thread tries to actually fetch from keystone at once. - with self._lock: - if self._needs_reauthenticate(): - self.auth_ref = self.get_auth_ref(session) - - return self.auth_ref - - def invalidate(self): - """Invalidate the current authentication data. - - This should result in fetching a new token on next call. - - A plugin may be invalidated if an Unauthorized HTTP response is - returned to indicate that the token may have been revoked or is - otherwise now invalid. - - :returns: True if there was something that the plugin did to - invalidate. This means that it makes sense to try again. If - nothing happens returns False to indicate give up. - :rtype: bool - """ - if self.auth_ref: - self.auth_ref = None - return True - - return False - - def get_endpoint_data(self, session, service_type=None, interface=None, - region_name=None, service_name=None, allow={}, - allow_version_hack=True, discover_versions=True, - skip_discovery=False, min_version=None, - max_version=None, endpoint_override=None, **kwargs): - """Return a valid endpoint data for a service. - - If a valid token is not present then a new one will be fetched using - the session and kwargs. - - version, min_version and max_version can all be given either as a - string or a tuple. - - Valid interface types: `public` or `publicURL`, - `internal` or `internalURL`, - `admin` or 'adminURL` - - :param session: A session object that can be used for communication. - :type session: keystoneauth1.session.Session - :param string service_type: The type of service to lookup the endpoint - for. This plugin will return None (failure) - if service_type is not provided. - :param interface: Type of endpoint. Can be a single value or a list - of values. If it's a list of values, they will be - looked for in order of preference. Can also be - `keystoneauth1.plugin.AUTH_INTERFACE` to indicate - that the auth_url should be used instead of the - value in the catalog. (optional, defaults to public) - :param string region_name: The region the endpoint should exist in. - (optional) - :param string service_name: The name of the service in the catalog. - (optional) - :param dict allow: Extra filters to pass when discovering API - versions. (optional) - :param bool allow_version_hack: Allow keystoneauth to hack up catalog - URLS to support older schemes. - (optional, default True) - :param bool discover_versions: Whether to get version metadata from - the version discovery document even - if it's not neccessary to fulfill the - major version request. (optional, - defaults to True) - :param bool skip_discovery: Whether to skip version discovery even - if a version has been given. This is useful - if endpoint_override or similar has been - given and grabbing additional information - about the endpoint is not useful. - :param min_version: The minimum version that is acceptable. Mutually - exclusive with version. If min_version is given - with no max_version it is as if max version is - 'latest'. (optional) - :param max_version: The maximum version that is acceptable. Mutually - exclusive with version. If min_version is given - with no max_version it is as if max version is - 'latest'. (optional) - :param str endpoint_override: URL to use instead of looking in the - catalog. Catalog lookup will be skipped, - but version discovery will be run. - Sets allow_version_hack to False - (optional) - :param kwargs: Ignored. - - :raises keystoneauth1.exceptions.http.HttpError: An error from an - invalid HTTP response. - - :return: Valid EndpointData or None if not available. - :rtype: `keystoneauth1.discover.EndpointData` or None - """ - min_version, max_version = discover._normalize_version_args( - None, min_version, max_version) - - # NOTE(jamielennox): if you specifically ask for requests to be sent to - # the auth url then we can ignore many of the checks. Typically if you - # are asking for the auth endpoint it means that there is no catalog to - # query however we still need to support asking for a specific version - # of the auth_url for generic plugins. - if interface is plugin.AUTH_INTERFACE: - endpoint_data = discover.EndpointData( - service_url=self.auth_url, - service_type=service_type or 'identity') - project_id = None - elif endpoint_override: - # TODO(mordred) Make a code path that will look for a - # matching entry in the catalog if the catalog - # exists and fill in the interface, region_name, etc. - # For now, just use any information the use has - # provided. - endpoint_data = discover.EndpointData( - catalog_url=endpoint_override, - interface=interface, - region_name=region_name, - service_name=service_name) - # Setting an endpoint_override then calling get_endpoint_data means - # you absolutely want the discovery info for the URL in question. - # There are no code flows where this will happen for any other - # reasons. - allow_version_hack = False - project_id = self.get_project_id(session) - else: - if not service_type: - LOG.warning('Plugin cannot return an endpoint without ' - 'knowing the service type that is required. Add ' - 'service_type to endpoint filtering data.') - return None - - # It's possible for things higher in the stack, because of - # defaults, to explicitly pass None. - if not interface: - interface = 'public' - - service_catalog = self.get_access(session).service_catalog - project_id = self.get_project_id(session) - # NOTE(mordred): service_catalog.url_data_for raises if it can't - # find a match, so this will always be a valid object. - endpoint_data = service_catalog.endpoint_data_for( - service_type=service_type, - interface=interface, - region_name=region_name, - service_name=service_name) - if not endpoint_data: - return None - - if skip_discovery: - return endpoint_data - - try: - return endpoint_data.get_versioned_data( - session, - project_id=project_id, - min_version=min_version, - max_version=max_version, - cache=self._discovery_cache, - discover_versions=discover_versions, - allow_version_hack=allow_version_hack, allow=allow) - except (exceptions.DiscoveryFailure, - exceptions.HttpError, - exceptions.ConnectionError): - # If a version was requested, we didn't find it, return - # None. - if max_version or min_version: - return None - # If one wasn't, then the endpoint_data we already have - # should be fine - return endpoint_data - - def get_endpoint(self, session, service_type=None, interface=None, - region_name=None, service_name=None, version=None, - allow={}, allow_version_hack=True, - skip_discovery=False, - min_version=None, max_version=None, - **kwargs): - """Return a valid endpoint for a service. - - If a valid token is not present then a new one will be fetched using - the session and kwargs. - - version, min_version and max_version can all be given either as a - string or a tuple. - - Valid interface types: `public` or `publicURL`, - `internal` or `internalURL`, - `admin` or 'adminURL` - - :param session: A session object that can be used for communication. - :type session: keystoneauth1.session.Session - :param string service_type: The type of service to lookup the endpoint - for. This plugin will return None (failure) - if service_type is not provided. - :param interface: Type of endpoint. Can be a single value or a list - of values. If it's a list of values, they will be - looked for in order of preference. Can also be - `keystoneauth1.plugin.AUTH_INTERFACE` to indicate - that the auth_url should be used instead of the - value in the catalog. (optional, defaults to public) - :param string region_name: The region the endpoint should exist in. - (optional) - :param string service_name: The name of the service in the catalog. - (optional) - :param version: The minimum version number required for this - endpoint. (optional) - :param dict allow: Extra filters to pass when discovering API - versions. (optional) - :param bool allow_version_hack: Allow keystoneauth to hack up catalog - URLS to support older schemes. - (optional, default True) - :param bool skip_discovery: Whether to skip version discovery even - if a version has been given. This is useful - if endpoint_override or similar has been - given and grabbing additional information - about the endpoint is not useful. - :param min_version: The minimum version that is acceptable. Mutually - exclusive with version. If min_version is given - with no max_version it is as if max version is - 'latest'. (optional) - :param max_version: The maximum version that is acceptable. Mutually - exclusive with version. If min_version is given - with no max_version it is as if max version is - 'latest'. (optional) - - :raises keystoneauth1.exceptions.http.HttpError: An error from an - invalid HTTP response. - - :return: A valid endpoint URL or None if not available. - :rtype: string or None - """ - # Explode `version` into min_version and max_version - everything below - # here uses the latter rather than the former. - min_version, max_version = discover._normalize_version_args( - version, min_version, max_version) - # Set discover_versions to False since we're only going to return - # a URL. Fetching the microversion data would be needlessly - # expensive in the common case. However, discover_versions=False - # will still run discovery if the version requested is not the - # version in the catalog. - endpoint_data = self.get_endpoint_data( - session, service_type=service_type, interface=interface, - region_name=region_name, service_name=service_name, - allow=allow, min_version=min_version, max_version=max_version, - discover_versions=False, skip_discovery=skip_discovery, - allow_version_hack=allow_version_hack, **kwargs) - return endpoint_data.url if endpoint_data else None - - def get_user_id(self, session, **kwargs): - return self.get_access(session).user_id - - def get_project_id(self, session, **kwargs): - return self.get_access(session).project_id - - def get_sp_auth_url(self, session, sp_id, **kwargs): - try: - return self.get_access( - session).service_providers.get_auth_url(sp_id) - except exceptions.ServiceProviderNotFound: - return None - - def get_sp_url(self, session, sp_id, **kwargs): - try: - return self.get_access( - session).service_providers.get_sp_url(sp_id) - except exceptions.ServiceProviderNotFound: - return None - - @positional() - def get_discovery(self, session, url, authenticated=None): - """Return the discovery object for a URL. - - Check the session and the plugin cache to see if we have already - performed discovery on the URL and if so return it, otherwise create - a new discovery object, cache it and return it. - - This function is expected to be used by subclasses and should not - be needed by users. - - :param session: A session object to discover with. - :type session: keystoneauth1.session.Session - :param str url: The url to lookup. - :param bool authenticated: Include a token in the discovery call. - (optional) Defaults to None (use a token - if a plugin is installed). - - :raises keystoneauth1.exceptions.discovery.DiscoveryFailure: - if for some reason the lookup fails. - :raises keystoneauth1.exceptions.http.HttpError: An error from an - invalid HTTP response. - - :returns: A discovery object with the results of looking up that URL. - """ - return discover.get_discovery(session=session, url=url, - cache=self._discovery_cache, - authenticated=authenticated) - - def get_cache_id_elements(self): - """Get the elements for this auth plugin that make it unique. - - As part of the get_cache_id requirement we need to determine what - aspects of this plugin and its values that make up the unique elements. - - This should be overridden by plugins that wish to allow caching. - - :returns: The unique attributes and values of this plugin. - :rtype: A flat dict with a str key and str or None value. This is - required as we feed these values into a hash. Pairs where the - value is None are ignored in the hashed id. - """ - raise NotImplementedError() - - def get_cache_id(self): - """Fetch an identifier that uniquely identifies the auth options. - - The returned identifier need not be decomposable or otherwise provide - any way to recreate the plugin. - - This string MUST change if any of the parameters that are used to - uniquely identity this plugin change. It should not change upon a - reauthentication of the plugin. - - :returns: A unique string for the set of options - :rtype: str or None if this is unsupported or unavailable. - """ - try: - elements = self.get_cache_id_elements() - except NotImplementedError: - return None - - hasher = hashlib.sha256() - - for k, v in sorted(elements.items()): - if v is not None: - # NOTE(jamielennox): in python3 you need to pass bytes to hash - if isinstance(k, six.string_types): - k = k.encode('utf-8') - if isinstance(v, six.string_types): - v = v.encode('utf-8') - - hasher.update(k) - hasher.update(v) - - return base64.b64encode(hasher.digest()).decode('utf-8') - - def get_auth_state(self): - """Retrieve the current authentication state for the plugin. - - Retrieve any internal state that represents the authenticated plugin. - - This should not fetch any new data if it is not present. - - :returns: a string that can be stored or None if there is no auth state - present in the plugin. This string can be reloaded with - set_auth_state to set the same authentication. - :rtype: str or None if no auth present. - """ - if self.auth_ref: - data = {'auth_token': self.auth_ref.auth_token, - 'body': self.auth_ref._data} - - return json.dumps(data) - - def set_auth_state(self, data): - """Install existing authentication state for a plugin. - - Take the output of get_auth_state and install that authentication state - into the current authentication plugin. - """ - if data: - auth_data = json.loads(data) - self.auth_ref = access.create(body=auth_data['body'], - auth_token=auth_data['auth_token']) - else: - self.auth_ref = None diff --git a/keystoneauth1/identity/generic/__init__.py b/keystoneauth1/identity/generic/__init__.py deleted file mode 100644 index df1831e..0000000 --- a/keystoneauth1/identity/generic/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -# Licensed under the Apache License, Version 2.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 keystoneauth1.identity.generic.base import BaseGenericPlugin # noqa -from keystoneauth1.identity.generic.password import Password # noqa -from keystoneauth1.identity.generic.token import Token # noqa - - -__all__ = ('BaseGenericPlugin', - 'Password', - 'Token', - ) diff --git a/keystoneauth1/identity/generic/base.py b/keystoneauth1/identity/generic/base.py deleted file mode 100644 index 603767e..0000000 --- a/keystoneauth1/identity/generic/base.py +++ /dev/null @@ -1,215 +0,0 @@ -# Licensed under the Apache License, Version 2.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 abc - -import six -import six.moves.urllib.parse as urlparse - -from keystoneauth1 import _utils as utils -from keystoneauth1 import discover -from keystoneauth1 import exceptions -from keystoneauth1.identity import base - - -LOG = utils.get_logger(__name__) - - -@six.add_metaclass(abc.ABCMeta) -class BaseGenericPlugin(base.BaseIdentityPlugin): - """An identity plugin that is not version dependent. - - Internally we will construct a version dependent plugin with the resolved - URL and then proxy all calls from the base plugin to the versioned one. - """ - - def __init__(self, auth_url, - tenant_id=None, - tenant_name=None, - project_id=None, - project_name=None, - project_domain_id=None, - project_domain_name=None, - domain_id=None, - domain_name=None, - trust_id=None, - default_domain_id=None, - default_domain_name=None, - reauthenticate=True): - super(BaseGenericPlugin, self).__init__(auth_url=auth_url, - reauthenticate=reauthenticate) - - self._project_id = project_id or tenant_id - self._project_name = project_name or tenant_name - self._project_domain_id = project_domain_id - self._project_domain_name = project_domain_name - self._domain_id = domain_id - self._domain_name = domain_name - self._trust_id = trust_id - self._default_domain_id = default_domain_id - self._default_domain_name = default_domain_name - - self._plugin = None - - @abc.abstractmethod - def create_plugin(self, session, version, url, raw_status=None): - """Create a plugin from the given parameters. - - This function will be called multiple times with the version and url - of a potential endpoint. If a plugin can be constructed that fits the - params then it should return it. If not return None and then another - call will be made with other available URLs. - - :param session: A session object. - :type session: keystoneauth1.session.Session - :param tuple version: A tuple of the API version at the URL. - :param str url: The base URL for this version. - :param str raw_status: The status that was in the discovery field. - - :returns: A plugin that can match the parameters or None if nothing. - """ - return None - - @property - def _has_domain_scope(self): - """Are there domain parameters. - - Domain parameters are v3 only so returns if any are set. - - :returns: True if a domain parameter is set, false otherwise. - """ - return any([self._domain_id, self._domain_name, - self._project_domain_id, self._project_domain_name]) - - @property - def _v2_params(self): - """Return the parameters that are common to v2 plugins.""" - return {'trust_id': self._trust_id, - 'tenant_id': self._project_id, - 'tenant_name': self._project_name, - 'reauthenticate': self.reauthenticate} - - @property - def _v3_params(self): - """Return the parameters that are common to v3 plugins.""" - return {'trust_id': self._trust_id, - 'project_id': self._project_id, - 'project_name': self._project_name, - 'project_domain_id': self.project_domain_id, - 'project_domain_name': self.project_domain_name, - 'domain_id': self._domain_id, - 'domain_name': self._domain_name, - 'reauthenticate': self.reauthenticate} - - @property - def project_domain_id(self): - return self._project_domain_id or self._default_domain_id - - @project_domain_id.setter - def project_domain_id(self, value): - self._project_domain_id = value - - @property - def project_domain_name(self): - return self._project_domain_name or self._default_domain_name - - @project_domain_name.setter - def project_domain_name(self, value): - self._project_domain_name = value - - def _do_create_plugin(self, session): - plugin = None - - try: - disc = self.get_discovery(session, - self.auth_url, - authenticated=False) - except (exceptions.DiscoveryFailure, - exceptions.HttpError, - exceptions.ConnectionError): - LOG.warning('Failed to discover available identity versions when ' - 'contacting %s. Attempting to parse version from URL.', - self.auth_url) - - url_parts = urlparse.urlparse(self.auth_url) - path = url_parts.path.lower() - - if path.startswith('/v2.0'): - if self._has_domain_scope: - raise exceptions.DiscoveryFailure( - 'Cannot use v2 authentication with domain scope') - plugin = self.create_plugin(session, (2, 0), self.auth_url) - elif path.startswith('/v3'): - plugin = self.create_plugin(session, (3, 0), self.auth_url) - - else: - # NOTE(jamielennox): version_data is always in oldest to newest - # order. This is fine normally because we explicitly skip v2 below - # if there is domain data present. With default_domain params - # though we want a v3 plugin if available and fall back to v2 so we - # have to process in reverse order. FIXME(jamielennox): if we ever - # go for another version we should reverse this logic as we always - # want to favour the newest available version. - reverse = self._default_domain_id or self._default_domain_name - disc_data = disc.version_data(reverse=bool(reverse)) - - v2_with_domain_scope = False - for data in disc_data: - version = data['version'] - - if (discover.version_match((2,), version) and - self._has_domain_scope): - # NOTE(jamielennox): if there are domain parameters there - # is no point even trying against v2 APIs. - v2_with_domain_scope = True - continue - - plugin = self.create_plugin(session, - version, - data['url'], - raw_status=data['raw_status']) - - if plugin: - break - if not plugin and v2_with_domain_scope: - raise exceptions.DiscoveryFailure( - 'Cannot use v2 authentication with domain scope') - - if plugin: - return plugin - - # so there were no URLs that i could use for auth of any version. - raise exceptions.DiscoveryFailure('Could not determine a suitable URL ' - 'for the plugin') - - def get_auth_ref(self, session, **kwargs): - if not self._plugin: - self._plugin = self._do_create_plugin(session) - - return self._plugin.get_auth_ref(session, **kwargs) - - def get_cache_id_elements(self, _implemented=False): - # NOTE(jamielennox): implemented here is just a way to make sure that - # something overrides this method. We don't want the base - # implementation to respond with a dict without the subclass modifying - # it to add their own data in case the subclass doesn't support caching - if not _implemented: - raise NotImplemented() - - return {'auth_url': self.auth_url, - 'project_id': self._project_id, - 'project_name': self._project_name, - 'project_domain_id': self.project_domain_id, - 'project_domain_name': self.project_domain_name, - 'domain_id': self._domain_id, - 'domain_name': self._domain_name, - 'trust_id': self._trust_id} diff --git a/keystoneauth1/identity/generic/password.py b/keystoneauth1/identity/generic/password.py deleted file mode 100644 index 4af6411..0000000 --- a/keystoneauth1/identity/generic/password.py +++ /dev/null @@ -1,90 +0,0 @@ -# Licensed under the Apache License, Version 2.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 positional import positional - -from keystoneauth1 import discover -from keystoneauth1.identity.generic import base -from keystoneauth1.identity import v2 -from keystoneauth1.identity import v3 - - -class Password(base.BaseGenericPlugin): - """A common user/password authentication plugin. - - :param string username: Username for authentication. - :param string user_id: User ID for authentication. - :param string password: Password for authentication. - :param string user_domain_id: User's domain ID for authentication. - :param string user_domain_name: User's domain name for authentication. - - """ - - @positional() - def __init__(self, auth_url, username=None, user_id=None, password=None, - user_domain_id=None, user_domain_name=None, **kwargs): - super(Password, self).__init__(auth_url=auth_url, **kwargs) - - self._username = username - self._user_id = user_id - self._password = password - self._user_domain_id = user_domain_id - self._user_domain_name = user_domain_name - - def create_plugin(self, session, version, url, raw_status=None): - if discover.version_match((2,), version): - if self._user_domain_id or self._user_domain_name: - return None - - return v2.Password(auth_url=url, - user_id=self._user_id, - username=self._username, - password=self._password, - **self._v2_params) - - elif discover.version_match((3,), version): - u_domain_id = self._user_domain_id or self._default_domain_id - u_domain_name = self._user_domain_name or self._default_domain_name - - return v3.Password(auth_url=url, - user_id=self._user_id, - username=self._username, - user_domain_id=u_domain_id, - user_domain_name=u_domain_name, - password=self._password, - **self._v3_params) - - @property - def user_domain_id(self): - return self._user_domain_id or self._default_domain_id - - @user_domain_id.setter - def user_domain_id(self, value): - self._user_domain_id = value - - @property - def user_domain_name(self): - return self._user_domain_name or self._default_domain_name - - @user_domain_name.setter - def user_domain_name(self, value): - self._user_domain_name = value - - def get_cache_id_elements(self): - elements = super(Password, self).get_cache_id_elements( - _implemented=True) - elements['username'] = self._username - elements['user_id'] = self._user_id - elements['password'] = self._password - elements['user_domain_id'] = self.user_domain_id - elements['user_domain_name'] = self.user_domain_name - return elements diff --git a/keystoneauth1/identity/generic/token.py b/keystoneauth1/identity/generic/token.py deleted file mode 100644 index 00239b6..0000000 --- a/keystoneauth1/identity/generic/token.py +++ /dev/null @@ -1,39 +0,0 @@ -# Licensed under the Apache License, Version 2.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 keystoneauth1 import discover -from keystoneauth1.identity.generic import base -from keystoneauth1.identity import v2 -from keystoneauth1.identity import v3 - - -class Token(base.BaseGenericPlugin): - """Generic token auth plugin. - - :param string token: Token for authentication. - """ - - def __init__(self, auth_url, token=None, **kwargs): - super(Token, self).__init__(auth_url, **kwargs) - self._token = token - - def create_plugin(self, session, version, url, raw_status=None): - if discover.version_match((2,), version): - return v2.Token(url, self._token, **self._v2_params) - - elif discover.version_match((3,), version): - return v3.Token(url, self._token, **self._v3_params) - - def get_cache_id_elements(self): - elements = super(Token, self).get_cache_id_elements(_implemented=True) - elements['token'] = self._token - return elements diff --git a/keystoneauth1/identity/v2.py b/keystoneauth1/identity/v2.py deleted file mode 100644 index 933657a..0000000 --- a/keystoneauth1/identity/v2.py +++ /dev/null @@ -1,178 +0,0 @@ -# Licensed under the Apache License, Version 2.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 abc - -from positional import positional -import six - -from keystoneauth1 import _utils as utils -from keystoneauth1 import access -from keystoneauth1 import exceptions -from keystoneauth1.identity import base - -_logger = utils.get_logger(__name__) - - -@six.add_metaclass(abc.ABCMeta) -class Auth(base.BaseIdentityPlugin): - """Identity V2 Authentication Plugin. - - :param string auth_url: Identity service endpoint for authorization. - :param string trust_id: Trust ID for trust scoping. - :param string tenant_id: Tenant ID for project scoping. - :param string tenant_name: Tenant name for project scoping. - :param bool reauthenticate: Allow fetching a new token if the current one - is going to expire. (optional) default True - """ - - @positional() - def __init__(self, auth_url, - trust_id=None, - tenant_id=None, - tenant_name=None, - reauthenticate=True): - super(Auth, self).__init__(auth_url=auth_url, - reauthenticate=reauthenticate) - - self.trust_id = trust_id - self.tenant_id = tenant_id - self.tenant_name = tenant_name - - def get_auth_ref(self, session, **kwargs): - headers = {'Accept': 'application/json'} - url = self.auth_url.rstrip('/') + '/tokens' - params = {'auth': self.get_auth_data(headers)} - - if self.tenant_id: - params['auth']['tenantId'] = self.tenant_id - elif self.tenant_name: - params['auth']['tenantName'] = self.tenant_name - if self.trust_id: - params['auth']['trust_id'] = self.trust_id - - _logger.debug('Making authentication request to %s', url) - resp = session.post(url, json=params, headers=headers, - authenticated=False, log=False) - - try: - resp_data = resp.json() - except ValueError: - raise exceptions.InvalidResponse(response=resp) - - if 'access' not in resp_data: - raise exceptions.InvalidResponse(response=resp) - - return access.AccessInfoV2(resp_data) - - @abc.abstractmethod - def get_auth_data(self, headers=None): - """Return the authentication section of an auth plugin. - - :param dict headers: The headers that will be sent with the auth - request if a plugin needs to add to them. - :return: A dict of authentication data for the auth type. - :rtype: dict - """ - - @property - def has_scope_parameters(self): - """Return true if parameters can be used to create a scoped token.""" - return self.tenant_id or self.tenant_name or self.trust_id - - -_NOT_PASSED = object() - - -class Password(Auth): - """A plugin for authenticating with a username and password. - - A username or user_id must be provided. - - :param string auth_url: Identity service endpoint for authorization. - :param string username: Username for authentication. - :param string password: Password for authentication. - :param string user_id: User ID for authentication. - :param string trust_id: Trust ID for trust scoping. - :param string tenant_id: Tenant ID for tenant scoping. - :param string tenant_name: Tenant name for tenant scoping. - :param bool reauthenticate: Allow fetching a new token if the current one - is going to expire. (optional) default True - - :raises TypeError: if a user_id or username is not provided. - """ - - @positional(4) - def __init__(self, auth_url, username=_NOT_PASSED, password=None, - user_id=_NOT_PASSED, **kwargs): - super(Password, self).__init__(auth_url, **kwargs) - - if username is _NOT_PASSED and user_id is _NOT_PASSED: - msg = 'You need to specify either a username or user_id' - raise TypeError(msg) - - if username is _NOT_PASSED: - username = None - if user_id is _NOT_PASSED: - user_id = None - - self.user_id = user_id - self.username = username - self.password = password - - def get_auth_data(self, headers=None): - auth = {'password': self.password} - - if self.username: - auth['username'] = self.username - elif self.user_id: - auth['userId'] = self.user_id - - return {'passwordCredentials': auth} - - def get_cache_id_elements(self): - return {'username': self.username, - 'user_id': self.user_id, - 'password': self.password, - 'auth_url': self.auth_url, - 'tenant_id': self.tenant_id, - 'tenant_name': self.tenant_name, - 'trust_id': self.trust_id} - - -class Token(Auth): - """A plugin for authenticating with an existing token. - - :param string auth_url: Identity service endpoint for authorization. - :param string token: Existing token for authentication. - :param string tenant_id: Tenant ID for tenant scoping. - :param string tenant_name: Tenant name for tenant scoping. - :param string trust_id: Trust ID for trust scoping. - :param bool reauthenticate: Allow fetching a new token if the current one - is going to expire. (optional) default True - """ - - def __init__(self, auth_url, token, **kwargs): - super(Token, self).__init__(auth_url, **kwargs) - self.token = token - - def get_auth_data(self, headers=None): - if headers is not None: - headers['X-Auth-Token'] = self.token - return {'token': {'id': self.token}} - - def get_cache_id_elements(self): - return {'token': self.token, - 'auth_url': self.auth_url, - 'tenant_id': self.tenant_id, - 'tenant_name': self.tenant_name, - 'trust_id': self.trust_id} diff --git a/keystoneauth1/identity/v3/__init__.py b/keystoneauth1/identity/v3/__init__.py deleted file mode 100644 index 38e78db..0000000 --- a/keystoneauth1/identity/v3/__init__.py +++ /dev/null @@ -1,46 +0,0 @@ -# Licensed under the Apache License, Version 2.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 keystoneauth1.identity.v3.base import * # noqa -from keystoneauth1.identity.v3.federation import * # noqa -from keystoneauth1.identity.v3.k2k import * # noqa -from keystoneauth1.identity.v3.oidc import * # noqa -from keystoneauth1.identity.v3.password import * # noqa -from keystoneauth1.identity.v3.token import * # noqa -from keystoneauth1.identity.v3.totp import * # noqa -from keystoneauth1.identity.v3.tokenless_auth import * # noqa - - -__all__ = ('Auth', - 'AuthConstructor', - 'AuthMethod', - 'BaseAuth', - - 'FederationBaseAuth', - - 'Keystone2Keystone', - - 'Password', - 'PasswordMethod', - - 'Token', - 'TokenMethod', - - 'OidcAccessToken', - 'OidcAuthorizationCode', - 'OidcClientCredentials', - 'OidcPassword', - - 'TOTPMethod', - 'TOTP', - - 'TokenlessAuth') diff --git a/keystoneauth1/identity/v3/base.py b/keystoneauth1/identity/v3/base.py deleted file mode 100644 index 1f8f8de..0000000 --- a/keystoneauth1/identity/v3/base.py +++ /dev/null @@ -1,282 +0,0 @@ -# Licensed under the Apache License, Version 2.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 abc -import json - -from positional import positional -import six - -from keystoneauth1 import _utils as utils -from keystoneauth1 import access -from keystoneauth1 import exceptions -from keystoneauth1.identity import base - -_logger = utils.get_logger(__name__) - -__all__ = ('Auth', 'AuthMethod', 'AuthConstructor', 'BaseAuth') - - -@six.add_metaclass(abc.ABCMeta) -class BaseAuth(base.BaseIdentityPlugin): - """Identity V3 Authentication Plugin. - - :param string auth_url: Identity service endpoint for authentication. - :param string trust_id: Trust ID for trust scoping. - :param string domain_id: Domain ID for domain scoping. - :param string domain_name: Domain name for domain scoping. - :param string project_id: Project ID for project scoping. - :param string project_name: Project name for project scoping. - :param string project_domain_id: Project's domain ID for project. - :param string project_domain_name: Project's domain name for project. - :param bool reauthenticate: Allow fetching a new token if the current one - is going to expire. (optional) default True - :param bool include_catalog: Include the service catalog in the returned - token. (optional) default True. - """ - - @positional() - def __init__(self, auth_url, - trust_id=None, - domain_id=None, - domain_name=None, - project_id=None, - project_name=None, - project_domain_id=None, - project_domain_name=None, - reauthenticate=True, - include_catalog=True): - super(BaseAuth, self).__init__(auth_url=auth_url, - reauthenticate=reauthenticate) - self.trust_id = trust_id - self.domain_id = domain_id - self.domain_name = domain_name - self.project_id = project_id - self.project_name = project_name - self.project_domain_id = project_domain_id - self.project_domain_name = project_domain_name - self.include_catalog = include_catalog - - @property - def token_url(self): - """The full URL where we will send authentication data.""" - return '%s/auth/tokens' % self.auth_url.rstrip('/') - - @abc.abstractmethod - def get_auth_ref(self, session, **kwargs): - return None - - @property - def has_scope_parameters(self): - """Return true if parameters can be used to create a scoped token.""" - return (self.domain_id or self.domain_name or - self.project_id or self.project_name or - self.trust_id) - - -class Auth(BaseAuth): - """Identity V3 Authentication Plugin. - - :param string auth_url: Identity service endpoint for authentication. - :param list auth_methods: A collection of methods to authenticate with. - :param string trust_id: Trust ID for trust scoping. - :param string domain_id: Domain ID for domain scoping. - :param string domain_name: Domain name for domain scoping. - :param string project_id: Project ID for project scoping. - :param string project_name: Project name for project scoping. - :param string project_domain_id: Project's domain ID for project. - :param string project_domain_name: Project's domain name for project. - :param bool reauthenticate: Allow fetching a new token if the current one - is going to expire. (optional) default True - :param bool include_catalog: Include the service catalog in the returned - token. (optional) default True. - :param bool unscoped: Force the return of an unscoped token. This will make - the keystone server return an unscoped token even if - a default_project_id is set for this user. - """ - - def __init__(self, auth_url, auth_methods, **kwargs): - self.unscoped = kwargs.pop('unscoped', False) - super(Auth, self).__init__(auth_url=auth_url, **kwargs) - self.auth_methods = auth_methods - - def get_auth_ref(self, session, **kwargs): - headers = {'Accept': 'application/json'} - body = {'auth': {'identity': {}}} - ident = body['auth']['identity'] - rkwargs = {} - - for method in self.auth_methods: - name, auth_data = method.get_auth_data(session, - self, - headers, - request_kwargs=rkwargs) - ident.setdefault('methods', []).append(name) - ident[name] = auth_data - - if not ident: - raise exceptions.AuthorizationFailure( - 'Authentication method required (e.g. password)') - - mutual_exclusion = [bool(self.domain_id or self.domain_name), - bool(self.project_id or self.project_name), - bool(self.trust_id), - bool(self.unscoped)] - - if sum(mutual_exclusion) > 1: - raise exceptions.AuthorizationFailure( - 'Authentication cannot be scoped to multiple targets. Pick ' - 'one of: project, domain, trust or unscoped') - - if self.domain_id: - body['auth']['scope'] = {'domain': {'id': self.domain_id}} - elif self.domain_name: - body['auth']['scope'] = {'domain': {'name': self.domain_name}} - elif self.project_id: - body['auth']['scope'] = {'project': {'id': self.project_id}} - elif self.project_name: - scope = body['auth']['scope'] = {'project': {}} - scope['project']['name'] = self.project_name - - if self.project_domain_id: - scope['project']['domain'] = {'id': self.project_domain_id} - elif self.project_domain_name: - scope['project']['domain'] = {'name': self.project_domain_name} - elif self.trust_id: - body['auth']['scope'] = {'OS-TRUST:trust': {'id': self.trust_id}} - elif self.unscoped: - body['auth']['scope'] = 'unscoped' - - # NOTE(jamielennox): we add nocatalog here rather than in token_url - # directly as some federation plugins require the base token_url - token_url = self.token_url - if not self.include_catalog: - token_url += '?nocatalog' - - _logger.debug('Making authentication request to %s', token_url) - resp = session.post(token_url, json=body, headers=headers, - authenticated=False, log=False, **rkwargs) - - try: - _logger.debug(json.dumps(resp.json())) - resp_data = resp.json() - except ValueError: - raise exceptions.InvalidResponse(response=resp) - - if 'token' not in resp_data: - raise exceptions.InvalidResponse(response=resp) - - return access.AccessInfoV3(auth_token=resp.headers['X-Subject-Token'], - body=resp_data) - - def get_cache_id_elements(self): - if not self.auth_methods: - return None - - params = {'auth_url': self.auth_url, - 'domain_id': self.domain_id, - 'domain_name': self.domain_name, - 'project_id': self.project_id, - 'project_name': self.project_name, - 'project_domain_id': self.project_domain_id, - 'project_domain_name': self.project_domain_name, - 'trust_id': self.trust_id} - - for method in self.auth_methods: - try: - elements = method.get_cache_id_elements() - except NotImplementedError: - return None - - params.update(elements) - - return params - - -@six.add_metaclass(abc.ABCMeta) -class AuthMethod(object): - """One part of a V3 Authentication strategy. - - V3 Tokens allow multiple methods to be presented when authentication - against the server. Each one of these methods is implemented by an - AuthMethod. - - Note: When implementing an AuthMethod use the method_parameters - and do not use positional arguments. Otherwise they can't be picked up by - the factory method and don't work as well with AuthConstructors. - """ - - _method_parameters = [] - - def __init__(self, **kwargs): - for param in self._method_parameters: - setattr(self, param, kwargs.pop(param, None)) - - if kwargs: - msg = "Unexpected Attributes: %s" % ", ".join(kwargs.keys()) - raise AttributeError(msg) - - @classmethod - def _extract_kwargs(cls, kwargs): - """Remove parameters related to this method from other kwargs.""" - return dict([(p, kwargs.pop(p, None)) - for p in cls._method_parameters]) - - @abc.abstractmethod - def get_auth_data(self, session, auth, headers, **kwargs): - """Return the authentication section of an auth plugin. - - :param session: The communication session. - :type session: keystoneauth1.session.Session - :param base.Auth auth: The auth plugin calling the method. - :param dict headers: The headers that will be sent with the auth - request if a plugin needs to add to them. - :return: The identifier of this plugin and a dict of authentication - data for the auth type. - :rtype: tuple(string, dict) - """ - - def get_cache_id_elements(self): - """Get the elements for this auth method that make it unique. - - These elements will be used as part of the - :py:meth:`keystoneauth1.plugin.BaseIdentityPlugin.get_cache_id` to - allow caching of the auth plugin. - - Plugins should override this if they want to allow caching of their - state. - - To avoid collision or overrides the keys of the returned dictionary - should be prefixed with the plugin identifier. For example the password - plugin returns its username value as 'password_username'. - """ - raise NotImplementedError() - - -@six.add_metaclass(abc.ABCMeta) -class AuthConstructor(Auth): - """Abstract base class for creating an Auth Plugin. - - The Auth Plugin created contains only one authentication method. This - is generally the required usage. - - An AuthConstructor creates an AuthMethod based on the method's - arguments and the auth_method_class defined by the plugin. It then - creates the auth plugin with only that authentication method. - """ - - _auth_method_class = None - - def __init__(self, auth_url, *args, **kwargs): - method_kwargs = self._auth_method_class._extract_kwargs(kwargs) - method = self._auth_method_class(*args, **method_kwargs) - super(AuthConstructor, self).__init__(auth_url, [method], **kwargs) diff --git a/keystoneauth1/identity/v3/federation.py b/keystoneauth1/identity/v3/federation.py deleted file mode 100644 index 8f5ff23..0000000 --- a/keystoneauth1/identity/v3/federation.py +++ /dev/null @@ -1,115 +0,0 @@ -# Licensed under the Apache License, Version 2.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 abc - -import six - -from keystoneauth1.identity.v3 import base -from keystoneauth1.identity.v3 import token - -__all__ = ('FederationBaseAuth',) - - -@six.add_metaclass(abc.ABCMeta) -class _Rescoped(base.BaseAuth): - """A plugin that is always going to go through a rescope process. - - The original keystone plugins could simply pass a project or domain to - along with the credentials and get a scoped token. For federation, K2K and - newer mechanisms we always get an unscoped token first and then rescope. - - This is currently not public as it's generally an abstraction of a flow - used by plugins within keystoneauth1. - - It also cannot go in base as it depends on token.Token for rescoping which - would create a circular dependency. - """ - - rescoping_plugin = token.Token - - def _get_scoping_data(self): - return {'trust_id': self.trust_id, - 'domain_id': self.domain_id, - 'domain_name': self.domain_name, - 'project_id': self.project_id, - 'project_name': self.project_name, - 'project_domain_id': self.project_domain_id, - 'project_domain_name': self.project_domain_name} - - def get_auth_ref(self, session, **kwargs): - """Authenticate retrieve token information. - - This is a multi-step process where a client does federated authn - receives an unscoped token. - - If an unscoped token is successfully received and scoping information - is present then the token is rescoped to that target. - - :param session: a session object to send out HTTP requests. - :type session: keystoneauth1.session.Session - - :returns: a token data representation - :rtype: :py:class:`keystoneauth1.access.AccessInfo` - - """ - auth_ref = self.get_unscoped_auth_ref(session) - scoping = self._get_scoping_data() - - if any(scoping.values()): - token_plugin = self.rescoping_plugin(self.auth_url, - token=auth_ref.auth_token, - **scoping) - - auth_ref = token_plugin.get_auth_ref(session) - - return auth_ref - - @abc.abstractmethod - def get_unscoped_auth_ref(self, session, **kwargs): - """Fetch unscoped federated token.""" - - -class FederationBaseAuth(_Rescoped): - """Federation authentication plugin. - - :param auth_url: URL of the Identity Service - :type auth_url: string - :param identity_provider: name of the Identity Provider the client - will authenticate against. This parameter - will be used to build a dynamic URL used to - obtain unscoped OpenStack token. - :type identity_provider: string - :param protocol: name of the protocol the client will authenticate - against. - :type protocol: string - - """ - - def __init__(self, auth_url, identity_provider, protocol, **kwargs): - super(FederationBaseAuth, self).__init__(auth_url=auth_url, **kwargs) - self.identity_provider = identity_provider - self.protocol = protocol - - @property - def federated_token_url(self): - """Full URL where authorization data is sent.""" - values = { - 'host': self.auth_url.rstrip('/'), - 'identity_provider': self.identity_provider, - 'protocol': self.protocol - } - url = ("%(host)s/OS-FEDERATION/identity_providers/" - "%(identity_provider)s/protocols/%(protocol)s/auth") - url = url % values - - return url diff --git a/keystoneauth1/identity/v3/k2k.py b/keystoneauth1/identity/v3/k2k.py deleted file mode 100644 index 666fb48..0000000 --- a/keystoneauth1/identity/v3/k2k.py +++ /dev/null @@ -1,173 +0,0 @@ -# Licensed under the Apache License, Version 2.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 six - -from keystoneauth1 import access -from keystoneauth1 import exceptions -from keystoneauth1.identity.v3 import federation -from keystoneauth1 import plugin - -__all__ = ('Keystone2Keystone',) - - -class Keystone2Keystone(federation._Rescoped): - """Plugin to execute the Keystone to Keyestone authentication flow. - - In this plugin, an ECP wrapped SAML assertion provided by a keystone - Identity Provider (IdP) is used to request an OpenStack unscoped token - from a keystone Service Provider (SP). - - :param base_plugin: Auth plugin already authenticated against the keystone - IdP. - :type base_plugin: keystoneauth1.identity.v3.base.BaseAuth - - :param service_provider: The Service Provider ID as returned by - ServiceProviderManager.list() - :type service_provider: str - - """ - - REQUEST_ECP_URL = '/auth/OS-FEDERATION/saml2/ecp' - """Path where the ECP wrapped SAML assertion should be presented to the - Keystone Service Provider.""" - - HTTP_MOVED_TEMPORARILY = 302 - HTTP_SEE_OTHER = 303 - - def __init__(self, base_plugin, service_provider, **kwargs): - super(Keystone2Keystone, self).__init__(auth_url=None, **kwargs) - - self._local_cloud_plugin = base_plugin - self._sp_id = service_provider - - @classmethod - def _remote_auth_url(cls, auth_url): - """Return auth_url of the remote Keystone Service Provider. - - Remote cloud's auth_url is an endpoint for getting federated unscoped - token, typically that would be - ``https://remote.example.com:5000/v3/OS-FEDERATION/identity_providers/ - /protocols//auth``. However we need to generate a - real auth_url, used for token scoping. This function assumes there are - static values today in the remote auth_url stored in the Service - Provider attribute and those can be used as a delimiter. If the - sp_auth_url doesn't comply with standard federation auth url the - function will simply return whole string. - - :param auth_url: auth_url of the remote cloud - :type auth_url: str - - :returns: auth_url of remote cloud where a token can be validated or - scoped. - :rtype: str - - """ - PATTERN = '/OS-FEDERATION/' - idx = auth_url.index(PATTERN) if PATTERN in auth_url else len(auth_url) - return auth_url[:idx] - - def _get_ecp_assertion(self, session): - body = { - 'auth': { - 'identity': { - 'methods': ['token'], - 'token': { - 'id': self._local_cloud_plugin.get_token(session) - } - }, - 'scope': { - 'service_provider': { - 'id': self._sp_id - } - } - } - } - - endpoint_filter = {'version': (3, 0), - 'interface': plugin.AUTH_INTERFACE} - - headers = {'Accept': 'application/json'} - - resp = session.post(self.REQUEST_ECP_URL, - json=body, - auth=self._local_cloud_plugin, - endpoint_filter=endpoint_filter, - headers=headers, - authenticated=False, - raise_exc=False) - - # NOTE(marek-denis): I am not sure whether disabling exceptions in the - # Session object and testing if resp.ok is sufficient. An alternative - # would be catching locally all exceptions and reraising with custom - # warning. - if not resp.ok: - msg = ("Error while requesting ECP wrapped assertion: response " - "exit code: %(status_code)d, reason: %(err)s") - msg = msg % {'status_code': resp.status_code, 'err': resp.reason} - raise exceptions.AuthorizationFailure(msg) - - if not resp.text: - raise exceptions.InvalidResponse(resp) - - return six.text_type(resp.text) - - def _send_service_provider_ecp_authn_response(self, session, sp_url, - sp_auth_url): - """Present ECP wrapped SAML assertion to the keystone SP. - - The assertion is issued by the keystone IdP and it is targeted to the - keystone that will serve as Service Provider. - - :param session: a session object to send out HTTP requests. - - :param sp_url: URL where the ECP wrapped SAML assertion will be - presented to the keystone SP. Usually, something like: - https://sp.com/Shibboleth.sso/SAML2/ECP - :type sp_url: str - - :param sp_auth_url: Federated authentication URL of the keystone SP. - It is specified by IdP, for example: - https://sp.com/v3/OS-FEDERATION/identity_providers/ - idp_id/protocols/protocol_id/auth - :type sp_auth_url: str - - """ - response = session.post( - sp_url, - headers={'Content-Type': 'application/vnd.paos+xml'}, - data=self._get_ecp_assertion(session), - authenticated=False, - redirect=False) - - # Don't follow HTTP specs - after the HTTP 302/303 response don't - # repeat the call directed to the Location URL. In this case, this is - # an indication that SAML2 session is now active and protected resource - # can be accessed. - if response.status_code in (self.HTTP_MOVED_TEMPORARILY, - self.HTTP_SEE_OTHER): - response = session.get( - sp_auth_url, - headers={'Content-Type': 'application/vnd.paos+xml'}, - authenticated=False) - - return response - - def get_unscoped_auth_ref(self, session, **kwargs): - sp_auth_url = self._local_cloud_plugin.get_sp_auth_url( - session, self._sp_id) - sp_url = self._local_cloud_plugin.get_sp_url(session, self._sp_id) - self.auth_url = self._remote_auth_url(sp_auth_url) - - response = self._send_service_provider_ecp_authn_response( - session, sp_url, sp_auth_url) - return access.create(resp=response) diff --git a/keystoneauth1/identity/v3/oidc.py b/keystoneauth1/identity/v3/oidc.py deleted file mode 100644 index 08c4a36..0000000 --- a/keystoneauth1/identity/v3/oidc.py +++ /dev/null @@ -1,480 +0,0 @@ -# Licensed under the Apache License, Version 2.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 abc -import warnings - -from positional import positional -import six - -from keystoneauth1 import _utils as utils -from keystoneauth1 import access -from keystoneauth1 import exceptions -from keystoneauth1.identity.v3 import federation - -_logger = utils.get_logger(__name__) - -__all__ = ('OidcAuthorizationCode', - 'OidcClientCredentials', - 'OidcPassword', - 'OidcAccessToken') - - -@six.add_metaclass(abc.ABCMeta) -class _OidcBase(federation.FederationBaseAuth): - """Base class for different OpenID Connect based flows. - - The OpenID Connect specification can be found at:: - ``http://openid.net/specs/openid-connect-core-1_0.html`` - """ - - grant_type = None - - def __init__(self, auth_url, identity_provider, protocol, - client_id, client_secret, - access_token_type, - scope="openid profile", - access_token_endpoint=None, - discovery_endpoint=None, - grant_type=None, - **kwargs): - """The OpenID Connect plugin expects the following. - - :param auth_url: URL of the Identity Service - :type auth_url: string - - :param identity_provider: Name of the Identity Provider the client - will authenticate against - :type identity_provider: string - - :param protocol: Protocol name as configured in keystone - :type protocol: string - - :param client_id: OAuth 2.0 Client ID - :type client_id: string - - :param client_secret: OAuth 2.0 Client Secret - :type client_secret: string - - :param access_token_type: OAuth 2.0 Authorization Server Introspection - token type, it is used to decide which type - of token will be used when processing token - introspection. Valid values are: - "access_token" or "id_token" - :type access_token_type: string - - :param access_token_endpoint: OpenID Connect Provider Token Endpoint, - for example: - https://localhost:8020/oidc/OP/token - Note that if a discovery document is - provided this value will override - the discovered one. - :type access_token_endpoint: string - - :param discovery_endpoint: OpenID Connect Discovery Document URL, - for example: - https://localhost:8020/oidc/.well-known/openid-configuration - :type access_token_endpoint: string - - :param scope: OpenID Connect scope that is requested from OP, - for example: "openid profile email", defaults to - "openid profile". Note that OpenID Connect specification - states that "openid" must be always specified. - :type scope: string - """ - super(_OidcBase, self).__init__(auth_url, identity_provider, protocol, - **kwargs) - self.client_id = client_id - self.client_secret = client_secret - - self.discovery_endpoint = discovery_endpoint - self._discovery_document = {} - self.access_token_endpoint = access_token_endpoint - - self.access_token_type = access_token_type - self.scope = scope - - if grant_type is not None: - if grant_type != self.grant_type: - raise exceptions.OidcGrantTypeMissmatch() - warnings.warn("Passing grant_type as an argument has been " - "deprecated as it is now defined in the plugin " - "itself. You should stop passing this argument " - "to the plugin, as it will be ignored, since you " - "cannot pass a free text string as a grant_type. " - "This argument will be dropped from the plugin in " - "July 2017 or with the next major release of " - "keystoneauth (3.0.0)", - DeprecationWarning) - - def _get_discovery_document(self, session): - """Get the contents of the OpenID Connect Discovery Document. - - This method grabs the contents of the OpenID Connect Discovery Document - if a discovery_endpoint was passed to the constructor and returns it as - a dict, otherwise returns an empty dict. Note that it will fetch the - discovery document only once, so subsequent calls to this method will - return the cached result, if any. - - :param session: a session object to send out HTTP requests. - :type session: keystoneauth1.session.Session - - :returns: a python dictionary containing the discovery document if any, - otherwise it will return an empty dict. - :rtype: dict - """ - if (self.discovery_endpoint is not None and - not self._discovery_document): - try: - resp = session.get(self.discovery_endpoint, - authenticated=False) - except exceptions.HttpError: - _logger.error("Cannot fetch discovery document %(discovery)s" % - {"discovery": self.discovery_endpoint}) - raise - - try: - self._discovery_document = resp.json() - except Exception: - pass - - if not self._discovery_document: - raise exceptions.InvalidOidcDiscoveryDocument() - - return self._discovery_document - - def _get_access_token_endpoint(self, session): - """Get the "token_endpoint" for the OpenID Connect flow. - - This method will return the correct access token endpoint to be used. - If the user has explicitly passed an access_token_endpoint to the - constructor that will be returned. If there is no explicit endpoint and - a discovery url is provided, it will try to get it from the discovery - document. If nothing is found, an exception will be raised. - - :param session: a session object to send out HTTP requests. - :type session: keystoneauth1.session.Session - - :return: the endpoint to use - :rtype: string or None if no endpoint is found - """ - if self.access_token_endpoint is not None: - return self.access_token_endpoint - - discovery = self._get_discovery_document(session) - endpoint = discovery.get("token_endpoint") - if endpoint is None: - raise exceptions.OidcAccessTokenEndpointNotFound() - return endpoint - - def _get_access_token(self, session, payload): - """Exchange a variety of user supplied values for an access token. - - :param session: a session object to send out HTTP requests. - :type session: keystoneauth1.session.Session - - :param payload: a dict containing various OpenID Connect values, for - example:: - {'grant_type': 'password', 'username': self.username, - 'password': self.password, 'scope': self.scope} - :type payload: dict - """ - client_auth = (self.client_id, self.client_secret) - access_token_endpoint = self._get_access_token_endpoint(session) - - op_response = session.post(access_token_endpoint, - requests_auth=client_auth, - data=payload, - authenticated=False) - access_token = op_response.json()[self.access_token_type] - return access_token - - def _get_keystone_token(self, session, access_token): - r"""Exchange an access token for a keystone token. - - By Sending the access token in an `Authorization: Bearer` header, to - an OpenID Connect protected endpoint (Federated Token URL). The - OpenID Connect server will use the access token to look up information - about the authenticated user (this technique is called instrospection). - The output of the instrospection will be an OpenID Connect Claim, that - will be used against the mapping engine. Should the mapping engine - succeed, a Keystone token will be presented to the user. - - :param session: a session object to send out HTTP requests. - :type session: keystoneauth1.session.Session - - :param access_token: The OpenID Connect access token. - :type access_token: str - """ - # use access token against protected URL - headers = {'Authorization': 'Bearer ' + access_token} - auth_response = session.post(self.federated_token_url, - headers=headers, - authenticated=False) - return auth_response - - def get_unscoped_auth_ref(self, session): - """Authenticate with OpenID Connect and get back claims. - - This is a multi-step process: - - 1.- An access token must be retrieved from the server. In order to do - so, we need to exchange an authorization grant or refresh token - with the token endpoint in order to obtain an access token. The - authorization grant varies from plugin to plugin. - - 2.- We then exchange the access token upon accessing the protected - Keystone endpoint (federated auth URL). This will trigger the - OpenID Connect Provider to perform a user introspection and - retrieve information (specified in the scope) about the user in the - form of an OpenID Connect Claim. These claims will be sent to - Keystone in the form of environment variables. - - :param session: a session object to send out HTTP requests. - :type session: keystoneauth1.session.Session - - :returns: a token data representation - :rtype: :py:class:`keystoneauth1.access.AccessInfoV3` - """ - # First of all, check if the grant type is supported - discovery = self._get_discovery_document(session) - grant_types = discovery.get("grant_types_supported") - if (grant_types and - self.grant_type is not None and - self.grant_type not in grant_types): - raise exceptions.OidcPluginNotSupported() - - # Get the payload - payload = self.get_payload(session) - payload.setdefault('grant_type', self.grant_type) - - # get an access token - access_token = self._get_access_token(session, payload) - - response = self._get_keystone_token(session, access_token) - - # grab the unscoped token - return access.create(resp=response) - - @abc.abstractmethod - def get_payload(self, session): - """Get the plugin specific payload for obtainin an access token. - - OpenID Connect supports different grant types. This method should - prepare the payload that needs to be exchanged with the server in - order to get an access token for the particular grant type that the - plugin is implementing. - - :param session: a session object to send out HTTP requests. - :type session: keystoneauth1.session.Session - - :returns: a python dictionary containing the payload to be exchanged - :rtype: dict - """ - raise NotImplementedError() - - -class OidcPassword(_OidcBase): - """Implementation for OpenID Connect Resource Owner Password Credential.""" - - grant_type = "password" - - @positional(4) - def __init__(self, auth_url, identity_provider, protocol, - client_id, client_secret, - access_token_endpoint=None, - discovery_endpoint=None, - access_token_type='access_token', - username=None, password=None, - **kwargs): - """The OpenID Password plugin expects the following. - - :param username: Username used to authenticate - :type username: string - - :param password: Password used to authenticate - :type password: string - """ - super(OidcPassword, self).__init__( - auth_url=auth_url, - identity_provider=identity_provider, - protocol=protocol, - client_id=client_id, - client_secret=client_secret, - access_token_endpoint=access_token_endpoint, - discovery_endpoint=discovery_endpoint, - access_token_type=access_token_type, - **kwargs) - self.username = username - self.password = password - - def get_payload(self, session): - """Get an authorization grant for the "password" grant type. - - :param session: a session object to send out HTTP requests. - :type session: keystoneauth1.session.Session - - :returns: a python dictionary containing the payload to be exchanged - :rtype: dict - """ - payload = {'username': self.username, - 'password': self.password, - 'scope': self.scope} - return payload - - -class OidcClientCredentials(_OidcBase): - """Implementation for OpenID Connect Client Credentials.""" - - grant_type = 'client_credentials' - - @positional(4) - def __init__(self, auth_url, identity_provider, protocol, - client_id, client_secret, - access_token_endpoint=None, - discovery_endpoint=None, - access_token_type='access_token', - **kwargs): - """The OpenID Client Credentials expects the following. - - :param client_id: Client ID used to authenticate - :type username: string - - :param client_secret: Client Secret used to authenticate - :type password: string - """ - super(OidcClientCredentials, self).__init__( - auth_url=auth_url, - identity_provider=identity_provider, - protocol=protocol, - client_id=client_id, - client_secret=client_secret, - access_token_endpoint=access_token_endpoint, - discovery_endpoint=discovery_endpoint, - access_token_type=access_token_type, - **kwargs) - - def get_payload(self, session): - """Get an authorization grant for the client credentials grant type. - - :param session: a session object to send out HTTP requests. - :type session: keystoneauth1.session.Session - - :returns: a python dictionary containing the payload to be exchanged - :rtype: dict - """ - payload = {'scope': self.scope} - return payload - - -class OidcAuthorizationCode(_OidcBase): - """Implementation for OpenID Connect Authorization Code.""" - - grant_type = 'authorization_code' - - @positional(4) - def __init__(self, auth_url, identity_provider, protocol, - client_id, client_secret, - access_token_endpoint=None, - discovery_endpoint=None, - access_token_type='access_token', - redirect_uri=None, code=None, **kwargs): - """The OpenID Authorization Code plugin expects the following. - - :param redirect_uri: OpenID Connect Client Redirect URL - :type redirect_uri: string - - :param code: OAuth 2.0 Authorization Code - :type code: string - - """ - super(OidcAuthorizationCode, self).__init__( - auth_url=auth_url, - identity_provider=identity_provider, - protocol=protocol, - client_id=client_id, - client_secret=client_secret, - access_token_endpoint=access_token_endpoint, - discovery_endpoint=discovery_endpoint, - access_token_type=access_token_type, - **kwargs) - self.redirect_uri = redirect_uri - self.code = code - - def get_payload(self, session): - """Get an authorization grant for the "authorization_code" grant type. - - :param session: a session object to send out HTTP requests. - :type session: keystoneauth1.session.Session - - :returns: a python dictionary containing the payload to be exchanged - :rtype: dict - """ - payload = {'redirect_uri': self.redirect_uri, 'code': self.code} - - return payload - - -class OidcAccessToken(_OidcBase): - """Implementation for OpenID Connect access token reuse.""" - - @positional(5) - def __init__(self, auth_url, identity_provider, protocol, - access_token, **kwargs): - """The OpenID Connect plugin based on the Access Token. - - It expects the following: - - :param auth_url: URL of the Identity Service - :type auth_url: string - - :param identity_provider: Name of the Identity Provider the client - will authenticate against - :type identity_provider: string - - :param protocol: Protocol name as configured in keystone - :type protocol: string - - :param access_token: OpenID Connect Access token - :type access_token: string - """ - super(OidcAccessToken, self).__init__(auth_url, identity_provider, - protocol, - client_id=None, - client_secret=None, - access_token_endpoint=None, - access_token_type=None, - **kwargs) - self.access_token = access_token - - def get_payload(self, session): - """OidcAccessToken does not require a payload.""" - return {} - - def get_unscoped_auth_ref(self, session): - """Authenticate with OpenID Connect and get back claims. - - We exchange the access token upon accessing the protected Keystone - endpoint (federated auth URL). This will trigger the OpenID Connect - Provider to perform a user introspection and retrieve information - (specified in the scope) about the user in the form of an OpenID - Connect Claim. These claims will be sent to Keystone in the form of - environment variables. - - :param session: a session object to send out HTTP requests. - :type session: keystoneclient.session.Session - - :returns: a token data representation - :rtype: :py:class:`keystoneauth1.access.AccessInfoV3` - """ - response = self._get_keystone_token(session, self.access_token) - return access.create(resp=response) diff --git a/keystoneauth1/identity/v3/password.py b/keystoneauth1/identity/v3/password.py deleted file mode 100644 index beb69b6..0000000 --- a/keystoneauth1/identity/v3/password.py +++ /dev/null @@ -1,75 +0,0 @@ -# Licensed under the Apache License, Version 2.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 keystoneauth1.identity.v3 import base - - -__all__ = ('PasswordMethod', 'Password') - - -class PasswordMethod(base.AuthMethod): - """Construct a User/Password based authentication method. - - :param string password: Password for authentication. - :param string username: Username for authentication. - :param string user_id: User ID for authentication. - :param string user_domain_id: User's domain ID for authentication. - :param string user_domain_name: User's domain name for authentication. - """ - - _method_parameters = ['user_id', - 'username', - 'user_domain_id', - 'user_domain_name', - 'password'] - - def get_auth_data(self, session, auth, headers, **kwargs): - user = {'password': self.password} - - if self.user_id: - user['id'] = self.user_id - elif self.username: - user['name'] = self.username - - if self.user_domain_id: - user['domain'] = {'id': self.user_domain_id} - elif self.user_domain_name: - user['domain'] = {'name': self.user_domain_name} - - return 'password', {'user': user} - - def get_cache_id_elements(self): - return dict(('password_%s' % p, getattr(self, p)) - for p in self._method_parameters) - - -class Password(base.AuthConstructor): - """A plugin for authenticating with a username and password. - - :param string auth_url: Identity service endpoint for authentication. - :param string password: Password for authentication. - :param string username: Username for authentication. - :param string user_id: User ID for authentication. - :param string user_domain_id: User's domain ID for authentication. - :param string user_domain_name: User's domain name for authentication. - :param string trust_id: Trust ID for trust scoping. - :param string domain_id: Domain ID for domain scoping. - :param string domain_name: Domain name for domain scoping. - :param string project_id: Project ID for project scoping. - :param string project_name: Project name for project scoping. - :param string project_domain_id: Project's domain ID for project. - :param string project_domain_name: Project's domain name for project. - :param bool reauthenticate: Allow fetching a new token if the current one - is going to expire. (optional) default True - """ - - _auth_method_class = PasswordMethod diff --git a/keystoneauth1/identity/v3/token.py b/keystoneauth1/identity/v3/token.py deleted file mode 100644 index c959d11..0000000 --- a/keystoneauth1/identity/v3/token.py +++ /dev/null @@ -1,54 +0,0 @@ -# Licensed under the Apache License, Version 2.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 keystoneauth1.identity.v3 import base - - -__all__ = ('TokenMethod', 'Token') - - -class TokenMethod(base.AuthMethod): - """Construct an Auth plugin to fetch a token from a token. - - :param string token: Token for authentication. - """ - - _method_parameters = ['token'] - - def get_auth_data(self, session, auth, headers, **kwargs): - headers['X-Auth-Token'] = self.token - return 'token', {'id': self.token} - - def get_cache_id_elements(self): - return {'token_token': self.token} - - -class Token(base.AuthConstructor): - """A plugin for authenticating with an existing Token. - - :param string auth_url: Identity service endpoint for authentication. - :param string token: Token for authentication. - :param string trust_id: Trust ID for trust scoping. - :param string domain_id: Domain ID for domain scoping. - :param string domain_name: Domain name for domain scoping. - :param string project_id: Project ID for project scoping. - :param string project_name: Project name for project scoping. - :param string project_domain_id: Project's domain ID for project. - :param string project_domain_name: Project's domain name for project. - :param bool reauthenticate: Allow fetching a new token if the current one - is going to expire. (optional) default True - """ - - _auth_method_class = TokenMethod - - def __init__(self, auth_url, token, **kwargs): - super(Token, self).__init__(auth_url, token=token, **kwargs) diff --git a/keystoneauth1/identity/v3/tokenless_auth.py b/keystoneauth1/identity/v3/tokenless_auth.py deleted file mode 100644 index 0e7e7f7..0000000 --- a/keystoneauth1/identity/v3/tokenless_auth.py +++ /dev/null @@ -1,115 +0,0 @@ -# Licensed under the Apache License, Version 2.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 abc -import six - -from keystoneauth1 import _utils as utils -from keystoneauth1 import plugin - -LOG = utils.get_logger(__name__) - - -@six.add_metaclass(abc.ABCMeta) -class TokenlessAuth(plugin.BaseAuthPlugin): - """A plugin for authenticating with Tokenless Auth. - - This is for Tokenless Authentication. Scoped information - like domain name and project ID will be passed in the headers and - token validation request will be authenticated based on - the provided HTTPS certificate along with the scope information. - """ - - def __init__(self, auth_url, - domain_id=None, - domain_name=None, - project_id=None, - project_name=None, - project_domain_id=None, - project_domain_name=None): - """A init method for TokenlessAuth. - - :param string auth_url: Identity service endpoint for authentication. - The URL must include a version or any request - will result in a 404 NotFound error. - :param string domain_id: Domain ID for domain scoping. - :param string domain_name: Domain name for domain scoping. - :param string project_id: Project ID for project scoping. - :param string project_name: Project name for project scoping. - :param string project_domain_id: Project's domain ID for project. - :param string project_domain_name: Project's domain name for project. - """ - self.auth_url = auth_url - self.domain_id = domain_id - self.domain_name = domain_name - self.project_id = project_id - self.project_name = project_name - self.project_domain_id = project_domain_id - self.project_domain_name = project_domain_name - - def get_headers(self, session, **kwargs): - """Fetch authentication headers for message. - - This is to override the default get_headers method to provide - tokenless auth scope headers if token is not provided in the - session. - - :param session: The session object that the auth_plugin belongs to. - :type session: keystoneauth1.session.Session - - :returns: Headers that are set to authenticate a message or None for - failure. Note that when checking this value that the empty - dict is a valid, non-failure response. - :rtype: dict - """ - scope_headers = {} - if self.project_id: - scope_headers['X-Project-Id'] = self.project_id - elif self.project_name: - scope_headers['X-Project-Name'] = self.project_name - if self.project_domain_id: - scope_headers['X-Project-Domain-Id'] = ( - self.project_domain_id) - elif self.project_domain_name: - scope_headers['X-Project-Domain-Name'] = ( - self.project_domain_name) - else: - LOG.warning( - 'Neither Project Domain ID nor Project Domain Name was ' - 'provided.') - return None - elif self.domain_id: - scope_headers['X-Domain-Id'] = self.domain_id - elif self.domain_name: - scope_headers['X-Domain-Name'] = self.domain_name - else: - LOG.warning( - 'Neither Project nor Domain scope was provided.') - return None - return scope_headers - - def get_endpoint(self, session, service_type=None, **kwargs): - """Return a valid endpoint for a service. - - :param session: A session object that can be used for communication. - :type session: keystoneauth1.session.Session - :param string service_type: The type of service to lookup the endpoint - for. This plugin will return None (failure) - if service_type is not provided. - :return: A valid endpoint URL or None if not available. - :rtype: string or None - """ - if (service_type is plugin.AUTH_INTERFACE - or service_type.lower() == 'identity'): - return self.auth_url - - return None diff --git a/keystoneauth1/identity/v3/totp.py b/keystoneauth1/identity/v3/totp.py deleted file mode 100644 index ac0a754..0000000 --- a/keystoneauth1/identity/v3/totp.py +++ /dev/null @@ -1,81 +0,0 @@ -# Licensed under the Apache License, Version 2.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 - -from keystoneauth1.identity.v3 import base - - -__all__ = ('TOTPMethod', 'TOTP') - - -class TOTPMethod(base.AuthMethod): - """Construct a User/Passcode based authentication method. - - :param string passcode: TOTP passcode for authentication. - :param string username: Username for authentication. - :param string user_id: User ID for authentication. - :param string user_domain_id: User's domain ID for authentication. - :param string user_domain_name: User's domain name for authentication. - """ - - _method_parameters = ['user_id', - 'username', - 'user_domain_id', - 'user_domain_name', - 'passcode'] - - def get_auth_data(self, session, auth, headers, **kwargs): - user = {'passcode': self.passcode} - - if self.user_id: - user['id'] = self.user_id - elif self.username: - user['name'] = self.username - - if self.user_domain_id: - user['domain'] = {'id': self.user_domain_id} - elif self.user_domain_name: - user['domain'] = {'name': self.user_domain_name} - - return 'totp', {'user': user} - - def get_cache_id_elements(self): - # NOTE(gyee): passcode is not static so we cannot use it as part of - # the key in caching. - params = copy.copy(self._method_parameters) - params.remove('passcode') - return dict(('totp_%s' % p, getattr(self, p)) - for p in self._method_parameters) - - -class TOTP(base.AuthConstructor): - """A plugin for authenticating with a username and TOTP passcode. - - :param string auth_url: Identity service endpoint for authentication. - :param string passcode: TOTP passcode for authentication. - :param string username: Username for authentication. - :param string user_id: User ID for authentication. - :param string user_domain_id: User's domain ID for authentication. - :param string user_domain_name: User's domain name for authentication. - :param string trust_id: Trust ID for trust scoping. - :param string domain_id: Domain ID for domain scoping. - :param string domain_name: Domain name for domain scoping. - :param string project_id: Project ID for project scoping. - :param string project_name: Project name for project scoping. - :param string project_domain_id: Project's domain ID for project. - :param string project_domain_name: Project's domain name for project. - :param bool reauthenticate: Allow fetching a new token if the current one - is going to expire. (optional) default True - """ - - _auth_method_class = TOTPMethod diff --git a/keystoneauth1/loading/__init__.py b/keystoneauth1/loading/__init__.py deleted file mode 100644 index e31b2f4..0000000 --- a/keystoneauth1/loading/__init__.py +++ /dev/null @@ -1,85 +0,0 @@ -# Licensed under the Apache License, Version 2.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 keystoneauth1.loading import adapter -from keystoneauth1.loading.base import * # noqa -from keystoneauth1.loading import cli -from keystoneauth1.loading import conf -from keystoneauth1.loading.identity import * # noqa -from keystoneauth1.loading.opts import * # noqa -from keystoneauth1.loading import session - - -register_auth_argparse_arguments = cli.register_argparse_arguments -load_auth_from_argparse_arguments = cli.load_from_argparse_arguments - -get_auth_common_conf_options = conf.get_common_conf_options -get_auth_plugin_conf_options = conf.get_plugin_conf_options -register_auth_conf_options = conf.register_conf_options -load_auth_from_conf_options = conf.load_from_conf_options - -register_session_argparse_arguments = session.register_argparse_arguments -load_session_from_argparse_arguments = session.load_from_argparse_arguments -register_session_conf_options = session.register_conf_options -load_session_from_conf_options = session.load_from_conf_options -get_session_conf_options = session.get_conf_options - -register_adapter_argparse_arguments = adapter.register_argparse_arguments -register_service_adapter_argparse_arguments = ( - adapter.register_service_argparse_arguments) -register_adapter_conf_options = adapter.register_conf_options -load_adapter_from_conf_options = adapter.load_from_conf_options -get_adapter_conf_options = adapter.get_conf_options - - -__all__ = ( - # loading.base - 'BaseLoader', - 'get_available_plugin_names', - 'get_available_plugin_loaders', - 'get_plugin_loader', - 'PLUGIN_NAMESPACE', - - # loading.identity - 'BaseIdentityLoader', - 'BaseV2Loader', - 'BaseV3Loader', - 'BaseFederationLoader', - 'BaseGenericLoader', - - # auth cli - 'register_auth_argparse_arguments', - 'load_auth_from_argparse_arguments', - - # auth conf - 'get_auth_common_conf_options', - 'get_auth_plugin_conf_options', - 'register_auth_conf_options', - 'load_auth_from_conf_options', - - # session - 'register_session_argparse_arguments', - 'load_session_from_argparse_arguments', - 'register_session_conf_options', - 'load_session_from_conf_options', - 'get_session_conf_options', - - # adapter - 'register_adapter_argparse_arguments', - 'register_service_adapter_argparse_arguments', - 'register_adapter_conf_options', - 'load_adapter_from_conf_options', - 'get_adapter_conf_options', - - # loading.opts - 'Opt', -) diff --git a/keystoneauth1/loading/_plugins/__init__.py b/keystoneauth1/loading/_plugins/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/keystoneauth1/loading/_plugins/admin_token.py b/keystoneauth1/loading/_plugins/admin_token.py deleted file mode 100644 index 3aba63d..0000000 --- a/keystoneauth1/loading/_plugins/admin_token.py +++ /dev/null @@ -1,46 +0,0 @@ -# Licensed under the Apache License, Version 2.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 keystoneauth1 import loading -from keystoneauth1 import token_endpoint - - -class AdminToken(loading.BaseLoader): - """Use an existing token and a known endpoint to perform requests. - - This plugin is primarily useful for development or for use with identity - service ADMIN tokens. Because this token is used directly there is no - fetching a service catalog or determining scope information and so it - cannot be used by clients that expect use this scope information. - - Because there is no service catalog the endpoint that is supplied with - initialization is used for all operations performed with this plugin so - must be the full base URL to an actual service. - """ - - @property - def plugin_class(self): - return token_endpoint.Token - - def get_options(self): - options = super(AdminToken, self).get_options() - - options.extend([ - loading.Opt('endpoint', - deprecated=[loading.Opt('url')], - help='The endpoint that will always be used'), - loading.Opt('token', - secret=True, - help='The token that will always be used'), - ]) - - return options diff --git a/keystoneauth1/loading/_plugins/identity/__init__.py b/keystoneauth1/loading/_plugins/identity/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/keystoneauth1/loading/_plugins/identity/generic.py b/keystoneauth1/loading/_plugins/identity/generic.py deleted file mode 100644 index b6c139c..0000000 --- a/keystoneauth1/loading/_plugins/identity/generic.py +++ /dev/null @@ -1,75 +0,0 @@ -# Licensed under the Apache License, Version 2.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 keystoneauth1 import identity -from keystoneauth1 import loading - - -class Token(loading.BaseGenericLoader): - """Given an existing token rescope it to another target. - - This plugin uses the Identity service's rescope mechanism to get a new - token based upon an existing token. Because an auth plugin requires a - service catalog and scope information it is often easier to fetch a new - token based on an existing one than validate and reuse the one you already - have. - - As a generic plugin this plugin is identity version independent and will - discover available versions before use. This means it expects to be - providen an unversioned URL to operate against. - """ - - @property - def plugin_class(self): - return identity.Token - - def get_options(self): - options = super(Token, self).get_options() - - options.extend([ - loading.Opt('token', secret=True, - help='Token to authenticate with'), - ]) - - return options - - -class Password(loading.BaseGenericLoader): - """Authenticate via a username and password. - - Authenticate to the identity service using an inbuilt username and - password. This is the standard and most common form of authentication. - - As a generic plugin this plugin is identity version independent and will - discover available versions before use. This means it expects to be - providen an unversioned URL to operate against. - """ - - @property - def plugin_class(self): - return identity.Password - - def get_options(cls): - options = super(Password, cls).get_options() - options.extend([ - loading.Opt('user-id', help='User id'), - loading.Opt('username', - help='Username', - deprecated=[loading.Opt('user-name')]), - loading.Opt('user-domain-id', help="User's domain id"), - loading.Opt('user-domain-name', help="User's domain name"), - loading.Opt('password', - secret=True, - prompt='Password: ', - help="User's password"), - ]) - return options diff --git a/keystoneauth1/loading/_plugins/identity/v2.py b/keystoneauth1/loading/_plugins/identity/v2.py deleted file mode 100644 index aaacd15..0000000 --- a/keystoneauth1/loading/_plugins/identity/v2.py +++ /dev/null @@ -1,53 +0,0 @@ -# Licensed under the Apache License, Version 2.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 keystoneauth1 import identity -from keystoneauth1 import loading - - -class Token(loading.BaseV2Loader): - - @property - def plugin_class(self): - return identity.V2Token - - def get_options(self): - options = super(Token, self).get_options() - - options.extend([ - loading.Opt('token', secret=True, help='Token'), - ]) - - return options - - -class Password(loading.BaseV2Loader): - - @property - def plugin_class(self): - return identity.V2Password - - def get_options(self): - options = super(Password, self).get_options() - - options.extend([ - loading.Opt('username', - deprecated=[loading.Opt('user-name')], - help='Username to login with'), - loading.Opt('user-id', help='User ID to login with'), - loading.Opt('password', - secret=True, - prompt='Password: ', - help='Password to use'), - ]) - - return options diff --git a/keystoneauth1/loading/_plugins/identity/v3.py b/keystoneauth1/loading/_plugins/identity/v3.py deleted file mode 100644 index cc0ae1f..0000000 --- a/keystoneauth1/loading/_plugins/identity/v3.py +++ /dev/null @@ -1,256 +0,0 @@ -# Licensed under the Apache License, Version 2.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 keystoneauth1 import exceptions -from keystoneauth1 import identity -from keystoneauth1 import loading - - -def _add_common_identity_options(options): - options.extend([ - loading.Opt('user-id', help='User ID'), - loading.Opt('username', - help='Username', - deprecated=[loading.Opt('user-name')]), - loading.Opt('user-domain-id', help="User's domain id"), - loading.Opt('user-domain-name', help="User's domain name"), - ]) - - -def _assert_identity_options(options): - if (options.get('username') and - not (options.get('user_domain_name') or - options.get('user_domain_id'))): - m = "You have provided a username. In the V3 identity API a " \ - "username is only unique within a domain so you must " \ - "also provide either a user_domain_id or user_domain_name." - raise exceptions.OptionError(m) - - -class Password(loading.BaseV3Loader): - - @property - def plugin_class(self): - return identity.V3Password - - def get_options(self): - options = super(Password, self).get_options() - _add_common_identity_options(options) - - options.extend([ - loading.Opt('password', - secret=True, - prompt='Password: ', - help="User's password"), - ]) - - return options - - def load_from_options(self, **kwargs): - _assert_identity_options(kwargs) - - return super(Password, self).load_from_options(**kwargs) - - -class Token(loading.BaseV3Loader): - - @property - def plugin_class(self): - return identity.V3Token - - def get_options(self): - options = super(Token, self).get_options() - - options.extend([ - loading.Opt('token', - secret=True, - help='Token to authenticate with'), - ]) - - return options - - -class _OpenIDConnectBase(loading.BaseFederationLoader): - - def load_from_options(self, **kwargs): - if not (kwargs.get('access_token_endpoint') or - kwargs.get('discovery_endpoint')): - m = ("You have to specify either an 'access-token-endpoint' or " - "a 'discovery-endpoint'.") - raise exceptions.OptionError(m) - - return super(_OpenIDConnectBase, self).load_from_options(**kwargs) - - def get_options(self): - options = super(_OpenIDConnectBase, self).get_options() - - options.extend([ - loading.Opt('client-id', help='OAuth 2.0 Client ID'), - loading.Opt('client-secret', secret=True, - help='OAuth 2.0 Client Secret'), - loading.Opt('openid-scope', default="openid profile", - dest="scope", - help='OpenID Connect scope that is requested from ' - 'authorization server. Note that the OpenID ' - 'Connect specification states that "openid" ' - 'must be always specified.'), - loading.Opt('access-token-endpoint', - help='OpenID Connect Provider Token Endpoint. Note ' - 'that if a discovery document is being passed this ' - 'option will override the endpoint provided by the ' - 'server in the discovery document.'), - loading.Opt('discovery-endpoint', - help='OpenID Connect Discovery Document URL. ' - 'The discovery document will be used to obtain the ' - 'values of the access token endpoint and the ' - 'authentication endpoint. This URL should look like ' - 'https://idp.example.org/.well-known/' - 'openid-configuration'), - loading.Opt('access-token-type', - help='OAuth 2.0 Authorization Server Introspection ' - 'token type, it is used to decide which type ' - 'of token will be used when processing token ' - 'introspection. Valid values are: ' - '"access_token" or "id_token"'), - ]) - - return options - - -class OpenIDConnectClientCredentials(_OpenIDConnectBase): - - @property - def plugin_class(self): - return identity.V3OidcClientCredentials - - def get_options(self): - options = super(OpenIDConnectClientCredentials, self).get_options() - - return options - - -class OpenIDConnectPassword(_OpenIDConnectBase): - - @property - def plugin_class(self): - return identity.V3OidcPassword - - def get_options(self): - options = super(OpenIDConnectPassword, self).get_options() - - options.extend([ - loading.Opt('username', help='Username', required=True), - loading.Opt('password', secret=True, - help='Password', required=True), - ]) - - return options - - -class OpenIDConnectAuthorizationCode(_OpenIDConnectBase): - - @property - def plugin_class(self): - return identity.V3OidcAuthorizationCode - - def get_options(self): - options = super(OpenIDConnectAuthorizationCode, self).get_options() - - options.extend([ - loading.Opt('redirect-uri', help='OpenID Connect Redirect URL'), - loading.Opt('code', secret=True, required=True, - deprecated=[loading.Opt('authorization-code')], - help='OAuth 2.0 Authorization Code'), - ]) - - return options - - -class OpenIDConnectAccessToken(loading.BaseFederationLoader): - - @property - def plugin_class(self): - return identity.V3OidcAccessToken - - def get_options(self): - options = super(OpenIDConnectAccessToken, self).get_options() - - options.extend([ - loading.Opt('access-token', secret=True, required=True, - help='OAuth 2.0 Access Token'), - ]) - return options - - -class TOTP(loading.BaseV3Loader): - - @property - def plugin_class(self): - return identity.V3TOTP - - def get_options(self): - options = super(TOTP, self).get_options() - _add_common_identity_options(options) - - options.extend([ - loading.Opt('passcode', secret=True, help="User's TOTP passcode"), - ]) - - return options - - def load_from_options(self, **kwargs): - _assert_identity_options(kwargs) - - return super(TOTP, self).load_from_options(**kwargs) - - -class TokenlessAuth(loading.BaseLoader): - - @property - def plugin_class(self): - return identity.V3TokenlessAuth - - def get_options(self): - options = super(TokenlessAuth, self).get_options() - - options.extend([ - loading.Opt('auth-url', required=True, - help='Authentication URL'), - loading.Opt('domain-id', help='Domain ID to scope to'), - loading.Opt('domain-name', help='Domain name to scope to'), - loading.Opt('project-id', help='Project ID to scope to'), - loading.Opt('project-name', help='Project name to scope to'), - loading.Opt('project-domain-id', - help='Domain ID containing project'), - loading.Opt('project-domain-name', - help='Domain name containing project'), - ]) - - return options - - def load_from_options(self, **kwargs): - if (not kwargs.get('domain_id') and - not kwargs.get('domain_name') and - not kwargs.get('project_id') and - not kwargs.get('project_name') or - (kwargs.get('project_name') and - not (kwargs.get('project_domain_name') or - kwargs.get('project_domain_id')))): - m = ('You need to provide either a domain_name, domain_id, ' - 'project_id or project_name. ' - 'If you have provided a project_name, in the V3 identity ' - 'API a project_name is only unique within a domain so ' - 'you must also provide either a project_domain_id or ' - 'project_domain_name.') - raise exceptions.OptionError(m) - - return super(TokenlessAuth, self).load_from_options(**kwargs) diff --git a/keystoneauth1/loading/_plugins/noauth.py b/keystoneauth1/loading/_plugins/noauth.py deleted file mode 100644 index ea75f5b..0000000 --- a/keystoneauth1/loading/_plugins/noauth.py +++ /dev/null @@ -1,33 +0,0 @@ -# Licensed under the Apache License, Version 2.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 keystoneauth1 import loading -from keystoneauth1 import noauth - - -class NoAuth(loading.BaseLoader): - """Use no tokens to perform requests. - - This must be used together with adapter.Adapter.endpoint_override - to instantiate clients for services deployed in noauth/standalone mode. - - There is no fetching a service catalog or determining scope information - and so it cannot be used by clients that expect use this scope information. - - """ - - @property - def plugin_class(self): - return noauth.NoAuth - - def get_options(self): - return [] diff --git a/keystoneauth1/loading/_utils.py b/keystoneauth1/loading/_utils.py deleted file mode 100644 index 2cb796c..0000000 --- a/keystoneauth1/loading/_utils.py +++ /dev/null @@ -1,40 +0,0 @@ -# Licensed under the Apache License, Version 2.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. - -cfg = None -_NOT_FOUND = object() - - -def get_oslo_config(): - """Runtime load the oslo.config object. - - In performance optimization of openstackclient it was determined that even - optimistically loading oslo.config if available had a performance cost. - Given that we used to only raise the ImportError when the function was - called also attempt to do the import to do everything at runtime. - """ - global cfg - - # First Call - if not cfg: - try: - from oslo_config import cfg - except ImportError: - cfg = _NOT_FOUND - - if cfg is _NOT_FOUND: - raise ImportError("oslo.config is not an automatic dependency of " - "keystoneauth. If you wish to use oslo.config " - "you need to import it into your application's " - "requirements file. ") - - return cfg diff --git a/keystoneauth1/loading/adapter.py b/keystoneauth1/loading/adapter.py deleted file mode 100644 index 24b1055..0000000 --- a/keystoneauth1/loading/adapter.py +++ /dev/null @@ -1,209 +0,0 @@ -# Licensed under the Apache License, Version 2.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 keystoneauth1 import adapter -from keystoneauth1.loading import _utils -from keystoneauth1.loading import base - - -__all__ = ('register_argparse_arguments', - 'register_service_argparse_arguments', - 'register_conf_options', - 'load_from_conf_options', - 'get_conf_options') - - -class Adapter(base.BaseLoader): - - @property - def plugin_class(self): - return adapter.Adapter - - def get_options(self): - return [] - - @staticmethod - def get_conf_options(): - """Get oslo_config options that are needed for a :py:class:`.Adapter`. - - These may be useful without being registered for config file generation - or to manipulate the options before registering them yourself. - - The options that are set are: - :service_type: The default service_type for URL discovery. - :service_name: The default service_name for URL discovery. - :interface: The default interface for URL discovery. - (deprecated) - :valid_interfaces: List of acceptable interfaces for URL - discovery. Can be a list of any of - 'public', 'internal' or 'admin'. - :region_name: The default region_name for URL discovery. - :endpoint_override: Always use this endpoint URL for requests - for this client. - :version: The minimum version restricted to a given Major - API. Mutually exclusive with min_version and - max_version. - :min_version: The minimum major version of a given API, - intended to be used as the lower bound of a - range with max_version. Mutually exclusive with - version. If min_version is given with no - max_version it is as if max version is - 'latest'. - :max_version: The maximum major version of a given API, - intended to be used as the upper bound of a - range with min_version. Mutually exclusive with - version. - - :returns: A list of oslo_config options. - """ - cfg = _utils.get_oslo_config() - - return [cfg.StrOpt('service-type', - help='The default service_type for endpoint URL ' - 'discovery.'), - cfg.StrOpt('service-name', - help='The default service_name for endpoint URL ' - 'discovery.'), - cfg.StrOpt('interface', - help='The default interface for endpoint URL ' - 'discovery.', - deprecated_for_removal=True, - deprecated_reason='Using valid-interfaces is' - ' preferrable because it is' - ' capable of accepting a list of' - ' possible interfaces.'), - cfg.ListOpt('valid-interfaces', - help='List of interfaces, in order of preference, ' - 'for endpoint URL.'), - cfg.StrOpt('region-name', - help='The default region_name for endpoint URL ' - 'discovery.'), - cfg.StrOpt('endpoint-override', - help='Always use this endpoint URL for requests ' - 'for this client.'), - cfg.StrOpt('version', - help='Minimum Major API version within a given ' - 'Major API version for endpoint URL ' - 'discovery. Mutually exclusive with ' - 'min_version and max_version'), - cfg.StrOpt('min-version', - help='The minimum major version of a given API, ' - 'intended to be used as the lower bound of a ' - 'range with max_version. Mutually exclusive ' - 'with version. If min_version is given with ' - 'no max_version it is as if max version is ' - '"latest".'), - cfg.StrOpt('max-version', - help='The maximum major version of a given API, ' - 'intended to be used as the upper bound of a ' - 'range with min_version. Mutually exclusive ' - 'with version.'), - ] - - def register_conf_options(self, conf, group): - """Register the oslo_config options that are needed for an Adapter. - - The options that are set are: - :service_type: The default service_type for URL discovery. - :service_name: The default service_name for URL discovery. - :interface: The default interface for URL discovery. - (deprecated) - :valid_interfaces: List of acceptable interfaces for URL - discovery. Can be a list of any of - 'public', 'internal' or 'admin'. - :region_name: The default region_name for URL discovery. - :endpoint_override: Always use this endpoint URL for requests - for this client. - :version: The minimum version restricted to a given Major - API. Mutually exclusive with min_version and - max_version. - :min_version: The minimum major version of a given API, - intended to be used as the lower bound of a - range with max_version. Mutually exclusive with - version. If min_version is given with no - max_version it is as if max version is - 'latest'. - :max_version: The maximum major version of a given API, - intended to be used as the upper bound of a - range with min_version. Mutually exclusive with - version. - - :param oslo_config.Cfg conf: config object to register with. - :param string group: The ini group to register options in. - :returns: The list of options that was registered. - """ - opts = self.get_conf_options() - conf.register_group(_utils.get_oslo_config().OptGroup(group)) - conf.register_opts(opts, group=group) - return opts - - def load_from_conf_options(self, conf, group, **kwargs): - """Create an Adapter object from an oslo_config object. - - The options must have been previously registered with - register_conf_options. - - :param oslo_config.Cfg conf: config object to register with. - :param string group: The ini group to register options in. - :param dict kwargs: Additional parameters to pass to Adapter - construction. - :returns: A new Adapter object. - :rtype: :py:class:`.Adapter` - """ - c = conf[group] - - if c.valid_interfaces and c.interface: - raise TypeError("interface and valid_interfaces are mutually" - " exclusive. Please use valid_interfaces.") - if c.valid_interfaces: - for iface in c.valid_interfaces: - if iface not in ('public', 'internal', 'admin'): - raise TypeError("'{iface}' is not a valid value for" - " valid_interfaces. Valid valies are" - " public, internal or admin") - kwargs.setdefault('interface', c.valid_interfaces) - else: - kwargs.setdefault('interface', c.interface) - kwargs.setdefault('service_type', c.service_type) - kwargs.setdefault('service_name', c.service_name) - kwargs.setdefault('region_name', c.region_name) - kwargs.setdefault('endpoint_override', c.endpoint_override) - kwargs.setdefault('version', c.version) - kwargs.setdefault('min_version', c.min_version) - kwargs.setdefault('max_version', c.max_version) - if kwargs['version'] and ( - kwargs['max_version'] or kwargs['min_version']): - raise TypeError( - "version is mutually exclusive with min_version and" - " max_version") - - return self.load_from_options(**kwargs) - - -def register_argparse_arguments(*args, **kwargs): - return adapter.register_adapter_argparse_arguments(*args, **kwargs) - - -def register_service_argparse_arguments(*args, **kwargs): - return adapter.register_service_adapter_argparse_arguments(*args, **kwargs) - - -def register_conf_options(*args, **kwargs): - return Adapter().register_conf_options(*args, **kwargs) - - -def load_from_conf_options(*args, **kwargs): - return Adapter().load_from_conf_options(*args, **kwargs) - - -def get_conf_options(): - return Adapter.get_conf_options() diff --git a/keystoneauth1/loading/base.py b/keystoneauth1/loading/base.py deleted file mode 100644 index ec91d07..0000000 --- a/keystoneauth1/loading/base.py +++ /dev/null @@ -1,187 +0,0 @@ -# Licensed under the Apache License, Version 2.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 abc - -import six -import stevedore - -from keystoneauth1 import exceptions - -PLUGIN_NAMESPACE = 'keystoneauth1.plugin' - - -__all__ = ('get_available_plugin_names', - 'get_available_plugin_loaders', - 'get_plugin_loader', - 'get_plugin_options', - 'BaseLoader', - 'PLUGIN_NAMESPACE') - - -def _auth_plugin_available(ext): - """Read the value of available for whether to load this plugin.""" - return ext.obj.available - - -def get_available_plugin_names(): - """Get the names of all the plugins that are available on the system. - - This is particularly useful for help and error text to prompt a user for - example what plugins they may specify. - - :returns: A list of names. - :rtype: frozenset - """ - mgr = stevedore.EnabledExtensionManager(namespace=PLUGIN_NAMESPACE, - check_func=_auth_plugin_available, - invoke_on_load=True, - propagate_map_exceptions=True) - return frozenset(mgr.names()) - - -def get_available_plugin_loaders(): - """Retrieve all the plugin classes available on the system. - - :returns: A dict with plugin entrypoint name as the key and the plugin - loader as the value. - :rtype: dict - """ - mgr = stevedore.EnabledExtensionManager(namespace=PLUGIN_NAMESPACE, - check_func=_auth_plugin_available, - invoke_on_load=True, - propagate_map_exceptions=True) - - return dict(mgr.map(lambda ext: (ext.entry_point.name, ext.obj))) - - -def get_plugin_loader(name): - """Retrieve a plugin class by its entrypoint name. - - :param str name: The name of the object to get. - - :returns: An auth plugin class. - :rtype: :py:class:`keystoneauth1.loading.BaseLoader` - - :raises keystoneauth1.exceptions.auth_plugins.NoMatchingPlugin: - if a plugin cannot be created. - """ - try: - mgr = stevedore.DriverManager(namespace=PLUGIN_NAMESPACE, - invoke_on_load=True, - name=name) - except RuntimeError: - raise exceptions.NoMatchingPlugin(name) - - return mgr.driver - - -def get_plugin_options(name): - """Get the options for a specific plugin. - - This will be the list of options that is registered and loaded by the - specified plugin. - - :returns: A list of :py:class:`keystoneauth1.loading.Opt` options. - - :raises keystoneauth1.exceptions.auth_plugins.NoMatchingPlugin: - if a plugin cannot be created. - """ - return get_plugin_loader(name).get_options() - - -@six.add_metaclass(abc.ABCMeta) -class BaseLoader(object): - - @property - def plugin_class(self): - raise NotImplementedError() - - def create_plugin(self, **kwargs): - """Create a plugin from the options available for the loader. - - Given the options that were specified by the loader create an - appropriate plugin. You can override this function in your loader. - - This used to be specified by providing the plugin_class property and - this is still supported, however specifying a property didn't let you - choose a plugin type based upon the options that were presented. - - Override this function if you wish to return different plugins based on - the options presented, otherwise you can simply provide the - plugin_class property. - - Added 2.9 - """ - return self.plugin_class(**kwargs) - - @abc.abstractmethod - def get_options(self): - """Return the list of parameters associated with the auth plugin. - - This list may be used to generate CLI or config arguments. - - :returns: A list of Param objects describing available plugin - parameters. - :rtype: list - """ - return [] - - @property - def available(self): - """Return if the plugin is available for loading. - - If a plugin is missing dependencies or for some other reason should not - be available to the current system it should override this property and - return False to exclude itself from the plugin list. - - :rtype: bool - """ - return True - - def load_from_options(self, **kwargs): - """Create a plugin from the arguments retrieved from get_options. - - A client can override this function to do argument validation or to - handle differences between the registered options and what is required - to create the plugin. - """ - missing_required = [o for o in self.get_options() - if o.required and kwargs.get(o.dest) is None] - - if missing_required: - raise exceptions.MissingRequiredOptions(missing_required) - - return self.create_plugin(**kwargs) - - def load_from_options_getter(self, getter, **kwargs): - """Load a plugin from getter function that returns appropriate values. - - To handle cases other than the provided CONF and CLI loading you can - specify a custom loader function that will be queried for the option - value. - The getter is a function that takes a - :py:class:`keystoneauth1.loading.Opt` and returns a value to load with. - - :param getter: A function that returns a value for the given opt. - :type getter: callable - - :returns: An authentication Plugin. - :rtype: :py:class:`keystoneauth1.plugin.BaseAuthPlugin` - """ - for opt in (o for o in self.get_options() if o.dest not in kwargs): - val = getter(opt) - if val is not None: - val = opt.type(val) - kwargs[opt.dest] = val - - return self.load_from_options(**kwargs) diff --git a/keystoneauth1/loading/cli.py b/keystoneauth1/loading/cli.py deleted file mode 100644 index 79816ca..0000000 --- a/keystoneauth1/loading/cli.py +++ /dev/null @@ -1,105 +0,0 @@ -# Licensed under the Apache License, Version 2.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 argparse -import os - -from positional import positional - -from keystoneauth1.loading import base - - -__all__ = ('register_argparse_arguments', - 'load_from_argparse_arguments') - - -def _register_plugin_argparse_arguments(parser, plugin): - for opt in plugin.get_options(): - parser.add_argument(*opt.argparse_args, - default=opt.argparse_default, - metavar=opt.metavar, - help=opt.help, - dest='os_%s' % opt.dest) - - -@positional() -def register_argparse_arguments(parser, argv, default=None): - """Register CLI options needed to create a plugin. - - The function inspects the provided arguments so that it can also register - the options required for that specific plugin if available. - - :param parser: the parser to attach argparse options to. - :type parser: argparse.ArgumentParser - :param list argv: the arguments provided to the appliation. - :param str/class default: a default plugin name or a plugin object to use - if one isn't specified by the CLI. default: None. - - :returns: The plugin class that will be loaded or None if not provided. - :rtype: :class:`keystoneauth1.plugin.BaseAuthPlugin` - - :raises keystoneauth1.exceptions.auth_plugins.NoMatchingPlugin: - if a plugin cannot be created. - """ - in_parser = argparse.ArgumentParser(add_help=False) - env_plugin = os.environ.get('OS_AUTH_TYPE', - os.environ.get('OS_AUTH_PLUGIN', default)) - for p in (in_parser, parser): - p.add_argument('--os-auth-type', - '--os-auth-plugin', - metavar='', - default=env_plugin, - help='Authentication type to use') - - options, _args = in_parser.parse_known_args(argv) - - if not options.os_auth_type: - return None - - if isinstance(options.os_auth_type, base.BaseLoader): - msg = 'Default Authentication options' - plugin = options.os_auth_type - else: - msg = 'Options specific to the %s plugin.' % options.os_auth_type - plugin = base.get_plugin_loader(options.os_auth_type) - - group = parser.add_argument_group('Authentication Options', msg) - _register_plugin_argparse_arguments(group, plugin) - return plugin - - -def load_from_argparse_arguments(namespace, **kwargs): - """Retrieve the created plugin from the completed argparse results. - - Loads and creates the auth plugin from the information parsed from the - command line by argparse. - - :param Namespace namespace: The result from CLI parsing. - - :returns: An auth plugin, or None if a name is not provided. - :rtype: :class:`keystoneauth1.plugin.BaseAuthPlugin` - - :raises keystoneauth1.exceptions.auth_plugins.NoMatchingPlugin: - if a plugin cannot be created. - """ - if not namespace.os_auth_type: - return None - - if isinstance(namespace.os_auth_type, type): - plugin = namespace.os_auth_type - else: - plugin = base.get_plugin_loader(namespace.os_auth_type) - - def _getter(opt): - return getattr(namespace, 'os_%s' % opt.dest) - - return plugin.load_from_options_getter(_getter, **kwargs) diff --git a/keystoneauth1/loading/conf.py b/keystoneauth1/loading/conf.py deleted file mode 100644 index a150260..0000000 --- a/keystoneauth1/loading/conf.py +++ /dev/null @@ -1,135 +0,0 @@ -# Licensed under the Apache License, Version 2.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 keystoneauth1.loading import base -from keystoneauth1.loading import opts - -_AUTH_TYPE_OPT = opts.Opt('auth_type', - deprecated=[opts.Opt('auth_plugin')], - help='Authentication type to load') - -_section_help = 'Config Section from which to load plugin specific options' -_AUTH_SECTION_OPT = opts.Opt('auth_section', help=_section_help) - - -__all__ = ('get_common_conf_options', - 'get_plugin_conf_options', - 'register_conf_options', - 'load_from_conf_options') - - -def get_common_conf_options(): - """Get the oslo_config options common for all auth plugins. - - These may be useful without being registered for config file generation - or to manipulate the options before registering them yourself. - - The options that are set are: - :auth_type: The name of the plugin to load. - :auth_section: The config file section to load options from. - - :returns: A list of oslo_config options. - """ - return [_AUTH_TYPE_OPT._to_oslo_opt(), _AUTH_SECTION_OPT._to_oslo_opt()] - - -def get_plugin_conf_options(plugin): - """Get the oslo_config options for a specific plugin. - - This will be the list of config options that is registered and loaded by - the specified plugin. - - :param plugin: The name of the plugin loader or a plugin loader object - :type plugin: str or keystoneauth1._loading.BaseLoader - - :returns: A list of oslo_config options. - """ - try: - getter = plugin.get_options - except AttributeError: - opts = base.get_plugin_options(plugin) - else: - opts = getter() - - return [o._to_oslo_opt() for o in opts] - - -def register_conf_options(conf, group): - """Register the oslo_config options that are needed for a plugin. - - This only registers the basic options shared by all plugins. Options that - are specific to a plugin are loaded just before they are read. - - The defined options are: - - - auth_type: the name of the auth plugin that will be used for - authentication. - - auth_section: the group from which further auth plugin options should be - taken. If section is not provided then the auth plugin options will be - taken from the same group as provided in the parameters. - - :param conf: config object to register with. - :type conf: oslo_config.cfg.ConfigOpts - :param string group: The ini group to register options in. - """ - conf.register_opt(_AUTH_SECTION_OPT._to_oslo_opt(), group=group) - - # NOTE(jamielennox): plugins are allowed to specify a 'section' which is - # the group that auth options should be taken from. If not present they - # come from the same as the base options were registered in. If present - # then the auth_plugin option may be read from that section so add that - # option. - if conf[group].auth_section: - group = conf[group].auth_section - - conf.register_opt(_AUTH_TYPE_OPT._to_oslo_opt(), group=group) - - -def load_from_conf_options(conf, group, **kwargs): - """Load a plugin from an oslo_config CONF object. - - Each plugin will register their own required options and so there is no - standard list and the plugin should be consulted. - - The base options should have been registered with register_conf_options - before this function is called. - - :param conf: A conf object. - :type conf: oslo_config.cfg.ConfigOpts - :param str group: The group name that options should be read from. - - :returns: An authentication Plugin or None if a name is not provided - :rtype: :class:`keystoneauth1.plugin.BaseAuthPlugin` - - :raises keystoneauth1.exceptions.auth_plugins.NoMatchingPlugin: - if a plugin cannot be created. - """ - # NOTE(jamielennox): plugins are allowed to specify a 'section' which is - # the group that auth options should be taken from. If not present they - # come from the same as the base options were registered in. - if conf[group].auth_section: - group = conf[group].auth_section - - name = conf[group].auth_type - if not name: - return None - - plugin = base.get_plugin_loader(name) - plugin_opts = plugin.get_options() - oslo_opts = [o._to_oslo_opt() for o in plugin_opts] - - conf.register_opts(oslo_opts, group=group) - - def _getter(opt): - return conf[group][opt.dest] - - return plugin.load_from_options_getter(_getter, **kwargs) diff --git a/keystoneauth1/loading/identity.py b/keystoneauth1/loading/identity.py deleted file mode 100644 index e08e257..0000000 --- a/keystoneauth1/loading/identity.py +++ /dev/null @@ -1,162 +0,0 @@ -# Licensed under the Apache License, Version 2.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 keystoneauth1 import exceptions -from keystoneauth1.loading import base -from keystoneauth1.loading import opts - -__all__ = ('BaseIdentityLoader', - 'BaseV2Loader', - 'BaseV3Loader', - 'BaseFederationLoader', - 'BaseGenericLoader') - - -class BaseIdentityLoader(base.BaseLoader): - """Base Option handling for identity plugins. - - This class defines options and handling that should be common across all - plugins that are developed against the OpenStack identity service. It - provides the options expected by the - :py:class:`keystoneauth1.identity.BaseIdentityPlugin` class. - """ - - def get_options(self): - options = super(BaseIdentityLoader, self).get_options() - - options.extend([ - opts.Opt('auth-url', - required=True, - help='Authentication URL'), - ]) - - return options - - -class BaseV2Loader(BaseIdentityLoader): - """Base Option handling for identity plugins. - - This class defines options and handling that should be common to the V2 - identity API. It provides the options expected by the - :py:class:`keystoneauth1.identity.v2.Auth` class. - """ - - def get_options(self): - options = super(BaseV2Loader, self).get_options() - - options.extend([ - opts.Opt('tenant-id', help='Tenant ID'), - opts.Opt('tenant-name', help='Tenant Name'), - opts.Opt('trust-id', help='Trust ID'), - ]) - - return options - - -class BaseV3Loader(BaseIdentityLoader): - """Base Option handling for identity plugins. - - This class defines options and handling that should be common to the V3 - identity API. It provides the options expected by the - :py:class:`keystoneauth1.identity.v3.Auth` class. - """ - - def get_options(self): - options = super(BaseV3Loader, self).get_options() - - options.extend([ - opts.Opt('domain-id', help='Domain ID to scope to'), - opts.Opt('domain-name', help='Domain name to scope to'), - opts.Opt('project-id', help='Project ID to scope to'), - opts.Opt('project-name', help='Project name to scope to'), - opts.Opt('project-domain-id', - help='Domain ID containing project'), - opts.Opt('project-domain-name', - help='Domain name containing project'), - opts.Opt('trust-id', help='Trust ID'), - ]) - - return options - - def load_from_options(self, **kwargs): - if (kwargs.get('project_name') and - not (kwargs.get('project_domain_name') or - kwargs.get('project_domain_id'))): - m = "You have provided a project_name. In the V3 identity API a " \ - "project_name is only unique within a domain so you must " \ - "also provide either a project_domain_id or " \ - "project_domain_name." - raise exceptions.OptionError(m) - - return super(BaseV3Loader, self).load_from_options(**kwargs) - - -class BaseFederationLoader(BaseV3Loader): - """Base Option handling for federation plugins. - - This class defines options and handling that should be common to the V3 - identity federation API. It provides the options expected by the - :py:class:`keystoneauth1.identity.v3.FederationBaseAuth` class. - """ - - def get_options(self): - options = super(BaseFederationLoader, self).get_options() - - options.extend([ - opts.Opt('identity-provider', - help="Identity Provider's name", - required=True), - opts.Opt('protocol', - help='Protocol for federated plugin', - required=True), - ]) - - return options - - -class BaseGenericLoader(BaseIdentityLoader): - """Base Option handling for generic plugins. - - This class defines options and handling that should be common to generic - plugins. These plugins target the OpenStack identity service however are - designed to be independent of API version. It provides the options expected - by the :py:class:`keystoneauth1.identity.v3.BaseGenericPlugin` class. - """ - - def get_options(self): - options = super(BaseGenericLoader, self).get_options() - - options.extend([ - opts.Opt('domain-id', help='Domain ID to scope to'), - opts.Opt('domain-name', help='Domain name to scope to'), - opts.Opt('project-id', help='Project ID to scope to', - deprecated=[opts.Opt('tenant-id')]), - opts.Opt('project-name', help='Project name to scope to', - deprecated=[opts.Opt('tenant-name')]), - opts.Opt('project-domain-id', - help='Domain ID containing project'), - opts.Opt('project-domain-name', - help='Domain name containing project'), - opts.Opt('trust-id', help='Trust ID'), - opts.Opt('default-domain-id', - help='Optional domain ID to use with v3 and v2 ' - 'parameters. It will be used for both the user ' - 'and project domain in v3 and ignored in ' - 'v2 authentication.'), - opts.Opt('default-domain-name', - help='Optional domain name to use with v3 API and v2 ' - 'parameters. It will be used for both the user ' - 'and project domain in v3 and ignored in ' - 'v2 authentication.'), - ]) - - return options diff --git a/keystoneauth1/loading/opts.py b/keystoneauth1/loading/opts.py deleted file mode 100644 index 03eed84..0000000 --- a/keystoneauth1/loading/opts.py +++ /dev/null @@ -1,151 +0,0 @@ -# Licensed under the Apache License, Version 2.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 itertools -import os - -from positional import positional - -from keystoneauth1.loading import _utils - - -__all__ = ('Opt',) - - -class Opt(object): - """An option required by an authentication plugin. - - Opts provide a means for authentication plugins that are going to be - dynamically loaded to specify the parameters that are to be passed to the - plugin on initialization. - - The Opt specifies information about the way the plugin parameter is to be - represented in different loading mechanisms. - - When defining an Opt with a - the - should be present in the name - parameter. This will automatically be converted to an _ when passing to the - plugin initialization. For example, you should specify:: - - Opt('user-domain-id') - - which will pass the value as `user_domain_id` to the plugin's - initialization. - - :param str name: The name of the option. - :param callable type: The type of the option. This is a callable which is - passed the raw option that was loaded (often a string) and is required - to return the parameter in the type expected by __init__. - :param str help: The help text that is shown along with the option. - :param bool secret: If the parameter is secret it should not be printed or - logged in debug output. - :param str dest: the name of the argument that will be passed to __init__. - This allows you to have a different name in loading than is used by the - __init__ function. Defaults to the value of name. - :param keystoneauth1.loading.Opt: A list of other options that are - deprecated in favour of this one. This ensures the old options are - still registered. - :type opt: list(Opt) - :param default: A default value that can be used if one is not provided. - :param str metavar: The that should be printed in CLI help text. - :param bool required: If the option is required to load the plugin. If a - required option is not present loading should fail. - :param str prompt: If the option can be requested via a prompt (where - appropriate) set the string that should be used to prompt with. - """ - - @positional() - def __init__(self, - name, - type=str, - help=None, - secret=False, - dest=None, - deprecated=None, - default=None, - metavar=None, - required=False, - prompt=None): - if not callable(type): - raise TypeError('type must be callable') - - if dest is None: - dest = name.replace('-', '_') - - self.name = name - self.type = type - self.help = help - self.secret = secret - self.required = required - self.dest = dest - self.deprecated = [] if deprecated is None else deprecated - self.default = default - self.metavar = metavar - self.prompt = prompt - # These are for oslo.config compat - self.deprecated_opts = self.deprecated - self.deprecated_for_removal = [] - self.sample_default = None - self.group = None - - def __repr__(self): - """Return string representation of option name.""" - return '' % self.name - - def _to_oslo_opt(self): - cfg = _utils.get_oslo_config() - deprecated_opts = [cfg.DeprecatedOpt(o.name) for o in self.deprecated] - - return cfg.Opt(name=self.name, - type=self.type, - help=self.help, - secret=self.secret, - required=self.required, - dest=self.dest, - deprecated_opts=deprecated_opts, - metavar=self.metavar) - - def __eq__(self, other): - """Define equality operator on option parameters.""" - return (type(self) == type(other) and - self.name == other.name and - self.type == other.type and - self.help == other.help and - self.secret == other.secret and - self.required == other.required and - self.dest == other.dest and - self.deprecated == other.deprecated and - self.default == other.default and - self.metavar == other.metavar) - - # NOTE: This function is only needed by Python 2. If we get to point where - # we don't support Python 2 anymore, this function should be removed. - def __ne__(self, other): - """Define inequality operator on option parameters.""" - return not self.__eq__(other) - - @property - def _all_opts(self): - return itertools.chain([self], self.deprecated) - - @property - def argparse_args(self): - return ['--os-%s' % o.name for o in self._all_opts] - - @property - def argparse_default(self): - # select the first ENV that is not false-y or return None - for o in self._all_opts: - v = os.environ.get('OS_%s' % o.name.replace('-', '_').upper()) - if v: - return v - - return self.default diff --git a/keystoneauth1/loading/session.py b/keystoneauth1/loading/session.py deleted file mode 100644 index 4e5ee3e..0000000 --- a/keystoneauth1/loading/session.py +++ /dev/null @@ -1,254 +0,0 @@ -# Licensed under the Apache License, Version 2.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 argparse -import os - -from positional import positional - -from keystoneauth1.loading import _utils -from keystoneauth1.loading import base -from keystoneauth1 import session - - -__all__ = ('register_argparse_arguments', - 'load_from_argparse_arguments', - 'register_conf_options', - 'load_from_conf_options', - 'get_conf_options') - - -def _positive_non_zero_float(argument_value): - if argument_value is None: - return None - try: - value = float(argument_value) - except ValueError: - msg = "%s must be a float" % argument_value - raise argparse.ArgumentTypeError(msg) - if value <= 0: - msg = "%s must be greater than 0" % argument_value - raise argparse.ArgumentTypeError(msg) - return value - - -class Session(base.BaseLoader): - - @property - def plugin_class(self): - return session.Session - - def get_options(self): - return [] - - @positional(1) - def load_from_options(self, - insecure=False, - verify=None, - cacert=None, - cert=None, - key=None, - **kwargs): - """Create a session with individual certificate parameters. - - Some parameters used to create a session don't lend themselves to be - loaded from config/CLI etc. Create a session by converting those - parameters into session __init__ parameters. - """ - if verify is None: - if insecure: - verify = False - else: - verify = cacert or True - - if cert and key: - # passing cert and key together is deprecated in favour of the - # requests lib form of having the cert and key as a tuple - cert = (cert, key) - - return super(Session, self).load_from_options(verify=verify, - cert=cert, - **kwargs) - - def register_argparse_arguments(self, parser): - session_group = parser.add_argument_group( - 'API Connection Options', - 'Options controlling the HTTP API Connections') - - session_group.add_argument( - '--insecure', - default=False, - action='store_true', - help='Explicitly allow client to perform ' - '"insecure" TLS (https) requests. The ' - 'server\'s certificate will not be verified ' - 'against any certificate authorities. This ' - 'option should be used with caution.') - - session_group.add_argument( - '--os-cacert', - metavar='', - default=os.environ.get('OS_CACERT'), - help='Specify a CA bundle file to use in ' - 'verifying a TLS (https) server certificate. ' - 'Defaults to env[OS_CACERT].') - - session_group.add_argument( - '--os-cert', - metavar='', - default=os.environ.get('OS_CERT'), - help='Defaults to env[OS_CERT].') - - session_group.add_argument( - '--os-key', - metavar='', - default=os.environ.get('OS_KEY'), - help='Defaults to env[OS_KEY].') - - session_group.add_argument( - '--timeout', - default=600, - type=_positive_non_zero_float, - metavar='', - help='Set request timeout (in seconds).') - - def load_from_argparse_arguments(self, namespace, **kwargs): - kwargs.setdefault('insecure', namespace.insecure) - kwargs.setdefault('cacert', namespace.os_cacert) - kwargs.setdefault('cert', namespace.os_cert) - kwargs.setdefault('key', namespace.os_key) - kwargs.setdefault('timeout', namespace.timeout) - - return self.load_from_options(**kwargs) - - def get_conf_options(self, deprecated_opts=None): - """Get oslo_config options that are needed for a :py:class:`.Session`. - - These may be useful without being registered for config file generation - or to manipulate the options before registering them yourself. - - The options that are set are: - :cafile: The certificate authority filename. - :certfile: The client certificate file to present. - :keyfile: The key for the client certificate. - :insecure: Whether to ignore SSL verification. - :timeout: The max time to wait for HTTP connections. - - :param dict deprecated_opts: Deprecated options that should be included - in the definition of new options. This should be a dict from the - name of the new option to a list of oslo.DeprecatedOpts that - correspond to the new option. (optional) - - For example, to support the ``ca_file`` option pointing to the new - ``cafile`` option name:: - - old_opt = oslo_cfg.DeprecatedOpt('ca_file', 'old_group') - deprecated_opts={'cafile': [old_opt]} - - :returns: A list of oslo_config options. - """ - cfg = _utils.get_oslo_config() - - if deprecated_opts is None: - deprecated_opts = {} - - return [cfg.StrOpt('cafile', - deprecated_opts=deprecated_opts.get('cafile'), - help='PEM encoded Certificate Authority to use ' - 'when verifying HTTPs connections.'), - cfg.StrOpt('certfile', - deprecated_opts=deprecated_opts.get('certfile'), - help='PEM encoded client certificate cert file'), - cfg.StrOpt('keyfile', - deprecated_opts=deprecated_opts.get('keyfile'), - help='PEM encoded client certificate key file'), - cfg.BoolOpt('insecure', - default=False, - deprecated_opts=deprecated_opts.get('insecure'), - help='Verify HTTPS connections.'), - cfg.IntOpt('timeout', - deprecated_opts=deprecated_opts.get('timeout'), - help='Timeout value for http requests'), - ] - - def register_conf_options(self, conf, group, deprecated_opts=None): - """Register the oslo_config options that are needed for a session. - - The options that are set are: - :cafile: The certificate authority filename. - :certfile: The client certificate file to present. - :keyfile: The key for the client certificate. - :insecure: Whether to ignore SSL verification. - :timeout: The max time to wait for HTTP connections. - - :param oslo_config.Cfg conf: config object to register with. - :param string group: The ini group to register options in. - :param dict deprecated_opts: Deprecated options that should be included - in the definition of new options. This should be a dict from the - name of the new option to a list of oslo.DeprecatedOpts that - correspond to the new option. (optional) - - For example, to support the ``ca_file`` option pointing to the new - ``cafile`` option name:: - - old_opt = oslo_cfg.DeprecatedOpt('ca_file', 'old_group') - deprecated_opts={'cafile': [old_opt]} - - :returns: The list of options that was registered. - """ - opts = self.get_conf_options(deprecated_opts=deprecated_opts) - conf.register_group(_utils.get_oslo_config().OptGroup(group)) - conf.register_opts(opts, group=group) - return opts - - def load_from_conf_options(self, conf, group, **kwargs): - """Create a session object from an oslo_config object. - - The options must have been previously registered with - register_conf_options. - - :param oslo_config.Cfg conf: config object to register with. - :param string group: The ini group to register options in. - :param dict kwargs: Additional parameters to pass to session - construction. - :returns: A new session object. - :rtype: :py:class:`.Session` - """ - c = conf[group] - - kwargs.setdefault('insecure', c.insecure) - kwargs.setdefault('cacert', c.cafile) - kwargs.setdefault('cert', c.certfile) - kwargs.setdefault('key', c.keyfile) - kwargs.setdefault('timeout', c.timeout) - - return self.load_from_options(**kwargs) - - -def register_argparse_arguments(*args, **kwargs): - return Session().register_argparse_arguments(*args, **kwargs) - - -def load_from_argparse_arguments(*args, **kwargs): - return Session().load_from_argparse_arguments(*args, **kwargs) - - -def register_conf_options(*args, **kwargs): - return Session().register_conf_options(*args, **kwargs) - - -def load_from_conf_options(*args, **kwargs): - return Session().load_from_conf_options(*args, **kwargs) - - -def get_conf_options(*args, **kwargs): - return Session().get_conf_options(*args, **kwargs) diff --git a/keystoneauth1/noauth.py b/keystoneauth1/noauth.py deleted file mode 100644 index 9503771..0000000 --- a/keystoneauth1/noauth.py +++ /dev/null @@ -1,24 +0,0 @@ -# Licensed under the Apache License, Version 2.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 keystoneauth1 import plugin - - -class NoAuth(plugin.BaseAuthPlugin): - """A provider that will always use no auth. - - This is useful to unify session/adapter loading for services - that might be deployed in standalone/noauth mode. - """ - - def get_token(self, session): - return 'notused' diff --git a/keystoneauth1/plugin.py b/keystoneauth1/plugin.py deleted file mode 100644 index 3551a0d..0000000 --- a/keystoneauth1/plugin.py +++ /dev/null @@ -1,252 +0,0 @@ -# Licensed under the Apache License, Version 2.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. - -# NOTE(jamielennox): The AUTH_INTERFACE is a special value that can be -# requested from get_endpoint. If a plugin receives this as the value of -# 'interface' it should return the initial URL that was passed to the plugin. -AUTH_INTERFACE = object() - -IDENTITY_AUTH_HEADER_NAME = 'X-Auth-Token' - - -class BaseAuthPlugin(object): - """The basic structure of an authentication plugin. - - .. note:: - See :doc:`/authentication-plugins` for a description of plugins - provided by this library. - - """ - - def get_token(self, session, **kwargs): - """Obtain a token. - - How the token is obtained is up to the plugin. If it is still valid - it may be re-used, retrieved from cache or invoke an authentication - request against a server. - - There are no required kwargs. They are passed directly to the auth - plugin and they are implementation specific. - - Returning None will indicate that no token was able to be retrieved. - - This function is misplaced as it should only be required for auth - plugins that use the 'X-Auth-Token' header. However due to the way - plugins evolved this method is required and often called to trigger an - authentication request on a new plugin. - - When implementing a new plugin it is advised that you implement this - method, however if you don't require the 'X-Auth-Token' header override - the `get_headers` method instead. - - :param session: A session object so the plugin can make HTTP calls. - :type session: keystoneauth1.session.Session - - :return: A token to use. - :rtype: string - """ - return None - - def get_headers(self, session, **kwargs): - """Fetch authentication headers for message. - - This is a more generalized replacement of the older get_token to allow - plugins to specify different or additional authentication headers to - the OpenStack standard 'X-Auth-Token' header. - - How the authentication headers are obtained is up to the plugin. If the - headers are still valid they may be re-used, retrieved from cache or - the plugin may invoke an authentication request against a server. - - The default implementation of get_headers calls the `get_token` method - to enable older style plugins to continue functioning unchanged. - Subclasses should feel free to completely override this function to - provide the headers that they want. - - There are no required kwargs. They are passed directly to the auth - plugin and they are implementation specific. - - Returning None will indicate that no token was able to be retrieved and - that authorization was a failure. Adding no authentication data can be - achieved by returning an empty dictionary. - - :param session: The session object that the auth_plugin belongs to. - :type session: keystoneauth1.session.Session - - :returns: Headers that are set to authenticate a message or None for - failure. Note that when checking this value that the empty - dict is a valid, non-failure response. - :rtype: dict - """ - token = self.get_token(session) - - if not token: - return None - - return {IDENTITY_AUTH_HEADER_NAME: token} - - def get_endpoint(self, session, **kwargs): - """Return an endpoint for the client. - - There are no required keyword arguments to ``get_endpoint`` as a plugin - implementation should use best effort with the information available to - determine the endpoint. However there are certain standard options that - will be generated by the clients and should be used by plugins: - - - ``service_type``: what sort of service is required. - - ``service_name``: the name of the service in the catalog. - - ``interface``: what visibility the endpoint should have. - - ``region_name``: the region the endpoint exists in. - - :param session: The session object that the auth_plugin belongs to. - :type session: keystoneauth1.session.Session - - :returns: The base URL that will be used to talk to the required - service or None if not available. - :rtype: string - """ - return None - - def get_connection_params(self, session, **kwargs): - """Return any additional connection parameters required for the plugin. - - :param session: The session object that the auth_plugin belongs to. - :type session: keystoneclient.session.Session - - :returns: Headers that are set to authenticate a message or None for - failure. Note that when checking this value that the empty - dict is a valid, non-failure response. - :rtype: dict - """ - return {} - - def invalidate(self): - """Invalidate the current authentication data. - - This should result in fetching a new token on next call. - - A plugin may be invalidated if an Unauthorized HTTP response is - returned to indicate that the token may have been revoked or is - otherwise now invalid. - - :returns: True if there was something that the plugin did to - invalidate. This means that it makes sense to try again. If - nothing happens returns False to indicate give up. - :rtype: bool - """ - return False - - def get_user_id(self, session, **kwargs): - """Return a unique user identifier of the plugin. - - Wherever possible the user id should be inferred from the token however - there are certain URLs and other places that require access to the - currently authenticated user id. - - :param session: A session object so the plugin can make HTTP calls. - :type session: keystoneauth1.session.Session - - :returns: A user identifier or None if one is not available. - :rtype: str - """ - return None - - def get_project_id(self, session, **kwargs): - """Return the project id that we are authenticated to. - - Wherever possible the project id should be inferred from the token - however there are certain URLs and other places that require access to - the currently authenticated project id. - - :param session: A session object so the plugin can make HTTP calls. - :type session: keystoneauth1.session.Session - - :returns: A project identifier or None if one is not available. - :rtype: str - """ - return None - - def get_sp_auth_url(self, session, sp_id, **kwargs): - """Return auth_url from the Service Provider object. - - This url is used for obtaining unscoped federated token from remote - cloud. - - :param sp_id: ID of the Service Provider to be queried. - :type sp_id: string - - :returns: A Service Provider auth_url or None if one is not available. - :rtype: str - - """ - return None - - def get_sp_url(self, session, sp_id, **kwargs): - """Return sp_url from the Service Provider object. - - This url is used for passing SAML2 assertion to the remote cloud. - - :param sp_id: ID of the Service Provider to be queried. - :type sp_id: str - - :returns: A Service Provider sp_url or None if one is not available. - :rtype: str - - """ - return None - - def get_cache_id(self): - """Fetch an identifier that uniquely identifies the auth options. - - The returned identifier need not be decomposable or otherwise provide - anyway to recreate the plugin. It should not contain sensitive data in - plaintext. - - This string MUST change if any of the parameters that are used to - uniquely identity this plugin change. - - If get_cache_id returns a str value suggesting that caching is - supported then get_auth_cache and set_auth_cache must also be - implemented. - - :returns: A unique string for the set of options - :rtype: str or None if this is unsupported or unavailable. - """ - return None - - def get_auth_state(self): - """Retrieve the current authentication state for the plugin. - - Retrieve any internal state that represents the authenticated plugin. - - This should not fetch any new data if it is not present. - - :raises NotImplementedError: if the plugin does not support this - feature. - - :returns: raw python data (which can be JSON serialized) that can be - moved into another plugin (of the same type) to have the - same authenticated state. - :rtype: object or None if unauthenticated. - """ - raise NotImplementedError() - - def set_auth_state(self, data): - """Install existing authentication state for a plugin. - - Take the output of get_auth_state and install that authentication state - into the current authentication plugin. - - :raises NotImplementedError: if the plugin does not support this - feature. - """ - raise NotImplementedError() diff --git a/keystoneauth1/service_token.py b/keystoneauth1/service_token.py deleted file mode 100644 index d06402b..0000000 --- a/keystoneauth1/service_token.py +++ /dev/null @@ -1,73 +0,0 @@ -# Licensed under the Apache License, Version 2.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 keystoneauth1 import plugin - -SERVICE_AUTH_HEADER_NAME = 'X-Service-Token' - -__all__ = ('ServiceTokenAuthWrapper',) - - -class ServiceTokenAuthWrapper(plugin.BaseAuthPlugin): - - def __init__(self, user_auth, service_auth): - self.user_auth = user_auth - self.service_auth = service_auth - - def get_headers(self, session, **kwargs): - headers = self.user_auth.get_headers(session, **kwargs) - - token = self.service_auth.get_token(session, **kwargs) - headers[SERVICE_AUTH_HEADER_NAME] = token - - return headers - - def invalidate(self): - # NOTE(jamielennox): hmm, what to do here? Should we invalidate both - # the service and user auth? Only one? There's no way to know what the - # failure was to selectively invalidate. - user = self.user_auth.invalidate() - service = self.service_auth.invalidate() - return user or service - - def get_connection_params(self, *args, **kwargs): - # NOTE(jamielennox): This is also a bit of a guess but unlikely to be a - # problem in practice. We don't know how merging connection parameters - # between these plugins will conflict - but there aren't many plugins - # that set this anyway. - # Take the service auth params first so that user auth params will be - # given priority. - params = self.service_auth.get_connection_params(*args, **kwargs) - params.update(self.user_auth.get_connection_params(*args, **kwargs)) - return params - - # TODO(jamielennox): Everything below here is a generic wrapper that could - # be extracted into a base wrapper class. We can do this as soon as there - # is a need for it, but we may never actually need it. - - def get_token(self, *args, **kwargs): - return self.user_auth.get_token(*args, **kwargs) - - def get_endpoint(self, *args, **kwargs): - return self.user_auth.get_endpoint(*args, **kwargs) - - def get_user_id(self, *args, **kwargs): - return self.user_auth.get_user_id(*args, **kwargs) - - def get_project_id(self, *args, **kwargs): - return self.user_auth.get_project_id(*args, **kwargs) - - def get_sp_auth_url(self, *args, **kwargs): - return self.user_auth.get_sp_auth_url(*args, **kwargs) - - def get_sp_url(self, *args, **kwargs): - return self.user_auth.get_sp_url(*args, **kwargs) diff --git a/keystoneauth1/session.py b/keystoneauth1/session.py deleted file mode 100644 index 16cc84c..0000000 --- a/keystoneauth1/session.py +++ /dev/null @@ -1,1114 +0,0 @@ -# Licensed under the Apache License, Version 2.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 functools -import hashlib -import json -import logging -import os -import platform -import socket -import sys -import time -import uuid - -from positional import positional -import requests -import six -from six.moves import urllib - -import keystoneauth1 -from keystoneauth1 import _utils as utils -from keystoneauth1 import discover -from keystoneauth1 import exceptions - -try: - import netaddr -except ImportError: - netaddr = None - -try: - import osprofiler.web as osprofiler_web -except ImportError: - osprofiler_web = None - -DEFAULT_USER_AGENT = 'keystoneauth1/%s %s %s/%s' % ( - keystoneauth1.__version__, requests.utils.default_user_agent(), - platform.python_implementation(), platform.python_version()) - -# NOTE(jamielennox): Clients will likely want to print more than json. Please -# propose a patch if you have a content type you think is reasonable to print -# here and we'll add it to the list as required. -_LOG_CONTENT_TYPES = set(['application/json']) - -_logger = utils.get_logger(__name__) - - -def _construct_session(session_obj=None): - # NOTE(morganfainberg): if the logic in this function changes be sure to - # update the betamax fixture's '_construct_session_with_betamax" function - # as well. - if not session_obj: - session_obj = requests.Session() - # Use TCPKeepAliveAdapter to fix bug 1323862 - for scheme in list(session_obj.adapters): - session_obj.mount(scheme, TCPKeepAliveAdapter()) - return session_obj - - -def _mv_legacy_headers_for_service(mv_service_type): - """Workaround for services that predate standardization. - - TODO(sdague): eventually convert this to using os-service-types - and put the logic there. However, right now this is so little - logic, inlining it for release is a better call. - - """ - headers = [] - if mv_service_type == "compute": - headers.append("X-OpenStack-Nova-API-Version") - elif mv_service_type == "baremetal": - headers.append("X-OpenStack-Ironic-API-Version") - return headers - - -class _JSONEncoder(json.JSONEncoder): - - def default(self, o): - if isinstance(o, datetime.datetime): - return o.isoformat() - if isinstance(o, uuid.UUID): - return six.text_type(o) - if netaddr and isinstance(o, netaddr.IPAddress): - return six.text_type(o) - - return super(_JSONEncoder, self).default(o) - - -class _StringFormatter(object): - """A String formatter that fetches values on demand.""" - - def __init__(self, session, auth): - self.session = session - self.auth = auth - - def __getitem__(self, item): - if item == 'project_id': - value = self.session.get_project_id(self.auth) - elif item == 'user_id': - value = self.session.get_user_id(self.auth) - else: - raise AttributeError(item) - - if not value: - raise ValueError("This type of authentication does not provide a " - "%s that can be substituted" % item) - - return value - - -def _determine_calling_package(): - """Walk the call frames trying to identify what is using this module.""" - # Create a lookup table mapping file name to module name. The ``inspect`` - # module does this but is far less efficient. Same story with the - # frame walking below. One could use ``inspect.stack()`` but it - # has far more overhead. - mod_lookup = dict((m.__file__, n) for n, m in sys.modules.items() - if hasattr(m, '__file__')) - - # NOTE(shaleh): these are not useful because they hide the real - # user of the code. debtcollector did not import keystoneauth but - # it will show up in the call stack. Similarly we do not want to - # report ourselves or keystone client as the user agent. The real - # user is the code importing them. - ignored = ('debtcollector', 'keystoneauth1', 'keystoneclient') - - i = 0 - while True: - i += 1 - - try: - # NOTE(shaleh): this is safe in CPython but could break in - # other implementations of Python. Yes, the `inspect` - # module could be used instead. But it does a lot more - # work so it has worse performance. - f = sys._getframe(i) - try: - name = mod_lookup[f.f_code.co_filename] - # finds the full name module.foo.bar but all we need - # is the module name. - name, _, _ = name.partition('.') - if name not in ignored: - return name - except KeyError: - pass # builtin or the like - except ValueError: - # hit the bottom of the frame stack - break - - return '' - - -def _determine_user_agent(): - """Attempt to programatically generate a user agent string. - - First, look at the name of the process. Return this unless it is in - the `ignored` list. Otherwise, look at the function call stack and - try to find the name of the code that invoked this module. - """ - # NOTE(shaleh): mod_wsgi is not any more useful than just - # reporting "keystoneauth". Ignore it and perform the package name - # heuristic. - ignored = ('mod_wsgi', ) - - try: - name = sys.argv[0] - except IndexError: - # sys.argv is empty, usually the Python interpreter prevents this. - return '' - - if not name: - return '' - - name = os.path.basename(name) - if name in ignored: - name = _determine_calling_package() - return name - - -class Session(object): - """Maintains client communication state and common functionality. - - As much as possible the parameters to this class reflect and are passed - directly to the :mod:`requests` library. - - :param auth: An authentication plugin to authenticate the session with. - (optional, defaults to None) - :type auth: keystoneauth1.plugin.BaseAuthPlugin - :param requests.Session session: A requests session object that can be used - for issuing requests. (optional) - :param str original_ip: The original IP of the requesting user which will - be sent to identity service in a 'Forwarded' - header. (optional) - :param verify: The verification arguments to pass to requests. These are of - the same form as requests expects, so True or False to - verify (or not) against system certificates or a path to a - bundle or CA certs to check against or None for requests to - attempt to locate and use certificates. (optional, defaults - to True) - :param cert: A client certificate to pass to requests. These are of the - same form as requests expects. Either a single filename - containing both the certificate and key or a tuple containing - the path to the certificate then a path to the key. (optional) - :param float timeout: A timeout to pass to requests. This should be a - numerical value indicating some amount (or fraction) - of seconds or 0 for no timeout. (optional, defaults - to 0) - :param str user_agent: A User-Agent header string to use for the request. - If not provided, a default of - :attr:`~keystoneauth1.session.DEFAULT_USER_AGENT` is - used, which contains the keystoneauth1 version as - well as those of the requests library and which - Python is being used. When a non-None value is - passed, it will be prepended to the default. - :param int/bool redirect: Controls the maximum number of redirections that - can be followed by a request. Either an integer - for a specific count or True/False for - forever/never. (optional, default to 30) - :param dict additional_headers: Additional headers that should be attached - to every request passing through the - session. Headers of the same name specified - per request will take priority. - :param str app_name: The name of the application that is creating the - session. This will be used to create the user_agent. - :param str app_version: The version of the application creating the - session. This will be used to create the - user_agent. - :param list additional_user_agent: A list of tuple of name, version that - will be added to the user agent. This - can be used by libraries that are part - of the communication process. - :param dict discovery_cache: A dict to be used for caching of discovery - information. This is normally managed - transparently, but if the user wants to - share a single cache across multiple sessions - that do not share an auth plugin, it can - be provided here. (optional, defaults to - None which means automatically manage) - """ - - user_agent = None - - _REDIRECT_STATUSES = (301, 302, 303, 305, 307, 308) - - _DEFAULT_REDIRECT_LIMIT = 30 - - @positional(2) - def __init__(self, auth=None, session=None, original_ip=None, verify=True, - cert=None, timeout=None, user_agent=None, - redirect=_DEFAULT_REDIRECT_LIMIT, additional_headers=None, - app_name=None, app_version=None, additional_user_agent=None, - discovery_cache=None): - - self.auth = auth - self.session = _construct_session(session) - self.original_ip = original_ip - self.verify = verify - self.cert = cert - self.timeout = None - self.redirect = redirect - self.additional_headers = additional_headers or {} - self.app_name = app_name - self.app_version = app_version - self.additional_user_agent = additional_user_agent or [] - self._determined_user_agent = None - if discovery_cache is None: - discovery_cache = {} - self._discovery_cache = discovery_cache - - if timeout is not None: - self.timeout = float(timeout) - - if user_agent is not None: - self.user_agent = "%s %s" % (user_agent, DEFAULT_USER_AGENT) - - self._json = _JSONEncoder() - - @property - def adapters(self): - return self.session.adapters - - @adapters.setter - def adapters(self, value): - self.session.adapters = value - - def mount(self, scheme, adapter): - self.session.mount(scheme, adapter) - - def _remove_service_catalog(self, body): - try: - data = json.loads(body) - - # V3 token - if 'token' in data and 'catalog' in data['token']: - data['token']['catalog'] = '' - return self._json.encode(data) - - # V2 token - if 'serviceCatalog' in data['access']: - data['access']['serviceCatalog'] = '' - return self._json.encode(data) - - except Exception: - # Don't fail trying to clean up the request body. - pass - return body - - @staticmethod - def _process_header(header): - """Redact the secure headers to be logged.""" - secure_headers = ('authorization', 'x-auth-token', - 'x-subject-token', 'x-service-token') - if header[0].lower() in secure_headers: - token_hasher = hashlib.sha1() - token_hasher.update(header[1].encode('utf-8')) - token_hash = token_hasher.hexdigest() - return (header[0], '{SHA1}%s' % token_hash) - return header - - @positional() - def _http_log_request(self, url, method=None, data=None, - json=None, headers=None, query_params=None, - logger=_logger): - if not logger.isEnabledFor(logging.DEBUG): - # NOTE(morganfainberg): This whole debug section is expensive, - # there is no need to do the work if we're not going to emit a - # debug log. - return - - string_parts = ['REQ: curl -g -i'] - - # NOTE(jamielennox): None means let requests do its default validation - # so we need to actually check that this is False. - if self.verify is False: - string_parts.append('--insecure') - elif isinstance(self.verify, six.string_types): - string_parts.append('--cacert "%s"' % self.verify) - - if method: - string_parts.extend(['-X', method]) - - if query_params: - # Don't check against `is not None` as this can be - # an empty dictionary, which we shouldn't bother logging. - url = url + '?' + urllib.parse.urlencode(query_params) - # URLs with query strings need to be wrapped in quotes in order - # for the CURL command to run properly. - string_parts.append('"%s"' % url) - else: - string_parts.append(url) - - if headers: - for header in headers.items(): - string_parts.append('-H "%s: %s"' - % self._process_header(header)) - if json: - data = self._json.encode(json) - if data: - if isinstance(data, six.binary_type): - try: - data = data.decode("ascii") - except UnicodeDecodeError: - data = "" - string_parts.append("-d '%s'" % data) - - logger.debug(' '.join(string_parts)) - - @positional() - def _http_log_response(self, response=None, json=None, - status_code=None, headers=None, text=None, - logger=_logger): - if not logger.isEnabledFor(logging.DEBUG): - return - - if response is not None: - if not status_code: - status_code = response.status_code - if not headers: - headers = response.headers - if not text: - # NOTE(samueldmq): If the response does not provide enough info - # about the content type to decide whether it is useful and - # safe to log it or not, just do not log the body. Trying to - # read the response body anyways may result on reading a long - # stream of bytes and getting an unexpected MemoryError. See - # bug 1616105 for further details. - content_type = response.headers.get('content-type', None) - - # NOTE(lamt): Per [1], the Content-Type header can be of the - # form Content-Type := type "/" subtype *[";" parameter] - # [1] https://www.w3.org/Protocols/rfc1341/4_Content-Type.html - for log_type in _LOG_CONTENT_TYPES: - if content_type is not None and content_type.startswith( - log_type): - text = self._remove_service_catalog(response.text) - break - else: - text = ('Omitted, Content-Type is set to %s. Only ' - '%s responses have their bodies logged.') - text = text % (content_type, ', '.join(_LOG_CONTENT_TYPES)) - if json: - text = self._json.encode(json) - - string_parts = ['RESP:'] - - if status_code: - string_parts.append('[%s]' % status_code) - if headers: - for header in headers.items(): - string_parts.append('%s: %s' % self._process_header(header)) - if text: - string_parts.append('\nRESP BODY: %s\n' % text) - - logger.debug(' '.join(string_parts)) - - @staticmethod - def _set_microversion_headers( - headers, microversion, service_type, endpoint_filter): - # We're converting it to normalized version number for two reasons. - # First, to validate it's a real version number. Second, so that in - # the future we can pre-validate that it is within the range of - # available microversions before we send the request. - # TODO(mordred) Validate when we get the response back that - # the server executed in the microversion we expected. - # TODO(mordred) Validate that the requested microversion works - # with the microversion range we found in discovery. - microversion = discover.normalize_version_number(microversion) - # Can't specify a M.latest microversion - if (microversion[0] != discover.LATEST and - discover.LATEST in microversion[1:]): - raise TypeError( - "Specifying a '{major}.latest' microversion is not allowed.") - microversion = discover.version_to_string(microversion) - if not service_type: - if endpoint_filter and 'service_type' in endpoint_filter: - service_type = endpoint_filter['service_type'] - else: - raise TypeError( - "microversion {microversion} was requested but no" - " service_type information is available. Either provide a" - " service_type in endpoint_filter or pass" - " microversion_service_type as an argument.".format( - microversion=microversion)) - - # TODO(mordred) cinder uses volume in its microversion header. This - # logic should be handled in the future by os-service-types but for - # now hard-code for cinder. - if (service_type.startswith('volume') - or service_type == 'block-storage'): - service_type = 'volume' - headers.setdefault('OpenStack-API-Version', - '{service_type} {microversion}'.format( - service_type=service_type, - microversion=microversion)) - header_names = _mv_legacy_headers_for_service(service_type) - for h in header_names: - headers.setdefault(h, microversion) - - @positional() - def request(self, url, method, json=None, original_ip=None, - user_agent=None, redirect=None, authenticated=None, - endpoint_filter=None, auth=None, requests_auth=None, - raise_exc=True, allow_reauth=True, log=True, - endpoint_override=None, connect_retries=0, logger=_logger, - allow={}, client_name=None, client_version=None, - microversion=None, microversion_service_type=None, - **kwargs): - """Send an HTTP request with the specified characteristics. - - Wrapper around `requests.Session.request` to handle tasks such as - setting headers, JSON encoding/decoding, and error handling. - - Arguments that are not handled are passed through to the requests - library. - - :param str url: Path or fully qualified URL of HTTP request. If only a - path is provided then endpoint_filter must also be - provided such that the base URL can be determined. If a - fully qualified URL is provided then endpoint_filter - will be ignored. - :param str method: The http method to use. (e.g. 'GET', 'POST') - :param str original_ip: Mark this request as forwarded for this ip. - (optional) - :param dict headers: Headers to be included in the request. (optional) - :param json: Some data to be represented as JSON. (optional) - :param str user_agent: A user_agent to use for the request. If present - will override one present in headers. (optional) - :param int/bool redirect: the maximum number of redirections that - can be followed by a request. Either an - integer for a specific count or True/False - for forever/never. (optional) - :param int connect_retries: the maximum number of retries that should - be attempted for connection errors. - (optional, defaults to 0 - never retry). - :param bool authenticated: True if a token should be attached to this - request, False if not or None for attach if - an auth_plugin is available. - (optional, defaults to None) - :param dict endpoint_filter: Data to be provided to an auth plugin with - which it should be able to determine an - endpoint to use for this request. If not - provided then URL is expected to be a - fully qualified URL. (optional) - :param str endpoint_override: The URL to use instead of looking up the - endpoint in the auth plugin. This will be - ignored if a fully qualified URL is - provided but take priority over an - endpoint_filter. This string may contain - the values ``%(project_id)s`` and - ``%(user_id)s`` to have those values - replaced by the project_id/user_id of the - current authentication. (optional) - :param auth: The auth plugin to use when authenticating this request. - This will override the plugin that is attached to the - session (if any). (optional) - :type auth: keystoneauth1.plugin.BaseAuthPlugin - :param requests_auth: A requests library auth plugin that cannot be - passed via kwarg because the `auth` kwarg - collides with our own auth plugins. (optional) - :type requests_auth: :py:class:`requests.auth.AuthBase` - :param bool raise_exc: If True then raise an appropriate exception for - failed HTTP requests. If False then return the - request object. (optional, default True) - :param bool allow_reauth: Allow fetching a new token and retrying the - request on receiving a 401 Unauthorized - response. (optional, default True) - :param bool log: If True then log the request and response data to the - debug log. (optional, default True) - :param logger: The logger object to use to log request and responses. - If not provided the keystoneauth1.session default - logger will be used. - :type logger: logging.Logger - :param dict allow: Extra filters to pass when discovering API - versions. (optional) - :param microversion: Microversion to send for this request. - microversion can be given as a string or a tuple. - (optional) - :param str microversion_service_type: The service_type to be sent in - the microversion header, if a microversion is given. - Defaults to the value of service_type from - endpoint_filter if one exists. If endpoint_filter is not - provided or does not have a service_type, microversion - is given and microversion_service_type is not provided, - an exception will be raised. - :param kwargs: any other parameter that can be passed to - :meth:`requests.Session.request` (such as `headers`). - Except: - - - `data` will be overwritten by the data in the `json` - param. - - `allow_redirects` is ignored as redirects are handled - by the session. - - :raises keystoneauth1.exceptions.base.ClientException: For connection - failure, or to indicate an error response code. - - :returns: The response to the request. - """ - headers = kwargs.setdefault('headers', dict()) - if microversion: - self._set_microversion_headers( - headers, microversion, microversion_service_type, - endpoint_filter) - - if authenticated is None: - authenticated = bool(auth or self.auth) - - if authenticated: - auth_headers = self.get_auth_headers(auth) - - if auth_headers is None: - msg = 'No valid authentication is available' - raise exceptions.AuthorizationFailure(msg) - - headers.update(auth_headers) - - if osprofiler_web: - headers.update(osprofiler_web.get_trace_id_headers()) - - # if we are passed a fully qualified URL and an endpoint_filter we - # should ignore the filter. This will make it easier for clients who - # want to overrule the default endpoint_filter data added to all client - # requests. We check fully qualified here by the presence of a host. - if not urllib.parse.urlparse(url).netloc: - base_url = None - - if endpoint_override: - base_url = endpoint_override % _StringFormatter(self, auth) - elif endpoint_filter: - base_url = self.get_endpoint(auth, allow=allow, - **endpoint_filter) - - if not base_url: - raise exceptions.EndpointNotFound() - - url = '%s/%s' % (base_url.rstrip('/'), url.lstrip('/')) - - if self.cert: - kwargs.setdefault('cert', self.cert) - - if self.timeout is not None: - kwargs.setdefault('timeout', self.timeout) - - if user_agent: - headers['User-Agent'] = user_agent - elif self.user_agent: - user_agent = headers.setdefault('User-Agent', self.user_agent) - else: - # Per RFC 7231 Section 5.5.3, identifiers in a user-agent should be - # ordered by decreasing significance. If a user sets their product - # that value will be used. Otherwise we attempt to derive a useful - # product value. The value will be prepended it to the KSA version, - # requests version, and then the Python version. - - agent = [] - - if self.app_name and self.app_version: - agent.append('%s/%s' % (self.app_name, self.app_version)) - elif self.app_name: - agent.append(self.app_name) - - for additional in self.additional_user_agent: - agent.append('%s/%s' % additional) - - if client_name and client_version: - agent.append('%s/%s' % (client_name, client_version)) - elif client_name: - agent.append(client_name) - - if not agent: - # NOTE(jamielennox): determine_user_agent will return an empty - # string on failure so checking for None will ensure it is only - # called once even on failure. - if self._determined_user_agent is None: - self._determined_user_agent = _determine_user_agent() - - if self._determined_user_agent: - agent.append(self._determined_user_agent) - - agent.append(DEFAULT_USER_AGENT) - user_agent = headers.setdefault('User-Agent', ' '.join(agent)) - - if self.original_ip: - headers.setdefault('Forwarded', - 'for=%s;by=%s' % (self.original_ip, user_agent)) - - if json is not None: - headers.setdefault('Content-Type', 'application/json') - kwargs['data'] = self._json.encode(json) - - for k, v in self.additional_headers.items(): - headers.setdefault(k, v) - - kwargs.setdefault('verify', self.verify) - - if requests_auth: - kwargs['auth'] = requests_auth - - # Query parameters that are included in the url string will - # be logged properly, but those sent in the `params` parameter - # (which the requests library handles) need to be explicitly - # picked out so they can be included in the URL that gets loggged. - query_params = kwargs.get('params', dict()) - - if log: - self._http_log_request(url, method=method, - data=kwargs.get('data'), - headers=headers, - query_params=query_params, - logger=logger) - - # Force disable requests redirect handling. We will manage this below. - kwargs['allow_redirects'] = False - - if redirect is None: - redirect = self.redirect - - send = functools.partial(self._send_request, - url, method, redirect, log, logger, - connect_retries) - - try: - connection_params = self.get_auth_connection_params(auth=auth) - except exceptions.MissingAuthPlugin: - # NOTE(jamielennox): If we've gotten this far without an auth - # plugin then we should be happy with allowing no additional - # connection params. This will be the typical case for plugins - # anyway. - pass - else: - if connection_params: - kwargs.update(connection_params) - - resp = send(**kwargs) - - # log callee and caller request-id for each api call - if log: - # service_name should be fetched from endpoint_filter if it is not - # present then use service_type as service_name. - service_name = None - if endpoint_filter: - service_name = endpoint_filter.get('service_name') - if not service_name: - service_name = endpoint_filter.get('service_type') - - # Nova uses 'x-compute-request-id' and other services like - # Glance, Cinder etc are using 'x-openstack-request-id' to store - # request-id in the header - request_id = (resp.headers.get('x-openstack-request-id') or - resp.headers.get('x-compute-request-id')) - if request_id: - logger.debug('%(method)s call to %(service_name)s for ' - '%(url)s used request id ' - '%(response_request_id)s', - {'method': resp.request.method, - 'service_name': service_name, - 'url': resp.url, - 'response_request_id': request_id}) - - # handle getting a 401 Unauthorized response by invalidating the plugin - # and then retrying the request. This is only tried once. - if resp.status_code == 401 and authenticated and allow_reauth: - if self.invalidate(auth): - auth_headers = self.get_auth_headers(auth) - - if auth_headers is not None: - headers.update(auth_headers) - resp = send(**kwargs) - - if raise_exc and resp.status_code >= 400: - logger.debug('Request returned failure status: %s', - resp.status_code) - raise exceptions.from_response(resp, method, url) - - return resp - - def _send_request(self, url, method, redirect, log, logger, - connect_retries, connect_retry_delay=0.5, **kwargs): - # NOTE(jamielennox): We handle redirection manually because the - # requests lib follows some browser patterns where it will redirect - # POSTs as GETs for certain statuses which is not want we want for an - # API. See: https://en.wikipedia.org/wiki/Post/Redirect/Get - - # NOTE(jamielennox): The interaction between retries and redirects are - # handled naively. We will attempt only a maximum number of retries and - # redirects rather than per request limits. Otherwise the extreme case - # could be redirects * retries requests. This will be sufficient in - # most cases and can be fixed properly if there's ever a need. - - try: - try: - resp = self.session.request(method, url, **kwargs) - except requests.exceptions.SSLError as e: - msg = 'SSL exception connecting to %(url)s: %(error)s' % { - 'url': url, 'error': e} - raise exceptions.SSLError(msg) - except requests.exceptions.Timeout: - msg = 'Request to %s timed out' % url - raise exceptions.ConnectTimeout(msg) - except requests.exceptions.ConnectionError as e: - # NOTE(sdague): urllib3/requests connection error is a - # translation of SocketError. However, SocketError - # happens for many different reasons, and that low - # level message is often really important in figuring - # out the difference between network misconfigurations - # and firewall blocking. - msg = 'Unable to establish connection to %s: %s' % (url, e) - raise exceptions.ConnectFailure(msg) - except requests.exceptions.RequestException as e: - msg = 'Unexpected exception for %(url)s: %(error)s' % { - 'url': url, 'error': e} - raise exceptions.UnknownConnectionError(msg, e) - - except exceptions.RetriableConnectionFailure as e: - if connect_retries <= 0: - raise - - logger.info('Failure: %(e)s. Retrying in %(delay).1fs.', - {'e': e, 'delay': connect_retry_delay}) - time.sleep(connect_retry_delay) - - return self._send_request( - url, method, redirect, log, logger, - connect_retries=connect_retries - 1, - connect_retry_delay=connect_retry_delay * 2, - **kwargs) - - if log: - self._http_log_response(response=resp, logger=logger) - - if resp.status_code in self._REDIRECT_STATUSES: - # be careful here in python True == 1 and False == 0 - if isinstance(redirect, bool): - redirect_allowed = redirect - else: - redirect -= 1 - redirect_allowed = redirect >= 0 - - if not redirect_allowed: - return resp - - try: - location = resp.headers['location'] - except KeyError: - logger.warning("Failed to redirect request to %s as new " - "location was not provided.", resp.url) - else: - # NOTE(jamielennox): We don't pass through connect_retry_delay. - # This request actually worked so we can reset the delay count. - new_resp = self._send_request( - location, method, redirect, log, logger, - connect_retries=connect_retries, - **kwargs) - - if not isinstance(new_resp.history, list): - new_resp.history = list(new_resp.history) - new_resp.history.insert(0, resp) - resp = new_resp - - return resp - - def head(self, url, **kwargs): - """Perform a HEAD request. - - This calls :py:meth:`.request()` with ``method`` set to ``HEAD``. - - """ - return self.request(url, 'HEAD', **kwargs) - - def get(self, url, **kwargs): - """Perform a GET request. - - This calls :py:meth:`.request()` with ``method`` set to ``GET``. - - """ - return self.request(url, 'GET', **kwargs) - - def post(self, url, **kwargs): - """Perform a POST request. - - This calls :py:meth:`.request()` with ``method`` set to ``POST``. - - """ - return self.request(url, 'POST', **kwargs) - - def put(self, url, **kwargs): - """Perform a PUT request. - - This calls :py:meth:`.request()` with ``method`` set to ``PUT``. - - """ - return self.request(url, 'PUT', **kwargs) - - def delete(self, url, **kwargs): - """Perform a DELETE request. - - This calls :py:meth:`.request()` with ``method`` set to ``DELETE``. - - """ - return self.request(url, 'DELETE', **kwargs) - - def patch(self, url, **kwargs): - """Perform a PATCH request. - - This calls :py:meth:`.request()` with ``method`` set to ``PATCH``. - - """ - return self.request(url, 'PATCH', **kwargs) - - def _auth_required(self, auth, msg): - if not auth: - auth = self.auth - - if not auth: - msg_fmt = 'An auth plugin is required to %s' - raise exceptions.MissingAuthPlugin(msg_fmt % msg) - - return auth - - def get_auth_headers(self, auth=None, **kwargs): - """Return auth headers as provided by the auth plugin. - - :param auth: The auth plugin to use for token. Overrides the plugin - on the session. (optional) - :type auth: keystoneauth1.plugin.BaseAuthPlugin - - :raises keystoneauth1.exceptions.auth.AuthorizationFailure: - if a new token fetch fails. - :raises keystoneauth1.exceptions.auth_plugins.MissingAuthPlugin: - if a plugin is not available. - - :returns: Authentication headers or None for failure. - :rtype: :class:`dict` - """ - auth = self._auth_required(auth, 'fetch a token') - return auth.get_headers(self, **kwargs) - - def get_token(self, auth=None): - """Return a token as provided by the auth plugin. - - :param auth: The auth plugin to use for token. Overrides the plugin - on the session. (optional) - :type auth: keystoneauth1.plugin.BaseAuthPlugin - - :raises keystoneauth1.exceptions.auth.AuthorizationFailure: - if a new token fetch fails. - :raises keystoneauth1.exceptions.auth_plugins.MissingAuthPlugin: - if a plugin is not available. - - .. warning:: - **DEPRECATED**: This assumes that the only header that is used to - authenticate a message is ``X-Auth-Token``. This may not be - correct. Use :meth:`get_auth_headers` instead. - - :returns: A valid token. - :rtype: string - """ - return (self.get_auth_headers(auth) or {}).get('X-Auth-Token') - - def get_endpoint(self, auth=None, **kwargs): - """Get an endpoint as provided by the auth plugin. - - :param auth: The auth plugin to use for token. Overrides the plugin on - the session. (optional) - :type auth: keystoneauth1.plugin.BaseAuthPlugin - - :raises keystoneauth1.exceptions.auth_plugins.MissingAuthPlugin: - if a plugin is not available. - - :returns: An endpoint if available or None. - :rtype: string - """ - if 'endpoint_override' in kwargs: - return kwargs['endpoint_override'] - - auth = self._auth_required(auth, 'determine endpoint URL') - - return auth.get_endpoint(self, **kwargs) - - def get_endpoint_data(self, auth=None, **kwargs): - """Get endpoint data as provided by the auth plugin. - - :param auth: The auth plugin to use for token. Overrides the plugin on - the session. (optional) - :type auth: keystoneauth1.plugin.BaseAuthPlugin - - :raises keystoneauth1.exceptions.auth_plugins.MissingAuthPlugin: - if a plugin is not available. - :raises TypeError: If arguments are invalid - - :returns: Endpoint data if available or None. - :rtype: keystoneauth1.discover.EndpointData - """ - auth = self._auth_required(auth, 'determine endpoint URL') - return auth.get_endpoint_data(self, **kwargs) - - def get_auth_connection_params(self, auth=None, **kwargs): - """Return auth connection params as provided by the auth plugin. - - An auth plugin may specify connection parameters to the request like - providing a client certificate for communication. - - We restrict the values that may be returned from this function to - prevent an auth plugin overriding values unrelated to connection - parmeters. The values that are currently accepted are: - - - `cert`: a path to a client certificate, or tuple of client - certificate and key pair that are used with this request. - - `verify`: a boolean value to indicate verifying SSL certificates - against the system CAs or a path to a CA file to verify with. - - These values are passed to the requests library and further information - on accepted values may be found there. - - :param auth: The auth plugin to use for tokens. Overrides the plugin - on the session. (optional) - :type auth: keystoneauth1.plugin.BaseAuthPlugin - - :raises keystoneauth1.exceptions.auth.AuthorizationFailure: - if a new token fetch fails. - :raises keystoneauth1.exceptions.auth_plugins.MissingAuthPlugin: - if a plugin is not available. - :raises keystoneauth1.exceptions.auth_plugins.UnsupportedParameters: - if the plugin returns a parameter that is not supported by this - session. - - :returns: Authentication headers or None for failure. - :rtype: :class:`dict` - """ - auth = self._auth_required(auth, 'fetch connection params') - params = auth.get_connection_params(self, **kwargs) - - # NOTE(jamielennox): There needs to be some consensus on what - # parameters are allowed to be modified by the auth plugin here. - # Ideally I think it would be only the send() parts of the request - # flow. For now lets just allow certain elements. - params_copy = params.copy() - - for arg in ('cert', 'verify'): - try: - kwargs[arg] = params_copy.pop(arg) - except KeyError: - pass - - if params_copy: - raise exceptions.UnsupportedParameters(list(params_copy.keys())) - - return params - - def invalidate(self, auth=None): - """Invalidate an authentication plugin. - - :param auth: The auth plugin to invalidate. Overrides the plugin on the - session. (optional) - :type auth: keystoneauth1.plugin.BaseAuthPlugin - - """ - auth = self._auth_required(auth, 'validate') - return auth.invalidate() - - def get_user_id(self, auth=None): - """Return the authenticated user_id as provided by the auth plugin. - - :param auth: The auth plugin to use for token. Overrides the plugin - on the session. (optional) - :type auth: keystoneauth1.plugin.BaseAuthPlugin - - :raises keystoneauth1.exceptions.auth.AuthorizationFailure: - if a new token fetch fails. - :raises keystoneauth1.exceptions.auth_plugins.MissingAuthPlugin: - if a plugin is not available. - - :returns: Current user_id or None if not supported by plugin. - :rtype: :class:`str` - """ - auth = self._auth_required(auth, 'get user_id') - return auth.get_user_id(self) - - def get_project_id(self, auth=None): - """Return the authenticated project_id as provided by the auth plugin. - - :param auth: The auth plugin to use for token. Overrides the plugin - on the session. (optional) - :type auth: keystoneauth1.plugin.BaseAuthPlugin - - :raises keystoneauth1.exceptions.auth.AuthorizationFailure: - if a new token fetch fails. - :raises keystoneauth1.exceptions.auth_plugins.MissingAuthPlugin: - if a plugin is not available. - - :returns: Current project_id or None if not supported by plugin. - :rtype: :class:`str` - """ - auth = self._auth_required(auth, 'get project_id') - return auth.get_project_id(self) - - -REQUESTS_VERSION = tuple(int(v) for v in requests.__version__.split('.')) - - -class TCPKeepAliveAdapter(requests.adapters.HTTPAdapter): - """The custom adapter used to set TCP Keep-Alive on all connections. - - This Adapter also preserves the default behaviour of Requests which - disables Nagle's Algorithm. See also: - https://blogs.msdn.com/b/windowsazurestorage/archive/2010/06/25/nagle-s-algorithm-is-not-friendly-towards-small-requests.aspx - """ - - def init_poolmanager(self, *args, **kwargs): - if 'socket_options' not in kwargs and REQUESTS_VERSION >= (2, 4, 1): - socket_options = [ - # Keep Nagle's algorithm off - (socket.IPPROTO_TCP, socket.TCP_NODELAY, 1), - # Turn on TCP Keep-Alive - (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1), - ] - - # Some operating systems (e.g., OSX) do not support setting - # keepidle - if hasattr(socket, 'TCP_KEEPIDLE'): - socket_options += [ - # Wait 60 seconds before sending keep-alive probes - (socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 60) - ] - - # Windows subsystem for Linux does not support this feature - if (hasattr(socket, 'TCP_KEEPCNT') and - not utils.is_windows_linux_subsystem): - socket_options += [ - # Set the maximum number of keep-alive probes - (socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 4), - ] - - if hasattr(socket, 'TCP_KEEPINTVL'): - socket_options += [ - # Send keep-alive probes every 15 seconds - (socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 15), - ] - - # After waiting 60 seconds, and then sending a probe once every 15 - # seconds 4 times, these options should ensure that a connection - # hands for no longer than 2 minutes before a ConnectionError is - # raised. - kwargs['socket_options'] = socket_options - super(TCPKeepAliveAdapter, self).init_poolmanager(*args, **kwargs) diff --git a/keystoneauth1/tests/__init__.py b/keystoneauth1/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/keystoneauth1/tests/unit/__init__.py b/keystoneauth1/tests/unit/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/keystoneauth1/tests/unit/access/__init__.py b/keystoneauth1/tests/unit/access/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/keystoneauth1/tests/unit/access/test_v2_access.py b/keystoneauth1/tests/unit/access/test_v2_access.py deleted file mode 100644 index aabef67..0000000 --- a/keystoneauth1/tests/unit/access/test_v2_access.py +++ /dev/null @@ -1,218 +0,0 @@ -# Licensed under the Apache License, Version 2.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 uuid - -from oslo_utils import timeutils - -from keystoneauth1 import access -from keystoneauth1 import fixture -from keystoneauth1.tests.unit import utils - - -class AccessV2Test(utils.TestCase): - - def test_building_unscoped_accessinfo(self): - token = fixture.V2Token(expires='2012-10-03T16:58:01Z') - - auth_ref = access.create(body=token) - - self.assertIsInstance(auth_ref, access.AccessInfoV2) - self.assertFalse(auth_ref.has_service_catalog()) - - self.assertEqual(auth_ref.auth_token, token.token_id) - self.assertEqual(auth_ref.username, token.user_name) - self.assertEqual(auth_ref.user_id, token.user_id) - - self.assertEqual(auth_ref.role_ids, []) - self.assertEqual(auth_ref.role_names, []) - - self.assertIsNone(auth_ref.tenant_name) - self.assertIsNone(auth_ref.tenant_id) - - self.assertFalse(auth_ref.domain_scoped) - self.assertFalse(auth_ref.project_scoped) - self.assertFalse(auth_ref.trust_scoped) - - self.assertIsNone(auth_ref.project_domain_id) - self.assertIsNone(auth_ref.project_domain_name) - self.assertIsNone(auth_ref.user_domain_id) - self.assertIsNone(auth_ref.user_domain_name) - - self.assertEqual(auth_ref.expires, token.expires) - self.assertEqual(auth_ref.issued, token.issued) - - self.assertEqual(token.audit_id, auth_ref.audit_id) - self.assertIsNone(auth_ref.audit_chain_id) - self.assertIsNone(token.audit_chain_id) - self.assertIsNone(auth_ref.bind) - - def test_will_expire_soon(self): - token = fixture.V2Token() - expires = timeutils.utcnow() + datetime.timedelta(minutes=5) - token.expires = expires - auth_ref = access.create(body=token) - self.assertIsInstance(auth_ref, access.AccessInfoV2) - self.assertFalse(auth_ref.will_expire_soon(stale_duration=120)) - self.assertTrue(auth_ref.will_expire_soon(stale_duration=300)) - self.assertFalse(auth_ref.will_expire_soon()) - - def test_building_scoped_accessinfo(self): - token = fixture.V2Token() - token.set_scope() - s = token.add_service('identity') - s.add_endpoint('http://url') - - role_data = token.add_role() - - auth_ref = access.create(body=token) - - self.assertIsInstance(auth_ref, access.AccessInfoV2) - self.assertTrue(auth_ref.has_service_catalog()) - - self.assertEqual(auth_ref.auth_token, token.token_id) - self.assertEqual(auth_ref.username, token.user_name) - self.assertEqual(auth_ref.user_id, token.user_id) - - self.assertEqual(auth_ref.role_ids, [role_data['id']]) - self.assertEqual(auth_ref.role_names, [role_data['name']]) - - self.assertEqual(auth_ref.tenant_name, token.tenant_name) - self.assertEqual(auth_ref.tenant_id, token.tenant_id) - - self.assertEqual(auth_ref.tenant_name, auth_ref.project_name) - self.assertEqual(auth_ref.tenant_id, auth_ref.project_id) - - self.assertIsNone(auth_ref.project_domain_id, 'default') - self.assertIsNone(auth_ref.project_domain_name, 'Default') - self.assertIsNone(auth_ref.user_domain_id, 'default') - self.assertIsNone(auth_ref.user_domain_name, 'Default') - - self.assertTrue(auth_ref.project_scoped) - self.assertFalse(auth_ref.domain_scoped) - - self.assertEqual(token.audit_id, auth_ref.audit_id) - self.assertEqual(token.audit_chain_id, auth_ref.audit_chain_id) - - def test_diablo_token(self): - diablo_token = { - 'access': { - 'token': { - 'id': uuid.uuid4().hex, - 'expires': '2020-01-01T00:00:10.000123Z', - 'tenantId': 'tenant_id1', - }, - 'user': { - 'id': 'user_id1', - 'name': 'user_name1', - 'roles': [ - {'name': 'role1'}, - {'name': 'role2'}, - ], - }, - }, - } - - auth_ref = access.create(body=diablo_token) - self.assertIsInstance(auth_ref, access.AccessInfoV2) - - self.assertTrue(auth_ref) - self.assertEqual(auth_ref.username, 'user_name1') - self.assertEqual(auth_ref.project_id, 'tenant_id1') - self.assertEqual(auth_ref.project_name, 'tenant_id1') - self.assertIsNone(auth_ref.project_domain_id) - self.assertIsNone(auth_ref.project_domain_name) - self.assertIsNone(auth_ref.user_domain_id) - self.assertIsNone(auth_ref.user_domain_name) - self.assertEqual(auth_ref.role_names, ['role1', 'role2']) - - def test_grizzly_token(self): - grizzly_token = { - 'access': { - 'token': { - 'id': uuid.uuid4().hex, - 'expires': '2020-01-01T00:00:10.000123Z', - }, - 'user': { - 'id': 'user_id1', - 'name': 'user_name1', - 'tenantId': 'tenant_id1', - 'tenantName': 'tenant_name1', - 'roles': [ - {'name': 'role1'}, - {'name': 'role2'}, - ], - }, - }, - } - - auth_ref = access.create(body=grizzly_token) - self.assertIsInstance(auth_ref, access.AccessInfoV2) - - self.assertEqual(auth_ref.project_id, 'tenant_id1') - self.assertEqual(auth_ref.project_name, 'tenant_name1') - self.assertIsNone(auth_ref.project_domain_id) - self.assertIsNone(auth_ref.project_domain_name) - self.assertIsNone(auth_ref.user_domain_id, 'default') - self.assertIsNone(auth_ref.user_domain_name, 'Default') - self.assertEqual(auth_ref.role_names, ['role1', 'role2']) - - def test_v2_roles(self): - role_id = 'a' - role_name = 'b' - - token = fixture.V2Token() - token.set_scope() - token.add_role(id=role_id, name=role_name) - - auth_ref = access.create(body=token) - self.assertIsInstance(auth_ref, access.AccessInfoV2) - - self.assertEqual([role_id], auth_ref.role_ids) - self.assertEqual([role_id], - auth_ref._data['access']['metadata']['roles']) - self.assertEqual([role_name], auth_ref.role_names) - self.assertEqual([{'name': role_name}], - auth_ref._data['access']['user']['roles']) - - def test_trusts(self): - user_id = uuid.uuid4().hex - trust_id = uuid.uuid4().hex - - token = fixture.V2Token(user_id=user_id, trust_id=trust_id) - token.set_scope() - token.add_role() - - auth_ref = access.create(body=token) - self.assertIsInstance(auth_ref, access.AccessInfoV2) - - self.assertEqual(trust_id, auth_ref.trust_id) - self.assertEqual(user_id, auth_ref.trustee_user_id) - - self.assertEqual(trust_id, token['access']['trust']['id']) - - def test_binding(self): - token = fixture.V2Token() - principal = uuid.uuid4().hex - token.set_bind('kerberos', principal) - - auth_ref = access.create(body=token) - self.assertIsInstance(auth_ref, access.AccessInfoV2) - - self.assertEqual({'kerberos': principal}, auth_ref.bind) - - def test_is_admin_project(self): - token = fixture.V2Token() - auth_ref = access.create(body=token) - self.assertIsInstance(auth_ref, access.AccessInfoV2) - self.assertIs(True, auth_ref.is_admin_project) diff --git a/keystoneauth1/tests/unit/access/test_v2_service_catalog.py b/keystoneauth1/tests/unit/access/test_v2_service_catalog.py deleted file mode 100644 index f62764a..0000000 --- a/keystoneauth1/tests/unit/access/test_v2_service_catalog.py +++ /dev/null @@ -1,269 +0,0 @@ -# Licensed under the Apache License, Version 2.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 uuid - -from keystoneauth1 import access -from keystoneauth1 import exceptions -from keystoneauth1 import fixture -from keystoneauth1.tests.unit import utils - - -class ServiceCatalogTest(utils.TestCase): - def setUp(self): - super(ServiceCatalogTest, self).setUp() - - self.AUTH_RESPONSE_BODY = fixture.V2Token( - token_id='ab48a9efdfedb23ty3494', - expires='2010-11-01T03:32:15-05:00', - tenant_id='345', - tenant_name='My Project', - user_id='123', - user_name='jqsmith', - audit_chain_id=uuid.uuid4().hex) - - self.AUTH_RESPONSE_BODY.add_role(id='234', name='compute:admin') - role = self.AUTH_RESPONSE_BODY.add_role(id='235', - name='object-store:admin') - role['tenantId'] = '1' - - s = self.AUTH_RESPONSE_BODY.add_service('compute', 'Cloud Servers') - endpoint = s.add_endpoint( - public='https://compute.north.host/v1/1234', - internal='https://compute.north.host/v1/1234', - region='North') - endpoint['tenantId'] = '1' - endpoint['versionId'] = '1.0' - endpoint['versionInfo'] = 'https://compute.north.host/v1.0/' - endpoint['versionList'] = 'https://compute.north.host/' - - endpoint = s.add_endpoint( - public='https://compute.north.host/v1.1/3456', - internal='https://compute.north.host/v1.1/3456', - region='North') - endpoint['tenantId'] = '2' - endpoint['versionId'] = '1.1' - endpoint['versionInfo'] = 'https://compute.north.host/v1.1/' - endpoint['versionList'] = 'https://compute.north.host/' - - s = self.AUTH_RESPONSE_BODY.add_service('object-store', 'Cloud Files') - endpoint = s.add_endpoint(public='https://swift.north.host/v1/blah', - internal='https://swift.north.host/v1/blah', - region='South') - endpoint['tenantId'] = '11' - endpoint['versionId'] = '1.0' - endpoint['versionInfo'] = 'uri' - endpoint['versionList'] = 'uri' - - endpoint = s.add_endpoint( - public='https://swift.north.host/v1.1/blah', - internal='https://compute.north.host/v1.1/blah', - region='South') - endpoint['tenantId'] = '2' - endpoint['versionId'] = '1.1' - endpoint['versionInfo'] = 'https://swift.north.host/v1.1/' - endpoint['versionList'] = 'https://swift.north.host/' - - s = self.AUTH_RESPONSE_BODY.add_service('image', 'Image Servers') - s.add_endpoint(public='https://image.north.host/v1/', - internal='https://image-internal.north.host/v1/', - region='North') - s.add_endpoint(public='https://image.south.host/v1/', - internal='https://image-internal.south.host/v1/', - region='South') - - def test_building_a_service_catalog(self): - auth_ref = access.create(body=self.AUTH_RESPONSE_BODY) - sc = auth_ref.service_catalog - - self.assertEqual(sc.url_for(service_type='compute'), - "https://compute.north.host/v1/1234") - self.assertRaises(exceptions.EndpointNotFound, - sc.url_for, - region_name="South", - service_type='compute') - - def test_service_catalog_endpoints(self): - auth_ref = access.create(body=self.AUTH_RESPONSE_BODY) - sc = auth_ref.service_catalog - public_ep = sc.get_endpoints(service_type='compute', - interface='publicURL') - self.assertEqual(public_ep['compute'][1]['tenantId'], '2') - self.assertEqual(public_ep['compute'][1]['versionId'], '1.1') - self.assertEqual(public_ep['compute'][1]['internalURL'], - "https://compute.north.host/v1.1/3456") - - def test_service_catalog_empty(self): - self.AUTH_RESPONSE_BODY['access']['serviceCatalog'] = [] - auth_ref = access.create(body=self.AUTH_RESPONSE_BODY) - self.assertRaises(exceptions.EmptyCatalog, - auth_ref.service_catalog.url_for, - service_type='image', - interface='internalURL') - - def test_service_catalog_get_endpoints_region_names(self): - auth_ref = access.create(body=self.AUTH_RESPONSE_BODY) - sc = auth_ref.service_catalog - - endpoints = sc.get_endpoints(service_type='image', region_name='North') - self.assertEqual(len(endpoints), 1) - self.assertEqual(endpoints['image'][0]['publicURL'], - 'https://image.north.host/v1/') - - endpoints = sc.get_endpoints(service_type='image', region_name='South') - self.assertEqual(len(endpoints), 1) - self.assertEqual(endpoints['image'][0]['publicURL'], - 'https://image.south.host/v1/') - - endpoints = sc.get_endpoints(service_type='compute') - self.assertEqual(len(endpoints['compute']), 2) - - endpoints = sc.get_endpoints(service_type='compute', - region_name='North') - self.assertEqual(len(endpoints['compute']), 2) - - endpoints = sc.get_endpoints(service_type='compute', - region_name='West') - self.assertEqual(len(endpoints['compute']), 0) - - def test_service_catalog_url_for_region_names(self): - auth_ref = access.create(body=self.AUTH_RESPONSE_BODY) - sc = auth_ref.service_catalog - - url = sc.url_for(service_type='image', region_name='North') - self.assertEqual(url, 'https://image.north.host/v1/') - - url = sc.url_for(service_type='image', region_name='South') - self.assertEqual(url, 'https://image.south.host/v1/') - - self.assertRaises(exceptions.EndpointNotFound, sc.url_for, - service_type='image', region_name='West') - - def test_servcie_catalog_get_url_region_names(self): - auth_ref = access.create(body=self.AUTH_RESPONSE_BODY) - sc = auth_ref.service_catalog - - urls = sc.get_urls(service_type='image') - self.assertEqual(len(urls), 2) - - urls = sc.get_urls(service_type='image', region_name='North') - self.assertEqual(len(urls), 1) - self.assertEqual(urls[0], 'https://image.north.host/v1/') - - urls = sc.get_urls(service_type='image', region_name='South') - self.assertEqual(len(urls), 1) - self.assertEqual(urls[0], 'https://image.south.host/v1/') - - urls = sc.get_urls(service_type='image', region_name='West') - self.assertEqual(len(urls), 0) - - def test_service_catalog_service_name(self): - auth_ref = access.create(body=self.AUTH_RESPONSE_BODY) - sc = auth_ref.service_catalog - - url = sc.url_for(service_name='Image Servers', interface='public', - service_type='image', region_name='North') - self.assertEqual('https://image.north.host/v1/', url) - - self.assertRaises(exceptions.EndpointNotFound, sc.url_for, - service_name='Image Servers', service_type='compute') - - urls = sc.get_urls(service_type='image', service_name='Image Servers', - interface='public') - - self.assertIn('https://image.north.host/v1/', urls) - self.assertIn('https://image.south.host/v1/', urls) - - urls = sc.get_urls(service_type='image', service_name='Servers', - interface='public') - - self.assertEqual(0, len(urls)) - - def test_service_catalog_multiple_service_types(self): - token = fixture.V2Token() - token.set_scope() - - for i in range(3): - s = token.add_service('compute') - s.add_endpoint(public='public-%d' % i, - admin='admin-%d' % i, - internal='internal-%d' % i, - region='region-%d' % i) - - auth_ref = access.create(body=token) - - urls = auth_ref.service_catalog.get_urls(service_type='compute', - interface='publicURL') - - self.assertEqual(set(['public-0', 'public-1', 'public-2']), set(urls)) - - urls = auth_ref.service_catalog.get_urls(service_type='compute', - interface='publicURL', - region_name='region-1') - - self.assertEqual(('public-1', ), urls) - - def test_service_catalog_endpoint_id(self): - token = fixture.V2Token() - token.set_scope() - endpoint_id = uuid.uuid4().hex - public_url = uuid.uuid4().hex - - s = token.add_service('compute') - s.add_endpoint(public=public_url, id=endpoint_id) - s.add_endpoint(public=uuid.uuid4().hex) - - auth_ref = access.create(body=token) - - # initially assert that we get back all our urls for a simple filter - urls = auth_ref.service_catalog.get_urls(interface='public') - self.assertEqual(2, len(urls)) - - urls = auth_ref.service_catalog.get_urls(endpoint_id=endpoint_id, - interface='public') - - self.assertEqual((public_url, ), urls) - - # with bad endpoint_id nothing should be found - urls = auth_ref.service_catalog.get_urls(endpoint_id=uuid.uuid4().hex, - interface='public') - - self.assertEqual(0, len(urls)) - - # we ignore a service_id because v2 doesn't know what it is - urls = auth_ref.service_catalog.get_urls(endpoint_id=endpoint_id, - service_id=uuid.uuid4().hex, - interface='public') - - self.assertEqual((public_url, ), urls) - - def test_service_catalog_without_service_type(self): - token = fixture.V2Token() - token.set_scope() - - public_urls = [] - - for i in range(0, 3): - public_url = uuid.uuid4().hex - public_urls.append(public_url) - - s = token.add_service(uuid.uuid4().hex) - s.add_endpoint(public=public_url) - - auth_ref = access.create(body=token) - urls = auth_ref.service_catalog.get_urls(service_type=None, - interface='public') - - self.assertEqual(3, len(urls)) - - for p in public_urls: - self.assertIn(p, urls) diff --git a/keystoneauth1/tests/unit/access/test_v3_access.py b/keystoneauth1/tests/unit/access/test_v3_access.py deleted file mode 100644 index 6dfca99..0000000 --- a/keystoneauth1/tests/unit/access/test_v3_access.py +++ /dev/null @@ -1,264 +0,0 @@ -# Licensed under the Apache License, Version 2.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 uuid - -import datetime -from oslo_utils import timeutils - -from keystoneauth1 import access -from keystoneauth1 import fixture -from keystoneauth1.tests.unit import utils - - -class AccessV3Test(utils.TestCase): - - def test_building_unscoped_accessinfo(self): - token = fixture.V3Token() - token_id = uuid.uuid4().hex - - auth_ref = access.create(body=token, auth_token=token_id) - - self.assertIn('methods', auth_ref._data['token']) - self.assertFalse(auth_ref.has_service_catalog()) - self.assertNotIn('catalog', auth_ref._data['token']) - - self.assertEqual(token_id, auth_ref.auth_token) - self.assertEqual(token.user_name, auth_ref.username) - self.assertEqual(token.user_id, auth_ref.user_id) - - self.assertEqual(auth_ref.role_ids, []) - self.assertEqual(auth_ref.role_names, []) - - self.assertIsNone(auth_ref.project_name) - self.assertIsNone(auth_ref.project_id) - - self.assertFalse(auth_ref.domain_scoped) - self.assertFalse(auth_ref.project_scoped) - self.assertIsNone(auth_ref.project_is_domain) - - self.assertEqual(token.user_domain_id, auth_ref.user_domain_id) - self.assertEqual(token.user_domain_name, auth_ref.user_domain_name) - - self.assertIsNone(auth_ref.project_domain_id) - self.assertIsNone(auth_ref.project_domain_name) - - self.assertEqual(auth_ref.expires, timeutils.parse_isotime( - token['token']['expires_at'])) - self.assertEqual(auth_ref.issued, timeutils.parse_isotime( - token['token']['issued_at'])) - - self.assertEqual(auth_ref.expires, token.expires) - self.assertEqual(auth_ref.issued, token.issued) - - self.assertEqual(auth_ref.audit_id, token.audit_id) - self.assertIsNone(auth_ref.audit_chain_id) - self.assertIsNone(token.audit_chain_id) - self.assertIsNone(auth_ref.bind) - - def test_will_expire_soon(self): - expires = timeutils.utcnow() + datetime.timedelta(minutes=5) - token = fixture.V3Token(expires=expires) - auth_ref = access.create(body=token) - self.assertFalse(auth_ref.will_expire_soon(stale_duration=120)) - self.assertTrue(auth_ref.will_expire_soon(stale_duration=301)) - self.assertFalse(auth_ref.will_expire_soon()) - - def test_building_domain_scoped_accessinfo(self): - token = fixture.V3Token() - token.set_domain_scope() - - s = token.add_service(type='identity') - s.add_standard_endpoints(public='http://url') - - token_id = uuid.uuid4().hex - - auth_ref = access.create(body=token, auth_token=token_id) - - self.assertTrue(auth_ref) - self.assertIn('methods', auth_ref._data['token']) - self.assertIn('catalog', auth_ref._data['token']) - self.assertTrue(auth_ref.has_service_catalog()) - self.assertTrue(auth_ref._data['token']['catalog']) - - self.assertEqual(token_id, auth_ref.auth_token) - self.assertEqual(token.user_name, auth_ref.username) - self.assertEqual(token.user_id, auth_ref.user_id) - - self.assertEqual(token.role_ids, auth_ref.role_ids) - self.assertEqual(token.role_names, auth_ref.role_names) - - self.assertEqual(token.domain_name, auth_ref.domain_name) - self.assertEqual(token.domain_id, auth_ref.domain_id) - - self.assertIsNone(auth_ref.project_name) - self.assertIsNone(auth_ref.project_id) - - self.assertEqual(token.user_domain_id, auth_ref.user_domain_id) - self.assertEqual(token.user_domain_name, auth_ref.user_domain_name) - - self.assertIsNone(auth_ref.project_domain_id) - self.assertIsNone(auth_ref.project_domain_name) - - self.assertTrue(auth_ref.domain_scoped) - self.assertFalse(auth_ref.project_scoped) - self.assertIsNone(auth_ref.project_is_domain) - - self.assertEqual(token.audit_id, auth_ref.audit_id) - self.assertEqual(token.audit_chain_id, auth_ref.audit_chain_id) - - def test_building_project_scoped_accessinfo(self): - token = fixture.V3Token() - token.set_project_scope() - - s = token.add_service(type='identity') - s.add_standard_endpoints(public='http://url') - - token_id = uuid.uuid4().hex - - auth_ref = access.create(body=token, auth_token=token_id) - - self.assertIn('methods', auth_ref._data['token']) - self.assertIn('catalog', auth_ref._data['token']) - self.assertTrue(auth_ref.has_service_catalog()) - self.assertTrue(auth_ref._data['token']['catalog']) - - self.assertEqual(token_id, auth_ref.auth_token) - self.assertEqual(token.user_name, auth_ref.username) - self.assertEqual(token.user_id, auth_ref.user_id) - - self.assertEqual(token.role_ids, auth_ref.role_ids) - self.assertEqual(token.role_names, auth_ref.role_names) - - self.assertIsNone(auth_ref.domain_name) - self.assertIsNone(auth_ref.domain_id) - - self.assertEqual(token.project_name, auth_ref.project_name) - self.assertEqual(token.project_id, auth_ref.project_id) - - self.assertEqual(auth_ref.tenant_name, auth_ref.project_name) - self.assertEqual(auth_ref.tenant_id, auth_ref.project_id) - - self.assertEqual(token.project_domain_id, auth_ref.project_domain_id) - self.assertEqual(token.project_domain_name, - auth_ref.project_domain_name) - - self.assertEqual(token.user_domain_id, auth_ref.user_domain_id) - self.assertEqual(token.user_domain_name, auth_ref.user_domain_name) - - self.assertFalse(auth_ref.domain_scoped) - self.assertTrue(auth_ref.project_scoped) - self.assertIsNone(auth_ref.project_is_domain) - - self.assertEqual(token.audit_id, auth_ref.audit_id) - self.assertEqual(token.audit_chain_id, auth_ref.audit_chain_id) - - def test_building_project_as_domain_scoped_accessinfo(self): - token = fixture.V3Token() - token.set_project_scope(is_domain=True) - - service = token.add_service(type='identity') - service.add_standard_endpoints(public='http://url') - - token_id = uuid.uuid4().hex - - auth_ref = access.create(body=token, auth_token=token_id) - - self.assertIn('methods', auth_ref._data['token']) - self.assertIn('catalog', auth_ref._data['token']) - self.assertTrue(auth_ref.has_service_catalog()) - self.assertTrue(auth_ref._data['token']['catalog']) - - self.assertEqual(token_id, auth_ref.auth_token) - self.assertEqual(token.user_name, auth_ref.username) - self.assertEqual(token.user_id, auth_ref.user_id) - - self.assertEqual(token.role_ids, auth_ref.role_ids) - self.assertEqual(token.role_names, auth_ref.role_names) - - self.assertIsNone(auth_ref.domain_name) - self.assertIsNone(auth_ref.domain_id) - - self.assertEqual(token.project_name, auth_ref.project_name) - self.assertEqual(token.project_id, auth_ref.project_id) - - self.assertEqual(auth_ref.tenant_name, auth_ref.project_name) - self.assertEqual(auth_ref.tenant_id, auth_ref.project_id) - - self.assertEqual(token.project_domain_id, auth_ref.project_domain_id) - self.assertEqual(token.project_domain_name, - auth_ref.project_domain_name) - - self.assertEqual(token.user_domain_id, auth_ref.user_domain_id) - self.assertEqual(token.user_domain_name, auth_ref.user_domain_name) - - self.assertFalse(auth_ref.domain_scoped) - self.assertTrue(auth_ref.project_scoped) - self.assertTrue(auth_ref.project_is_domain) - - self.assertEqual(token.audit_id, auth_ref.audit_id) - self.assertEqual(token.audit_chain_id, auth_ref.audit_chain_id) - - def test_oauth_access(self): - consumer_id = uuid.uuid4().hex - access_token_id = uuid.uuid4().hex - - token = fixture.V3Token() - token.set_project_scope() - token.set_oauth(access_token_id=access_token_id, - consumer_id=consumer_id) - - auth_ref = access.create(body=token) - - self.assertEqual(consumer_id, auth_ref.oauth_consumer_id) - self.assertEqual(access_token_id, auth_ref.oauth_access_token_id) - - self.assertEqual(consumer_id, - auth_ref._data['token']['OS-OAUTH1']['consumer_id']) - self.assertEqual( - access_token_id, - auth_ref._data['token']['OS-OAUTH1']['access_token_id']) - - def test_federated_property_standard_token(self): - """Check if is_federated property returns expected value.""" - token = fixture.V3Token() - token.set_project_scope() - auth_ref = access.create(body=token) - self.assertFalse(auth_ref.is_federated) - - def test_binding(self): - token = fixture.V3Token() - principal = uuid.uuid4().hex - token.set_bind('kerberos', principal) - - auth_ref = access.create(body=token) - self.assertIsInstance(auth_ref, access.AccessInfoV3) - - self.assertEqual({'kerberos': principal}, auth_ref.bind) - - def test_is_admin_project_unset(self): - token = fixture.V3Token() - auth_ref = access.create(body=token) - self.assertIsInstance(auth_ref, access.AccessInfoV3) - self.assertIs(True, auth_ref.is_admin_project) - - def test_is_admin_project_true(self): - token = fixture.V3Token(is_admin_project=True) - auth_ref = access.create(body=token) - self.assertIsInstance(auth_ref, access.AccessInfoV3) - self.assertIs(True, auth_ref.is_admin_project) - - def test_is_admin_project_false(self): - token = fixture.V3Token(is_admin_project=False) - auth_ref = access.create(body=token) - self.assertIsInstance(auth_ref, access.AccessInfoV3) - self.assertIs(False, auth_ref.is_admin_project) diff --git a/keystoneauth1/tests/unit/access/test_v3_service_catalog.py b/keystoneauth1/tests/unit/access/test_v3_service_catalog.py deleted file mode 100644 index 713586c..0000000 --- a/keystoneauth1/tests/unit/access/test_v3_service_catalog.py +++ /dev/null @@ -1,392 +0,0 @@ -# Licensed under the Apache License, Version 2.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 uuid - -from keystoneauth1 import access -from keystoneauth1 import exceptions -from keystoneauth1 import fixture -from keystoneauth1.tests.unit import utils - - -class ServiceCatalogTest(utils.TestCase): - def setUp(self): - super(ServiceCatalogTest, self).setUp() - - self.AUTH_RESPONSE_BODY = fixture.V3Token( - audit_chain_id=uuid.uuid4().hex) - self.AUTH_RESPONSE_BODY.set_project_scope() - - self.AUTH_RESPONSE_BODY.add_role(name='admin') - self.AUTH_RESPONSE_BODY.add_role(name='member') - - s = self.AUTH_RESPONSE_BODY.add_service('compute', name='nova') - s.add_standard_endpoints( - public='https://compute.north.host/novapi/public', - internal='https://compute.north.host/novapi/internal', - admin='https://compute.north.host/novapi/admin', - region='North') - - s = self.AUTH_RESPONSE_BODY.add_service('object-store', name='swift') - s.add_standard_endpoints( - public='http://swift.north.host/swiftapi/public', - internal='http://swift.north.host/swiftapi/internal', - admin='http://swift.north.host/swiftapi/admin', - region='South') - - s = self.AUTH_RESPONSE_BODY.add_service('image', name='glance') - s.add_standard_endpoints( - public='http://glance.north.host/glanceapi/public', - internal='http://glance.north.host/glanceapi/internal', - admin='http://glance.north.host/glanceapi/admin', - region='North') - - s.add_standard_endpoints( - public='http://glance.south.host/glanceapi/public', - internal='http://glance.south.host/glanceapi/internal', - admin='http://glance.south.host/glanceapi/admin', - region='South') - - self.north_endpoints = {'public': - 'http://glance.north.host/glanceapi/public', - 'internal': - 'http://glance.north.host/glanceapi/internal', - 'admin': - 'http://glance.north.host/glanceapi/admin'} - - self.south_endpoints = {'public': - 'http://glance.south.host/glanceapi/public', - 'internal': - 'http://glance.south.host/glanceapi/internal', - 'admin': - 'http://glance.south.host/glanceapi/admin'} - - def test_building_a_service_catalog(self): - auth_ref = access.create(auth_token=uuid.uuid4().hex, - body=self.AUTH_RESPONSE_BODY) - sc = auth_ref.service_catalog - - self.assertEqual(sc.url_for(service_type='compute'), - "https://compute.north.host/novapi/public") - self.assertEqual(sc.url_for(service_type='compute', - interface='internal'), - "https://compute.north.host/novapi/internal") - - self.assertRaises(exceptions.EndpointNotFound, - sc.url_for, - region_name='South', - service_type='compute') - - def test_service_catalog_endpoints(self): - auth_ref = access.create(auth_token=uuid.uuid4().hex, - body=self.AUTH_RESPONSE_BODY) - sc = auth_ref.service_catalog - - public_ep = sc.get_endpoints(service_type='compute', - interface='public') - self.assertEqual(public_ep['compute'][0]['region'], 'North') - self.assertEqual(public_ep['compute'][0]['url'], - "https://compute.north.host/novapi/public") - - def test_service_catalog_regions(self): - self.AUTH_RESPONSE_BODY['token']['region_name'] = "North" - auth_ref = access.create(auth_token=uuid.uuid4().hex, - body=self.AUTH_RESPONSE_BODY) - sc = auth_ref.service_catalog - - url = sc.url_for(service_type='image', interface='public') - self.assertEqual(url, "http://glance.north.host/glanceapi/public") - - self.AUTH_RESPONSE_BODY['token']['region_name'] = "South" - auth_ref = access.create(auth_token=uuid.uuid4().hex, - body=self.AUTH_RESPONSE_BODY) - sc = auth_ref.service_catalog - url = sc.url_for(service_type='image', - region_name="South", - interface='internal') - self.assertEqual(url, "http://glance.south.host/glanceapi/internal") - - def test_service_catalog_empty(self): - self.AUTH_RESPONSE_BODY['token']['catalog'] = [] - auth_ref = access.create(auth_token=uuid.uuid4().hex, - body=self.AUTH_RESPONSE_BODY) - self.assertRaises(exceptions.EmptyCatalog, - auth_ref.service_catalog.url_for, - service_type='image', - interface='internalURL') - - def test_service_catalog_get_endpoints_region_names(self): - sc = access.create(auth_token=uuid.uuid4().hex, - body=self.AUTH_RESPONSE_BODY).service_catalog - - endpoints = sc.get_endpoints(service_type='image', region_name='North') - self.assertEqual(len(endpoints), 1) - for endpoint in endpoints['image']: - self.assertEqual(endpoint['url'], - self.north_endpoints[endpoint['interface']]) - - endpoints = sc.get_endpoints(service_type='image', region_name='South') - self.assertEqual(len(endpoints), 1) - for endpoint in endpoints['image']: - self.assertEqual(endpoint['url'], - self.south_endpoints[endpoint['interface']]) - - endpoints = sc.get_endpoints(service_type='compute') - self.assertEqual(len(endpoints['compute']), 3) - - endpoints = sc.get_endpoints(service_type='compute', - region_name='North') - self.assertEqual(len(endpoints['compute']), 3) - - endpoints = sc.get_endpoints(service_type='compute', - region_name='West') - self.assertEqual(len(endpoints['compute']), 0) - - def test_service_catalog_url_for_region_names(self): - sc = access.create(auth_token=uuid.uuid4().hex, - body=self.AUTH_RESPONSE_BODY).service_catalog - - url = sc.url_for(service_type='image', region_name='North') - self.assertEqual(url, self.north_endpoints['public']) - - url = sc.url_for(service_type='image', region_name='South') - self.assertEqual(url, self.south_endpoints['public']) - - self.assertRaises(exceptions.EndpointNotFound, sc.url_for, - service_type='image', region_name='West') - - def test_service_catalog_get_url_region_names(self): - sc = access.create(auth_token=uuid.uuid4().hex, - body=self.AUTH_RESPONSE_BODY).service_catalog - - urls = sc.get_urls(service_type='image') - self.assertEqual(len(urls), 2) - - urls = sc.get_urls(service_type='image', region_name='North') - self.assertEqual(len(urls), 1) - self.assertEqual(urls[0], self.north_endpoints['public']) - - urls = sc.get_urls(service_type='image', region_name='South') - self.assertEqual(len(urls), 1) - self.assertEqual(urls[0], self.south_endpoints['public']) - - urls = sc.get_urls(service_type='image', region_name='West') - self.assertEqual(len(urls), 0) - - def test_service_catalog_service_name(self): - sc = access.create(auth_token=uuid.uuid4().hex, - body=self.AUTH_RESPONSE_BODY).service_catalog - - url = sc.url_for(service_name='glance', interface='public', - service_type='image', region_name='North') - self.assertEqual('http://glance.north.host/glanceapi/public', url) - - url = sc.url_for(service_name='glance', interface='public', - service_type='image', region_name='South') - self.assertEqual('http://glance.south.host/glanceapi/public', url) - - self.assertRaises(exceptions.EndpointNotFound, sc.url_for, - service_name='glance', service_type='compute') - - urls = sc.get_urls(service_type='image', service_name='glance', - interface='public') - - self.assertIn('http://glance.north.host/glanceapi/public', urls) - self.assertIn('http://glance.south.host/glanceapi/public', urls) - - urls = sc.get_urls(service_type='image', - service_name='Servers', - interface='public') - - self.assertEqual(0, len(urls)) - - def test_service_catalog_without_name(self): - f = fixture.V3Token(audit_chain_id=uuid.uuid4().hex) - - if not f.project_id: - f.set_project_scope() - - f.add_role(name='admin') - f.add_role(name='member') - - region = 'RegionOne' - tenant = '225da22d3ce34b15877ea70b2a575f58' - - s = f.add_service('volume') - s.add_standard_endpoints( - public='http://public.com:8776/v1/%s' % tenant, - internal='http://internal:8776/v1/%s' % tenant, - admin='http://admin:8776/v1/%s' % tenant, - region=region) - - s = f.add_service('image') - s.add_standard_endpoints(public='http://public.com:9292/v1', - internal='http://internal:9292/v1', - admin='http://admin:9292/v1', - region=region) - - s = f.add_service('compute') - s.add_standard_endpoints( - public='http://public.com:8774/v2/%s' % tenant, - internal='http://internal:8774/v2/%s' % tenant, - admin='http://admin:8774/v2/%s' % tenant, - region=region) - - s = f.add_service('ec2') - s.add_standard_endpoints( - public='http://public.com:8773/services/Cloud', - internal='http://internal:8773/services/Cloud', - admin='http://admin:8773/services/Admin', - region=region) - - s = f.add_service('identity') - s.add_standard_endpoints(public='http://public.com:5000/v3', - internal='http://internal:5000/v3', - admin='http://admin:35357/v3', - region=region) - - pr_auth_ref = access.create(body=f) - pr_sc = pr_auth_ref.service_catalog - - # this will work because there are no service names on that token - url_ref = 'http://public.com:8774/v2/225da22d3ce34b15877ea70b2a575f58' - url = pr_sc.url_for(service_type='compute', service_name='NotExist', - interface='public') - self.assertEqual(url_ref, url) - - ab_auth_ref = access.create(body=self.AUTH_RESPONSE_BODY) - ab_sc = ab_auth_ref.service_catalog - - # this won't work because there is a name and it's not this one - self.assertRaises(exceptions.EndpointNotFound, ab_sc.url_for, - service_type='compute', service_name='NotExist', - interface='public') - - -class ServiceCatalogV3Test(ServiceCatalogTest): - - def test_building_a_service_catalog(self): - sc = access.create(auth_token=uuid.uuid4().hex, - body=self.AUTH_RESPONSE_BODY).service_catalog - - self.assertEqual(sc.url_for(service_type='compute'), - 'https://compute.north.host/novapi/public') - self.assertEqual(sc.url_for(service_type='compute', - interface='internal'), - 'https://compute.north.host/novapi/internal') - - self.assertRaises(exceptions.EndpointNotFound, - sc.url_for, - region_name='South', - service_type='compute') - - def test_service_catalog_endpoints(self): - sc = access.create(auth_token=uuid.uuid4().hex, - body=self.AUTH_RESPONSE_BODY).service_catalog - - public_ep = sc.get_endpoints(service_type='compute', - interface='public') - self.assertEqual(public_ep['compute'][0]['region_id'], 'North') - self.assertEqual(public_ep['compute'][0]['url'], - 'https://compute.north.host/novapi/public') - - def test_service_catalog_multiple_service_types(self): - token = fixture.V3Token() - token.set_project_scope() - - for i in range(3): - s = token.add_service('compute') - s.add_standard_endpoints(public='public-%d' % i, - admin='admin-%d' % i, - internal='internal-%d' % i, - region='region-%d' % i) - - auth_ref = access.create(resp=None, body=token) - - urls = auth_ref.service_catalog.get_urls(service_type='compute', - interface='public') - - self.assertEqual(set(['public-0', 'public-1', 'public-2']), set(urls)) - - urls = auth_ref.service_catalog.get_urls(service_type='compute', - interface='public', - region_name='region-1') - - self.assertEqual(('public-1', ), urls) - - def test_service_catalog_endpoint_id(self): - token = fixture.V3Token() - token.set_project_scope() - - service_id = uuid.uuid4().hex - endpoint_id = uuid.uuid4().hex - public_url = uuid.uuid4().hex - - s = token.add_service('compute', id=service_id) - s.add_endpoint('public', public_url, id=endpoint_id) - s.add_endpoint('public', uuid.uuid4().hex) - - auth_ref = access.create(body=token) - - # initially assert that we get back all our urls for a simple filter - urls = auth_ref.service_catalog.get_urls(service_type='compute', - interface='public') - self.assertEqual(2, len(urls)) - - # with bad endpoint_id nothing should be found - urls = auth_ref.service_catalog.get_urls(service_type='compute', - endpoint_id=uuid.uuid4().hex, - interface='public') - - self.assertEqual(0, len(urls)) - - # with service_id we get back both public endpoints - urls = auth_ref.service_catalog.get_urls(service_type='compute', - service_id=service_id, - interface='public') - self.assertEqual(2, len(urls)) - - # with service_id and endpoint_id we get back the url we want - urls = auth_ref.service_catalog.get_urls(service_type='compute', - service_id=service_id, - endpoint_id=endpoint_id, - interface='public') - - self.assertEqual((public_url, ), urls) - - # with service_id and endpoint_id we get back the url we want - urls = auth_ref.service_catalog.get_urls(service_type='compute', - endpoint_id=endpoint_id, - interface='public') - - self.assertEqual((public_url, ), urls) - - def test_service_catalog_without_service_type(self): - token = fixture.V3Token() - token.set_project_scope() - - public_urls = [] - - for i in range(0, 3): - public_url = uuid.uuid4().hex - public_urls.append(public_url) - - s = token.add_service(uuid.uuid4().hex) - s.add_endpoint('public', public_url) - - auth_ref = access.create(body=token) - urls = auth_ref.service_catalog.get_urls(interface='public') - - self.assertEqual(3, len(urls)) - - for p in public_urls: - self.assertIn(p, urls) diff --git a/keystoneauth1/tests/unit/client_fixtures.py b/keystoneauth1/tests/unit/client_fixtures.py deleted file mode 100644 index e749ac2..0000000 --- a/keystoneauth1/tests/unit/client_fixtures.py +++ /dev/null @@ -1,122 +0,0 @@ -# Licensed under the Apache License, Version 2.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 keystoneauth1 import fixture as kfixture - - -def project_scoped_token(): - fixture = kfixture.V3Token( - user_id='c4da488862bd435c9e6c0275a0d0e49a', - user_name='exampleuser', - user_domain_id='4e6893b7ba0b4006840c3845660b86ed', - user_domain_name='exampledomain', - expires='2010-11-01T03:32:15-05:00', - project_id='225da22d3ce34b15877ea70b2a575f58', - project_name='exampleproject', - project_domain_id='4e6893b7ba0b4006840c3845660b86ed', - project_domain_name='exampledomain') - - fixture.add_role(id='76e72a', name='admin') - fixture.add_role(id='f4f392', name='member') - - region = 'RegionOne' - tenant = '225da22d3ce34b15877ea70b2a575f58' - - service = fixture.add_service('volume') - service.add_standard_endpoints( - public='http://public.com:8776/v1/%s' % tenant, - internal='http://internal:8776/v1/%s' % tenant, - admin='http://admin:8776/v1/%s' % tenant, - region=region) - - service = fixture.add_service('image') - service.add_standard_endpoints(public='http://public.com:9292/v1', - internal='http://internal:9292/v1', - admin='http://admin:9292/v1', - region=region) - - service = fixture.add_service('compute') - service.add_standard_endpoints( - public='http://public.com:8774/v2/%s' % tenant, - internal='http://internal:8774/v2/%s' % tenant, - admin='http://admin:8774/v2/%s' % tenant, - region=region) - - service = fixture.add_service('ec2') - service.add_standard_endpoints( - public='http://public.com:8773/services/Cloud', - internal='http://internal:8773/services/Cloud', - admin='http://admin:8773/services/Admin', - region=region) - - service = fixture.add_service('identity') - service.add_standard_endpoints(public='http://public.com:5000/v3', - internal='http://internal:5000/v3', - admin='http://admin:35357/v3', - region=region) - - return fixture - - -def domain_scoped_token(): - fixture = kfixture.V3Token( - user_id='c4da488862bd435c9e6c0275a0d0e49a', - user_name='exampleuser', - user_domain_id='4e6893b7ba0b4006840c3845660b86ed', - user_domain_name='exampledomain', - expires='2010-11-01T03:32:15-05:00', - domain_id='8e9283b7ba0b1038840c3842058b86ab', - domain_name='anotherdomain') - - fixture.add_role(id='76e72a', name='admin') - fixture.add_role(id='f4f392', name='member') - region = 'RegionOne' - - service = fixture.add_service('volume') - service.add_standard_endpoints(public='http://public.com:8776/v1/None', - internal='http://internal.com:8776/v1/None', - admin='http://admin.com:8776/v1/None', - region=region) - - service = fixture.add_service('image') - service.add_standard_endpoints(public='http://public.com:9292/v1', - internal='http://internal:9292/v1', - admin='http://admin:9292/v1', - region=region) - - service = fixture.add_service('compute') - service.add_standard_endpoints(public='http://public.com:8774/v1.1/None', - internal='http://internal:8774/v1.1/None', - admin='http://admin:8774/v1.1/None', - region=region) - - service = fixture.add_service('ec2') - service.add_standard_endpoints( - public='http://public.com:8773/services/Cloud', - internal='http://internal:8773/services/Cloud', - admin='http://admin:8773/services/Admin', - region=region) - - service = fixture.add_service('identity') - service.add_standard_endpoints(public='http://public.com:5000/v3', - internal='http://internal:5000/v3', - admin='http://admin:35357/v3', - region=region) - - return fixture - - -AUTH_SUBJECT_TOKEN = '3e2813b7ba0b4006840c3825860b86ed' - -AUTH_RESPONSE_HEADERS = { - 'X-Subject-Token': AUTH_SUBJECT_TOKEN, -} diff --git a/keystoneauth1/tests/unit/data/README b/keystoneauth1/tests/unit/data/README deleted file mode 100644 index e77f01d..0000000 --- a/keystoneauth1/tests/unit/data/README +++ /dev/null @@ -1,7 +0,0 @@ -This directory holds the betamax test cassettes that are pre-generated -for unit testing. This can be removed in the future with a functional -test that stands up a full devstack, records a cassette and then -replays it as part of the test suite. - -Until the functional testing is implemented do not remove this -directory or enclosed files. diff --git a/keystoneauth1/tests/unit/data/keystone_v2_sample_request.json b/keystoneauth1/tests/unit/data/keystone_v2_sample_request.json deleted file mode 100644 index e7b6cd7..0000000 --- a/keystoneauth1/tests/unit/data/keystone_v2_sample_request.json +++ /dev/null @@ -1 +0,0 @@ -{"auth":{"tenantName": "customer-x", "passwordCredentials": {"username": "joeuser", "password": "secrete"}}} diff --git a/keystoneauth1/tests/unit/data/keystone_v2_sample_response.json b/keystoneauth1/tests/unit/data/keystone_v2_sample_response.json deleted file mode 100644 index 341a93a..0000000 --- a/keystoneauth1/tests/unit/data/keystone_v2_sample_response.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "access":{ - "token":{ - "expires":"2012-02-05T00:00:00", - "id":"887665443383838", - "tenant":{ - "id":"1", - "name":"customer-x" - } - }, - "serviceCatalog":[ - { - "endpoints":[ - { - "adminURL":"http://swift.admin-nets.local:8080/", - "region":"RegionOne", - "internalURL":"http://127.0.0.1:8080/v1/AUTH_1", - "publicURL":"http://swift.publicinternets.com/v1/AUTH_1" - } - ], - "type":"object-store", - "name":"swift" - }, - { - "endpoints":[ - { - "adminURL":"http://cdn.admin-nets.local/v1.1/1", - "region":"RegionOne", - "internalURL":"http://127.0.0.1:7777/v1.1/1", - "publicURL":"http://cdn.publicinternets.com/v1.1/1" - } - ], - "type":"object-store", - "name":"cdn" - } - ], - "user":{ - "id":"1", - "roles":[ - { - "tenantId":"1", - "id":"3", - "name":"Member" - } - ], - "name":"joeuser" - } - } -} diff --git a/keystoneauth1/tests/unit/data/keystone_v3_sample_request.json b/keystoneauth1/tests/unit/data/keystone_v3_sample_request.json deleted file mode 100644 index 8acdaf5..0000000 --- a/keystoneauth1/tests/unit/data/keystone_v3_sample_request.json +++ /dev/null @@ -1,13 +0,0 @@ -{ "auth": { - "identity": { - "methods": ["password"], - "password": { - "user": { - "name": "admin", - "domain": { "id": "default" }, - "password": "adminpwd" - } - } - } - } -} diff --git a/keystoneauth1/tests/unit/data/keystone_v3_sample_response.json b/keystoneauth1/tests/unit/data/keystone_v3_sample_response.json deleted file mode 100644 index 2defa33..0000000 --- a/keystoneauth1/tests/unit/data/keystone_v3_sample_response.json +++ /dev/null @@ -1,15 +0,0 @@ -{"token": {"methods": ["password"], "roles": [{"id": -"9fe2ff9ee4384b1894a90878d3e92bab", "name": "_member_"}, {"id": -"c703057be878458588961ce9a0ce686b", "name": "admin"}], "expires_at": -"2014-06-10T2:55:16.806001Z", "project": {"domain": {"id": "default", "name": -"Default"}, "id": "8538a3f13f9541b28c2620eb19065e45", "name": "admin"}, -"catalog": [{"endpoints": [{"url": "http://localhost:3537/v2.0", "region": -"RegionOne", "interface": "admin", "id": "29beb2f1567642eb810b042b6719ea88"}, -{"url": "http://localhost:5000/v2.0", "region": "RegionOne", "interface": -"internal", "id": "8707e3735d4415c97ae231b4841eb1c"}, {"url": -"http://localhost:5000/v2.0", "region": "RegionOne", "interface": "public", -"id": "ef303187fc8d41668f25199c298396a5"}], "type": "identity", "id": -"bd73972c0e14fb69bae8ff76e112a90", "name": "keystone"}], "extras": {}, -"user": {"domain": {"id": "default", "name": "Default"}, "id": -"3ec3164f750146be97f21559ee4d9c51", "name": "admin"}, "audit_ids": -["yRt0UrxJSs6-WYJgwEMMmg"], "issued_at": "201406-10T20:55:16.806027Z"}} diff --git a/keystoneauth1/tests/unit/data/ksa_betamax_test_cassette.yaml b/keystoneauth1/tests/unit/data/ksa_betamax_test_cassette.yaml deleted file mode 100644 index 5793652..0000000 --- a/keystoneauth1/tests/unit/data/ksa_betamax_test_cassette.yaml +++ /dev/null @@ -1,92 +0,0 @@ -http_interactions: -- request: - body: - string: |- - { - "auth": { - "tenantName": "test_tenant_name", - "passwordCredentials": { - "username": "test_user_name", - "password": "test_password" - } - } - } - encoding: utf-8 - headers: - Content-Length: - - '128' - Accept-Encoding: - - gzip, deflate - Accept: - - application/json - User-Agent: - - keystoneauth1 - Connection: - - keep-alive - Content-Type: - - application/json - method: POST - uri: http://keystoneauth-betamax.test/v2.0/tokens - response: - body: - string: |- - { - "access": { - "token": { - "issued_at": "2015-11-27T15:17:19.755470", - "expires": "2015-11-27T16:17:19Z", - "id": "c000c5ee4ba04594a00886028584b50d", - "tenant": { - "enabled": true, - "description": null, - "name": "test_tenant_name", - "id": "6932cad596634a61ac9c759fb91beef1" - }, - "audit_ids": [ - "jY3gYg_YTbmzY2a4ioGuCw" - ] - }, - "user": { - "username": "test_user_name", - "roles_links": [], - "id": "96995e6cc15b40fa8e7cd762f6a5d4c0", - "roles": [ - { - "name": "_member_" - } - ], - "name": "67eff5f6-9477-4961-88b4-437e6596a795" - }, - "metadata": { - "is_admin": 0, - "roles": [ - "9fe2ff9ee4384b1894a90878d3e92bab" - ] - } - } - } - encoding: null - headers: - X-Openstack-Request-Id: - - req-f9e188b4-06fd-4a4c-a952-2315b368218c - Content-Length: - - '2684' - Connection: - - keep-alive - Date: - - Fri, 27 Nov 2015 15:17:19 GMT - Content-Type: - - application/json - Vary: - - X-Auth-Token - X-Distribution: - - Ubuntu - Server: - - Fake - status: - message: OK - code: 200 - url: http://keystoneauth-betamax.test/v2.0/tokens - recorded_at: '2015-11-27T15:17:19' -recorded_with: betamax/0.5.1 - diff --git a/keystoneauth1/tests/unit/data/ksa_serializer_data.json b/keystoneauth1/tests/unit/data/ksa_serializer_data.json deleted file mode 100644 index 59a2ddc..0000000 --- a/keystoneauth1/tests/unit/data/ksa_serializer_data.json +++ /dev/null @@ -1 +0,0 @@ -{"http_interactions": [{"request": {"body": {"string": "{\"auth\": {\"tenantName\": \"test_tenant_name\", \"passwordCredentials\": {\"username\": \"test_user_name\", \"password\": \"test_password\"}}}", "encoding": "utf-8"}, "headers": {"Content-Length": ["128"], "Accept-Encoding": ["gzip, deflate"], "Accept": ["application/json"], "User-Agent": ["keystoneauth1"], "Connection": ["keep-alive"], "Content-Type": ["application/json"]}, "method": "POST", "uri": "http://keystoneauth-betamax.test/v2.0/tokens"}, "response": {"body": {"string": "{\"access\": {\"token\": {\"issued_at\": \"2015-11-27T15:17:19.755470\", \"expires\": \"2015-11-27T16:17:19Z\", \"id\": \"c000c5ee4ba04594a00886028584b50d\", \"tenant\": {\"description\": null, \"enabled\": true, \"id\": \"6932cad596634a61ac9c759fb91beef1\", \"name\": \"test_tenant_name\"}, \"audit_ids\": [\"jY3gYg_YTbmzY2a4ioGuCw\"]}, \"user\": {\"username\": \"test_user_name\", \"roles_links\": [], \"id\": \"96995e6cc15b40fa8e7cd762f6a5d4c0\", \"roles\": [{\"name\": \"_member_\"}], \"name\": \"67eff5f6-9477-4961-88b4-437e6596a795\"}, \"metadata\": {\"is_admin\": 0, \"roles\": [\"9fe2ff9ee4384b1894a90878d3e92bab\"]}}}", "encoding": null}, "headers": {"X-Openstack-Request-Id": ["req-f9e188b4-06fd-4a4c-a952-2315b368218c"], "Content-Length": ["2684"], "Connection": ["keep-alive"], "Date": ["Fri, 27 Nov 2015 15:17:19 GMT"], "Content-Type": ["application/json"], "Vary": ["X-Auth-Token"], "X-Distribution": ["Ubuntu"], "Server": ["Fake"]}, "status": {"message": "OK", "code": 200}, "url": "http://keystoneauth-betamax.test/v2.0/tokens"}, "recorded_at": "2015-11-27T15:17:19"}], "recorded_with": "betamax/0.5.1"} diff --git a/keystoneauth1/tests/unit/data/test_pre_record_hook.json b/keystoneauth1/tests/unit/data/test_pre_record_hook.json deleted file mode 100644 index e69de29..0000000 diff --git a/keystoneauth1/tests/unit/exceptions/__init__.py b/keystoneauth1/tests/unit/exceptions/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/keystoneauth1/tests/unit/exceptions/test_exceptions.py b/keystoneauth1/tests/unit/exceptions/test_exceptions.py deleted file mode 100644 index 341ef57..0000000 --- a/keystoneauth1/tests/unit/exceptions/test_exceptions.py +++ /dev/null @@ -1,32 +0,0 @@ -# Licensed under the Apache License, Version 2.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 keystoneauth1 import exceptions -from keystoneauth1.tests.unit import utils - - -class ExceptionTests(utils.TestCase): - - def test_clientexception_with_message(self): - test_message = 'Unittest exception message.' - exc = exceptions.ClientException(message=test_message) - self.assertEqual(test_message, exc.message) - - def test_clientexception_with_no_message(self): - exc = exceptions.ClientException() - self.assertEqual(exceptions.ClientException.__name__, - exc.message) - - def test_using_default_message(self): - exc = exceptions.AuthorizationFailure() - self.assertEqual(exceptions.AuthorizationFailure.message, - exc.message) diff --git a/keystoneauth1/tests/unit/extras/__init__.py b/keystoneauth1/tests/unit/extras/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/keystoneauth1/tests/unit/extras/kerberos/__init__.py b/keystoneauth1/tests/unit/extras/kerberos/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/keystoneauth1/tests/unit/extras/kerberos/base.py b/keystoneauth1/tests/unit/extras/kerberos/base.py deleted file mode 100644 index f76d148..0000000 --- a/keystoneauth1/tests/unit/extras/kerberos/base.py +++ /dev/null @@ -1,44 +0,0 @@ -# Copyright 2010-2011 OpenStack Foundation -# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. -# -# Licensed under the Apache License, Version 2.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 keystoneauth1.tests.unit.extras.kerberos import utils -from keystoneauth1.tests.unit import utils as test_utils - - -REQUEST = {'auth': {'identity': {'methods': ['kerberos'], - 'kerberos': {}}}} - - -class TestCase(test_utils.TestCase): - """Test case base class for Kerberos unit tests.""" - - TEST_V3_URL = test_utils.TestCase.TEST_ROOT_URL + 'v3' - - def setUp(self): - super(TestCase, self).setUp() - - km = utils.KerberosMock(self.requests_mock) - self.kerberos_mock = self.useFixture(km) - - def assertRequestBody(self, body=None): - """Ensure the request body is the standard Kerberos auth request. - - :param dict body: the body to compare. If not provided the last request - body will be used. - """ - if not body: - body = self.requests_mock.last_request.json() - - self.assertEqual(REQUEST, body) diff --git a/keystoneauth1/tests/unit/extras/kerberos/test_fedkerb_loading.py b/keystoneauth1/tests/unit/extras/kerberos/test_fedkerb_loading.py deleted file mode 100644 index 874543c..0000000 --- a/keystoneauth1/tests/unit/extras/kerberos/test_fedkerb_loading.py +++ /dev/null @@ -1,48 +0,0 @@ -# Licensed under the Apache License, Version 2.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 keystoneauth1 import exceptions -from keystoneauth1 import loading -from keystoneauth1.tests.unit import utils as test_utils - - -class FedKerbLoadingTests(test_utils.TestCase): - - def test_options(self): - opts = [o.name for o in - loading.get_plugin_loader('v3fedkerb').get_options()] - - allowed_opts = ['domain-id', - 'domain-name', - 'identity-provider', - 'project-id', - 'project-name', - 'project-domain-id', - 'project-domain-name', - 'protocol', - 'trust-id', - 'auth-url', - ] - - self.assertItemsEqual(allowed_opts, opts) - - def create(self, **kwargs): - loader = loading.get_plugin_loader('v3fedkerb') - return loader.load_from_options(**kwargs) - - def test_load_none(self): - self.assertRaises(exceptions.MissingRequiredOptions, self.create) - - def test_load(self): - self.create(auth_url='auth_url', - identity_provider='idp', - protocol='protocol') diff --git a/keystoneauth1/tests/unit/extras/kerberos/test_kerberos_loading.py b/keystoneauth1/tests/unit/extras/kerberos/test_kerberos_loading.py deleted file mode 100644 index e4da307..0000000 --- a/keystoneauth1/tests/unit/extras/kerberos/test_kerberos_loading.py +++ /dev/null @@ -1,33 +0,0 @@ -# Licensed under the Apache License, Version 2.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 keystoneauth1 import loading -from keystoneauth1.tests.unit import utils as test_utils - - -class KerberosLoadingTests(test_utils.TestCase): - - def test_options(self): - opts = [o.name for o in - loading.get_plugin_loader('v3kerberos').get_options()] - - allowed_opts = ['domain-id', - 'domain-name', - 'project-id', - 'project-name', - 'project-domain-id', - 'project-domain-name', - 'trust-id', - 'auth-url', - ] - - self.assertItemsEqual(allowed_opts, opts) diff --git a/keystoneauth1/tests/unit/extras/kerberos/test_mapped.py b/keystoneauth1/tests/unit/extras/kerberos/test_mapped.py deleted file mode 100644 index a5fa41c..0000000 --- a/keystoneauth1/tests/unit/extras/kerberos/test_mapped.py +++ /dev/null @@ -1,77 +0,0 @@ -# Licensed under the Apache License, Version 2.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 uuid - -from keystoneauth1.extras import kerberos -from keystoneauth1 import fixture as ks_fixture -from keystoneauth1 import session -from keystoneauth1.tests.unit.extras.kerberos import base - - -class TestMappedAuth(base.TestCase): - - def setUp(self): - if kerberos.requests_kerberos is None: - self.skipTest("Kerberos support isn't available.") - - super(TestMappedAuth, self).setUp() - - self.protocol = uuid.uuid4().hex - self.identity_provider = uuid.uuid4().hex - - @property - def token_url(self): - fmt = '%s/OS-FEDERATION/identity_providers/%s/protocols/%s/auth' - return fmt % ( - self.TEST_V3_URL, - self.identity_provider, - self.protocol) - - def test_unscoped_mapped_auth(self): - token_id, _ = self.kerberos_mock.mock_auth_success( - url=self.token_url, method='GET') - - plugin = kerberos.MappedKerberos( - auth_url=self.TEST_V3_URL, protocol=self.protocol, - identity_provider=self.identity_provider) - - sess = session.Session() - tok = plugin.get_token(sess) - - self.assertEqual(token_id, tok) - - def test_project_scoped_mapped_auth(self): - self.kerberos_mock.mock_auth_success(url=self.token_url, - method='GET') - - scoped_id = uuid.uuid4().hex - scoped_body = ks_fixture.V3Token() - scoped_body.set_project_scope() - - self.requests_mock.post( - '%s/auth/tokens' % self.TEST_V3_URL, - json=scoped_body, - headers={'X-Subject-Token': scoped_id, - 'Content-Type': 'application/json'}) - - plugin = kerberos.MappedKerberos( - auth_url=self.TEST_V3_URL, protocol=self.protocol, - identity_provider=self.identity_provider, - project_id=scoped_body.project_id) - - sess = session.Session() - tok = plugin.get_token(sess) - proj = plugin.get_project_id(sess) - - self.assertEqual(scoped_id, tok) - self.assertEqual(scoped_body.project_id, proj) diff --git a/keystoneauth1/tests/unit/extras/kerberos/test_v3.py b/keystoneauth1/tests/unit/extras/kerberos/test_v3.py deleted file mode 100644 index 1d6e5e9..0000000 --- a/keystoneauth1/tests/unit/extras/kerberos/test_v3.py +++ /dev/null @@ -1,38 +0,0 @@ -# Licensed under the Apache License, Version 2.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 keystoneauth1.extras import kerberos -from keystoneauth1 import session -from keystoneauth1.tests.unit.extras.kerberos import base - - -class TestKerberosAuth(base.TestCase): - - def setUp(self): - if kerberos.requests_kerberos is None: - self.skipTest("Kerberos support isn't available.") - - super(TestKerberosAuth, self).setUp() - - def test_authenticate_with_kerberos_domain_scoped(self): - token_id, token_body = self.kerberos_mock.mock_auth_success() - - a = kerberos.Kerberos(self.TEST_ROOT_URL + 'v3') - s = session.Session(a) - token = a.get_token(s) - - self.assertRequestBody() - self.assertEqual( - self.kerberos_mock.challenge_header, - self.requests_mock.last_request.headers['Authorization']) - self.assertEqual(token_id, a.auth_ref.auth_token) - self.assertEqual(token_id, token) diff --git a/keystoneauth1/tests/unit/extras/kerberos/utils.py b/keystoneauth1/tests/unit/extras/kerberos/utils.py deleted file mode 100644 index 78513c3..0000000 --- a/keystoneauth1/tests/unit/extras/kerberos/utils.py +++ /dev/null @@ -1,83 +0,0 @@ -# Licensed under the Apache License, Version 2.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 uuid - -import fixtures -try: - # requests_kerberos won't be available on py3, it doesn't work with py3. - import requests_kerberos -except ImportError: - requests_kerberos = None - -from keystoneauth1 import fixture as ks_fixture -from keystoneauth1.tests.unit import utils as test_utils - - -class KerberosMock(fixtures.Fixture): - - def __init__(self, requests_mock): - super(KerberosMock, self).__init__() - - self.challenge_header = 'Negotiate %s' % uuid.uuid4().hex - self.pass_header = 'Negotiate %s' % uuid.uuid4().hex - self.requests_mock = requests_mock - - def setUp(self): - super(KerberosMock, self).setUp() - - if requests_kerberos is None: - return - - m = fixtures.MockPatchObject(requests_kerberos.HTTPKerberosAuth, - 'generate_request_header', - self._generate_request_header) - - self.header_fixture = self.useFixture(m) - - m = fixtures.MockPatchObject(requests_kerberos.HTTPKerberosAuth, - 'authenticate_server', - self._authenticate_server) - - self.authenticate_fixture = self.useFixture(m) - - def _generate_request_header(self, *args, **kwargs): - return self.challenge_header - - def _authenticate_server(self, response): - return response.headers.get('www-authenticate') == self.pass_header - - def mock_auth_success( - self, - token_id=None, - token_body=None, - method='POST', - url=test_utils.TestCase.TEST_ROOT_URL + 'v3/auth/tokens'): - if not token_id: - token_id = uuid.uuid4().hex - if not token_body: - token_body = ks_fixture.V3Token() - - response_list = [{'text': 'Fail', - 'status_code': 401, - 'headers': {'WWW-Authenticate': 'Negotiate'}}, - {'headers': {'X-Subject-Token': token_id, - 'Content-Type': 'application/json', - 'WWW-Authenticate': self.pass_header}, - 'status_code': 200, - 'json': token_body}] - - self.requests_mock.register_uri(method, - url, - response_list=response_list) - - return token_id, token_body diff --git a/keystoneauth1/tests/unit/extras/oauth1/__init__.py b/keystoneauth1/tests/unit/extras/oauth1/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/keystoneauth1/tests/unit/extras/oauth1/test_oauth1.py b/keystoneauth1/tests/unit/extras/oauth1/test_oauth1.py deleted file mode 100644 index 482c155..0000000 --- a/keystoneauth1/tests/unit/extras/oauth1/test_oauth1.py +++ /dev/null @@ -1,117 +0,0 @@ -# Licensed under the Apache License, Version 2.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 uuid - -from oauthlib import oauth1 -import six -from testtools import matchers - -from keystoneauth1.extras import oauth1 as ksa_oauth1 -from keystoneauth1 import fixture -from keystoneauth1 import session -from keystoneauth1.tests.unit import utils as test_utils - - -class OAuth1AuthTests(test_utils.TestCase): - - TEST_ROOT_URL = 'http://127.0.0.1:5000/' - TEST_URL = '%s%s' % (TEST_ROOT_URL, 'v3') - TEST_TOKEN = uuid.uuid4().hex - - def stub_auth(self, subject_token=None, **kwargs): - if not subject_token: - subject_token = self.TEST_TOKEN - - self.stub_url('POST', ['auth', 'tokens'], - headers={'X-Subject-Token': subject_token}, **kwargs) - - def _validate_oauth_headers(self, auth_header, oauth_client): - """Validate data in the headers. - - Assert that the data in the headers matches the data - that is produced from oauthlib. - """ - self.assertThat(auth_header, matchers.StartsWith('OAuth ')) - parameters = dict( - oauth1.rfc5849.utils.parse_authorization_header(auth_header)) - - self.assertEqual('HMAC-SHA1', parameters['oauth_signature_method']) - self.assertEqual('1.0', parameters['oauth_version']) - self.assertIsInstance(parameters['oauth_nonce'], six.string_types) - self.assertEqual(oauth_client.client_key, - parameters['oauth_consumer_key']) - if oauth_client.resource_owner_key: - self.assertEqual(oauth_client.resource_owner_key, - parameters['oauth_token'],) - if oauth_client.verifier: - self.assertEqual(oauth_client.verifier, - parameters['oauth_verifier']) - if oauth_client.callback_uri: - self.assertEqual(oauth_client.callback_uri, - parameters['oauth_callback']) - return parameters - - def test_oauth_authenticate_success(self): - consumer_key = uuid.uuid4().hex - consumer_secret = uuid.uuid4().hex - access_key = uuid.uuid4().hex - access_secret = uuid.uuid4().hex - - oauth_token = fixture.V3Token(methods=['oauth1'], - oauth_consumer_id=consumer_key, - oauth_access_token_id=access_key) - oauth_token.set_project_scope() - - self.stub_auth(json=oauth_token) - - a = ksa_oauth1.V3OAuth1(self.TEST_URL, - consumer_key=consumer_key, - consumer_secret=consumer_secret, - access_key=access_key, - access_secret=access_secret) - - s = session.Session(auth=a) - t = s.get_token() - - self.assertEqual(self.TEST_TOKEN, t) - - OAUTH_REQUEST_BODY = { - "auth": { - "identity": { - "methods": ["oauth1"], - "oauth1": {} - } - } - } - - self.assertRequestBodyIs(json=OAUTH_REQUEST_BODY) - - # Assert that the headers have the same oauthlib data - req_headers = self.requests_mock.last_request.headers - oauth_client = oauth1.Client(consumer_key, - client_secret=consumer_secret, - resource_owner_key=access_key, - resource_owner_secret=access_secret, - signature_method=oauth1.SIGNATURE_HMAC) - self._validate_oauth_headers(req_headers['Authorization'], - oauth_client) - - def test_warning_dual_scope(self): - ksa_oauth1.V3OAuth1(self.TEST_URL, - consumer_key=uuid.uuid4().hex, - consumer_secret=uuid.uuid4().hex, - access_key=uuid.uuid4().hex, - access_secret=uuid.uuid4().hex, - project_id=uuid.uuid4().hex) - - self.assertIn('ignored by the identity server', self.logger.output) diff --git a/keystoneauth1/tests/unit/extras/oauth1/test_oauth1_loading.py b/keystoneauth1/tests/unit/extras/oauth1/test_oauth1_loading.py deleted file mode 100644 index ef3ffc0..0000000 --- a/keystoneauth1/tests/unit/extras/oauth1/test_oauth1_loading.py +++ /dev/null @@ -1,57 +0,0 @@ -# Licensed under the Apache License, Version 2.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 uuid - -from keystoneauth1 import loading -from keystoneauth1.tests.unit import utils as test_utils - - -class OAuth1LoadingTests(test_utils.TestCase): - - def setUp(self): - super(OAuth1LoadingTests, self).setUp() - self.auth_url = uuid.uuid4().hex - - def create(self, **kwargs): - kwargs.setdefault('auth_url', self.auth_url) - loader = loading.get_plugin_loader('v3oauth1') - return loader.load_from_options(**kwargs) - - def test_basic(self): - access_key = uuid.uuid4().hex - access_secret = uuid.uuid4().hex - consumer_key = uuid.uuid4().hex - consumer_secret = uuid.uuid4().hex - - p = self.create(access_key=access_key, - access_secret=access_secret, - consumer_key=consumer_key, - consumer_secret=consumer_secret) - - oauth_method = p.auth_methods[0] - - self.assertEqual(self.auth_url, p.auth_url) - self.assertEqual(access_key, oauth_method.access_key) - self.assertEqual(access_secret, oauth_method.access_secret) - self.assertEqual(consumer_key, oauth_method.consumer_key) - self.assertEqual(consumer_secret, oauth_method.consumer_secret) - - def test_options(self): - options = loading.get_plugin_loader('v3oauth1').get_options() - - self.assertEqual(set([o.name for o in options]), - set(['auth-url', - 'access-key', - 'access-secret', - 'consumer-key', - 'consumer-secret'])) diff --git a/keystoneauth1/tests/unit/extras/saml2/__init__.py b/keystoneauth1/tests/unit/extras/saml2/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/keystoneauth1/tests/unit/extras/saml2/examples/xml/ADFS_RequestSecurityTokenResponse.xml b/keystoneauth1/tests/unit/extras/saml2/examples/xml/ADFS_RequestSecurityTokenResponse.xml deleted file mode 100644 index 487bcac..0000000 --- a/keystoneauth1/tests/unit/extras/saml2/examples/xml/ADFS_RequestSecurityTokenResponse.xml +++ /dev/null @@ -1,132 +0,0 @@ - - - http://docs.oasis-open.org/ws-sx/ws-trust/200512/RSTRC/IssueFinal - urn:uuid:487c064b-b7c6-4654-b4d4-715f9961170e - - - 2014-08-05T18:36:14.235Z - 2014-08-05T18:41:14.235Z - - - - - - - - 2014-08-05T18:36:14.063Z - 2014-08-05T19:36:14.063Z - - - - https://ltartari2.cern.ch:5000/Shibboleth.sso/ADFS - - - - - - - https://ltartari2.cern.ch:5000/Shibboleth.sso/ADFS - - - - - marek.denis@cern.ch - - urn:oasis:names:tc:SAML:1.0:cm:bearer - - - - marek.denis@cern.ch - - - marek.denis@cern.ch - - - madenis - - - CERN Users - - - Domain Users - occupants-bldg-31 - CERN-Direct-Employees - ca-dev-allowed - cernts-cerntstest-users - staf-fell-pjas-at-cern - ELG-CERN - student-club-new-members - pawel-dynamic-test-82 - - - Marek Kamil Denis - - - +5555555 - - - 31S-013 - - - Marek Kamil - - - Denis - - - CERN Registered - - - CERN - - - Normal - - - - - marek.denis@cern.ch - - urn:oasis:names:tc:SAML:1.0:cm:bearer - - - - - - - - - - - - - - EaZ/2d0KAY5un9akV3++Npyk6hBc8JuTYs2S3lSxUeQ= - - - CxYiYvNsbedhHdmDbb9YQCBy6Ppus3bNJdw2g2HLq0VU2yRhv23mUW05I89Hs4yG4OcCo0uOZ3zaeNFbSNXMW+Mr996tAXtujKjgyrCXNJAToE+gwltvGxwY1EluSbe3IzoSM3Ao87mKhxGOSzlDhuN7dQ9Rv6l/J4gUjbOO5SIX4pdZ6mVF7cHEfe9x+H8Lg15YjnElQUEaPi+NSW5jYTdtIpsB4ORxJvALuSt6+4doDYc9wuwBiWkEdnBHAQBINoKpAV2oy0/C85SBX3IdRhxUznmL5yEUmf8JvPccXecMPqJow0L43mnCdu74xPwU0as3MNfYQ10kLvHXHfIExg== - - - MIIIEjCCBfqgAwIBAgIKLYgjvQAAAAAAMDANBgkqhkiG9w0BAQsFADBRMRIwEAYKCZImiZPyLGQBGRYCY2gxFDASBgoJkiaJk/IsZAEZFgRjZXJuMSUwIwYDVQQDExxDRVJOIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTEzMTEwODA4Mzg1NVoXDTIzMDcyOTA5MTkzOFowVjESMBAGCgmSJomT8ixkARkWAmNoMRQwEgYKCZImiZPyLGQBGRYEY2VybjESMBAGA1UECxMJY29tcHV0ZXJzMRYwFAYDVQQDEw1sb2dpbi5jZXJuLmNoMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp6t1C0SGlLddL2M+ltffGioTnDT3eztOxlA9bAGuvB8/Rjym8en6+ET9boM02CyoR5Vpn8iElXVWccAExPIQEq70D6LPe86vb+tYhuKPeLfuICN9Z0SMQ4f+57vk61Co1/uw/8kPvXlyd+Ai8Dsn/G0hpH67bBI9VOQKfpJqclcSJuSlUB5PJffvMUpr29B0eRx8LKFnIHbDILSu6nVbFLcadtWIjbYvoKorXg3J6urtkz+zEDeYMTvA6ZGOFf/Xy5eGtroSq9csSC976tx+umKEPhXBA9AcpiCV9Cj5axN03Aaa+iTE36jpnjcd9d02dy5Q9jE2nUN6KXnB6qF6eQIDAQABo4ID5TCCA+EwPQYJKwYBBAGCNxUHBDAwLgYmKwYBBAGCNxUIg73QCYLtjQ2G7Ysrgd71N4WA0GIehd2yb4Wu9TkCAWQCARkwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMA4GA1UdDwEB/wQEAwIFoDBoBgNVHSAEYTBfMF0GCisGAQQBYAoEAQEwTzBNBggrBgEFBQcCARZBaHR0cDovL2NhLWRvY3MuY2Vybi5jaC9jYS1kb2NzL2NwLWNwcy9jZXJuLXRydXN0ZWQtY2EyLWNwLWNwcy5wZGYwJwYJKwYBBAGCNxUKBBowGDAKBggrBgEFBQcDAjAKBggrBgEFBQcDATAdBgNVHQ4EFgQUqtJcwUXasyM6sRaO5nCMFoFDenMwGAYDVR0RBBEwD4INbG9naW4uY2Vybi5jaDAfBgNVHSMEGDAWgBQdkBnqyM7MPI0UsUzZ7BTiYUADYTCCASoGA1UdHwSCASEwggEdMIIBGaCCARWgggERhkdodHRwOi8vY2FmaWxlcy5jZXJuLmNoL2NhZmlsZXMvY3JsL0NFUk4lMjBDZXJ0aWZpY2F0aW9uJTIwQXV0aG9yaXR5LmNybIaBxWxkYXA6Ly8vQ049Q0VSTiUyMENlcnRpZmljYXRpb24lMjBBdXRob3JpdHksQ049Q0VSTlBLSTA3LENOPUNEUCxDTj1QdWJsaWMlMjBLZXklMjBTZXJ2aWNlcyxDTj1TZXJ2aWNlcyxDTj1Db25maWd1cmF0aW9uLERDPWNlcm4sREM9Y2g/Y2VydGlmaWNhdGVSZXZvY2F0aW9uTGlzdD9iYXNlP29iamVjdENsYXNzPWNSTERpc3RyaWJ1dGlvblBvaW50MIIBVAYIKwYBBQUHAQEEggFGMIIBQjBcBggrBgEFBQcwAoZQaHR0cDovL2NhZmlsZXMuY2Vybi5jaC9jYWZpbGVzL2NlcnRpZmljYXRlcy9DRVJOJTIwQ2VydGlmaWNhdGlvbiUyMEF1dGhvcml0eS5jcnQwgbsGCCsGAQUFBzAChoGubGRhcDovLy9DTj1DRVJOJTIwQ2VydGlmaWNhdGlvbiUyMEF1dGhvcml0eSxDTj1BSUEsQ049UHVibGljJTIwS2V5JTIwU2VydmljZXMsQ049U2VydmljZXMsQ049Q29uZmlndXJhdGlvbixEQz1jZXJuLERDPWNoP2NBQ2VydGlmaWNhdGU/YmFzZT9vYmplY3RDbGFzcz1jZXJ0aWZpY2F0aW9uQXV0aG9yaXR5MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5jZXJuLmNoL29jc3AwDQYJKoZIhvcNAQELBQADggIBAGKZ3bknTCfNuh4TMaL3PuvBFjU8LQ5NKY9GLZvY2ibYMRk5Is6eWRgyUsy1UJRQdaQQPnnysqrGq8VRw/NIFotBBsA978/+jj7v4e5Kr4o8HvwAQNLBxNmF6XkDytpLL701FcNEGRqIsoIhNzihi2VBADLC9HxljEyPT52IR767TMk/+xTOqClceq3sq6WRD4m+xaWRUJyOhn+Pqr+wbhXIw4wzHC6X0hcLj8P9Povtm6VmKkN9JPuymMo/0+zSrUt2+TYfmbbEKYJSP0+sceQ76IKxxmSdKAr1qDNE8v+c3DvPM2PKmfivwaV2l44FdP8ulzqTgphkYcN1daa9Oc+qJeyu/eL7xWzk6Zq5R+jVrMlM0p1y2XczI7Hoc96TMOcbVnwgMcVqRM9p57VItn6XubYPR0C33i1yUZjkWbIfqEjq6Vev6lVgngOyzu+hqC/8SDyORA3dlF9aZOD13kPZdF/JRphHREQtaRydAiYRlE/WHTvOcY52jujDftUR6oY0eWaWkwSHbX+kDFx8IlR8UtQCUgkGHBGwnOYLIGu7SRDGSfOBOiVhxKoHWVk/pL6eKY2SkmyOmmgO4JnQGg95qeAOMG/EQZt/2x8GAavUqGvYy9dPFwFf08678hQqkjNSuex7UD0ku8OP1QKvpP44l6vZhFc6A5XqjdU9lus1 - - - - - - - - _c9e77bc4-a81b-4da7-88c2-72a6ba376d3f - - - - - _c9e77bc4-a81b-4da7-88c2-72a6ba376d3f - - - urn:oasis:names:tc:SAML:1.0:assertion - http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue - http://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer - - - - \ No newline at end of file diff --git a/keystoneauth1/tests/unit/extras/saml2/examples/xml/ADFS_fault.xml b/keystoneauth1/tests/unit/extras/saml2/examples/xml/ADFS_fault.xml deleted file mode 100644 index 913252e..0000000 --- a/keystoneauth1/tests/unit/extras/saml2/examples/xml/ADFS_fault.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - http://www.w3.org/2005/08/addressing/soap/fault - urn:uuid:89c47849-2622-4cdc-bb06-1d46c89ed12d - - - - - s:Sender - - a:FailedAuthentication - - - - At least one security token in the message could not be validated. - - - - \ No newline at end of file diff --git a/keystoneauth1/tests/unit/extras/saml2/fixtures/__init__.py b/keystoneauth1/tests/unit/extras/saml2/fixtures/__init__.py deleted file mode 100644 index 59d87c0..0000000 --- a/keystoneauth1/tests/unit/extras/saml2/fixtures/__init__.py +++ /dev/null @@ -1,120 +0,0 @@ -# Licensed under the Apache License, Version 2.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 string - -DIR = os.path.dirname(os.path.abspath(__file__)) - - -def template(f, **kwargs): - with open(os.path.join(DIR, 'templates', f)) as f: - return string.Template(f.read()).substitute(**kwargs) - - -def soap_response(**kwargs): - kwargs.setdefault('provider', 'https://idp.testshib.org/idp/shibboleth') - kwargs.setdefault('consumer', - 'https://openstack4.local/Shibboleth.sso/SAML2/ECP') - kwargs.setdefault('issuer', 'https://openstack4.local/shibboleth') - return template('soap_response.xml', **kwargs).encode('utf-8') - - -def saml_assertion(**kwargs): - kwargs.setdefault('issuer', 'https://idp.testshib.org/idp/shibboleth') - kwargs.setdefault('destination', - 'https://openstack4.local/Shibboleth.sso/SAML2/ECP') - return template('saml_assertion.xml', **kwargs).encode('utf-8') - - -def authn_request(**kwargs): - kwargs.setdefault('issuer', - 'https://openstack4.local/Shibboleth.sso/SAML2/ECP') - return template('authn_request.xml', **kwargs).encode('utf-8') - - -SP_SOAP_RESPONSE = soap_response() -SAML2_ASSERTION = saml_assertion() -AUTHN_REQUEST = authn_request() - -UNSCOPED_TOKEN_HEADER = 'UNSCOPED_TOKEN' - -UNSCOPED_TOKEN = { - "token": { - "issued_at": "2014-06-09T09:48:59.643406Z", - "extras": {}, - "methods": ["saml2"], - "expires_at": "2014-06-09T10:48:59.643375Z", - "user": { - "OS-FEDERATION": { - "identity_provider": { - "id": "testshib" - }, - "protocol": { - "id": "saml2" - }, - "groups": [ - {"id": "1764fa5cf69a49a4918131de5ce4af9a"} - ] - }, - "id": "testhib%20user", - "name": "testhib user" - } - } -} - -PROJECTS = { - "projects": [ - { - "domain_id": "37ef61", - "enabled": 'true', - "id": "12d706", - "links": { - "self": "http://identity:35357/v3/projects/12d706" - }, - "name": "a project name" - }, - { - "domain_id": "37ef61", - "enabled": 'true', - "id": "9ca0eb", - "links": { - "self": "http://identity:35357/v3/projects/9ca0eb" - }, - "name": "another project" - } - ], - "links": { - "self": "http://identity:35357/v3/OS-FEDERATION/projects", - "previous": 'null', - "next": 'null' - } -} - -DOMAINS = { - "domains": [ - { - "description": "desc of domain", - "enabled": 'true', - "id": "37ef61", - "links": { - "self": "http://identity:35357/v3/domains/37ef61" - }, - "name": "my domain" - } - ], - "links": { - "self": "http://identity:35357/v3/OS-FEDERATION/domains", - "previous": 'null', - "next": 'null' - } -} diff --git a/keystoneauth1/tests/unit/extras/saml2/fixtures/templates/authn_request.xml b/keystoneauth1/tests/unit/extras/saml2/fixtures/templates/authn_request.xml deleted file mode 100644 index f5a1c8d..0000000 --- a/keystoneauth1/tests/unit/extras/saml2/fixtures/templates/authn_request.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - $issuer - - - - - - - - - - diff --git a/keystoneauth1/tests/unit/extras/saml2/fixtures/templates/saml_assertion.xml b/keystoneauth1/tests/unit/extras/saml2/fixtures/templates/saml_assertion.xml deleted file mode 100644 index 1306937..0000000 --- a/keystoneauth1/tests/unit/extras/saml2/fixtures/templates/saml_assertion.xml +++ /dev/null @@ -1,69 +0,0 @@ - - - - - x= - - - - - - $issuer - - - - - - - - - - - - - - - - - - - - - VALUE== - - - - - - - VALUE= - - - - - - - diff --git a/keystoneauth1/tests/unit/extras/saml2/fixtures/templates/soap_response.xml b/keystoneauth1/tests/unit/extras/saml2/fixtures/templates/soap_response.xml deleted file mode 100644 index 879e5f2..0000000 --- a/keystoneauth1/tests/unit/extras/saml2/fixtures/templates/soap_response.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - $issuer - - - - - - - ss:mem:6f1f20fafbb38433467e9d477df67615 - - - - - - $issuer - - - - - - - - - - diff --git a/keystoneauth1/tests/unit/extras/saml2/test_auth_adfs.py b/keystoneauth1/tests/unit/extras/saml2/test_auth_adfs.py deleted file mode 100644 index 8ff6844..0000000 --- a/keystoneauth1/tests/unit/extras/saml2/test_auth_adfs.py +++ /dev/null @@ -1,261 +0,0 @@ -# Licensed under the Apache License, Version 2.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 uuid - -from lxml import etree -from six.moves import urllib - -from keystoneauth1 import exceptions -from keystoneauth1.extras import _saml2 as saml2 -from keystoneauth1.tests.unit import client_fixtures -from keystoneauth1.tests.unit.extras.saml2 import fixtures as saml2_fixtures -from keystoneauth1.tests.unit.extras.saml2 import utils -from keystoneauth1.tests.unit import matchers - - -class AuthenticateviaADFSTests(utils.TestCase): - - GROUP = 'auth' - - NAMESPACES = { - 's': 'http://www.w3.org/2003/05/soap-envelope', - 'trust': 'http://docs.oasis-open.org/ws-sx/ws-trust/200512', - 'wsa': 'http://www.w3.org/2005/08/addressing', - 'wsp': 'http://schemas.xmlsoap.org/ws/2004/09/policy', - 'a': 'http://www.w3.org/2005/08/addressing', - 'o': ('http://docs.oasis-open.org/wss/2004/01/oasis' - '-200401-wss-wssecurity-secext-1.0.xsd') - } - - USER_XPATH = ('/s:Envelope/s:Header' - '/o:Security' - '/o:UsernameToken' - '/o:Username') - PASSWORD_XPATH = ('/s:Envelope/s:Header' - '/o:Security' - '/o:UsernameToken' - '/o:Password') - ADDRESS_XPATH = ('/s:Envelope/s:Body' - '/trust:RequestSecurityToken' - '/wsp:AppliesTo/wsa:EndpointReference' - '/wsa:Address') - TO_XPATH = ('/s:Envelope/s:Header' - '/a:To') - - TEST_TOKEN = uuid.uuid4().hex - - PROTOCOL = 'saml2' - - @property - def _uuid4(self): - return '4b911420-4982-4009-8afc-5c596cd487f5' - - def setUp(self): - super(AuthenticateviaADFSTests, self).setUp() - - self.IDENTITY_PROVIDER = 'adfs' - self.IDENTITY_PROVIDER_URL = ('http://adfs.local/adfs/service/trust/13' - '/usernamemixed') - self.FEDERATION_AUTH_URL = '%s/%s' % ( - self.TEST_URL, - 'OS-FEDERATION/identity_providers/adfs/protocols/saml2/auth') - self.SP_ENDPOINT = 'https://openstack4.local/Shibboleth.sso/ADFS' - self.SP_ENTITYID = 'https://openstack4.local' - - self.adfsplugin = saml2.V3ADFSPassword( - self.TEST_URL, self.IDENTITY_PROVIDER, - self.IDENTITY_PROVIDER_URL, self.SP_ENDPOINT, - self.TEST_USER, self.TEST_TOKEN, self.PROTOCOL) - - self.ADFS_SECURITY_TOKEN_RESPONSE = utils._load_xml( - 'ADFS_RequestSecurityTokenResponse.xml') - self.ADFS_FAULT = utils._load_xml('ADFS_fault.xml') - - def test_get_adfs_security_token(self): - """Test ADFSPassword._get_adfs_security_token().""" - self.requests_mock.post( - self.IDENTITY_PROVIDER_URL, - content=utils.make_oneline(self.ADFS_SECURITY_TOKEN_RESPONSE), - status_code=200) - - self.adfsplugin._prepare_adfs_request() - self.adfsplugin._get_adfs_security_token(self.session) - - adfs_response = etree.tostring(self.adfsplugin.adfs_token) - fixture_response = self.ADFS_SECURITY_TOKEN_RESPONSE - - self.assertThat(fixture_response, - matchers.XMLEquals(adfs_response)) - - def test_adfs_request_user(self): - self.adfsplugin._prepare_adfs_request() - user = self.adfsplugin.prepared_request.xpath( - self.USER_XPATH, namespaces=self.NAMESPACES)[0] - self.assertEqual(self.TEST_USER, user.text) - - def test_adfs_request_password(self): - self.adfsplugin._prepare_adfs_request() - password = self.adfsplugin.prepared_request.xpath( - self.PASSWORD_XPATH, namespaces=self.NAMESPACES)[0] - self.assertEqual(self.TEST_TOKEN, password.text) - - def test_adfs_request_to(self): - self.adfsplugin._prepare_adfs_request() - to = self.adfsplugin.prepared_request.xpath( - self.TO_XPATH, namespaces=self.NAMESPACES)[0] - self.assertEqual(self.IDENTITY_PROVIDER_URL, to.text) - - def test_prepare_adfs_request_address(self): - self.adfsplugin._prepare_adfs_request() - address = self.adfsplugin.prepared_request.xpath( - self.ADDRESS_XPATH, namespaces=self.NAMESPACES)[0] - self.assertEqual(self.SP_ENDPOINT, address.text) - - def test_prepare_adfs_request_custom_endpointreference(self): - self.adfsplugin = saml2.V3ADFSPassword( - self.TEST_URL, self.IDENTITY_PROVIDER, - self.IDENTITY_PROVIDER_URL, self.SP_ENDPOINT, - self.TEST_USER, self.TEST_TOKEN, self.PROTOCOL, self.SP_ENTITYID) - self.adfsplugin._prepare_adfs_request() - address = self.adfsplugin.prepared_request.xpath( - self.ADDRESS_XPATH, namespaces=self.NAMESPACES)[0] - self.assertEqual(self.SP_ENTITYID, address.text) - - def test_prepare_sp_request(self): - assertion = etree.XML(self.ADFS_SECURITY_TOKEN_RESPONSE) - assertion = assertion.xpath( - saml2.V3ADFSPassword.ADFS_ASSERTION_XPATH, - namespaces=saml2.V3ADFSPassword.ADFS_TOKEN_NAMESPACES) - assertion = assertion[0] - assertion = etree.tostring(assertion) - - assertion = assertion.replace( - b'http://docs.oasis-open.org/ws-sx/ws-trust/200512', - b'http://schemas.xmlsoap.org/ws/2005/02/trust') - assertion = urllib.parse.quote(assertion) - assertion = 'wa=wsignin1.0&wresult=' + assertion - - self.adfsplugin.adfs_token = etree.XML( - self.ADFS_SECURITY_TOKEN_RESPONSE) - self.adfsplugin._prepare_sp_request() - - self.assertEqual(assertion, self.adfsplugin.encoded_assertion) - - def test_get_adfs_security_token_authn_fail(self): - """Test proper parsing XML fault after bad authentication. - - An exceptions.AuthorizationFailure should be raised including - error message from the XML message indicating where was the problem. - """ - content = utils.make_oneline(self.ADFS_FAULT) - self.requests_mock.register_uri('POST', - self.IDENTITY_PROVIDER_URL, - content=content, - status_code=500) - - self.adfsplugin._prepare_adfs_request() - self.assertRaises(exceptions.AuthorizationFailure, - self.adfsplugin._get_adfs_security_token, - self.session) - # TODO(marek-denis): Python3 tests complain about missing 'message' - # attributes - # self.assertEqual('a:FailedAuthentication', e.message) - - def test_get_adfs_security_token_bad_response(self): - """Test proper handling HTTP 500 and mangled (non XML) response. - - This should never happen yet, keystoneauth1 should be prepared - and correctly raise exceptions.InternalServerError once it cannot - parse XML fault message - """ - self.requests_mock.register_uri('POST', - self.IDENTITY_PROVIDER_URL, - content=b'NOT XML', - status_code=500) - self.adfsplugin._prepare_adfs_request() - self.assertRaises(exceptions.InternalServerError, - self.adfsplugin._get_adfs_security_token, - self.session) - - # TODO(marek-denis): Need to figure out how to properly send cookies - # from the request_mock methods. - def _send_assertion_to_service_provider(self): - """Test whether SP issues a cookie.""" - cookie = uuid.uuid4().hex - - self.requests_mock.post(self.SP_ENDPOINT, - headers={"set-cookie": cookie}, - status_code=302) - - self.adfsplugin.adfs_token = self._build_adfs_request() - self.adfsplugin._prepare_sp_request() - self.adfsplugin._send_assertion_to_service_provider(self.session) - - self.assertEqual(1, len(self.session.session.cookies)) - - def test_send_assertion_to_service_provider_bad_status(self): - self.requests_mock.register_uri('POST', self.SP_ENDPOINT, - status_code=500) - - self.adfsplugin.adfs_token = etree.XML( - self.ADFS_SECURITY_TOKEN_RESPONSE) - self.adfsplugin._prepare_sp_request() - - self.assertRaises( - exceptions.InternalServerError, - self.adfsplugin._send_assertion_to_service_provider, - self.session) - - def test_access_sp_no_cookies_fail(self): - # clean cookie jar - self.session.session.cookies = [] - - self.assertRaises(exceptions.AuthorizationFailure, - self.adfsplugin._access_service_provider, - self.session) - - def test_check_valid_token_when_authenticated(self): - self.requests_mock.register_uri( - 'GET', self.FEDERATION_AUTH_URL, - json=saml2_fixtures.UNSCOPED_TOKEN, - headers=client_fixtures.AUTH_RESPONSE_HEADERS) - - self.session.session.cookies = [object()] - self.adfsplugin._access_service_provider(self.session) - response = self.adfsplugin.authenticated_response - - self.assertEqual(client_fixtures.AUTH_RESPONSE_HEADERS, - response.headers) - - self.assertEqual(saml2_fixtures.UNSCOPED_TOKEN['token'], - response.json()['token']) - - def test_end_to_end_workflow(self): - self.requests_mock.register_uri( - 'POST', self.IDENTITY_PROVIDER_URL, - content=self.ADFS_SECURITY_TOKEN_RESPONSE, - status_code=200) - self.requests_mock.register_uri( - 'POST', self.SP_ENDPOINT, - headers={"set-cookie": 'x'}, - status_code=302) - self.requests_mock.register_uri( - 'GET', self.FEDERATION_AUTH_URL, - json=saml2_fixtures.UNSCOPED_TOKEN, - headers=client_fixtures.AUTH_RESPONSE_HEADERS) - - # NOTE(marek-denis): We need to mimic this until self.requests_mock can - # issue cookies properly. - self.session.session.cookies = [object()] - token = self.adfsplugin.get_auth_ref(self.session) - self.assertEqual(client_fixtures.AUTH_SUBJECT_TOKEN, token.auth_token) diff --git a/keystoneauth1/tests/unit/extras/saml2/test_auth_saml2.py b/keystoneauth1/tests/unit/extras/saml2/test_auth_saml2.py deleted file mode 100644 index a1800a7..0000000 --- a/keystoneauth1/tests/unit/extras/saml2/test_auth_saml2.py +++ /dev/null @@ -1,312 +0,0 @@ -# Licensed under the Apache License, Version 2.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 uuid - -import requests - -from keystoneauth1 import exceptions -from keystoneauth1.extras import _saml2 as saml2 -from keystoneauth1 import fixture as ksa_fixtures -from keystoneauth1 import session -from keystoneauth1.tests.unit.extras.saml2 import fixtures as saml2_fixtures -from keystoneauth1.tests.unit.extras.saml2 import utils -from keystoneauth1.tests.unit import matchers - -PAOS_HEADER = 'application/vnd.paos+xml' -CONTENT_TYPE_PAOS_HEADER = {'Content-Type': PAOS_HEADER} -InvalidResponse = saml2.v3.saml2.InvalidResponse - - -class SamlAuth2PluginTests(utils.TestCase): - """These test ONLY the standalone requests auth plugin. - - Tests for the auth plugin are later so that hopefully these can be - extracted into it's own module. - """ - - HEADER_MEDIA_TYPE_SEPARATOR = ',' - - TEST_USER = 'user' - TEST_PASS = 'pass' - TEST_SP_URL = 'http://sp.test' - TEST_IDP_URL = 'http://idp.test' - TEST_CONSUMER_URL = "https://openstack4.local/Shibboleth.sso/SAML2/ECP" - - def get_plugin(self, **kwargs): - kwargs.setdefault('identity_provider_url', self.TEST_IDP_URL) - kwargs.setdefault('requests_auth', (self.TEST_USER, self.TEST_PASS)) - return saml2.v3.saml2._SamlAuth(**kwargs) - - @property - def calls(self): - return [r.url.strip('/') for r in self.requests_mock.request_history] - - def basic_header(self, username=TEST_USER, password=TEST_PASS): - user_pass = ('%s:%s' % (username, password)).encode('utf-8') - return 'Basic %s' % base64.b64encode(user_pass).decode('utf-8') - - def test_request_accept_headers(self): - # Include some random Accept header - random_header = uuid.uuid4().hex - headers = {'Accept': random_header} - req = requests.Request('GET', 'http://another.test', headers=headers) - - plugin = self.get_plugin() - plugin_headers = plugin(req).headers - self.assertIn('Accept', plugin_headers) - - # Since we have included a random Accept header, the plugin should have - # added the PAOS_HEADER to it using the correct media type separator - accept_header = plugin_headers['Accept'] - self.assertIn(self.HEADER_MEDIA_TYPE_SEPARATOR, accept_header) - self.assertIn(random_header, - accept_header.split(self.HEADER_MEDIA_TYPE_SEPARATOR)) - self.assertIn(PAOS_HEADER, - accept_header.split(self.HEADER_MEDIA_TYPE_SEPARATOR)) - - def test_passed_when_not_200(self): - text = uuid.uuid4().hex - test_url = 'http://another.test' - self.requests_mock.get(test_url, - status_code=201, - headers=CONTENT_TYPE_PAOS_HEADER, - text=text) - - resp = requests.get(test_url, auth=self.get_plugin()) - self.assertEqual(201, resp.status_code) - self.assertEqual(text, resp.text) - - def test_200_without_paos_header(self): - text = uuid.uuid4().hex - test_url = 'http://another.test' - self.requests_mock.get(test_url, status_code=200, text=text) - - resp = requests.get(test_url, auth=self.get_plugin()) - self.assertEqual(200, resp.status_code) - self.assertEqual(text, resp.text) - - def test_standard_workflow_302_redirect(self): - text = uuid.uuid4().hex - - self.requests_mock.get(self.TEST_SP_URL, response_list=[ - dict(headers=CONTENT_TYPE_PAOS_HEADER, - content=utils.make_oneline(saml2_fixtures.SP_SOAP_RESPONSE)), - dict(text=text) - ]) - - authm = self.requests_mock.post(self.TEST_IDP_URL, - content=saml2_fixtures.SAML2_ASSERTION) - - self.requests_mock.post( - self.TEST_CONSUMER_URL, - status_code=302, - headers={'Location': self.TEST_SP_URL}) - - resp = requests.get(self.TEST_SP_URL, auth=self.get_plugin()) - self.assertEqual(200, resp.status_code) - self.assertEqual(text, resp.text) - - self.assertEqual(self.calls, [self.TEST_SP_URL, - self.TEST_IDP_URL, - self.TEST_CONSUMER_URL, - self.TEST_SP_URL]) - - self.assertEqual(self.basic_header(), - authm.last_request.headers['Authorization']) - - authn_request = self.requests_mock.request_history[1].text - self.assertThat(saml2_fixtures.AUTHN_REQUEST, - matchers.XMLEquals(authn_request)) - - def test_standard_workflow_303_redirect(self): - text = uuid.uuid4().hex - - self.requests_mock.get(self.TEST_SP_URL, response_list=[ - dict(headers=CONTENT_TYPE_PAOS_HEADER, - content=utils.make_oneline(saml2_fixtures.SP_SOAP_RESPONSE)), - dict(text=text) - ]) - - authm = self.requests_mock.post(self.TEST_IDP_URL, - content=saml2_fixtures.SAML2_ASSERTION) - - self.requests_mock.post( - self.TEST_CONSUMER_URL, - status_code=303, - headers={'Location': self.TEST_SP_URL}) - - resp = requests.get(self.TEST_SP_URL, auth=self.get_plugin()) - self.assertEqual(200, resp.status_code) - self.assertEqual(text, resp.text) - - url_flow = [self.TEST_SP_URL, - self.TEST_IDP_URL, - self.TEST_CONSUMER_URL, - self.TEST_SP_URL] - - self.assertEqual(url_flow, [r.url.rstrip('/') for r in resp.history]) - self.assertEqual(url_flow, self.calls) - - self.assertEqual(self.basic_header(), - authm.last_request.headers['Authorization']) - - authn_request = self.requests_mock.request_history[1].text - self.assertThat(saml2_fixtures.AUTHN_REQUEST, - matchers.XMLEquals(authn_request)) - - def test_initial_sp_call_invalid_response(self): - """Send initial SP HTTP request and receive wrong server response.""" - self.requests_mock.get(self.TEST_SP_URL, - headers=CONTENT_TYPE_PAOS_HEADER, - text='NON XML RESPONSE') - - self.assertRaises(InvalidResponse, - requests.get, - self.TEST_SP_URL, - auth=self.get_plugin()) - - self.assertEqual(self.calls, [self.TEST_SP_URL]) - - def test_consumer_mismatch_error_workflow(self): - consumer1 = 'http://consumer1/Shibboleth.sso/SAML2/ECP' - consumer2 = 'http://consumer2/Shibboleth.sso/SAML2/ECP' - soap_response = saml2_fixtures.soap_response(consumer=consumer1) - saml_assertion = saml2_fixtures.saml_assertion(destination=consumer2) - - self.requests_mock.get(self.TEST_SP_URL, - headers=CONTENT_TYPE_PAOS_HEADER, - content=soap_response) - - self.requests_mock.post(self.TEST_IDP_URL, content=saml_assertion) - - # receive the SAML error, body unchecked - saml_error = self.requests_mock.post(consumer1) - - self.assertRaises(saml2.v3.saml2.ConsumerMismatch, - requests.get, - self.TEST_SP_URL, - auth=self.get_plugin()) - - self.assertTrue(saml_error.called) - - -class AuthenticateviaSAML2Tests(utils.TestCase): - - TEST_USER = 'user' - TEST_PASS = 'pass' - TEST_IDP = 'tester' - TEST_PROTOCOL = 'saml2' - TEST_AUTH_URL = 'http://keystone.test:5000/v3/' - - TEST_IDP_URL = 'https://idp.test' - TEST_CONSUMER_URL = "https://openstack4.local/Shibboleth.sso/SAML2/ECP" - - def get_plugin(self, **kwargs): - kwargs.setdefault('auth_url', self.TEST_AUTH_URL) - kwargs.setdefault('username', self.TEST_USER) - kwargs.setdefault('password', self.TEST_PASS) - kwargs.setdefault('identity_provider', self.TEST_IDP) - kwargs.setdefault('identity_provider_url', self.TEST_IDP_URL) - kwargs.setdefault('protocol', self.TEST_PROTOCOL) - return saml2.V3Saml2Password(**kwargs) - - def sp_url(self, **kwargs): - kwargs.setdefault('base', self.TEST_AUTH_URL.rstrip('/')) - kwargs.setdefault('identity_provider', self.TEST_IDP) - kwargs.setdefault('protocol', self.TEST_PROTOCOL) - - templ = ('%(base)s/OS-FEDERATION/identity_providers/' - '%(identity_provider)s/protocols/%(protocol)s/auth') - return templ % kwargs - - @property - def calls(self): - return [r.url.strip('/') for r in self.requests_mock.request_history] - - def basic_header(self, username=TEST_USER, password=TEST_PASS): - user_pass = ('%s:%s' % (username, password)).encode('utf-8') - return 'Basic %s' % base64.b64encode(user_pass).decode('utf-8') - - def setUp(self): - super(AuthenticateviaSAML2Tests, self).setUp() - self.session = session.Session() - self.default_sp_url = self.sp_url() - - def test_workflow(self): - token_id = uuid.uuid4().hex - token = ksa_fixtures.V3Token() - - self.requests_mock.get(self.default_sp_url, response_list=[ - dict(headers=CONTENT_TYPE_PAOS_HEADER, - content=utils.make_oneline(saml2_fixtures.SP_SOAP_RESPONSE)), - dict(headers={'X-Subject-Token': token_id}, json=token) - ]) - - authm = self.requests_mock.post(self.TEST_IDP_URL, - content=saml2_fixtures.SAML2_ASSERTION) - - self.requests_mock.post( - self.TEST_CONSUMER_URL, - status_code=302, - headers={'Location': self.sp_url()}) - - auth_ref = self.get_plugin().get_auth_ref(self.session) - - self.assertEqual(token_id, auth_ref.auth_token) - - self.assertEqual(self.calls, [self.default_sp_url, - self.TEST_IDP_URL, - self.TEST_CONSUMER_URL, - self.default_sp_url]) - - self.assertEqual(self.basic_header(), - authm.last_request.headers['Authorization']) - - authn_request = self.requests_mock.request_history[1].text - self.assertThat(saml2_fixtures.AUTHN_REQUEST, - matchers.XMLEquals(authn_request)) - - def test_consumer_mismatch_error_workflow(self): - consumer1 = 'http://keystone.test/Shibboleth.sso/SAML2/ECP' - consumer2 = 'http://consumer2/Shibboleth.sso/SAML2/ECP' - - soap_response = saml2_fixtures.soap_response(consumer=consumer1) - saml_assertion = saml2_fixtures.saml_assertion(destination=consumer2) - - self.requests_mock.get(self.default_sp_url, - headers=CONTENT_TYPE_PAOS_HEADER, - content=soap_response) - - self.requests_mock.post(self.TEST_IDP_URL, content=saml_assertion) - - # receive the SAML error, body unchecked - saml_error = self.requests_mock.post(consumer1) - - self.assertRaises(exceptions.AuthorizationFailure, - self.get_plugin().get_auth_ref, - self.session) - - self.assertTrue(saml_error.called) - - def test_initial_sp_call_invalid_response(self): - """Send initial SP HTTP request and receive wrong server response.""" - self.requests_mock.get(self.default_sp_url, - headers=CONTENT_TYPE_PAOS_HEADER, - text='NON XML RESPONSE') - - self.assertRaises(exceptions.AuthorizationFailure, - self.get_plugin().get_auth_ref, - self.session) - - self.assertEqual(self.calls, [self.default_sp_url]) diff --git a/keystoneauth1/tests/unit/extras/saml2/utils.py b/keystoneauth1/tests/unit/extras/saml2/utils.py deleted file mode 100644 index d338c7e..0000000 --- a/keystoneauth1/tests/unit/extras/saml2/utils.py +++ /dev/null @@ -1,39 +0,0 @@ -# Licensed under the Apache License, Version 2.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 lxml import etree - -from keystoneauth1 import session -from keystoneauth1.tests.unit import utils - -ROOTDIR = os.path.dirname(os.path.abspath(__file__)) -XMLDIR = os.path.join(ROOTDIR, 'examples', 'xml/') - - -def make_oneline(s): - return etree.tostring(etree.XML(s)).replace(b'\n', b'') - - -def _load_xml(filename): - with open(XMLDIR + filename, 'rb') as f: - return f.read() - - -class TestCase(utils.TestCase): - - TEST_URL = 'https://keystone:5000/v3' - - def setUp(self): - super(TestCase, self).setUp() - self.session = session.Session() diff --git a/keystoneauth1/tests/unit/identity/__init__.py b/keystoneauth1/tests/unit/identity/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/keystoneauth1/tests/unit/identity/test_access.py b/keystoneauth1/tests/unit/identity/test_access.py deleted file mode 100644 index 15eaba3..0000000 --- a/keystoneauth1/tests/unit/identity/test_access.py +++ /dev/null @@ -1,76 +0,0 @@ -# Licensed under the Apache License, Version 2.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 uuid - -from keystoneauth1 import access -from keystoneauth1 import fixture -from keystoneauth1.identity import access as access_plugin -from keystoneauth1 import plugin -from keystoneauth1 import session -from keystoneauth1.tests.unit import utils - - -class AccessInfoPluginTests(utils.TestCase): - - def setUp(self): - super(AccessInfoPluginTests, self).setUp() - self.session = session.Session() - self.auth_token = uuid.uuid4().hex - - def _plugin(self, **kwargs): - token = fixture.V3Token() - s = token.add_service('identity') - s.add_standard_endpoints(public=self.TEST_ROOT_URL) - - auth_ref = access.create(body=token, auth_token=self.auth_token) - return access_plugin.AccessInfoPlugin(auth_ref, **kwargs) - - def test_auth_ref(self): - plugin_obj = self._plugin() - self.assertEqual(self.TEST_ROOT_URL, - plugin_obj.get_endpoint(self.session, - service_type='identity', - interface='public')) - self.assertEqual(self.auth_token, plugin_obj.get_token(session)) - - def test_auth_url(self): - auth_url = 'http://keystone.test.url' - obj = self._plugin(auth_url=auth_url) - - self.assertEqual(auth_url, - obj.get_endpoint(self.session, - interface=plugin.AUTH_INTERFACE)) - - def test_invalidate(self): - plugin = self._plugin() - auth_ref = plugin.auth_ref - - self.assertIsInstance(auth_ref, access.AccessInfo) - self.assertFalse(plugin.invalidate()) - self.assertIs(auth_ref, plugin.auth_ref) - - def test_project_auth_properties(self): - plugin = self._plugin() - auth_ref = plugin.auth_ref - - self.assertIsNone(auth_ref.project_domain_id) - self.assertIsNone(auth_ref.project_domain_name) - self.assertIsNone(auth_ref.project_id) - self.assertIsNone(auth_ref.project_name) - - def test_domain_auth_properties(self): - plugin = self._plugin() - auth_ref = plugin.auth_ref - - self.assertIsNone(auth_ref.domain_id) - self.assertIsNone(auth_ref.domain_name) diff --git a/keystoneauth1/tests/unit/identity/test_identity_common.py b/keystoneauth1/tests/unit/identity/test_identity_common.py deleted file mode 100644 index fb7e4ef..0000000 --- a/keystoneauth1/tests/unit/identity/test_identity_common.py +++ /dev/null @@ -1,1681 +0,0 @@ -# Licensed under the Apache License, Version 2.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 abc -import collections -import uuid - -import six -from six.moves import urllib - -from keystoneauth1 import _utils -from keystoneauth1 import access -from keystoneauth1 import discover -from keystoneauth1 import exceptions -from keystoneauth1 import fixture -from keystoneauth1 import identity -from keystoneauth1 import plugin -from keystoneauth1 import session -from keystoneauth1.tests.unit import utils - -_Endpoints = collections.namedtuple( - 'ServiceVersion', - 'public, internal, admin') - -_ServiceVersion = collections.namedtuple( - 'ServiceVersion', - 'discovery, service') - - -class FakeServiceEndpoints(object): - def __init__(self, base_url, versions=None, project_id=None, **kwargs): - self.base_url = base_url - self._interfaces = {} - for interface in ('public', 'internal', 'admin'): - if interface in kwargs and not kwargs[interface]: - self._interfaces[interface] = False - else: - self._interfaces[interface] = True - - self.versions = {} - self.unversioned = self._make_urls() - if not versions: - self.catalog = self.unversioned - else: - self.catalog = self._make_urls(versions[0], project_id) - for version in versions: - self.versions[version] = _ServiceVersion( - self._make_urls(version), - self._make_urls(version, project_id), - ) - - def _make_urls(self, *parts): - return _Endpoints( - self._make_url('public', *parts), - self._make_url('internal', *parts), - self._make_url('admin', *parts), - ) - - def _make_url(self, interface, *parts): - if not self._interfaces[interface]: - return None - url = urllib.parse.urljoin(self.base_url + '/', interface) - for part in parts: - if part: - url = urllib.parse.urljoin(url + '/', part) - return url - - -@six.add_metaclass(abc.ABCMeta) -class CommonIdentityTests(object): - - PROJECT_ID = uuid.uuid4().hex - TEST_ROOT_URL = 'http://127.0.0.1:5000/' - TEST_ROOT_ADMIN_URL = 'http://127.0.0.1:35357/' - - TEST_COMPUTE_BASE = 'https://compute.example.com' - TEST_COMPUTE_PUBLIC = TEST_COMPUTE_BASE + '/nova/public' - TEST_COMPUTE_INTERNAL = TEST_COMPUTE_BASE + '/nova/internal' - TEST_COMPUTE_ADMIN = TEST_COMPUTE_BASE + '/nova/admin' - - TEST_VOLUME = FakeServiceEndpoints( - base_url='https://block-storage.example.com', - versions=['v3', 'v2'], project_id=PROJECT_ID) - - TEST_BAREMETAL_BASE = 'https://baremetal.example.com' - TEST_BAREMETAL_INTERNAL = TEST_BAREMETAL_BASE + '/internal' - - TEST_PASS = uuid.uuid4().hex - - def setUp(self): - super(CommonIdentityTests, self).setUp() - - self.TEST_URL = '%s%s' % (self.TEST_ROOT_URL, self.version) - self.TEST_ADMIN_URL = '%s%s' % (self.TEST_ROOT_ADMIN_URL, self.version) - self.TEST_DISCOVERY = fixture.DiscoveryList(href=self.TEST_ROOT_URL) - - self.stub_auth_data() - - @abc.abstractmethod - def create_auth_plugin(self, **kwargs): - """Create an auth plugin that makes sense for the auth data. - - It doesn't really matter what auth mechanism is used but it should be - appropriate to the API version. - """ - - @abc.abstractmethod - def get_auth_data(self, **kwargs): - """Return fake authentication data. - - This should register a valid token response and ensure that the compute - endpoints are set to TEST_COMPUTE_PUBLIC, _INTERNAL and _ADMIN. - """ - - def stub_auth_data(self, **kwargs): - token = self.get_auth_data(**kwargs) - self.user_id = token.user_id - - try: - self.project_id = token.project_id - except AttributeError: - self.project_id = token.tenant_id - - self.stub_auth(json=token) - - @abc.abstractproperty - def version(self): - """The API version being tested.""" - - def test_discovering(self): - self.stub_url('GET', [], - base_url=self.TEST_COMPUTE_ADMIN, - json=self.TEST_DISCOVERY) - - body = 'SUCCESS' - - # which gives our sample values - self.stub_url('GET', ['path'], text=body) - - a = self.create_auth_plugin() - s = session.Session(auth=a) - - resp = s.get('/path', endpoint_filter={'service_type': 'compute', - 'interface': 'admin', - 'version': self.version}) - - self.assertEqual(200, resp.status_code) - self.assertEqual(body, resp.text) - - new_body = 'SC SUCCESS' - # if we don't specify a version, we use the URL from the SC - self.stub_url('GET', ['path'], - base_url=self.TEST_COMPUTE_ADMIN, - text=new_body) - - resp = s.get('/path', endpoint_filter={'service_type': 'compute', - 'interface': 'admin'}) - - self.assertEqual(200, resp.status_code) - self.assertEqual(new_body, resp.text) - - def test_discovery_uses_provided_session_cache(self): - # register responses such that if the discovery URL is hit more than - # once then the response will be invalid and not point to COMPUTE_ADMIN - resps = [{'json': self.TEST_DISCOVERY}, {'status_code': 500}] - self.requests_mock.get(self.TEST_COMPUTE_ADMIN, resps) - - body = 'SUCCESS' - self.stub_url('GET', ['path'], text=body) - - cache = {} - # now either of the two plugins I use, it should not cause a second - # request to the discovery url. - s = session.Session(discovery_cache=cache) - a = self.create_auth_plugin() - b = self.create_auth_plugin() - - for auth in (a, b): - resp = s.get('/path', - auth=auth, - endpoint_filter={'service_type': 'compute', - 'interface': 'admin', - 'version': self.version}) - - self.assertEqual(200, resp.status_code) - self.assertEqual(body, resp.text) - self.assertIn(self.TEST_COMPUTE_ADMIN, cache.keys()) - - def test_discovery_uses_session_cache(self): - # register responses such that if the discovery URL is hit more than - # once then the response will be invalid and not point to COMPUTE_ADMIN - resps = [{'json': self.TEST_DISCOVERY}, {'status_code': 500}] - self.requests_mock.get(self.TEST_COMPUTE_ADMIN, resps) - - body = 'SUCCESS' - self.stub_url('GET', ['path'], text=body) - - filter = {'service_type': 'compute', 'interface': 'admin', - 'version': self.version} - - # create a session and call the endpoint, causing its cache to be set - sess = session.Session() - sess.get('/path', auth=self.create_auth_plugin(), - endpoint_filter=filter) - self.assertIn(self.TEST_COMPUTE_ADMIN, sess._discovery_cache.keys()) - - # now either of the two plugins I use, it should not cause a second - # request to the discovery url. - a = self.create_auth_plugin() - b = self.create_auth_plugin() - - for auth in (a, b): - resp = sess.get('/path', auth=auth, endpoint_filter=filter) - self.assertEqual(200, resp.status_code) - self.assertEqual(body, resp.text) - - def test_discovery_uses_plugin_cache(self): - # register responses such that if the discovery URL is hit more than - # once then the response will be invalid and not point to COMPUTE_ADMIN - resps = [{'json': self.TEST_DISCOVERY}, {'status_code': 500}] - self.requests_mock.get(self.TEST_COMPUTE_ADMIN, resps) - - body = 'SUCCESS' - self.stub_url('GET', ['path'], text=body) - - # now either of the two sessions I use, it should not cause a second - # request to the discovery url. Calling discovery directly should also - # not cause an additional request. - sa = session.Session() - sb = session.Session() - auth = self.create_auth_plugin() - - for sess in (sa, sb): - resp = sess.get('/path', - auth=auth, - endpoint_filter={'service_type': 'compute', - 'interface': 'admin', - 'version': self.version}) - - self.assertEqual(200, resp.status_code) - self.assertEqual(body, resp.text) - - def test_discovery_uses_session_plugin_cache(self): - # register responses such that if the discovery URL is hit more than - # once then the response will be invalid and not point to COMPUTE_ADMIN - resps = [{'json': self.TEST_DISCOVERY}, {'status_code': 500}] - self.requests_mock.get(self.TEST_COMPUTE_ADMIN, resps) - - body = 'SUCCESS' - self.stub_url('GET', ['path'], text=body) - - filter = {'service_type': 'compute', 'interface': 'admin', - 'version': self.version} - - # create a plugin and call the endpoint, causing its cache to be set - plugin = self.create_auth_plugin() - session.Session().get('/path', auth=plugin, endpoint_filter=filter) - self.assertIn(self.TEST_COMPUTE_ADMIN, plugin._discovery_cache.keys()) - - # with the plugin in the session, no more calls to the discovery URL - sess = session.Session(auth=plugin) - for auth in (plugin, self.create_auth_plugin()): - resp = sess.get('/path', auth=auth, endpoint_filter=filter) - self.assertEqual(200, resp.status_code) - self.assertEqual(body, resp.text) - - def test_direct_discovery_provided_plugin_cache(self): - # register responses such that if the discovery URL is hit more than - # once then the response will be invalid and not point to COMPUTE_ADMIN - resps = [{'json': self.TEST_DISCOVERY}, {'status_code': 500}] - self.requests_mock.get(self.TEST_COMPUTE_ADMIN, resps) - - # now either of the two sessions I use, it should not cause a second - # request to the discovery url. Calling discovery directly should also - # not cause an additional request. - sa = session.Session() - sb = session.Session() - discovery_cache = {} - - expected_url = urllib.parse.urljoin(self.TEST_ROOT_URL, '/v2.0') - for sess in (sa, sb): - - disc = discover.get_discovery( - sess, self.TEST_COMPUTE_ADMIN, cache=discovery_cache) - url = disc.url_for(('2', '0')) - - self.assertEqual(expected_url, url) - - self.assertIn(self.TEST_COMPUTE_ADMIN, discovery_cache.keys()) - - def test_discovering_with_no_data(self): - # which returns discovery information pointing to TEST_URL but there is - # no data there. - self.stub_url('GET', [], - base_url=self.TEST_COMPUTE_ADMIN, - status_code=400) - - # so the url that will be used is the same TEST_COMPUTE_ADMIN - body = 'SUCCESS' - self.stub_url('GET', ['path'], base_url=self.TEST_COMPUTE_ADMIN, - text=body, status_code=200) - - a = self.create_auth_plugin() - s = session.Session(auth=a) - - resp = s.get('/path', endpoint_filter={'service_type': 'compute', - 'interface': 'admin', - 'version': self.version}) - - self.assertEqual(200, resp.status_code) - self.assertEqual(body, resp.text) - - def test_direct_discovering_with_no_data(self): - # returns discovery information pointing to TEST_URL but there is - # no data there. - self.stub_url('GET', [], - base_url=self.TEST_COMPUTE_ADMIN, - status_code=400) - - a = self.create_auth_plugin() - s = session.Session(auth=a) - - # A direct call for discovery should fail - self.assertRaises(exceptions.BadRequest, - discover.get_discovery, s, self.TEST_COMPUTE_ADMIN) - - def test_discovering_with_relative_link(self): - # need to construct list this way for relative - disc = fixture.DiscoveryList(v2=False, v3=False) - disc.add_v2('v2.0') - disc.add_v3('v3') - - self.stub_url('GET', [], base_url=self.TEST_COMPUTE_ADMIN, json=disc) - - a = self.create_auth_plugin() - s = session.Session(auth=a) - - endpoint_v2 = s.get_endpoint(service_type='compute', - interface='admin', - version=(2, 0)) - - endpoint_v3 = s.get_endpoint(service_type='compute', - interface='admin', - version=(3, 0)) - - self.assertEqual(self.TEST_COMPUTE_ADMIN + '/v2.0', endpoint_v2) - self.assertEqual(self.TEST_COMPUTE_ADMIN + '/v3', endpoint_v3) - - def test_direct_discovering(self): - v2_compute = self.TEST_COMPUTE_ADMIN + '/v2.0' - v3_compute = self.TEST_COMPUTE_ADMIN + '/v3' - - disc = fixture.DiscoveryList(v2=False, v3=False) - disc.add_v2(v2_compute) - disc.add_v3(v3_compute) - - self.stub_url('GET', [], base_url=self.TEST_COMPUTE_ADMIN, json=disc) - - a = self.create_auth_plugin() - s = session.Session(auth=a) - - catalog_url = s.get_endpoint( - service_type='compute', interface='admin') - disc = discover.get_discovery(s, catalog_url) - - url_v2 = disc.url_for(('2', '0')) - url_v3 = disc.url_for(('3', '0')) - - self.assertEqual(v2_compute, url_v2) - self.assertEqual(v3_compute, url_v3) - - # Verify that passing strings and not tuples works - url_v2 = disc.url_for('2.0') - url_v3 = disc.url_for('3.0') - - self.assertEqual(v2_compute, url_v2) - self.assertEqual(v3_compute, url_v3) - - def test_direct_discovering_with_relative_link(self): - # need to construct list this way for relative - disc = fixture.DiscoveryList(v2=False, v3=False) - disc.add_v2('v2.0') - disc.add_v3('v3') - - self.stub_url('GET', [], base_url=self.TEST_COMPUTE_ADMIN, json=disc) - - a = self.create_auth_plugin() - s = session.Session(auth=a) - - catalog_url = s.get_endpoint( - service_type='compute', interface='admin') - disc = discover.get_discovery(s, catalog_url) - - url_v2 = disc.url_for(('2', '0')) - url_v3 = disc.url_for(('3', '0')) - - self.assertEqual(self.TEST_COMPUTE_ADMIN + '/v2.0', url_v2) - self.assertEqual(self.TEST_COMPUTE_ADMIN + '/v3', url_v3) - - # Verify that passing strings and not tuples works - url_v2 = disc.url_for('2.0') - url_v3 = disc.url_for('3.0') - - self.assertEqual(self.TEST_COMPUTE_ADMIN + '/v2.0', url_v2) - self.assertEqual(self.TEST_COMPUTE_ADMIN + '/v3', url_v3) - - def test_discovering_with_relative_anchored_link(self): - # need to construct list this way for relative - disc = fixture.DiscoveryList(v2=False, v3=False) - disc.add_v2('/v2.0') - disc.add_v3('/v3') - - self.stub_url('GET', [], base_url=self.TEST_COMPUTE_ADMIN, json=disc) - - a = self.create_auth_plugin() - s = session.Session(auth=a) - - endpoint_v2 = s.get_endpoint(service_type='compute', - interface='admin', - version=(2, 0)) - - endpoint_v3 = s.get_endpoint(service_type='compute', - interface='admin', - version=(3, 0)) - - # by the nature of urljoin a relative link with a /path gets joined - # back to the root. - self.assertEqual(self.TEST_COMPUTE_BASE + '/v2.0', endpoint_v2) - self.assertEqual(self.TEST_COMPUTE_BASE + '/v3', endpoint_v3) - - def test_discovering_with_protocol_relative(self): - # strip up to and including the : leaving //host/path - path = self.TEST_COMPUTE_ADMIN[self.TEST_COMPUTE_ADMIN.find(':') + 1:] - - disc = fixture.DiscoveryList(v2=False, v3=False) - disc.add_v2(path + '/v2.0') - disc.add_v3(path + '/v3') - - self.stub_url('GET', [], base_url=self.TEST_COMPUTE_ADMIN, json=disc) - - a = self.create_auth_plugin() - s = session.Session(auth=a) - - endpoint_v2 = s.get_endpoint(service_type='compute', - interface='admin', - version=(2, 0)) - - endpoint_v3 = s.get_endpoint(service_type='compute', - interface='admin', - version=(3, 0)) - - # ensures that the http is carried over from the lookup url - self.assertEqual(self.TEST_COMPUTE_ADMIN + '/v2.0', endpoint_v2) - self.assertEqual(self.TEST_COMPUTE_ADMIN + '/v3', endpoint_v3) - - def test_discovering_when_version_missing(self): - # need to construct list this way for relative - disc = fixture.DiscoveryList(v2=False, v3=False) - disc.add_v2('v2.0') - - self.stub_url('GET', [], base_url=self.TEST_COMPUTE_ADMIN, json=disc) - - a = self.create_auth_plugin() - s = session.Session(auth=a) - - endpoint_v2 = s.get_endpoint(service_type='compute', - interface='admin', - version=(2, 0)) - - endpoint_v3 = s.get_endpoint(service_type='compute', - interface='admin', - version=(3, 0)) - - self.assertEqual(self.TEST_COMPUTE_ADMIN + '/v2.0', endpoint_v2) - self.assertIsNone(endpoint_v3) - - def test_endpoint_data_no_version(self): - path = self.TEST_COMPUTE_ADMIN[self.TEST_COMPUTE_ADMIN.find(':') + 1:] - - disc = fixture.DiscoveryList(v2=False, v3=False) - disc.add_v2(path + '/v2.0') - disc.add_v3(path + '/v3') - - self.stub_url('GET', [], base_url=self.TEST_COMPUTE_ADMIN, json=disc) - a = self.create_auth_plugin() - s = session.Session(auth=a) - - data = a.get_endpoint_data(session=s, - service_type='compute', - interface='admin') - - self.assertEqual(self.TEST_COMPUTE_ADMIN + '/v3', data.url) - - def test_endpoint_data_no_version_no_discovery(self): - a = self.create_auth_plugin() - s = session.Session(auth=a) - - data = a.get_endpoint_data(session=s, - service_type='compute', - interface='admin', - discover_versions=False) - - self.assertEqual(self.TEST_COMPUTE_ADMIN, data.url) - - def test_endpoint_no_version(self): - a = self.create_auth_plugin() - s = session.Session(auth=a) - - data = a.get_endpoint(session=s, - service_type='compute', - interface='admin') - - self.assertEqual(self.TEST_COMPUTE_ADMIN, data) - - def test_endpoint_data_relative_version(self): - # need to construct list this way for relative - disc = fixture.DiscoveryList(v2=False, v3=False) - disc.add_v2('v2.0') - disc.add_v3('v3') - - self.stub_url('GET', [], base_url=self.TEST_COMPUTE_ADMIN, json=disc) - - a = self.create_auth_plugin() - s = session.Session(auth=a) - - data_v2 = a.get_endpoint_data(session=s, - service_type='compute', - interface='admin', - min_version=(2, 0), - max_version=(2, discover.LATEST)) - data_v3 = a.get_endpoint_data(session=s, - service_type='compute', - interface='admin', - min_version=(3, 0), - max_version=(3, discover.LATEST)) - - self.assertEqual(self.TEST_COMPUTE_ADMIN + '/v2.0', data_v2.url) - self.assertEqual(self.TEST_COMPUTE_ADMIN + '/v3', data_v3.url) - - def test_get_versioned_data(self): - v2_compute = self.TEST_COMPUTE_ADMIN + '/v2.0' - v3_compute = self.TEST_COMPUTE_ADMIN + '/v3' - - disc = fixture.DiscoveryList(v2=False, v3=False) - disc.add_v2(v2_compute) - disc.add_v3(v3_compute) - - # Make sure that we don't do more than one discovery call - # register responses such that if the discovery URL is hit more than - # once then the response will be invalid and not point to COMPUTE_ADMIN - resps = [{'json': disc}, {'status_code': 500}] - self.requests_mock.get(self.TEST_COMPUTE_ADMIN, resps) - - a = self.create_auth_plugin() - s = session.Session(auth=a) - - data = a.get_endpoint_data(session=s, - service_type='compute', - interface='admin') - self.assertEqual(v3_compute, data.url) - - v2_data = data.get_versioned_data(s, min_version='2.0', - max_version='2.latest') - self.assertEqual(v2_compute, v2_data.url) - self.assertEqual(v2_compute, v2_data.service_url) - self.assertEqual(self.TEST_COMPUTE_ADMIN, v2_data.catalog_url) - - # Variants that all return v3 data - for vkwargs in (dict(min_version='3.0', max_version='3.latest'), - # min/max spans major versions - dict(min_version='2.0', max_version='3.latest'), - # latest major max - dict(min_version='2.0', max_version='latest'), - # implicit max - dict(min_version='2.0'), - # implicit min/max - dict()): - v3_data = data.get_versioned_data(s, **vkwargs) - self.assertEqual(v3_compute, v3_data.url) - self.assertEqual(v3_compute, v3_data.service_url) - self.assertEqual(self.TEST_COMPUTE_ADMIN, v3_data.catalog_url) - - def test_interface_list(self): - - a = self.create_auth_plugin() - s = session.Session(auth=a) - - ep = s.get_endpoint(service_type='baremetal', - interface=['internal', 'public']) - self.assertEqual(ep, self.TEST_BAREMETAL_INTERNAL) - - ep = s.get_endpoint(service_type='baremetal', - interface=['public', 'internal']) - self.assertEqual(ep, self.TEST_BAREMETAL_INTERNAL) - - ep = s.get_endpoint(service_type='compute', - interface=['internal', 'public']) - self.assertEqual(ep, self.TEST_COMPUTE_INTERNAL) - - ep = s.get_endpoint(service_type='compute', - interface=['public', 'internal']) - self.assertEqual(ep, self.TEST_COMPUTE_PUBLIC) - - def test_get_versioned_data_volume_project_id(self): - - disc = fixture.DiscoveryList(v2=False, v3=False) - - # The version discovery dict will not have a project_id - disc.add_nova_microversion( - href=self.TEST_VOLUME.versions['v3'].discovery.public, - id='v3.0', status='CURRENT', - min_version='3.0', version='3.20') - - # Adding a v2 version to a service named volumev3 is not - # an error. The service itself is cinder and has more than - # one major version. - disc.add_nova_microversion( - href=self.TEST_VOLUME.versions['v2'].discovery.public, - id='v2.0', status='SUPPORTED') - - a = self.create_auth_plugin() - s = session.Session(auth=a) - - # volume endpoint ends in v3, we should not make an API call - endpoint = a.get_endpoint(session=s, - service_type='volumev3', - interface='public', - version='3.0') - self.assertEqual(self.TEST_VOLUME.catalog.public, endpoint) - - resps = [{'json': disc}, {'status_code': 500}] - - # We should only try to fetch the versioned discovery url once - self.requests_mock.get( - self.TEST_VOLUME.versions['v3'].discovery.public, resps) - - data = a.get_endpoint_data(session=s, - service_type='volumev3', - interface='public') - self.assertEqual(self.TEST_VOLUME.versions['v3'].service.public, - data.url) - - v3_data = data.get_versioned_data( - s, min_version='3.0', max_version='3.latest', - project_id=self.project_id) - - self.assertEqual(self.TEST_VOLUME.versions['v3'].service.public, - v3_data.url) - self.assertEqual(self.TEST_VOLUME.catalog.public, v3_data.catalog_url) - self.assertEqual((3, 0), v3_data.min_microversion) - self.assertEqual((3, 20), v3_data.max_microversion) - self.assertEqual(self.TEST_VOLUME.versions['v3'].service.public, - v3_data.service_url) - - # Because of the v3 optimization before, requesting v2 should now go - # find the unversioned endpoint - self.requests_mock.get(self.TEST_VOLUME.unversioned.public, resps) - v2_data = data.get_versioned_data( - s, min_version='2.0', max_version='2.latest', - project_id=self.project_id) - - # Even though we never requested volumev2 from the catalog, we should - # wind up re-constructing it via version discovery and re-appending - # the project_id to the URL - self.assertEqual(self.TEST_VOLUME.versions['v2'].service.public, - v2_data.url) - self.assertEqual(self.TEST_VOLUME.versions['v2'].service.public, - v2_data.service_url) - self.assertEqual(self.TEST_VOLUME.catalog.public, v2_data.catalog_url) - self.assertEqual(None, v2_data.min_microversion) - self.assertEqual(None, v2_data.max_microversion) - - def test_get_versioned_data_volume_project_id_unversioned_first(self): - - disc = fixture.DiscoveryList(v2=False, v3=False) - - # The version discovery dict will not have a project_id - disc.add_nova_microversion( - href=self.TEST_VOLUME.versions['v3'].discovery.public, - id='v3.0', status='CURRENT', - min_version='3.0', version='3.20') - - # Adding a v2 version to a service named volumev3 is not - # an error. The service itself is cinder and has more than - # one major version. - disc.add_nova_microversion( - href=self.TEST_VOLUME.versions['v2'].discovery.public, - id='v2.0', status='SUPPORTED') - - a = self.create_auth_plugin() - s = session.Session(auth=a) - - # cinder endpoint ends in v3, we should not make an API call - endpoint = a.get_endpoint(session=s, - service_type='volumev3', - interface='public', - version='3.0') - self.assertEqual(self.TEST_VOLUME.catalog.public, endpoint) - - resps = [{'json': disc}, {'status_code': 500}] - - # We should only try to fetch the unversioned non-project_id url once - # Because the catalog has the versioned endpoint but we constructed - # an unversioned endpoint, the url needs to have a trailing / - self.requests_mock.get( - self.TEST_VOLUME.unversioned.public + '/', resps) - - # Fetch v2.0 first - since that doesn't match endpoint optimization, - # it should fetch the unversioned endpoint - v2_data = s.get_endpoint_data(service_type='volumev3', - interface='public', - min_version='2.0', - max_version='2.latest', - project_id=self.project_id) - - # Even though we never requested volumev2 from the catalog, we should - # wind up re-constructing it via version discovery and re-appending - # the project_id to the URL - self.assertEqual(self.TEST_VOLUME.versions['v2'].service.public, - v2_data.url) - self.assertEqual(self.TEST_VOLUME.versions['v2'].service.public, - v2_data.service_url) - self.assertEqual(self.TEST_VOLUME.catalog.public, v2_data.catalog_url) - self.assertEqual(None, v2_data.min_microversion) - self.assertEqual(None, v2_data.max_microversion) - - # Since we fetched from the unversioned endpoint to satisfy the - # request for v2, we should have all the relevant data cached in the - # discovery object - and should not fetch anything new. - v3_data = v2_data.get_versioned_data( - s, min_version='3.0', max_version='3.latest', - project_id=self.project_id) - - self.assertEqual(self.TEST_VOLUME.versions['v3'].service.public, - v3_data.url) - self.assertEqual(self.TEST_VOLUME.catalog.public, v3_data.catalog_url) - self.assertEqual((3, 0), v3_data.min_microversion) - self.assertEqual((3, 20), v3_data.max_microversion) - self.assertEqual(self.TEST_VOLUME.versions['v3'].service.public, - v3_data.service_url) - - def test_asking_for_auth_endpoint_ignores_checks(self): - a = self.create_auth_plugin() - s = session.Session(auth=a) - - auth_url = s.get_endpoint(service_type='compute', - interface=plugin.AUTH_INTERFACE) - - self.assertEqual(self.TEST_URL, auth_url) - - def _create_expired_auth_plugin(self, **kwargs): - expires = _utils.before_utcnow(minutes=20) - expired_token = self.get_auth_data(expires=expires) - expired_auth_ref = access.create(body=expired_token) - - a = self.create_auth_plugin(**kwargs) - a.auth_ref = expired_auth_ref - return a - - def test_reauthenticate(self): - a = self._create_expired_auth_plugin() - expired_auth_ref = a.auth_ref - s = session.Session(auth=a) - self.assertIsNot(expired_auth_ref, a.get_access(s)) - - def test_no_reauthenticate(self): - a = self._create_expired_auth_plugin(reauthenticate=False) - expired_auth_ref = a.auth_ref - s = session.Session(auth=a) - self.assertIs(expired_auth_ref, a.get_access(s)) - - def test_invalidate(self): - a = self.create_auth_plugin() - s = session.Session(auth=a) - - # trigger token fetching - s.get_auth_headers() - - self.assertTrue(a.auth_ref) - self.assertTrue(a.invalidate()) - self.assertIsNone(a.auth_ref) - self.assertFalse(a.invalidate()) - - def test_get_auth_properties(self): - a = self.create_auth_plugin() - s = session.Session() - - self.assertEqual(self.user_id, a.get_user_id(s)) - self.assertEqual(self.project_id, a.get_project_id(s)) - - def assertAccessInfoEqual(self, a, b): - self.assertEqual(a.auth_token, b.auth_token) - self.assertEqual(a._data, b._data) - - def test_check_cache_id_match(self): - a = self.create_auth_plugin() - b = self.create_auth_plugin() - - self.assertIsNot(a, b) - self.assertIsNone(a.get_auth_state()) - self.assertIsNone(b.get_auth_state()) - - a_id = a.get_cache_id() - b_id = b.get_cache_id() - - self.assertIsNotNone(a_id) - self.assertIsNotNone(b_id) - - self.assertEqual(a_id, b_id) - - def test_check_cache_id_no_match(self): - a = self.create_auth_plugin(project_id='a') - b = self.create_auth_plugin(project_id='b') - - self.assertIsNot(a, b) - self.assertIsNone(a.get_auth_state()) - self.assertIsNone(b.get_auth_state()) - - a_id = a.get_cache_id() - b_id = b.get_cache_id() - - self.assertIsNotNone(a_id) - self.assertIsNotNone(b_id) - - self.assertNotEqual(a_id, b_id) - - def test_get_set_auth_state(self): - a = self.create_auth_plugin() - b = self.create_auth_plugin() - - self.assertEqual(a.get_cache_id(), b.get_cache_id()) - - s = session.Session() - - a_token = a.get_token(s) - - self.assertEqual(1, self.requests_mock.call_count) - - auth_state = a.get_auth_state() - - self.assertIsNotNone(auth_state) - - b.set_auth_state(auth_state) - - b_token = b.get_token(s) - self.assertEqual(1, self.requests_mock.call_count) - - self.assertEqual(a_token, b_token) - self.assertAccessInfoEqual(a.auth_ref, b.auth_ref) - - def test_pathless_url(self): - disc = fixture.DiscoveryList(v2=False, v3=False) - url = 'http://path.less.url:1234' - disc.add_microversion(href=url, id='v2.1') - - self.stub_url('GET', base_url=url, status_code=200, json=disc) - - token = fixture.V2Token() - service = token.add_service('network') - service.add_endpoint(public=url, admin=url, internal=url) - - self.stub_url('POST', ['tokens'], base_url=url, json=token) - - v2_auth = identity.V2Password(url, username='u', password='p') - - sess = session.Session(auth=v2_auth) - - data = sess.get_endpoint_data(service_type='network') - - # Discovery ran and returned the URL - self.assertEqual(url, data.url) - - # Run with a project_id to ensure that path is covered - self.assertEqual( - 3, len(list(data._get_discovery_url_choices(project_id='42')))) - - -class V3(CommonIdentityTests, utils.TestCase): - - @property - def version(self): - return 'v3' - - def get_auth_data(self, **kwargs): - kwargs.setdefault('project_id', self.PROJECT_ID) - token = fixture.V3Token(**kwargs) - region = 'RegionOne' - - svc = token.add_service('identity') - svc.add_standard_endpoints(admin=self.TEST_ADMIN_URL, region=region) - - svc = token.add_service('compute') - svc.add_standard_endpoints(admin=self.TEST_COMPUTE_ADMIN, - public=self.TEST_COMPUTE_PUBLIC, - internal=self.TEST_COMPUTE_INTERNAL, - region=region) - - svc = token.add_service('volumev2') - svc.add_standard_endpoints( - admin=self.TEST_VOLUME.versions['v2'].service.admin, - public=self.TEST_VOLUME.versions['v2'].service.public, - internal=self.TEST_VOLUME.versions['v2'].service.internal, - region=region) - - svc = token.add_service('volumev3') - svc.add_standard_endpoints( - admin=self.TEST_VOLUME.versions['v3'].service.admin, - public=self.TEST_VOLUME.versions['v3'].service.public, - internal=self.TEST_VOLUME.versions['v3'].service.internal, - region=region) - - svc = token.add_service('baremetal') - svc.add_standard_endpoints( - internal=self.TEST_BAREMETAL_INTERNAL, - region=region) - - return token - - def stub_auth(self, subject_token=None, **kwargs): - if not subject_token: - subject_token = self.TEST_TOKEN - - kwargs.setdefault('headers', {})['X-Subject-Token'] = subject_token - self.stub_url('POST', ['auth', 'tokens'], **kwargs) - - def create_auth_plugin(self, **kwargs): - kwargs.setdefault('auth_url', self.TEST_URL) - kwargs.setdefault('username', self.TEST_USER) - kwargs.setdefault('password', self.TEST_PASS) - return identity.V3Password(**kwargs) - - -class V2(CommonIdentityTests, utils.TestCase): - - @property - def version(self): - return 'v2.0' - - def create_auth_plugin(self, **kwargs): - kwargs.setdefault('auth_url', self.TEST_URL) - kwargs.setdefault('username', self.TEST_USER) - kwargs.setdefault('password', self.TEST_PASS) - - try: - kwargs.setdefault('tenant_id', kwargs.pop('project_id')) - except KeyError: - pass - - try: - kwargs.setdefault('tenant_name', kwargs.pop('project_name')) - except KeyError: - pass - - return identity.V2Password(**kwargs) - - def get_auth_data(self, **kwargs): - kwargs.setdefault('tenant_id', self.PROJECT_ID) - token = fixture.V2Token(**kwargs) - region = 'RegionOne' - - svc = token.add_service('identity') - svc.add_endpoint(self.TEST_ADMIN_URL, region=region) - - svc = token.add_service('compute') - svc.add_endpoint(public=self.TEST_COMPUTE_PUBLIC, - internal=self.TEST_COMPUTE_INTERNAL, - admin=self.TEST_COMPUTE_ADMIN, - region=region) - - svc = token.add_service('volumev2') - svc.add_endpoint( - admin=self.TEST_VOLUME.versions['v2'].service.admin, - public=self.TEST_VOLUME.versions['v2'].service.public, - internal=self.TEST_VOLUME.versions['v2'].service.internal, - region=region) - - svc = token.add_service('volumev3') - svc.add_endpoint( - admin=self.TEST_VOLUME.versions['v3'].service.admin, - public=self.TEST_VOLUME.versions['v3'].service.public, - internal=self.TEST_VOLUME.versions['v3'].service.internal, - region=region) - - svc = token.add_service('baremetal') - svc.add_endpoint( - public=None, admin=None, - internal=self.TEST_BAREMETAL_INTERNAL, - region=region) - - return token - - def stub_auth(self, **kwargs): - self.stub_url('POST', ['tokens'], **kwargs) - - -class CatalogHackTests(utils.TestCase): - - TEST_URL = 'http://keystone.server:5000/v2.0' - OTHER_URL = 'http://other.server:5000/path' - - IDENTITY = 'identity' - - BASE_URL = 'http://keystone.server:5000/' - V2_URL = BASE_URL + 'v2.0' - V3_URL = BASE_URL + 'v3' - - def test_getting_endpoints(self): - disc = fixture.DiscoveryList(href=self.BASE_URL) - self.stub_url('GET', - ['/'], - base_url=self.BASE_URL, - json=disc) - - token = fixture.V2Token() - service = token.add_service(self.IDENTITY) - service.add_endpoint(public=self.V2_URL, - admin=self.V2_URL, - internal=self.V2_URL) - - self.stub_url('POST', - ['tokens'], - base_url=self.V2_URL, - json=token) - - v2_auth = identity.V2Password(self.V2_URL, - username=uuid.uuid4().hex, - password=uuid.uuid4().hex) - - sess = session.Session(auth=v2_auth) - - endpoint = sess.get_endpoint(service_type=self.IDENTITY, - interface='public', - version=(3, 0)) - - self.assertEqual(self.V3_URL, endpoint) - - def test_returns_original_when_discover_fails(self): - token = fixture.V2Token() - service = token.add_service(self.IDENTITY) - service.add_endpoint(public=self.V2_URL, - admin=self.V2_URL, - internal=self.V2_URL) - - self.stub_url('POST', - ['tokens'], - base_url=self.V2_URL, - json=token) - - self.stub_url('GET', [], base_url=self.BASE_URL, status_code=404) - self.stub_url('GET', [], base_url=self.V2_URL, status_code=404) - - v2_auth = identity.V2Password(self.V2_URL, - username=uuid.uuid4().hex, - password=uuid.uuid4().hex) - - sess = session.Session(auth=v2_auth) - - endpoint = sess.get_endpoint(service_type=self.IDENTITY, - interface='public', - version=(3, 0)) - - self.assertEqual(self.V2_URL, endpoint) - - def test_returns_original_skipping_discovery(self): - token = fixture.V2Token() - service = token.add_service(self.IDENTITY) - service.add_endpoint(public=self.V2_URL, - admin=self.V2_URL, - internal=self.V2_URL) - - self.stub_url('POST', - ['tokens'], - base_url=self.V2_URL, - json=token) - - v2_auth = identity.V2Password(self.V2_URL, - username=uuid.uuid4().hex, - password=uuid.uuid4().hex) - - sess = session.Session(auth=v2_auth) - - endpoint = sess.get_endpoint(service_type=self.IDENTITY, - interface='public', - skip_discovery=True, - version=(3, 0)) - - self.assertEqual(self.V2_URL, endpoint) - - def test_endpoint_override_skips_discovery(self): - token = fixture.V2Token() - service = token.add_service(self.IDENTITY) - service.add_endpoint(public=self.V2_URL, - admin=self.V2_URL, - internal=self.V2_URL) - - self.stub_url('POST', - ['tokens'], - base_url=self.V2_URL, - json=token) - - v2_auth = identity.V2Password(self.V2_URL, - username=uuid.uuid4().hex, - password=uuid.uuid4().hex) - - sess = session.Session(auth=v2_auth) - - endpoint = sess.get_endpoint(endpoint_override=self.OTHER_URL, - service_type=self.IDENTITY, - interface='public', - version=(3, 0)) - - self.assertEqual(self.OTHER_URL, endpoint) - - def test_endpoint_override_data_runs_discovery(self): - common_disc = fixture.DiscoveryList(v2=False, v3=False) - common_disc.add_microversion(href=self.OTHER_URL, id='v2.1', - min_version='2.1', max_version='2.35') - - common_m = self.stub_url('GET', - base_url=self.OTHER_URL, - status_code=200, - json=common_disc) - - token = fixture.V2Token() - service = token.add_service(self.IDENTITY) - service.add_endpoint(public=self.V2_URL, - admin=self.V2_URL, - internal=self.V2_URL) - - self.stub_url('POST', - ['tokens'], - base_url=self.V2_URL, - json=token) - - v2_auth = identity.V2Password(self.V2_URL, - username=uuid.uuid4().hex, - password=uuid.uuid4().hex) - - sess = session.Session(auth=v2_auth) - - data = sess.get_endpoint_data(endpoint_override=self.OTHER_URL, - service_type=self.IDENTITY, - interface='public', - min_version=(2, 0), - max_version=(2, discover.LATEST)) - - self.assertTrue(common_m.called) - self.assertEqual(self.OTHER_URL, data.url) - self.assertEqual((2, 1), data.min_microversion) - self.assertEqual((2, 35), data.max_microversion) - - def test_forcing_discovery(self): - v2_disc = fixture.V2Discovery(self.V2_URL) - common_disc = fixture.DiscoveryList(href=self.BASE_URL) - - v2_m = self.stub_url('GET', - ['v2.0'], - base_url=self.BASE_URL, - status_code=200, - json={'version': v2_disc}) - - common_m = self.stub_url('GET', - [], - base_url=self.BASE_URL, - status_code=300, - json=common_disc) - - token = fixture.V2Token() - service = token.add_service(self.IDENTITY) - service.add_endpoint(public=self.V2_URL, - admin=self.V2_URL, - internal=self.V2_URL) - - self.stub_url('POST', - ['tokens'], - base_url=self.V2_URL, - json=token) - - v2_auth = identity.V2Password(self.V2_URL, - username=uuid.uuid4().hex, - password=uuid.uuid4().hex) - - sess = session.Session(auth=v2_auth) - - # v2 auth with v2 url doesn't make any discovery calls. - self.assertFalse(v2_m.called) - self.assertFalse(common_m.called) - - data = sess.get_endpoint_data(service_type=self.IDENTITY, - discover_versions=True) - - # We should get the v2 document, but not the unversioned - self.assertTrue(v2_m.called) - self.assertFalse(common_m.called) - - # got v2 url - self.assertEqual(self.V2_URL, data.url) - - def test_forcing_discovery_list_returns_url(self): - common_disc = fixture.DiscoveryList(href=self.BASE_URL) - - # 2.0 doesn't usually return a list. This is testing that if - # the catalog url returns an endpoint that has a discovery document - # with more than one URL and that a different url would be returned - # by "return the latest" rules, that we get the info of the url from - # the catalog if we don't provide a version but do provide - # discover_versions - v2_m = self.stub_url('GET', - ['v2.0'], - base_url=self.BASE_URL, - status_code=200, - json=common_disc) - - token = fixture.V2Token() - service = token.add_service(self.IDENTITY) - service.add_endpoint(public=self.V2_URL, - admin=self.V2_URL, - internal=self.V2_URL) - - self.stub_url('POST', - ['tokens'], - base_url=self.V2_URL, - json=token) - - v2_auth = identity.V2Password(self.V2_URL, - username=uuid.uuid4().hex, - password=uuid.uuid4().hex) - - sess = session.Session(auth=v2_auth) - - # v2 auth with v2 url doesn't make any discovery calls. - self.assertFalse(v2_m.called) - - data = sess.get_endpoint_data(service_type=self.IDENTITY, - discover_versions=True) - - # We should make the one call - self.assertTrue(v2_m.called) - - # got v2 url - self.assertEqual(self.V2_URL, data.url) - - def test_latest_version_gets_latest_version(self): - common_disc = fixture.DiscoveryList(href=self.BASE_URL) - - # 2.0 doesn't usually return a list. But we're testing version matching - # rules, so it's nice to ensure that we don't fallback to something - v2_m = self.stub_url('GET', - base_url=self.BASE_URL, - status_code=200, - json=common_disc) - - token = fixture.V2Token() - service = token.add_service(self.IDENTITY) - service.add_endpoint(public=self.V2_URL, - admin=self.V2_URL, - internal=self.V2_URL) - - self.stub_url('POST', - ['tokens'], - base_url=self.V2_URL, - json=token) - - v2_auth = identity.V2Password(self.V2_URL, - username=uuid.uuid4().hex, - password=uuid.uuid4().hex) - - sess = session.Session(auth=v2_auth) - - # v2 auth with v2 url doesn't make any discovery calls. - self.assertFalse(v2_m.called) - - endpoint = sess.get_endpoint(service_type=self.IDENTITY, - version='latest') - - # We should make the one call - self.assertTrue(v2_m.called) - - # And get the v3 url - self.assertEqual(self.V3_URL, endpoint) - - # Make sure latest logic works for min and max version - endpoint = sess.get_endpoint(service_type=self.IDENTITY, - max_version='latest') - self.assertEqual(self.V3_URL, endpoint) - endpoint = sess.get_endpoint(service_type=self.IDENTITY, - min_version='latest') - self.assertEqual(self.V3_URL, endpoint) - endpoint = sess.get_endpoint(service_type=self.IDENTITY, - min_version='latest', - max_version='latest') - self.assertEqual(self.V3_URL, endpoint) - - self.assertRaises(ValueError, sess.get_endpoint, - service_type=self.IDENTITY, - min_version='latest', max_version='3.0') - - def test_version_range(self): - v2_disc = fixture.V2Discovery(self.V2_URL) - common_disc = fixture.DiscoveryList(href=self.BASE_URL) - - def stub_urls(): - v2_m = self.stub_url('GET', - ['v2.0'], - base_url=self.BASE_URL, - status_code=200, - json={'version': v2_disc}) - common_m = self.stub_url('GET', - base_url=self.BASE_URL, - status_code=200, - json=common_disc) - return v2_m, common_m - v2_m, common_m = stub_urls() - - token = fixture.V2Token() - service = token.add_service(self.IDENTITY) - service.add_endpoint(public=self.V2_URL, - admin=self.V2_URL, - internal=self.V2_URL) - - self.stub_url('POST', - ['tokens'], - base_url=self.V2_URL, - json=token) - - v2_auth = identity.V2Password(self.V2_URL, - username=uuid.uuid4().hex, - password=uuid.uuid4().hex) - - sess = session.Session(auth=v2_auth) - - # v2 auth with v2 url doesn't make any discovery calls. - self.assertFalse(v2_m.called) - - endpoint = sess.get_endpoint(service_type=self.IDENTITY, - min_version='2.0', max_version='3.0') - - # We should make the one call - self.assertFalse(v2_m.called) - self.assertTrue(common_m.called) - - # And get the v3 url - self.assertEqual(self.V3_URL, endpoint) - - v2_m, common_m = stub_urls() - endpoint = sess.get_endpoint(service_type=self.IDENTITY, - min_version='1', max_version='2') - - # We should make no calls - we peek in the cache - self.assertFalse(v2_m.called) - self.assertFalse(common_m.called) - - # And get the v2 url - self.assertEqual(self.V2_URL, endpoint) - - v2_m, common_m = stub_urls() - endpoint = sess.get_endpoint(service_type=self.IDENTITY, - min_version='4') - - # We should make no more calls - self.assertFalse(v2_m.called) - self.assertFalse(common_m.called) - - # And get no url - self.assertEqual(None, endpoint) - - v2_m, common_m = stub_urls() - endpoint = sess.get_endpoint(service_type=self.IDENTITY, - min_version='2') - - # We should make no more calls - self.assertFalse(v2_m.called) - self.assertFalse(common_m.called) - - # And get the v3 url - self.assertEqual(self.V3_URL, endpoint) - - v2_m, common_m = stub_urls() - self.assertRaises(ValueError, sess.get_endpoint, - service_type=self.IDENTITY, version=3, - min_version='2') - - # We should make no more calls - self.assertFalse(v2_m.called) - self.assertFalse(common_m.called) - - def test_get_endpoint_data(self): - common_disc = fixture.DiscoveryList(v2=False, v3=False) - common_disc.add_microversion(href=self.OTHER_URL, id='v2.1', - min_version='2.1', max_version='2.35') - - common_m = self.stub_url('GET', - base_url=self.OTHER_URL, - status_code=200, - json=common_disc) - - token = fixture.V2Token() - service = token.add_service('network') - service.add_endpoint(public=self.OTHER_URL, - admin=self.OTHER_URL, - internal=self.OTHER_URL) - - self.stub_url('POST', - ['tokens'], - base_url=self.V2_URL, - json=token) - - v2_auth = identity.V2Password(self.V2_URL, - username=uuid.uuid4().hex, - password=uuid.uuid4().hex) - - sess = session.Session(auth=v2_auth) - - # v2 auth with v2 url doesn't make any discovery calls. - self.assertFalse(common_m.called) - - data = sess.get_endpoint_data(service_type='network', - min_version='2.0', max_version='3.0') - - # We should make the one call - self.assertTrue(common_m.called) - - # And get the v3 url - self.assertEqual(self.OTHER_URL, data.url) - self.assertEqual((2, 1), data.min_microversion) - self.assertEqual((2, 35), data.max_microversion) - - def test_get_endpoint_data_compute(self): - common_disc = fixture.DiscoveryList(v2=False, v3=False) - common_disc.add_nova_microversion(href=self.OTHER_URL, id='v2.1', - min_version='2.1', version='2.35') - - common_m = self.stub_url('GET', - base_url=self.OTHER_URL, - status_code=200, - json=common_disc) - - token = fixture.V2Token() - service = token.add_service('compute') - service.add_endpoint(public=self.OTHER_URL, - admin=self.OTHER_URL, - internal=self.OTHER_URL) - - self.stub_url('POST', - ['tokens'], - base_url=self.V2_URL, - json=token) - - v2_auth = identity.V2Password(self.V2_URL, - username=uuid.uuid4().hex, - password=uuid.uuid4().hex) - - sess = session.Session(auth=v2_auth) - - # v2 auth with v2 url doesn't make any discovery calls. - self.assertFalse(common_m.called) - - data = sess.get_endpoint_data(service_type='compute', - min_version='2.0', max_version='3.0') - - # We should make the one call - self.assertTrue(common_m.called) - - # And get the v3 url - self.assertEqual(self.OTHER_URL, data.url) - self.assertEqual((2, 1), data.min_microversion) - self.assertEqual((2, 35), data.max_microversion) - - def test_getting_endpoints_on_auth_interface(self): - disc = fixture.DiscoveryList(href=self.BASE_URL) - self.stub_url('GET', - ['/'], - base_url=self.BASE_URL, - status_code=300, - json=disc) - - token = fixture.V2Token() - service = token.add_service(self.IDENTITY) - service.add_endpoint(public=self.V2_URL, - admin=self.V2_URL, - internal=self.V2_URL) - - self.stub_url('POST', - ['tokens'], - base_url=self.V2_URL, - json=token) - - v2_auth = identity.V2Password(self.V2_URL, - username=uuid.uuid4().hex, - password=uuid.uuid4().hex) - - sess = session.Session(auth=v2_auth) - - endpoint = sess.get_endpoint(interface=plugin.AUTH_INTERFACE, - version=(3, 0)) - - self.assertEqual(self.V3_URL, endpoint) - - def test_setting_no_discover_hack(self): - v2_disc = fixture.V2Discovery(self.V2_URL) - common_disc = fixture.DiscoveryList(href=self.BASE_URL) - - v2_m = self.stub_url('GET', - ['v2.0'], - base_url=self.BASE_URL, - status_code=200, - json=v2_disc) - - common_m = self.stub_url('GET', - [], - base_url=self.BASE_URL, - status_code=300, - json=common_disc) - - resp_text = uuid.uuid4().hex - - resp_m = self.stub_url('GET', - ['v3', 'path'], - base_url=self.BASE_URL, - status_code=200, - text=resp_text) - - # it doesn't matter that we auth with v2 here, discovery hack is in - # base. All identity endpoints point to v2 urls. - token = fixture.V2Token() - service = token.add_service(self.IDENTITY) - service.add_endpoint(public=self.V2_URL, - admin=self.V2_URL, - internal=self.V2_URL) - - self.stub_url('POST', - ['tokens'], - base_url=self.V2_URL, - json=token) - - v2_auth = identity.V2Password(self.V2_URL, - username=uuid.uuid4().hex, - password=uuid.uuid4().hex) - - sess = session.Session(auth=v2_auth) - - # v2 auth with v2 url doesn't make any discovery calls. - self.assertFalse(v2_m.called) - self.assertFalse(common_m.called) - - # v3 endpoint with hack will strip v2 suffix and call root discovery - endpoint = sess.get_endpoint(service_type=self.IDENTITY, - version=(3, 0), - allow_version_hack=True) - - # got v3 url - self.assertEqual(self.V3_URL, endpoint) - - # only called root discovery. - self.assertFalse(v2_m.called) - self.assertTrue(common_m.called_once) - - # with hack turned off it calls v2 discovery and finds nothing - endpoint = sess.get_endpoint(service_type=self.IDENTITY, - version=(3, 0), - allow_version_hack=False) - self.assertIsNone(endpoint) - - # this one called v2 - self.assertTrue(v2_m.called_once) - self.assertTrue(common_m.called_once) - - # get_endpoint returning None raises EndpointNotFound when requesting - self.assertRaises(exceptions.EndpointNotFound, - sess.get, - '/path', - endpoint_filter={'service_type': 'identity', - 'version': (3, 0), - 'allow_version_hack': False}) - - self.assertFalse(resp_m.called) - - # works when allow_version_hack is set - resp = sess.get('/path', - endpoint_filter={'service_type': 'identity', - 'version': (3, 0), - 'allow_version_hack': True}) - - self.assertTrue(resp_m.called_once) - self.assertEqual(resp_text, resp.text) - - -class GenericPlugin(plugin.BaseAuthPlugin): - - BAD_TOKEN = uuid.uuid4().hex - - def __init__(self): - super(GenericPlugin, self).__init__() - - self.endpoint = 'http://keystone.host:5000' - - self.headers = {'headerA': 'valueA', - 'headerB': 'valueB'} - - self.cert = '/path/to/cert' - self.connection_params = {'cert': self.cert, 'verify': False} - - def url(self, prefix): - return '%s/%s' % (self.endpoint, prefix) - - def get_token(self, session, **kwargs): - # NOTE(jamielennox): by specifying get_headers this should not be used - return self.BAD_TOKEN - - def get_headers(self, session, **kwargs): - return self.headers - - def get_endpoint(self, session, **kwargs): - return self.endpoint - - def get_connection_params(self, session, **kwargs): - return self.connection_params - - -class GenericAuthPluginTests(utils.TestCase): - - # filter doesn't matter to GenericPlugin, but we have to specify one - ENDPOINT_FILTER = {uuid.uuid4().hex: uuid.uuid4().hex} - - def setUp(self): - super(GenericAuthPluginTests, self).setUp() - self.auth = GenericPlugin() - self.session = session.Session(auth=self.auth) - - def test_setting_headers(self): - text = uuid.uuid4().hex - self.stub_url('GET', base_url=self.auth.url('prefix'), text=text) - - resp = self.session.get('prefix', endpoint_filter=self.ENDPOINT_FILTER) - - self.assertEqual(text, resp.text) - - for k, v in self.auth.headers.items(): - self.assertRequestHeaderEqual(k, v) - - self.assertIsNone(self.session.get_token()) - self.assertEqual(self.auth.headers, - self.session.get_auth_headers()) - self.assertNotIn('X-Auth-Token', - self.requests_mock.last_request.headers) - - def test_setting_connection_params(self): - text = uuid.uuid4().hex - self.stub_url('GET', base_url=self.auth.url('prefix'), text=text) - - resp = self.session.get('prefix', - endpoint_filter=self.ENDPOINT_FILTER) - - self.assertEqual(text, resp.text) - - # the cert and verify values passed to request are those that were - # returned from the auth plugin as connection params. - self.assertEqual(self.auth.cert, self.requests_mock.last_request.cert) - self.assertFalse(self.requests_mock.last_request.verify) - - def test_setting_bad_connection_params(self): - # The uuid name parameter here is unknown and not in the allowed params - # to be returned to the session and so an error will be raised. - name = uuid.uuid4().hex - self.auth.connection_params[name] = uuid.uuid4().hex - - e = self.assertRaises(exceptions.UnsupportedParameters, - self.session.get, - 'prefix', - endpoint_filter=self.ENDPOINT_FILTER) - - self.assertIn(name, str(e)) diff --git a/keystoneauth1/tests/unit/identity/test_identity_v2.py b/keystoneauth1/tests/unit/identity/test_identity_v2.py deleted file mode 100644 index 530b046..0000000 --- a/keystoneauth1/tests/unit/identity/test_identity_v2.py +++ /dev/null @@ -1,369 +0,0 @@ -# Licensed under the Apache License, Version 2.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 json -import uuid - -from keystoneauth1 import _utils as ksa_utils -from keystoneauth1 import access -from keystoneauth1 import exceptions -from keystoneauth1 import fixture -from keystoneauth1.identity import v2 -from keystoneauth1 import session -from keystoneauth1.tests.unit import utils - - -class V2IdentityPlugin(utils.TestCase): - - TEST_ROOT_URL = 'http://127.0.0.1:5000/' - TEST_URL = '%s%s' % (TEST_ROOT_URL, 'v2.0') - TEST_ROOT_ADMIN_URL = 'http://127.0.0.1:35357/' - TEST_ADMIN_URL = '%s%s' % (TEST_ROOT_ADMIN_URL, 'v2.0') - - TEST_PASS = 'password' - - TEST_SERVICE_CATALOG = [{ - "endpoints": [{ - "adminURL": "http://cdn.admin-nets.local:8774/v1.0", - "region": "RegionOne", - "internalURL": "http://127.0.0.1:8774/v1.0", - "publicURL": "http://cdn.admin-nets.local:8774/v1.0/" - }], - "type": "nova_compat", - "name": "nova_compat" - }, { - "endpoints": [{ - "adminURL": "http://nova/novapi/admin", - "region": "RegionOne", - "internalURL": "http://nova/novapi/internal", - "publicURL": "http://nova/novapi/public" - }], - "type": "compute", - "name": "nova" - }, { - "endpoints": [{ - "adminURL": "http://glance/glanceapi/admin", - "region": "RegionOne", - "internalURL": "http://glance/glanceapi/internal", - "publicURL": "http://glance/glanceapi/public" - }], - "type": "image", - "name": "glance" - }, { - "endpoints": [{ - "adminURL": TEST_ADMIN_URL, - "region": "RegionOne", - "internalURL": "http://127.0.0.1:5000/v2.0", - "publicURL": "http://127.0.0.1:5000/v2.0" - }], - "type": "identity", - "name": "keystone" - }, { - "endpoints": [{ - "adminURL": "http://swift/swiftapi/admin", - "region": "RegionOne", - "internalURL": "http://swift/swiftapi/internal", - "publicURL": "http://swift/swiftapi/public" - }], - "type": "object-store", - "name": "swift" - }] - - def setUp(self): - super(V2IdentityPlugin, self).setUp() - self.TEST_RESPONSE_DICT = { - "access": { - "token": { - "expires": "2020-01-01T00:00:10.000123Z", - "id": self.TEST_TOKEN, - "tenant": { - "id": self.TEST_TENANT_ID - }, - }, - "user": { - "id": self.TEST_USER - }, - "serviceCatalog": self.TEST_SERVICE_CATALOG, - }, - } - - def stub_auth(self, **kwargs): - self.stub_url('POST', ['tokens'], **kwargs) - - def test_authenticate_with_username_password(self): - self.stub_auth(json=self.TEST_RESPONSE_DICT) - a = v2.Password(self.TEST_URL, username=self.TEST_USER, - password=self.TEST_PASS) - self.assertIsNone(a.user_id) - self.assertFalse(a.has_scope_parameters) - s = session.Session(a) - self.assertEqual({'X-Auth-Token': self.TEST_TOKEN}, - s.get_auth_headers()) - - req = {'auth': {'passwordCredentials': {'username': self.TEST_USER, - 'password': self.TEST_PASS}}} - self.assertRequestBodyIs(json=req) - self.assertRequestHeaderEqual('Content-Type', 'application/json') - self.assertRequestHeaderEqual('Accept', 'application/json') - self.assertEqual(s.auth.auth_ref.auth_token, self.TEST_TOKEN) - - def test_authenticate_with_user_id_password(self): - self.stub_auth(json=self.TEST_RESPONSE_DICT) - a = v2.Password(self.TEST_URL, user_id=self.TEST_USER, - password=self.TEST_PASS) - self.assertIsNone(a.username) - self.assertFalse(a.has_scope_parameters) - s = session.Session(a) - self.assertEqual({'X-Auth-Token': self.TEST_TOKEN}, - s.get_auth_headers()) - - req = {'auth': {'passwordCredentials': {'userId': self.TEST_USER, - 'password': self.TEST_PASS}}} - self.assertRequestBodyIs(json=req) - self.assertRequestHeaderEqual('Content-Type', 'application/json') - self.assertRequestHeaderEqual('Accept', 'application/json') - self.assertEqual(s.auth.auth_ref.auth_token, self.TEST_TOKEN) - - def test_authenticate_with_username_password_scoped(self): - self.stub_auth(json=self.TEST_RESPONSE_DICT) - a = v2.Password(self.TEST_URL, username=self.TEST_USER, - password=self.TEST_PASS, tenant_id=self.TEST_TENANT_ID) - self.assertTrue(a.has_scope_parameters) - self.assertIsNone(a.user_id) - s = session.Session(a) - self.assertEqual({'X-Auth-Token': self.TEST_TOKEN}, - s.get_auth_headers()) - - req = {'auth': {'passwordCredentials': {'username': self.TEST_USER, - 'password': self.TEST_PASS}, - 'tenantId': self.TEST_TENANT_ID}} - self.assertRequestBodyIs(json=req) - self.assertEqual(s.auth.auth_ref.auth_token, self.TEST_TOKEN) - - def test_authenticate_with_user_id_password_scoped(self): - self.stub_auth(json=self.TEST_RESPONSE_DICT) - a = v2.Password(self.TEST_URL, user_id=self.TEST_USER, - password=self.TEST_PASS, tenant_id=self.TEST_TENANT_ID) - self.assertIsNone(a.username) - self.assertTrue(a.has_scope_parameters) - s = session.Session(a) - self.assertEqual({'X-Auth-Token': self.TEST_TOKEN}, - s.get_auth_headers()) - - req = {'auth': {'passwordCredentials': {'userId': self.TEST_USER, - 'password': self.TEST_PASS}, - 'tenantId': self.TEST_TENANT_ID}} - self.assertRequestBodyIs(json=req) - self.assertEqual(s.auth.auth_ref.auth_token, self.TEST_TOKEN) - - def test_authenticate_with_token(self): - self.stub_auth(json=self.TEST_RESPONSE_DICT) - a = v2.Token(self.TEST_URL, 'foo') - s = session.Session(a) - self.assertEqual({'X-Auth-Token': self.TEST_TOKEN}, - s.get_auth_headers()) - - req = {'auth': {'token': {'id': 'foo'}}} - self.assertRequestBodyIs(json=req) - self.assertRequestHeaderEqual('x-Auth-Token', 'foo') - self.assertRequestHeaderEqual('Content-Type', 'application/json') - self.assertRequestHeaderEqual('Accept', 'application/json') - self.assertEqual(s.auth.auth_ref.auth_token, self.TEST_TOKEN) - - def test_with_trust_id(self): - self.stub_auth(json=self.TEST_RESPONSE_DICT) - a = v2.Password(self.TEST_URL, username=self.TEST_USER, - password=self.TEST_PASS, trust_id='trust') - self.assertTrue(a.has_scope_parameters) - s = session.Session(a) - self.assertEqual({'X-Auth-Token': self.TEST_TOKEN}, - s.get_auth_headers()) - - req = {'auth': {'passwordCredentials': {'username': self.TEST_USER, - 'password': self.TEST_PASS}, - 'trust_id': 'trust'}} - - self.assertRequestBodyIs(json=req) - self.assertEqual(s.auth.auth_ref.auth_token, self.TEST_TOKEN) - - def _do_service_url_test(self, base_url, endpoint_filter): - self.stub_auth(json=self.TEST_RESPONSE_DICT) - self.stub_url('GET', ['path'], - base_url=base_url, - text='SUCCESS', status_code=200) - - a = v2.Password(self.TEST_URL, username=self.TEST_USER, - password=self.TEST_PASS) - s = session.Session(auth=a) - - resp = s.get('/path', endpoint_filter=endpoint_filter) - - self.assertEqual(resp.status_code, 200) - self.assertEqual(self.requests_mock.last_request.url, - base_url + '/path') - - def test_service_url(self): - endpoint_filter = {'service_type': 'compute', - 'interface': 'admin', - 'service_name': 'nova'} - self._do_service_url_test('http://nova/novapi/admin', endpoint_filter) - - def test_service_url_defaults_to_public(self): - endpoint_filter = {'service_type': 'compute'} - self._do_service_url_test('http://nova/novapi/public', endpoint_filter) - - def test_endpoint_filter_without_service_type_fails(self): - self.stub_auth(json=self.TEST_RESPONSE_DICT) - - a = v2.Password(self.TEST_URL, username=self.TEST_USER, - password=self.TEST_PASS) - s = session.Session(auth=a) - - self.assertRaises(exceptions.EndpointNotFound, s.get, '/path', - endpoint_filter={'interface': 'admin'}) - - def test_full_url_overrides_endpoint_filter(self): - self.stub_auth(json=self.TEST_RESPONSE_DICT) - self.stub_url('GET', [], - base_url='http://testurl/', - text='SUCCESS', status_code=200) - - a = v2.Password(self.TEST_URL, username=self.TEST_USER, - password=self.TEST_PASS) - s = session.Session(auth=a) - - resp = s.get('http://testurl/', - endpoint_filter={'service_type': 'compute'}) - self.assertEqual(resp.status_code, 200) - self.assertEqual(resp.text, 'SUCCESS') - - def test_invalid_auth_response_dict(self): - self.stub_auth(json={'hello': 'world'}) - - a = v2.Password(self.TEST_URL, username=self.TEST_USER, - password=self.TEST_PASS) - s = session.Session(auth=a) - - self.assertRaises(exceptions.InvalidResponse, s.get, 'http://any', - authenticated=True) - - def test_invalid_auth_response_type(self): - self.stub_url('POST', ['tokens'], text='testdata') - - a = v2.Password(self.TEST_URL, username=self.TEST_USER, - password=self.TEST_PASS) - s = session.Session(auth=a) - - self.assertRaises(exceptions.InvalidResponse, s.get, 'http://any', - authenticated=True) - - def test_invalidate_response(self): - resp_data1 = copy.deepcopy(self.TEST_RESPONSE_DICT) - resp_data2 = copy.deepcopy(self.TEST_RESPONSE_DICT) - - resp_data1['access']['token']['id'] = 'token1' - resp_data2['access']['token']['id'] = 'token2' - - auth_responses = [{'json': resp_data1}, {'json': resp_data2}] - self.stub_auth(response_list=auth_responses) - - a = v2.Password(self.TEST_URL, username=self.TEST_USER, - password=self.TEST_PASS) - s = session.Session(auth=a) - - self.assertEqual('token1', s.get_token()) - self.assertEqual({'X-Auth-Token': 'token1'}, s.get_auth_headers()) - - a.invalidate() - self.assertEqual('token2', s.get_token()) - self.assertEqual({'X-Auth-Token': 'token2'}, s.get_auth_headers()) - - def test_doesnt_log_password(self): - self.stub_auth(json=self.TEST_RESPONSE_DICT) - password = uuid.uuid4().hex - - a = v2.Password(self.TEST_URL, username=self.TEST_USER, - password=password) - s = session.Session(auth=a) - self.assertEqual(self.TEST_TOKEN, s.get_token()) - self.assertEqual({'X-Auth-Token': self.TEST_TOKEN}, - s.get_auth_headers()) - self.assertNotIn(password, self.logger.output) - - def test_password_with_no_user_id_or_name(self): - self.assertRaises(TypeError, - v2.Password, self.TEST_URL, password=self.TEST_PASS) - - def test_password_cache_id(self): - self.stub_auth(json=self.TEST_RESPONSE_DICT) - trust_id = uuid.uuid4().hex - - a = v2.Password(self.TEST_URL, - username=self.TEST_USER, - password=self.TEST_PASS, - trust_id=trust_id) - - b = v2.Password(self.TEST_URL, - username=self.TEST_USER, - password=self.TEST_PASS, - trust_id=trust_id) - - a_id = a.get_cache_id() - b_id = b.get_cache_id() - - self.assertEqual(a_id, b_id) - - c = v2.Password(self.TEST_URL, - username=self.TEST_USER, - password=self.TEST_PASS, - tenant_id=trust_id) # same value different param - - c_id = c.get_cache_id() - - self.assertNotEqual(a_id, c_id) - - self.assertIsNone(a.get_auth_state()) - self.assertIsNone(b.get_auth_state()) - self.assertIsNone(c.get_auth_state()) - - s = session.Session() - self.assertEqual(self.TEST_TOKEN, a.get_token(s)) - self.assertTrue(self.requests_mock.called) - - def test_password_change_auth_state(self): - self.stub_auth(json=self.TEST_RESPONSE_DICT) - - expired = ksa_utils.before_utcnow(days=2) - token = fixture.V2Token(expires=expired) - - auth_ref = access.create(body=token) - - a = v2.Password(self.TEST_URL, - username=self.TEST_USER, - password=self.TEST_PASS, - tenant_id=uuid.uuid4().hex) - - initial_cache_id = a.get_cache_id() - - state = a.get_auth_state() - self.assertIsNone(state) - - state = json.dumps({'auth_token': auth_ref.auth_token, - 'body': auth_ref._data}) - a.set_auth_state(state) - - self.assertEqual(token.token_id, a.auth_ref.auth_token) - - s = session.Session() - self.assertEqual(self.TEST_TOKEN, a.get_token(s)) # updates expired - self.assertEqual(initial_cache_id, a.get_cache_id()) diff --git a/keystoneauth1/tests/unit/identity/test_identity_v3.py b/keystoneauth1/tests/unit/identity/test_identity_v3.py deleted file mode 100644 index 5c88105..0000000 --- a/keystoneauth1/tests/unit/identity/test_identity_v3.py +++ /dev/null @@ -1,632 +0,0 @@ -# Licensed under the Apache License, Version 2.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 json -import uuid - -from keystoneauth1 import _utils as ksa_utils -from keystoneauth1 import access -from keystoneauth1 import exceptions -from keystoneauth1 import fixture -from keystoneauth1.identity import v3 -from keystoneauth1.identity.v3 import base as v3_base -from keystoneauth1 import session -from keystoneauth1.tests.unit import utils - - -class V3IdentityPlugin(utils.TestCase): - - TEST_ROOT_URL = 'http://127.0.0.1:5000/' - TEST_URL = '%s%s' % (TEST_ROOT_URL, 'v3') - TEST_ROOT_ADMIN_URL = 'http://127.0.0.1:35357/' - TEST_ADMIN_URL = '%s%s' % (TEST_ROOT_ADMIN_URL, 'v3') - - TEST_PASS = 'password' - - TEST_SERVICE_CATALOG = [{ - "endpoints": [{ - "url": "http://cdn.admin-nets.local:8774/v1.0/", - "region": "RegionOne", - "interface": "public" - }, { - "url": "http://127.0.0.1:8774/v1.0", - "region": "RegionOne", - "interface": "internal" - }, { - "url": "http://cdn.admin-nets.local:8774/v1.0", - "region": "RegionOne", - "interface": "admin" - }], - "type": "nova_compat" - }, { - "endpoints": [{ - "url": "http://nova/novapi/public", - "region": "RegionOne", - "interface": "public" - }, { - "url": "http://nova/novapi/internal", - "region": "RegionOne", - "interface": "internal" - }, { - "url": "http://nova/novapi/admin", - "region": "RegionOne", - "interface": "admin" - }], - "type": "compute", - "name": "nova", - }, { - "endpoints": [{ - "url": "http://glance/glanceapi/public", - "region": "RegionOne", - "interface": "public" - }, { - "url": "http://glance/glanceapi/internal", - "region": "RegionOne", - "interface": "internal" - }, { - "url": "http://glance/glanceapi/admin", - "region": "RegionOne", - "interface": "admin" - }], - "type": "image", - "name": "glance" - }, { - "endpoints": [{ - "url": "http://127.0.0.1:5000/v3", - "region": "RegionOne", - "interface": "public" - }, { - "url": "http://127.0.0.1:5000/v3", - "region": "RegionOne", - "interface": "internal" - }, { - "url": TEST_ADMIN_URL, - "region": "RegionOne", - "interface": "admin" - }], - "type": "identity" - }, { - "endpoints": [{ - "url": "http://swift/swiftapi/public", - "region": "RegionOne", - "interface": "public" - }, { - "url": "http://swift/swiftapi/internal", - "region": "RegionOne", - "interface": "internal" - }, { - "url": "http://swift/swiftapi/admin", - "region": "RegionOne", - "interface": "admin" - }], - "type": "object-store" - }] - - TEST_SERVICE_PROVIDERS = [ - { - "auth_url": "https://sp1.com/v3/OS-FEDERATION/" - "identity_providers/acme/protocols/saml2/auth", - "id": "sp1", - "sp_url": "https://sp1.com/Shibboleth.sso/SAML2/ECP" - }, { - "auth_url": "https://sp2.com/v3/OS-FEDERATION/" - "identity_providers/acme/protocols/saml2/auth", - "id": "sp2", - "sp_url": "https://sp2.com/Shibboleth.sso/SAML2/ECP" - } - ] - - def setUp(self): - super(V3IdentityPlugin, self).setUp() - - self.TEST_DISCOVERY_RESPONSE = { - 'versions': {'values': [fixture.V3Discovery(self.TEST_URL)]}} - - self.TEST_RESPONSE_DICT = { - "token": { - "methods": [ - "token", - "password" - ], - - "expires_at": "2020-01-01T00:00:10.000123Z", - "project": { - "domain": { - "id": self.TEST_DOMAIN_ID, - "name": self.TEST_DOMAIN_NAME - }, - "id": self.TEST_TENANT_ID, - "name": self.TEST_TENANT_NAME - }, - "user": { - "domain": { - "id": self.TEST_DOMAIN_ID, - "name": self.TEST_DOMAIN_NAME - }, - "id": self.TEST_USER, - "name": self.TEST_USER - }, - "issued_at": "2013-05-29T16:55:21.468960Z", - "catalog": self.TEST_SERVICE_CATALOG, - "service_providers": self.TEST_SERVICE_PROVIDERS - }, - } - self.TEST_PROJECTS_RESPONSE = { - "projects": [ - { - "domain_id": "1789d1", - "enabled": "True", - "id": "263fd9", - "links": { - "self": "https://identity:5000/v3/projects/263fd9" - }, - "name": "Dev Group A" - }, - { - "domain_id": "1789d1", - "enabled": "True", - "id": "e56ad3", - "links": { - "self": "https://identity:5000/v3/projects/e56ad3" - }, - "name": "Dev Group B" - } - ], - "links": { - "self": "https://identity:5000/v3/projects", - } - } - - def stub_auth(self, subject_token=None, **kwargs): - if not subject_token: - subject_token = self.TEST_TOKEN - - self.stub_url('POST', ['auth', 'tokens'], - headers={'X-Subject-Token': subject_token}, **kwargs) - - def test_authenticate_with_username_password(self): - self.stub_auth(json=self.TEST_RESPONSE_DICT) - a = v3.Password(self.TEST_URL, - username=self.TEST_USER, - password=self.TEST_PASS) - self.assertFalse(a.has_scope_parameters) - s = session.Session(auth=a) - - self.assertEqual({'X-Auth-Token': self.TEST_TOKEN}, - s.get_auth_headers()) - - req = {'auth': {'identity': - {'methods': ['password'], - 'password': {'user': {'name': self.TEST_USER, - 'password': self.TEST_PASS}}}}} - - self.assertRequestBodyIs(json=req) - self.assertRequestHeaderEqual('Content-Type', 'application/json') - self.assertRequestHeaderEqual('Accept', 'application/json') - self.assertEqual(s.auth.auth_ref.auth_token, self.TEST_TOKEN) - - def test_authenticate_with_username_password_domain_scoped(self): - self.stub_auth(json=self.TEST_RESPONSE_DICT) - a = v3.Password(self.TEST_URL, username=self.TEST_USER, - password=self.TEST_PASS, domain_id=self.TEST_DOMAIN_ID) - self.assertTrue(a.has_scope_parameters) - s = session.Session(a) - - self.assertEqual({'X-Auth-Token': self.TEST_TOKEN}, - s.get_auth_headers()) - - req = {'auth': {'identity': - {'methods': ['password'], - 'password': {'user': {'name': self.TEST_USER, - 'password': self.TEST_PASS}}}, - 'scope': {'domain': {'id': self.TEST_DOMAIN_ID}}}} - self.assertRequestBodyIs(json=req) - self.assertEqual(s.auth.auth_ref.auth_token, self.TEST_TOKEN) - - def test_authenticate_with_username_password_project_scoped(self): - self.stub_auth(json=self.TEST_RESPONSE_DICT) - a = v3.Password(self.TEST_URL, username=self.TEST_USER, - password=self.TEST_PASS, - project_id=self.TEST_TENANT_ID) - self.assertTrue(a.has_scope_parameters) - s = session.Session(a) - - self.assertEqual({'X-Auth-Token': self.TEST_TOKEN}, - s.get_auth_headers()) - - req = {'auth': {'identity': - {'methods': ['password'], - 'password': {'user': {'name': self.TEST_USER, - 'password': self.TEST_PASS}}}, - 'scope': {'project': {'id': self.TEST_TENANT_ID}}}} - self.assertRequestBodyIs(json=req) - self.assertEqual(s.auth.auth_ref.auth_token, self.TEST_TOKEN) - self.assertEqual(s.auth.auth_ref.project_id, self.TEST_TENANT_ID) - - def test_authenticate_with_token(self): - self.stub_auth(json=self.TEST_RESPONSE_DICT) - a = v3.Token(self.TEST_URL, self.TEST_TOKEN) - s = session.Session(auth=a) - - self.assertEqual({'X-Auth-Token': self.TEST_TOKEN}, - s.get_auth_headers()) - - req = {'auth': {'identity': - {'methods': ['token'], - 'token': {'id': self.TEST_TOKEN}}}} - - self.assertRequestBodyIs(json=req) - - self.assertRequestHeaderEqual('Content-Type', 'application/json') - self.assertRequestHeaderEqual('Accept', 'application/json') - self.assertEqual(s.auth.auth_ref.auth_token, self.TEST_TOKEN) - - def test_with_expired(self): - self.stub_auth(json=self.TEST_RESPONSE_DICT) - - d = copy.deepcopy(self.TEST_RESPONSE_DICT) - d['token']['expires_at'] = '2000-01-01T00:00:10.000123Z' - - a = v3.Password(self.TEST_URL, username='username', - password='password') - a.auth_ref = access.create(body=d) - s = session.Session(auth=a) - - self.assertEqual({'X-Auth-Token': self.TEST_TOKEN}, - s.get_auth_headers()) - - self.assertEqual(a.auth_ref._data['token']['expires_at'], - self.TEST_RESPONSE_DICT['token']['expires_at']) - - def test_with_domain_and_project_scoping(self): - a = v3.Password(self.TEST_URL, username='username', - password='password', project_id='project', - domain_id='domain') - - self.assertTrue(a.has_scope_parameters) - self.assertRaises(exceptions.AuthorizationFailure, - a.get_token, None) - self.assertRaises(exceptions.AuthorizationFailure, - a.get_headers, None) - - def test_with_trust_id(self): - self.stub_auth(json=self.TEST_RESPONSE_DICT) - a = v3.Password(self.TEST_URL, username=self.TEST_USER, - password=self.TEST_PASS, trust_id='trust') - self.assertTrue(a.has_scope_parameters) - s = session.Session(a) - - self.assertEqual({'X-Auth-Token': self.TEST_TOKEN}, - s.get_auth_headers()) - - req = {'auth': {'identity': - {'methods': ['password'], - 'password': {'user': {'name': self.TEST_USER, - 'password': self.TEST_PASS}}}, - 'scope': {'OS-TRUST:trust': {'id': 'trust'}}}} - self.assertRequestBodyIs(json=req) - self.assertEqual(s.auth.auth_ref.auth_token, self.TEST_TOKEN) - - def test_with_multiple_mechanisms_factory(self): - self.stub_auth(json=self.TEST_RESPONSE_DICT) - p = v3.PasswordMethod(username=self.TEST_USER, password=self.TEST_PASS) - t = v3.TokenMethod(token='foo') - a = v3.Auth(self.TEST_URL, [p, t], trust_id='trust') - s = session.Session(a) - - self.assertEqual({'X-Auth-Token': self.TEST_TOKEN}, - s.get_auth_headers()) - - req = {'auth': {'identity': - {'methods': ['password', 'token'], - 'password': {'user': {'name': self.TEST_USER, - 'password': self.TEST_PASS}}, - 'token': {'id': 'foo'}}, - 'scope': {'OS-TRUST:trust': {'id': 'trust'}}}} - self.assertRequestBodyIs(json=req) - self.assertEqual(s.auth.auth_ref.auth_token, self.TEST_TOKEN) - - def test_with_multiple_mechanisms(self): - self.stub_auth(json=self.TEST_RESPONSE_DICT) - p = v3.PasswordMethod(username=self.TEST_USER, - password=self.TEST_PASS) - t = v3.TokenMethod(token='foo') - a = v3.Auth(self.TEST_URL, [p, t], trust_id='trust') - self.assertTrue(a.has_scope_parameters) - s = session.Session(auth=a) - - self.assertEqual({'X-Auth-Token': self.TEST_TOKEN}, - s.get_auth_headers()) - - req = {'auth': {'identity': - {'methods': ['password', 'token'], - 'password': {'user': {'name': self.TEST_USER, - 'password': self.TEST_PASS}}, - 'token': {'id': 'foo'}}, - 'scope': {'OS-TRUST:trust': {'id': 'trust'}}}} - self.assertRequestBodyIs(json=req) - self.assertEqual(s.auth.auth_ref.auth_token, self.TEST_TOKEN) - - def test_with_multiple_scopes(self): - s = session.Session() - - a = v3.Password(self.TEST_URL, - username=self.TEST_USER, password=self.TEST_PASS, - domain_id='x', project_id='x') - self.assertRaises(exceptions.AuthorizationFailure, a.get_auth_ref, s) - - a = v3.Password(self.TEST_URL, - username=self.TEST_USER, password=self.TEST_PASS, - domain_id='x', trust_id='x') - self.assertRaises(exceptions.AuthorizationFailure, a.get_auth_ref, s) - - def _do_service_url_test(self, base_url, endpoint_filter): - self.stub_auth(json=self.TEST_RESPONSE_DICT) - self.stub_url('GET', ['path'], - base_url=base_url, - text='SUCCESS', status_code=200) - - a = v3.Password(self.TEST_URL, username=self.TEST_USER, - password=self.TEST_PASS) - s = session.Session(auth=a) - - resp = s.get('/path', endpoint_filter=endpoint_filter) - - self.assertEqual(resp.status_code, 200) - self.assertEqual(self.requests_mock.last_request.url, - base_url + '/path') - - def test_service_url(self): - endpoint_filter = {'service_type': 'compute', - 'interface': 'admin', - 'service_name': 'nova'} - self._do_service_url_test('http://nova/novapi/admin', endpoint_filter) - - def test_service_url_defaults_to_public(self): - endpoint_filter = {'service_type': 'compute'} - self._do_service_url_test('http://nova/novapi/public', endpoint_filter) - - def test_endpoint_filter_without_service_type_fails(self): - self.stub_auth(json=self.TEST_RESPONSE_DICT) - - a = v3.Password(self.TEST_URL, username=self.TEST_USER, - password=self.TEST_PASS) - s = session.Session(auth=a) - - self.assertRaises(exceptions.EndpointNotFound, s.get, '/path', - endpoint_filter={'interface': 'admin'}) - - def test_full_url_overrides_endpoint_filter(self): - self.stub_auth(json=self.TEST_RESPONSE_DICT) - self.stub_url('GET', [], - base_url='http://testurl/', - text='SUCCESS', status_code=200) - - a = v3.Password(self.TEST_URL, username=self.TEST_USER, - password=self.TEST_PASS) - s = session.Session(auth=a) - - resp = s.get('http://testurl/', - endpoint_filter={'service_type': 'compute'}) - self.assertEqual(resp.status_code, 200) - self.assertEqual(resp.text, 'SUCCESS') - - def test_service_providers_urls(self): - self.stub_auth(json=self.TEST_RESPONSE_DICT) - a = v3.Password(self.TEST_URL, username=self.TEST_USER, - password=self.TEST_PASS) - s = session.Session() - auth_ref = a.get_auth_ref(s) - - service_providers = auth_ref.service_providers - self.assertEqual('https://sp1.com/v3/OS-FEDERATION/' - 'identity_providers/acme/protocols/saml2/auth', - service_providers.get_auth_url('sp1')) - self.assertEqual('https://sp1.com/Shibboleth.sso/SAML2/ECP', - service_providers.get_sp_url('sp1')) - self.assertEqual('https://sp2.com/v3/OS-FEDERATION/' - 'identity_providers/acme/protocols/saml2/auth', - service_providers.get_auth_url('sp2')) - self.assertEqual('https://sp2.com/Shibboleth.sso/SAML2/ECP', - service_providers.get_sp_url('sp2')) - - def test_handle_missing_service_provider(self): - self.stub_auth(json=self.TEST_RESPONSE_DICT) - - a = v3.Password(self.TEST_URL, username=self.TEST_USER, - password=self.TEST_PASS) - s = session.Session() - auth_ref = a.get_auth_ref(s) - - service_providers = auth_ref.service_providers - - self.assertRaises(exceptions.ServiceProviderNotFound, - service_providers._get_service_provider, - uuid.uuid4().hex) - - def test_invalid_auth_response_dict(self): - self.stub_auth(json={'hello': 'world'}) - - a = v3.Password(self.TEST_URL, username=self.TEST_USER, - password=self.TEST_PASS) - s = session.Session(auth=a) - - self.assertRaises(exceptions.InvalidResponse, s.get, 'http://any', - authenticated=True) - - def test_invalid_auth_response_type(self): - self.stub_url('POST', ['auth', 'tokens'], text='testdata') - - a = v3.Password(self.TEST_URL, username=self.TEST_USER, - password=self.TEST_PASS) - s = session.Session(auth=a) - - self.assertRaises(exceptions.InvalidResponse, s.get, 'http://any', - authenticated=True) - - def test_invalidate_response(self): - auth_responses = [{'status_code': 200, 'json': self.TEST_RESPONSE_DICT, - 'headers': {'X-Subject-Token': 'token1'}}, - {'status_code': 200, 'json': self.TEST_RESPONSE_DICT, - 'headers': {'X-Subject-Token': 'token2'}}] - - self.requests_mock.post('%s/auth/tokens' % self.TEST_URL, - auth_responses) - - a = v3.Password(self.TEST_URL, username=self.TEST_USER, - password=self.TEST_PASS) - s = session.Session(auth=a) - - self.assertEqual('token1', s.get_token()) - self.assertEqual({'X-Auth-Token': 'token1'}, s.get_auth_headers()) - a.invalidate() - self.assertEqual('token2', s.get_token()) - self.assertEqual({'X-Auth-Token': 'token2'}, s.get_auth_headers()) - - def test_doesnt_log_password(self): - self.stub_auth(json=self.TEST_RESPONSE_DICT) - - password = uuid.uuid4().hex - a = v3.Password(self.TEST_URL, username=self.TEST_USER, - password=password) - s = session.Session(a) - self.assertEqual(self.TEST_TOKEN, s.get_token()) - self.assertEqual({'X-Auth-Token': self.TEST_TOKEN}, - s.get_auth_headers()) - - self.assertNotIn(password, self.logger.output) - - def test_sends_nocatalog(self): - del self.TEST_RESPONSE_DICT['token']['catalog'] - self.stub_auth(json=self.TEST_RESPONSE_DICT) - - a = v3.Password(self.TEST_URL, - username=self.TEST_USER, - password=self.TEST_PASS, - include_catalog=False) - s = session.Session(auth=a) - - s.get_token() - - auth_url = self.TEST_URL + '/auth/tokens' - self.assertEqual(auth_url, a.token_url) - self.assertEqual(auth_url + '?nocatalog', - self.requests_mock.last_request.url) - - def test_symbols(self): - self.assertIs(v3.AuthMethod, v3_base.AuthMethod) - self.assertIs(v3.AuthConstructor, v3_base.AuthConstructor) - self.assertIs(v3.Auth, v3_base.Auth) - - def test_unscoped_request(self): - token = fixture.V3Token() - self.stub_auth(json=token) - password = uuid.uuid4().hex - - a = v3.Password(self.TEST_URL, - user_id=token.user_id, - password=password, - unscoped=True) - s = session.Session() - - auth_ref = a.get_access(s) - - self.assertFalse(auth_ref.scoped) - body = self.requests_mock.last_request.json() - - ident = body['auth']['identity'] - - self.assertEqual(['password'], ident['methods']) - self.assertEqual(token.user_id, ident['password']['user']['id']) - self.assertEqual(password, ident['password']['user']['password']) - - self.assertEqual('unscoped', body['auth']['scope']) - - def test_unscoped_with_scope_data(self): - a = v3.Password(self.TEST_URL, - user_id=uuid.uuid4().hex, - password=uuid.uuid4().hex, - unscoped=True, - project_id=uuid.uuid4().hex) - - s = session.Session() - - self.assertRaises(exceptions.AuthorizationFailure, a.get_auth_ref, s) - - def test_password_cache_id(self): - self.stub_auth(json=self.TEST_RESPONSE_DICT) - project_name = uuid.uuid4().hex - - a = v3.Password(self.TEST_URL, - username=self.TEST_USER, - password=self.TEST_PASS, - user_domain_id=self.TEST_DOMAIN_ID, - project_domain_name=self.TEST_DOMAIN_NAME, - project_name=project_name) - - b = v3.Password(self.TEST_URL, - username=self.TEST_USER, - password=self.TEST_PASS, - user_domain_id=self.TEST_DOMAIN_ID, - project_domain_name=self.TEST_DOMAIN_NAME, - project_name=project_name) - - a_id = a.get_cache_id() - b_id = b.get_cache_id() - - self.assertEqual(a_id, b_id) - - c = v3.Password(self.TEST_URL, - username=self.TEST_USER, - password=self.TEST_PASS, - user_domain_id=self.TEST_DOMAIN_ID, - project_domain_name=self.TEST_DOMAIN_NAME, - project_id=project_name) # same value different param - - c_id = c.get_cache_id() - - self.assertNotEqual(a_id, c_id) - - self.assertIsNone(a.get_auth_state()) - self.assertIsNone(b.get_auth_state()) - self.assertIsNone(c.get_auth_state()) - - s = session.Session() - self.assertEqual(self.TEST_TOKEN, a.get_token(s)) - self.assertTrue(self.requests_mock.called) - - def test_password_change_auth_state(self): - self.stub_auth(json=self.TEST_RESPONSE_DICT) - - expired = ksa_utils.before_utcnow(days=2) - token = fixture.V3Token(expires=expired) - token_id = uuid.uuid4().hex - - state = json.dumps({'auth_token': token_id, 'body': token}) - - a = v3.Password(self.TEST_URL, - username=self.TEST_USER, - password=self.TEST_PASS, - user_domain_id=self.TEST_DOMAIN_ID, - project_id=uuid.uuid4().hex) - - initial_cache_id = a.get_cache_id() - - self.assertIsNone(a.get_auth_state()) - a.set_auth_state(state) - - self.assertEqual(token_id, a.auth_ref.auth_token) - - s = session.Session() - self.assertEqual(self.TEST_TOKEN, a.get_token(s)) # updates expired - self.assertEqual(initial_cache_id, a.get_cache_id()) diff --git a/keystoneauth1/tests/unit/identity/test_identity_v3_federation.py b/keystoneauth1/tests/unit/identity/test_identity_v3_federation.py deleted file mode 100644 index e7e940f..0000000 --- a/keystoneauth1/tests/unit/identity/test_identity_v3_federation.py +++ /dev/null @@ -1,292 +0,0 @@ -# Licensed under the Apache License, Version 2.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 uuid - -import six - -from keystoneauth1 import access -from keystoneauth1 import exceptions -from keystoneauth1 import fixture -from keystoneauth1 import identity -from keystoneauth1.identity import v3 -from keystoneauth1 import session -from keystoneauth1.tests.unit import k2k_fixtures -from keystoneauth1.tests.unit import utils - - -class TesterFederationPlugin(v3.FederationBaseAuth): - - def get_unscoped_auth_ref(self, sess, **kwargs): - # This would go and talk to an idp or something - resp = sess.post(self.federated_token_url, authenticated=False) - return access.create(resp=resp) - - -class V3FederatedPlugin(utils.TestCase): - - AUTH_URL = 'http://keystone/v3' - - def setUp(self): - super(V3FederatedPlugin, self).setUp() - - self.unscoped_token = fixture.V3Token() - self.unscoped_token_id = uuid.uuid4().hex - self.scoped_token = copy.deepcopy(self.unscoped_token) - self.scoped_token.set_project_scope() - self.scoped_token.methods.append('token') - self.scoped_token_id = uuid.uuid4().hex - - s = self.scoped_token.add_service('compute', name='nova') - s.add_standard_endpoints(public='http://nova/public', - admin='http://nova/admin', - internal='http://nova/internal') - - self.idp = uuid.uuid4().hex - self.protocol = uuid.uuid4().hex - - self.token_url = ('%s/OS-FEDERATION/identity_providers/%s/protocols/%s' - '/auth' % (self.AUTH_URL, self.idp, self.protocol)) - - headers = {'X-Subject-Token': self.unscoped_token_id} - self.unscoped_mock = self.requests_mock.post(self.token_url, - json=self.unscoped_token, - headers=headers) - - headers = {'X-Subject-Token': self.scoped_token_id} - auth_url = self.AUTH_URL + '/auth/tokens' - self.scoped_mock = self.requests_mock.post(auth_url, - json=self.scoped_token, - headers=headers) - - def get_plugin(self, **kwargs): - kwargs.setdefault('auth_url', self.AUTH_URL) - kwargs.setdefault('protocol', self.protocol) - kwargs.setdefault('identity_provider', self.idp) - return TesterFederationPlugin(**kwargs) - - def test_federated_url(self): - plugin = self.get_plugin() - self.assertEqual(self.token_url, plugin.federated_token_url) - - def test_unscoped_behaviour(self): - sess = session.Session(auth=self.get_plugin()) - self.assertEqual(self.unscoped_token_id, sess.get_token()) - - self.assertTrue(self.unscoped_mock.called) - self.assertFalse(self.scoped_mock.called) - - def test_scoped_behaviour(self): - auth = self.get_plugin(project_id=self.scoped_token.project_id) - sess = session.Session(auth=auth) - self.assertEqual(self.scoped_token_id, sess.get_token()) - - self.assertTrue(self.unscoped_mock.called) - self.assertTrue(self.scoped_mock.called) - - -class K2KAuthPluginTest(utils.TestCase): - - TEST_ROOT_URL = 'http://127.0.0.1:5000/' - TEST_URL = '%s%s' % (TEST_ROOT_URL, 'v3') - TEST_PASS = 'password' - - REQUEST_ECP_URL = TEST_URL + '/auth/OS-FEDERATION/saml2/ecp' - - SP_ROOT_URL = 'https://sp1.com/v3' - SP_ID = 'sp1' - SP_URL = 'https://sp1.com/Shibboleth.sso/SAML2/ECP' - SP_AUTH_URL = (SP_ROOT_URL + - '/OS-FEDERATION/identity_providers' - '/testidp/protocols/saml2/auth') - - SERVICE_PROVIDER_DICT = { - 'id': SP_ID, - 'auth_url': SP_AUTH_URL, - 'sp_url': SP_URL - } - - def setUp(self): - super(K2KAuthPluginTest, self).setUp() - self.token_v3 = fixture.V3Token() - self.token_v3.add_service_provider( - self.SP_ID, self.SP_AUTH_URL, self.SP_URL) - self.session = session.Session() - - self.k2kplugin = self.get_plugin() - - def _get_base_plugin(self): - self.stub_url('POST', ['auth', 'tokens'], - headers={'X-Subject-Token': uuid.uuid4().hex}, - json=self.token_v3) - return v3.Password(self.TEST_URL, - username=self.TEST_USER, - password=self.TEST_PASS) - - def _mock_k2k_flow_urls(self, redirect_code=302): - # List versions available for auth - self.requests_mock.get( - self.TEST_URL, - json={'version': fixture.V3Discovery(self.TEST_URL)}, - headers={'Content-Type': 'application/json'}) - - # The IdP should return a ECP wrapped assertion when requested - self.requests_mock.register_uri( - 'POST', - self.REQUEST_ECP_URL, - content=six.b(k2k_fixtures.ECP_ENVELOPE), - headers={'Content-Type': 'application/vnd.paos+xml'}, - status_code=200) - - # The SP should respond with a redirect (302 or 303) - self.requests_mock.register_uri( - 'POST', - self.SP_URL, - content=six.b(k2k_fixtures.TOKEN_BASED_ECP), - headers={'Content-Type': 'application/vnd.paos+xml'}, - status_code=redirect_code) - - # Should not follow the redirect URL, but use the auth_url attribute - self.requests_mock.register_uri( - 'GET', - self.SP_AUTH_URL, - json=k2k_fixtures.UNSCOPED_TOKEN, - headers={'X-Subject-Token': k2k_fixtures.UNSCOPED_TOKEN_HEADER}) - - def get_plugin(self, **kwargs): - kwargs.setdefault('base_plugin', self._get_base_plugin()) - kwargs.setdefault('service_provider', self.SP_ID) - return v3.Keystone2Keystone(**kwargs) - - def test_remote_url(self): - remote_auth_url = self.k2kplugin._remote_auth_url(self.SP_AUTH_URL) - self.assertEqual(self.SP_ROOT_URL, remote_auth_url) - - def test_fail_getting_ecp_assertion(self): - self.requests_mock.get( - self.TEST_URL, - json={'version': fixture.V3Discovery(self.TEST_URL)}, - headers={'Content-Type': 'application/json'}) - - self.requests_mock.register_uri( - 'POST', self.REQUEST_ECP_URL, - status_code=401) - - self.assertRaises(exceptions.AuthorizationFailure, - self.k2kplugin._get_ecp_assertion, - self.session) - - def test_get_ecp_assertion_empty_response(self): - self.requests_mock.get( - self.TEST_URL, - json={'version': fixture.V3Discovery(self.TEST_URL)}, - headers={'Content-Type': 'application/json'}) - - self.requests_mock.register_uri( - 'POST', self.REQUEST_ECP_URL, - headers={'Content-Type': 'application/vnd.paos+xml'}, - content=six.b(''), status_code=200) - - self.assertRaises(exceptions.InvalidResponse, - self.k2kplugin._get_ecp_assertion, - self.session) - - def test_get_ecp_assertion_wrong_headers(self): - self.requests_mock.get( - self.TEST_URL, - json={'version': fixture.V3Discovery(self.TEST_URL)}, - headers={'Content-Type': 'application/json'}) - - self.requests_mock.register_uri( - 'POST', self.REQUEST_ECP_URL, - headers={'Content-Type': uuid.uuid4().hex}, - content=six.b(''), status_code=200) - - self.assertRaises(exceptions.InvalidResponse, - self.k2kplugin._get_ecp_assertion, - self.session) - - def test_send_ecp_authn_response(self): - self._mock_k2k_flow_urls() - # Perform the request - response = self.k2kplugin._send_service_provider_ecp_authn_response( - self.session, self.SP_URL, self.SP_AUTH_URL) - - # Check the response - self.assertEqual(k2k_fixtures.UNSCOPED_TOKEN_HEADER, - response.headers['X-Subject-Token']) - - def test_send_ecp_authn_response_303_redirect(self): - self._mock_k2k_flow_urls(redirect_code=303) - # Perform the request - response = self.k2kplugin._send_service_provider_ecp_authn_response( - self.session, self.SP_URL, self.SP_AUTH_URL) - - # Check the response - self.assertEqual(k2k_fixtures.UNSCOPED_TOKEN_HEADER, - response.headers['X-Subject-Token']) - - def test_end_to_end_workflow(self): - self._mock_k2k_flow_urls() - auth_ref = self.k2kplugin.get_auth_ref(self.session) - self.assertEqual(k2k_fixtures.UNSCOPED_TOKEN_HEADER, - auth_ref.auth_token) - - def test_end_to_end_workflow_303_redirect(self): - self._mock_k2k_flow_urls(redirect_code=303) - auth_ref = self.k2kplugin.get_auth_ref(self.session) - self.assertEqual(k2k_fixtures.UNSCOPED_TOKEN_HEADER, - auth_ref.auth_token) - - def test_end_to_end_with_generic_password(self): - # List versions available for auth - self.requests_mock.get( - self.TEST_ROOT_URL, - json=fixture.DiscoveryList(self.TEST_ROOT_URL), - headers={'Content-Type': 'application/json'}) - - # The IdP should return a ECP wrapped assertion when requested - self.requests_mock.register_uri( - 'POST', - self.REQUEST_ECP_URL, - content=six.b(k2k_fixtures.ECP_ENVELOPE), - headers={'Content-Type': 'application/vnd.paos+xml'}, - status_code=200) - - # The SP should respond with a redirect (302 or 303) - self.requests_mock.register_uri( - 'POST', - self.SP_URL, - content=six.b(k2k_fixtures.TOKEN_BASED_ECP), - headers={'Content-Type': 'application/vnd.paos+xml'}, - status_code=302) - - # Should not follow the redirect URL, but use the auth_url attribute - self.requests_mock.register_uri( - 'GET', - self.SP_AUTH_URL, - json=k2k_fixtures.UNSCOPED_TOKEN, - headers={'X-Subject-Token': k2k_fixtures.UNSCOPED_TOKEN_HEADER}) - - self.stub_url('POST', ['auth', 'tokens'], - headers={'X-Subject-Token': uuid.uuid4().hex}, - json=self.token_v3) - - plugin = identity.Password(self.TEST_ROOT_URL, - username=self.TEST_USER, - password=self.TEST_PASS, - user_domain_id='default') - - k2kplugin = self.get_plugin(base_plugin=plugin) - self.assertEqual(k2k_fixtures.UNSCOPED_TOKEN_HEADER, - k2kplugin.get_token(self.session)) diff --git a/keystoneauth1/tests/unit/identity/test_identity_v3_oidc.py b/keystoneauth1/tests/unit/identity/test_identity_v3_oidc.py deleted file mode 100644 index 9bf1218..0000000 --- a/keystoneauth1/tests/unit/identity/test_identity_v3_oidc.py +++ /dev/null @@ -1,402 +0,0 @@ -# Licensed under the Apache License, Version 2.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 uuid -import warnings - -from six.moves import urllib - -from keystoneauth1 import exceptions -from keystoneauth1.identity.v3 import oidc -from keystoneauth1 import session -from keystoneauth1.tests.unit import oidc_fixtures -from keystoneauth1.tests.unit import utils - - -KEYSTONE_TOKEN_VALUE = uuid.uuid4().hex - - -class BaseOIDCTests(object): - - def setUp(self): - super(BaseOIDCTests, self).setUp() - self.session = session.Session() - - self.AUTH_URL = 'http://keystone:5000/v3' - self.IDENTITY_PROVIDER = 'bluepages' - self.PROTOCOL = 'oidc' - self.USER_NAME = 'oidc_user@example.com' - self.PROJECT_NAME = 'foo project' - self.PASSWORD = uuid.uuid4().hex - self.CLIENT_ID = uuid.uuid4().hex - self.CLIENT_SECRET = uuid.uuid4().hex - self.ACCESS_TOKEN = uuid.uuid4().hex - self.ACCESS_TOKEN_ENDPOINT = 'https://localhost:8020/oidc/token' - self.FEDERATION_AUTH_URL = '%s/%s' % ( - self.AUTH_URL, - 'OS-FEDERATION/identity_providers/bluepages/protocols/oidc/auth') - self.REDIRECT_URL = 'urn:ietf:wg:oauth:2.0:oob' - self.CODE = '4/M9TNz2G9WVwYxSjx0w9AgA1bOmryJltQvOhQMq0czJs.cnLNVAfqwG' - - self.DISCOVERY_URL = ('https://localhost:8020/oidc/.well-known/' - 'openid-configuration') - self.GRANT_TYPE = None - - def test_grant_type_and_plugin_missmatch(self): - self.assertRaises( - exceptions.OidcGrantTypeMissmatch, - self.plugin.__class__, - self.AUTH_URL, - self.IDENTITY_PROVIDER, - self.PROTOCOL, - client_id=self.CLIENT_ID, - client_secret=self.CLIENT_SECRET, - grant_type=uuid.uuid4().hex - ) - - def test_can_pass_grant_type_but_warning_is_issued(self): - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") - self.plugin.__class__( - self.AUTH_URL, - self.IDENTITY_PROVIDER, - self.PROTOCOL, - client_id=self.CLIENT_ID, - client_secret=self.CLIENT_SECRET, - grant_type=self.GRANT_TYPE) - assert len(w) == 1 - assert issubclass(w[-1].category, DeprecationWarning) - assert "grant_type" in str(w[-1].message) - - def test_discovery_not_found(self): - self.requests_mock.get("http://not.found", - status_code=404) - - plugin = self.plugin.__class__( - self.AUTH_URL, - self.IDENTITY_PROVIDER, - self.PROTOCOL, - client_id=self.CLIENT_ID, - client_secret=self.CLIENT_SECRET, - discovery_endpoint="http://not.found") - - self.assertRaises(exceptions.http.NotFound, - plugin._get_discovery_document, - self.session) - - def test_no_discovery(self): - - plugin = self.plugin.__class__( - self.AUTH_URL, - self.IDENTITY_PROVIDER, - self.PROTOCOL, - client_id=self.CLIENT_ID, - client_secret=self.CLIENT_SECRET, - access_token_endpoint=self.ACCESS_TOKEN_ENDPOINT, - ) - self.assertEqual(self.ACCESS_TOKEN_ENDPOINT, - plugin.access_token_endpoint) - - def test_load_discovery(self): - self.requests_mock.get(self.DISCOVERY_URL, - json=oidc_fixtures.DISCOVERY_DOCUMENT) - - plugin = self.plugin.__class__(self.AUTH_URL, - self.IDENTITY_PROVIDER, - self.PROTOCOL, - client_id=self.CLIENT_ID, - client_secret=self.CLIENT_SECRET, - discovery_endpoint=self.DISCOVERY_URL) - self.assertEqual( - oidc_fixtures.DISCOVERY_DOCUMENT["token_endpoint"], - plugin._get_access_token_endpoint(self.session) - ) - - def test_no_access_token_endpoint(self): - plugin = self.plugin.__class__(self.AUTH_URL, - self.IDENTITY_PROVIDER, - self.PROTOCOL, - client_id=self.CLIENT_ID, - client_secret=self.CLIENT_SECRET) - - self.assertRaises(exceptions.OidcAccessTokenEndpointNotFound, - plugin._get_access_token_endpoint, - self.session) - - def test_invalid_discovery_document(self): - self.requests_mock.get(self.DISCOVERY_URL, - json={}) - - plugin = self.plugin.__class__(self.AUTH_URL, - self.IDENTITY_PROVIDER, - self.PROTOCOL, - client_id=self.CLIENT_ID, - client_secret=self.CLIENT_SECRET, - discovery_endpoint=self.DISCOVERY_URL) - - self.assertRaises(exceptions.InvalidOidcDiscoveryDocument, - plugin._get_discovery_document, - self.session) - - def test_load_discovery_override_by_endpoints(self): - self.requests_mock.get(self.DISCOVERY_URL, - json=oidc_fixtures.DISCOVERY_DOCUMENT) - - access_token_endpoint = uuid.uuid4().hex - plugin = self.plugin.__class__( - self.AUTH_URL, - self.IDENTITY_PROVIDER, - self.PROTOCOL, - client_id=self.CLIENT_ID, - client_secret=self.CLIENT_SECRET, - discovery_endpoint=self.DISCOVERY_URL, - access_token_endpoint=access_token_endpoint - ) - self.assertEqual(access_token_endpoint, - plugin._get_access_token_endpoint(self.session)) - - def test_wrong_grant_type(self): - self.requests_mock.get(self.DISCOVERY_URL, - json={"grant_types_supported": ["foo", "bar"]}) - - plugin = self.plugin.__class__(self.AUTH_URL, - self.IDENTITY_PROVIDER, - self.PROTOCOL, - client_id=self.CLIENT_ID, - client_secret=self.CLIENT_SECRET, - discovery_endpoint=self.DISCOVERY_URL) - - self.assertRaises(exceptions.OidcPluginNotSupported, - plugin.get_unscoped_auth_ref, - self.session) - - -class OIDCClientCredentialsTests(BaseOIDCTests, utils.TestCase): - def setUp(self): - super(OIDCClientCredentialsTests, self).setUp() - - self.GRANT_TYPE = 'client_credentials' - - self.plugin = oidc.OidcClientCredentials( - self.AUTH_URL, - self.IDENTITY_PROVIDER, - self.PROTOCOL, - client_id=self.CLIENT_ID, - client_secret=self.CLIENT_SECRET, - access_token_endpoint=self.ACCESS_TOKEN_ENDPOINT, - project_name=self.PROJECT_NAME) - - def test_initial_call_to_get_access_token(self): - """Test initial call, expect JSON access token.""" - # Mock the output that creates the access token - self.requests_mock.post( - self.ACCESS_TOKEN_ENDPOINT, - json=oidc_fixtures.ACCESS_TOKEN_VIA_PASSWORD_RESP) - - # Prep all the values and send the request - scope = 'profile email' - payload = {'grant_type': self.GRANT_TYPE, 'scope': scope} - self.plugin._get_access_token(self.session, payload) - - # Verify the request matches the expected structure - last_req = self.requests_mock.last_request - self.assertEqual(self.ACCESS_TOKEN_ENDPOINT, last_req.url) - self.assertEqual('POST', last_req.method) - encoded_payload = urllib.parse.urlencode(payload) - self.assertEqual(encoded_payload, last_req.body) - - def test_second_call_to_protected_url(self): - """Test subsequent call, expect Keystone token.""" - # Mock the output that creates the keystone token - self.requests_mock.post( - self.FEDERATION_AUTH_URL, - json=oidc_fixtures.UNSCOPED_TOKEN, - headers={'X-Subject-Token': KEYSTONE_TOKEN_VALUE}) - - res = self.plugin._get_keystone_token(self.session, - self.ACCESS_TOKEN) - - # Verify the request matches the expected structure - self.assertEqual(self.FEDERATION_AUTH_URL, res.request.url) - self.assertEqual('POST', res.request.method) - - headers = {'Authorization': 'Bearer ' + self.ACCESS_TOKEN} - self.assertEqual(headers['Authorization'], - res.request.headers['Authorization']) - - def test_end_to_end_workflow(self): - """Test full OpenID Connect workflow.""" - # Mock the output that creates the access token - self.requests_mock.post( - self.ACCESS_TOKEN_ENDPOINT, - json=oidc_fixtures.ACCESS_TOKEN_VIA_PASSWORD_RESP) - - # Mock the output that creates the keystone token - self.requests_mock.post( - self.FEDERATION_AUTH_URL, - json=oidc_fixtures.UNSCOPED_TOKEN, - headers={'X-Subject-Token': KEYSTONE_TOKEN_VALUE}) - - response = self.plugin.get_unscoped_auth_ref(self.session) - self.assertEqual(KEYSTONE_TOKEN_VALUE, response.auth_token) - - -class OIDCPasswordTests(BaseOIDCTests, utils.TestCase): - def setUp(self): - super(OIDCPasswordTests, self).setUp() - - self.GRANT_TYPE = 'password' - - self.plugin = oidc.OidcPassword( - self.AUTH_URL, - self.IDENTITY_PROVIDER, - self.PROTOCOL, - client_id=self.CLIENT_ID, - client_secret=self.CLIENT_SECRET, - access_token_endpoint=self.ACCESS_TOKEN_ENDPOINT, - project_name=self.PROJECT_NAME, - username=self.USER_NAME, - password=self.PASSWORD) - - def test_initial_call_to_get_access_token(self): - """Test initial call, expect JSON access token.""" - # Mock the output that creates the access token - self.requests_mock.post( - self.ACCESS_TOKEN_ENDPOINT, - json=oidc_fixtures.ACCESS_TOKEN_VIA_PASSWORD_RESP) - - # Prep all the values and send the request - grant_type = 'password' - scope = 'profile email' - payload = {'grant_type': grant_type, 'username': self.USER_NAME, - 'password': self.PASSWORD, 'scope': scope} - self.plugin._get_access_token(self.session, payload) - - # Verify the request matches the expected structure - last_req = self.requests_mock.last_request - self.assertEqual(self.ACCESS_TOKEN_ENDPOINT, last_req.url) - self.assertEqual('POST', last_req.method) - encoded_payload = urllib.parse.urlencode(payload) - self.assertEqual(encoded_payload, last_req.body) - - def test_second_call_to_protected_url(self): - """Test subsequent call, expect Keystone token.""" - # Mock the output that creates the keystone token - self.requests_mock.post( - self.FEDERATION_AUTH_URL, - json=oidc_fixtures.UNSCOPED_TOKEN, - headers={'X-Subject-Token': KEYSTONE_TOKEN_VALUE}) - - res = self.plugin._get_keystone_token(self.session, - self.ACCESS_TOKEN) - - # Verify the request matches the expected structure - self.assertEqual(self.FEDERATION_AUTH_URL, res.request.url) - self.assertEqual('POST', res.request.method) - - headers = {'Authorization': 'Bearer ' + self.ACCESS_TOKEN} - self.assertEqual(headers['Authorization'], - res.request.headers['Authorization']) - - def test_end_to_end_workflow(self): - """Test full OpenID Connect workflow.""" - # Mock the output that creates the access token - self.requests_mock.post( - self.ACCESS_TOKEN_ENDPOINT, - json=oidc_fixtures.ACCESS_TOKEN_VIA_PASSWORD_RESP) - - # Mock the output that creates the keystone token - self.requests_mock.post( - self.FEDERATION_AUTH_URL, - json=oidc_fixtures.UNSCOPED_TOKEN, - headers={'X-Subject-Token': KEYSTONE_TOKEN_VALUE}) - - response = self.plugin.get_unscoped_auth_ref(self.session) - self.assertEqual(KEYSTONE_TOKEN_VALUE, response.auth_token) - - -class OIDCAuthorizationGrantTests(BaseOIDCTests, utils.TestCase): - def setUp(self): - super(OIDCAuthorizationGrantTests, self).setUp() - - self.GRANT_TYPE = 'authorization_code' - - self.plugin = oidc.OidcAuthorizationCode( - self.AUTH_URL, - self.IDENTITY_PROVIDER, - self.PROTOCOL, - client_id=self.CLIENT_ID, - client_secret=self.CLIENT_SECRET, - access_token_endpoint=self.ACCESS_TOKEN_ENDPOINT, - redirect_uri=self.REDIRECT_URL, - project_name=self.PROJECT_NAME, - code=self.CODE) - - def test_initial_call_to_get_access_token(self): - """Test initial call, expect JSON access token.""" - # Mock the output that creates the access token - self.requests_mock.post( - self.ACCESS_TOKEN_ENDPOINT, - json=oidc_fixtures.ACCESS_TOKEN_VIA_AUTH_GRANT_RESP) - - # Prep all the values and send the request - grant_type = 'authorization_code' - payload = {'grant_type': grant_type, - 'redirect_uri': self.REDIRECT_URL, - 'code': self.CODE} - self.plugin._get_access_token(self.session, payload) - - # Verify the request matches the expected structure - last_req = self.requests_mock.last_request - self.assertEqual(self.ACCESS_TOKEN_ENDPOINT, last_req.url) - self.assertEqual('POST', last_req.method) - encoded_payload = urllib.parse.urlencode(payload) - self.assertEqual(encoded_payload, last_req.body) - - -# NOTE(aloga): This is a special case, as we do not need all the other openid -# parameters, like client_id, client_secret, access_token_endpoint and so on, -# therefore we do not inherit from the base oidc test class, but from the base -# TestCase -class OIDCTokenTests(utils.TestCase): - def setUp(self): - super(OIDCTokenTests, self).setUp() - - self.session = session.Session() - - self.AUTH_URL = 'http://keystone:5000/v3' - self.IDENTITY_PROVIDER = 'bluepages' - self.PROTOCOL = 'oidc' - self.PROJECT_NAME = 'foo project' - self.ACCESS_TOKEN = uuid.uuid4().hex - - self.FEDERATION_AUTH_URL = '%s/%s' % ( - self.AUTH_URL, - 'OS-FEDERATION/identity_providers/bluepages/protocols/oidc/auth') - - self.plugin = oidc.OidcAccessToken( - self.AUTH_URL, - self.IDENTITY_PROVIDER, - self.PROTOCOL, - access_token=self.ACCESS_TOKEN, - project_name=self.PROJECT_NAME) - - def test_end_to_end_workflow(self): - """Test full OpenID Connect workflow.""" - # Mock the output that creates the keystone token - self.requests_mock.post( - self.FEDERATION_AUTH_URL, - json=oidc_fixtures.UNSCOPED_TOKEN, - headers={'X-Subject-Token': KEYSTONE_TOKEN_VALUE}) - - response = self.plugin.get_unscoped_auth_ref(self.session) - self.assertEqual(KEYSTONE_TOKEN_VALUE, response.auth_token) diff --git a/keystoneauth1/tests/unit/identity/test_password.py b/keystoneauth1/tests/unit/identity/test_password.py deleted file mode 100644 index 7c91018..0000000 --- a/keystoneauth1/tests/unit/identity/test_password.py +++ /dev/null @@ -1,106 +0,0 @@ -# Licensed under the Apache License, Version 2.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 uuid - -from keystoneauth1.identity.generic import password -from keystoneauth1.identity import v2 -from keystoneauth1.identity import v3 -from keystoneauth1.identity.v3 import password as v3_password -from keystoneauth1.tests.unit.identity import utils - - -class PasswordTests(utils.GenericPluginTestCase): - - PLUGIN_CLASS = password.Password - V2_PLUGIN_CLASS = v2.Password - V3_PLUGIN_CLASS = v3.Password - - def new_plugin(self, **kwargs): - kwargs.setdefault('username', uuid.uuid4().hex) - kwargs.setdefault('password', uuid.uuid4().hex) - return super(PasswordTests, self).new_plugin(**kwargs) - - def test_with_user_domain_params(self): - self.stub_discovery() - - self.assertCreateV3(domain_id=uuid.uuid4().hex, - user_domain_id=uuid.uuid4().hex) - - def test_v3_user_params_v2_url(self): - self.stub_discovery(v3=False) - self.assertDiscoveryFailure(user_domain_id=uuid.uuid4().hex) - - def test_v3_domain_params_v2_url(self): - self.stub_discovery(v3=False) - self.assertDiscoveryFailure(domain_id=uuid.uuid4().hex) - - def test_v3_disocovery_failure_v2_url(self): - auth_url = self.TEST_URL + 'v2.0' - self.stub_url('GET', json={}, base_url='/v2.0', status_code=500) - self.assertDiscoveryFailure(domain_id=uuid.uuid4().hex, - auth_url=auth_url) - - def test_symbols(self): - self.assertIs(v3.Password, v3_password.Password) - self.assertIs(v3.PasswordMethod, v3_password.PasswordMethod) - - def test_default_domain_id_with_v3(self): - default_domain_id = uuid.uuid4().hex - - p = super(PasswordTests, self).test_default_domain_id_with_v3( - default_domain_id=default_domain_id) - - self.assertEqual(default_domain_id, - p._plugin.auth_methods[0].user_domain_id) - - def test_default_domain_name_with_v3(self): - default_domain_name = uuid.uuid4().hex - - p = super(PasswordTests, self).test_default_domain_name_with_v3( - default_domain_name=default_domain_name) - - self.assertEqual(default_domain_name, - p._plugin.auth_methods[0].user_domain_name) - - def test_password_cache_id(self): - username = uuid.uuid4().hex - the_password = uuid.uuid4().hex - project_name = uuid.uuid4().hex - default_domain_id = uuid.uuid4().hex - - a = password.Password(self.TEST_URL, - username=username, - password=the_password, - project_name=project_name, - default_domain_id=default_domain_id) - - b = password.Password(self.TEST_URL, - username=username, - password=the_password, - project_name=project_name, - default_domain_id=default_domain_id) - - a_id = a.get_cache_id() - b_id = b.get_cache_id() - - self.assertEqual(a_id, b_id) - - c = password.Password(self.TEST_URL, - username=username, - password=uuid.uuid4().hex, # different - project_name=project_name, - default_domain_id=default_domain_id) - - c_id = c.get_cache_id() - - self.assertNotEqual(a_id, c_id) diff --git a/keystoneauth1/tests/unit/identity/test_token.py b/keystoneauth1/tests/unit/identity/test_token.py deleted file mode 100644 index 0303fb1..0000000 --- a/keystoneauth1/tests/unit/identity/test_token.py +++ /dev/null @@ -1,63 +0,0 @@ -# Licensed under the Apache License, Version 2.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 uuid - -from keystoneauth1.identity.generic import token -from keystoneauth1.identity import v2 -from keystoneauth1.identity import v3 -from keystoneauth1.identity.v3 import token as v3_token -from keystoneauth1.tests.unit.identity import utils - - -class TokenTests(utils.GenericPluginTestCase): - - PLUGIN_CLASS = token.Token - V2_PLUGIN_CLASS = v2.Token - V3_PLUGIN_CLASS = v3.Token - - def new_plugin(self, **kwargs): - kwargs.setdefault('token', uuid.uuid4().hex) - return super(TokenTests, self).new_plugin(**kwargs) - - def test_symbols(self): - self.assertIs(v3.Token, v3_token.Token) - self.assertIs(v3.TokenMethod, v3_token.TokenMethod) - - def test_token_cache_id(self): - the_token = uuid.uuid4().hex - project_name = uuid.uuid4().hex - default_domain_id = uuid.uuid4().hex - - a = token.Token(self.TEST_URL, - token=the_token, - project_name=project_name, - default_domain_id=default_domain_id) - - b = token.Token(self.TEST_URL, - token=the_token, - project_name=project_name, - default_domain_id=default_domain_id) - - a_id = a.get_cache_id() - b_id = b.get_cache_id() - - self.assertEqual(a_id, b_id) - - c = token.Token(self.TEST_URL, - token=the_token, - project_name=uuid.uuid4().hex, # different - default_domain_id=default_domain_id) - - c_id = c.get_cache_id() - - self.assertNotEqual(a_id, c_id) diff --git a/keystoneauth1/tests/unit/identity/test_tokenless_auth.py b/keystoneauth1/tests/unit/identity/test_tokenless_auth.py deleted file mode 100644 index 160291f..0000000 --- a/keystoneauth1/tests/unit/identity/test_tokenless_auth.py +++ /dev/null @@ -1,105 +0,0 @@ -# Licensed under the Apache License, Version 2.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 uuid - -from keystoneauth1 import exceptions -from keystoneauth1.identity.v3 import tokenless_auth -from keystoneauth1 import session -from keystoneauth1.tests.unit import utils - - -class TokenlessAuthTest(utils.TestCase): - - TEST_URL = 'http://server/prefix' - - def create(self, auth_url, - domain_id=None, - domain_name=None, - project_id=None, - project_name=None, - project_domain_id=None, - project_domain_name=None): - self.requests_mock.get(self.TEST_URL) - auth = tokenless_auth.TokenlessAuth( - auth_url=self.TEST_URL, - domain_id=domain_id, - domain_name=domain_name, - project_id=project_id, - project_name=project_name, - project_domain_id=project_domain_id, - project_domain_name=project_domain_name) - return auth, session.Session(auth=auth) - - def test_domain_id_scope_header_pass(self): - domain_id = uuid.uuid4().hex - auth, session = self.create(auth_url=self.TEST_URL, - domain_id=domain_id) - session.get(self.TEST_URL, authenticated=True) - self.assertRequestHeaderEqual('X-Domain-Id', domain_id) - - def test_domain_name_scope_header_pass(self): - domain_name = uuid.uuid4().hex - auth, session = self.create(auth_url=self.TEST_URL, - domain_name=domain_name) - session.get(self.TEST_URL, authenticated=True) - self.assertRequestHeaderEqual('X-Domain-Name', domain_name) - - def test_project_id_scope_header_pass(self): - project_id = uuid.uuid4().hex - auth, session = self.create(auth_url=self.TEST_URL, - project_id=project_id) - session.get(self.TEST_URL, authenticated=True) - self.assertRequestHeaderEqual('X-Project-Id', project_id) - - def test_project_of_domain_id_scope_header_pass(self): - project_name = uuid.uuid4().hex - project_domain_id = uuid.uuid4().hex - auth, session = self.create(auth_url=self.TEST_URL, - project_name=project_name, - project_domain_id=project_domain_id) - session.get(self.TEST_URL, authenticated=True) - self.assertRequestHeaderEqual('X-Project-Name', project_name) - self.assertRequestHeaderEqual('X-Project-Domain-Id', project_domain_id) - - def test_project_of_domain__name_scope_header_pass(self): - project_name = uuid.uuid4().hex - project_domain_name = uuid.uuid4().hex - auth, session = self.create(auth_url=self.TEST_URL, - project_name=project_name, - project_domain_name=project_domain_name) - session.get(self.TEST_URL, authenticated=True) - self.assertRequestHeaderEqual('X-Project-Name', project_name) - self.assertRequestHeaderEqual('X-Project-Domain-Name', - project_domain_name) - - def test_no_scope_header_fail(self): - auth, session = self.create(auth_url=self.TEST_URL) - self.assertIsNone(auth.get_headers(session)) - msg = 'No valid authentication is available' - self.assertRaisesRegex(exceptions.AuthorizationFailure, - msg, - session.get, - self.TEST_URL, - authenticated=True) - - def test_project_name_scope_only_header_fail(self): - project_name = uuid.uuid4().hex - auth, session = self.create(auth_url=self.TEST_URL, - project_name=project_name) - self.assertIsNone(auth.get_headers(session)) - msg = 'No valid authentication is available' - self.assertRaisesRegex(exceptions.AuthorizationFailure, - msg, - session.get, - self.TEST_URL, - authenticated=True) diff --git a/keystoneauth1/tests/unit/identity/utils.py b/keystoneauth1/tests/unit/identity/utils.py deleted file mode 100644 index 89883b5..0000000 --- a/keystoneauth1/tests/unit/identity/utils.py +++ /dev/null @@ -1,180 +0,0 @@ -# Licensed under the Apache License, Version 2.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 uuid - -from keystoneauth1 import access -from keystoneauth1 import exceptions -from keystoneauth1 import fixture -from keystoneauth1 import session -from keystoneauth1.tests.unit import utils - - -class GenericPluginTestCase(utils.TestCase): - - TEST_URL = 'http://keystone.host:5000/' - - # OVERRIDE THESE IN SUB CLASSES - PLUGIN_CLASS = None - V2_PLUGIN_CLASS = None - V3_PLUGIN_CLASS = None - - def setUp(self): - super(GenericPluginTestCase, self).setUp() - - self.token_v2 = fixture.V2Token() - self.token_v3 = fixture.V3Token() - self.token_v3_id = uuid.uuid4().hex - self.session = session.Session() - - self.stub_url('POST', ['v2.0', 'tokens'], json=self.token_v2) - self.stub_url('POST', ['v3', 'auth', 'tokens'], - headers={'X-Subject-Token': self.token_v3_id}, - json=self.token_v3) - - def new_plugin(self, **kwargs): - kwargs.setdefault('auth_url', self.TEST_URL) - return self.PLUGIN_CLASS(**kwargs) - - def stub_discovery(self, base_url=None, **kwargs): - kwargs.setdefault('href', self.TEST_URL) - disc = fixture.DiscoveryList(**kwargs) - self.stub_url('GET', json=disc, base_url=base_url, status_code=300) - return disc - - def assertCreateV3(self, **kwargs): - auth = self.new_plugin(**kwargs) - auth_ref = auth.get_auth_ref(self.session) - self.assertIsInstance(auth_ref, access.AccessInfoV3) - self.assertEqual(self.TEST_URL + 'v3/auth/tokens', - self.requests_mock.last_request.url) - self.assertIsInstance(auth._plugin, self.V3_PLUGIN_CLASS) - return auth - - def assertCreateV2(self, **kwargs): - auth = self.new_plugin(**kwargs) - auth_ref = auth.get_auth_ref(self.session) - self.assertIsInstance(auth_ref, access.AccessInfoV2) - self.assertEqual(self.TEST_URL + 'v2.0/tokens', - self.requests_mock.last_request.url) - self.assertIsInstance(auth._plugin, self.V2_PLUGIN_CLASS) - return auth - - def assertDiscoveryFailure(self, **kwargs): - plugin = self.new_plugin(**kwargs) - self.assertRaises(exceptions.DiscoveryFailure, - plugin.get_auth_ref, - self.session) - - def test_create_v3_if_domain_params(self): - self.stub_discovery() - - self.assertCreateV3(domain_id=uuid.uuid4().hex) - self.assertCreateV3(domain_name=uuid.uuid4().hex) - self.assertCreateV3(project_name=uuid.uuid4().hex, - project_domain_name=uuid.uuid4().hex) - self.assertCreateV3(project_name=uuid.uuid4().hex, - project_domain_id=uuid.uuid4().hex) - - def test_create_v2_if_no_domain_params(self): - self.stub_discovery() - self.assertCreateV2() - self.assertCreateV2(project_id=uuid.uuid4().hex) - self.assertCreateV2(project_name=uuid.uuid4().hex) - self.assertCreateV2(tenant_id=uuid.uuid4().hex) - self.assertCreateV2(tenant_name=uuid.uuid4().hex) - - def test_create_plugin_no_reauthenticate(self): - self.stub_discovery() - self.assertCreateV2(reauthenticate=False) - self.assertCreateV3(domain_id=uuid.uuid4().hex, reauthenticate=False) - - def test_v3_params_v2_url(self): - self.stub_discovery(v3=False) - self.assertDiscoveryFailure(domain_name=uuid.uuid4().hex) - - def test_v2_params_v3_url(self): - self.stub_discovery(v2=False) - self.assertCreateV3() - - def test_no_urls(self): - self.stub_discovery(v2=False, v3=False) - self.assertDiscoveryFailure() - - def test_path_based_url_v2(self): - self.stub_url('GET', ['v2.0'], status_code=403) - self.assertCreateV2(auth_url=self.TEST_URL + 'v2.0') - - def test_path_based_url_v3(self): - self.stub_url('GET', ['v3'], status_code=403) - self.assertCreateV3(auth_url=self.TEST_URL + 'v3') - - def test_disc_error_for_failure(self): - self.stub_url('GET', [], status_code=403) - self.assertDiscoveryFailure() - self.assertIn(self.TEST_URL, self.logger.output) - - def test_v3_plugin_from_failure(self): - url = self.TEST_URL + 'v3' - self.stub_url('GET', [], base_url=url, status_code=403) - self.assertCreateV3(auth_url=url) - - def test_unknown_discovery_version(self): - # make a v4 entry that's mostly the same as a v3 - self.stub_discovery(v2=False, v3_id='v4.0') - self.assertDiscoveryFailure() - - def test_default_domain_id_with_v3(self, **kwargs): - self.stub_discovery() - project_name = uuid.uuid4().hex - default_domain_id = kwargs.setdefault('default_domain_id', - uuid.uuid4().hex) - - p = self.assertCreateV3(project_name=project_name, **kwargs) - - self.assertEqual(default_domain_id, p._plugin.project_domain_id) - self.assertEqual(project_name, p._plugin.project_name) - - return p - - def test_default_domain_id_no_v3(self): - self.stub_discovery(v3=False) - project_name = uuid.uuid4().hex - default_domain_id = uuid.uuid4().hex - - p = self.assertCreateV2(project_name=project_name, - default_domain_id=default_domain_id) - - self.assertEqual(project_name, p._plugin.tenant_name) - - def test_default_domain_name_with_v3(self, **kwargs): - self.stub_discovery() - project_name = uuid.uuid4().hex - default_domain_name = kwargs.setdefault('default_domain_name', - uuid.uuid4().hex) - - p = self.assertCreateV3(project_name=project_name, **kwargs) - - self.assertEqual(default_domain_name, p._plugin.project_domain_name) - self.assertEqual(project_name, p._plugin.project_name) - - return p - - def test_default_domain_name_no_v3(self): - self.stub_discovery(v3=False) - project_name = uuid.uuid4().hex - default_domain_name = uuid.uuid4().hex - - p = self.assertCreateV2(project_name=project_name, - default_domain_name=default_domain_name) - - self.assertEqual(project_name, p._plugin.tenant_name) diff --git a/keystoneauth1/tests/unit/k2k_fixtures.py b/keystoneauth1/tests/unit/k2k_fixtures.py deleted file mode 100644 index f78cb0e..0000000 --- a/keystoneauth1/tests/unit/k2k_fixtures.py +++ /dev/null @@ -1,148 +0,0 @@ -# Licensed under the Apache License, Version 2.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. - -UNSCOPED_TOKEN_HEADER = 'UNSCOPED_TOKEN' - -UNSCOPED_TOKEN = { - "token": { - "issued_at": "2014-06-09T09:48:59.643406Z", - "extras": {}, - "methods": ["token"], - "expires_at": "2014-06-09T10:48:59.643375Z", - "user": { - "OS-FEDERATION": { - "identity_provider": { - "id": "testshib" - }, - "protocol": { - "id": "saml2" - }, - "groups": [ - {"id": "1764fa5cf69a49a4918131de5ce4af9a"} - ] - }, - "id": "testhib%20user", - "name": "testhib user" - } - } -} - - -SAML_ENCODING = "" - -TOKEN_SAML_RESPONSE = """ - - - http://keystone.idp/v3/OS-FEDERATION/saml2/idp - - - - - - - http://keystone.idp/v3/OS-FEDERATION/saml2/idp - - - - - - - - - - - - 0KH2CxdkfzU+6eiRhTC+mbObUKI= - - - - - m2jh5gDvX/1k+4uKtbb08CHp2b9UWsLw - - - - ... - - - - - admin - - - - - - - - urn:oasis:names:tc:SAML:2.0:ac:classes:Password - - - http://keystone.idp/v3/OS-FEDERATION/saml2/idp - - - - - - admin - - - admin - - - admin - - - - -""" - -TOKEN_BASED_SAML = ''.join([SAML_ENCODING, TOKEN_SAML_RESPONSE]) - -ECP_ENVELOPE = """ - - - - ss:mem:1ddfe8b0f58341a5a840d2e8717b0737 - - - - {0} - - -""".format(TOKEN_SAML_RESPONSE) - -TOKEN_BASED_ECP = ''.join([SAML_ENCODING, ECP_ENVELOPE]) diff --git a/keystoneauth1/tests/unit/keystoneauth_fixtures.py b/keystoneauth1/tests/unit/keystoneauth_fixtures.py deleted file mode 100644 index 11f9697..0000000 --- a/keystoneauth1/tests/unit/keystoneauth_fixtures.py +++ /dev/null @@ -1,75 +0,0 @@ -# Licensed under the Apache License, Version 2.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 fixtures - - -class HackingCode(fixtures.Fixture): - """A fixture to house the various code examples. - - Examples contains various keystoneauth hacking style checks. - """ - - oslo_namespace_imports = { - 'code': """ - import oslo.utils - import oslo_utils - import oslo.utils.encodeutils - import oslo_utils.encodeutils - from oslo import utils - from oslo.utils import encodeutils - from oslo_utils import encodeutils - - import oslo.serialization - import oslo_serialization - import oslo.serialization.jsonutils - import oslo_serialization.jsonutils - from oslo import serialization - from oslo.serialization import jsonutils - from oslo_serialization import jsonutils - - import oslo.config - import oslo_config - import oslo.config.cfg - import oslo_config.cfg - from oslo import config - from oslo.config import cfg - from oslo_config import cfg - - import oslo.i18n - import oslo_i18n - import oslo.i18n.log - import oslo_i18n.log - from oslo import i18n - from oslo.i18n import log - from oslo_i18n import log - """, - 'expected_errors': [ - (1, 0, 'K333'), - (3, 0, 'K333'), - (5, 0, 'K333'), - (6, 0, 'K333'), - (9, 0, 'K333'), - (11, 0, 'K333'), - (13, 0, 'K333'), - (14, 0, 'K333'), - (17, 0, 'K333'), - (19, 0, 'K333'), - (21, 0, 'K333'), - (22, 0, 'K333'), - (25, 0, 'K333'), - (27, 0, 'K333'), - (29, 0, 'K333'), - (30, 0, 'K333'), - ], - } diff --git a/keystoneauth1/tests/unit/loading/__init__.py b/keystoneauth1/tests/unit/loading/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/keystoneauth1/tests/unit/loading/test_adapter.py b/keystoneauth1/tests/unit/loading/test_adapter.py deleted file mode 100644 index 3c7d008..0000000 --- a/keystoneauth1/tests/unit/loading/test_adapter.py +++ /dev/null @@ -1,172 +0,0 @@ -# Licensed under the Apache License, Version 2.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 oslo_config import cfg -from oslo_config import fixture as config - -from keystoneauth1 import loading -from keystoneauth1.tests.unit.loading import utils - - -class ConfLoadingTests(utils.TestCase): - - GROUP = 'adaptergroup' - - def setUp(self): - super(ConfLoadingTests, self).setUp() - - self.conf_fx = self.useFixture(config.Config()) - loading.register_adapter_conf_options(self.conf_fx.conf, self.GROUP) - - def test_load(self): - self.conf_fx.config( - service_type='type', service_name='name', - valid_interfaces='internal', - region_name='region', endpoint_override='endpoint', - version='2.0', group=self.GROUP) - adap = loading.load_adapter_from_conf_options( - self.conf_fx.conf, self.GROUP, session='session', auth='auth') - self.assertEqual('type', adap.service_type) - self.assertEqual('name', adap.service_name) - self.assertEqual(['internal'], adap.interface) - self.assertEqual('region', adap.region_name) - self.assertEqual('endpoint', adap.endpoint_override) - self.assertEqual('session', adap.session) - self.assertEqual('auth', adap.auth) - self.assertEqual('2.0', adap.version) - self.assertIsNone(adap.min_version) - self.assertIsNone(adap.max_version) - - def test_load_valid_interfaces_list(self): - self.conf_fx.config( - service_type='type', service_name='name', - valid_interfaces=['internal', 'public'], - region_name='region', endpoint_override='endpoint', - version='2.0', group=self.GROUP) - adap = loading.load_adapter_from_conf_options( - self.conf_fx.conf, self.GROUP, session='session', auth='auth') - self.assertEqual('type', adap.service_type) - self.assertEqual('name', adap.service_name) - self.assertEqual(['internal', 'public'], adap.interface) - self.assertEqual('region', adap.region_name) - self.assertEqual('endpoint', adap.endpoint_override) - self.assertEqual('session', adap.session) - self.assertEqual('auth', adap.auth) - self.assertEqual('2.0', adap.version) - self.assertIsNone(adap.min_version) - self.assertIsNone(adap.max_version) - - def test_load_valid_interfaces_comma_list(self): - self.conf_fx.config( - service_type='type', service_name='name', - valid_interfaces='internal,public', - region_name='region', endpoint_override='endpoint', - version='2.0', group=self.GROUP) - adap = loading.load_adapter_from_conf_options( - self.conf_fx.conf, self.GROUP, session='session', auth='auth') - self.assertEqual('type', adap.service_type) - self.assertEqual('name', adap.service_name) - self.assertEqual(['internal', 'public'], adap.interface) - self.assertEqual('region', adap.region_name) - self.assertEqual('endpoint', adap.endpoint_override) - self.assertEqual('session', adap.session) - self.assertEqual('auth', adap.auth) - self.assertEqual('2.0', adap.version) - self.assertIsNone(adap.min_version) - self.assertIsNone(adap.max_version) - - def test_load_old_interface(self): - self.conf_fx.config( - service_type='type', service_name='name', - interface='internal', - region_name='region', endpoint_override='endpoint', - version='2.0', group=self.GROUP) - adap = loading.load_adapter_from_conf_options( - self.conf_fx.conf, self.GROUP, session='session', auth='auth') - self.assertEqual('type', adap.service_type) - self.assertEqual('name', adap.service_name) - self.assertEqual('internal', adap.interface) - self.assertEqual('region', adap.region_name) - self.assertEqual('endpoint', adap.endpoint_override) - self.assertEqual('session', adap.session) - self.assertEqual('auth', adap.auth) - self.assertEqual('2.0', adap.version) - self.assertIsNone(adap.min_version) - self.assertIsNone(adap.max_version) - - def test_load_bad_valid_interfaces_value(self): - self.conf_fx.config( - service_type='type', service_name='name', - valid_interfaces='bad', - region_name='region', endpoint_override='endpoint', - version='2.0', group=self.GROUP) - self.assertRaises( - TypeError, - loading.load_adapter_from_conf_options, - self.conf_fx.conf, self.GROUP, session='session', auth='auth') - - def test_load_version_range(self): - self.conf_fx.config( - service_type='type', service_name='name', - valid_interfaces='internal', - region_name='region', endpoint_override='endpoint', - min_version='2.0', max_version='3.0', group=self.GROUP) - adap = loading.load_adapter_from_conf_options( - self.conf_fx.conf, self.GROUP, session='session', auth='auth') - self.assertEqual('type', adap.service_type) - self.assertEqual('name', adap.service_name) - self.assertEqual(['internal'], adap.interface) - self.assertEqual('region', adap.region_name) - self.assertEqual('endpoint', adap.endpoint_override) - self.assertEqual('session', adap.session) - self.assertEqual('auth', adap.auth) - self.assertIsNone(adap.version) - self.assertEqual('2.0', adap.min_version) - self.assertEqual('3.0', adap.max_version) - - def test_interface_conflict(self): - self.conf_fx.config( - service_type='type', service_name='name', interface='iface', - valid_interfaces='internal,public', - region_name='region', endpoint_override='endpoint', - group=self.GROUP) - - self.assertRaises( - TypeError, - loading.load_adapter_from_conf_options, - self.conf_fx.conf, self.GROUP, session='session', auth='auth') - - def test_load_bad_version(self): - self.conf_fx.config( - service_type='type', service_name='name', - valid_interfaces='iface', - region_name='region', endpoint_override='endpoint', - version='2.0', min_version='2.0', max_version='3.0', - group=self.GROUP) - - self.assertRaises( - TypeError, - loading.load_adapter_from_conf_options, - self.conf_fx.conf, self.GROUP, session='session', auth='auth') - - def test_get_conf_options(self): - opts = loading.get_adapter_conf_options() - for opt in opts: - if opt.name != 'valid-interfaces': - self.assertIsInstance(opt, cfg.StrOpt) - else: - self.assertIsInstance(opt, cfg.ListOpt) - self.assertEqual({'service-type', 'service-name', - 'interface', 'valid-interfaces', - 'region-name', 'endpoint-override', 'version', - 'min-version', 'max-version'}, - {opt.name for opt in opts}) diff --git a/keystoneauth1/tests/unit/loading/test_cli.py b/keystoneauth1/tests/unit/loading/test_cli.py deleted file mode 100644 index 5bbc32c..0000000 --- a/keystoneauth1/tests/unit/loading/test_cli.py +++ /dev/null @@ -1,217 +0,0 @@ -# Licensed under the Apache License, Version 2.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 argparse -import uuid - -import fixtures -import mock - -from keystoneauth1 import loading -from keystoneauth1.loading import cli -from keystoneauth1.tests.unit.loading import utils - - -TesterPlugin, TesterLoader = utils.create_plugin( - opts=[ - loading.Opt('test-opt', - help='tester', - deprecated=[loading.Opt('test-other')]) - ] -) - - -class CliTests(utils.TestCase): - - def setUp(self): - super(CliTests, self).setUp() - self.p = argparse.ArgumentParser() - - def env(self, name, value=None): - if value is not None: - # environment variables are always strings - value = str(value) - - return self.useFixture(fixtures.EnvironmentVariable(name, value)) - - def test_creating_with_no_args(self): - ret = loading.register_auth_argparse_arguments(self.p, []) - self.assertIsNone(ret) - self.assertIn('--os-auth-type', self.p.format_usage()) - - def test_load_with_nothing(self): - loading.register_auth_argparse_arguments(self.p, []) - opts = self.p.parse_args([]) - self.assertIsNone(loading.load_auth_from_argparse_arguments(opts)) - - @utils.mock_plugin() - def test_basic_params_added(self, m): - name = uuid.uuid4().hex - argv = ['--os-auth-plugin', name] - ret = loading.register_auth_argparse_arguments(self.p, argv) - self.assertIsInstance(ret, utils.MockLoader) - - for n in ('--os-a-int', '--os-a-bool', '--os-a-float'): - self.assertIn(n, self.p.format_usage()) - - m.assert_called_once_with(name) - - @utils.mock_plugin() - def test_param_loading(self, m): - name = uuid.uuid4().hex - argv = ['--os-auth-type', name, - '--os-a-int', str(self.a_int), - '--os-a-float', str(self.a_float), - '--os-a-bool', str(self.a_bool)] - - klass = loading.register_auth_argparse_arguments(self.p, argv) - self.assertIsInstance(klass, utils.MockLoader) - - opts = self.p.parse_args(argv) - self.assertEqual(name, opts.os_auth_type) - - a = loading.load_auth_from_argparse_arguments(opts) - self.assertTestVals(a) - - self.assertEqual(name, opts.os_auth_type) - self.assertEqual(str(self.a_int), opts.os_a_int) - self.assertEqual(str(self.a_float), opts.os_a_float) - self.assertEqual(str(self.a_bool), opts.os_a_bool) - - @utils.mock_plugin() - def test_default_options(self, m): - name = uuid.uuid4().hex - argv = ['--os-auth-type', name, - '--os-a-float', str(self.a_float)] - - klass = loading.register_auth_argparse_arguments(self.p, argv) - self.assertIsInstance(klass, utils.MockLoader) - - opts = self.p.parse_args(argv) - self.assertEqual(name, opts.os_auth_type) - - a = loading.load_auth_from_argparse_arguments(opts) - - self.assertEqual(self.a_float, a['a_float']) - self.assertEqual(3, a['a_int']) - - @utils.mock_plugin() - def test_with_default_string_value(self, m): - name = uuid.uuid4().hex - klass = loading.register_auth_argparse_arguments(self.p, - [], - default=name) - self.assertIsInstance(klass, utils.MockLoader) - m.assert_called_once_with(name) - - @utils.mock_plugin() - def test_overrides_default_string_value(self, m): - name = uuid.uuid4().hex - default = uuid.uuid4().hex - argv = ['--os-auth-type', name] - klass = loading.register_auth_argparse_arguments(self.p, - argv, - default=default) - self.assertIsInstance(klass, utils.MockLoader) - m.assert_called_once_with(name) - - @utils.mock_plugin() - def test_with_default_type_value(self, m): - default = utils.MockLoader() - klass = loading.register_auth_argparse_arguments(self.p, - [], - default=default) - self.assertIsInstance(klass, utils.MockLoader) - self.assertEqual(0, m.call_count) - - @utils.mock_plugin() - def test_overrides_default_type_value(self, m): - # using this test plugin would fail if called because there - # is no get_options() function - class TestLoader(object): - pass - name = uuid.uuid4().hex - argv = ['--os-auth-type', name] - klass = loading.register_auth_argparse_arguments(self.p, - argv, - default=TestLoader) - self.assertIsInstance(klass, utils.MockLoader) - m.assert_called_once_with(name) - - @utils.mock_plugin() - def test_env_overrides_default_opt(self, m): - name = uuid.uuid4().hex - val = uuid.uuid4().hex - self.env('OS_A_STR', val) - - klass = loading.register_auth_argparse_arguments(self.p, - [], - default=name) - self.assertIsInstance(klass, utils.MockLoader) - opts = self.p.parse_args([]) - a = loading.load_auth_from_argparse_arguments(opts) - - self.assertEqual(val, a['a_str']) - - def test_deprecated_cli_options(self): - cli._register_plugin_argparse_arguments(self.p, TesterLoader()) - val = uuid.uuid4().hex - opts = self.p.parse_args(['--os-test-other', val]) - self.assertEqual(val, opts.os_test_opt) - - def test_deprecated_multi_cli_options(self): - cli._register_plugin_argparse_arguments(self.p, TesterLoader()) - val1 = uuid.uuid4().hex - val2 = uuid.uuid4().hex - # argarse rules say that the last specified wins. - opts = self.p.parse_args(['--os-test-other', val2, - '--os-test-opt', val1]) - self.assertEqual(val1, opts.os_test_opt) - - def test_deprecated_env_options(self): - val = uuid.uuid4().hex - - with mock.patch.dict('os.environ', {'OS_TEST_OTHER': val}): - cli._register_plugin_argparse_arguments(self.p, TesterLoader()) - - opts = self.p.parse_args([]) - self.assertEqual(val, opts.os_test_opt) - - def test_deprecated_env_multi_options(self): - val1 = uuid.uuid4().hex - val2 = uuid.uuid4().hex - - with mock.patch.dict('os.environ', {'OS_TEST_OPT': val1, - 'OS_TEST_OTHER': val2}): - cli._register_plugin_argparse_arguments(self.p, TesterLoader()) - - opts = self.p.parse_args([]) - self.assertEqual(val1, opts.os_test_opt) - - def test_adapter_service_type(self): - argv = ['--os-service-type', 'compute'] - - loading.register_adapter_argparse_arguments(self.p, 'compute') - - opts = self.p.parse_args(argv) - self.assertEqual('compute', opts.os_service_type) - self.assertFalse(hasattr(opts, 'os_compute_service_type')) - - def test_adapter_service_type_per_service(self): - argv = ['--os-compute-service-type', 'weirdness'] - - loading.register_adapter_argparse_arguments(self.p, 'compute') - loading.register_service_adapter_argparse_arguments(self.p, 'compute') - - opts = self.p.parse_args(argv) - self.assertEqual('compute', opts.os_service_type) - self.assertEqual('weirdness', opts.os_compute_service_type) diff --git a/keystoneauth1/tests/unit/loading/test_conf.py b/keystoneauth1/tests/unit/loading/test_conf.py deleted file mode 100644 index ce3e9ab..0000000 --- a/keystoneauth1/tests/unit/loading/test_conf.py +++ /dev/null @@ -1,207 +0,0 @@ -# Licensed under the Apache License, Version 2.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 uuid - -import mock -from oslo_config import cfg -from oslo_config import fixture as config -import stevedore - -from keystoneauth1 import exceptions -from keystoneauth1 import loading -from keystoneauth1.loading._plugins.identity import v2 -from keystoneauth1.loading._plugins.identity import v3 -from keystoneauth1.tests.unit.loading import utils - - -class ConfTests(utils.TestCase): - - def setUp(self): - super(ConfTests, self).setUp() - self.conf_fixture = self.useFixture(config.Config()) - - # NOTE(jamielennox): we register the basic config options first because - # we need them in place before we can stub them. We will need to run - # the register again after we stub the auth section and auth plugin so - # it can load the plugin specific options. - loading.register_auth_conf_options(self.conf_fixture.conf, - group=self.GROUP) - - def test_loading_v2(self): - section = uuid.uuid4().hex - auth_url = uuid.uuid4().hex - username = uuid.uuid4().hex - password = uuid.uuid4().hex - trust_id = uuid.uuid4().hex - tenant_id = uuid.uuid4().hex - - self.conf_fixture.config(auth_section=section, group=self.GROUP) - loading.register_auth_conf_options(self.conf_fixture.conf, - group=self.GROUP) - - opts = loading.get_auth_plugin_conf_options(v2.Password()) - self.conf_fixture.register_opts(opts, group=section) - - self.conf_fixture.config(auth_type=self.V2PASS, - auth_url=auth_url, - username=username, - password=password, - trust_id=trust_id, - tenant_id=tenant_id, - group=section) - - a = loading.load_auth_from_conf_options(self.conf_fixture.conf, - self.GROUP) - - self.assertEqual(auth_url, a.auth_url) - self.assertEqual(username, a.username) - self.assertEqual(password, a.password) - self.assertEqual(trust_id, a.trust_id) - self.assertEqual(tenant_id, a.tenant_id) - - def test_loading_v3(self): - section = uuid.uuid4().hex - auth_url = uuid.uuid4().hex, - token = uuid.uuid4().hex - trust_id = uuid.uuid4().hex - project_id = uuid.uuid4().hex - project_domain_name = uuid.uuid4().hex - - self.conf_fixture.config(auth_section=section, group=self.GROUP) - loading.register_auth_conf_options(self.conf_fixture.conf, - group=self.GROUP) - - opts = loading.get_auth_plugin_conf_options(v3.Token()) - self.conf_fixture.register_opts(opts, group=section) - - self.conf_fixture.config(auth_type=self.V3TOKEN, - auth_url=auth_url, - token=token, - trust_id=trust_id, - project_id=project_id, - project_domain_name=project_domain_name, - group=section) - - a = loading.load_auth_from_conf_options(self.conf_fixture.conf, - self.GROUP) - - self.assertEqual(token, a.auth_methods[0].token) - self.assertEqual(trust_id, a.trust_id) - self.assertEqual(project_id, a.project_id) - self.assertEqual(project_domain_name, a.project_domain_name) - - def test_loading_invalid_plugin(self): - auth_type = uuid.uuid4().hex - self.conf_fixture.config(auth_type=auth_type, - group=self.GROUP) - - e = self.assertRaises(exceptions.NoMatchingPlugin, - loading.load_auth_from_conf_options, - self.conf_fixture.conf, - self.GROUP) - - self.assertEqual(auth_type, e.name) - - def test_loading_with_no_data(self): - l = loading.load_auth_from_conf_options(self.conf_fixture.conf, - self.GROUP) - self.assertIsNone(l) - - @mock.patch('stevedore.DriverManager') - def test_other_params(self, m): - m.return_value = utils.MockManager(utils.MockLoader()) - driver_name = uuid.uuid4().hex - - opts = loading.get_auth_plugin_conf_options(utils.MockLoader()) - self.conf_fixture.register_opts(opts, group=self.GROUP) - self.conf_fixture.config(auth_type=driver_name, - group=self.GROUP, - **self.TEST_VALS) - - a = loading.load_auth_from_conf_options(self.conf_fixture.conf, - self.GROUP) - self.assertTestVals(a) - - m.assert_called_once_with(namespace=loading.PLUGIN_NAMESPACE, - name=driver_name, - invoke_on_load=True) - - @utils.mock_plugin() - def test_same_section(self, m): - opts = loading.get_auth_plugin_conf_options(utils.MockLoader()) - self.conf_fixture.register_opts(opts, group=self.GROUP) - - loading.register_auth_conf_options(self.conf_fixture.conf, - group=self.GROUP) - self.conf_fixture.config(auth_type=uuid.uuid4().hex, - group=self.GROUP, - **self.TEST_VALS) - - a = loading.load_auth_from_conf_options(self.conf_fixture.conf, - self.GROUP) - self.assertTestVals(a) - - @utils.mock_plugin() - def test_diff_section(self, m): - section = uuid.uuid4().hex - - self.conf_fixture.config(auth_section=section, group=self.GROUP) - loading.register_auth_conf_options(self.conf_fixture.conf, - group=self.GROUP) - - opts = loading.get_auth_plugin_conf_options(utils.MockLoader()) - self.conf_fixture.register_opts(opts, group=section) - self.conf_fixture.config(group=section, - auth_type=uuid.uuid4().hex, - **self.TEST_VALS) - - a = loading.load_auth_from_conf_options(self.conf_fixture.conf, - self.GROUP) - self.assertTestVals(a) - - def test_plugins_are_all_opts(self): - manager = stevedore.ExtensionManager(loading.PLUGIN_NAMESPACE, - propagate_map_exceptions=True) - - def inner(driver): - for p in driver.plugin().get_options(): - self.assertIsInstance(p, loading.Opt) - - manager.map(inner) - - def test_get_common(self): - opts = loading.get_auth_common_conf_options() - for opt in opts: - self.assertIsInstance(opt, cfg.Opt) - self.assertEqual(2, len(opts)) - - def test_get_named(self): - loaded_opts = loading.get_plugin_options('v2password') - plugin_opts = v2.Password().get_options() - - loaded_names = set([o.name for o in loaded_opts]) - plugin_names = set([o.name for o in plugin_opts]) - - self.assertEqual(plugin_names, loaded_names) - - def test_register_cfg(self): - loading.register_auth_conf_options(self.conf_fixture.conf, - group=self.GROUP) - - def test_common_conf_options(self): - opts = loading.get_auth_common_conf_options() - - self.assertEqual(2, len(opts)) - auth_type = [o for o in opts if o.name == 'auth_type'][0] - self.assertEqual(1, len(auth_type.deprecated_opts)) - self.assertIsInstance(auth_type.deprecated_opts[0], cfg.DeprecatedOpt) diff --git a/keystoneauth1/tests/unit/loading/test_entry_points.py b/keystoneauth1/tests/unit/loading/test_entry_points.py deleted file mode 100644 index 49968ef..0000000 --- a/keystoneauth1/tests/unit/loading/test_entry_points.py +++ /dev/null @@ -1,35 +0,0 @@ -# Licensed under the Apache License, Version 2.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 stevedore - -from keystoneauth1 import loading -from keystoneauth1.tests.unit.loading import utils - - -class EntryPointTests(utils.TestCase): - """Simple test that will check that all entry points are loadable.""" - - def test_all_entry_points_are_valid(self): - errors = [] - - def raise_exception_callback(manager, entrypoint, exc): - error = ("Cannot load '%(entrypoint)s' entry_point: %(error)s'" % - {"entrypoint": entrypoint, "error": exc}) - errors.append(error) - - stevedore.ExtensionManager( - namespace=loading.PLUGIN_NAMESPACE, - on_load_failure_callback=raise_exception_callback - ) - - self.assertEqual([], errors) diff --git a/keystoneauth1/tests/unit/loading/test_generic.py b/keystoneauth1/tests/unit/loading/test_generic.py deleted file mode 100644 index 3973e6b..0000000 --- a/keystoneauth1/tests/unit/loading/test_generic.py +++ /dev/null @@ -1,86 +0,0 @@ -# Licensed under the Apache License, Version 2.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 uuid - -from keystoneauth1 import fixture -from keystoneauth1 import identity -from keystoneauth1.loading._plugins.identity import generic -from keystoneauth1 import session -from keystoneauth1.tests.unit.loading import utils - - -class PasswordTests(utils.TestCase): - - def test_options(self): - opts = [o.name for o in generic.Password().get_options()] - - allowed_opts = ['username', - 'user-domain-id', - 'user-domain-name', - 'user-id', - 'password', - - 'domain-id', - 'domain-name', - 'project-id', - 'project-name', - 'project-domain-id', - 'project-domain-name', - 'trust-id', - 'auth-url', - 'default-domain-id', - 'default-domain-name', - ] - - self.assertEqual(set(allowed_opts), set(opts)) - self.assertEqual(len(allowed_opts), len(opts)) - - def test_loads_v3_with_user_domain(self): - auth_url = 'http://keystone.test:5000' - disc = fixture.DiscoveryList(href=auth_url) - sess = session.Session() - self.requests_mock.get(auth_url, json=disc) - - plugin = generic.Password().load_from_options( - auth_url=auth_url, - user_id=uuid.uuid4().hex, - password=uuid.uuid4().hex, - project_id=uuid.uuid4().hex, - user_domain_id=uuid.uuid4().hex) - - inner_plugin = plugin._do_create_plugin(sess) - - self.assertIsInstance(inner_plugin, identity.V3Password) - self.assertEqual(inner_plugin.auth_url, auth_url + '/v3') - - -class TokenTests(utils.TestCase): - - def test_options(self): - opts = [o.name for o in generic.Token().get_options()] - - allowed_opts = ['token', - 'domain-id', - 'domain-name', - 'project-id', - 'project-name', - 'project-domain-id', - 'project-domain-name', - 'trust-id', - 'auth-url', - 'default-domain-id', - 'default-domain-name', - ] - - self.assertEqual(set(allowed_opts), set(opts)) - self.assertEqual(len(allowed_opts), len(opts)) diff --git a/keystoneauth1/tests/unit/loading/test_loading.py b/keystoneauth1/tests/unit/loading/test_loading.py deleted file mode 100644 index ce07c1f..0000000 --- a/keystoneauth1/tests/unit/loading/test_loading.py +++ /dev/null @@ -1,145 +0,0 @@ -# Licensed under the Apache License, Version 2.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 uuid - -from testtools import matchers - -from keystoneauth1 import exceptions -from keystoneauth1 import loading -from keystoneauth1.tests.unit.loading import utils - - -class PluginA(object): - - def __init__(self, a): - self.val = a - - -class PluginB(object): - - def __init__(self, b): - self.val = b - - -class TestSplitLoader(loading.BaseLoader): - - def get_options(self): - opts = super(TestSplitLoader, self).get_options() - opts += [loading.Opt('a'), loading.Opt('b')] - return opts - - def create_plugin(self, a=None, b=None, **kwargs): - if a: - return PluginA(a) - if b: - return PluginB(b) - - raise AssertionError('Expected A or B') - - -class LoadingTests(utils.TestCase): - - def test_required_values(self): - opts = [loading.Opt('a', required=False), - loading.Opt('b', required=True)] - - Plugin, Loader = utils.create_plugin(opts=opts) - - l = Loader() - v = uuid.uuid4().hex - - p1 = l.load_from_options(b=v) - self.assertEqual(v, p1['b']) - - e = self.assertRaises(exceptions.MissingRequiredOptions, - l.load_from_options, - a=v) - - self.assertEqual(1, len(e.options)) - - for o in e.options: - self.assertIsInstance(o, loading.Opt) - - self.assertEqual('b', e.options[0].name) - - def test_loaders(self): - loaders = loading.get_available_plugin_loaders() - self.assertThat(len(loaders), matchers.GreaterThan(0)) - - for l in loaders.values(): - self.assertIsInstance(l, loading.BaseLoader) - - def test_loading_getter(self): - - called_opts = [] - - vals = {'a-int': 44, - 'a-bool': False, - 'a-float': 99.99, - 'a-str': 'value'} - - val = uuid.uuid4().hex - - def _getter(opt): - called_opts.append(opt.name) - # return str because oslo.config should convert them back - return str(vals[opt.name]) - - p = utils.MockLoader().load_from_options_getter(_getter, other=val) - - self.assertEqual(set(vals), set(called_opts)) - - for k, v in vals.items(): - # replace - to _ because it's the dest used to create kwargs - self.assertEqual(v, p[k.replace('-', '_')]) - - # check that additional kwargs get passed through - self.assertEqual(val, p['other']) - - def test_loading_getter_with_kwargs(self): - called_opts = set() - - vals = {'a-bool': False, - 'a-float': 99.99} - - def _getter(opt): - called_opts.add(opt.name) - # return str because oslo.config should convert them back - return str(vals[opt.name]) - - p = utils.MockLoader().load_from_options_getter(_getter, - a_int=66, - a_str='another') - - # only the options not passed by kwargs should get passed to getter - self.assertEqual(set(('a-bool', 'a-float')), called_opts) - - self.assertFalse(p['a_bool']) - self.assertEqual(99.99, p['a_float']) - self.assertEqual('another', p['a_str']) - self.assertEqual(66, p['a_int']) - - def test_create_plugin_loader(self): - val_a = uuid.uuid4().hex - val_b = uuid.uuid4().hex - - loader = TestSplitLoader() - - plugin_a = loader.load_from_options(a=val_a) - plugin_b = loader.load_from_options(b=val_b) - - self.assertIsInstance(plugin_a, PluginA) - self.assertIsInstance(plugin_b, PluginB) - - self.assertEqual(val_a, plugin_a.val) - self.assertEqual(val_b, plugin_b.val) diff --git a/keystoneauth1/tests/unit/loading/test_session.py b/keystoneauth1/tests/unit/loading/test_session.py deleted file mode 100644 index 253ec34..0000000 --- a/keystoneauth1/tests/unit/loading/test_session.py +++ /dev/null @@ -1,114 +0,0 @@ -# Licensed under the Apache License, Version 2.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 argparse -import uuid - -from oslo_config import cfg -from oslo_config import fixture as config -from testtools import matchers - -from keystoneauth1 import loading -from keystoneauth1.tests.unit.loading import utils - - -class ConfLoadingTests(utils.TestCase): - - GROUP = 'sessiongroup' - - def setUp(self): - super(ConfLoadingTests, self).setUp() - - self.conf_fixture = self.useFixture(config.Config()) - loading.register_session_conf_options(self.conf_fixture.conf, - self.GROUP) - - def config(self, **kwargs): - kwargs['group'] = self.GROUP - self.conf_fixture.config(**kwargs) - - def get_session(self, **kwargs): - return loading.load_session_from_conf_options(self.conf_fixture.conf, - self.GROUP, - **kwargs) - - def test_insecure_timeout(self): - self.config(insecure=True, timeout=5) - s = self.get_session() - - self.assertFalse(s.verify) - self.assertEqual(5, s.timeout) - - def test_client_certs(self): - cert = '/path/to/certfile' - key = '/path/to/keyfile' - - self.config(certfile=cert, keyfile=key) - s = self.get_session() - - self.assertTrue(s.verify) - self.assertEqual((cert, key), s.cert) - - def test_cacert(self): - cafile = '/path/to/cacert' - - self.config(cafile=cafile) - s = self.get_session() - - self.assertEqual(cafile, s.verify) - - def test_deprecated(self): - def new_deprecated(): - return cfg.DeprecatedOpt(uuid.uuid4().hex, group=uuid.uuid4().hex) - - opt_names = ['cafile', 'certfile', 'keyfile', 'insecure', 'timeout'] - depr = dict([(n, [new_deprecated()]) for n in opt_names]) - opts = loading.get_session_conf_options(deprecated_opts=depr) - - self.assertThat(opt_names, matchers.HasLength(len(opts))) - for opt in opts: - self.assertIn(depr[opt.name][0], opt.deprecated_opts) - - -class CliLoadingTests(utils.TestCase): - - def setUp(self): - super(CliLoadingTests, self).setUp() - - self.parser = argparse.ArgumentParser() - loading.register_session_argparse_arguments(self.parser) - - def get_session(self, val, **kwargs): - args = self.parser.parse_args(val.split()) - return loading.load_session_from_argparse_arguments(args, **kwargs) - - def test_insecure_timeout(self): - s = self.get_session('--insecure --timeout 5.5') - - self.assertFalse(s.verify) - self.assertEqual(5.5, s.timeout) - - def test_client_certs(self): - cert = '/path/to/certfile' - key = '/path/to/keyfile' - - s = self.get_session('--os-cert %s --os-key %s' % (cert, key)) - - self.assertTrue(s.verify) - self.assertEqual((cert, key), s.cert) - - def test_cacert(self): - cacert = '/path/to/cacert' - - s = self.get_session('--os-cacert %s' % cacert) - - self.assertEqual(cacert, s.verify) diff --git a/keystoneauth1/tests/unit/loading/test_v3.py b/keystoneauth1/tests/unit/loading/test_v3.py deleted file mode 100644 index 516c0ec..0000000 --- a/keystoneauth1/tests/unit/loading/test_v3.py +++ /dev/null @@ -1,365 +0,0 @@ -# Licensed under the Apache License, Version 2.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 uuid - -from keystoneauth1 import exceptions -from keystoneauth1 import loading -from keystoneauth1.tests.unit.loading import utils - - -class V3PasswordTests(utils.TestCase): - - def setUp(self): - super(V3PasswordTests, self).setUp() - - self.auth_url = uuid.uuid4().hex - - def create(self, **kwargs): - kwargs.setdefault('auth_url', self.auth_url) - loader = loading.get_plugin_loader('v3password') - return loader.load_from_options(**kwargs) - - def test_basic(self): - username = uuid.uuid4().hex - user_domain_id = uuid.uuid4().hex - password = uuid.uuid4().hex - project_name = uuid.uuid4().hex - project_domain_id = uuid.uuid4().hex - - p = self.create(username=username, - user_domain_id=user_domain_id, - project_name=project_name, - project_domain_id=project_domain_id, - password=password) - - pw_method = p.auth_methods[0] - - self.assertEqual(username, pw_method.username) - self.assertEqual(user_domain_id, pw_method.user_domain_id) - self.assertEqual(password, pw_method.password) - - self.assertEqual(project_name, p.project_name) - self.assertEqual(project_domain_id, p.project_domain_id) - - def test_without_user_domain(self): - self.assertRaises(exceptions.OptionError, - self.create, - username=uuid.uuid4().hex, - password=uuid.uuid4().hex) - - def test_without_project_domain(self): - self.assertRaises(exceptions.OptionError, - self.create, - username=uuid.uuid4().hex, - password=uuid.uuid4().hex, - user_domain_id=uuid.uuid4().hex, - project_name=uuid.uuid4().hex) - - -class TOTPTests(utils.TestCase): - - def setUp(self): - super(TOTPTests, self).setUp() - - self.auth_url = uuid.uuid4().hex - - def create(self, **kwargs): - kwargs.setdefault('auth_url', self.auth_url) - loader = loading.get_plugin_loader('v3totp') - return loader.load_from_options(**kwargs) - - def test_basic(self): - username = uuid.uuid4().hex - user_domain_id = uuid.uuid4().hex - # passcode is 6 digits - passcode = ''.join(str(random.randint(0, 9)) for x in range(6)) - project_name = uuid.uuid4().hex - project_domain_id = uuid.uuid4().hex - - p = self.create(username=username, - user_domain_id=user_domain_id, - project_name=project_name, - project_domain_id=project_domain_id, - passcode=passcode) - - totp_method = p.auth_methods[0] - - self.assertEqual(username, totp_method.username) - self.assertEqual(user_domain_id, totp_method.user_domain_id) - self.assertEqual(passcode, totp_method.passcode) - - self.assertEqual(project_name, p.project_name) - self.assertEqual(project_domain_id, p.project_domain_id) - - def test_without_user_domain(self): - self.assertRaises(exceptions.OptionError, - self.create, - username=uuid.uuid4().hex, - passcode=uuid.uuid4().hex) - - def test_without_project_domain(self): - self.assertRaises(exceptions.OptionError, - self.create, - username=uuid.uuid4().hex, - passcode=uuid.uuid4().hex, - user_domain_id=uuid.uuid4().hex, - project_name=uuid.uuid4().hex) - - -class OpenIDConnectBaseTests(object): - - plugin_name = None - - def setUp(self): - super(OpenIDConnectBaseTests, self).setUp() - - self.auth_url = uuid.uuid4().hex - - def create(self, **kwargs): - kwargs.setdefault('auth_url', self.auth_url) - loader = loading.get_plugin_loader(self.plugin_name) - return loader.load_from_options(**kwargs) - - def test_base_options_are_there(self): - options = loading.get_plugin_loader(self.plugin_name).get_options() - self.assertTrue( - set(['client-id', 'client-secret', 'access-token-endpoint', - 'access-token-type', 'openid-scope', - 'discovery-endpoint']).issubset( - set([o.name for o in options])) - ) - # openid-scope gets renamed into "scope" - self.assertIn('scope', [o.dest for o in options]) - - -class OpenIDConnectClientCredentialsTests(OpenIDConnectBaseTests, - utils.TestCase): - - plugin_name = "v3oidcclientcredentials" - - def test_options(self): - options = loading.get_plugin_loader(self.plugin_name).get_options() - self.assertTrue( - set(['openid-scope']).issubset( - set([o.name for o in options])) - ) - - def test_basic(self): - access_token_endpoint = uuid.uuid4().hex - scope = uuid.uuid4().hex - identity_provider = uuid.uuid4().hex - protocol = uuid.uuid4().hex - scope = uuid.uuid4().hex - client_id = uuid.uuid4().hex - client_secret = uuid.uuid4().hex - - oidc = self.create(identity_provider=identity_provider, - protocol=protocol, - access_token_endpoint=access_token_endpoint, - client_id=client_id, - client_secret=client_secret, - scope=scope) - - self.assertEqual(scope, oidc.scope) - self.assertEqual(identity_provider, oidc.identity_provider) - self.assertEqual(protocol, oidc.protocol) - self.assertEqual(access_token_endpoint, oidc.access_token_endpoint) - self.assertEqual(client_id, oidc.client_id) - self.assertEqual(client_secret, oidc.client_secret) - - -class OpenIDConnectPasswordTests(OpenIDConnectBaseTests, utils.TestCase): - - plugin_name = "v3oidcpassword" - - def test_options(self): - options = loading.get_plugin_loader(self.plugin_name).get_options() - self.assertTrue( - set(['username', 'password', 'openid-scope']).issubset( - set([o.name for o in options])) - ) - - def test_basic(self): - access_token_endpoint = uuid.uuid4().hex - username = uuid.uuid4().hex - password = uuid.uuid4().hex - scope = uuid.uuid4().hex - identity_provider = uuid.uuid4().hex - protocol = uuid.uuid4().hex - scope = uuid.uuid4().hex - client_id = uuid.uuid4().hex - client_secret = uuid.uuid4().hex - - oidc = self.create(username=username, - password=password, - identity_provider=identity_provider, - protocol=protocol, - access_token_endpoint=access_token_endpoint, - client_id=client_id, - client_secret=client_secret, - scope=scope) - - self.assertEqual(username, oidc.username) - self.assertEqual(password, oidc.password) - self.assertEqual(scope, oidc.scope) - self.assertEqual(identity_provider, oidc.identity_provider) - self.assertEqual(protocol, oidc.protocol) - self.assertEqual(access_token_endpoint, oidc.access_token_endpoint) - self.assertEqual(client_id, oidc.client_id) - self.assertEqual(client_secret, oidc.client_secret) - - -class OpenIDConnectAuthCodeTests(OpenIDConnectBaseTests, utils.TestCase): - - plugin_name = "v3oidcauthcode" - - def test_options(self): - options = loading.get_plugin_loader(self.plugin_name).get_options() - self.assertTrue( - set(['redirect-uri', 'code']).issubset( - set([o.name for o in options])) - ) - - def test_basic(self): - access_token_endpoint = uuid.uuid4().hex - redirect_uri = uuid.uuid4().hex - authorization_code = uuid.uuid4().hex - scope = uuid.uuid4().hex - identity_provider = uuid.uuid4().hex - protocol = uuid.uuid4().hex - client_id = uuid.uuid4().hex - client_secret = uuid.uuid4().hex - - oidc = self.create(code=authorization_code, - redirect_uri=redirect_uri, - identity_provider=identity_provider, - protocol=protocol, - access_token_endpoint=access_token_endpoint, - client_id=client_id, - client_secret=client_secret, - scope=scope) - - self.assertEqual(redirect_uri, oidc.redirect_uri) - self.assertEqual(authorization_code, oidc.code) - self.assertEqual(scope, oidc.scope) - self.assertEqual(identity_provider, oidc.identity_provider) - self.assertEqual(protocol, oidc.protocol) - self.assertEqual(access_token_endpoint, oidc.access_token_endpoint) - self.assertEqual(client_id, oidc.client_id) - self.assertEqual(client_secret, oidc.client_secret) - - -class OpenIDConnectAccessToken(utils.TestCase): - - plugin_name = "v3oidcaccesstoken" - - def setUp(self): - super(OpenIDConnectAccessToken, self).setUp() - - self.auth_url = uuid.uuid4().hex - - def create(self, **kwargs): - kwargs.setdefault('auth_url', self.auth_url) - loader = loading.get_plugin_loader(self.plugin_name) - return loader.load_from_options(**kwargs) - - def test_options(self): - options = loading.get_plugin_loader(self.plugin_name).get_options() - self.assertTrue( - set(['access-token']).issubset( - set([o.name for o in options])) - ) - - def test_basic(self): - access_token = uuid.uuid4().hex - identity_provider = uuid.uuid4().hex - protocol = uuid.uuid4().hex - - oidc = self.create(access_token=access_token, - identity_provider=identity_provider, - protocol=protocol) - - self.assertEqual(identity_provider, oidc.identity_provider) - self.assertEqual(protocol, oidc.protocol) - self.assertEqual(access_token, oidc.access_token) - - -class V3TokenlessAuthTests(utils.TestCase): - - def setUp(self): - super(V3TokenlessAuthTests, self).setUp() - - self.auth_url = uuid.uuid4().hex - - def create(self, **kwargs): - kwargs.setdefault('auth_url', self.auth_url) - loader = loading.get_plugin_loader('v3tokenlessauth') - return loader.load_from_options(**kwargs) - - def test_basic(self): - domain_id = uuid.uuid4().hex - domain_name = uuid.uuid4().hex - project_id = uuid.uuid4().hex - project_name = uuid.uuid4().hex - project_domain_id = uuid.uuid4().hex - project_domain_name = uuid.uuid4().hex - - tla = self.create(domain_id=domain_id, - domain_name=domain_name, - project_id=project_id, - project_name=project_name, - project_domain_id=project_domain_id, - project_domain_name=project_domain_name) - - self.assertEqual(domain_id, tla.domain_id) - self.assertEqual(domain_name, tla.domain_name) - self.assertEqual(project_id, tla.project_id) - self.assertEqual(project_name, tla.project_name) - self.assertEqual(project_domain_id, tla.project_domain_id) - self.assertEqual(project_domain_name, tla.project_domain_name) - - def test_missing_parameters(self): - self.assertRaises(exceptions.OptionError, - self.create, - domain_id=None) - self.assertRaises(exceptions.OptionError, - self.create, - domain_name=None) - self.assertRaises(exceptions.OptionError, - self.create, - project_id=None) - self.assertRaises(exceptions.OptionError, - self.create, - project_name=None) - self.assertRaises(exceptions.OptionError, - self.create, - project_domain_id=None) - self.assertRaises(exceptions.OptionError, - self.create, - project_domain_name=None) - # only when a project_name is provided, project_domain_id will - # be use to uniquely identify the project. It's an invalid - # option when it's just by itself. - self.assertRaises(exceptions.OptionError, - self.create, - project_domain_id=uuid.uuid4().hex) - # only when a project_name is provided, project_domain_name will - # be use to uniquely identify the project. It's an invalid - # option when it's just by itself. - self.assertRaises(exceptions.OptionError, - self.create, - project_domain_name=uuid.uuid4().hex) - self.assertRaises(exceptions.OptionError, - self.create, - project_name=uuid.uuid4().hex) diff --git a/keystoneauth1/tests/unit/loading/utils.py b/keystoneauth1/tests/unit/loading/utils.py deleted file mode 100644 index 0e3c2b9..0000000 --- a/keystoneauth1/tests/unit/loading/utils.py +++ /dev/null @@ -1,123 +0,0 @@ -# Licensed under the Apache License, Version 2.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 functools -import uuid - -import mock - -from keystoneauth1 import loading -from keystoneauth1.loading import base -from keystoneauth1 import plugin -from keystoneauth1.tests.unit import utils - - -class TestCase(utils.TestCase): - - GROUP = 'auth' - V2PASS = 'v2password' - V3TOKEN = 'v3token' - - a_int = 88 - a_float = 88.8 - a_bool = False - - TEST_VALS = {'a_int': a_int, - 'a_float': a_float, - 'a_bool': a_bool} - - def assertTestVals(self, plugin, vals=TEST_VALS): - for k, v in vals.items(): - self.assertEqual(v, plugin[k]) - - -def create_plugin(opts=[], token=None, endpoint=None): - - class Plugin(plugin.BaseAuthPlugin): - - def __init__(self, **kwargs): - self._data = kwargs - - def __getitem__(self, key): - return self._data[key] - - def get_token(self, *args, **kwargs): - return token - - def get_endpoint(self, *args, **kwargs): - return endpoint - - class Loader(loading.BaseLoader): - - @property - def plugin_class(self): - return Plugin - - def get_options(self): - return opts - - return Plugin, Loader - - -class BoolType(object): - - def __eq__(self, other): - """Define equiality for many bool types.""" - # hack around oslo.config equality comparison - return type(self) == type(other) - - # NOTE: This function is only needed by Python 2. If we get to point where - # we don't support Python 2 anymore, this function should be removed. - def __ne__(self, other): - """Define inequiality for many bool types.""" - return not self.__eq__(other) - - def __call__(self, value): - return str(value).lower() in ('1', 'true', 't', 'yes', 'y') - - -INT_DESC = 'test int' -FLOAT_DESC = 'test float' -BOOL_DESC = 'test bool' -STR_DESC = 'test str' -STR_DEFAULT = uuid.uuid4().hex - - -MockPlugin, MockLoader = create_plugin( - endpoint='http://test', - token='aToken', - opts=[ - loading.Opt('a-int', default=3, type=int, help=INT_DESC), - loading.Opt('a-bool', type=BoolType(), help=BOOL_DESC), - loading.Opt('a-float', type=float, help=FLOAT_DESC), - loading.Opt('a-str', help=STR_DESC, default=STR_DEFAULT), - ] -) - - -class MockManager(object): - - def __init__(self, driver): - self.driver = driver - - -def mock_plugin(loader=MockLoader): - def _wrapper(f): - @functools.wraps(f) - def inner(*args, **kwargs): - with mock.patch.object(base, 'get_plugin_loader') as m: - m.return_value = loader() - args = list(args) + [m] - return f(*args, **kwargs) - - return inner - return _wrapper diff --git a/keystoneauth1/tests/unit/matchers.py b/keystoneauth1/tests/unit/matchers.py deleted file mode 100644 index 6322850..0000000 --- a/keystoneauth1/tests/unit/matchers.py +++ /dev/null @@ -1,89 +0,0 @@ -# Copyright 2013 OpenStack Foundation -# -# Licensed under the Apache License, Version 2.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 lxml import etree -from testtools import matchers - - -class XMLEquals(object): - """Parses two XML documents from strings and compares the results.""" - - def __init__(self, expected): - self.expected = expected - - def __str__(self): - """Return string representation of xml document info.""" - return "%s(%r)" % (self.__class__.__name__, self.expected) - - def match(self, other): - def xml_element_equals(expected_doc, observed_doc): - """Test whether two XML documents are equivalent. - - This is a recursive algorithm that operates on each element in - the hierarchy. Siblings are sorted before being checked to - account for two semantically equivalent documents where siblings - appear in different document order. - - The sorting algorithm is a little weak in that it could fail for - documents where siblings at a given level are the same, but have - different children. - - """ - if expected_doc.tag != observed_doc.tag: - return False - - if expected_doc.attrib != observed_doc.attrib: - return False - - def _sorted_children(doc): - return sorted(doc.getchildren(), key=lambda el: el.tag) - - expected_children = _sorted_children(expected_doc) - observed_children = _sorted_children(observed_doc) - - if len(expected_children) != len(observed_children): - return False - - for expected_el, observed_el in zip(expected_children, - observed_children): - if not xml_element_equals(expected_el, observed_el): - return False - - return True - - parser = etree.XMLParser(remove_blank_text=True) - expected_doc = etree.fromstring(self.expected.strip(), parser) - observed_doc = etree.fromstring(other.strip(), parser) - - if xml_element_equals(expected_doc, observed_doc): - return - - return XMLMismatch(self.expected, other) - - -class XMLMismatch(matchers.Mismatch): - - def __init__(self, expected, other): - self.expected = expected - self.other = other - - def describe(self): - def pretty_xml(xml): - parser = etree.XMLParser(remove_blank_text=True) - doc = etree.fromstring(xml.strip(), parser) - return (etree.tostring(doc, encoding='utf-8', pretty_print=True) - .decode('utf-8')) - - return 'expected =\n%s\nactual =\n%s' % ( - pretty_xml(self.expected), pretty_xml(self.other)) diff --git a/keystoneauth1/tests/unit/oidc_fixtures.py b/keystoneauth1/tests/unit/oidc_fixtures.py deleted file mode 100644 index df81254..0000000 --- a/keystoneauth1/tests/unit/oidc_fixtures.py +++ /dev/null @@ -1,98 +0,0 @@ -# Licensed under the Apache License, Version 2.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. - -UNSCOPED_TOKEN = { - "token": { - "issued_at": "2014-06-09T09:48:59.643406Z", - "extras": {}, - "methods": ["oidc"], - "expires_at": "2014-06-09T10:48:59.643375Z", - "user": { - "OS-FEDERATION": { - "identity_provider": { - "id": "bluepages" - }, - "protocol": { - "id": "oidc" - }, - "groups": [ - {"id": "1764fa5cf69a49a4918131de5ce4af9a"} - ] - }, - "id": "oidc_user%40example.com", - "name": "oidc_user@example.com" - } - } -} - -ACCESS_TOKEN_VIA_PASSWORD_RESP = { - "access_token": "z5H1ITZLlJVDHQXqJun", - "token_type": "bearer", - "expires_in": 3599, - "scope": "openid profile", - "refresh_token": "DCERsh83IAhu9bhavrp" -} - -ACCESS_TOKEN_VIA_AUTH_GRANT_RESP = { - "access_token": "ya29.jgGIjfVrBPWLStWSU3eh8ioE6hG06QQ", - "token_type": "Bearer", - "expires_in": 3600, - "refresh_token": "1/ySXNO9XISBMIgOrJDtdun6zK6XiATCKT", - "id_token": "eyJhbGciOiJSUzI1Ni8hOYHuZT8dt_yynmJVhcU" -} - -DISCOVERY_DOCUMENT = { - "authorization_endpoint": "https://localhost:8020/oidc/authorize", - "claims_supported": [ - "sub", - "name", - "preferred_username", - "given_name", - "family_name", - "middle_name", - "nickname", - "profile", - "picture", - "website", - "gender", - "zoneinfo", - "locale", - "updated_at", - "birthdate", - "email", - "email_verified", - "phone_number", - "phone_number_verified", - "address" - ], - "grant_types_supported": [ - "authorization_code", - "password", - ], - "introspection_endpoint": "https://localhost:8020/oidc/introspect", - "issuer": "https://localhost:8020/oidc/", - "jwks_uri": "https://localhost:8020/oidc/jwk", - "op_policy_uri": "https://localhost:8020/oidc/about", - "op_tos_uri": "https://localhost:8020/oidc/about", - "registration_endpoint": "https://localhost:8020/oidc/register", - "revocation_endpoint": "https://localhost:8020/oidc/revoke", - "service_documentation": "https://localhost:8020/oidc/about", - "token_endpoint": "https://localhost:8020/oidc/token", - "userinfo_endpoint": "https://localhost:8020/oidc/userinfo", - "token_endpoint_auth_methods_supported": [ - "client_secret_post", - "client_secret_basic", - "client_secret_jwt", - "private_key_jwt", - "none" - ], -} diff --git a/keystoneauth1/tests/unit/test_betamax_fixture.py b/keystoneauth1/tests/unit/test_betamax_fixture.py deleted file mode 100644 index b537821..0000000 --- a/keystoneauth1/tests/unit/test_betamax_fixture.py +++ /dev/null @@ -1,125 +0,0 @@ -# Licensed under the Apache License, Version 2.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 testtools - -import betamax -from betamax import exceptions -import mock - -from keystoneauth1.fixture import keystoneauth_betamax -from keystoneauth1.fixture import serializer -from keystoneauth1.fixture import v2 as v2Fixtures -from keystoneauth1.identity import v2 -from keystoneauth1 import session - - -class TestBetamaxFixture(testtools.TestCase): - - TEST_USERNAME = 'test_user_name' - TEST_PASSWORD = 'test_password' - TEST_TENANT_NAME = 'test_tenant_name' - TEST_AUTH_URL = 'http://keystoneauth-betamax.test/v2.0/' - - V2_TOKEN = v2Fixtures.Token(tenant_name=TEST_TENANT_NAME, - user_name=TEST_USERNAME) - - def setUp(self): - super(TestBetamaxFixture, self).setUp() - self.ksa_betamax_fixture = self.useFixture( - keystoneauth_betamax.BetamaxFixture( - cassette_name='ksa_betamax_test_cassette', - cassette_library_dir='keystoneauth1/tests/unit/data/', - record=False)) - - def _replay_cassette(self): - plugin = v2.Password( - auth_url=self.TEST_AUTH_URL, - password=self.TEST_PASSWORD, - username=self.TEST_USERNAME, - tenant_name=self.TEST_TENANT_NAME) - s = session.Session() - s.get_token(auth=plugin) - - def test_keystoneauth_betamax_fixture(self): - self._replay_cassette() - - def test_replay_of_bad_url_fails(self): - plugin = v2.Password( - auth_url='http://invalid-auth-url/v2.0/', - password=self.TEST_PASSWORD, - username=self.TEST_USERNAME, - tenant_name=self.TEST_TENANT_NAME) - s = session.Session() - self.assertRaises(exceptions.BetamaxError, s.get_token, auth=plugin) - - -class TestBetamaxFixtureSerializerBehaviour(testtools.TestCase): - """Test the fixture's logic, not its monkey-patching. - - The setUp method of our BetamaxFixture monkey-patches the function to - construct a session. We don't need to test that particular bit of logic - here so we do not need to call useFixture in our setUp method. - """ - - @mock.patch.object(betamax.Betamax, 'register_serializer') - def test_can_pass_custom_serializer(self, register_serializer): - serializer = mock.Mock() - serializer.name = 'mocked-serializer' - fixture = keystoneauth_betamax.BetamaxFixture( - cassette_name='fake', - cassette_library_dir='keystoneauth1/tests/unit/data', - serializer=serializer, - ) - - register_serializer.assert_called_once_with(serializer) - self.assertIs(serializer, fixture.serializer) - self.assertEqual('mocked-serializer', fixture.serializer_name) - - def test_can_pass_serializer_name(self): - fixture = keystoneauth_betamax.BetamaxFixture( - cassette_name='fake', - cassette_library_dir='keystoneauth1/tests/unit/data', - serializer_name='json', - ) - - self.assertIsNone(fixture.serializer) - self.assertEqual('json', fixture.serializer_name) - - def test_no_serializer_options_provided(self): - fixture = keystoneauth_betamax.BetamaxFixture( - cassette_name='fake', - cassette_library_dir='keystoneauth1/tests/unit/data', - ) - - self.assertIs(serializer.YamlJsonSerializer, fixture.serializer) - self.assertEqual('yamljson', fixture.serializer_name) - - def test_no_request_matchers_provided(self): - fixture = keystoneauth_betamax.BetamaxFixture( - cassette_name='fake', - cassette_library_dir='keystoneauth1/tests/unit/data', - ) - - self.assertDictEqual({}, fixture.use_cassette_kwargs) - - def test_request_matchers(self): - fixture = keystoneauth_betamax.BetamaxFixture( - cassette_name='fake', - cassette_library_dir='keystoneauth1/tests/unit/data', - request_matchers=['method', 'uri', 'json-body'], - ) - - self.assertDictEqual( - {'match_requests_on': ['method', 'uri', 'json-body']}, - fixture.use_cassette_kwargs, - ) diff --git a/keystoneauth1/tests/unit/test_betamax_hooks.py b/keystoneauth1/tests/unit/test_betamax_hooks.py deleted file mode 100644 index 9eb78c6..0000000 --- a/keystoneauth1/tests/unit/test_betamax_hooks.py +++ /dev/null @@ -1,198 +0,0 @@ -# Licensed under the Apache License, Version 2.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 betamax -import json -import mock -from requests import models -import testtools - -try: - from requests.packages.urllib3._collections import HTTPHeaderDict -except ImportError: - from betamax.headers import HTTPHeaderDict - -from keystoneauth1.fixture import hooks - - -class TestBetamaxHooks(testtools.TestCase): - - def test_pre_record_hook_v3(self): - fixtures_path = 'keystoneauth1/tests/unit/data' - - with betamax.Betamax.configure() as config: - config.before_record(callback=hooks.pre_record_hook) - - cassette = betamax.cassette.Cassette( - 'test_pre_record_hook', 'json', record_mode=None, - cassette_library_dir=fixtures_path) - - # Create a new object to serialize - r = models.Response() - r.status_code = 200 - r.reason = 'OK' - r.encoding = 'utf-8' - r.headers = {} - r.url = 'http://localhost:35357/' - - # load request and response - with open('%s/keystone_v3_sample_response.json' % fixtures_path) as f: - response_content = json.loads(f.read()) - with open('%s/keystone_v3_sample_request.json' % fixtures_path) as f: - request_content = json.loads(f.read()) - - body_content = { - 'body': { - 'string': json.dumps(response_content), - 'encoding': 'utf-8', - } - } - - betamax.util.add_urllib3_response( - body_content, r, - HTTPHeaderDict({'Accept': 'application/json'})) - response = r - - # Create an associated request - r = models.Request() - r.method = 'GET' - r.url = 'http://localhost:35357/' - r.headers = {} - r.data = {} - response.request = r.prepare() - response.request.headers.update( - {'User-Agent': 'betamax/test header'} - ) - - response.request.body = json.dumps(request_content) - - interaction = cassette.save_interaction(response, response.request) - - # check that all values have been masked - response_content = json.loads( - interaction.data['response']['body']['string']) - self.assertEqual( - response_content['token']['expires_at'], - u'9999-12-31T23:59:59Z') - self.assertEqual( - response_content['token']['project']['domain']['id'], - u'dummy') - self.assertEqual( - response_content['token']['user']['domain']['id'], - u'dummy') - self.assertEqual( - response_content['token']['user']['name'], u'dummy') - - request_content = json.loads( - interaction.data['request']['body']['string']) - self.assertEqual( - request_content['auth']['identity']['password'] - ['user']['domain']['id'], u'dummy') - self.assertEqual( - request_content['auth']['identity']['password'] - ['user']['password'], u'********') - - def test_pre_record_hook_v2(self): - fixtures_path = 'keystoneauth1/tests/unit/data' - - with betamax.Betamax.configure() as config: - config.before_record(callback=hooks.pre_record_hook) - - cassette = betamax.cassette.Cassette( - 'test_pre_record_hook', 'json', record_mode=None, - cassette_library_dir=fixtures_path) - - # Create a new object to serialize - r = models.Response() - r.status_code = 200 - r.reason = 'OK' - r.encoding = 'utf-8' - r.headers = {} - r.url = 'http://localhost:35357/' - - # load request and response - with open('%s/keystone_v2_sample_response.json' % fixtures_path) as f: - response_content = json.loads(f.read()) - with open('%s/keystone_v2_sample_request.json' % fixtures_path) as f: - request_content = json.loads(f.read()) - - body_content = { - 'body': { - 'string': json.dumps(response_content), - 'encoding': 'utf-8', - } - } - - betamax.util.add_urllib3_response( - body_content, r, - HTTPHeaderDict({'Accept': 'application/json'})) - response = r - - # Create an associated request - r = models.Request() - r.method = 'GET' - r.url = 'http://localhost:35357/' - r.headers = {} - r.data = {} - response.request = r.prepare() - response.request.headers.update( - {'User-Agent': 'betamax/test header'} - ) - - response.request.body = json.dumps(request_content) - - interaction = cassette.save_interaction(response, response.request) - - # check that all values have been masked - response_content = json.loads( - interaction.data['response']['body']['string']) - self.assertEqual( - response_content['access']['token']['expires'], - u'9999-12-31T23:59:59Z') - self.assertEqual( - response_content['access']['token']['tenant']['name'], - u'dummy') - self.assertEqual( - response_content['access']['user']['name'], - u'dummy') - - request_content = json.loads( - interaction.data['request']['body']['string']) - self.assertEqual( - request_content['auth']['passwordCredentials']['password'], - u'********') - self.assertEqual( - request_content['auth']['passwordCredentials']['username'], - u'dummy') - self.assertEqual( - request_content['auth']['tenantName'], u'dummy') - - @mock.patch('keystoneauth1.fixture.hooks.mask_fixture_values') - def test_pre_record_hook_empty_body(self, mask_fixture_values): - interaction = mock.Mock() - interaction.data = { - 'request': { - 'body': { - 'encoding': 'utf-8', - 'string': '', - }, - }, - 'response': { - 'body': { - 'encoding': 'utf-8', - 'string': '', - }, - }, - } - - hooks.pre_record_hook(interaction, mock.Mock()) - self.assertFalse(mask_fixture_values.called) diff --git a/keystoneauth1/tests/unit/test_betamax_serializer.py b/keystoneauth1/tests/unit/test_betamax_serializer.py deleted file mode 100644 index a69318d..0000000 --- a/keystoneauth1/tests/unit/test_betamax_serializer.py +++ /dev/null @@ -1,53 +0,0 @@ -# Licensed under the Apache License, Version 2.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 testtools -import yaml - -from keystoneauth1.fixture import serializer - - -class TestBetamaxSerializer(testtools.TestCase): - - TEST_FILE = os.path.join( - os.path.dirname(os.path.abspath(__file__)), - 'data', 'ksa_betamax_test_cassette.yaml') - TEST_JSON = os.path.join( - os.path.dirname(os.path.abspath(__file__)), - 'data', 'ksa_serializer_data.json') - - def setUp(self): - super(TestBetamaxSerializer, self).setUp() - self.serializer = serializer.YamlJsonSerializer() - - def test_deserialize(self): - data = self.serializer.deserialize(open(self.TEST_FILE, 'r').read()) - request = data['http_interactions'][0]['request'] - self.assertEqual( - 'http://keystoneauth-betamax.test/v2.0/tokens', - request['uri']) - payload = json.loads(request['body']['string']) - self.assertEqual('test_tenant_name', payload['auth']['tenantName']) - - def test_serialize(self): - data = json.loads(open(self.TEST_JSON, 'r').read()) - serialized = self.serializer.serialize(data) - data = yaml.safe_load(serialized) - request = data['http_interactions'][0]['request'] - self.assertEqual( - 'http://keystoneauth-betamax.test/v2.0/tokens', - request['uri']) - payload = json.loads(request['body']['string']) - self.assertEqual('test_tenant_name', payload['auth']['tenantName']) diff --git a/keystoneauth1/tests/unit/test_discovery.py b/keystoneauth1/tests/unit/test_discovery.py deleted file mode 100644 index ec5a15c..0000000 --- a/keystoneauth1/tests/unit/test_discovery.py +++ /dev/null @@ -1,951 +0,0 @@ -# Licensed under the Apache License, Version 2.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 re - -from testtools import matchers - -from keystoneauth1 import discover -from keystoneauth1 import exceptions -from keystoneauth1 import fixture -from keystoneauth1 import session -from keystoneauth1.tests.unit import utils - - -BASE_HOST = 'http://keystone.example.com' -BASE_URL = "%s:5000/" % BASE_HOST -UPDATED = '2013-03-06T00:00:00Z' - -TEST_SERVICE_CATALOG = [{ - "endpoints": [{ - "adminURL": "%s:8774/v1.0" % BASE_HOST, - "region": "RegionOne", - "internalURL": "%s://127.0.0.1:8774/v1.0" % BASE_HOST, - "publicURL": "%s:8774/v1.0/" % BASE_HOST - }], - "type": "nova_compat", - "name": "nova_compat" -}, { - "endpoints": [{ - "adminURL": "http://nova/novapi/admin", - "region": "RegionOne", - "internalURL": "http://nova/novapi/internal", - "publicURL": "http://nova/novapi/public" - }], - "type": "compute", - "name": "nova" -}, { - "endpoints": [{ - "adminURL": "http://glance/glanceapi/admin", - "region": "RegionOne", - "internalURL": "http://glance/glanceapi/internal", - "publicURL": "http://glance/glanceapi/public" - }], - "type": "image", - "name": "glance" -}, { - "endpoints": [{ - "adminURL": "%s:35357/v2.0" % BASE_HOST, - "region": "RegionOne", - "internalURL": "%s:5000/v2.0" % BASE_HOST, - "publicURL": "%s:5000/v2.0" % BASE_HOST - }], - "type": "identity", - "name": "keystone" -}, { - "endpoints": [{ - "adminURL": "http://swift/swiftapi/admin", - "region": "RegionOne", - "internalURL": "http://swift/swiftapi/internal", - "publicURL": "http://swift/swiftapi/public" - }], - "type": "object-store", - "name": "swift" -}] - -V2_URL = "%sv2.0" % BASE_URL -V2_VERSION = fixture.V2Discovery(V2_URL) -V2_VERSION.updated_str = UPDATED - -V2_AUTH_RESPONSE = json.dumps({ - "access": { - "token": { - "expires": "2020-01-01T00:00:10.000123Z", - "id": 'fakeToken', - "tenant": { - "id": '1' - }, - }, - "user": { - "id": 'test' - }, - "serviceCatalog": TEST_SERVICE_CATALOG, - }, -}) - -V3_URL = "%sv3" % BASE_URL -V3_VERSION = fixture.V3Discovery(V3_URL) -V3_MEDIA_TYPES = V3_VERSION.media_types -V3_VERSION.updated_str = UPDATED - -V3_AUTH_RESPONSE = json.dumps({ - "token": { - "methods": [ - "token", - "password" - ], - - "expires_at": "2020-01-01T00:00:10.000123Z", - "project": { - "domain": { - "id": '1', - "name": 'test-domain' - }, - "id": '1', - "name": 'test-project' - }, - "user": { - "domain": { - "id": '1', - "name": 'test-domain' - }, - "id": '1', - "name": 'test-user' - }, - "issued_at": "2013-05-29T16:55:21.468960Z", - }, -}) - -CINDER_EXAMPLES = { - "versions": [ - { - "status": "CURRENT", - "updated": "2012-01-04T11:33:21Z", - "id": "v1.0", - "links": [ - { - "href": "%sv1/" % BASE_URL, - "rel": "self" - } - ] - }, - { - "status": "CURRENT", - "updated": "2012-11-21T11:33:21Z", - "id": "v2.0", - "links": [ - { - "href": "%sv2/" % BASE_URL, - "rel": "self" - } - ] - }, - { - "status": "CURRENT", - "updated": "2012-11-21T11:33:21Z", - "id": "v3.0", - "version": "3.27", - "min_version": "3.0", - "next_min_version": "3.4", - "not_before": "2019-12-31", - "links": [ - { - "href": BASE_URL, - "rel": "collection" - }, - { - "href": "%sv3/" % BASE_URL, - "rel": "self" - } - ] - } - ] -} - -GLANCE_EXAMPLES = { - "versions": [ - { - "status": "CURRENT", - "id": "v2.2", - "links": [ - { - "href": "%sv2/" % BASE_URL, - "rel": "self" - } - ] - }, - { - "status": "SUPPORTED", - "id": "v2.1", - "links": [ - { - "href": "%sv2/" % BASE_URL, - "rel": "self" - } - ] - }, - { - "status": "SUPPORTED", - "id": "v2.0", - "links": [ - { - "href": "%sv2/" % BASE_URL, - "rel": "self" - } - ] - }, - { - "status": "CURRENT", - "id": "v1.1", - "links": [ - { - "href": "%sv1/" % BASE_URL, - "rel": "self" - } - ] - }, - { - "status": "SUPPORTED", - "id": "v1.0", - "links": [ - { - "href": "%sv1/" % BASE_URL, - "rel": "self" - } - ] - } - ] -} - - -def _create_version_list(versions): - return {'versions': {'values': versions}} - - -def _create_single_version(version): - return {'version': version} - - -V3_VERSION_LIST = _create_version_list([V3_VERSION, V2_VERSION]) -V2_VERSION_LIST = _create_version_list([V2_VERSION]) - -V3_VERSION_ENTRY = _create_single_version(V3_VERSION) -V2_VERSION_ENTRY = _create_single_version(V2_VERSION) - - -class CatalogHackTests(utils.TestCase): - - TEST_URL = 'http://keystone.server:5000/v2.0' - OTHER_URL = 'http://other.server:5000/path' - - IDENTITY = 'identity' - - BASE_URL = 'http://keystone.server:5000/' - V2_URL = BASE_URL + 'v2.0' - V3_URL = BASE_URL + 'v3' - - def setUp(self): - super(CatalogHackTests, self).setUp() - self.hacks = discover._VersionHacks() - self.hacks.add_discover_hack(self.IDENTITY, - re.compile('/v2.0/?$'), - '/') - - def test_version_hacks(self): - self.assertEqual(self.BASE_URL, - self.hacks.get_discover_hack(self.IDENTITY, - self.V2_URL)) - - self.assertEqual(self.BASE_URL, - self.hacks.get_discover_hack(self.IDENTITY, - self.V2_URL + '/')) - - self.assertEqual(self.OTHER_URL, - self.hacks.get_discover_hack(self.IDENTITY, - self.OTHER_URL)) - - def test_ignored_non_service_type(self): - self.assertEqual(self.V2_URL, - self.hacks.get_discover_hack('other', self.V2_URL)) - - -class DiscoverUtils(utils.TestCase): - - def test_version_number(self): - def assertVersion(inp, out): - self.assertEqual(out, discover.normalize_version_number(inp)) - - def versionRaises(inp): - self.assertRaises(TypeError, - discover.normalize_version_number, - inp) - - assertVersion('v1.2', (1, 2)) - assertVersion('v11', (11, 0)) - assertVersion('1.2', (1, 2)) - assertVersion('1.5.1', (1, 5, 1)) - assertVersion('1', (1, 0)) - assertVersion(1, (1, 0)) - assertVersion(5.2, (5, 2)) - assertVersion('3.20', (3, 20)) - assertVersion((6, 1), (6, 1)) - assertVersion([1, 40], (1, 40)) - assertVersion((1,), (1, 0)) - assertVersion(['1'], (1, 0)) - assertVersion('latest', (discover.LATEST, discover.LATEST)) - assertVersion(['latest'], (discover.LATEST, discover.LATEST)) - assertVersion(discover.LATEST, (discover.LATEST, discover.LATEST)) - assertVersion((discover.LATEST,), (discover.LATEST, discover.LATEST)) - assertVersion('10.latest', (10, discover.LATEST)) - assertVersion((10, 'latest'), (10, discover.LATEST)) - assertVersion((10, discover.LATEST), (10, discover.LATEST)) - - versionRaises(None) - versionRaises('hello') - versionRaises('1.a') - versionRaises('vacuum') - versionRaises('') - versionRaises(('1', 'a')) - - def test_version_args(self): - """Validate _normalize_version_args.""" - def assert_min_max(in_ver, in_min, in_max, out_min, out_max): - self.assertEqual( - (out_min, out_max), - discover._normalize_version_args(in_ver, in_min, in_max)) - - def normalize_raises(ver, min, max): - self.assertRaises(ValueError, - discover._normalize_version_args, ver, min, max) - - assert_min_max(None, None, None, - None, None) - assert_min_max(None, None, 'v1.2', - None, (1, 2)) - assert_min_max(None, 'v1.2', 'latest', - (1, 2), (discover.LATEST, discover.LATEST)) - assert_min_max(None, 'v1.2', '1.6', - (1, 2), (1, 6)) - assert_min_max(None, 'v1.2', '1.latest', - (1, 2), (1, discover.LATEST)) - assert_min_max(None, 'latest', 'latest', - (discover.LATEST, discover.LATEST), - (discover.LATEST, discover.LATEST)) - assert_min_max(None, 'latest', None, - (discover.LATEST, discover.LATEST), - (discover.LATEST, discover.LATEST)) - assert_min_max(None, (1, 2), None, - (1, 2), (discover.LATEST, discover.LATEST)) - assert_min_max('', ('1', '2'), (1, 6), - (1, 2), (1, 6)) - assert_min_max(None, ('1', '2'), (1, discover.LATEST), - (1, 2), (1, discover.LATEST)) - assert_min_max('v1.2', '', None, - (1, 2), (1, discover.LATEST)) - assert_min_max('1.latest', None, '', - (1, discover.LATEST), (1, discover.LATEST)) - assert_min_max('v1', None, None, - (1, 0), (1, discover.LATEST)) - assert_min_max('latest', None, None, - (discover.LATEST, discover.LATEST), - (discover.LATEST, discover.LATEST)) - - normalize_raises('v1', 'v2', None) - normalize_raises('v1', None, 'v2') - normalize_raises(None, 'latest', 'v1') - normalize_raises(None, 'v1.2', 'v1.1') - normalize_raises(None, 'v1.2', 1) - - def test_version_to_string(self): - def assert_string(inp, out): - self.assertEqual(out, discover.version_to_string(inp)) - - assert_string((discover.LATEST,), 'latest') - assert_string((discover.LATEST, discover.LATEST), 'latest') - assert_string((discover.LATEST, discover.LATEST, discover.LATEST), - 'latest') - assert_string((1,), '1') - assert_string((1, 2), '1.2') - assert_string((1, discover.LATEST), '1.latest') - - def test_version_between(self): - def good(minver, maxver, cand): - self.assertTrue(discover._version_between(minver, maxver, cand)) - - def bad(minver, maxver, cand): - self.assertFalse(discover._version_between(minver, maxver, cand)) - - def exc(minver, maxver, cand): - self.assertRaises(ValueError, - discover._version_between, minver, maxver, cand) - - good((1, 0), (1, 0), (1, 0)) - good((1, 0), (1, 10), (1, 2)) - good(None, (1, 10), (1, 2)) - good((1, 20), (2, 0), (1, 21)) - good((1, 0), (2, discover.LATEST), (1, 21)) - good((1, 0), (2, discover.LATEST), (1, discover.LATEST)) - good((1, 50), (2, discover.LATEST), (2, discover.LATEST)) - - bad((discover.LATEST, discover.LATEST), - (discover.LATEST, discover.LATEST), (1, 0)) - bad(None, None, (1, 0)) - bad((1, 50), (2, discover.LATEST), (3, 0)) - bad((1, 50), (2, discover.LATEST), (3, discover.LATEST)) - bad((1, 50), (2, 5), (2, discover.LATEST)) - - exc((1, 0), (1, 0), None) - exc('v1.0', (1, 0), (1, 0)) - exc((1, 0), 'v1.0', (1, 0)) - exc((1, 0), (1, 0), 'v1.0') - exc((1, 0), None, (1, 0)) - - -class VersionDataTests(utils.TestCase): - - def setUp(self): - super(VersionDataTests, self).setUp() - self.session = session.Session() - - def test_version_data_basics(self): - examples = {'keystone': V3_VERSION_LIST, - 'cinder': CINDER_EXAMPLES, - 'glance': GLANCE_EXAMPLES} - - for path, data in examples.items(): - url = "%s%s" % (BASE_URL, path) - - mock = self.requests_mock.get(url, status_code=300, json=data) - - disc = discover.Discover(self.session, url) - raw_data = disc.raw_version_data() - clean_data = disc.version_data() - - for v in raw_data: - for n in ('id', 'status', 'links'): - msg = '%s missing from %s version data' % (n, path) - self.assertThat(v, matchers.Annotate(msg, - matchers.Contains(n))) - - for v in clean_data: - for n in ('version', 'url', 'raw_status'): - msg = '%s missing from %s version data' % (n, path) - self.assertThat(v, matchers.Annotate(msg, - matchers.Contains(n))) - - self.assertTrue(mock.called_once) - - def test_version_data_individual(self): - mock = self.requests_mock.get(V3_URL, - status_code=200, - json=V3_VERSION_ENTRY) - - disc = discover.Discover(self.session, V3_URL) - raw_data = disc.raw_version_data() - clean_data = disc.version_data() - - for v in raw_data: - self.assertEqual(v['id'], 'v3.0') - self.assertEqual(v['status'], 'stable') - self.assertIn('media-types', v) - self.assertIn('links', v) - - for v in clean_data: - self.assertEqual(v['version'], (3, 0)) - self.assertEqual(v['raw_status'], 'stable') - self.assertEqual(v['url'], V3_URL) - - self.assertTrue(mock.called_once) - - def test_version_data_microversions(self): - """Validate [min_|max_]version conversion to {min|max}_microversion.""" - def setup_mock(versions_in): - # Set up the test data with the input version data - jsondata = { - "versions": [ - dict( - { - "status": "CURRENT", - "id": "v2.2", - "links": [ - { - "href": V3_URL, - "rel": "self" - } - ] - }, - **versions_in - ) - ] - } - self.requests_mock.get( - V3_URL, status_code=200, json=jsondata) - - def test_ok(versions_in, versions_out): - setup_mock(versions_in) - # Ensure the output contains the expected microversions - self.assertEqual( - [ - dict( - { - 'collection': None, - 'version': (2, 2), - 'url': V3_URL, - 'raw_status': 'CURRENT', - }, - **versions_out - ) - ], - discover.Discover(self.session, V3_URL).version_data()) - - def test_exc(versions_in): - setup_mock(versions_in) - # Ensure TypeError is raised - self.assertRaises( - TypeError, - discover.Discover(self.session, V3_URL).version_data) - - # no version info in input - test_ok({}, - {'min_microversion': None, 'max_microversion': None, - 'next_min_version': None, 'not_before': None}) - - # version => max_microversion - test_ok({'version': '2.2'}, - {'min_microversion': None, 'max_microversion': (2, 2), - 'next_min_version': None, 'not_before': None}) - - # max_version supersedes version (even if malformed). min_version & - # normalization. - test_ok({'min_version': '2', 'version': 'foo', 'max_version': '2.2'}, - {'min_microversion': (2, 0), 'max_microversion': (2, 2), - 'next_min_version': None, 'not_before': None}) - - # Edge case: min/max_version ignored if present but "empty"; version - # used for max_microversion. - test_ok({'min_version': '', 'version': '2.1', 'max_version': ''}, - {'min_microversion': None, 'max_microversion': (2, 1), - 'next_min_version': None, 'not_before': None}) - - # next_min_version set - test_ok({'min_version': '2', 'max_version': '2.2', - 'next_min_version': '2.1', 'not_before': '2019-07-01'}, - {'min_microversion': (2, 0), 'max_microversion': (2, 2), - 'next_min_version': (2, 1), 'not_before': '2019-07-01'}) - - # Badly-formatted min_version - test_exc({'min_version': 'foo', 'max_version': '2.1'}) - - # Badly-formatted max_version - test_exc({'min_version': '2.1', 'max_version': 'foo'}) - - # Badly-formatted version (when max_version omitted) - test_exc({'min_version': '2.1', 'version': 'foo'}) - - # Badly-formatted next_min_version - test_exc({'next_min_version': 'bogus', 'not_before': '2019-07-01'}) - - def test_data_for_url(self): - mock = self.requests_mock.get(V3_URL, - status_code=200, - json=V3_VERSION_ENTRY) - - disc = discover.Discover(self.session, V3_URL) - for url in (V3_URL, V3_URL + '/'): - data = disc.versioned_data_for(url=url) - self.assertEqual(data['version'], (3, 0)) - self.assertEqual(data['raw_status'], 'stable') - self.assertEqual(data['url'], V3_URL) - - self.assertTrue(mock.called_once) - - def test_data_for_no_version(self): - mock = self.requests_mock.get(V3_URL, - status_code=200, - json=V3_VERSION_ENTRY) - - disc = discover.Discover(self.session, V3_URL) - - data = disc.versioned_data_for() - self.assertEqual(data['version'], (3, 0)) - self.assertEqual(data['raw_status'], 'stable') - self.assertEqual(data['url'], V3_URL) - self.assertRaises(TypeError, disc.data_for, version=None) - - self.assertTrue(mock.called_once) - - def test_keystone_version_data(self): - mock = self.requests_mock.get(BASE_URL, - status_code=300, - json=V3_VERSION_LIST) - - disc = discover.Discover(self.session, BASE_URL) - raw_data = disc.raw_version_data() - clean_data = disc.version_data() - - self.assertEqual(2, len(raw_data)) - self.assertEqual(2, len(clean_data)) - - for v in raw_data: - self.assertIn(v['id'], ('v2.0', 'v3.0')) - self.assertEqual(v['updated'], UPDATED) - self.assertEqual(v['status'], 'stable') - - if v['id'] == 'v3.0': - self.assertEqual(v['media-types'], V3_MEDIA_TYPES) - - for v in clean_data: - self.assertIn(v['version'], ((2, 0), (3, 0))) - self.assertEqual(v['raw_status'], 'stable') - - for version in (disc.data_for('v3.0'), - disc.data_for('3.latest'), - disc.data_for('latest'), - disc.versioned_data_for( - min_version='v3.0', max_version='v3.latest'), - disc.versioned_data_for(min_version='3'), - disc.versioned_data_for(min_version='3.latest'), - disc.versioned_data_for(min_version='latest'), - disc.versioned_data_for(min_version='3.latest', - max_version='latest'), - disc.versioned_data_for(min_version='latest', - max_version='latest'), - disc.versioned_data_for(min_version=2), - disc.versioned_data_for(min_version='2.latest')): - self.assertEqual((3, 0), version['version']) - self.assertEqual('stable', version['raw_status']) - self.assertEqual(V3_URL, version['url']) - - for version in (disc.data_for(2), - disc.data_for('2.latest'), - disc.versioned_data_for( - min_version=2, max_version=(2, discover.LATEST)), - disc.versioned_data_for( - min_version='2.latest', max_version='2.latest')): - self.assertEqual((2, 0), version['version']) - self.assertEqual('stable', version['raw_status']) - self.assertEqual(V2_URL, version['url']) - - self.assertIsNone(disc.url_for('v4')) - self.assertIsNone(disc.versioned_url_for( - min_version='v4', max_version='v4.latest')) - self.assertEqual(V3_URL, disc.url_for('v3')) - self.assertEqual(V3_URL, disc.versioned_url_for( - min_version='v3', max_version='v3.latest')) - self.assertEqual(V2_URL, disc.url_for('v2')) - self.assertEqual(V2_URL, disc.versioned_url_for( - min_version='v2', max_version='v2.latest')) - - self.assertTrue(mock.called_once) - - def test_cinder_version_data(self): - mock = self.requests_mock.get(BASE_URL, - status_code=300, - json=CINDER_EXAMPLES) - - disc = discover.Discover(self.session, BASE_URL) - raw_data = disc.raw_version_data() - clean_data = disc.version_data() - - self.assertEqual(3, len(raw_data)) - - for v in raw_data: - self.assertEqual(v['status'], 'CURRENT') - if v['id'] == 'v1.0': - self.assertEqual(v['updated'], '2012-01-04T11:33:21Z') - elif v['id'] == 'v2.0': - self.assertEqual(v['updated'], '2012-11-21T11:33:21Z') - elif v['id'] == 'v3.0': - self.assertEqual(v['updated'], '2012-11-21T11:33:21Z') - else: - self.fail("Invalid version found") - - v1_url = "%sv1/" % BASE_URL - v2_url = "%sv2/" % BASE_URL - v3_url = "%sv3/" % BASE_URL - - self.assertEqual(clean_data, [ - { - 'collection': None, - 'max_microversion': None, - 'min_microversion': None, - 'next_min_version': None, - 'not_before': None, - 'version': (1, 0), - 'url': v1_url, - 'raw_status': 'CURRENT', - }, - { - 'collection': None, - 'max_microversion': None, - 'min_microversion': None, - 'next_min_version': None, - 'not_before': None, - 'version': (2, 0), - 'url': v2_url, - 'raw_status': 'CURRENT', - }, - { - 'collection': BASE_URL, - 'max_microversion': (3, 27), - 'min_microversion': (3, 0), - 'next_min_version': (3, 4), - 'not_before': u'2019-12-31', - 'version': (3, 0), - 'url': v3_url, - 'raw_status': 'CURRENT', - }, - ]) - - for version in (disc.data_for('v2.0'), - disc.versioned_data_for(min_version='v2.0', - max_version='v2.latest')): - self.assertEqual((2, 0), version['version']) - self.assertEqual('CURRENT', version['raw_status']) - self.assertEqual(v2_url, version['url']) - - for version in (disc.data_for(1), - disc.versioned_data_for( - min_version=(1,), - max_version=(1, discover.LATEST))): - self.assertEqual((1, 0), version['version']) - self.assertEqual('CURRENT', version['raw_status']) - self.assertEqual(v1_url, version['url']) - - self.assertIsNone(disc.url_for('v4')) - self.assertIsNone(disc.versioned_url_for(min_version='v4', - max_version='v4.latest')) - self.assertEqual(v3_url, disc.url_for('v3')) - self.assertEqual(v3_url, disc.versioned_url_for( - min_version='v3', max_version='v3.latest')) - self.assertEqual(v2_url, disc.url_for('v2')) - self.assertEqual(v2_url, disc.versioned_url_for( - min_version='v2', max_version='v2.latest')) - self.assertEqual(v1_url, disc.url_for('v1')) - self.assertEqual(v1_url, disc.versioned_url_for( - min_version='v1', max_version='v1.latest')) - - self.assertTrue(mock.called_once) - - def test_glance_version_data(self): - mock = self.requests_mock.get(BASE_URL, - status_code=200, - json=GLANCE_EXAMPLES) - - disc = discover.Discover(self.session, BASE_URL) - raw_data = disc.raw_version_data() - clean_data = disc.version_data() - - self.assertEqual(5, len(raw_data)) - - for v in raw_data: - if v['id'] in ('v2.2', 'v1.1'): - self.assertEqual(v['status'], 'CURRENT') - elif v['id'] in ('v2.1', 'v2.0', 'v1.0'): - self.assertEqual(v['status'], 'SUPPORTED') - else: - self.fail("Invalid version found") - - v1_url = '%sv1/' % BASE_URL - v2_url = '%sv2/' % BASE_URL - - self.assertEqual(clean_data, [ - { - 'collection': None, - 'max_microversion': None, - 'min_microversion': None, - 'next_min_version': None, - 'not_before': None, - 'version': (1, 0), - 'url': v1_url, - 'raw_status': 'SUPPORTED', - }, - { - 'collection': None, - 'max_microversion': None, - 'min_microversion': None, - 'next_min_version': None, - 'not_before': None, - 'version': (1, 1), - 'url': v1_url, - 'raw_status': 'CURRENT', - }, - { - 'collection': None, - 'max_microversion': None, - 'min_microversion': None, - 'next_min_version': None, - 'not_before': None, - 'version': (2, 0), - 'url': v2_url, - 'raw_status': 'SUPPORTED', - }, - { - 'collection': None, - 'max_microversion': None, - 'min_microversion': None, - 'next_min_version': None, - 'not_before': None, - 'version': (2, 1), - 'url': v2_url, - 'raw_status': 'SUPPORTED', - }, - { - 'collection': None, - 'max_microversion': None, - 'min_microversion': None, - 'next_min_version': None, - 'not_before': None, - 'version': (2, 2), - 'url': v2_url, - 'raw_status': 'CURRENT', - }, - ]) - - for ver in (2, 2.1, 2.2): - for version in (disc.data_for(ver), - disc.versioned_data_for( - min_version=ver, - max_version=(2, discover.LATEST))): - self.assertEqual((2, 2), version['version']) - self.assertEqual('CURRENT', version['raw_status']) - self.assertEqual(v2_url, version['url']) - self.assertEqual(v2_url, disc.url_for(ver)) - - for ver in (1, 1.1): - for version in (disc.data_for(ver), - disc.versioned_data_for( - min_version=ver, - max_version=(1, discover.LATEST))): - self.assertEqual((1, 1), version['version']) - self.assertEqual('CURRENT', version['raw_status']) - self.assertEqual(v1_url, version['url']) - self.assertEqual(v1_url, disc.url_for(ver)) - - self.assertIsNone(disc.url_for('v3')) - self.assertIsNone(disc.versioned_url_for(min_version='v3', - max_version='v3.latest')) - self.assertIsNone(disc.url_for('v2.3')) - self.assertIsNone(disc.versioned_url_for(min_version='v2.3', - max_version='v2.latest')) - - self.assertTrue(mock.called_once) - - def test_allow_deprecated(self): - status = 'deprecated' - version_list = [{'id': 'v3.0', - 'links': [{'href': V3_URL, 'rel': 'self'}], - 'media-types': V3_MEDIA_TYPES, - 'status': status, - 'updated': UPDATED}] - self.requests_mock.get(BASE_URL, json={'versions': version_list}) - - disc = discover.Discover(self.session, BASE_URL) - - # deprecated is allowed by default - versions = disc.version_data(allow_deprecated=False) - self.assertEqual(0, len(versions)) - - versions = disc.version_data(allow_deprecated=True) - self.assertEqual(1, len(versions)) - self.assertEqual(status, versions[0]['raw_status']) - self.assertEqual(V3_URL, versions[0]['url']) - self.assertEqual((3, 0), versions[0]['version']) - - def test_allow_experimental(self): - status = 'experimental' - version_list = [{'id': 'v3.0', - 'links': [{'href': V3_URL, 'rel': 'self'}], - 'media-types': V3_MEDIA_TYPES, - 'status': status, - 'updated': UPDATED}] - self.requests_mock.get(BASE_URL, json={'versions': version_list}) - - disc = discover.Discover(self.session, BASE_URL) - - versions = disc.version_data() - self.assertEqual(0, len(versions)) - - versions = disc.version_data(allow_experimental=True) - self.assertEqual(1, len(versions)) - self.assertEqual(status, versions[0]['raw_status']) - self.assertEqual(V3_URL, versions[0]['url']) - self.assertEqual((3, 0), versions[0]['version']) - - def test_allow_unknown(self): - status = 'abcdef' - version_list = fixture.DiscoveryList(BASE_URL, - v2=False, - v3_status=status) - self.requests_mock.get(BASE_URL, json=version_list) - - disc = discover.Discover(self.session, BASE_URL) - - versions = disc.version_data() - self.assertEqual(0, len(versions)) - - versions = disc.version_data(allow_unknown=True) - self.assertEqual(1, len(versions)) - self.assertEqual(status, versions[0]['raw_status']) - self.assertEqual(V3_URL, versions[0]['url']) - self.assertEqual((3, 0), versions[0]['version']) - - def test_ignoring_invalid_links(self): - version_list = [{'id': 'v3.0', - 'links': [{'href': V3_URL, 'rel': 'self'}], - 'media-types': V3_MEDIA_TYPES, - 'status': 'stable', - 'updated': UPDATED}, - {'id': 'v3.1', - 'media-types': V3_MEDIA_TYPES, - 'status': 'stable', - 'updated': UPDATED}, - {'media-types': V3_MEDIA_TYPES, - 'status': 'stable', - 'updated': UPDATED, - 'links': [{'href': V3_URL, 'rel': 'self'}], - }] - - self.requests_mock.get(BASE_URL, json={'versions': version_list}) - - disc = discover.Discover(self.session, BASE_URL) - - # raw_version_data will return all choices, even invalid ones - versions = disc.raw_version_data() - self.assertEqual(3, len(versions)) - - # only the version with both id and links will be actually returned - versions = disc.version_data() - self.assertEqual(1, len(versions)) - - -class EndpointDataTests(utils.TestCase): - @mock.patch('keystoneauth1.discover.get_discovery') - @mock.patch('keystoneauth1.discover.EndpointData.' - '_get_discovery_url_choices') - def test_run_discovery_cache(self, mock_url_choices, mock_get_disc): - # get_discovery raises so we keep looping - mock_get_disc.side_effect = exceptions.DiscoveryFailure() - # Duplicate 'url1' in here to validate the cache behavior - mock_url_choices.return_value = ('url1', 'url2', 'url1', 'url3') - epd = discover.EndpointData() - epd._run_discovery( - session='sess', cache='cache', min_version='min', - max_version='max', project_id='projid', - allow_version_hack='allow_hack', discover_versions='disc_vers') - # Only one call with 'url1' - self.assertEqual(3, mock_get_disc.call_count) - mock_get_disc.assert_has_calls( - [mock.call('sess', url, cache='cache', authenticated=False) - for url in ('url1', 'url2', 'url3')]) diff --git a/keystoneauth1/tests/unit/test_fixtures.py b/keystoneauth1/tests/unit/test_fixtures.py deleted file mode 100644 index cded4ca..0000000 --- a/keystoneauth1/tests/unit/test_fixtures.py +++ /dev/null @@ -1,342 +0,0 @@ -# Licensed under the Apache License, Version 2.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 uuid - -from keystoneauth1 import fixture -from keystoneauth1.tests.unit import utils - - -class V2TokenTests(utils.TestCase): - - def test_unscoped(self): - token_id = uuid.uuid4().hex - user_id = uuid.uuid4().hex - user_name = uuid.uuid4().hex - - token = fixture.V2Token(token_id=token_id, - user_id=user_id, - user_name=user_name) - - self.assertEqual(token_id, token.token_id) - self.assertEqual(token_id, token['access']['token']['id']) - self.assertEqual(user_id, token.user_id) - self.assertEqual(user_id, token['access']['user']['id']) - self.assertEqual(user_name, token.user_name) - self.assertEqual(user_name, token['access']['user']['name']) - self.assertIsNone(token.trust_id) - - def test_tenant_scoped(self): - tenant_id = uuid.uuid4().hex - tenant_name = uuid.uuid4().hex - - token = fixture.V2Token(tenant_id=tenant_id, - tenant_name=tenant_name) - - self.assertEqual(tenant_id, token.tenant_id) - self.assertEqual(tenant_id, token['access']['token']['tenant']['id']) - self.assertEqual(tenant_name, token.tenant_name) - tn = token['access']['token']['tenant']['name'] - self.assertEqual(tenant_name, tn) - self.assertIsNone(token.trust_id) - - def test_trust_scoped(self): - trust_id = uuid.uuid4().hex - trustee_user_id = uuid.uuid4().hex - - token = fixture.V2Token(trust_id=trust_id, - trustee_user_id=trustee_user_id) - trust = token['access']['trust'] - - self.assertEqual(trust_id, token.trust_id) - self.assertEqual(trust_id, trust['id']) - self.assertEqual(trustee_user_id, token.trustee_user_id) - self.assertEqual(trustee_user_id, trust['trustee_user_id']) - - def test_roles(self): - role_id1 = uuid.uuid4().hex - role_name1 = uuid.uuid4().hex - role_id2 = uuid.uuid4().hex - role_name2 = uuid.uuid4().hex - - token = fixture.V2Token() - token.add_role(id=role_id1, name=role_name1) - token.add_role(id=role_id2, name=role_name2) - - role_names = token['access']['user']['roles'] - role_ids = token['access']['metadata']['roles'] - - self.assertEqual(set([role_id1, role_id2]), set(role_ids)) - for r in (role_name1, role_name2): - self.assertIn({'name': r}, role_names) - - def test_services(self): - service_type = uuid.uuid4().hex - service_name = uuid.uuid4().hex - endpoint_id = uuid.uuid4().hex - region = uuid.uuid4().hex - - public = uuid.uuid4().hex - admin = uuid.uuid4().hex - internal = uuid.uuid4().hex - - token = fixture.V2Token() - svc = token.add_service(type=service_type, name=service_name) - - svc.add_endpoint(public=public, - admin=admin, - internal=internal, - region=region, - id=endpoint_id) - - self.assertEqual(1, len(token['access']['serviceCatalog'])) - service = token['access']['serviceCatalog'][0]['endpoints'][0] - - self.assertEqual(public, service['publicURL']) - self.assertEqual(internal, service['internalURL']) - self.assertEqual(admin, service['adminURL']) - self.assertEqual(region, service['region']) - self.assertEqual(endpoint_id, service['id']) - - def test_token_bind(self): - name1 = uuid.uuid4().hex - data1 = uuid.uuid4().hex - name2 = uuid.uuid4().hex - data2 = {uuid.uuid4().hex: uuid.uuid4().hex} - - token = fixture.V2Token() - token.set_bind(name1, data1) - token.set_bind(name2, data2) - - self.assertEqual({name1: data1, name2: data2}, - token['access']['token']['bind']) - - -class V3TokenTests(utils.TestCase): - - def test_unscoped(self): - user_id = uuid.uuid4().hex - user_name = uuid.uuid4().hex - user_domain_id = uuid.uuid4().hex - user_domain_name = uuid.uuid4().hex - - token = fixture.V3Token(user_id=user_id, - user_name=user_name, - user_domain_id=user_domain_id, - user_domain_name=user_domain_name) - - self.assertEqual(user_id, token.user_id) - self.assertEqual(user_id, token['token']['user']['id']) - self.assertEqual(user_name, token.user_name) - self.assertEqual(user_name, token['token']['user']['name']) - - user_domain = token['token']['user']['domain'] - - self.assertEqual(user_domain_id, token.user_domain_id) - self.assertEqual(user_domain_id, user_domain['id']) - self.assertEqual(user_domain_name, token.user_domain_name) - self.assertEqual(user_domain_name, user_domain['name']) - - def test_project_scoped(self): - project_id = uuid.uuid4().hex - project_name = uuid.uuid4().hex - project_domain_id = uuid.uuid4().hex - project_domain_name = uuid.uuid4().hex - - token = fixture.V3Token(project_id=project_id, - project_name=project_name, - project_domain_id=project_domain_id, - project_domain_name=project_domain_name) - - self.assertEqual(project_id, token.project_id) - self.assertEqual(project_id, token['token']['project']['id']) - self.assertEqual(project_name, token.project_name) - self.assertEqual(project_name, token['token']['project']['name']) - - self.assertIsNone(token.get('token', {}).get('is_domain')) - - project_domain = token['token']['project']['domain'] - - self.assertEqual(project_domain_id, token.project_domain_id) - self.assertEqual(project_domain_id, project_domain['id']) - self.assertEqual(project_domain_name, token.project_domain_name) - self.assertEqual(project_domain_name, project_domain['name']) - - def test_project_as_domain_scoped(self): - project_id = uuid.uuid4().hex - project_name = uuid.uuid4().hex - project_domain_id = uuid.uuid4().hex - project_domain_name = uuid.uuid4().hex - project_is_domain = True - - token = fixture.V3Token(project_id=project_id, - project_name=project_name, - project_domain_id=project_domain_id, - project_domain_name=project_domain_name, - project_is_domain=project_is_domain) - - self.assertEqual(project_id, token.project_id) - self.assertEqual(project_id, token['token']['project']['id']) - self.assertEqual(project_name, token.project_name) - self.assertEqual(project_name, token['token']['project']['name']) - self.assertEqual(project_is_domain, token['token']['is_domain']) - - project_domain = token['token']['project']['domain'] - - self.assertEqual(project_domain_id, token.project_domain_id) - self.assertEqual(project_domain_id, project_domain['id']) - self.assertEqual(project_domain_name, token.project_domain_name) - self.assertEqual(project_domain_name, project_domain['name']) - - def test_domain_scoped(self): - domain_id = uuid.uuid4().hex - domain_name = uuid.uuid4().hex - - token = fixture.V3Token(domain_id=domain_id, - domain_name=domain_name) - - self.assertEqual(domain_id, token.domain_id) - self.assertEqual(domain_id, token['token']['domain']['id']) - self.assertEqual(domain_name, token.domain_name) - self.assertEqual(domain_name, token['token']['domain']['name']) - - def test_roles(self): - role1 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex} - role2 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex} - - token = fixture.V3Token() - token.add_role(**role1) - token.add_role(**role2) - - self.assertEqual(2, len(token['token']['roles'])) - - self.assertIn(role1, token['token']['roles']) - self.assertIn(role2, token['token']['roles']) - - def test_trust_scoped(self): - trust_id = uuid.uuid4().hex - trustee_user_id = uuid.uuid4().hex - trustor_user_id = uuid.uuid4().hex - impersonation = True - - token = fixture.V3Token(trust_id=trust_id, - trustee_user_id=trustee_user_id, - trustor_user_id=trustor_user_id, - trust_impersonation=impersonation) - - trust = token['token']['OS-TRUST:trust'] - self.assertEqual(trust_id, token.trust_id) - self.assertEqual(trust_id, trust['id']) - self.assertEqual(trustee_user_id, token.trustee_user_id) - self.assertEqual(trustee_user_id, trust['trustee_user']['id']) - self.assertEqual(trustor_user_id, token.trustor_user_id) - self.assertEqual(trustor_user_id, trust['trustor_user']['id']) - self.assertEqual(impersonation, token.trust_impersonation) - self.assertEqual(impersonation, trust['impersonation']) - - def test_oauth_scoped(self): - access_id = uuid.uuid4().hex - consumer_id = uuid.uuid4().hex - - token = fixture.V3Token(oauth_access_token_id=access_id, - oauth_consumer_id=consumer_id) - - oauth = token['token']['OS-OAUTH1'] - - self.assertEqual(access_id, token.oauth_access_token_id) - self.assertEqual(access_id, oauth['access_token_id']) - self.assertEqual(consumer_id, token.oauth_consumer_id) - self.assertEqual(consumer_id, oauth['consumer_id']) - - def test_catalog(self): - service_type = uuid.uuid4().hex - service_name = uuid.uuid4().hex - service_id = uuid.uuid4().hex - region = uuid.uuid4().hex - endpoints = {'public': uuid.uuid4().hex, - 'internal': uuid.uuid4().hex, - 'admin': uuid.uuid4().hex} - - token = fixture.V3Token() - svc = token.add_service(type=service_type, - name=service_name, - id=service_id) - svc.add_standard_endpoints(region=region, **endpoints) - - self.assertEqual(1, len(token['token']['catalog'])) - service = token['token']['catalog'][0] - self.assertEqual(3, len(service['endpoints'])) - - self.assertEqual(service_name, service['name']) - self.assertEqual(service_type, service['type']) - self.assertEqual(service_id, service['id']) - - for endpoint in service['endpoints']: - # assert an id exists for each endpoint, remove it to make testing - # the endpoint content below easier. - self.assertTrue(endpoint.pop('id')) - - for interface, url in endpoints.items(): - endpoint = {'interface': interface, 'url': url, - 'region': region, 'region_id': region} - self.assertIn(endpoint, service['endpoints']) - - def test_empty_default_service_providers(self): - token = fixture.V3Token() - self.assertIsNone(token.service_providers) - - def test_service_providers(self): - def new_sp(): - return { - 'id': uuid.uuid4().hex, - 'sp_url': uuid.uuid4().hex, - 'auth_url': uuid.uuid4().hex - } - ref_service_providers = [new_sp(), new_sp()] - token = fixture.V3Token() - for sp in ref_service_providers: - token.add_service_provider(sp['id'], - sp['auth_url'], - sp['sp_url']) - self.assertEqual(ref_service_providers, token.service_providers) - self.assertEqual(ref_service_providers, - token['token']['service_providers']) - - def test_token_bind(self): - name1 = uuid.uuid4().hex - data1 = uuid.uuid4().hex - name2 = uuid.uuid4().hex - data2 = {uuid.uuid4().hex: uuid.uuid4().hex} - - token = fixture.V3Token() - token.set_bind(name1, data1) - token.set_bind(name2, data2) - - self.assertEqual({name1: data1, name2: data2}, - token['token']['bind']) - - def test_is_admin_project(self): - token = fixture.V3Token() - self.assertIsNone(token.is_admin_project) - self.assertNotIn('is_admin_project', token['token']) - - token.is_admin_project = True - self.assertIs(True, token.is_admin_project) - self.assertIs(True, token['token']['is_admin_project']) - - token.is_admin_project = False - self.assertIs(False, token.is_admin_project) - self.assertIs(False, token['token']['is_admin_project']) - - del token.is_admin_project - self.assertIsNone(token.is_admin_project) - self.assertNotIn('is_admin_project', token['token']) diff --git a/keystoneauth1/tests/unit/test_hacking_checks.py b/keystoneauth1/tests/unit/test_hacking_checks.py deleted file mode 100644 index b81ede7..0000000 --- a/keystoneauth1/tests/unit/test_hacking_checks.py +++ /dev/null @@ -1,47 +0,0 @@ -# Licensed under the Apache License, Version 2.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 textwrap - -import mock -import pep8 -import testtools - -from keystoneauth1.hacking import checks -from keystoneauth1.tests.unit import keystoneauth_fixtures - - -class TestCheckOsloNamespaceImports(testtools.TestCase): - - # We are patching pep8 so that only the check under test is actually - # installed. - @mock.patch('pep8._checks', - {'physical_line': {}, 'logical_line': {}, 'tree': {}}) - def run_check(self, code): - pep8.register_check(checks.check_oslo_namespace_imports) - - lines = textwrap.dedent(code).strip().splitlines(True) - - checker = pep8.Checker(lines=lines) - checker.check_all() - checker.report._deferred_print.sort() - return checker.report._deferred_print - - def assert_has_errors(self, code, expected_errors=None): - actual_errors = [e[:3] for e in self.run_check(code)] - self.assertEqual(expected_errors or [], actual_errors) - - def test(self): - code_ex = self.useFixture(keystoneauth_fixtures.HackingCode()) - code = code_ex.oslo_namespace_imports['code'] - errors = code_ex.oslo_namespace_imports['expected_errors'] - self.assert_has_errors(code, expected_errors=errors) diff --git a/keystoneauth1/tests/unit/test_matchers.py b/keystoneauth1/tests/unit/test_matchers.py deleted file mode 100644 index 4e704b6..0000000 --- a/keystoneauth1/tests/unit/test_matchers.py +++ /dev/null @@ -1,93 +0,0 @@ -# Copyright 2013 OpenStack Foundation -# -# Licensed under the Apache License, Version 2.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 testtools -from testtools import matchers as tt_matchers - -from keystoneauth1.tests.unit import matchers as ks_matchers - -# NOTE(jamielennox): The tests in this file are copied form the non-public -# testtools.tests.matchers.helpers.TestMatchersInterface. - - -class TestXMLEquals(testtools.TestCase): - matches_xml = b""" - - - - -""" - equivalent_xml = b""" - - - - -""" - mismatches_xml = b""" - - - -""" - mismatches_description = """expected = - - - - - -actual = - - - -""" - - matches_matcher = ks_matchers.XMLEquals(matches_xml) - matches_matches = [matches_xml, equivalent_xml] - matches_mismatches = [mismatches_xml] - describe_examples = [ - (mismatches_description, mismatches_xml, matches_matcher), - ] - str_examples = [('XMLEquals(%r)' % matches_xml, matches_matcher)] - - def test_matches_match(self): - matcher = self.matches_matcher - matches = self.matches_matches - mismatches = self.matches_mismatches - for candidate in matches: - self.assertIsNone(matcher.match(candidate)) - for candidate in mismatches: - mismatch = matcher.match(candidate) - self.assertIsNotNone(mismatch) - self.assertIsNotNone(getattr(mismatch, 'describe', None)) - - def test__str__(self): - # [(expected, object to __str__)]. - examples = self.str_examples - for expected, matcher in examples: - self.assertThat(matcher, tt_matchers.DocTestMatches(expected)) - - def test_describe_difference(self): - # [(expected, matchee, matcher), ...] - examples = self.describe_examples - for difference, matchee, matcher in examples: - mismatch = matcher.match(matchee) - self.assertEqual(difference, mismatch.describe()) - - def test_mismatch_details(self): - # The mismatch object must provide get_details, which must return a - # dictionary mapping names to Content objects. - examples = self.describe_examples - for difference, matchee, matcher in examples: - mismatch = matcher.match(matchee) - details = mismatch.get_details() - self.assertEqual(dict(details), details) diff --git a/keystoneauth1/tests/unit/test_noauth.py b/keystoneauth1/tests/unit/test_noauth.py deleted file mode 100644 index 8050b69..0000000 --- a/keystoneauth1/tests/unit/test_noauth.py +++ /dev/null @@ -1,37 +0,0 @@ -# Licensed under the Apache License, Version 2.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 keystoneauth1.loading._plugins import noauth as loader -from keystoneauth1 import noauth -from keystoneauth1 import session -from keystoneauth1.tests.unit import utils - - -class NoAuthTest(utils.TestCase): - - NOAUTH_TOKEN = 'notused' - TEST_URL = 'http://server/prefix' - - def test_basic_case(self): - self.requests_mock.get(self.TEST_URL, text='body') - - a = noauth.NoAuth() - s = session.Session(auth=a) - - data = s.get(self.TEST_URL, authenticated=True) - - self.assertEqual(data.text, 'body') - self.assertRequestHeaderEqual('X-Auth-Token', self.NOAUTH_TOKEN) - self.assertIsNone(a.get_endpoint(s)) - - def test_noauth_options(self): - self.assertEqual([], loader.NoAuth().get_options()) diff --git a/keystoneauth1/tests/unit/test_service_token.py b/keystoneauth1/tests/unit/test_service_token.py deleted file mode 100644 index 1fe2056..0000000 --- a/keystoneauth1/tests/unit/test_service_token.py +++ /dev/null @@ -1,116 +0,0 @@ -# Licensed under the Apache License, Version 2.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 uuid - -from keystoneauth1 import fixture -from keystoneauth1 import identity -from keystoneauth1 import service_token -from keystoneauth1 import session -from keystoneauth1.tests.unit import utils - - -class ServiceTokenTests(utils.TestCase): - - TEST_URL = 'http://test.example.com/path/' - USER_URL = 'http://user-keystone.example.com/v3' - SERVICE_URL = 'http://service-keystone.example.com/v3' - - def setUp(self): - super(ServiceTokenTests, self).setUp() - - self.user_token_id = uuid.uuid4().hex - self.user_token = fixture.V3Token() - self.user_token.set_project_scope() - self.user_auth = identity.V3Password(auth_url=self.USER_URL, - user_id=uuid.uuid4().hex, - password=uuid.uuid4().hex, - project_id=uuid.uuid4().hex) - - self.service_token_id = uuid.uuid4().hex - self.service_token = fixture.V3Token() - self.service_token.set_project_scope() - self.service_auth = identity.V3Password(auth_url=self.SERVICE_URL, - user_id=uuid.uuid4().hex, - password=uuid.uuid4().hex, - project_id=uuid.uuid4().hex) - - for t in (self.user_token, self.service_token): - s = t.add_service('identity') - s.add_standard_endpoints(public='http://keystone.example.com', - admin='http://keystone.example.com', - internal='http://keystone.example.com') - - self.test_data = {'data': uuid.uuid4().hex} - - self.user_mock = self.requests_mock.post( - self.USER_URL + '/auth/tokens', - json=self.user_token, - headers={'X-Subject-Token': self.user_token_id}) - - self.service_mock = self.requests_mock.post( - self.SERVICE_URL + '/auth/tokens', - json=self.service_token, - headers={'X-Subject-Token': self.service_token_id}) - - self.requests_mock.get(self.TEST_URL, json=self.test_data) - - self.combined_auth = service_token.ServiceTokenAuthWrapper( - self.user_auth, - self.service_auth) - - self.session = session.Session(auth=self.combined_auth) - - def test_setting_service_token(self): - self.session.get(self.TEST_URL) - - headers = self.requests_mock.last_request.headers - - self.assertEqual(self.user_token_id, headers['X-Auth-Token']) - self.assertEqual(self.service_token_id, headers['X-Service-Token']) - - self.assertTrue(self.user_mock.called_once) - self.assertTrue(self.service_mock.called_once) - - def test_invalidation(self): - text = uuid.uuid4().hex - test_url = 'http://test.example.com/abc' - - response_list = [{'status_code': 401}, {'text': text}] - mock = self.requests_mock.get(test_url, response_list=response_list) - - resp = self.session.get(test_url) - self.assertEqual(text, resp.text) - - self.assertEqual(2, mock.call_count) - self.assertEqual(2, self.user_mock.call_count) - self.assertEqual(2, self.service_mock.call_count) - - def test_pass_throughs(self): - self.assertEqual(self.user_auth.get_token(self.session), - self.combined_auth.get_token(self.session)) - - self.assertEqual( - self.user_auth.get_endpoint(self.session, 'identity'), - self.combined_auth.get_endpoint(self.session, 'identity')) - - self.assertEqual(self.user_auth.get_user_id(self.session), - self.combined_auth.get_user_id(self.session)) - - self.assertEqual(self.user_auth.get_project_id(self.session), - self.combined_auth.get_project_id(self.session)) - - self.assertEqual(self.user_auth.get_sp_auth_url(self.session, 'a'), - self.combined_auth.get_sp_auth_url(self.session, 'a')) - - self.assertEqual(self.user_auth.get_sp_url(self.session, 'a'), - self.combined_auth.get_sp_url(self.session, 'a')) diff --git a/keystoneauth1/tests/unit/test_session.py b/keystoneauth1/tests/unit/test_session.py deleted file mode 100644 index 5aa0ccb..0000000 --- a/keystoneauth1/tests/unit/test_session.py +++ /dev/null @@ -1,1540 +0,0 @@ -# Licensed under the Apache License, Version 2.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 itertools -import json -import logging -import sys -import uuid - -import mock -import requests -import requests.auth -import six -from testtools import matchers - -from keystoneauth1 import adapter -from keystoneauth1 import discover -from keystoneauth1 import exceptions -from keystoneauth1 import plugin -from keystoneauth1 import session as client_session -from keystoneauth1.tests.unit import utils -from keystoneauth1 import token_endpoint - - -class RequestsAuth(requests.auth.AuthBase): - - def __init__(self, *args, **kwargs): - super(RequestsAuth, self).__init__(*args, **kwargs) - self.header_name = uuid.uuid4().hex - self.header_val = uuid.uuid4().hex - self.called = False - - def __call__(self, request): - request.headers[self.header_name] = self.header_val - self.called = True - return request - - -class SessionTests(utils.TestCase): - - TEST_URL = 'http://127.0.0.1:5000/' - - def test_get(self): - session = client_session.Session() - self.stub_url('GET', text='response') - resp = session.get(self.TEST_URL) - - self.assertEqual('GET', self.requests_mock.last_request.method) - self.assertEqual(resp.text, 'response') - self.assertTrue(resp.ok) - - def test_post(self): - session = client_session.Session() - self.stub_url('POST', text='response') - resp = session.post(self.TEST_URL, json={'hello': 'world'}) - - self.assertEqual('POST', self.requests_mock.last_request.method) - self.assertEqual(resp.text, 'response') - self.assertTrue(resp.ok) - self.assertRequestBodyIs(json={'hello': 'world'}) - - def test_head(self): - session = client_session.Session() - self.stub_url('HEAD') - resp = session.head(self.TEST_URL) - - self.assertEqual('HEAD', self.requests_mock.last_request.method) - self.assertTrue(resp.ok) - self.assertRequestBodyIs('') - - def test_put(self): - session = client_session.Session() - self.stub_url('PUT', text='response') - resp = session.put(self.TEST_URL, json={'hello': 'world'}) - - self.assertEqual('PUT', self.requests_mock.last_request.method) - self.assertEqual(resp.text, 'response') - self.assertTrue(resp.ok) - self.assertRequestBodyIs(json={'hello': 'world'}) - - def test_delete(self): - session = client_session.Session() - self.stub_url('DELETE', text='response') - resp = session.delete(self.TEST_URL) - - self.assertEqual('DELETE', self.requests_mock.last_request.method) - self.assertTrue(resp.ok) - self.assertEqual(resp.text, 'response') - - def test_patch(self): - session = client_session.Session() - self.stub_url('PATCH', text='response') - resp = session.patch(self.TEST_URL, json={'hello': 'world'}) - - self.assertEqual('PATCH', self.requests_mock.last_request.method) - self.assertTrue(resp.ok) - self.assertEqual(resp.text, 'response') - self.assertRequestBodyIs(json={'hello': 'world'}) - - def test_set_microversion_headers(self): - - # String microversion, specified service type - headers = {} - client_session.Session._set_microversion_headers( - headers, '2.30', 'compute', None) - self.assertEqual(headers['OpenStack-API-Version'], 'compute 2.30') - self.assertEqual(headers['X-OpenStack-Nova-API-Version'], '2.30') - self.assertEqual(len(headers.keys()), 2) - - # Tuple microversion, service type via endpoint_filter - headers = {} - client_session.Session._set_microversion_headers( - headers, (2, 30), None, {'service_type': 'compute'}) - self.assertEqual(headers['OpenStack-API-Version'], 'compute 2.30') - self.assertEqual(headers['X-OpenStack-Nova-API-Version'], '2.30') - self.assertEqual(len(headers.keys()), 2) - - # 'latest' (string) microversion - headers = {} - client_session.Session._set_microversion_headers( - headers, 'latest', 'compute', None) - self.assertEqual(headers['OpenStack-API-Version'], 'compute latest') - self.assertEqual(headers['X-OpenStack-Nova-API-Version'], 'latest') - self.assertEqual(len(headers.keys()), 2) - - # LATEST (tuple) microversion - headers = {} - client_session.Session._set_microversion_headers( - headers, (discover.LATEST, discover.LATEST), 'compute', None) - self.assertEqual(headers['OpenStack-API-Version'], 'compute latest') - self.assertEqual(headers['X-OpenStack-Nova-API-Version'], 'latest') - self.assertEqual(len(headers.keys()), 2) - - # ironic microversion, specified service type - headers = {} - client_session.Session._set_microversion_headers( - headers, '2.30', 'baremetal', None) - self.assertEqual(headers['OpenStack-API-Version'], 'baremetal 2.30') - self.assertEqual(headers['X-OpenStack-Ironic-API-Version'], '2.30') - self.assertEqual(len(headers.keys()), 2) - - # volumev2 service-type - volume microversion - headers = {} - client_session.Session._set_microversion_headers( - headers, (2, 30), None, {'service_type': 'volumev2'}) - self.assertEqual(headers['OpenStack-API-Version'], 'volume 2.30') - self.assertEqual(len(headers.keys()), 1) - - # block-storage service-type - volume microversion - headers = {} - client_session.Session._set_microversion_headers( - headers, (2, 30), None, {'service_type': 'block-storage'}) - self.assertEqual(headers['OpenStack-API-Version'], 'volume 2.30') - self.assertEqual(len(headers.keys()), 1) - - # Headers already exist - no change - headers = { - 'OpenStack-API-Version': 'compute 2.30', - 'X-OpenStack-Nova-API-Version': '2.30', - } - client_session.Session._set_microversion_headers( - headers, (2, 31), None, {'service_type': 'volume'}) - self.assertEqual(headers['OpenStack-API-Version'], 'compute 2.30') - self.assertEqual(headers['X-OpenStack-Nova-API-Version'], '2.30') - - # Can't specify a 'M.latest' microversion - self.assertRaises(TypeError, - client_session.Session._set_microversion_headers, - {}, '2.latest', 'service_type', None) - self.assertRaises(TypeError, - client_session.Session._set_microversion_headers, - {}, (2, discover.LATEST), 'service_type', None) - - # Normalization error - self.assertRaises(TypeError, - client_session.Session._set_microversion_headers, - {}, 'bogus', 'service_type', None) - - # No service type in param or endpoint filter - self.assertRaises(TypeError, - client_session.Session._set_microversion_headers, - {}, (2, 30), None, None) - self.assertRaises(TypeError, - client_session.Session._set_microversion_headers, - {}, (2, 30), None, {'no_service_type': 'here'}) - - def test_microversion(self): - # microversion not specified - session = client_session.Session() - self.stub_url('GET', text='response') - resp = session.get(self.TEST_URL) - - self.assertTrue(resp.ok) - self.assertRequestNotInHeader('OpenStack-API-Version') - - session = client_session.Session() - self.stub_url('GET', text='response') - resp = session.get(self.TEST_URL, microversion='2.30', - microversion_service_type='compute', - endpoint_filter={'endpoint': 'filter'}) - - self.assertTrue(resp.ok) - self.assertRequestHeaderEqual('OpenStack-API-Version', 'compute 2.30') - self.assertRequestHeaderEqual('X-OpenStack-Nova-API-Version', '2.30') - - def test_user_agent(self): - session = client_session.Session() - self.stub_url('GET', text='response') - resp = session.get(self.TEST_URL) - - self.assertTrue(resp.ok) - self.assertRequestHeaderEqual( - 'User-Agent', - '%s %s' % ("run.py", client_session.DEFAULT_USER_AGENT)) - - custom_agent = 'custom-agent/1.0' - session = client_session.Session(user_agent=custom_agent) - self.stub_url('GET', text='response') - resp = session.get(self.TEST_URL) - - self.assertTrue(resp.ok) - self.assertRequestHeaderEqual( - 'User-Agent', - '%s %s' % (custom_agent, client_session.DEFAULT_USER_AGENT)) - - resp = session.get(self.TEST_URL, headers={'User-Agent': 'new-agent'}) - self.assertTrue(resp.ok) - self.assertRequestHeaderEqual('User-Agent', 'new-agent') - - resp = session.get(self.TEST_URL, headers={'User-Agent': 'new-agent'}, - user_agent='overrides-agent') - self.assertTrue(resp.ok) - self.assertRequestHeaderEqual('User-Agent', 'overrides-agent') - - # If sys.argv is an empty list, then doesn't fail. - with mock.patch.object(sys, 'argv', []): - session = client_session.Session() - resp = session.get(self.TEST_URL) - self.assertTrue(resp.ok) - self.assertRequestHeaderEqual( - 'User-Agent', - client_session.DEFAULT_USER_AGENT) - - # If sys.argv[0] is an empty string, then doesn't fail. - with mock.patch.object(sys, 'argv', ['']): - session = client_session.Session() - resp = session.get(self.TEST_URL) - self.assertTrue(resp.ok) - self.assertRequestHeaderEqual( - 'User-Agent', - client_session.DEFAULT_USER_AGENT) - - def test_http_session_opts(self): - session = client_session.Session(cert='cert.pem', timeout=5, - verify='certs') - - FAKE_RESP = utils.TestResponse({'status_code': 200, 'text': 'resp'}) - RESP = mock.Mock(return_value=FAKE_RESP) - - with mock.patch.object(session.session, 'request', RESP) as mocked: - session.post(self.TEST_URL, data='value') - - mock_args, mock_kwargs = mocked.call_args - - self.assertEqual(mock_args[0], 'POST') - self.assertEqual(mock_args[1], self.TEST_URL) - self.assertEqual(mock_kwargs['data'], 'value') - self.assertEqual(mock_kwargs['cert'], 'cert.pem') - self.assertEqual(mock_kwargs['verify'], 'certs') - self.assertEqual(mock_kwargs['timeout'], 5) - - def test_not_found(self): - session = client_session.Session() - self.stub_url('GET', status_code=404) - self.assertRaises(exceptions.NotFound, session.get, self.TEST_URL) - - def test_server_error(self): - session = client_session.Session() - self.stub_url('GET', status_code=500) - self.assertRaises(exceptions.InternalServerError, - session.get, self.TEST_URL) - - def test_session_debug_output(self): - """Test request and response headers in debug logs. - - in order to redact secure headers while debug is true. - """ - session = client_session.Session(verify=False) - headers = {'HEADERA': 'HEADERVALB', - 'Content-Type': 'application/json'} - security_headers = {'Authorization': uuid.uuid4().hex, - 'X-Auth-Token': uuid.uuid4().hex, - 'X-Subject-Token': uuid.uuid4().hex, - 'X-Service-Token': uuid.uuid4().hex} - body = '{"a": "b"}' - data = '{"c": "d"}' - all_headers = dict( - itertools.chain(headers.items(), security_headers.items())) - self.stub_url('POST', text=body, headers=all_headers) - resp = session.post(self.TEST_URL, headers=all_headers, data=data) - self.assertEqual(resp.status_code, 200) - - self.assertIn('curl', self.logger.output) - self.assertIn('POST', self.logger.output) - self.assertIn('--insecure', self.logger.output) - self.assertIn(body, self.logger.output) - self.assertIn("'%s'" % data, self.logger.output) - - for k, v in headers.items(): - self.assertIn(k, self.logger.output) - self.assertIn(v, self.logger.output) - - # Assert that response headers contains actual values and - # only debug logs has been masked - for k, v in security_headers.items(): - self.assertIn('%s: {SHA1}' % k, self.logger.output) - self.assertEqual(v, resp.headers[k]) - self.assertNotIn(v, self.logger.output) - - def test_session_debug_output_logs_openstack_request_id(self): - """Test x-openstack-request-id is logged in debug logs.""" - def get_response(log=True): - session = client_session.Session(verify=False) - endpoint_filter = {'service_name': 'Identity'} - headers = {'X-OpenStack-Request-Id': 'req-1234'} - body = 'BODYRESPONSE' - data = 'BODYDATA' - all_headers = dict(itertools.chain(headers.items())) - self.stub_url('POST', text=body, headers=all_headers) - resp = session.post(self.TEST_URL, endpoint_filter=endpoint_filter, - headers=all_headers, data=data, log=log) - return resp - - # if log is disabled then request-id is not logged in debug logs - resp = get_response(log=False) - self.assertEqual(resp.status_code, 200) - - expected_log = ('POST call to Identity for %s used request ' - 'id req-1234' % self.TEST_URL) - self.assertNotIn(expected_log, self.logger.output) - - # if log is enabled then request-id is logged in debug logs - resp = get_response() - self.assertEqual(resp.status_code, 200) - self.assertIn(expected_log, self.logger.output) - - def test_logs_failed_output(self): - """Test that output is logged even for failed requests.""" - session = client_session.Session() - body = {uuid.uuid4().hex: uuid.uuid4().hex} - - self.stub_url('GET', json=body, status_code=400, - headers={'Content-Type': 'application/json'}) - resp = session.get(self.TEST_URL, raise_exc=False) - - self.assertEqual(resp.status_code, 400) - self.assertIn(list(body.keys())[0], self.logger.output) - self.assertIn(list(body.values())[0], self.logger.output) - - def test_logging_body_only_for_specified_content_types(self): - """Verify response body is only logged in specific content types. - - Response bodies are logged only when the response's Content-Type header - is set to application/json. This prevents us to get an unexpected - MemoryError when reading arbitrary responses, such as streams. - """ - OMITTED_BODY = ('Omitted, Content-Type is set to %s. Only ' - 'application/json responses have their bodies logged.') - session = client_session.Session(verify=False) - - # Content-Type is not set - body = json.dumps({'token': {'id': '...'}}) - self.stub_url('POST', text=body) - session.post(self.TEST_URL) - self.assertNotIn(body, self.logger.output) - self.assertIn(OMITTED_BODY % None, self.logger.output) - - # Content-Type is set to text/xml - body = '...' - self.stub_url('POST', text=body, headers={'Content-Type': 'text/xml'}) - session.post(self.TEST_URL) - self.assertNotIn(body, self.logger.output) - self.assertIn(OMITTED_BODY % 'text/xml', self.logger.output) - - # Content-Type is set to application/json - body = json.dumps({'token': {'id': '...'}}) - self.stub_url('POST', text=body, - headers={'Content-Type': 'application/json'}) - session.post(self.TEST_URL) - self.assertIn(body, self.logger.output) - self.assertNotIn(OMITTED_BODY % 'application/json', self.logger.output) - - # Content-Type is set to application/json; charset=UTF-8 - body = json.dumps({'token': {'id': '...'}}) - self.stub_url( - 'POST', text=body, - headers={'Content-Type': 'application/json; charset=UTF-8'}) - session.post(self.TEST_URL) - self.assertIn(body, self.logger.output) - self.assertNotIn(OMITTED_BODY % 'application/json; charset=UTF-8', - self.logger.output) - - def test_logging_cacerts(self): - path_to_certs = '/path/to/certs' - session = client_session.Session(verify=path_to_certs) - - self.stub_url('GET', text='text') - session.get(self.TEST_URL) - - self.assertIn('--cacert', self.logger.output) - self.assertIn(path_to_certs, self.logger.output) - - def test_connect_retries(self): - self.stub_url('GET', exc=requests.exceptions.Timeout()) - - session = client_session.Session() - retries = 3 - - with mock.patch('time.sleep') as m: - self.assertRaises(exceptions.ConnectTimeout, - session.get, - self.TEST_URL, connect_retries=retries) - - self.assertEqual(retries, m.call_count) - # 3 retries finishing with 2.0 means 0.5, 1.0 and 2.0 - m.assert_called_with(2.0) - - # we count retries so there will be one initial request + 3 retries - self.assertThat(self.requests_mock.request_history, - matchers.HasLength(retries + 1)) - - def test_uses_tcp_keepalive_by_default(self): - session = client_session.Session() - requests_session = session.session - self.assertIsInstance(requests_session.adapters['http://'], - client_session.TCPKeepAliveAdapter) - self.assertIsInstance(requests_session.adapters['https://'], - client_session.TCPKeepAliveAdapter) - - def test_does_not_set_tcp_keepalive_on_custom_sessions(self): - mock_session = mock.Mock() - client_session.Session(session=mock_session) - self.assertFalse(mock_session.mount.called) - - def test_ssl_error_message(self): - error = uuid.uuid4().hex - - self.stub_url('GET', exc=requests.exceptions.SSLError(error)) - session = client_session.Session() - - # The exception should contain the URL and details about the SSL error - msg = 'SSL exception connecting to %(url)s: %(error)s' % { - 'url': self.TEST_URL, 'error': error} - self.assertRaisesRegex(exceptions.SSLError, - msg, - session.get, - self.TEST_URL) - - def test_json_content_type(self): - session = client_session.Session() - self.stub_url('POST', text='response') - resp = session.post( - self.TEST_URL, - json=[{'op': 'replace', - 'path': '/name', - 'value': 'new_name'}], - headers={'Content-Type': 'application/json-patch+json'}) - - self.assertEqual('POST', self.requests_mock.last_request.method) - self.assertEqual(resp.text, 'response') - self.assertTrue(resp.ok) - self.assertRequestBodyIs( - json=[{'op': 'replace', - 'path': '/name', - 'value': 'new_name'}]) - self.assertContentTypeIs('application/json-patch+json') - - -class RedirectTests(utils.TestCase): - - REDIRECT_CHAIN = ['http://myhost:3445/', - 'http://anotherhost:6555/', - 'http://thirdhost/', - 'http://finaldestination:55/'] - - DEFAULT_REDIRECT_BODY = 'Redirect' - DEFAULT_RESP_BODY = 'Found' - - def setup_redirects(self, method='GET', status_code=305, - redirect_kwargs={}, final_kwargs={}): - redirect_kwargs.setdefault('text', self.DEFAULT_REDIRECT_BODY) - - for s, d in zip(self.REDIRECT_CHAIN, self.REDIRECT_CHAIN[1:]): - self.requests_mock.register_uri(method, s, status_code=status_code, - headers={'Location': d}, - **redirect_kwargs) - - final_kwargs.setdefault('status_code', 200) - final_kwargs.setdefault('text', self.DEFAULT_RESP_BODY) - self.requests_mock.register_uri(method, self.REDIRECT_CHAIN[-1], - **final_kwargs) - - def assertResponse(self, resp): - self.assertEqual(resp.status_code, 200) - self.assertEqual(resp.text, self.DEFAULT_RESP_BODY) - - def test_basic_get(self): - session = client_session.Session() - self.setup_redirects() - resp = session.get(self.REDIRECT_CHAIN[-2]) - self.assertResponse(resp) - - def test_basic_post_keeps_correct_method(self): - session = client_session.Session() - self.setup_redirects(method='POST', status_code=301) - resp = session.post(self.REDIRECT_CHAIN[-2]) - self.assertResponse(resp) - - def test_redirect_forever(self): - session = client_session.Session(redirect=True) - self.setup_redirects() - resp = session.get(self.REDIRECT_CHAIN[0]) - self.assertResponse(resp) - self.assertTrue(len(resp.history), len(self.REDIRECT_CHAIN)) - - def test_no_redirect(self): - session = client_session.Session(redirect=False) - self.setup_redirects() - resp = session.get(self.REDIRECT_CHAIN[0]) - self.assertEqual(resp.status_code, 305) - self.assertEqual(resp.url, self.REDIRECT_CHAIN[0]) - - def test_redirect_limit(self): - self.setup_redirects() - for i in (1, 2): - session = client_session.Session(redirect=i) - resp = session.get(self.REDIRECT_CHAIN[0]) - self.assertEqual(resp.status_code, 305) - self.assertEqual(resp.url, self.REDIRECT_CHAIN[i]) - self.assertEqual(resp.text, self.DEFAULT_REDIRECT_BODY) - - def test_history_matches_requests(self): - self.setup_redirects(status_code=301) - session = client_session.Session(redirect=True) - req_resp = requests.get(self.REDIRECT_CHAIN[0], - allow_redirects=True) - - ses_resp = session.get(self.REDIRECT_CHAIN[0]) - - self.assertEqual(len(req_resp.history), len(ses_resp.history)) - - for r, s in zip(req_resp.history, ses_resp.history): - self.assertEqual(r.url, s.url) - self.assertEqual(r.status_code, s.status_code) - - def test_permanent_redirect_308(self): - session = client_session.Session() - self.setup_redirects(status_code=308) - resp = session.get(self.REDIRECT_CHAIN[-2]) - self.assertResponse(resp) - - -class AuthPlugin(plugin.BaseAuthPlugin): - """Very simple debug authentication plugin. - - Takes Parameters such that it can throw exceptions at the right times. - """ - - TEST_TOKEN = utils.TestCase.TEST_TOKEN - TEST_USER_ID = 'aUser' - TEST_PROJECT_ID = 'aProject' - - SERVICE_URLS = { - 'identity': {'public': 'http://identity-public:1111/v2.0', - 'admin': 'http://identity-admin:1111/v2.0'}, - 'compute': {'public': 'http://compute-public:2222/v1.0', - 'admin': 'http://compute-admin:2222/v1.0'}, - 'image': {'public': 'http://image-public:3333/v2.0', - 'admin': 'http://image-admin:3333/v2.0'} - } - - def __init__(self, token=TEST_TOKEN, invalidate=True): - self.token = token - self._invalidate = invalidate - - def get_token(self, session): - return self.token - - def get_endpoint(self, session, service_type=None, interface=None, - **kwargs): - try: - return self.SERVICE_URLS[service_type][interface] - except (KeyError, AttributeError): - return None - - def invalidate(self): - return self._invalidate - - def get_user_id(self, session): - return self.TEST_USER_ID - - def get_project_id(self, session): - return self.TEST_PROJECT_ID - - -class CalledAuthPlugin(plugin.BaseAuthPlugin): - - ENDPOINT = 'http://fakeendpoint/' - TOKEN = utils.TestCase.TEST_TOKEN - USER_ID = uuid.uuid4().hex - PROJECT_ID = uuid.uuid4().hex - - def __init__(self, invalidate=True): - self.get_token_called = False - self.get_endpoint_called = False - self.endpoint_arguments = {} - self.invalidate_called = False - self.get_project_id_called = False - self.get_user_id_called = False - self._invalidate = invalidate - - def get_token(self, session): - self.get_token_called = True - return self.TOKEN - - def get_endpoint(self, session, **kwargs): - self.get_endpoint_called = True - self.endpoint_arguments = kwargs - return self.ENDPOINT - - def invalidate(self): - self.invalidate_called = True - return self._invalidate - - def get_project_id(self, session, **kwargs): - self.get_project_id_called = True - return self.PROJECT_ID - - def get_user_id(self, session, **kwargs): - self.get_user_id_called = True - return self.USER_ID - - -class SessionAuthTests(utils.TestCase): - - TEST_URL = 'http://127.0.0.1:5000/' - TEST_JSON = {'hello': 'world'} - - def stub_service_url(self, service_type, interface, path, - method='GET', **kwargs): - base_url = AuthPlugin.SERVICE_URLS[service_type][interface] - uri = "%s/%s" % (base_url.rstrip('/'), path.lstrip('/')) - - self.requests_mock.register_uri(method, uri, **kwargs) - - def test_auth_plugin_default_with_plugin(self): - self.stub_url('GET', base_url=self.TEST_URL, json=self.TEST_JSON) - - # if there is an auth_plugin then it should default to authenticated - auth = AuthPlugin() - sess = client_session.Session(auth=auth) - resp = sess.get(self.TEST_URL) - self.assertEqual(resp.json(), self.TEST_JSON) - - self.assertRequestHeaderEqual('X-Auth-Token', AuthPlugin.TEST_TOKEN) - - def test_auth_plugin_disable(self): - self.stub_url('GET', base_url=self.TEST_URL, json=self.TEST_JSON) - - auth = AuthPlugin() - sess = client_session.Session(auth=auth) - resp = sess.get(self.TEST_URL, authenticated=False) - self.assertEqual(resp.json(), self.TEST_JSON) - - self.assertRequestHeaderEqual('X-Auth-Token', None) - - def test_service_type_urls(self): - service_type = 'compute' - interface = 'public' - path = '/instances' - status = 200 - body = 'SUCCESS' - - self.stub_service_url(service_type=service_type, - interface=interface, - path=path, - status_code=status, - text=body) - - sess = client_session.Session(auth=AuthPlugin()) - resp = sess.get(path, - endpoint_filter={'service_type': service_type, - 'interface': interface}) - - self.assertEqual(self.requests_mock.last_request.url, - AuthPlugin.SERVICE_URLS['compute']['public'] + path) - self.assertEqual(resp.text, body) - self.assertEqual(resp.status_code, status) - - def test_service_url_raises_if_no_auth_plugin(self): - sess = client_session.Session() - self.assertRaises(exceptions.MissingAuthPlugin, - sess.get, '/path', - endpoint_filter={'service_type': 'compute', - 'interface': 'public'}) - - def test_service_url_raises_if_no_url_returned(self): - sess = client_session.Session(auth=AuthPlugin()) - self.assertRaises(exceptions.EndpointNotFound, - sess.get, '/path', - endpoint_filter={'service_type': 'unknown', - 'interface': 'public'}) - - def test_raises_exc_only_when_asked(self): - # A request that returns a HTTP error should by default raise an - # exception by default, if you specify raise_exc=False then it will not - self.requests_mock.get(self.TEST_URL, status_code=401) - - sess = client_session.Session() - self.assertRaises(exceptions.Unauthorized, sess.get, self.TEST_URL) - - resp = sess.get(self.TEST_URL, raise_exc=False) - self.assertEqual(401, resp.status_code) - - def test_passed_auth_plugin(self): - passed = CalledAuthPlugin() - sess = client_session.Session() - - self.requests_mock.get(CalledAuthPlugin.ENDPOINT + 'path', - status_code=200) - endpoint_filter = {'service_type': 'identity'} - - # no plugin with authenticated won't work - self.assertRaises(exceptions.MissingAuthPlugin, sess.get, 'path', - authenticated=True) - - # no plugin with an endpoint filter won't work - self.assertRaises(exceptions.MissingAuthPlugin, sess.get, 'path', - authenticated=False, endpoint_filter=endpoint_filter) - - resp = sess.get('path', auth=passed, endpoint_filter=endpoint_filter) - - self.assertEqual(200, resp.status_code) - self.assertTrue(passed.get_endpoint_called) - self.assertTrue(passed.get_token_called) - - def test_passed_auth_plugin_overrides(self): - fixed = CalledAuthPlugin() - passed = CalledAuthPlugin() - - sess = client_session.Session(fixed) - - self.requests_mock.get(CalledAuthPlugin.ENDPOINT + 'path', - status_code=200) - - resp = sess.get('path', auth=passed, - endpoint_filter={'service_type': 'identity'}) - - self.assertEqual(200, resp.status_code) - self.assertTrue(passed.get_endpoint_called) - self.assertTrue(passed.get_token_called) - self.assertFalse(fixed.get_endpoint_called) - self.assertFalse(fixed.get_token_called) - - def test_requests_auth_plugin(self): - sess = client_session.Session() - requests_auth = RequestsAuth() - - self.requests_mock.get(self.TEST_URL, text='resp') - - sess.get(self.TEST_URL, requests_auth=requests_auth) - last = self.requests_mock.last_request - - self.assertEqual(requests_auth.header_val, - last.headers[requests_auth.header_name]) - self.assertTrue(requests_auth.called) - - def test_reauth_called(self): - auth = CalledAuthPlugin(invalidate=True) - sess = client_session.Session(auth=auth) - - self.requests_mock.get(self.TEST_URL, - [{'text': 'Failed', 'status_code': 401}, - {'text': 'Hello', 'status_code': 200}]) - - # allow_reauth=True is the default - resp = sess.get(self.TEST_URL, authenticated=True) - - self.assertEqual(200, resp.status_code) - self.assertEqual('Hello', resp.text) - self.assertTrue(auth.invalidate_called) - - def test_reauth_not_called(self): - auth = CalledAuthPlugin(invalidate=True) - sess = client_session.Session(auth=auth) - - self.requests_mock.get(self.TEST_URL, - [{'text': 'Failed', 'status_code': 401}, - {'text': 'Hello', 'status_code': 200}]) - - self.assertRaises(exceptions.Unauthorized, sess.get, self.TEST_URL, - authenticated=True, allow_reauth=False) - self.assertFalse(auth.invalidate_called) - - def test_endpoint_override_overrides_filter(self): - auth = CalledAuthPlugin() - sess = client_session.Session(auth=auth) - - override_base = 'http://mytest/' - path = 'path' - override_url = override_base + path - resp_text = uuid.uuid4().hex - - self.requests_mock.get(override_url, text=resp_text) - - resp = sess.get(path, - endpoint_override=override_base, - endpoint_filter={'service_type': 'identity'}) - - self.assertEqual(resp_text, resp.text) - self.assertEqual(override_url, self.requests_mock.last_request.url) - - self.assertTrue(auth.get_token_called) - self.assertFalse(auth.get_endpoint_called) - - self.assertFalse(auth.get_user_id_called) - self.assertFalse(auth.get_project_id_called) - - def test_endpoint_override_ignore_full_url(self): - auth = CalledAuthPlugin() - sess = client_session.Session(auth=auth) - - path = 'path' - url = self.TEST_URL + path - - resp_text = uuid.uuid4().hex - self.requests_mock.get(url, text=resp_text) - - resp = sess.get(url, - endpoint_override='http://someother.url', - endpoint_filter={'service_type': 'identity'}) - - self.assertEqual(resp_text, resp.text) - self.assertEqual(url, self.requests_mock.last_request.url) - - self.assertTrue(auth.get_token_called) - self.assertFalse(auth.get_endpoint_called) - - self.assertFalse(auth.get_user_id_called) - self.assertFalse(auth.get_project_id_called) - - def test_endpoint_override_does_id_replacement(self): - auth = CalledAuthPlugin() - sess = client_session.Session(auth=auth) - - override_base = 'http://mytest/%(project_id)s/%(user_id)s' - path = 'path' - replacements = {'user_id': CalledAuthPlugin.USER_ID, - 'project_id': CalledAuthPlugin.PROJECT_ID} - override_url = override_base % replacements + '/' + path - resp_text = uuid.uuid4().hex - - self.requests_mock.get(override_url, text=resp_text) - - resp = sess.get(path, - endpoint_override=override_base, - endpoint_filter={'service_type': 'identity'}) - - self.assertEqual(resp_text, resp.text) - self.assertEqual(override_url, self.requests_mock.last_request.url) - - self.assertTrue(auth.get_token_called) - self.assertTrue(auth.get_user_id_called) - self.assertTrue(auth.get_project_id_called) - self.assertFalse(auth.get_endpoint_called) - - def test_endpoint_override_fails_to_replace_if_none(self): - # The token_endpoint plugin doesn't know user_id or project_id - auth = token_endpoint.Token(uuid.uuid4().hex, uuid.uuid4().hex) - sess = client_session.Session(auth=auth) - - override_base = 'http://mytest/%(project_id)s' - - e = self.assertRaises(ValueError, - sess.get, - '/path', - endpoint_override=override_base, - endpoint_filter={'service_type': 'identity'}) - - self.assertIn('project_id', str(e)) - override_base = 'http://mytest/%(user_id)s' - - e = self.assertRaises(ValueError, - sess.get, - '/path', - endpoint_override=override_base, - endpoint_filter={'service_type': 'identity'}) - self.assertIn('user_id', str(e)) - - def test_endpoint_override_fails_to_do_unknown_replacement(self): - auth = CalledAuthPlugin() - sess = client_session.Session(auth=auth) - - override_base = 'http://mytest/%(unknown_id)s' - - e = self.assertRaises(AttributeError, - sess.get, - '/path', - endpoint_override=override_base, - endpoint_filter={'service_type': 'identity'}) - self.assertIn('unknown_id', str(e)) - - def test_user_and_project_id(self): - auth = AuthPlugin() - sess = client_session.Session(auth=auth) - - self.assertEqual(auth.TEST_USER_ID, sess.get_user_id()) - self.assertEqual(auth.TEST_PROJECT_ID, sess.get_project_id()) - - def test_logger_object_passed(self): - logger = logging.getLogger(uuid.uuid4().hex) - logger.setLevel(logging.DEBUG) - logger.propagate = False - - io = six.StringIO() - handler = logging.StreamHandler(io) - logger.addHandler(handler) - - auth = AuthPlugin() - sess = client_session.Session(auth=auth) - response = {uuid.uuid4().hex: uuid.uuid4().hex} - - self.stub_url('GET', - json=response, - headers={'Content-Type': 'application/json'}) - - resp = sess.get(self.TEST_URL, logger=logger) - - self.assertEqual(response, resp.json()) - output = io.getvalue() - - self.assertIn(self.TEST_URL, output) - self.assertIn(list(response.keys())[0], output) - self.assertIn(list(response.values())[0], output) - - self.assertNotIn(self.TEST_URL, self.logger.output) - self.assertNotIn(list(response.keys())[0], self.logger.output) - self.assertNotIn(list(response.values())[0], self.logger.output) - - -class AdapterTest(utils.TestCase): - - SERVICE_TYPE = uuid.uuid4().hex - SERVICE_NAME = uuid.uuid4().hex - INTERFACE = uuid.uuid4().hex - REGION_NAME = uuid.uuid4().hex - USER_AGENT = uuid.uuid4().hex - VERSION = uuid.uuid4().hex - ALLOW = {'allow_deprecated': False, - 'allow_experimental': True, - 'allow_unknown': True} - - TEST_URL = CalledAuthPlugin.ENDPOINT - - def _create_loaded_adapter(self, sess=None, auth=None): - return adapter.Adapter(sess or client_session.Session(), - auth=auth or CalledAuthPlugin(), - service_type=self.SERVICE_TYPE, - service_name=self.SERVICE_NAME, - interface=self.INTERFACE, - region_name=self.REGION_NAME, - user_agent=self.USER_AGENT, - version=self.VERSION, - allow=self.ALLOW) - - def _verify_endpoint_called(self, adpt): - self.assertEqual(self.SERVICE_TYPE, - adpt.auth.endpoint_arguments['service_type']) - self.assertEqual(self.SERVICE_NAME, - adpt.auth.endpoint_arguments['service_name']) - self.assertEqual(self.INTERFACE, - adpt.auth.endpoint_arguments['interface']) - self.assertEqual(self.REGION_NAME, - adpt.auth.endpoint_arguments['region_name']) - self.assertEqual(self.VERSION, - adpt.auth.endpoint_arguments['version']) - - def test_setting_variables_on_request(self): - response = uuid.uuid4().hex - self.stub_url('GET', text=response) - adpt = self._create_loaded_adapter() - resp = adpt.get('/') - self.assertEqual(resp.text, response) - - self._verify_endpoint_called(adpt) - self.assertEqual(self.ALLOW, - adpt.auth.endpoint_arguments['allow']) - self.assertTrue(adpt.auth.get_token_called) - self.assertRequestHeaderEqual('User-Agent', self.USER_AGENT) - - def test_setting_global_id_on_request(self): - global_id = "req-%s" % uuid.uuid4() - response = uuid.uuid4().hex - self.stub_url('GET', text=response) - adpt = adapter.Adapter(client_session.Session(), - auth=CalledAuthPlugin(), - service_type=self.SERVICE_TYPE, - service_name=self.SERVICE_NAME, - interface=self.INTERFACE, - region_name=self.REGION_NAME, - user_agent=self.USER_AGENT, - version=self.VERSION, - allow=self.ALLOW, - global_request_id=global_id) - resp = adpt.get('/') - self.assertEqual(resp.text, response) - - self._verify_endpoint_called(adpt) - self.assertEqual(self.ALLOW, - adpt.auth.endpoint_arguments['allow']) - self.assertTrue(adpt.auth.get_token_called) - self.assertRequestHeaderEqual('X-OpenStack-Request-ID', global_id) - - def test_setting_variables_on_get_endpoint(self): - adpt = self._create_loaded_adapter() - url = adpt.get_endpoint() - - self.assertEqual(self.TEST_URL, url) - self._verify_endpoint_called(adpt) - - def test_legacy_binding(self): - key = uuid.uuid4().hex - val = uuid.uuid4().hex - response = json.dumps({key: val}) - - self.stub_url('GET', text=response) - - auth = CalledAuthPlugin() - sess = client_session.Session(auth=auth) - adpt = adapter.LegacyJsonAdapter(sess, - service_type=self.SERVICE_TYPE, - user_agent=self.USER_AGENT) - - resp, body = adpt.get('/') - self.assertEqual(self.SERVICE_TYPE, - auth.endpoint_arguments['service_type']) - self.assertEqual(resp.text, response) - self.assertEqual(val, body[key]) - - def test_legacy_binding_non_json_resp(self): - response = uuid.uuid4().hex - self.stub_url('GET', text=response, - headers={'Content-Type': 'text/html'}) - - auth = CalledAuthPlugin() - sess = client_session.Session(auth=auth) - adpt = adapter.LegacyJsonAdapter(sess, - service_type=self.SERVICE_TYPE, - user_agent=self.USER_AGENT) - - resp, body = adpt.get('/') - self.assertEqual(self.SERVICE_TYPE, - auth.endpoint_arguments['service_type']) - self.assertEqual(resp.text, response) - self.assertIsNone(body) - - def test_methods(self): - sess = client_session.Session() - adpt = adapter.Adapter(sess) - url = 'http://url' - - for method in ['get', 'head', 'post', 'put', 'patch', 'delete']: - with mock.patch.object(adpt, 'request') as m: - getattr(adpt, method)(url) - m.assert_called_once_with(url, method.upper()) - - def test_setting_endpoint_override(self): - endpoint_override = 'http://overrideurl' - path = '/path' - endpoint_url = endpoint_override + path - - auth = CalledAuthPlugin() - sess = client_session.Session(auth=auth) - adpt = adapter.Adapter(sess, endpoint_override=endpoint_override) - - response = uuid.uuid4().hex - self.requests_mock.get(endpoint_url, text=response) - - resp = adpt.get(path) - - self.assertEqual(response, resp.text) - self.assertEqual(endpoint_url, self.requests_mock.last_request.url) - - self.assertEqual(endpoint_override, adpt.get_endpoint()) - - def test_adapter_invalidate(self): - auth = CalledAuthPlugin() - sess = client_session.Session() - adpt = adapter.Adapter(sess, auth=auth) - - adpt.invalidate() - - self.assertTrue(auth.invalidate_called) - - def test_adapter_get_token(self): - auth = CalledAuthPlugin() - sess = client_session.Session() - adpt = adapter.Adapter(sess, auth=auth) - - self.assertEqual(self.TEST_TOKEN, adpt.get_token()) - self.assertTrue(auth.get_token_called) - - def test_adapter_connect_retries(self): - retries = 2 - sess = client_session.Session() - adpt = adapter.Adapter(sess, connect_retries=retries) - - self.stub_url('GET', exc=requests.exceptions.ConnectionError()) - - with mock.patch('time.sleep') as m: - self.assertRaises(exceptions.ConnectionError, - adpt.get, self.TEST_URL) - self.assertEqual(retries, m.call_count) - - # we count retries so there will be one initial request + 2 retries - self.assertThat(self.requests_mock.request_history, - matchers.HasLength(retries + 1)) - - def test_user_and_project_id(self): - auth = AuthPlugin() - sess = client_session.Session() - adpt = adapter.Adapter(sess, auth=auth) - - self.assertEqual(auth.TEST_USER_ID, adpt.get_user_id()) - self.assertEqual(auth.TEST_PROJECT_ID, adpt.get_project_id()) - - def test_logger_object_passed(self): - logger = logging.getLogger(uuid.uuid4().hex) - logger.setLevel(logging.DEBUG) - logger.propagate = False - - io = six.StringIO() - handler = logging.StreamHandler(io) - logger.addHandler(handler) - - auth = AuthPlugin() - sess = client_session.Session(auth=auth) - adpt = adapter.Adapter(sess, auth=auth, logger=logger) - - response = {uuid.uuid4().hex: uuid.uuid4().hex} - - self.stub_url('GET', json=response, - headers={'Content-Type': 'application/json'}) - - resp = adpt.get(self.TEST_URL, logger=logger) - - self.assertEqual(response, resp.json()) - output = io.getvalue() - - self.assertIn(self.TEST_URL, output) - self.assertIn(list(response.keys())[0], output) - self.assertIn(list(response.values())[0], output) - - self.assertNotIn(self.TEST_URL, self.logger.output) - self.assertNotIn(list(response.keys())[0], self.logger.output) - self.assertNotIn(list(response.values())[0], self.logger.output) - - def test_unknown_connection_error(self): - self.stub_url('GET', exc=requests.exceptions.RequestException) - self.assertRaises(exceptions.UnknownConnectionError, - client_session.Session().request, - self.TEST_URL, - 'GET') - - def test_additional_headers(self): - session_key = uuid.uuid4().hex - session_val = uuid.uuid4().hex - adapter_key = uuid.uuid4().hex - adapter_val = uuid.uuid4().hex - request_key = uuid.uuid4().hex - request_val = uuid.uuid4().hex - text = uuid.uuid4().hex - - url = 'http://keystone.test.com' - self.requests_mock.get(url, text=text) - - sess = client_session.Session( - additional_headers={session_key: session_val}) - adap = adapter.Adapter(session=sess, - additional_headers={adapter_key: adapter_val}) - resp = adap.get(url, headers={request_key: request_val}) - - request = self.requests_mock.last_request - - self.assertEqual(resp.text, text) - self.assertEqual(session_val, request.headers[session_key]) - self.assertEqual(adapter_val, request.headers[adapter_key]) - self.assertEqual(request_val, request.headers[request_key]) - - def test_additional_headers_overrides(self): - header = uuid.uuid4().hex - session_val = uuid.uuid4().hex - adapter_val = uuid.uuid4().hex - request_val = uuid.uuid4().hex - - url = 'http://keystone.test.com' - self.requests_mock.get(url) - - sess = client_session.Session(additional_headers={header: session_val}) - adap = adapter.Adapter(session=sess) - - adap.get(url) - self.assertEqual(session_val, - self.requests_mock.last_request.headers[header]) - - adap.additional_headers[header] = adapter_val - adap.get(url) - self.assertEqual(adapter_val, - self.requests_mock.last_request.headers[header]) - - adap.get(url, headers={header: request_val}) - self.assertEqual(request_val, - self.requests_mock.last_request.headers[header]) - - def test_adapter_user_agent_session_adapter(self): - sess = client_session.Session(app_name='ksatest', app_version='1.2.3') - adap = adapter.Adapter(client_name='testclient', - client_version='4.5.6', - session=sess) - - url = 'http://keystone.test.com' - self.requests_mock.get(url) - - adap.get(url) - - agent = 'ksatest/1.2.3 testclient/4.5.6' - self.assertEqual(agent + ' ' + client_session.DEFAULT_USER_AGENT, - self.requests_mock.last_request.headers['User-Agent']) - - def test_adapter_user_agent_session_version_on_adapter(self): - - class TestAdapter(adapter.Adapter): - - client_name = 'testclient' - client_version = '4.5.6' - - sess = client_session.Session(app_name='ksatest', app_version='1.2.3') - adap = TestAdapter(session=sess) - - url = 'http://keystone.test.com' - self.requests_mock.get(url) - - adap.get(url) - - agent = 'ksatest/1.2.3 testclient/4.5.6' - self.assertEqual(agent + ' ' + client_session.DEFAULT_USER_AGENT, - self.requests_mock.last_request.headers['User-Agent']) - - def test_adapter_user_agent_session_adapter_no_app_version(self): - sess = client_session.Session(app_name='ksatest') - adap = adapter.Adapter(client_name='testclient', - client_version='4.5.6', - session=sess) - - url = 'http://keystone.test.com' - self.requests_mock.get(url) - - adap.get(url) - - agent = 'ksatest testclient/4.5.6' - self.assertEqual(agent + ' ' + client_session.DEFAULT_USER_AGENT, - self.requests_mock.last_request.headers['User-Agent']) - - def test_adapter_user_agent_session_adapter_no_client_version(self): - sess = client_session.Session(app_name='ksatest', app_version='1.2.3') - adap = adapter.Adapter(client_name='testclient', session=sess) - - url = 'http://keystone.test.com' - self.requests_mock.get(url) - - adap.get(url) - - agent = 'ksatest/1.2.3 testclient' - self.assertEqual(agent + ' ' + client_session.DEFAULT_USER_AGENT, - self.requests_mock.last_request.headers['User-Agent']) - - def test_adapter_user_agent_session_adapter_additional(self): - sess = client_session.Session(app_name='ksatest', - app_version='1.2.3', - additional_user_agent=[('one', '1.1.1'), - ('two', '2.2.2')]) - adap = adapter.Adapter(client_name='testclient', - client_version='4.5.6', - session=sess) - - url = 'http://keystone.test.com' - self.requests_mock.get(url) - - adap.get(url) - - agent = 'ksatest/1.2.3 one/1.1.1 two/2.2.2 testclient/4.5.6' - self.assertEqual(agent + ' ' + client_session.DEFAULT_USER_AGENT, - self.requests_mock.last_request.headers['User-Agent']) - - def test_adapter_user_agent_session(self): - sess = client_session.Session(app_name='ksatest', app_version='1.2.3') - adap = adapter.Adapter(session=sess) - - url = 'http://keystone.test.com' - self.requests_mock.get(url) - - adap.get(url) - - agent = 'ksatest/1.2.3' - self.assertEqual(agent + ' ' + client_session.DEFAULT_USER_AGENT, - self.requests_mock.last_request.headers['User-Agent']) - - def test_adapter_user_agent_adapter(self): - sess = client_session.Session() - adap = adapter.Adapter(client_name='testclient', - client_version='4.5.6', - session=sess) - - url = 'http://keystone.test.com' - self.requests_mock.get(url) - - adap.get(url) - - agent = 'testclient/4.5.6' - self.assertEqual(agent + ' ' + client_session.DEFAULT_USER_AGENT, - self.requests_mock.last_request.headers['User-Agent']) - - def test_adapter_user_agent_session_override(self): - sess = client_session.Session(app_name='ksatest', - app_version='1.2.3', - additional_user_agent=[('one', '1.1.1'), - ('two', '2.2.2')]) - adap = adapter.Adapter(client_name='testclient', - client_version='4.5.6', - session=sess) - - url = 'http://keystone.test.com' - self.requests_mock.get(url) - - override_user_agent = '%s/%s' % (uuid.uuid4().hex, uuid.uuid4().hex) - adap.get(url, user_agent=override_user_agent) - - self.assertEqual(override_user_agent, - self.requests_mock.last_request.headers['User-Agent']) - - def test_nested_adapters(self): - text = uuid.uuid4().hex - token = uuid.uuid4().hex - url = 'http://keystone.example.com/path' - - sess = client_session.Session() - auth = CalledAuthPlugin() - auth.ENDPOINT = url - auth.TOKEN = token - - adap1 = adapter.Adapter(session=sess, - interface='public') - adap2 = adapter.Adapter(session=adap1, - service_type='identity', - auth=auth) - - self.requests_mock.get(url + '/test', text=text) - - resp = adap2.get('/test') - - self.assertEqual(text, resp.text) - self.assertTrue(auth.get_endpoint_called) - - self.assertEqual('public', auth.endpoint_arguments['interface']) - self.assertEqual('identity', auth.endpoint_arguments['service_type']) - - last_token = self.requests_mock.last_request.headers['X-Auth-Token'] - self.assertEqual(token, last_token) - - def test_default_microversion(self): - sess = client_session.Session() - url = 'http://url' - - def validate(adap_kwargs, get_kwargs, exp_kwargs): - with mock.patch.object(sess, 'request') as m: - adapter.Adapter(sess, **adap_kwargs).get(url, **get_kwargs) - m.assert_called_once_with(url, 'GET', endpoint_filter={}, - **exp_kwargs) - - # No default_microversion in Adapter, no microversion in get() - validate({}, {}, {}) - - # default_microversion in Adapter, no microversion in get() - validate({'default_microversion': '1.2'}, {}, {'microversion': '1.2'}) - - # No default_microversion in Adapter, microversion specified in get() - validate({}, {'microversion': '1.2'}, {'microversion': '1.2'}) - - # microversion in get() overrides default_microversion in Adapter - validate({'default_microversion': '1.2'}, {'microversion': '1.5'}, - {'microversion': '1.5'}) - - -class TCPKeepAliveAdapterTest(utils.TestCase): - - def setUp(self): - super(TCPKeepAliveAdapterTest, self).setUp() - self.init_poolmanager = self.patch( - client_session.requests.adapters.HTTPAdapter, - 'init_poolmanager') - self.constructor = self.patch( - client_session.TCPKeepAliveAdapter, '__init__', lambda self: None) - - def test_init_poolmanager_with_requests_lesser_than_2_4_1(self): - self.patch(client_session, 'REQUESTS_VERSION', (2, 4, 0)) - given_adapter = client_session.TCPKeepAliveAdapter() - - # when pool manager is initialized - given_adapter.init_poolmanager(1, 2, 3) - - # then no socket_options are given - self.init_poolmanager.assert_called_once_with(1, 2, 3) - - def test_init_poolmanager_with_basic_options(self): - self.patch(client_session, 'REQUESTS_VERSION', (2, 4, 1)) - socket = self.patch_socket_with_options( - ['IPPROTO_TCP', 'TCP_NODELAY', 'SOL_SOCKET', 'SO_KEEPALIVE']) - given_adapter = client_session.TCPKeepAliveAdapter() - - # when pool manager is initialized - given_adapter.init_poolmanager(1, 2, 3) - - # then no socket_options are given - self.init_poolmanager.assert_called_once_with( - 1, 2, 3, socket_options=[ - (socket.IPPROTO_TCP, socket.TCP_NODELAY, 1), - (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)]) - - def test_init_poolmanager_with_tcp_keepidle(self): - self.patch(client_session, 'REQUESTS_VERSION', (2, 4, 1)) - socket = self.patch_socket_with_options( - ['IPPROTO_TCP', 'TCP_NODELAY', 'SOL_SOCKET', 'SO_KEEPALIVE', - 'TCP_KEEPIDLE']) - given_adapter = client_session.TCPKeepAliveAdapter() - - # when pool manager is initialized - given_adapter.init_poolmanager(1, 2, 3) - - # then socket_options are given - self.init_poolmanager.assert_called_once_with( - 1, 2, 3, socket_options=[ - (socket.IPPROTO_TCP, socket.TCP_NODELAY, 1), - (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1), - (socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 60)]) - - def test_init_poolmanager_with_tcp_keepcnt(self): - self.patch(client_session, 'REQUESTS_VERSION', (2, 4, 1)) - self.patch(client_session.utils, 'is_windows_linux_subsystem', False) - socket = self.patch_socket_with_options( - ['IPPROTO_TCP', 'TCP_NODELAY', 'SOL_SOCKET', 'SO_KEEPALIVE', - 'TCP_KEEPCNT']) - given_adapter = client_session.TCPKeepAliveAdapter() - - # when pool manager is initialized - given_adapter.init_poolmanager(1, 2, 3) - - # then socket_options are given - self.init_poolmanager.assert_called_once_with( - 1, 2, 3, socket_options=[ - (socket.IPPROTO_TCP, socket.TCP_NODELAY, 1), - (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1), - (socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 4)]) - - def test_init_poolmanager_with_tcp_keepcnt_on_windows(self): - self.patch(client_session, 'REQUESTS_VERSION', (2, 4, 1)) - self.patch(client_session.utils, 'is_windows_linux_subsystem', True) - socket = self.patch_socket_with_options( - ['IPPROTO_TCP', 'TCP_NODELAY', 'SOL_SOCKET', 'SO_KEEPALIVE', - 'TCP_KEEPCNT']) - given_adapter = client_session.TCPKeepAliveAdapter() - - # when pool manager is initialized - given_adapter.init_poolmanager(1, 2, 3) - - # then socket_options are given - self.init_poolmanager.assert_called_once_with( - 1, 2, 3, socket_options=[ - (socket.IPPROTO_TCP, socket.TCP_NODELAY, 1), - (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)]) - - def test_init_poolmanager_with_tcp_keepintvl(self): - self.patch(client_session, 'REQUESTS_VERSION', (2, 4, 1)) - socket = self.patch_socket_with_options( - ['IPPROTO_TCP', 'TCP_NODELAY', 'SOL_SOCKET', 'SO_KEEPALIVE', - 'TCP_KEEPINTVL']) - given_adapter = client_session.TCPKeepAliveAdapter() - - # when pool manager is initialized - given_adapter.init_poolmanager(1, 2, 3) - - # then socket_options are given - self.init_poolmanager.assert_called_once_with( - 1, 2, 3, socket_options=[ - (socket.IPPROTO_TCP, socket.TCP_NODELAY, 1), - (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1), - (socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 15)]) - - def test_init_poolmanager_with_given_optionsl(self): - self.patch(client_session, 'REQUESTS_VERSION', (2, 4, 1)) - given_adapter = client_session.TCPKeepAliveAdapter() - given_options = object() - - # when pool manager is initialized - given_adapter.init_poolmanager(1, 2, 3, socket_options=given_options) - - # then socket_options are given - self.init_poolmanager.assert_called_once_with( - 1, 2, 3, socket_options=given_options) - - def patch_socket_with_options(self, option_names): - # to mock socket module with exactly the attributes I want I create - # a class with that attributes - socket = type('socket', (object,), - {name: 'socket.' + name for name in option_names}) - return self.patch(client_session, 'socket', socket) - - def patch(self, target, name, *args, **kwargs): - context = mock.patch.object(target, name, *args, **kwargs) - patch = context.start() - self.addCleanup(context.stop) - return patch diff --git a/keystoneauth1/tests/unit/test_token_endpoint.py b/keystoneauth1/tests/unit/test_token_endpoint.py deleted file mode 100644 index ff28c05..0000000 --- a/keystoneauth1/tests/unit/test_token_endpoint.py +++ /dev/null @@ -1,76 +0,0 @@ -# Licensed under the Apache License, Version 2.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 testtools import matchers - -from keystoneauth1.loading._plugins import admin_token as loader -from keystoneauth1 import session -from keystoneauth1.tests.unit import utils -from keystoneauth1 import token_endpoint - - -class TokenEndpointTest(utils.TestCase): - - TEST_TOKEN = 'aToken' - TEST_URL = 'http://server/prefix' - - def test_basic_case(self): - self.requests_mock.get(self.TEST_URL, text='body') - - a = token_endpoint.Token(self.TEST_URL, self.TEST_TOKEN) - s = session.Session(auth=a) - - data = s.get(self.TEST_URL, authenticated=True) - - self.assertEqual(data.text, 'body') - self.assertRequestHeaderEqual('X-Auth-Token', self.TEST_TOKEN) - - def test_basic_endpoint_case(self): - self.stub_url('GET', ['p'], text='body') - a = token_endpoint.Token(self.TEST_URL, self.TEST_TOKEN) - s = session.Session(auth=a) - - data = s.get('/p', - authenticated=True, - endpoint_filter={'service': 'identity'}) - - self.assertEqual(self.TEST_URL, a.get_endpoint(s)) - self.assertEqual('body', data.text) - self.assertRequestHeaderEqual('X-Auth-Token', self.TEST_TOKEN) - - def test_token_endpoint_user_id(self): - a = token_endpoint.Token(self.TEST_URL, self.TEST_TOKEN) - s = session.Session() - - # we can't know this information about this sort of plugin - self.assertIsNone(a.get_user_id(s)) - self.assertIsNone(a.get_project_id(s)) - - -class AdminTokenTest(utils.TestCase): - - def test_token_endpoint_options(self): - opt_names = [opt.name for opt in loader.AdminToken().get_options()] - - self.assertThat(opt_names, matchers.HasLength(2)) - - self.assertIn('token', opt_names) - self.assertIn('endpoint', opt_names) - - def test_token_endpoint_deprecated_options(self): - endpoint_opt = [ - opt for opt in loader.AdminToken().get_options() - if opt.name == 'endpoint'][0] - - opt_names = [opt.name for opt in endpoint_opt.deprecated] - - self.assertEqual(['url'], opt_names) diff --git a/keystoneauth1/tests/unit/test_utils.py b/keystoneauth1/tests/unit/test_utils.py deleted file mode 100644 index 8ec9edc..0000000 --- a/keystoneauth1/tests/unit/test_utils.py +++ /dev/null @@ -1,22 +0,0 @@ -# Licensed under the Apache License, Version 2.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 testtools - -from keystoneauth1 import _utils - - -class UtilsTests(testtools.TestCase): - - def test_get_logger(self): - self.assertEqual('keystoneauth.tests.unit.test_utils', - _utils.get_logger(__name__).name) diff --git a/keystoneauth1/tests/unit/utils.py b/keystoneauth1/tests/unit/utils.py deleted file mode 100644 index 4a77b16..0000000 --- a/keystoneauth1/tests/unit/utils.py +++ /dev/null @@ -1,153 +0,0 @@ -# Licensed under the Apache License, Version 2.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 as jsonutils -import logging -import time -import uuid - -import fixtures -import requests -from requests_mock.contrib import fixture -from six.moves.urllib import parse as urlparse -import testtools - - -class TestCase(testtools.TestCase): - - TEST_DOMAIN_ID = uuid.uuid4().hex - TEST_DOMAIN_NAME = uuid.uuid4().hex - TEST_GROUP_ID = uuid.uuid4().hex - TEST_ROLE_ID = uuid.uuid4().hex - TEST_TENANT_ID = uuid.uuid4().hex - TEST_TENANT_NAME = uuid.uuid4().hex - TEST_TOKEN = uuid.uuid4().hex - TEST_TRUST_ID = uuid.uuid4().hex - TEST_USER = uuid.uuid4().hex - TEST_USER_ID = uuid.uuid4().hex - - TEST_ROOT_URL = 'http://127.0.0.1:5000/' - - def setUp(self): - super(TestCase, self).setUp() - self.logger = self.useFixture(fixtures.FakeLogger(level=logging.DEBUG)) - - fixtures.MockPatchObject(time, 'time', lambda: 1234) - - self.requests_mock = self.useFixture(fixture.Fixture()) - - def stub_url(self, method, parts=None, base_url=None, json=None, **kwargs): - if not base_url: - base_url = self.TEST_URL - - if json: - kwargs['text'] = jsonutils.dumps(json) - headers = kwargs.setdefault('headers', {}) - headers.setdefault('Content-Type', 'application/json') - - if parts: - url = '/'.join([p.strip('/') for p in [base_url] + parts]) - else: - url = base_url - - url = url.replace("/?", "?") - return self.requests_mock.register_uri(method, url, **kwargs) - - def assertRequestBodyIs(self, body=None, json=None): - last_request_body = self.requests_mock.last_request.body - if json: - val = jsonutils.loads(last_request_body) - self.assertEqual(json, val) - elif body: - self.assertEqual(body, last_request_body) - - def assertContentTypeIs(self, content_type): - last_request = self.requests_mock.last_request - self.assertEqual(last_request.headers['Content-Type'], content_type) - - def assertQueryStringIs(self, qs=''): - r"""Verify the QueryString matches what is expected. - - The qs parameter should be of the format \'foo=bar&abc=xyz\' - """ - expected = urlparse.parse_qs(qs, keep_blank_values=True) - parts = urlparse.urlparse(self.requests_mock.last_request.url) - querystring = urlparse.parse_qs(parts.query, keep_blank_values=True) - self.assertEqual(expected, querystring) - - def assertQueryStringContains(self, **kwargs): - """Verify the query string contains the expected parameters. - - This method is used to verify that the query string for the most recent - request made contains all the parameters provided as ``kwargs``, and - that the value of each parameter contains the value for the kwarg. If - the value for the kwarg is an empty string (''), then all that's - verified is that the parameter is present. - - """ - parts = urlparse.urlparse(self.requests_mock.last_request.url) - qs = urlparse.parse_qs(parts.query, keep_blank_values=True) - - for k, v in kwargs.items(): - self.assertIn(k, qs) - self.assertIn(v, qs[k]) - - def assertRequestHeaderEqual(self, name, val): - """Verify that the last request made contains a header and its value. - - The request must have already been made. - """ - headers = self.requests_mock.last_request.headers - self.assertEqual(headers.get(name), val) - - def assertRequestNotInHeader(self, name): - """Verify that the last request made does not contain a header key. - - The request must have already been made. - """ - headers = self.requests_mock.last_request.headers - self.assertNotIn(name, headers) - - -class TestResponse(requests.Response): - """Class used to wrap requests.Response. - - This provides some convenience to initialize with a dict. - """ - - def __init__(self, data): - self._text = None - super(TestResponse, self).__init__() - if isinstance(data, dict): - self.status_code = data.get('status_code', 200) - headers = data.get('headers') - if headers: - self.headers.update(headers) - # Fake the text attribute to streamline Response creation - # _content is defined by requests.Response - self._content = data.get('text') - else: - self.status_code = data - - def __eq__(self, other): - """Define equiality behavior of request and response.""" - return self.__dict__ == other.__dict__ - - # NOTE: This function is only needed by Python 2. If we get to point where - # we don't support Python 2 anymore, this function should be removed. - def __ne__(self, other): - """Define inequiality behavior of request and response.""" - return not self.__eq__(other) - - @property - def text(self): - return self.content diff --git a/keystoneauth1/token_endpoint.py b/keystoneauth1/token_endpoint.py deleted file mode 100644 index 675d4c1..0000000 --- a/keystoneauth1/token_endpoint.py +++ /dev/null @@ -1,48 +0,0 @@ -# Licensed under the Apache License, Version 2.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 keystoneauth1 import plugin - - -class Token(plugin.BaseAuthPlugin): - """A provider that will always use the given token and endpoint. - - This is really only useful for testing and in certain CLI cases where you - have a known endpoint and admin token that you want to use. - """ - - def __init__(self, endpoint, token): - # NOTE(jamielennox): endpoint is reserved for when plugins - # can be used to provide that information - self.endpoint = endpoint - self.token = token - - def get_token(self, session): - return self.token - - def get_endpoint(self, session, **kwargs): - """Return the supplied endpoint. - - Using this plugin the same endpoint is returned regardless of the - parameters passed to the plugin. - """ - return self.endpoint - - def get_auth_ref(self, session, **kwargs): - """Return the authentication reference of an auth plugin. - - :param session: A session object to be used for communication - :type session: keystoneauth1.session.session - """ - # token plugin does not have an auth ref, because it's a - # "static" authentication using a pre-existing token. - return None diff --git a/releasenotes/notes/.placeholder b/releasenotes/notes/.placeholder deleted file mode 100644 index e69de29..0000000 diff --git a/releasenotes/notes/1583780-700f99713e06324e.yaml b/releasenotes/notes/1583780-700f99713e06324e.yaml deleted file mode 100644 index 343392c..0000000 --- a/releasenotes/notes/1583780-700f99713e06324e.yaml +++ /dev/null @@ -1,9 +0,0 @@ ---- -features: - - Added a new OidcAccessToken plugin, accessible via the 'v3oidcaccesstoken' - entry point, making possible to authenticate using an existing OpenID - Connect Access token. -fixes: - - > - [`bug 1583780 `_] - OpenID connect support should include authenticating using directly an access token. diff --git a/releasenotes/notes/add-oidc-client-credentials-2be065926ba4b849.yaml b/releasenotes/notes/add-oidc-client-credentials-2be065926ba4b849.yaml deleted file mode 100644 index a5dadd7..0000000 --- a/releasenotes/notes/add-oidc-client-credentials-2be065926ba4b849.yaml +++ /dev/null @@ -1,4 +0,0 @@ ---- -features: - - Add support for the Client Credentials OpenID Connect - grant type. diff --git a/releasenotes/notes/add-oidc-discovery-document-support-b07fe54f83286d62.yaml b/releasenotes/notes/add-oidc-discovery-document-support-b07fe54f83286d62.yaml deleted file mode 100644 index 80b4bc7..0000000 --- a/releasenotes/notes/add-oidc-discovery-document-support-b07fe54f83286d62.yaml +++ /dev/null @@ -1,12 +0,0 @@ ---- -features: - - > - Add support for the `OpenID Connect Discovery Document - `_ - into the OpenID Connect related plugins. Now it is possible to only pass the - `discovery-url` option and the plugins will try to fetch the required - metadata from there. -fixes: - - > - [`bug 1583682 `_] - OpenID Connect plugins should support OpenID Connect Discovery. diff --git a/releasenotes/notes/add-prompt-to-opt-d083acc357a7f07b.yaml b/releasenotes/notes/add-prompt-to-opt-d083acc357a7f07b.yaml deleted file mode 100644 index 72bc114..0000000 --- a/releasenotes/notes/add-prompt-to-opt-d083acc357a7f07b.yaml +++ /dev/null @@ -1,10 +0,0 @@ ---- -prelude: > - Add the prompt parameter to loader Opts -features: - - The prompt parameter was added to the Opts provided by auth plugins. The - presence of the prompt parameter on an Option will indicate to plugin - loaders that it is ok to prompt the user for input for this parameter if - none is provided initially. Actual implementation of this prompting - mechanism will be handled by the individual loaders such as - os-client-config. diff --git a/releasenotes/notes/add-totp-auth-plugin-0650d220899c25b7.yaml b/releasenotes/notes/add-totp-auth-plugin-0650d220899c25b7.yaml deleted file mode 100644 index b96b46d..0000000 --- a/releasenotes/notes/add-totp-auth-plugin-0650d220899c25b7.yaml +++ /dev/null @@ -1,16 +0,0 @@ ---- -features: - - > - [`blueprint totp-auth `_] - Add an auth plugin to handle Time-Based One-Time Password (TOTP) - authentication via the ``totp`` method. This new plugin will accept the - following identity options: - - - ``user-id``: user ID - - ``username``: username - - ``user-domain-id``: user's domain ID - - ``user-domain-name``: user's domain name - - ``passcode``: passcode generated by TOTP app or device - - User is uniquely identified by either ``user-id`` or combination of - ``username`` and ``user-domain-id`` or ``user-domain-name``. diff --git a/releasenotes/notes/additional-headers-f2d16f85f5abe942.yaml b/releasenotes/notes/additional-headers-f2d16f85f5abe942.yaml deleted file mode 100644 index f18fa31..0000000 --- a/releasenotes/notes/additional-headers-f2d16f85f5abe942.yaml +++ /dev/null @@ -1,10 +0,0 @@ ---- -prelude: > - Allow specifying additional_headers to the session and the adapter to add - headers to all requests that pass through these objects. -features: - - Add the ability to provide additional_headers to the session and adapter - object. This will allow clients particularly to provide additional ways to - identify their requests. It will also hopefully provide an intermediate way - to handle setting microversions until we support them directly with - keystoneauth. diff --git a/releasenotes/notes/allow_version_hack-flag-9b53b72d9b084c04.yaml b/releasenotes/notes/allow_version_hack-flag-9b53b72d9b084c04.yaml deleted file mode 100644 index 6a5f73d..0000000 --- a/releasenotes/notes/allow_version_hack-flag-9b53b72d9b084c04.yaml +++ /dev/null @@ -1,9 +0,0 @@ ---- -features: - - A new flag `allow_version_hack` was added to identity plugins and the - adapter which will allow a client to opt out of making guesses at the - version url page of a service. This means that if a deployment is - misconfigured and the service catalog contains a versioned endpoint that - does not match the requested version the request will fail. This will be - useful in beginning to require correctly deployed catalogs rather than - continue to hide the problem. diff --git a/releasenotes/notes/bug-1582774-49af731b6dfc6f2f.yaml b/releasenotes/notes/bug-1582774-49af731b6dfc6f2f.yaml deleted file mode 100644 index d58ca63..0000000 --- a/releasenotes/notes/bug-1582774-49af731b6dfc6f2f.yaml +++ /dev/null @@ -1,4 +0,0 @@ ---- -fixes: - - Fix passing scope parameters in Oidc* auth plugins. - [Bug `1582774 `_] diff --git a/releasenotes/notes/bug-1614688-c4a1bd54f4ba5644.yaml b/releasenotes/notes/bug-1614688-c4a1bd54f4ba5644.yaml deleted file mode 100644 index 2883c69..0000000 --- a/releasenotes/notes/bug-1614688-c4a1bd54f4ba5644.yaml +++ /dev/null @@ -1,9 +0,0 @@ ---- -prelude: > - HTTP connections work under Windows Subsystem for Linux -fixes: - - > - [`bug 1614688 `_] - HTTP connections were failing under Windows subsystem for Linux because - TCP_KEEPCNT was being set and that environment does not support such - override yet. diff --git a/releasenotes/notes/bug-1616105-cc8b85eb056e99e2.yaml b/releasenotes/notes/bug-1616105-cc8b85eb056e99e2.yaml deleted file mode 100644 index e9c1c9c..0000000 --- a/releasenotes/notes/bug-1616105-cc8b85eb056e99e2.yaml +++ /dev/null @@ -1,8 +0,0 @@ ---- -fixes: - - > - [`bug 1616105 `_] - Only log the response body when the ``Content-Type`` header is set to - ``application/json``. This avoids logging large binary objects (such as - images). Other ``Content-Type`` will not be logged. Additional - ``Content-Type`` strings can be added as required. diff --git a/releasenotes/notes/bug-1654847-acdf9543158329ec.yaml b/releasenotes/notes/bug-1654847-acdf9543158329ec.yaml deleted file mode 100644 index 5d066e9..0000000 --- a/releasenotes/notes/bug-1654847-acdf9543158329ec.yaml +++ /dev/null @@ -1,5 +0,0 @@ ---- -fixes: - - | - The ``X-Service-Token`` header value is now properly masked, and is - displayed as a hash value, in the log. diff --git a/releasenotes/notes/bug-1689424-set-adfspassword-endpointreference-f186d84a54007b09.yaml b/releasenotes/notes/bug-1689424-set-adfspassword-endpointreference-f186d84a54007b09.yaml deleted file mode 100644 index 1df886b..0000000 --- a/releasenotes/notes/bug-1689424-set-adfspassword-endpointreference-f186d84a54007b09.yaml +++ /dev/null @@ -1,13 +0,0 @@ ---- -prelude: > - Allow setting EndpointReference in ADFSPassword -features: - - > - Add the ability to specify the WS-Policy EndpointReference used in the - ADFSPassword plugin's RequestSecurityToken message via the - 'service-provider-entity-id' option. Also added 'identity-provider-url' - option which was required, but missing from option list. -fixes: - - > - [`bug 1689424 `_] - Allow setting EndpointReference in ADFSPassword. diff --git a/releasenotes/notes/ksa_2.2.0-81145229d4b43043.yaml b/releasenotes/notes/ksa_2.2.0-81145229d4b43043.yaml deleted file mode 100644 index 36d6b43..0000000 --- a/releasenotes/notes/ksa_2.2.0-81145229d4b43043.yaml +++ /dev/null @@ -1,8 +0,0 @@ ---- -fixes: - - > - [`bug 1527131 `_] - Do not provide socket values for OSX and Windows. -other: - - Added a betamax fixture for keystoneauth sessions. - - Added a RFC 7231 compliant user agent string. diff --git a/releasenotes/notes/microversion-header-support-901acd820a21d788.yaml b/releasenotes/notes/microversion-header-support-901acd820a21d788.yaml deleted file mode 100644 index 3f841f3..0000000 --- a/releasenotes/notes/microversion-header-support-901acd820a21d788.yaml +++ /dev/null @@ -1,6 +0,0 @@ ---- -features: - - Added support for specifying a microversion to use on - a given REST request. The microversion can be specified - on session request calls and a default can be set on - Adapter construction. diff --git a/releasenotes/notes/support-api-wg-discovery-2cb4b0186619e124.yaml b/releasenotes/notes/support-api-wg-discovery-2cb4b0186619e124.yaml deleted file mode 100644 index 2c4bc54..0000000 --- a/releasenotes/notes/support-api-wg-discovery-2cb4b0186619e124.yaml +++ /dev/null @@ -1,10 +0,0 @@ ---- -features: - - Added support for the API Working Group recommendations - on service and version discovery. New methods on - Session and Adapter, "get_endpoint_data" will return - endpoint metadata including microversion information. - Additionally, versions can be requested with a range - and with the string "latest", and interface values - can be given as a list in case a user wants to express - a 'best available' set of preferences. diff --git a/releasenotes/notes/user-agent-generation-b069100508c06177.yaml b/releasenotes/notes/user-agent-generation-b069100508c06177.yaml deleted file mode 100644 index 7e1a443..0000000 --- a/releasenotes/notes/user-agent-generation-b069100508c06177.yaml +++ /dev/null @@ -1,17 +0,0 @@ ---- -prelude: > - Allow adding client and application name and version to the session and - adapter that will generate a userful user agent string. -features: - - You can specify a ``app_name`` and ``app_version`` when creating a - session. This information will be encoded into the user agent. - - You can specify a ``client_name`` and ``client_version`` when creating an - adapter. This will be handled by client libraries and incluced into the user - agent. - - Libraries like shade that modify the way requests are made can add - themselves to additional_user_agent and have their version reflected in the - user agent string. -deprecations: - - We suggest you fill the name and version for the application and client - instead of specifying a custom ``user_agent``. This will then generate a - standard user agent string. diff --git a/releasenotes/source/_static/.placeholder b/releasenotes/source/_static/.placeholder deleted file mode 100644 index e69de29..0000000 diff --git a/releasenotes/source/_templates/.placeholder b/releasenotes/source/_templates/.placeholder deleted file mode 100644 index e69de29..0000000 diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py deleted file mode 100644 index 77cd260..0000000 --- a/releasenotes/source/conf.py +++ /dev/null @@ -1,286 +0,0 @@ -# -*- coding: utf-8 -*- -# Licensed under the Apache License, Version 2.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. - -# keystoneauth Release Notes documentation build configuration file, created -# by sphinx-quickstart on Tue Nov 3 17:40:50 2015. -# -# 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. -# 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 = [ - 'openstackdocstheme', - 'reno.sphinxext', -] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -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 = u'keystoneauth Release Notes' -copyright = u'2015, Keystone Developers' - -# 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. -import pbr.version -keystone_version = pbr.version.VersionInfo('keystoneauth') -# The full version, including alpha/beta/rc tags. -release = keystone_version.version_string_with_vcs() -# The short X.Y version. -version = keystone_version.canonical_version_string() - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# 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. -exclude_patterns = [] - -# 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 - - -# -- 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 = 'openstackdocs' - -# 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. If None, it defaults to -# " v documentation". -# html_title = None - -# 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 (within the static path) to use as 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 '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -# html_last_updated_fmt = '%b %d, %Y' -html_last_updated_fmt = '%Y-%m-%d %H:%M' - -# 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 - -# Output file base name for HTML help builder. -htmlhelp_basename = 'keystoneauthReleaseNotesdoc' - - -# -- 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': '', -} - -# 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 = [ - ('index', 'keystoneauthReleaseNotes.tex', - u'keystoneauth Release Notes Documentation', - u'Keystone Developers', '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 = [] - -# 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 = [ - ('index', 'keystoneauthreleasenotes', - u'keystoneauth Release Notes Documentation', - [u'Keystone Developers'], 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 = [ - ('index', 'keystoneauthReleaseNotes', - u'keystoneauth Release Notes Documentation', - u'Keystone Developers', 'keystoneauthReleaseNotes', - 'Authentication plugins for the OpenStack Identity service.', - '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 - -# -- Options for Internationalization output ------------------------------ -locale_dirs = ['locale/'] - -# -- Options for openstackdocstheme ------------------------------------------- -repository_name = 'openstack/keystoneauth' -bug_project = 'keystoneauth' -bug_tag = 'doc' diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst deleted file mode 100644 index ad95286..0000000 --- a/releasenotes/source/index.rst +++ /dev/null @@ -1,11 +0,0 @@ -============================ - keystoneauth Release Notes -============================ - -.. toctree:: - :maxdepth: 1 - - unreleased - ocata - newton - mitaka diff --git a/releasenotes/source/mitaka.rst b/releasenotes/source/mitaka.rst deleted file mode 100644 index e545609..0000000 --- a/releasenotes/source/mitaka.rst +++ /dev/null @@ -1,6 +0,0 @@ -=================================== - Mitaka Series Release Notes -=================================== - -.. release-notes:: - :branch: origin/stable/mitaka diff --git a/releasenotes/source/newton.rst b/releasenotes/source/newton.rst deleted file mode 100644 index 7b7d735..0000000 --- a/releasenotes/source/newton.rst +++ /dev/null @@ -1,6 +0,0 @@ -============================= - Newton Series Release Notes -============================= - -.. release-notes:: - :branch: origin/stable/newton diff --git a/releasenotes/source/ocata.rst b/releasenotes/source/ocata.rst deleted file mode 100644 index 9515f6c..0000000 --- a/releasenotes/source/ocata.rst +++ /dev/null @@ -1,6 +0,0 @@ -============================ - Ocata Series Release Notes -============================ - -.. release-notes:: - :branch: origin/stable/ocata diff --git a/releasenotes/source/unreleased.rst b/releasenotes/source/unreleased.rst deleted file mode 100644 index cd22aab..0000000 --- a/releasenotes/source/unreleased.rst +++ /dev/null @@ -1,5 +0,0 @@ -============================== - Current Series Release Notes -============================== - -.. release-notes:: diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 2e3b9c5..0000000 --- a/requirements.txt +++ /dev/null @@ -1,19 +0,0 @@ -# The order of packages is significant, because pip processes them in the order -# of appearance. Changing the order has an impact on the overall integration -# process, which may cause wedges in the gate later. - -# All additions to this file must have significant justification. - -# NOTE(morgan) At no time may any olso library be added to the keystoneauth -# requirements. The requirements for keystoneauth are very tightly controlled -# to ensure we are not pulling in a ton of transient dependencies. This is -# important from the standpoint of ensuring keystoneauth can be used outside -# of openstack-specific projects (allowing interaction with openstack APIs) -# where oslo and associated transient dependencies are not desired. - -pbr!=2.1.0,>=2.0.0 # Apache-2.0 -iso8601>=0.1.11 # MIT -positional>=1.1.1 # Apache-2.0 -requests>=2.14.2 # Apache-2.0 -six>=1.9.0 # MIT -stevedore>=1.20.0 # Apache-2.0 diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 28c8978..0000000 --- a/setup.cfg +++ /dev/null @@ -1,74 +0,0 @@ -[metadata] -name = keystoneauth1 -summary = Authentication Library for OpenStack Identity -description-file = - README.rst -author = OpenStack -author-email = openstack-dev@lists.openstack.org -home-page = https://docs.openstack.org/keystoneauth/latest/ -classifier = - Environment :: OpenStack - Intended Audience :: Information Technology - Intended Audience :: System Administrators - License :: OSI Approved :: Apache Software License - Operating System :: POSIX :: Linux - Programming Language :: Python - Programming Language :: Python :: 2 - Programming Language :: Python :: 2.7 - Programming Language :: Python :: 3 - Programming Language :: Python :: 3.5 - -[files] -packages = - keystoneauth1 - -[extras] -kerberos = - requests-kerberos>=0.6 # ISC -saml2 = - lxml!=3.7.0,>=2.3 # BSD -oauth1 = - oauthlib>=0.6 # BSD -betamax = - betamax>=0.7.0 # Apache-2.0 - fixtures>=3.0.0 # Apache-2.0/BSD - mock>=2.0 # BSD - -[entry_points] - -keystoneauth1.plugin = - none = keystoneauth1.loading._plugins.noauth:NoAuth - password = keystoneauth1.loading._plugins.identity.generic:Password - token = keystoneauth1.loading._plugins.identity.generic:Token - admin_token = keystoneauth1.loading._plugins.admin_token:AdminToken - v2password = keystoneauth1.loading._plugins.identity.v2:Password - v2token = keystoneauth1.loading._plugins.identity.v2:Token - v3password = keystoneauth1.loading._plugins.identity.v3:Password - v3token = keystoneauth1.loading._plugins.identity.v3:Token - v3oidcclientcredentials = keystoneauth1.loading._plugins.identity.v3:OpenIDConnectClientCredentials - v3oidcpassword = keystoneauth1.loading._plugins.identity.v3:OpenIDConnectPassword - v3oidcauthcode = keystoneauth1.loading._plugins.identity.v3:OpenIDConnectAuthorizationCode - v3oidcaccesstoken = keystoneauth1.loading._plugins.identity.v3:OpenIDConnectAccessToken - v3oauth1 = keystoneauth1.extras.oauth1._loading:V3OAuth1 - v3kerberos = keystoneauth1.extras.kerberos._loading:Kerberos - v3totp = keystoneauth1.loading._plugins.identity.v3:TOTP - v3fedkerb = keystoneauth1.extras.kerberos._loading:MappedKerberos - v3tokenlessauth = keystoneauth1.loading._plugins.identity.v3:TokenlessAuth - v3adfspassword = keystoneauth1.extras._saml2._loading:ADFSPassword - v3samlpassword = keystoneauth1.extras._saml2._loading:Saml2Password - -[build_sphinx] -source-dir = doc/source -build-dir = doc/build -all_files = 1 -warning-is-error = 1 - -[pbr] -autodoc_tree_index_modules = True -autodoc_tree_excludes = setup.py - -[upload_sphinx] -upload-dir = doc/build/html - -[wheel] -universal = 1 diff --git a/setup.py b/setup.py deleted file mode 100644 index 566d844..0000000 --- a/setup.py +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. -# -# Licensed under the Apache License, Version 2.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 MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT -import setuptools - -# In python < 2.7.4, a lazy loading of package `pbr` will break -# setuptools if some other modules registered functions in `atexit`. -# solution from: http://bugs.python.org/issue15881#msg170215 -try: - import multiprocessing # noqa -except ImportError: - pass - -setuptools.setup( - setup_requires=['pbr>=2.0.0'], - pbr=True) diff --git a/test-requirements.txt b/test-requirements.txt deleted file mode 100644 index e78b494..0000000 --- a/test-requirements.txt +++ /dev/null @@ -1,24 +0,0 @@ -# The order of packages is significant, because pip processes them in the order -# of appearance. Changing the order has an impact on the overall integration -# process, which may cause wedges in the gate later. - -hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0 -flake8-docstrings==0.2.1.post1 # MIT - -bandit>=1.1.0 # Apache-2.0 -coverage!=4.4,>=4.0 # Apache-2.0 -fixtures>=3.0.0 # Apache-2.0/BSD -mock>=2.0 # BSD -oslo.config!=4.3.0,!=4.4.0,>=4.0.0 # Apache-2.0 -openstackdocstheme>=1.11.0 # Apache-2.0 -oslo.utils>=3.20.0 # Apache-2.0 -oslotest>=1.10.0 # Apache-2.0 -os-testr>=0.8.0 # Apache-2.0 -betamax>=0.7.0 # Apache-2.0 -reno!=2.3.1,>=1.8.0 # Apache-2.0 -requests-mock>=1.1 # Apache-2.0 -sphinx>=1.6.2 # BSD -testrepository>=0.0.18 # Apache-2.0/BSD -testresources>=0.2.4 # Apache-2.0/BSD -testtools>=1.4.0 # MIT -PyYAML>=3.10.0 # MIT diff --git a/tools/tox_install.sh b/tools/tox_install.sh deleted file mode 100755 index e61b63a..0000000 --- a/tools/tox_install.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env bash - -# Client constraint file contains this client version pin that is in conflict -# with installing the client from source. We should remove the version pin in -# the constraints file before applying it for from-source installation. - -CONSTRAINTS_FILE="$1" -shift 1 - -set -e - -# NOTE(tonyb): Place this in the tox enviroment's log dir so it will get -# published to logs.openstack.org for easy debugging. -localfile="$VIRTUAL_ENV/log/upper-constraints.txt" - -if [[ "$CONSTRAINTS_FILE" != http* ]]; then - CONSTRAINTS_FILE="file://$CONSTRAINTS_FILE" -fi -# NOTE(tonyb): need to add curl to bindep.txt if the project supports bindep -curl "$CONSTRAINTS_FILE" --insecure --progress-bar --output "$localfile" - -pip install -c"$localfile" openstack-requirements - -# This is the main purpose of the script: Allow local installation of -# the current repo. It is listed in constraints file and thus any -# install will be constrained and we need to unconstrain it. -edit-constraints "$localfile" -- "$CLIENT_NAME" - -pip install -c"$localfile" -U "$@" -exit $? diff --git a/tox.ini b/tox.ini deleted file mode 100644 index b51de02..0000000 --- a/tox.ini +++ /dev/null @@ -1,65 +0,0 @@ -[tox] -minversion = 2.0 -skipsdist = True -envlist = py35,py27,pep8,releasenotes - -[testenv] -usedevelop = True -install_command = {toxinidir}/tools/tox_install.sh {env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages} -setenv = VIRTUAL_ENV={envdir} - BRANCH_NAME=master - CLIENT_NAME=keystoneauth1 - OS_STDOUT_NOCAPTURE=False - OS_STDERR_NOCAPTURE=False - -deps = -r{toxinidir}/requirements.txt - -r{toxinidir}/test-requirements.txt - .[kerberos,saml2,betamax,oauth1] -commands = ostestr {posargs} -whitelist_externals = - bash - -[testenv:pep8] -commands = - flake8 - # Run security linter - # B110: except: pass - # B410: importing etree - bandit -r keystoneauth1 -x tests -s B110,B410 - -[testenv:bandit] -# NOTE(browne): This is required for the integration test job of the bandit -# project. Please do not remove. -commands = bandit -r keystoneauth1 -x tests -s B110,B410 - -[testenv:venv] -commands = {posargs} - -[testenv:cover] -commands = python setup.py testr --coverage --testr-args='{posargs}' - -[testenv:debug] -commands = oslo_debug_helper -t keystoneauth1/tests {posargs} - -[flake8] -# D100: Missing docstring in public module -# D101: Missing docstring in public class -# D102: Missing docstring in public method -# D103: Missing docstring in public function -# D104: Missing docstring in public package -# D203: 1 blank line required before class docstring (deprecated in pep257) -ignore = D100,D101,D102,D103,D104,D203 -show-source = True -exclude = .venv,.tox,dist,doc,*egg,build - -[testenv:docs] -commands= - bash -c "rm -rf doc/build" - bash -c "rm -rf doc/source/api" - python setup.py build_sphinx - -[testenv:releasenotes] -commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html - -[hacking] -local-check-factory = keystoneauth1.hacking.checks.factory